A Beta value closer to zero helps favorably in the contest. Beta is volatility compared to market volatility.
This code shows a way to move Beta toward zero automatically (using spy, tlt as examples) and also how to calculate Beta.
An example of dynamically adjusting long, short instead.
'''
Zero-Beta targeting example.
Automatically adjust proportions of spy and tlt to hold Beta to around 0.0 or beta_target.
c.beta_limit is one strictness adjustment, there are others.
In terms of *effect* on Beta generally:
- Longs (many) tend to be like SPY (increase)
- Short often acts similar to TLT here (decrease)
'''
import pandas as pd
def initialize(context):
c = context
c.spy = sid(8554)
c.tlt = sid(23921)
c.beta = 1.0 # Assumed starting beta
c.beta_target = 0.0 # Target any Beta you wish
c.beta_limit = .01 # Pos/neg threshold, balance only outside of this either side of target
c.spy_limit_hi = .95 # Max ratio of spy to portfolio
c.spy_limit_lo = 1.0 - c.spy_limit_hi
c.beta_df = pd.DataFrame([], columns=['pf', 'spy'])
schedule_function(balance, date_rules.week_start(), time_rules.market_open())
def balance(context, data):
c = context
if not c.portfolio.positions: # Initial positions to start, reusing spy_limit
order_target_percent(c.spy, c.spy_limit_hi)
order_target_percent(c.tlt, c.spy_limit_lo)
return
beta = calc_beta(c)
bzat = beta - c.beta_target # bzat is beta-zero adjusted for target
if (c.beta_target - c.beta_limit) < beta < (c.beta_target + c.beta_limit): # Skip if inside boundaries
return
# -------- Adjust positions to move toward target Beta --------
pos_val = c.portfolio.positions_value
spy_val = c.portfolio.positions[c.spy].amount * data.current(c.spy, 'price')
spy_ratio = spy_val / pos_val
# Reduce spy & increase tlt or visa-versa
# The further away from target Beta, the stronger the adjustment.
# https://www.quantopian.com/posts/scaling for explanation of next line ...
temperance = scale(abs(bzat), 0, .30, .35, .80) # Not straight Beta, a portion of it.
adjust = max(c.spy_limit_lo, spy_ratio - (bzat * temperance))
adjust = min(c.spy_limit_hi, adjust) # spy ratio no higher than spy_limit_hi
log.info('b{} spy {} to {}'.format('%.2f' % beta, '%.2f' % spy_ratio, '%.2f' % adjust))
order_target_percent(c.spy, adjust)
order_target_percent(c.tlt, 1.0 - adjust) # Remainder for tlt
def before_trading_start(context, data):
c = context
c.beta_df = c.beta_df.append({ # Beta calc prep
'pf' : c.portfolio.portfolio_value,
'spy': data.current(c.spy, 'price')}, ignore_index=True)
c.beta_df['spy_chg'] = c.beta_df.spy.pct_change()
c.beta_df[ 'pf_chg'] = c.beta_df.pf .pct_change()
c.beta_df = c.beta_df.ix[-252:] # trim to one year
def calc_beta(c): # Calculate current Beta value
if len(c.beta_df.spy.values) < 3: return c.beta
beta = c.beta_df.pf_chg.cov(c.beta_df.spy_chg) / c.beta_df.spy_chg.var()
record(beta_calculated = beta)
return beta
def scale(wild, a_lo, a_hi, b_lo, b_hi):
''' Based on wild value relative to a_lo_hi range,
return its analog within b_lo_hi, with min b_lo and max b_hi
'''
return min(b_hi, max(b_lo, (b_hi * (wild - a_lo)) / (a_hi - a_lo)))