-
Notifications
You must be signed in to change notification settings - Fork 625
/
roa-effect-within-stocks.py
130 lines (105 loc) · 4.72 KB
/
roa-effect-within-stocks.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# https://quantpedia.com/strategies/roa-effect-within-stocks/
#
# The investment universe contains all stocks on NYSE and AMEX and Nasdaq with Sales greater than 10 million USD. Stocks are then sorted into
# two halves based on market capitalization. Each half is then divided into deciles based on Return on assets (ROA) calculated as quarterly
# earnings (Compustat quarterly item IBQ – income before extraordinary items) divided by one-quarter-lagged assets (item ATQ – total assets).
# The investor then goes long the top three deciles from each market capitalization group and goes short bottom three deciles. The strategy is
# rebalanced monthly, and stocks are equally weighted.
#
# QC implementation changes:
# - Instead of all listed stock, we select 500 most liquid stocks traded on NYSE, AMEX, or NASDAQ.
from AlgorithmImports import *
class ROAEffectWithinStocks(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
self.course_count = 500
self.long = []
self.short = []
self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(
self.DateRules.MonthEnd(self.symbol),
self.TimeRules.AfterMarketOpen(self.symbol),
self.Selection,
)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(5)
def CoarseSelectionFunction(self, coarse):
if not self.selection_flag:
return Universe.Unchanged
selected = sorted(
[
x
for x in coarse
if x.HasFundamentalData and x.Market == "usa" and x.Price > 5
],
key=lambda x: x.DollarVolume,
reverse=True,
)
return [x.Symbol for x in selected[: self.course_count]]
def FineSelectionFunction(self, fine):
fine = [
x
for x in fine
if x.MarketCap != 0
and x.ValuationRatios.SalesPerShare
* x.EarningReports.DilutedAverageShares.Value
> 10000000
and x.OperationRatios.ROA.ThreeMonths != 0
and (
(x.SecurityReference.ExchangeId == "NYS")
or (x.SecurityReference.ExchangeId == "NAS")
or (x.SecurityReference.ExchangeId == "ASE")
)
]
# Sorting by market cap.
sorted_by_market_cap = sorted(fine, key=lambda x: x.MarketCap, reverse=True)
half = int(len(sorted_by_market_cap) / 2)
top_mc = [x for x in sorted_by_market_cap[:half]]
bottom_mc = [x for x in sorted_by_market_cap[half:]]
if len(top_mc) >= 10 and len(bottom_mc) >= 10:
# Sorting by ROA.
sorted_top_by_roa = sorted(
top_mc, key=lambda x: (x.OperationRatios.ROA.Value), reverse=True
)
decile = int(len(sorted_top_by_roa) / 10)
long_top = [x.Symbol for x in sorted_top_by_roa[: decile * 3]]
short_top = [x.Symbol for x in sorted_top_by_roa[-(decile * 3) :]]
sorted_bottom_by_roa = sorted(
bottom_mc, key=lambda x: (x.OperationRatios.ROA.Value), reverse=True
)
decile = int(len(sorted_bottom_by_roa) / 10)
long_bottom = [x.Symbol for x in sorted_bottom_by_roa[: decile * 3]]
short_bottom = [x.Symbol for x in sorted_bottom_by_roa[-(decile * 3) :]]
self.long = long_top + long_bottom
self.short = short_top + short_bottom
return self.long + self.short
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution.
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
if symbol not in self.long + self.short:
self.Liquidate(symbol)
long_count = len(self.long)
short_count = len(self.short)
for symbol in self.long:
self.SetHoldings(symbol, 1 / long_count)
for symbol in self.short:
self.SetHoldings(symbol, -1 / short_count)
self.long.clear()
self.short.clear()
def Selection(self):
self.selection_flag = True
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))