Back to Community
Valuation based market timing

Hello all,

I attempted to implement the market timing strategy discussed in this post from Alpha Architect. I took Schiller’s CAPE ratio data from his website and calculated the inflation rate directly from the CPI data listed in the spreadsheet.

I applied the data to an Acquirer’s Multiple strategy that rebalances yearly, but also sells when the market is ‘expensive’ and buys back when the market is ‘cheap’.

I tested the system from 2003-06-01 to 2015-12-04. The simple Acquirer’s Multiple strategy has the following returns,

Total Returns Benchmark Returns Alpha Beta Sharpe Sortino Information Ratio Volatility Max Drawdown
358.4% 173.2% .14 1.06 1.20 1.56 .67 .22 53.5%

The Acquirer’s Multiple strategy with market timing has the following returns,

Total Returns Benchmark Returns Alpha Beta Sharpe Sortino Information Ratio Volatility Max Drawdown
304.6% 173.2% .11 0.92 1.04 1.32 .49 .21 42.4%

Although there is a significant reduction in Max DD the results seam underwhelming given the reduction in overall volatility is negligible and there is a reduction in total return.

I also calculated the Real Yield Metric but only using CAPE and CPI data going back to 1945. The results using this data are more impressive,

Total Returns Benchmark Returns Alpha Beta Sharpe Sortino Information Ratio Volatility Max Drawdown
484.5% 173.2% .26 0.93 1.70 2.18 1.16 .21 40.4%

I’ll leave it to the reader to decide whether this is a case of datasnooping or if using post-war inflation and market data is a more realistic measure for estimating future valuations.

Clone Algorithm
27
Loading...
Backtest from to with initial capital
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
'''
Implemented tactical asset allocation algorithm for value strategies discussed in this post from Alpha Architect,
    http://blog.alphaarchitect.com/2015/07/21/eureka-a-valuation-based-asset-allocation-strategy-that-might-work/

To compute the Real Yield Spread Metric I took the Shiller CAPE ratio data directly from Robert Shiller's website. I computed an annualized inflation rate directly from the CPI data contained in the spreadsheet and used an expanding window for the percentile calculations as well as a 1-month lag on the inflation rate for the Real Yield calculation.

'''

from datetime import timedelta
import pandas as pd
import numpy as np

def initialize(context):
    context.lower_mktcap = 500e6
    context.limit = 500
    context.num_positions = 100
    context.base_weight = 1.0/context.num_positions
    context.rebalance_month = 6 #June
    context.is_rebalance_day = False
    context.shy = symbol('IEF')
    context.market_expensive = False
    context.market_switched = False
    
    #get Real Yield data
    #fetch_csv( 'https://dl.dropboxusercontent.com/u/90042659/Quantopian-RealYield-1880.csv',date_column='Date',date_format='%m/%d/%y',symbol='Real Yield')
    fetch_csv( 'https://dl.dropboxusercontent.com/u/90042659/Quantopian-RealYield-1945.csv',date_column='Date',date_format='%m/%d/%y',symbol='Real Yield')

    schedule_function(set_rebalance_day,date_rules.month_start(days_offset=10),
                  time_rules.market_close(minutes=5)) 
    schedule_function(rebalance_sell,date_rules.month_start(days_offset=11),
                      time_rules.market_open(minutes=240)) 
    schedule_function(rebalance_buy,date_rules.month_start(days_offset=13),
                      time_rules.market_open(minutes=240))
         

def set_rebalance_day(context,data):
    today = get_datetime()
    
    if today.month == context.rebalance_month:
        context.is_rebalance_day = True
    else:
        context.is_rebalance_day = False

        
def rebalance_sell(context,data):
    if context.is_rebalance_day or (context.market_expensive and context.market_switched) :
        for s in context.portfolio.positions:
            order_target_percent(s,0)
        
        if context.market_switched:
            context.market_switched = False

        
def rebalance_buy(context,data):
    if not(context.market_expensive) and (context.is_rebalance_day or  context.market_switched):
        for s in context.longs:
            if s in data and s.symbol != 'NP':
                order_target_percent(s,context.base_weight)

        context.is_rebalance_day = False
        if context.market_switched:
            context.market_switched = False
        
def before_trading_start(context,data): 
    if context.is_rebalance_day or  context.market_switched:
        getstocks(context)
        update_universe(context.longs)
        
def getstocks(context):     
    #103 = Financials, 207 = Utilities, 104 = Real Estate
    excluded_sectors = [103, 207]
    sector_code = fundamentals.asset_classification.morningstar_sector_code
    fundamental_df = get_fundamentals(
        query(
            sector_code,
            fundamentals.valuation.market_cap,
            fundamentals.valuation.enterprise_value,
            fundamentals.cash_flow_statement.capital_expenditure,
            fundamentals.operation_ratios.roic,
            fundamentals.income_statement.ebit,
            fundamentals.income_statement.ebitda,
            fundamentals.balance_sheet.total_assets,  
            fundamentals.valuation_ratios.ev_to_ebitda
        )
        .filter(fundamentals.share_class_reference.is_primary_share == True)
        .filter(fundamentals.company_reference.primary_exchange_id != "OTCPK")
        .filter(fundamentals.share_class_reference.is_depositary_receipt == False)
        .filter(~sector_code.in_(excluded_sectors))
        .filter(fundamentals.valuation.market_cap > context.lower_mktcap ) 
        .filter(fundamentals.valuation_ratios.ev_to_ebitda > 0)
        #.filter(fundamentals.valuation.market_cap < context.upper_mktcap ) 
        .order_by(fundamentals.valuation_ratios.ev_to_ebitda.asc())
        .limit(context.limit)
    )
    
    context.longs = fundamental_df.columns[:context.num_positions]


def handle_data(context, data):
    #log.info("in handle_data")
    
    if 'Real Yield' in data and data['Real Yield']['Below 20th Percentile']:
        if not(context.market_expensive):
            context.market_switched = True
        context.market_expensive = True
    elif 'Real Yield' in data and not (data['Real Yield']['Below 20th Percentile']):
        if context.market_expensive:
            context.market_switched = True
        context.market_expensive = False
    
    record(leverage = context.account.leverage)
    
There was a runtime error.
12 responses

My apologies,

I noticed an error in the calculation of the Real Yield metric. I've updated the data,
here are the updated results.

For Real Yield calculated from 1800 the results are

Total Returns Benchmark Returns Alpha Beta Sharpe Sortino Information Ratio Volatility Max Drawdown
336.2% 173.2% .14 0.88 1.14 1.50 .63 .21 42.6%

For Real Yield calculated from 1945 the results are

Total Returns Benchmark Returns Alpha Beta Sharpe Sortino Information Ratio Volatility Max Drawdown
441.7% 173.2% .23 0.87 1.59 2.02 1.04 .21 38.8%

Your question "whether this is a case of datasnooping or if using post-war inflation and market data is a more realistic measure for estimating future valuations."

Meanwhile pardon this side-note, wanted to point out the shorts around 2010 in case it was inadvertent. Might have been beneficial or neutral. Happened just after weekly returns took a jump, visible on the full backtest page.

Edit: I guess I should add a note about something to watch out for. There are times when we make a change and the chart might show, say, 40% lower returns, while the dollars activated to achieve that return could have been lower, the profit on each dollar put to work can be higher. The change made was very good and just seemed worse. PvR in the code below can help keep an eye on that. The algo above makes $5 per dollar compared to starting capital. Only $2 each vs. the $2M utilized (the amount needed to achieve that result), turn on the cash_low option to see it.

Clone Algorithm
13
Loading...
Backtest from to with initial capital
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
'''
Implemented tactical asset allocation algorithm for value strategies discussed in this post from Alpha Architect,
    http://blog.alphaarchitect.com/2015/07/21/eureka-a-valuation-based-asset-allocation-strategy-that-might-work/

To compute the Real Yield Spread Metric I took the Shiller CAPE ratio data directly from Robert Shiller's website. I computed an annualized inflation rate directly from the CPI data contained in the spreadsheet and used an expanding window for the percentile calculations as well as a 1-month lag on the inflation rate for the Real Yield calculation.

'''

from datetime import timedelta
import pandas as pd
import numpy as np

def initialize(context):
    context.lower_mktcap = 500e6
    context.limit = 500
    context.num_positions = 100
    context.base_weight = 1.0/context.num_positions
    context.rebalance_month = 6 #June
    context.is_rebalance_day = False
    context.shy = symbol('IEF')
    context.market_expensive = False
    context.market_switched = False
    
    #get Real Yield data
    #fetch_csv( 'https://dl.dropboxusercontent.com/u/90042659/Quantopian-RealYield-1880.csv',date_column='Date',date_format='%m/%d/%y',symbol='Real Yield')
    fetch_csv( 'https://dl.dropboxusercontent.com/u/90042659/Quantopian-RealYield-1945.csv',date_column='Date',date_format='%m/%d/%y',symbol='Real Yield')

    schedule_function(set_rebalance_day,date_rules.month_start(days_offset=10),
                  time_rules.market_close(minutes=5)) 
    schedule_function(rebalance_sell,date_rules.month_start(days_offset=11),
                      time_rules.market_open(minutes=240)) 
    schedule_function(rebalance_buy,date_rules.month_start(days_offset=13),
                      time_rules.market_open(minutes=240))
    #schedule_function(info, date_rules.every_day(), time_rules.market_close())
    c = context
    c.max_lvrg = 0
    c.risk_hi  = 0
    c.date_prv = ''
    c.cash_low = c.portfolio.starting_cash
    c.date_end = str(get_environment('end').date())
    print '{} to {}  {}  {}'.format(str(get_datetime().date()), c.date_end, 
        int(c.cash_low), get_environment('data_frequency'))

def set_rebalance_day(context,data):
    today = get_datetime()
    
    if today.month == context.rebalance_month:
        context.is_rebalance_day = True
    else:
        context.is_rebalance_day = False

def rebalance_sell(context,data):
    if context.is_rebalance_day or (context.market_expensive and context.market_switched) :
        for s in context.portfolio.positions:
            order_target_percent(s,0)
        
        if context.market_switched:
            context.market_switched = False
        
def rebalance_buy(context,data):
    if not(context.market_expensive) and (context.is_rebalance_day or  context.market_switched):
        for s in context.longs:
            if s in data and s.symbol != 'NP':
                order_target_percent(s,context.base_weight)

        context.is_rebalance_day = False
        if context.market_switched:
            context.market_switched = False
        
def before_trading_start(context,data): 
    if context.is_rebalance_day or  context.market_switched:
        getstocks(context)
        update_universe(context.longs)
        
def getstocks(context):     
    #103 = Financials, 207 = Utilities, 104 = Real Estate
    excluded_sectors = [103, 207]
    sector_code = fundamentals.asset_classification.morningstar_sector_code
    fundamental_df = get_fundamentals(
        query(
            sector_code,
            fundamentals.valuation.market_cap,
            fundamentals.valuation.enterprise_value,
            fundamentals.cash_flow_statement.capital_expenditure,
            fundamentals.operation_ratios.roic,
            fundamentals.income_statement.ebit,
            fundamentals.income_statement.ebitda,
            fundamentals.balance_sheet.total_assets,  
            fundamentals.valuation_ratios.ev_to_ebitda
        )
        .filter(fundamentals.share_class_reference.is_primary_share == True)
        .filter(fundamentals.company_reference.primary_exchange_id != "OTCPK")
        .filter(fundamentals.share_class_reference.is_depositary_receipt == False)
        .filter(~sector_code.in_(excluded_sectors))
        .filter(fundamentals.valuation.market_cap > context.lower_mktcap ) 
        .filter(fundamentals.valuation_ratios.ev_to_ebitda > 0)
        #.filter(fundamentals.valuation.market_cap < context.upper_mktcap ) 
        .order_by(fundamentals.valuation_ratios.ev_to_ebitda.asc())
        .limit(context.limit)
    )
    
    context.longs = fundamental_df.columns[:context.num_positions]

def handle_data(context, data):
    #log.info("in handle_data")
    
    if 'Real Yield' in data and data['Real Yield']['Below 20th Percentile']:
        if not(context.market_expensive):
            context.market_switched = True
        context.market_expensive = True
    elif 'Real Yield' in data and not (data['Real Yield']['Below 20th Percentile']):
        if context.market_expensive:
            context.market_switched = True
        context.market_expensive = False
    
    record(leverage = context.account.leverage)
    info(context, data)

def info(context, data):
    ''' Custom chart and/or log of profit_vs_risk returns and related information
    '''
    # # # # # # # # # #  Options  # # # # # # # # # #
    record_max_lvrg = 1          # Maximum leverage encountered
    record_leverage = 0          # Leverage (context.account.leverage)
    record_q_return = 1          # Quantopian returns (percentage)
    record_pvr      = 1          # Profit vs Risk returns (percentage)
    record_pnl      = 0          # Profit-n-Loss
    record_shorting = 1          # Total value of any shorts
    record_risk     = 0          # Risked, maximum cash spent or shorts in excess of cash at any time
    record_risk_hi  = 0          # Highest risk overall
    record_cash     = 0          # Cash available
    record_cash_low = 0          # Any new lowest cash level
    logging         = 1          # Also log to the logging window conditionally (1) or not (0)
    log_method      = 'risk_hi'  # 'daily' or 'risk_hi'

    c = context                          # For brevity
    new_cash_low = 0                     # To trigger logging in cash_low case
    date = str(get_datetime().date())    # To trigger logging in daily case
    cash = c.portfolio.cash

    if int(cash) < c.cash_low:    # New cash low
        new_cash_low = 1
        c.cash_low   = int(cash)
        if record_cash_low:
            record(CashLow = int(c.cash_low))

    pvr_rtrn      = 0        # Profit vs Risk returns based on maximum spent
    profit_loss   = 0        # Profit-n-loss
    shorts        = 0        # Shorts value
    start         = c.portfolio.starting_cash
    cash_dip      = int(max(0, start - cash))

    if record_cash:
        record(Cash = int(c.portfolio.cash))  # Cash

    if record_leverage:
        record(Lvrg = c.account.leverage)     # Leverage

    if record_max_lvrg:
        if c.account.leverage > c.max_lvrg:
            c.max_lvrg = c.account.leverage
            record(MaxLv = c.max_lvrg)        # Maximum leverage

    if record_pnl:
        profit_loss = c.portfolio.pnl
        record(PnL = profit_loss)             # "Profit and Loss" in dollars

    for p in c.portfolio.positions:
        shrs = c.portfolio.positions[p].amount
        if shrs < 0:
            shorts += int(abs(shrs * data[p].price))

    if record_shorting:
        record(Shorts = shorts)               # Shorts value as a positve

    risk = int(max(cash_dip, shorts))
    if record_risk:
        record(Risk = risk)                   # Amount in play, maximum of shorts or cash used

    new_risk_hi = 0
    if risk > c.risk_hi:
        c.risk_hi = risk
        new_risk_hi = 1

        if record_risk_hi:
            record(RiskHi = c.risk_hi)       # Highest risk overall

    if record_pvr:      # Profit_vs_Risk returns based on max amount actually spent (risk high)
        if c.risk_hi != 0:     # Avoid zero-divide
            pvr_rtrn = 100 * (c.portfolio.portfolio_value - start) / c.risk_hi
            record(PvR = pvr_rtrn)            # Profit_vs_Risk returns

    q_rtrn = 100 * (c.portfolio.portfolio_value - start) / start
    if record_q_return:
        record(QRet = q_rtrn)                 # Quantopian returns to compare to pvr returns curve

    from pytz import timezone
    if logging:
        if log_method == 'risk_hi' and new_risk_hi \
          or log_method == 'daily' and c.date_prv != date \
          or c.date_end == date \
          or new_cash_low:
            qret   = 'QRet '    + '%.1f' % q_rtrn
            mxlv   = 'MaxLv '   + '%.1f' % c.max_lvrg   if record_max_lvrg else ''
            pvr    = 'PvR '     + '%.1f' % pvr_rtrn     if record_pvr      else ''
            pnl    = 'PnL '     + '%.0f' % profit_loss  if record_pnl      else ''
            csh    = 'Cash '    + '%.0f' % cash         if record_cash     else ''
            csh_lw = 'CshLw '   + '%.0f' % c.cash_low   if record_cash_low else ''
            shrt   = 'Shrt '    + '%.0f' % shorts       if record_shorting else ''
            risk   = 'Risk '    + '%.0f' % risk         if record_risk     else ''
            rsk_hi = 'RskHi '   + '%.0f' % c.risk_hi    if record_risk_hi  else ''
            minute = get_datetime().astimezone(timezone('US/Eastern')).time().minute
            log.info('{} {} {} {} {} {} {} {} {} {}'.format(
                    minute, mxlv, qret, pvr, pnl, csh, csh_lw, shrt, risk, rsk_hi))

    if c.date_end == date:    # Log on last day, like cash 125199  portfolio 126890
        log.info('cash {}  portfolio {}'.format(
                int(cash), int(c.portfolio.portfolio_value)))

    c.date_prv = date

         
    
There was a runtime error.

Hi Gary,

Thanks for pointing this out. I will take a closer look at this. At first glance though I don't understand how a short can enter the portfolio since whenever an order is sent in the weight is guaranteed to be >= 0. So how can order_target_percent place a short sell?

regards,
Mark

order_target_percent and their ilk do not take into account open orders, so if you are long and target 0%, then do that again before the first has fully executed, you might go short.

That's right.
This adds track_orders() to help you hone in on it with some lines added to only log sell/sold if a short would result.
The sell orders in excess of current shares happen more often than actual fill.
In possibly the most far-out use of the custom chart ever so far, when something is short, its security id is recorded. Only happened once, AAN 523.

I ran your code earlier today, then later with no changes there was an error about DECO on the order line here. As a rough workaround I added the line above it. The only thing I can figure is, since you are pulling in as many as 500 sids, the Morningstar data was updated in the meantime resulting in a different set of securities adding DECO and maybe it became delisted. PvR/info() is turned off. Check the log window. Clone/run this.

        for s in context.portfolio.positions:  
            if s not in data: continue  
            order_target_percent(s,0)  
Clone Algorithm
13
Loading...
Backtest from to with initial capital
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
'''
Implemented tactical asset allocation algorithm for value strategies discussed in this post from Alpha Architect,
    http://blog.alphaarchitect.com/2015/07/21/eureka-a-valuation-based-asset-allocation-strategy-that-might-work/

To compute the Real Yield Spread Metric I took the Shiller CAPE ratio data directly from Robert Shiller's website. I computed an annualized inflation rate directly from the CPI data contained in the spreadsheet and used an expanding window for the percentile calculations as well as a 1-month lag on the inflation rate for the Real Yield calculation.

'''

from datetime import timedelta
import pandas as pd
import numpy as np

def initialize(context):
    context.lower_mktcap = 500e6
    context.limit = 500
    context.num_positions = 100
    context.base_weight = 1.0/context.num_positions
    context.rebalance_month = 6 #June
    context.is_rebalance_day = False
    context.shy = symbol('IEF')
    context.market_expensive = False
    context.market_switched = False
    
    #get Real Yield data
    #fetch_csv( 'https://dl.dropboxusercontent.com/u/90042659/Quantopian-RealYield-1880.csv',date_column='Date',date_format='%m/%d/%y',symbol='Real Yield')
    fetch_csv( 'https://dl.dropboxusercontent.com/u/90042659/Quantopian-RealYield-1945.csv',date_column='Date',date_format='%m/%d/%y',symbol='Real Yield')

    schedule_function(set_rebalance_day,date_rules.month_start(days_offset=10),
                  time_rules.market_close(minutes=5)) 
    schedule_function(rebalance_sell,date_rules.month_start(days_offset=11),
                      time_rules.market_open(minutes=240)) 
    schedule_function(rebalance_buy,date_rules.month_start(days_offset=13),
                      time_rules.market_open(minutes=240))
    #schedule_function(info, date_rules.every_day(), time_rules.market_close())
    c = context
    c.max_lvrg = 0
    c.risk_hi  = 0
    c.date_prv = ''
    c.cash_low = c.portfolio.starting_cash
    c.date_end = str(get_environment('end').date())
    print '{} to {}  {}  {}'.format(str(get_datetime().date()), c.date_end, 
        int(c.cash_low), get_environment('data_frequency'))

def set_rebalance_day(context,data):
    today = get_datetime()
    
    if today.month == context.rebalance_month:
        context.is_rebalance_day = True
    else:
        context.is_rebalance_day = False

def rebalance_sell(context,data):
    if context.is_rebalance_day or (context.market_expensive and context.market_switched) :
        for s in context.portfolio.positions:
            if s not in data: continue
            order_target_percent(s,0)
        
        if context.market_switched:
            context.market_switched = False
        
def rebalance_buy(context,data):
    if not(context.market_expensive) and (context.is_rebalance_day or  context.market_switched):
        for s in context.longs:
            if s in data and s.symbol != 'NP':
                if context.base_weight < 0:
                    log.warn('context.base_weight neg at ' + str(context.base_weight))
                order_target_percent(s,context.base_weight)

        context.is_rebalance_day = False
        if context.market_switched:
            context.market_switched = False
        
def before_trading_start(context,data): 
    if context.is_rebalance_day or  context.market_switched:
        getstocks(context)
        update_universe(context.longs)
        
def getstocks(context):     
    #103 = Financials, 207 = Utilities, 104 = Real Estate
    excluded_sectors = [103, 207]
    sector_code = fundamentals.asset_classification.morningstar_sector_code
    fundamental_df = get_fundamentals(
        query(
            sector_code,
            fundamentals.valuation.market_cap,
            fundamentals.valuation.enterprise_value,
            fundamentals.cash_flow_statement.capital_expenditure,
            fundamentals.operation_ratios.roic,
            fundamentals.income_statement.ebit,
            fundamentals.income_statement.ebitda,
            fundamentals.balance_sheet.total_assets,  
            fundamentals.valuation_ratios.ev_to_ebitda
        )
        .filter(fundamentals.share_class_reference.is_primary_share == True)
        .filter(fundamentals.company_reference.primary_exchange_id != "OTCPK")
        .filter(fundamentals.share_class_reference.is_depositary_receipt == False)
        .filter(~sector_code.in_(excluded_sectors))
        .filter(fundamentals.valuation.market_cap > context.lower_mktcap ) 
        .filter(fundamentals.valuation_ratios.ev_to_ebitda > 0)
        #.filter(fundamentals.valuation.market_cap < context.upper_mktcap ) 
        .order_by(fundamentals.valuation_ratios.ev_to_ebitda.asc())
        .limit(context.limit)
    )
    
    context.longs = fundamental_df.columns[:context.num_positions]

def handle_data(context, data):
    #log.info("in handle_data")
    
    if 'Real Yield' in data and data['Real Yield']['Below 20th Percentile']:
        if not(context.market_expensive):
            context.market_switched = True
        context.market_expensive = True
    elif 'Real Yield' in data and not (data['Real Yield']['Below 20th Percentile']):
        if context.market_expensive:
            context.market_switched = True
        context.market_expensive = False
    
    record(leverage = context.account.leverage)
    
    #info(context, data)
    track_orders(context, data)

def track_orders(context, data):  # Log orders created or filled.
    if 'orders' not in context:
        context.orders = {}

    to_delete = []
    for id in context.orders:
        o   = get_order(id)
        sec = o.sid
        sym = sec.symbol
        if o.filled:        # Filled at least some, status 1 is Filled


            if context.portfolio.positions[sec].amount < 0:
                # cover these where short occured
                log.info('{} now {} shares, shorted at {}'.format(
                    sym, 
                    context.portfolio.positions[sec].amount + o.amount,
                    context.portfolio.positions[sec].amount
                    ))
                # Funky use of record, if new short, its security id
                record(shorted_sid_id = o.sid.sid)
            else:
                continue    # skip all normal


            trade = 'Bot' if o.amount > 0 else 'Sold'
            log.info('      {} {} {} at {} id {}\n'.format(
                    trade, o.filled, sym, data[sec].price, o.id))
            to_delete.append(o.id)
        else:
            log.info('         {} {} unfilled\n'.format(o.sid.symbol, o.amount))

    for sec, oo_for_sid in get_open_orders().iteritems(): # Open orders
        sym = sec.symbol
        for o in oo_for_sid: # Orders per security

            
            if context.portfolio.positions[sec].amount + o.amount < 0:
                # cover these where short would occur
                log.info('Sell {} would result in {} shares, short, currently {}'.format(
                    sym, 
                    context.portfolio.positions[sec].amount + o.amount,
                    context.portfolio.positions[sec].amount
                    ))
            else:
                continue    # skip all normal

                
            if o.id in to_delete:
                continue
            if o.status == 2:                 # Cancelled
                log.info('    Cancelled {} {} order\n'.format(
                        trade, o.amount, sym, data[sec].price))
                to_delete.append(o.id)
            elif o.id not in context.orders:  # New
                context.orders[o.id] = 1
                trade = 'Buy' if o.amount > 0 else 'Sell'
                if o.limit:        # Limit order
                    log.info('   {} {} {} now {} limit {}\n'.format(
                            trade, o.amount, sym, data[sec].price, o.limit))
                elif o.stop:       # Stop order
                    log.info('   {} {} {} now {} stop {}\n'.format(
                            trade, o.amount, sym, data[sec].price, o.stop))
                else:              # Market order
                    log.info('   {} {} {} at {}\n'.format(
                            trade, o.amount, sym, data[sec].price))
    for d in to_delete:
        del context.orders[d]

def info(context, data):
    ''' Custom chart and/or log of profit_vs_risk returns and related information
    '''
    # # # # # # # # # #  Options  # # # # # # # # # #
    record_max_lvrg = 1          # Maximum leverage encountered
    record_leverage = 0          # Leverage (context.account.leverage)
    record_q_return = 0          # Quantopian returns (percentage)
    record_pvr      = 1          # Profit vs Risk returns (percentage)
    record_pnl      = 0          # Profit-n-Loss
    record_shorting = 1          # Total value of any shorts
    record_risk     = 0          # Risked, maximum cash spent or shorts in excess of cash at any time
    record_risk_hi  = 0          # Highest risk overall
    record_cash     = 0          # Cash available
    record_cash_low = 1          # Any new lowest cash level
    logging         = 1          # Also log to the logging window conditionally (1) or not (0)
    log_method      = 'risk_hi'  # 'daily' or 'risk_hi'

    c = context                          # For brevity
    new_cash_low = 0                     # To trigger logging in cash_low case
    date = str(get_datetime().date())    # To trigger logging in daily case
    cash = c.portfolio.cash

    if int(cash) < c.cash_low:    # New cash low
        new_cash_low = 1
        c.cash_low   = int(cash)
        if record_cash_low:
            record(CashLow = int(c.cash_low))

    pvr_rtrn      = 0        # Profit vs Risk returns based on maximum spent
    profit_loss   = 0        # Profit-n-loss
    shorts        = 0        # Shorts value
    start         = c.portfolio.starting_cash
    cash_dip      = int(max(0, start - cash))

    if record_cash:
        record(Cash = int(c.portfolio.cash))  # Cash

    if record_leverage:
        record(Lvrg = c.account.leverage)     # Leverage

    if record_max_lvrg:
        if c.account.leverage > c.max_lvrg:
            c.max_lvrg = c.account.leverage
            record(MaxLv = c.max_lvrg)        # Maximum leverage

    if record_pnl:
        profit_loss = c.portfolio.pnl
        record(PnL = profit_loss)             # "Profit and Loss" in dollars

    for p in c.portfolio.positions:
        shrs = c.portfolio.positions[p].amount
        if shrs < 0:
            shorts += int(abs(shrs * data[p].price))

    if record_shorting:
        record(Shorts = shorts)               # Shorts value as a positve

    risk = int(max(cash_dip, shorts))
    if record_risk:
        record(Risk = risk)                   # Amount in play, maximum of shorts or cash used

    new_risk_hi = 0
    if risk > c.risk_hi:
        c.risk_hi = risk
        new_risk_hi = 1

        if record_risk_hi:
            record(RiskHi = c.risk_hi)       # Highest risk overall

    if record_pvr:      # Profit_vs_Risk returns based on max amount actually spent (risk high)
        if c.risk_hi != 0:     # Avoid zero-divide
            pvr_rtrn = 100 * (c.portfolio.portfolio_value - start) / c.risk_hi
            record(PvR = pvr_rtrn)            # Profit_vs_Risk returns

    q_rtrn = 100 * (c.portfolio.portfolio_value - start) / start
    if record_q_return:
        record(QRet = q_rtrn)                 # Quantopian returns to compare to pvr returns curve

    from pytz import timezone
    if logging:
        if log_method == 'risk_hi' and new_risk_hi \
          or log_method == 'daily' and c.date_prv != date \
          or c.date_end == date \
          or new_cash_low:
            qret   = 'QRet '    + '%.1f' % q_rtrn
            mxlv   = 'MaxLv '   + '%.1f' % c.max_lvrg   if record_max_lvrg else ''
            pvr    = 'PvR '     + '%.1f' % pvr_rtrn     if record_pvr      else ''
            pnl    = 'PnL '     + '%.0f' % profit_loss  if record_pnl      else ''
            csh    = 'Cash '    + '%.0f' % cash         if record_cash     else ''
            csh_lw = 'CshLw '   + '%.0f' % c.cash_low   if record_cash_low else ''
            shrt   = 'Shrt '    + '%.0f' % shorts       if record_shorting else ''
            risk   = 'Risk '    + '%.0f' % risk         if record_risk     else ''
            rsk_hi = 'RskHi '   + '%.0f' % c.risk_hi    if record_risk_hi  else ''
            minute = get_datetime().astimezone(timezone('US/Eastern')).time().minute
            log.info('{} {} {} {} {} {} {} {} {} {}'.format(
                    minute, mxlv, qret, pvr, pnl, csh, csh_lw, shrt, risk, rsk_hi))

    if c.date_end == date:    # Log on last day, like cash 125199  portfolio 126890
        log.info('cash {}  portfolio {}'.format(
                int(cash), int(c.portfolio.portfolio_value)))

    c.date_prv = date

         
    
There was a runtime error.

Hi Gary,

Thanks a lot for taking the time to look into this. Taking Simon’s comment into account I added this line,

if s not in get_open_orders():

to both the buy and sell functions, this removes the inadvertent short in the backtest, without changing the performance results.

Clone Algorithm
22
Loading...
Backtest from to with initial capital
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
'''
Implemented tactical asset allocation algorithm for value strategies discussed in this post from Alpha Architect,
    http://blog.alphaarchitect.com/2015/07/21/eureka-a-valuation-based-asset-allocation-strategy-that-might-work/

To compute the Real Yield Spread Metric I took the Shiller CAPE ratio data directly from Robert Shiller's website. I computed an annualized inflation rate directly from the CPI data contained in the spreadsheet and used an expanding window for the percentile calculations as well as a 1-month lag on the inflation rate for the Real Yield calculation.

'''

from datetime import timedelta
import pandas as pd
import numpy as np

def initialize(context):
    context.lower_mktcap = 500e6
    context.limit = 500
    context.num_positions = 100
    context.base_weight = 1.0/context.num_positions
    context.rebalance_month = 6 #June
    context.is_rebalance_day = False
    context.shy = symbol('IEF')
    context.market_expensive = False
    context.market_switched = False
    
    #get Real Yield data
    #fetch_csv( 'https://dl.dropboxusercontent.com/u/90042659/Quantopian-RealYield-1880.csv',date_column='Date',date_format='%m/%d/%y',symbol='Real Yield')
    fetch_csv( 'https://dl.dropboxusercontent.com/u/90042659/Quantopian-RealYield-1945.csv',date_column='Date',date_format='%m/%d/%y',symbol='Real Yield')

    schedule_function(set_rebalance_day,date_rules.month_start(days_offset=10),
                  time_rules.market_close(minutes=5)) 
    schedule_function(rebalance_sell,date_rules.month_start(days_offset=11),
                      time_rules.market_open(minutes=240)) 
    schedule_function(rebalance_buy,date_rules.month_start(days_offset=13),
                      time_rules.market_open(minutes=240))
    #schedule_function(info, date_rules.every_day(), time_rules.market_close())
    c = context
    c.max_lvrg = 0
    c.risk_hi  = 0
    c.date_prv = ''
    c.cash_low = c.portfolio.starting_cash
    c.date_end = str(get_environment('end').date())
    print '{} to {}  {}  {}'.format(str(get_datetime().date()), c.date_end, 
        int(c.cash_low), get_environment('data_frequency'))

def set_rebalance_day(context,data):
    today = get_datetime()
    
    if today.month == context.rebalance_month:
        context.is_rebalance_day = True
    else:
        context.is_rebalance_day = False

def rebalance_sell(context,data):
    if context.is_rebalance_day or (context.market_expensive and context.market_switched) :
        for s in context.portfolio.positions:
            if s not in get_open_orders():
                order_target_percent(s,0)
        
        if context.market_switched:
            context.market_switched = False
        
def rebalance_buy(context,data):
    if not(context.market_expensive) and (context.is_rebalance_day or  context.market_switched):
        for s in context.longs:
            if s in data and s.symbol != 'NP':
                if s not in get_open_orders():
                    order_target_percent(s,context.base_weight)

        context.is_rebalance_day = False
        if context.market_switched:
            context.market_switched = False
        
def before_trading_start(context,data): 
    if context.is_rebalance_day or  context.market_switched:
        getstocks(context)
        update_universe(context.longs)
        
def getstocks(context):     
    #103 = Financials, 207 = Utilities, 104 = Real Estate
    excluded_sectors = [103, 207]
    sector_code = fundamentals.asset_classification.morningstar_sector_code
    fundamental_df = get_fundamentals(
        query(
            sector_code,
            fundamentals.valuation.market_cap,
            fundamentals.valuation.enterprise_value,
            fundamentals.cash_flow_statement.capital_expenditure,
            fundamentals.operation_ratios.roic,
            fundamentals.income_statement.ebit,
            fundamentals.income_statement.ebitda,
            fundamentals.balance_sheet.total_assets,  
            fundamentals.valuation_ratios.ev_to_ebitda
        )
        .filter(fundamentals.share_class_reference.is_primary_share == True)
        .filter(fundamentals.company_reference.primary_exchange_id != "OTCPK")
        .filter(fundamentals.share_class_reference.is_depositary_receipt == False)
        .filter(~sector_code.in_(excluded_sectors))
        .filter(fundamentals.valuation.market_cap > context.lower_mktcap ) 
        .filter(fundamentals.valuation_ratios.ev_to_ebitda > 0)
        #.filter(fundamentals.valuation.market_cap < context.upper_mktcap ) 
        .order_by(fundamentals.valuation_ratios.ev_to_ebitda.asc())
        .limit(context.limit)
    )
    
    context.longs = fundamental_df.columns[:context.num_positions]

def handle_data(context, data):
    #log.info("in handle_data")
    
    if 'Real Yield' in data and data['Real Yield']['Below 20th Percentile']:
        if not(context.market_expensive):
            context.market_switched = True
        context.market_expensive = True
    elif 'Real Yield' in data and not (data['Real Yield']['Below 20th Percentile']):
        if context.market_expensive:
            context.market_switched = True
        context.market_expensive = False
    
    record(leverage = context.account.leverage)
    info(context, data)

def info(context, data):
    ''' Custom chart and/or log of profit_vs_risk returns and related information
    '''
    # # # # # # # # # #  Options  # # # # # # # # # #
    record_max_lvrg = 1          # Maximum leverage encountered
    record_leverage = 0          # Leverage (context.account.leverage)
    record_q_return = 0          # Quantopian returns (percentage)
    record_pvr      = 0          # Profit vs Risk returns (percentage)
    record_pnl      = 0          # Profit-n-Loss
    record_shorting = 1          # Total value of any shorts
    record_risk     = 0          # Risked, maximum cash spent or shorts in excess of cash at any time
    record_risk_hi  = 0          # Highest risk overall
    record_cash     = 0          # Cash available
    record_cash_low = 1          # Any new lowest cash level
    logging         = 1          # Also log to the logging window conditionally (1) or not (0)
    log_method      = 'risk_hi'  # 'daily' or 'risk_hi'

    c = context                          # For brevity
    new_cash_low = 0                     # To trigger logging in cash_low case
    date = str(get_datetime().date())    # To trigger logging in daily case
    cash = c.portfolio.cash

    if int(cash) < c.cash_low:    # New cash low
        new_cash_low = 1
        c.cash_low   = int(cash)
        if record_cash_low:
            record(CashLow = int(c.cash_low))

    pvr_rtrn      = 0        # Profit vs Risk returns based on maximum spent
    profit_loss   = 0        # Profit-n-loss
    shorts        = 0        # Shorts value
    start         = c.portfolio.starting_cash
    cash_dip      = int(max(0, start - cash))

    if record_cash:
        record(Cash = int(c.portfolio.cash))  # Cash

    if record_leverage:
        record(Lvrg = c.account.leverage)     # Leverage

    if record_max_lvrg:
        if c.account.leverage > c.max_lvrg:
            c.max_lvrg = c.account.leverage
            record(MaxLv = c.max_lvrg)        # Maximum leverage

    if record_pnl:
        profit_loss = c.portfolio.pnl
        record(PnL = profit_loss)             # "Profit and Loss" in dollars

    for p in c.portfolio.positions:
        shrs = c.portfolio.positions[p].amount
        if shrs < 0:
            shorts += int(abs(shrs * data[p].price))

    if record_shorting:
        record(Shorts = shorts)               # Shorts value as a positve

    risk = int(max(cash_dip, shorts))
    if record_risk:
        record(Risk = risk)                   # Amount in play, maximum of shorts or cash used

    new_risk_hi = 0
    if risk > c.risk_hi:
        c.risk_hi = risk
        new_risk_hi = 1

        if record_risk_hi:
            record(RiskHi = c.risk_hi)       # Highest risk overall

    if record_pvr:      # Profit_vs_Risk returns based on max amount actually spent (risk high)
        if c.risk_hi != 0:     # Avoid zero-divide
            pvr_rtrn = 100 * (c.portfolio.portfolio_value - start) / c.risk_hi
            record(PvR = pvr_rtrn)            # Profit_vs_Risk returns

    q_rtrn = 100 * (c.portfolio.portfolio_value - start) / start
    if record_q_return:
        record(QRet = q_rtrn)                 # Quantopian returns to compare to pvr returns curve

    from pytz import timezone
    if logging:
        if log_method == 'risk_hi' and new_risk_hi \
          or log_method == 'daily' and c.date_prv != date \
          or c.date_end == date \
          or new_cash_low:
            qret   = 'QRet '    + '%.1f' % q_rtrn
            mxlv   = 'MaxLv '   + '%.1f' % c.max_lvrg   if record_max_lvrg else ''
            pvr    = 'PvR '     + '%.1f' % pvr_rtrn     if record_pvr      else ''
            pnl    = 'PnL '     + '%.0f' % profit_loss  if record_pnl      else ''
            csh    = 'Cash '    + '%.0f' % cash         if record_cash     else ''
            csh_lw = 'CshLw '   + '%.0f' % c.cash_low   if record_cash_low else ''
            shrt   = 'Shrt '    + '%.0f' % shorts       if record_shorting else ''
            risk   = 'Risk '    + '%.0f' % risk         if record_risk     else ''
            rsk_hi = 'RskHi '   + '%.0f' % c.risk_hi    if record_risk_hi  else ''
            minute = get_datetime().astimezone(timezone('US/Eastern')).time().minute
            log.info('{} {} {} {} {} {} {} {} {} {}'.format(
                    minute, mxlv, qret, pvr, pnl, csh, csh_lw, shrt, risk, rsk_hi))

    if c.date_end == date:    # Log on last day, like cash 125199  portfolio 126890
        log.info('cash {}  portfolio {}'.format(
                int(cash), int(c.portfolio.portfolio_value)))

    c.date_prv = date

         
    
There was a runtime error.

Next, I took a look at the leverage/cash_low issue that you mentioned. The leverage in the algorithm is the result of a number of zombie trades. It seems when a company gets bought the Q backtester is not able to execute the sell order on the name, so it just stays in the list of open orders.

I added some code to explicitly exclude those companies, you can see in the associated backtest that the leverage stays at or below one and the cash_low is above 0 (not sure why it dips below right at the end, will need to have a look at that at some point).

Interestingly, the companies that get bought out are generally profitable trades, so the Acquirer’s multiple algorithm is doing what its supposed to, i.e. finding companies that are so cheap that they are likely to get bought. Not sure how to appropriately handle this case in Q, let me know if you have some ideas.

Regards,
Mark

Clone Algorithm
22
Loading...
Backtest from to with initial capital
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
'''
Implemented tactical asset allocation algorithm for value strategies discussed in this post from Alpha Architect,
    http://blog.alphaarchitect.com/2015/07/21/eureka-a-valuation-based-asset-allocation-strategy-that-might-work/

To compute the Real Yield Spread Metric I took the Shiller CAPE ratio data directly from Robert Shiller's website. I computed an annualized inflation rate directly from the CPI data contained in the spreadsheet and used an expanding window for the percentile calculations as well as a 1-month lag on the inflation rate for the Real Yield calculation.

'''

from datetime import timedelta
import pandas as pd
import numpy as np

def initialize(context):
    context.lower_mktcap = 500e6
    context.limit = 500
    context.num_positions = 100
    context.base_weight = 1.0/context.num_positions
    context.rebalance_month = 6 #June
    context.is_rebalance_day = False
    context.shy = symbol('IEF')
    context.market_expensive = False
    context.market_switched = False
    context.no_buy_list = [sid(27653),symbol('DCEL'),symbol('TRA'),symbol('MVL'),symbol('AMSY'),sid(7448),symbol('OMM'),sid(42021),symbol('KMR'),symbol('CRDN'),symbol('GYMB'),symbol('PD'),sid(17104),sid(209),symbol('AWIN'),symbol('CVH'),sid(25948),symbol('IAIA'),symbol('PPD'),symbol('WINN'),symbol('SHS'),symbol('GW'),symbol('KG'),sid(764),symbol('HLYW')]
    
    #get Real Yield data
    fetch_csv( 'https://dl.dropboxusercontent.com/u/90042659/Quantopian-RealYield-1880.csv',date_column='Date',date_format='%m/%d/%y',symbol='Real Yield')
    #fetch_csv( 'https://dl.dropboxusercontent.com/u/90042659/Quantopian-RealYield-1945.csv',date_column='Date',date_format='%m/%d/%y',symbol='Real Yield')

    schedule_function(set_rebalance_day,date_rules.month_start(days_offset=10),
                  time_rules.market_close(minutes=5)) 
    schedule_function(rebalance_sell,date_rules.month_start(days_offset=11),
                      time_rules.market_open(minutes=240)) 
    schedule_function(rebalance_buy,date_rules.month_start(days_offset=13),
                      time_rules.market_open(minutes=240))
    #schedule_function(info, date_rules.every_day(), time_rules.market_close())
    c = context
    c.max_lvrg = 0
    c.risk_hi  = 0
    c.date_prv = ''
    c.cash_low = c.portfolio.starting_cash
    c.date_end = str(get_environment('end').date())
    print '{} to {}  {}  {}'.format(str(get_datetime().date()), c.date_end, 
        int(c.cash_low), get_environment('data_frequency'))

def set_rebalance_day(context,data):
    today = get_datetime()
    
    if today.month == context.rebalance_month:
        context.is_rebalance_day = True
    else:
        context.is_rebalance_day = False

def rebalance_sell(context,data):
    if context.is_rebalance_day or (context.market_expensive and context.market_switched) :
        for s in context.portfolio.positions:
            if s not in get_open_orders():
                order_target_percent(s,0)
        
        if context.market_switched:
            context.market_switched = False
        
def rebalance_buy(context,data):
    if not(context.market_expensive) and (context.is_rebalance_day or  context.market_switched):
        for s in context.longs:
            if s in data and s.symbol != 'NP':
                if (s not in get_open_orders()) and (s not in context.no_buy_list):
                    order_target_percent(s,context.base_weight)

        context.is_rebalance_day = False
        if context.market_switched:
            context.market_switched = False
        
def before_trading_start(context,data): 
    if context.is_rebalance_day or  context.market_switched:
        getstocks(context)
        update_universe(context.longs)
        
def getstocks(context):     
    #103 = Financials, 207 = Utilities, 104 = Real Estate
    excluded_sectors = [103, 207]
    sector_code = fundamentals.asset_classification.morningstar_sector_code
    fundamental_df = get_fundamentals(
        query(
            sector_code,
            fundamentals.valuation.market_cap,
            fundamentals.valuation.enterprise_value,
            fundamentals.cash_flow_statement.capital_expenditure,
            fundamentals.operation_ratios.roic,
            fundamentals.income_statement.ebit,
            fundamentals.income_statement.ebitda,
            fundamentals.balance_sheet.total_assets,  
            fundamentals.valuation_ratios.ev_to_ebitda
        )
        .filter(fundamentals.share_class_reference.is_primary_share == True)
        .filter(fundamentals.company_reference.primary_exchange_id != "OTCPK")
        .filter(fundamentals.share_class_reference.is_depositary_receipt == False)
        .filter(~sector_code.in_(excluded_sectors))
        .filter(fundamentals.valuation.market_cap > context.lower_mktcap ) 
        .filter(fundamentals.valuation_ratios.ev_to_ebitda > 0)
        #.filter(fundamentals.valuation.market_cap < context.upper_mktcap ) 
        .order_by(fundamentals.valuation_ratios.ev_to_ebitda.asc())
        .limit(context.limit)
    )
    
    context.longs = fundamental_df.columns[:context.num_positions]

def handle_data(context, data):
    #log.info("in handle_data")
    
    if 'Real Yield' in data and data['Real Yield']['Below 20th Percentile']:
        if not(context.market_expensive):
            context.market_switched = True
        context.market_expensive = True
    elif 'Real Yield' in data and not (data['Real Yield']['Below 20th Percentile']):
        if context.market_expensive:
            context.market_switched = True
        context.market_expensive = False
    
    record(leverage = context.account.leverage)
    info(context, data)

def info(context, data):
    ''' Custom chart and/or log of profit_vs_risk returns and related information
    '''
    # # # # # # # # # #  Options  # # # # # # # # # #
    record_max_lvrg = 1          # Maximum leverage encountered
    record_leverage = 0          # Leverage (context.account.leverage)
    record_q_return = 0          # Quantopian returns (percentage)
    record_pvr      = 0          # Profit vs Risk returns (percentage)
    record_pnl      = 0          # Profit-n-Loss
    record_shorting = 1          # Total value of any shorts
    record_risk     = 0          # Risked, maximum cash spent or shorts in excess of cash at any time
    record_risk_hi  = 0          # Highest risk overall
    record_cash     = 0          # Cash available
    record_cash_low = 1          # Any new lowest cash level
    logging         = 1          # Also log to the logging window conditionally (1) or not (0)
    log_method      = 'risk_hi'  # 'daily' or 'risk_hi'

    c = context                          # For brevity
    new_cash_low = 0                     # To trigger logging in cash_low case
    date = str(get_datetime().date())    # To trigger logging in daily case
    cash = c.portfolio.cash

    if int(cash) < c.cash_low:    # New cash low
        new_cash_low = 1
        c.cash_low   = int(cash)
        if record_cash_low:
            record(CashLow = int(c.cash_low))

    pvr_rtrn      = 0        # Profit vs Risk returns based on maximum spent
    profit_loss   = 0        # Profit-n-loss
    shorts        = 0        # Shorts value
    start         = c.portfolio.starting_cash
    cash_dip      = int(max(0, start - cash))

    if record_cash:
        record(Cash = int(c.portfolio.cash))  # Cash

    if record_leverage:
        record(Lvrg = c.account.leverage)     # Leverage

    if record_max_lvrg:
        if c.account.leverage > c.max_lvrg:
            c.max_lvrg = c.account.leverage
            record(MaxLv = c.max_lvrg)        # Maximum leverage

    if record_pnl:
        profit_loss = c.portfolio.pnl
        record(PnL = profit_loss)             # "Profit and Loss" in dollars

    for p in c.portfolio.positions:
        shrs = c.portfolio.positions[p].amount
        if shrs < 0:
            shorts += int(abs(shrs * data[p].price))

    if record_shorting:
        record(Shorts = shorts)               # Shorts value as a positve

    risk = int(max(cash_dip, shorts))
    if record_risk:
        record(Risk = risk)                   # Amount in play, maximum of shorts or cash used

    new_risk_hi = 0
    if risk > c.risk_hi:
        c.risk_hi = risk
        new_risk_hi = 1

        if record_risk_hi:
            record(RiskHi = c.risk_hi)       # Highest risk overall

    if record_pvr:      # Profit_vs_Risk returns based on max amount actually spent (risk high)
        if c.risk_hi != 0:     # Avoid zero-divide
            pvr_rtrn = 100 * (c.portfolio.portfolio_value - start) / c.risk_hi
            record(PvR = pvr_rtrn)            # Profit_vs_Risk returns

    q_rtrn = 100 * (c.portfolio.portfolio_value - start) / start
    if record_q_return:
        record(QRet = q_rtrn)                 # Quantopian returns to compare to pvr returns curve

    from pytz import timezone
    if logging:
        if log_method == 'risk_hi' and new_risk_hi \
          or log_method == 'daily' and c.date_prv != date \
          or c.date_end == date \
          or new_cash_low:
            qret   = 'QRet '    + '%.1f' % q_rtrn
            mxlv   = 'MaxLv '   + '%.1f' % c.max_lvrg   if record_max_lvrg else ''
            pvr    = 'PvR '     + '%.1f' % pvr_rtrn     if record_pvr      else ''
            pnl    = 'PnL '     + '%.0f' % profit_loss  if record_pnl      else ''
            csh    = 'Cash '    + '%.0f' % cash         if record_cash     else ''
            csh_lw = 'CshLw '   + '%.0f' % c.cash_low   if record_cash_low else ''
            shrt   = 'Shrt '    + '%.0f' % shorts       if record_shorting else ''
            risk   = 'Risk '    + '%.0f' % risk         if record_risk     else ''
            rsk_hi = 'RskHi '   + '%.0f' % c.risk_hi    if record_risk_hi  else ''
            minute = get_datetime().astimezone(timezone('US/Eastern')).time().minute
            log.info('{} {} {} {} {} {} {} {} {} {}'.format(
                    minute, mxlv, qret, pvr, pnl, csh, csh_lw, shrt, risk, rsk_hi))

    if c.date_end == date:    # Log on last day, like cash 125199  portfolio 126890
        log.info('cash {}  portfolio {}'.format(
                int(cash), int(c.portfolio.portfolio_value)))

    c.date_prv = date

         
    
There was a runtime error.

You increased profits on each dollar put to work by 27%, congrats. (The last algo might appear to be worse, it is better on a per-dollar-utilized basis, PvR, Profit vs. Risk).

A couple of the stocks on your no-buy list show up in google finance with a price of 0.0000 right now even tho they've been trading around 10 and 50, LCC and CVH. Consider maybe skipping anything below $2 or $5 or some threshold, keep a count of the number of times each in a dictionary (that it happens), and if the count reaches some ceiling, append to no_buy_list, starting out empty.

Hi Gary,

Again thank you for taking a look at this. The thing is that LCC and CVH do not go to zero they get bought out, this is a situation that the Acquirer’s multiple actually seeks out so I’m not sure it makes sense to exclude these trades. I posted the previous backtest to illustrate that these kinds of trades were the cause of the extra leverage. I added the trades back in and added some logging to the latest backtest to list all open orders at the end of the backtest period. It shows in general that they are profitable trades. For example LCC was bought out by American Airlines (AAL) in 2013, the last price it traded at was$22.35 which is above the $16.36 that the algorithm paid for it. In reality you would be given shares in AAL and be able to sell them (presumably at a similar profit), however this is not modelled in the backtest. So, for the most part the money tied up in the zombie trades could be converted to cash and made available to the algorithm. Your ‘pvr’ metric doesn’t account for this which is throwing off the measure to some degree.

Clone Algorithm
22
Loading...
Backtest from to with initial capital
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
'''
Implemented tactical asset allocation algorithm for value strategies discussed in this post from Alpha Architect,
    http://blog.alphaarchitect.com/2015/07/21/eureka-a-valuation-based-asset-allocation-strategy-that-might-work/

To compute the Real Yield Spread Metric I took the Shiller CAPE ratio data directly from Robert Shiller's website. I computed an annualized inflation rate directly from the CPI data contained in the spreadsheet and used an expanding window for the percentile calculations as well as a 1-month lag on the inflation rate for the Real Yield calculation.

'''

from datetime import timedelta
import pandas as pd
import numpy as np

def initialize(context):
    context.lower_mktcap = 500e6
    context.limit = 500
    context.num_positions = 100
    context.base_weight = 1.0/context.num_positions
    context.rebalance_month = 6 #June
    context.is_rebalance_day = False
    context.shy = symbol('IEF')
    context.market_expensive = False
    context.market_switched = False
    context.lastYear = 2015   
    context.lastMonth = 11
    #context.no_buy_list = [sid(27653),symbol('DCEL'),symbol('TRA'),symbol('MVL'),symbol('AMSY'),sid(7448),symbol('OMM'),sid(42021),symbol('KMR'),symbol('CRDN'),symbol('GYMB'),symbol('PD'),sid(17104),sid(209),symbol('AWIN'),symbol('CVH'),sid(25948),symbol('IAIA'),symbol('PPD'),symbol('WINN'),symbol('SHS'),symbol('GW'),symbol('KG'),sid(764),symbol('HLYW')]
    
    #get Real Yield data
    fetch_csv( 'https://dl.dropboxusercontent.com/u/90042659/Quantopian-RealYield-1880.csv',date_column='Date',date_format='%m/%d/%y',symbol='Real Yield')
    #fetch_csv( 'https://dl.dropboxusercontent.com/u/90042659/Quantopian-RealYield-1945.csv',date_column='Date',date_format='%m/%d/%y',symbol='Real Yield')

    schedule_function(set_rebalance_day,date_rules.month_start(days_offset=10),
                  time_rules.market_close(minutes=5)) 
    schedule_function(rebalance_sell,date_rules.month_start(days_offset=11),
                      time_rules.market_open(minutes=240)) 
    schedule_function(rebalance_buy,date_rules.month_start(days_offset=13),
                      time_rules.market_open(minutes=240))
    #schedule_function(info, date_rules.every_day(), time_rules.market_close())
    schedule_function(check_positions,date_rules.month_start(days_offset=14),
                      time_rules.market_open(minutes=240))
    
    c = context
    c.max_lvrg = 0
    c.risk_hi  = 0
    c.date_prv = ''
    c.cash_low = c.portfolio.starting_cash
    c.date_end = str(get_environment('end').date())
    print '{} to {}  {}  {}'.format(str(get_datetime().date()), c.date_end, 
        int(c.cash_low), get_environment('data_frequency'))

    
def check_positions(context,data):
    today = get_datetime()
    
    if today.year == context.lastYear and today.month == context.lastMonth:
        #log current account
        log.info(" account total positions value: " + str(context.account.total_positions_value))
        log.info(" account leverage: " + str(context.account.leverage))
        log.info(" portfolio cash: " + str(context.portfolio.cash))
        log.info(" portfolio value: " + str(context.portfolio.portfolio_value))
        log.info(" portfolio positions value: " + str(context.portfolio.positions_value))
        log.info(" portfolio returns: " + str(context.portfolio.returns))
        
        open_order_sids = get_open_orders()
        
        #log open orders
        for s in context.portfolio.positions:
            if s in open_order_sids:
                shrs = context.portfolio.positions[s].amount
                cost = context.portfolio.positions[s].cost_basis
                current = data[s].price
                log.info("for s: " + str(s.symbol) )
                log.info(" shares: " + str(shrs))
                log.info(" cost: " + str(cost))
                log.info(" current: " + str(current))
                log.info(" last day traded: " + str(s.end_date))
    
   
def set_rebalance_day(context,data):
    today = get_datetime()
    
    if today.month == context.rebalance_month:
        context.is_rebalance_day = True
    else:
        context.is_rebalance_day = False

def rebalance_sell(context,data):
    if context.is_rebalance_day or (context.market_expensive and context.market_switched) :
        for s in context.portfolio.positions:
            if s not in get_open_orders():
                order_target_percent(s,0)
        
        if context.market_switched:
            context.market_switched = False
        
def rebalance_buy(context,data):
    if not(context.market_expensive) and (context.is_rebalance_day or  context.market_switched):
        for s in context.longs:
            if s in data and s.symbol != 'NP':
                if (s not in get_open_orders()):# and (s not in context.no_buy_list):
                    order_target_percent(s,context.base_weight)

        context.is_rebalance_day = False
        if context.market_switched:
            context.market_switched = False
        
def before_trading_start(context,data): 
    if context.is_rebalance_day or  context.market_switched:
        getstocks(context)
        update_universe(context.longs)
        
    #make sure open order sids in data for loggin in check_positions
    today = get_datetime()
    if today.year == context.lastYear and today.month == context.lastMonth:
        update_universe(list(get_open_orders().keys()))
        
def getstocks(context):     
    #103 = Financials, 207 = Utilities, 104 = Real Estate
    excluded_sectors = [103, 207]
    sector_code = fundamentals.asset_classification.morningstar_sector_code
    fundamental_df = get_fundamentals(
        query(
            sector_code,
            fundamentals.valuation.market_cap,
            fundamentals.valuation.enterprise_value,
            fundamentals.cash_flow_statement.capital_expenditure,
            fundamentals.operation_ratios.roic,
            fundamentals.income_statement.ebit,
            fundamentals.income_statement.ebitda,
            fundamentals.balance_sheet.total_assets,  
            fundamentals.valuation_ratios.ev_to_ebitda
        )
        .filter(fundamentals.share_class_reference.is_primary_share == True)
        .filter(fundamentals.company_reference.primary_exchange_id != "OTCPK")
        .filter(fundamentals.share_class_reference.is_depositary_receipt == False)
        .filter(~sector_code.in_(excluded_sectors))
        .filter(fundamentals.valuation.market_cap > context.lower_mktcap ) 
        .filter(fundamentals.valuation_ratios.ev_to_ebitda > 0)
        #.filter(fundamentals.valuation.market_cap < context.upper_mktcap ) 
        .order_by(fundamentals.valuation_ratios.ev_to_ebitda.asc())
        .limit(context.limit)
    )
    
    context.longs = fundamental_df.columns[:context.num_positions]

def handle_data(context, data):
    #log.info("in handle_data")
    
    if 'Real Yield' in data and data['Real Yield']['Below 20th Percentile']:
        if not(context.market_expensive):
            context.market_switched = True
        context.market_expensive = True
    elif 'Real Yield' in data and not (data['Real Yield']['Below 20th Percentile']):
        if context.market_expensive:
            context.market_switched = True
        context.market_expensive = False
    
    record(leverage = context.account.leverage)
    info(context, data)

def info(context, data):
    ''' Custom chart and/or log of profit_vs_risk returns and related information
    '''
    # # # # # # # # # #  Options  # # # # # # # # # #
    record_max_lvrg = 1          # Maximum leverage encountered
    record_leverage = 0          # Leverage (context.account.leverage)
    record_q_return = 0          # Quantopian returns (percentage)
    record_pvr      = 1          # Profit vs Risk returns (percentage)
    record_pnl      = 0          # Profit-n-Loss
    record_shorting = 0          # Total value of any shorts
    record_risk     = 0          # Risked, maximum cash spent or shorts in excess of cash at any time
    record_risk_hi  = 0          # Highest risk overall
    record_cash     = 0          # Cash available
    record_cash_low = 1          # Any new lowest cash level
    logging         = 1          # Also log to the logging window conditionally (1) or not (0)
    log_method      = 'risk_hi'  # 'daily' or 'risk_hi'

    c = context                          # For brevity
    new_cash_low = 0                     # To trigger logging in cash_low case
    date = str(get_datetime().date())    # To trigger logging in daily case
    cash = c.portfolio.cash

    if int(cash) < c.cash_low:    # New cash low
        new_cash_low = 1
        c.cash_low   = int(cash)
        if record_cash_low:
            record(CashLow = int(c.cash_low))

    pvr_rtrn      = 0        # Profit vs Risk returns based on maximum spent
    profit_loss   = 0        # Profit-n-loss
    shorts        = 0        # Shorts value
    start         = c.portfolio.starting_cash
    cash_dip      = int(max(0, start - cash))

    if record_cash:
        record(Cash = int(c.portfolio.cash))  # Cash

    if record_leverage:
        record(Lvrg = c.account.leverage)     # Leverage

    if record_max_lvrg:
        if c.account.leverage > c.max_lvrg:
            c.max_lvrg = c.account.leverage
            record(MaxLv = c.max_lvrg)        # Maximum leverage

    if record_pnl:
        profit_loss = c.portfolio.pnl
        record(PnL = profit_loss)             # "Profit and Loss" in dollars

    for p in c.portfolio.positions:
        shrs = c.portfolio.positions[p].amount
        if shrs < 0:
            shorts += int(abs(shrs * data[p].price))

    if record_shorting:
        record(Shorts = shorts)               # Shorts value as a positve

    risk = int(max(cash_dip, shorts))
    if record_risk:
        record(Risk = risk)                   # Amount in play, maximum of shorts or cash used

    new_risk_hi = 0
    if risk > c.risk_hi:
        c.risk_hi = risk
        new_risk_hi = 1

        if record_risk_hi:
            record(RiskHi = c.risk_hi)       # Highest risk overall

    if record_pvr:      # Profit_vs_Risk returns based on max amount actually spent (risk high)
        if c.risk_hi != 0:     # Avoid zero-divide
            pvr_rtrn = 100 * (c.portfolio.portfolio_value - start) / c.risk_hi
            record(PvR = pvr_rtrn)            # Profit_vs_Risk returns

    q_rtrn = 100 * (c.portfolio.portfolio_value - start) / start
    if record_q_return:
        record(QRet = q_rtrn)                 # Quantopian returns to compare to pvr returns curve

    from pytz import timezone
    if logging:
        if log_method == 'risk_hi' and new_risk_hi \
          or log_method == 'daily' and c.date_prv != date \
          or c.date_end == date \
          or new_cash_low:
            qret   = 'QRet '    + '%.1f' % q_rtrn
            mxlv   = 'MaxLv '   + '%.1f' % c.max_lvrg   if record_max_lvrg else ''
            pvr    = 'PvR '     + '%.1f' % pvr_rtrn     if record_pvr      else ''
            pnl    = 'PnL '     + '%.0f' % profit_loss  if record_pnl      else ''
            csh    = 'Cash '    + '%.0f' % cash         if record_cash     else ''
            csh_lw = 'CshLw '   + '%.0f' % c.cash_low   if record_cash_low else ''
            shrt   = 'Shrt '    + '%.0f' % shorts       if record_shorting else ''
            risk   = 'Risk '    + '%.0f' % risk         if record_risk     else ''
            rsk_hi = 'RskHi '   + '%.0f' % c.risk_hi    if record_risk_hi  else ''
            minute = get_datetime().astimezone(timezone('US/Eastern')).time().minute
            log.info('{} {} {} {} {} {} {} {} {} {}'.format(
                    minute, mxlv, qret, pvr, pnl, csh, csh_lw, shrt, risk, rsk_hi))

    if c.date_end == date:    # Log on last day, like cash 125199  portfolio 126890
        log.info('cash {}  portfolio {}'.format(
                int(cash), int(c.portfolio.portfolio_value)))

    c.date_prv = date

         
    
There was a runtime error.

While acquisitions are an interesting thing to tackle for sure, regarding the last line, pvr essentially operates on pnl which is not changed by open orders. Perhaps you are saying in the real world one would be able to trade some acquired shares that in the backtest environment are no longer in data , looking forward to a run-it-ready code example if humanly possible to model in a backtest, and until then, pvr is not throwing anything off, it is passive and just factually measuring what's there, basically pnl divided by most_risked i.e. amount needed in the brokerage account to achieve that result. The only difference from the standard returns calc is the denominator, transacted instead of starting capital.

Hi, I was hoping to look at this algorithmn, but it uses a .csv from dropbox which is not available. Any ideas on how to run it w/o that or where to get the source data?

Hello,
When I cloned this algorithm nd tried too runa backtest, I got this error. How do I get the dropbox content?

thanks

-kamal

Something went wrong. Sorry for the inconvenience. Try using the built-in debugger to analyze your code. If you would like help, send us an email.
Exception: Problem reaching https://dl.dropboxusercontent.com/u/90042659/Quantopian-RealYield-1945.csv
There was a runtime error on line 26.