forked from gbeced/pyalgotrade
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmarket_timing.py
168 lines (139 loc) · 6.59 KB
/
market_timing.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
from pyalgotrade import strategy
from pyalgotrade import plotter
from pyalgotrade.tools import yahoofinance
from pyalgotrade.technical import ma
from pyalgotrade.technical import cumret
from pyalgotrade.stratanalyzer import sharpe
from pyalgotrade.stratanalyzer import returns
class MarketTiming(strategy.BacktestingStrategy):
def __init__(self, feed, instrumentsByClass, initialCash):
strategy.BacktestingStrategy.__init__(self, feed, initialCash)
self.setUseAdjustedValues(True)
self.__instrumentsByClass = instrumentsByClass
self.__rebalanceMonth = None
self.__sharesToBuy = {}
# Initialize indicators for each instrument.
self.__sma = {}
for assetClass in instrumentsByClass:
for instrument in instrumentsByClass[assetClass]:
priceDS = feed[instrument].getPriceDataSeries()
self.__sma[instrument] = ma.SMA(priceDS, 200)
def _shouldRebalance(self, dateTime):
return dateTime.month != self.__rebalanceMonth
def _getRank(self, instrument):
# If the price is below the SMA, then this instrument doesn't rank at
# all.
smas = self.__sma[instrument]
price = self.getLastPrice(instrument)
if len(smas) == 0 or smas[-1] is None or price < smas[-1]:
return None
# Rank based on 20 day returns.
ret = None
lookBack = 20
priceDS = self.getFeed()[instrument].getPriceDataSeries()
if len(priceDS) >= lookBack and smas[-1] is not None and smas[-1*lookBack] is not None:
ret = (priceDS[-1] - priceDS[-1*lookBack]) / float(priceDS[-1*lookBack])
return ret
def _getTopByClass(self, assetClass):
# Find the instrument with the highest rank.
ret = None
highestRank = None
for instrument in self.__instrumentsByClass[assetClass]:
rank = self._getRank(instrument)
if rank is not None and (highestRank is None or rank > highestRank):
highestRank = rank
ret = instrument
return ret
def _getTop(self):
ret = {}
for assetClass in self.__instrumentsByClass:
ret[assetClass] = self._getTopByClass(assetClass)
return ret
def _placePendingOrders(self):
remainingCash = self.getBroker().getCash() * 0.9 # Use less chash just in case price changes too much.
for instrument in self.__sharesToBuy:
orderSize = self.__sharesToBuy[instrument]
if orderSize > 0:
# Adjust the order size based on available cash.
lastPrice = self.getLastPrice(instrument)
cost = orderSize * lastPrice
while cost > remainingCash and orderSize > 0:
orderSize -= 1
cost = orderSize * lastPrice
if orderSize > 0:
remainingCash -= cost
assert(remainingCash >= 0)
if orderSize != 0:
self.info("Placing market order for %d %s shares" % (orderSize, instrument))
self.marketOrder(instrument, orderSize, goodTillCanceled=True)
self.__sharesToBuy[instrument] -= orderSize
def _logPosSize(self):
totalEquity = self.getBroker().getEquity()
positions = self.getBroker().getPositions()
for instrument in self.getBroker().getPositions():
posSize = positions[instrument] * self.getLastPrice(instrument) / totalEquity * 100
self.info("%s - %0.2f %%" % (instrument, posSize))
def _rebalance(self):
self.info("Rebalancing")
# Cancel all active/pending orders.
for order in self.getBroker().getActiveOrders():
self.getBroker().cancelOrder(order)
cashPerAssetClass = self.getBroker().getEquity() / float(len(self.__instrumentsByClass))
self.__sharesToBuy = {}
# Calculate which positions should be open during the next period.
topByClass = self._getTop()
for assetClass in topByClass:
instrument = topByClass[assetClass]
self.info("Best for class %s: %s" % (assetClass, instrument))
if instrument is not None:
lastPrice = self.getLastPrice(instrument)
cashForInstrument = cashPerAssetClass - self.getBroker().getShares(instrument) * lastPrice
# This may yield a negative value and we have to reduce this
# position.
self.__sharesToBuy[instrument] = int(cashForInstrument / lastPrice)
# Calculate which positions should be closed.
for instrument in self.getBroker().getPositions():
if instrument not in topByClass.values():
currentShares = self.getBroker().getShares(instrument)
assert(instrument not in self.__sharesToBuy)
self.__sharesToBuy[instrument] = currentShares * -1
def getSMA(self, instrument):
return self.__sma[instrument]
def onBars(self, bars):
currentDateTime = bars.getDateTime()
if self._shouldRebalance(currentDateTime):
self.__rebalanceMonth = currentDateTime.month
self._rebalance()
self._placePendingOrders()
def main(plot):
initialCash = 10000
instrumentsByClass = {
"US Stocks": ["VTI"],
"Foreign Stocks": ["VEU"],
"US 10 Year Government Bonds": ["IEF"],
"Real Estate": ["VNQ"],
"Commodities": ["DBC"],
}
# Download the bars.
instruments = ["SPY"]
for assetClass in instrumentsByClass:
instruments.extend(instrumentsByClass[assetClass])
feed = yahoofinance.build_feed(instruments, 2007, 2013, "data", skipErrors=True)
strat = MarketTiming(feed, instrumentsByClass, initialCash)
sharpeRatioAnalyzer = sharpe.SharpeRatio()
strat.attachAnalyzer(sharpeRatioAnalyzer)
returnsAnalyzer = returns.Returns()
strat.attachAnalyzer(returnsAnalyzer)
if plot:
plt = plotter.StrategyPlotter(strat, False, False, True)
plt.getOrCreateSubplot("cash").addCallback("Cash", lambda x: strat.getBroker().getCash())
# Plot strategy vs. SPY cumulative returns.
plt.getOrCreateSubplot("returns").addDataSeries("SPY", cumret.CumulativeReturn(feed["SPY"].getPriceDataSeries()))
plt.getOrCreateSubplot("returns").addDataSeries("Strategy", returnsAnalyzer.getCumulativeReturns())
strat.run()
print "Sharpe ratio: %.2f" % sharpeRatioAnalyzer.getSharpeRatio(0.05)
print "Returns: %.2f %%" % (returnsAnalyzer.getCumulativeReturns()[-1] * 100)
if plot:
plt.plot()
if __name__ == "__main__":
main(True)