This is my first algorithm attempt (and I'm still learning Python)....

The algo is actually inspired by www.etfreplay.com : on the second-last trading day of each month (because I want to trade on the last day of the month and need to take the Quantopian "lag" into account) I calculate the RS according to the formula RS = 0.5 * 62 day performance + 0.3 * 20 day performance + 0.2 * annualized 20 day volatility.

I did some optimization of the parameters using zipline - would be nice if we could do the same with Quantopian :)

Comments (and improvements?) welcome!

DaveG

Clone Algorithm

155

Loading...

There was an error loading this backtest.
Retry

Backtest from
to
with
initial capital

Cumulative performance:

Algorithm
Benchmark

Custom data:

Total Returns

--

Alpha

--

Beta

--

Sharpe

--

Sortino

--

Max Drawdown

--

Benchmark Returns

--

Volatility

--

Returns | 1 Month | 3 Month | 6 Month | 12 Month |

Alpha | 1 Month | 3 Month | 6 Month | 12 Month |

Beta | 1 Month | 3 Month | 6 Month | 12 Month |

Sharpe | 1 Month | 3 Month | 6 Month | 12 Month |

Sortino | 1 Month | 3 Month | 6 Month | 12 Month |

Volatility | 1 Month | 3 Month | 6 Month | 12 Month |

Max Drawdown | 1 Month | 3 Month | 6 Month | 12 Month |

from zipline.utils import tradingcalendar import pytz import datetime as dt from math import sqrt import pandas window = 63 def lookup(sid) : symbol = {'Security(2174)':'DIA','Security(8554)':'SPY','Security(32012)':'IHE','Security(32290)':'FBT', 'Security(25903)':'VDC','Security(12915)':'MDY','Security(33370)':'UUP','Security(23911)':'SHY', 'Security(22972)':'EFA','Security(14517)':'EWC','Security(18387)':'BND','Security(23870)':'IEF', 'Security(26669)':'VNQ','Security(33080)':'INP','Security(28054)':'DBC','Security(27102)':'VWO', 'Security(32616)':'EEB','Security(32266)':'MYY','Security(23921)':'TLT','Security(23134)':'ILF', 'Security(23118)':'EPP','Security(21757)':'EWZ','Security(33334)':'FXY','Security(26703)':'FXI'} return symbol[str(sid)] def get_trading_dates(start, end, offset=0): trading_dates = list([]) trading_days= tradingcalendar.get_trading_days(start, end) month = trading_days[0].month for i in range(len(trading_days)) : if trading_days[i].month != month : try : trading_dates = trading_dates + list([trading_days[i + offset]]) except : raise month = trading_days[i].month return trading_dates @batch_transform(window_length=window) def get_past_prices(data): prices = data['price'] buy_prices = data['price'] sell_prices = data['price'] return prices, buy_prices, sell_prices # data consists of dict for each sid for each date def initialize(context): # STOCKS = ['DIA','SPY','IHE','FBT','VDC','MDY','UUP','SHY','EFA','EWC','BND','IEF','VNQ','INP','DBC', 'VWO','EEB','MYY','TLT','ILF','EPP','EWZ','FXY','FXI'] context.stocks = [sid(2174),sid(8554),sid(32012),sid(32290),sid(25903),sid(12915),sid(33370),sid(23911),\ sid(22972),sid(14517),sid(23870),sid(26669),sid(33080),sid(28054),sid(27102),sid(32616),\ sid(32266),sid(23921),sid(23134),sid(23118),sid(21757),sid(33334),sid(26703)] context.top_n = 2 # shares in portfolio at last rebalance date context.last = [] context.capital_base = 1000000. context.notional = 1000000. context.slippage = slippage.FixedSlippage(spread=0.0) context.commission = commission.PerTrade(10.0) # want to rebalance on the last trading day of the month # because of the event-driven nature of zipline, this means # that we need to apply the ranking formula a day earlier start = dt.datetime(2003,1,1, 0, 0, 0, 0, pytz.utc) end = dt.datetime(2013,7,1, 0, 0, 0, 0, pytz.utc) context.event_dates = get_trading_dates(start, end, offset = -1 ) def handle_data(context, data): #get prices for each security # all_prices[0] = price, [1] = buy_prices, [2] = sell_prices # can set these up in transform get_past_prices() all_prices = get_past_prices(data) #circuit breaker in case transform returns none if all_prices is None: return sell_prices = all_prices[2] buy_prices = all_prices[1] all_prices = all_prices[0] #circuit breaker, only calculate on 2nd last trading day of month if get_datetime() in context.event_dates: # log.debug('%s' % get_datetime()) # daily returns d_returns = all_prices / all_prices.shift(1) - 1 #calculate 20 and 62 day performance and 20 day volatility perf_62 = all_prices.ix[-1] / all_prices.ix[0] - 1 perf_20 = all_prices.ix[-1] / all_prices.ix[window - 1 - 20] - 1 vol_20 = d_returns[window - 1 - 19:].std() * sqrt(252) rs = perf_62.rank() * 0.5 + perf_20.rank() * 0.3 + vol_20.rank(ascending=False) * 0.2 rs_modified = rs * 10 + perf_62.max()- perf_62 / perf_62.max() ranks = rs_modified.rank(ascending=False) holdings = pandas.Series(data=ranks.values, index=ranks.index) for symbol in holdings.index : if holdings[symbol] <= context.top_n : holdings[symbol] = 1 else : holdings[symbol] = 0 pass # hold shares if in last rebalance portfolio # rebalance the remainder in equal proportions # shares in portfolio for this rebalance date this = [sym for sym in context.stocks if holdings[sym] > 0] # only shares in 'last' and not in 'this' are "liquidated" # and "liquidated value" used to buy shares in 'this' not in last in equal proportions for symbol in [sym for sym in context.last if sym not in this] : qty = context.portfolio.positions[symbol].amount # treat nan as 0 if qty > 0 : pass else : qty = 0 order(symbol, - qty) context.notional += qty * sell_prices[symbol][-1] log.debug(' %s' % symbol + ' %s' % -qty + ' @ ' + '%s' % sell_prices[symbol][-1] + ' Cash = ' + str(context.notional)) liquidation_value = context.notional pass if len([sym for sym in this if sym not in context.last]) > 0 : # divide available cash equally weight_per_share = 1. / len([sym for sym in this if sym not in context.last]) # now buy new shares for symbol in [sym for sym in this if sym not in context.last] : qty = int(liquidation_value * weight_per_share / buy_prices[symbol][-1]) order(symbol, qty) context.notional -= qty * sell_prices[symbol][-1] log.debug(' %s' % symbol + ' %s' % qty + ' @ ' + '%s' % buy_prices[symbol][-1] + ' Cash = ' + str(context.notional)) else : pass context.last = this pass pass pass

This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.