Back to Community
Breadth Momentum and Vigilant Asset Allocation (VAA)

Breadth Momentum and Vigilant Asset Allocation (VAA);

I have found my adding XIV the perf increases while the drawdowns are still in hand....

I found this interesting paper on momentum stratgeies and I build a flexible version of it so we can experiment with the assets and with the number of B and T assets

Read moare at Winning More by Losing Less By Wouter J. Keller and Jan Willem Keuning1 July 14, 2017, v0.99

Clone Algorithm
149
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
# BREADTH MOMENTUM STRATEGY
# FROM : 	Breadth Momentum and Vigilant Asset Allocation (VAA); 
# 			Winning More by Losing Less  By Wouter J. Keller and Jan Willem Keuning1 July 14, 2017, v0.99 
#			https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3002624

# - suitable for IB cash accounts (T+3)
# - prevent trading too much by limiting threshold for orders

# ---------------------------------------------------------------------------------  
from collections import defaultdict
#Some Risk-On universes
core_etf 	=   symbols('QQQ', 'XLP', 'TLT', 'IEF')
tact_etf	= symbols('XLV', 'XLY', 'TLO', 'GLD')

VAAG12 		= symbols('SPY', 'IWM', 'QQQ', 'VGK', 'EWJ', 'VWO', 'VNQ', 'GSG', 'GLD', 'TLT', 'LQD', 'HYG')
VAAG4  		= symbols('SPY', 'VEA', 'VWO', 'BND')

alletf =  symbols('QQQ', 'XLP', 'TLT', 'IEF','SPY', 'IWM', 'QQQ', 'VGK', 'EWJ', 'VWO', 'VNQ', 'GSG', 'GLD', 'TLT', 'LQD', 'HYG','XLV', 'XLY', 'TLO', 'GLD','VEA')
#Some Risk Off (Cash) universes
CASH = symbols('SHY', 'IEF', 'LQD')

TOP = 2 #if there are no X BAD assets how many assets do I invest in
BAD = 1 #How many bad assets are allowed (assets with negative return)

RISK=VAAG4 #Which Riskasset universe to use?

lev= 0.98 # max leverage to use

TplusX= 0 # if you have a T+3 Cash account use 3 else 0 for margin accounts
Month_Offset= 0 #when using TplusX the max month offset can be 3
# ---------------------------------------------------------------------------------  
def initialize(context):
    
    schedule_function(define_weights, date_rules.month_end(Month_Offset), time_rules.market_open(minutes=64))
    log.info('define_weights for last trading day of the month')
    schedule_function(trade_sell, date_rules.month_end(Month_Offset), time_rules.market_open(minutes=65)) 
    log.info('trade_sell for last trading day of the month')
    if TplusX>0:
    	schedule_function(trade_buy, date_rules.month_start(max(0,TplusX-1-Month_Offset)), time_rules.market_open(minutes=66))   
    	log.info('trade_buy for month day '+ str(max(0,TplusX-Month_Offset)))
    else:
        schedule_function(trade_buy, date_rules.month_end(Month_Offset), time_rules.market_open(minutes=66))   
        log.info('trade_buy last trading day of the month')
        
    stocks = core_etf+ tact_etf
    context.sell = {}
    context.buy = {}
    for stock in stocks:
        context.sell[stock]=0
        context.buy[stock]=0
            
    context.difference_perc = 0.02

    context.stops         = {}
    context.stoploss      = 0.30
    context.stoppedout    = []



def trade_sell(context,data): 
    log.info('\n\n! running Trade Sell '+str(get_datetime()))
    for stocksymbol in context.sell:
        try:
            if context.sell[stocksymbol] != -1 and data.can_trade(stocksymbol):
                order_target_percent(stocksymbol, context.sell[stocksymbol])
                log.info('-- Stock: ' + str(stocksymbol) + ': Rebalanced so % is now '+str(context.sell[stocksymbol]) +'%')
            else:
                log.info('!  Stock: ' + str(stocksymbol) + ': No need to sell' )
        except:
            log.info("FAIL "+str(stocksymbol))
            
def trade_buy(context,data):
    log.info('\n\n! running Trade Buy '+str(get_datetime()))
    for stocksymbol in context.buy:
        try:
            if context.buy[stocksymbol] != -1 and data.can_trade(stocksymbol):
                order_target_percent(stocksymbol, context.buy[stocksymbol])
                log.info('++ Stock: ' + str(stocksymbol) + ': bought '+str(context.buy[stocksymbol]) +'%')
            else:
                log.info('!  Stock: ' + str(stocksymbol) + ': No need to buy' )
        except:
            log.info("FAIL "+str(stocksymbol))

            
            
            
def define_weights(context,data):
    
    log.info('\n\n! running define_weights '+str(get_datetime()))
    m = data.history(RISK+CASH, 'price', 400, '1d')
    p = m.resample('M',  closed='right', label='right').last()
    W13612 = 12*(p.iloc[-1]/p.iloc[-2]) + 4*(p.iloc[-1]/p.iloc[-4]) + 2*(p.iloc[-1]/p.iloc[-7]) + 1*(p.iloc[-1]/p.iloc[-13]) -19
    record(spy=W13612[symbol('SPY')])
    riskon  = W13612[W13612.index.isin(RISK)]
    riskoff = W13612[W13612.index.isin(CASH)]
    pos_mom = riskon[riskon >= 0.0]  
    
    if len(pos_mom)+BAD-1 >= len(RISK):
        #now we are going to find the highest TOP
        log.info('!! Risk ON Universe chosen, top '+str(TOP))
    	universe_to_invest = riskon.sort_values(ascending=False).head(TOP)
        
        for stock in CASH:
            assign_weights(context, stock, 0.0) 
        for stock in RISK:
            tar_perc = 0.0
            if stock in universe_to_invest: 
                tar_perc = lev/len(universe_to_invest)
            assign_weights(context, stock, tar_perc)
    else:
        log.info('!! Risk OFF Universe chosen')
        universe_to_invest = riskoff.sort_values(ascending=False).head(1)
        for stock in RISK:
            assign_weights(context, stock, 0.0)
        for stock in CASH:
            tar_perc = 0.0
            if stock in universe_to_invest: 
                tar_perc = lev/len(universe_to_invest)
            assign_weights(context, stock, tar_perc)
    
    
def assign_weights(context, stock, tar_perc):
    if stock not in context.portfolio.positions :       
        context.buy[stock]=tar_perc
        if tar_perc <> 0.0: 
            log.info(stock.symbol+'++ 1st run: current percent 0.00%; target: '+str(round(tar_perc*100,2))+'%' )
        return
    else:
        net_val = context.account.net_liquidation 
        stock_owned=context.portfolio.positions[stock]        
        cur_perc = (stock_owned.amount * stock_owned.last_sale_price)/net_val       
        if cur_perc == tar_perc:
            context.buy[stock_owned.sid]= -1
            context.sell[stock_owned.sid]= -1
            return
        elif cur_perc >  tar_perc+context.difference_perc:
            context.sell[stock_owned.sid]=tar_perc
            context.buy[stock_owned.sid]= -1
        elif cur_perc <  tar_perc-context.difference_perc:
            context.buy[stock_owned.sid]=tar_perc
            context.sell[stock_owned.sid]= -1
        else:
            context.buy[stock_owned.sid]= -1
            context.sell[stock_owned.sid]= -1
            return
        log.info(stock_owned.asset.symbol+' current percent ' +str(round(cur_perc*100,2))+'%; target: '+str(round(tar_perc*100,2))+'%' )
 
    
    

                
                
def before_trading_start(context,data):  
    record(leverage = context.account.leverage)  
    
    
    
def handle_data(context, data):
    c=context
    for position in c.portfolio.positions.itervalues():
        if position.amount == 0:
            if position.asset.symbol in c.stops: del c.stops[position.asset.symbol]
            continue
        elif position.asset.symbol not in c.stops:
            stoploss= c.stoploss if position.amount > 0 else -c.stoploss
            c.stops[position.asset.symbol]=position.last_sale_price*(1-stoploss)
            #log.info(' ! I have added '+str(position.asset.symbol)+' to Stops @ '+str((position.last_sale_price)*(1-stoploss)))
        elif c.stops[position.asset.symbol] > position.last_sale_price and position.amount > 0:
            #sell
            log.info(' ! '+str(position.asset.symbol)+'- (Long) has hit stoploss @ '+str(position.last_sale_price))
            if not get_open_orders(position.sid):
                order_target_value(position.sid,0.0)
                c.stoppedout.append(position.asset.symbol)
                del c.stops[position.asset.symbol]
        elif c.stops[position.asset.symbol] < position.last_sale_price and position.amount < 0:
            #sell
            log.info(' ! '+str(position.asset.symbol)+'- (Short) has hit stoploss @ '+str(position.last_sale_price))
            if not get_open_orders(position.sid): 
                order_target_value(position.sid,0.0)
                c.stoppedout.append(position.asset.symbol)
                del c.stops[position.asset.symbol]
        elif c.stops[position.asset.symbol] < position.last_sale_price*(1- c.stoploss) and position.amount > 0:
            c.stops[position.asset.symbol]=position.last_sale_price*(1- c.stoploss)
            #log.info(' ! I have updated '+str(position.asset.symbol)+'- (Long) to stop @ '+str((position.last_sale_price)*(1- c.stoploss)))
        elif c.stops[position.asset.symbol] > position.last_sale_price*(1+ c.stoploss) and position.amount < 0:
            c.stops[position.asset.symbol]=position.last_sale_price*(1+ c.stoploss)
           # log.info(' ! I have updated '+str(position.asset.symbol)+'- (Short) to stop @ '+str((position.last_sale_price)*(1+ c.stoploss)))
    
    
There was a runtime error.