import matplotlib matplotlib.use('Agg') 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(None,None,"output.png") if __name__ == "__main__": main(True)
いやー、深く考えれば考えるほど泥沼なんでしょうか。ついにシャープレシオはマイナスになりました。
0 件のコメント:
コメントを投稿