Back to Community
Betting Against Beta

Betting Against Beta from 2/2003 - 3/2019

Clone Algorithm
4
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
"""
This is a template algorithm on Quantopian for you to adapt and fill in.
"""
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
import numpy as np
import pandas as pd
import scipy as sp
from scipy.stats.mstats import winsorize
from scipy.stats import norm, linregress
import math
from quantopian.pipeline.data import morningstar, Fundamentals
from quantopian.pipeline.factors import CustomFactor, Returns, SimpleBeta, DailyReturns
from datetime import datetime, timedelta
from quantopian.pipeline.filters import QTradableStocksUS
from quantopian.pipeline.filters.morningstar import Q500US, Q1500US
from quantopian.pipeline.classifiers.morningstar import Sector
from sklearn.linear_model import LinearRegression
import cvxopt as opt
from cvxopt import blas, solvers
solvers.options['show_progress'] = False


def initialize(context):
    """
    Called once at the start of the algorithm.
    """  
    context.base_universe = Q1500US()
    set_benchmark(symbol('SHY'))
   
    # Rebalance every month end at open.
    schedule_function(my_rebalance, date_rule=date_rules.month_end(),\
                      time_rule=time_rules.market_open(minutes=30))
    
    attach_pipeline(BABPipe(context), 'bab_pipeline')

    context.strategies = {'bab':FactorStrategy('bab')}

            
        
def my_rebalance(context,data):
    """
    Execute orders according to our schedule_function() timing. 
    """
    bab_results = pipeline_output('bab_pipeline')
    bab_results['date'] = get_datetime()
    bab_results.index.names = ['ticker']
    bab_results = bab_results.reset_index().sort_values(['date','ticker']).set_index(['date','ticker'])
    bab_orders = factor_port_bab(bab_port=bab_results)
    context.strategies['bab'].RecordPortRet(data, context, bab_orders)
    
    if len(context.strategies['bab'].weighted_orders.keys()) >= 1:        
        context.strategies['bab'].Trade(data, context, 1.0, bab_orders)
        


class BetaVols(CustomFactor):
    outputs = ['lnret_vol','spy_lnret_vol']
    inputs = [DailyReturns(), DailyReturns()[symbol('SPY')]]
    window_length = 252
    def compute(self,today,assets,out,rets,spy_rets):
        out.lnret_vol[:] = np.std(np.log(1+rets),axis=0)
        out.spy_lnret_vol[:] = np.std(np.log(1+spy_rets),axis=0)
        
 
class BetaCorr(CustomFactor):
    inputs = [USEquityPricing.close]
    # window_length = 252*5
    def compute(self,today,assets,out,close):
        spy = close[:,np.where(assets == 8554)] 
        spy = np.log(spy)
        ret_3d_spy = (spy-np.roll(spy,3))[3:]
        
        for i in range(len(out)):
            ret_3d = close[:,i]
            ret_3d = np.log(ret_3d)
            ret_3d = (ret_3d-np.roll(ret_3d,3))[3:]

            corr = np.concatenate((ret_3d.reshape(1,-1), ret_3d_spy.reshape(1,-1)))
            corr = np.corrcoef(corr)
            out[i] = corr[0][1]
        

def BABPipe(context):
    Data_Pipe = Pipeline()

    lnret_vol, spy_lnret_vol = BetaVols()
    
    window_length = min(252*5, np.busday_count(datetime(2002,2,2).date(),get_datetime().date()))
    # window_length = np.busday_count(datetime(2002,2,2).date(),get_datetime().date())    
    corr = BetaCorr(window_length=window_length)
    
    beta = corr*(lnret_vol/spy_lnret_vol)
    
    beta_adj = (0.6*beta)+(0.4*1)    
    Data_Pipe.add(beta,'beta')
    Data_Pipe.add(beta_adj,'beta_adj')
    
    universe = context.base_universe & beta.notnull()
    
    beta_rank = beta.rank(mask=universe)
    high_beta = beta.percentile_between(50,100,mask=universe)
    Data_Pipe.add(beta_rank,'beta_rank')
    Data_Pipe.add(high_beta,'high_beta')
    
    Data_Pipe.set_screen(universe)
    return Data_Pipe


def factor_port_bab(bab_port):
    rank_mu = bab_port['beta_rank'].mean()
    k = 2.0/(bab_port['beta_rank'] - rank_mu).abs().sum()
    weights = k*(bab_port['beta_rank'] - rank_mu)
    
    bab_port['weight'] = weights
    bab_port['weighted_beta_adj'] = bab_port.apply(lambda x: x.beta_adj*x.weight, axis=1)
    bab_port['weight_beta_levered'] = np.nan
    
    low_port_beta = bab_port.loc[bab_port.weight < 0, 'weighted_beta_adj'].sum()
    high_port_beta = bab_port.loc[bab_port.weight > 0, 'weighted_beta_adj'].sum()

    bab_port.loc[bab_port.weight < 0, 'weight_beta_levered'] = \
    (bab_port.loc[bab_port.weight < 0, 'weight']*(1/low_port_beta)).abs()

    bab_port.loc[bab_port.weight > 0, 'weight_beta_levered'] = \
    -1*(bab_port.loc[bab_port.weight > 0, 'weight']*(1/high_port_beta)).abs()
    
    bab_port.index = bab_port.index.droplevel(level=0)
    
    order_weights = pd.DataFrame(bab_port[['weight_beta_levered']].sum(axis=1),columns=['weight'])
    
    return order_weights


# Strategy class to keep track of basic strategy parameters
class Strategy:
    def __init__(self, name):
        self.name = name
        #Keep track if we are invested or Flat
        self.raw_orders = {}
        self.weighted_orders = {}
        #Keep track of the last shares bought from this strategy so we can exit only those shares
        self.port_rets = {}
        #init these dicts
        self.port_vars = {}
        
# Sub-Strategy class to implement specific rules
class FactorStrategy(Strategy):
    def RecordPortRet(self, data, context, order_weights):
        self.weighted_orders[get_datetime().date()] = order_weights
                
        keys = set(self.weighted_orders.keys())
        unwanted = set([i for i in keys if i <= (get_datetime() + timedelta(days=-10*365.25)).date()])
        for unwanted_key in unwanted: del self.weighted_orders[unwanted_key]

        if len(self.weighted_orders.keys()) > 1:
            last_date = sorted(self.weighted_orders.keys())[-2]
            last_orders = self.weighted_orders[last_date]
            price_data = data.history(last_orders.index.tolist(), fields = 'price', bar_count=40, frequency='1d')
            rets = price_data.loc[last_date:, :]
            rets = np.log(rets/rets.shift())
            port_ret = rets.mul(last_orders['weight']).sum().sum()
            self.port_rets[last_day_of_month(last_date)] = port_ret         
            
    def RecordPortVar(self, data, context, order_weights):
        self.raw_orders[get_datetime().date()] = order_weights
                
        keys = set(self.raw_orders.keys())
        unwanted = set([i for i in keys if i <= (get_datetime() + timedelta(days=-10*365.25)).date()])
        for unwanted_key in unwanted: del self.raw_orders[unwanted_key]

        if len(self.raw_orders.keys()) > 1:
            last_date = sorted(self.raw_orders.keys())[-2]
            last_orders = self.raw_orders[last_date]
            price_data = data.history(last_orders.index.tolist(), fields = 'price', bar_count=40, frequency='1d')
            var = price_data.loc[last_date:,:]
            var = np.log(var/var.shift())
            var = var.mul(last_orders['weight'])
            var = var.sum(axis=1)
            var = var**2
            var = var.values
            var = np.nansum(var)
            self.port_vars[last_day_of_month(last_date)] = var
        
    
    def Trade(self, data, context, allocation, order_weights):
        if allocation > 0:
            # Close all current positions
            for stock in context.portfolio.positions:
                if stock not in order_weights.index:
                   order_target(stock, 0)
            #Order
            for stock, weight in zip(order_weights.index, order_weights['weight']):
                if stock in context.portfolio.positions:
                    order_target_percent(stock, allocation*weight)
                else:
                    order_percent(stock, allocation*weight)

                

def last_day_of_month(any_day):
    next_month = any_day.replace(day=28) + timedelta(days=4)  # this will never fail
    return next_month - timedelta(days=next_month.day)

        
standardize = lambda x: (x-x.mean())/x.std()   
#
There was a runtime error.