Back to Community
Tactical Bond Strategy

Hi,

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?

Regards,
Ed

Clone Algorithm
88
Loading...
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
"""
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[1]:
            best = security, rank

    if best:
        log.info("Best is %s" % best[0].symbol)
        return best[0]
    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
There was a runtime error.
15 responses

IIRC quantopian puts dividends into your cash. So you could write an algo that monitors your position + changes in cash, and recalculate your MA accordingly.

@Jason is correct, dividends are added to your cash portfolio. They're not automatically reinvested back into your portfolio.

His suggestion sounds like a plausible implementation - let us know how it goes!

Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

someone could give the example to handle the Jason point about dividends reinvested back to portfolio!

i'm planning on giving this a try later today (implement this as a strategy in my framework and post the results) though handling kids, so plans are always... adjustable :P

hi, i started looking into this, and I guess it's not possible, because you don't know which bond will give you the dividend. additionally you'd have to be invested to see the dividend. so you'd need some external data loaded through the fetcher to know dividend history. if someone has this info let me know and I can continue on this.

Hey Jason,

Found a way to load dividend history through an external source (Quandl was perfect for this). I couldn't find one for TBX's dividend history, but the other three were there.

Hope this helps!

-Seong

Clone Algorithm
Loading...
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
# Example for pulling in dividend history using external data

# List of dividend history and api links 
DIVS = {
        'HYLD_div': 'http://www.quandl.com/api/v1/datasets/SEC/DIV_HYLD.csv?&trim_start=2010-12-23&trim_end=2014-04-24',
        'HYS_div': 'http://www.quandl.com/api/v1/datasets/SEC/DIV_HYS.csv?&trim_start=2011-07-29&trim_end=2014-03-31',
        'IEF_div': 'http://www.quandl.com/api/v1/datasets/SEC/DIV_IEF.csv?&trim_start=2002-09-03&trim_end=2014-04-01'
       }

# Forward fill the dataframe for empty dates
def fetcher_magic(df):
    df = df.fillna(method='ffill')
    return df
    
def initialize(context):
        
    # 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
    
    # Fetching the different dividends
    for div in DIVS:
        fetch_csv(DIVS[div],
                date_column='Date',
                symbol=div,
                usecols=['Dividend'],
                post_func=fetcher_magic,
                #date_format='%m-%d-%Y'
                )
        

def handle_data(context, data):
    HYS = data['HYS_div']
    HYLD = data['HYLD_div']
    IEF = data['IEF_div']
    # Record the different dividends
    try:
        record(HYS_div = HYS['Dividend'])
        record(HYLD_div = HYLD['Dividend'])
        record(IEF_div = IEF['Dividend'])
    except KeyError:
        print "Empty Dataframe, for now.."

    
    
There was a runtime error.
Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

Based on Quantopian says: "Currently, dividends are added as cash to your portfolio and are not automatically reinvested."
so as the following code in this strategy, about dividends that whether should be auto reinvest in next rebalancing_days?
Sorry, I am confused about why we still need to handle the dividends through external data source ?


        # buy new 'best' security  
        shares = (context.portfolio.cash + current_value)/data[best].price  

you can automatically reinvest by using the order_target_percent(1.0) (ie: rebalancing)

however this strategy needs to know the mavg including dividend gains, thus the external source used to determine dividends. I'm still working on the "robust mean revision" strategy but i'll take a look at this next

Seong, thanks for the idea. I've found that yahoo provides more accurate and up to date dividend data than quandl and implemented getting dividends using yahoo finance API.

PS: This might be interesting for Quantopian devs. While playing with this I've noticed strange behavior of record visualization in 'Build Algorithm' mode. Sometimes it showed wrong values. It works just fine in 'Full Backtest' mode though. I can provide more details if you're interested.

Clone Algorithm
19
Loading...
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
"""
Get dividends from yahoo.finance

This is an example of usage of Fetcher Quantopian API to
get dividend data from yahoo.finance CSV Historical Quotes Download:
http://code.google.com/p/yahoo-finance-managed/wiki/csvHistQuotesDownload
"""
from functools import partial

def save_df(context, df):
    """
    Save dividends DataFrame in context.div
    This function is passed to fetch_csv as post_func
    """
    context.div = df
    return df

def initialize(context):
    """Initialize context object. It's passed to the handle_data function."""
    context.secs = {'HYLD': sid(40528)}
    
    # Fetching the dividends
    context.div = None # Dividend dataframe is saved here by save_df function
    fetch_csv('http://ichart.finance.yahoo.com/table.csv?s=HYLD&g=v',
              date_column='Date', symbol='HYLD', post_func=partial(save_df, context),
              date_format='%Y-%m-%d')

def get_dividend(date, df):
    """Get dividend from dividend DataFrame for specified date."""
    label = date.strftime('%Y%m%d')
    if label in df.index:
        log.info("%s %s" % (label, df.loc[label]['Dividends']))
        return df.loc[label]['Dividends']
    return 0
      
def handle_data(context, data):
    if context.div:
        record(DIV=get_dividend(get_datetime(), context.div))
    else:
        record(DIV=0)
There was a runtime error.

Why recently I run the strategy again, got following error message, it running well in before:

Something went wrong on our end. Sorry for the inconvenience. Please email us so we can help.
TypeError: int() argument must be a string or a number, not 'NoneType'
There was a runtime error on line 153.

The algorithm was trying to evaluated None to a sid object, and failing. Some time after this algorithm was posted, we changed the Type for sids from an int to a custom class for Zipline purposes.

In initialize(), I changed context.current from None to 0, fixing the TypeError.

Cheers,
Alisa

Clone Algorithm
27
Loading...
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
"""
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 = 0
    
    # 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[1]:
            best = security, rank

    if best:
        log.info("Best is %s" % best[0].symbol)
        return best[0]
    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
There was a runtime error.

Anybody know how to replace batch transform given it's deprecated? Strategy looks interesting for large accounts.

sure, enjoy!

Clone Algorithm
39
Loading...
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
"""
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 = 0
    
    # 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(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.current(security, "price")
        hist6m = data.history(security, fields="price", bar_count=130, frequency="1d")
        hist20d = data.history(security, fields="price", bar_count=15, frequency="1d")
        if hist6m[-70:].mean()  >= 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, hist6m[-70:].mean(), price))
            continue

        #print data_sec[-15:-14]
        #return cashbond
        # six-month(182 days) growth
        growth6m = (price - hist6m[0])/hist6m[0] * 100
        # 20-day growth
        growth20d = (price - hist20d[0])/hist20d[0] * 100
        # rank
        rank = growth6m * 0.4 + growth20d * 0.6

        #log.info(f'{security.symbol}: {price:%.2f} {hist20d[0]:%.2f} {hist6m[0]:%.2f}')
        #log.info(f'{security.symbol}: Ranking: {growth6m:.2f}, {growth20d:%.2f}, {rank:.2f}')

        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[1]:
            best = security, rank

    if best:
        log.info("Best is %s" % best[0].symbol)
        return best[0]
    else:
        log.info("Best is cash(%s)" % cashbond.symbol)
        return cashbond


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
    """
    
    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: 
        return

    # Determine which security to use for the next period
    best = getbest(context.secs, context.cashbond, data)

    if context.current != best:
        if context.current:
            # sell current security
            position = context.portfolio.positions[context.current]
            log.info("selling %s" % position.sid.symbol)
            #order(position.sid, -position.amount)
            order_target_percent(context.current, 0)
            #current_value = data[context.current].price * position.amount
        # buy new 'best' security
        #shares = (context.portfolio.cash + current_value)/data.current(best, "price")
        log.info("buying %s" % best.symbol)
        #order(best, shares)
        order_target_percent(best, 1.00)
        context.current = best

    context.rebalancing_date = current_date
There was a runtime error.

@ed

Thanks!

Hey guys can anyone help add futures to this, would be interesting to see how selecting the best performing bond futures would work as opposed to ETFs, and you can control margin / leverage much better than trading ETFs. and dealing with margin costs. I tried adding futures from the examples get an error about roll=calendar and equities.