Back to Community
Hedged Bridesmaid strategy

I read somewhere a vaguely described "Bridesmaid Strategy" in which you go long the second-best performing sector ETF of the previous year and supposedly that reliably beats SPY. It's a bunch of crap. It is not at all a reliable indicator. Depending on how you time the rebalance and the calculation it either just barely beats SPY or significantly underperforms, and generally exhibits quite a bit more volatility than SPY.

While messing around with the code though, I tried shorting the best performer as a hedge against market fluctuations. It seems the best performer has a tendency to mean revert while the second-best has a tendency to out-perform. Together they even out market volatility. So here's my hedged-variation on the Bridesmaid Strategy.

I think the problem with strategies like this that are based on general prior observations is that they are in their essence over-fit. It might continue to perform in the future... or it might not.

Clone Algorithm
70
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
 
def initialize(context):
    """
    Called once at the start of the algorithm.
    """   
    #schedule_function(close_positions, date_rules.month_start(2), time_rules.market_open()) 
    schedule_function(figure_it_out, date_rules.month_end(3), time_rules.market_open(minutes=10))
    schedule_function(my_rebalance, date_rules.month_end(), time_rules.market_open())
    schedule_function(daily_check, date_rules.every_day(), time_rules.market_open())
    context.figured_out = False
      
    # Record tracking variables at the end of each day.
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())
     
    context.secs =   [ sid(8554),   # SPY S&P 500
                       sid(22148),  # OEF S&P 100
                       sid(19662),  # XLY Consumer Discrectionary SPDR Fund   
                       sid(19656),  # XLF Financial SPDR Fund  
                       sid(19658),  # XLK Technology SPDR Fund  
                       sid(19655),  # XLE Energy SPDR Fund  
                       sid(19661),  # XLV Health Care SPRD Fund  
                       sid(19657),  # XLI Industrial SPDR Fund  
                       sid(19659),  # XLP Consumer Staples SPDR Fund   
                       sid(19654),  # XLB Materials SPDR Fund  
                       sid(19660),  # XLU Utilities SPRD Fund
                       sid(26669),  # VNQ Real Estate
                       sid(21525),  # IYZ Telecommunications
                     ]
    
    context.spy = sid(8554)
    
    #set_commission(commission.PerShare(cost=0, min_trade_cost=0))
 
def before_trading_start(context, data):
    pass


def close_positions(context,data):
    for stock in context.portfolio.positions:
        order_target_percent(stock, 0)
    

def my_rebalance(context,data):
    if not context.figured_out:
        end_of_december(context,data)

    
    for stock in context.secs:
        if stock != context.second_best_stock and stock != context.best_stock and not get_open_orders(stock):
            order_target_percent(stock, 0)

    if not get_open_orders(context.second_best_stock):
        order_target_percent(context.second_best_stock, 0.45)
    if not get_open_orders(context.best_stock):
        order_target_percent(context.best_stock, -0.45)

    
def figure_it_out(context,data):
    today = get_datetime() 
    if today.month == 12:
        end_of_december(context,data)
    
def end_of_december(context,data):
    gains = {}
    for stock in context.secs:
        hist = data.history(stock,'price',240,'1d')  #20: 25.6%
        gains[stock] =  max( hist[-4] / hist[0] , hist[-1] / hist[0] ) 
        
    sorted_gains = sorted(((v,k) for k,v in gains.items()))
    x, context.best_stock = sorted_gains[-1]
    x, context.second_best_stock = sorted_gains[-2]
    x, worst_stock = sorted_gains[0]
    
    context.figured_out = True

def my_record_vars(context, data):
    record(l=context.account.leverage)
    
def daily_check(context, data):
    if context.account.leverage > 0.95:
        my_rebalance(context,data)
There was a runtime error.
5 responses

Here's a longer-timeframe, using TLT as the benchmark. Almost similar returns as TLT with significantly less volatility. Might be useful as a "risk-free" cash substitute?

Clone Algorithm
70
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
 
def initialize(context):
    """
    Called once at the start of the algorithm.
    """   
    #schedule_function(close_positions, date_rules.month_start(2), time_rules.market_open()) 
    schedule_function(figure_it_out, date_rules.month_end(3), time_rules.market_open(minutes=10))
    schedule_function(my_rebalance, date_rules.month_end(), time_rules.market_open())
    schedule_function(daily_check, date_rules.every_day(), time_rules.market_open())
    context.figured_out = False
      
    # Record tracking variables at the end of each day.
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())
     
    context.secs =   [ sid(8554),   # SPY S&P 500
                       sid(22148),  # OEF S&P 100
                       sid(19662),  # XLY Consumer Discrectionary SPDR Fund   
                       sid(19656),  # XLF Financial SPDR Fund  
                       sid(19658),  # XLK Technology SPDR Fund  
                       sid(19655),  # XLE Energy SPDR Fund  
                       sid(19661),  # XLV Health Care SPRD Fund  
                       sid(19657),  # XLI Industrial SPDR Fund  
                       sid(19659),  # XLP Consumer Staples SPDR Fund   
                       sid(19654),  # XLB Materials SPDR Fund  
                       sid(19660),  # XLU Utilities SPRD Fund
                       sid(26669),  # VNQ Real Estate
                       sid(21525),  # IYZ Telecommunications
                     ]
    
    context.spy = sid(8554)
    set_benchmark(sid(23921))
    
    #set_commission(commission.PerShare(cost=0, min_trade_cost=0))
 
def before_trading_start(context, data):
    pass


def close_positions(context,data):
    for stock in context.portfolio.positions:
        order_target_percent(stock, 0)
    

def my_rebalance(context,data):
    if not context.figured_out:
        end_of_december(context,data)

    
    for stock in context.secs:
        if stock != context.second_best_stock and stock != context.best_stock and not get_open_orders(stock):
            order_target_percent(stock, 0)

    if not get_open_orders(context.second_best_stock):
        order_target_percent(context.second_best_stock, 0.45)
    if not get_open_orders(context.best_stock):
        order_target_percent(context.best_stock, -0.45)

    
def figure_it_out(context,data):
    today = get_datetime() 
    if today.month == 12:
        end_of_december(context,data)
    
def end_of_december(context,data):
    gains = {}
    for stock in context.secs:
        hist = data.history(stock,'price',240,'1d')  #20: 25.6%
        gains[stock] =  max( hist[-4] / hist[0] , hist[-1] / hist[0] ) 
        
    sorted_gains = sorted(((v,k) for k,v in gains.items()))
    x, context.best_stock = sorted_gains[-1]
    x, context.second_best_stock = sorted_gains[-2]
    x, worst_stock = sorted_gains[0]
    
    context.figured_out = True

def my_record_vars(context, data):
    record(l=context.account.leverage)
    
def daily_check(context, data):
    if context.account.leverage > 0.95:
        my_rebalance(context,data)
There was a runtime error.

I find this phenomenon incredibly amusing. It doesn't beat the market, but it works. Thanks for sharing.

It's continued to work since I posted it... but there's no reason why it couldn't fail in the future.

Also be aware that they're changing the sector categorizations next year: http://www.etf.com/sections/features-and-news/seismic-sector-shift-shake-30-etfs?nopaging=1

hi Viridian, not sure if you're still pursuing this type of strategy but just wanted to check in on the results to date. I'm trying to add a ranking system by industry rather than sector to a fundamental long/short strategy but have not had much luck in the coding department.

No, I think it's a terrible strategy. There aren't enough data points to make it statistically viable.