Below is my draft implementation of Tactical Bond Strategy described here: http://seekingalpha.com/article/2073493-tactical-bond-strategy-for-rising-treasury-rates
As you can see results are not even comparable with author's GAR 12.1% and STD 5.4%.
The reason of that difference is a way of processing dividends. Author developed this algo using ETFREply, which differs from Quantopian in this respect.
I've asked the author about that and here is his reply:
The MA is calculated not on the price of bond, but on the total return of bond (that includes the dividends).
Does Quantopian track total return or price only? This is a fundamental question that needs answering.
Reading here https://www.quantopian.com/help#ide-dividends I understood that calculation of MA in my algo uses only price of the bond etf not counting dividends. Is there any way to implement this kind of algo on Quantopian?
|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|
""" RRBS. Rising rates bond strategy. Tactical bond strategy. Author: Cliff Smith http://seekingalpha.com/author/cliff-smith Implemented for Quantopian by Ed Bartosh <[email protected]> Detailed description of the strategy by the author: http://seekingalpha.com/article/2073493-tactical-bond-strategy-for-rising-treasury-rates http://seekingalpha.com/article/2080113-improvements-to-tactical-bond-strategy-for-rising-u-s-interest-rates """ def initialize(context): """ Initialize context object. It's passed to the handle_data function. :param context: context object :returns: None """ # Bond ETFS context.secs = [sid(40528), # HYLD AdvisorShares Peritus High Yield Bond ETF sid(41604), # HYS PIMCO BofA ML 0-5 Year High Yield Bond ETF sid(23870), # IEF iShares Barclays 7-10 Year Treasury Bond ETF sid(41199)] # TBX ProShares Short 7-10 Year Treasury Bond ETF # SHY or BILL is aquivalent of cash context.cashbond = sid(23911) # SHY iShares Barclays 1-3 Year Treasry Bond ETF context.rebalancing_date = None context.rebalancing_days = 15 # currently bought security context.current = None # set benchmark to AGG Total US Bond market set_benchmark(sid(25485)) # disable slippage and commissions set_slippage(slippage.FixedSlippage(spread=0.00)) set_commission(commission.PerTrade(cost=3.0)) def getbest(datapanel, securities, cashbond, data): """ Get best security. Rank the securities in two categories: six-month growth and 20-day growth. The rankings are weighted 40% and 60% respectively, and added together to get a final ranking. The top-ranked ETF should be selected if it passed a moving average filter of three months. Otherwise, cash should be selected. :param datapanel: datapanel containing market data for last six months :param securities: list of security objects :param cashbond: cash replacement security :param data: market data for all securities used in the algo :returns: best security object """ best = None for security in securities: price = data[security].price if data[security].mavg(70) >= price: # filter out security if its price is under 3-months MA log.info("%s has been filtered out: MA(%.2f) >= price(%.2f)" % \ (security.symbol, data[security].mavg(70), price)) continue data_sec = datapanel['price'][security] #print data_sec[-15:-14] #return cashbond # six-month(182 days) growth growth6m = (price - data_sec[-130])/data_sec[-130] * 100 # 20-day growth growth20d = (price - data_sec[-15])/data_sec[-15] * 100 # rank rank = growth6m * 0.4 + growth20d * 0.6 log.info('%s: %.2f %.2f %.2f' % (security.symbol, price, data_sec[-15], data_sec[-130])) log.info('Ranking %s: growth6m=%.2f, growth20d=%.2f, rank=%.2f' % \ (security.symbol, growth6m, growth20d, rank)) if growth6m < 0 or growth20d < 0: # filter out securities with negative growth continue # If the new rank is greater than the old best rank, pick it. if best is None or rank > best: best = security, rank if best: log.info("Best is %s" % best.symbol) return best else: log.info("Best is cash(%s)" % cashbond.symbol) return cashbond @batch_transform(window_length=130) def accumulatedata(data): """ Utilize the batch_transform decorator to accumulate multiple days of data into one datapanel. Need the window length to be 6 months to calculate grouth. :param window_length: accumulating period in days :returns: accumulated market data """ return data def days(begin, end): """ Calculate amount of calendar days between two dates. :param begin: start datetime :param end: end datetime :returns: days between begin and end """ roundb = begin.replace(hour = 0, minute = 0, second = 0, microsecond = 0) rounde = end.replace(hour = 0, minute = 0, second = 0, microsecond = 0) return (rounde - roundb).days def handle_data(context, data): """ The main proccessing function. Called whenever a market event occurs for any of algorithm's securities. :param context: context object :param data: Object contains all the market data for algorithm securities keyed by security id. It represents a snapshot of algorithm's universe as of when this method is called. :returns: None """ # Accumulate data until there is enough days worth of data # to process without having outOfBounds issues. datapanel = accumulatedata(data) if not datapanel: # There is insufficient data accumulated to process return record(capital_used=context.portfolio.capital_used) current_date = get_datetime() if context.rebalancing_date and \ days(context.rebalancing_date, current_date) < context.rebalancing_days: # It's not a time to rebalance yet, nothing further to do return # Determine which security to use for the next period best = getbest(datapanel, context.secs, context.cashbond, data) if context.current != best: current_value = 0 # value of current position if context.current: # sell current security position = context.portfolio.positions[context.current] log.info("selling %d shares of %s" % (position.amount, position.sid.symbol)) order(position.sid, -position.amount) current_value = data[context.current].price * position.amount # buy new 'best' security shares = (context.portfolio.cash + current_value)/data[best].price log.info("buying %d shares of %s" % (shares, best.symbol)) order(best, shares) context.current = best context.rebalancing_date = current_date