Back to Community
Looking for loopholes In seemingly stable algo

This in an algo that I did not really modify, simply increased the length of backtest run to the present. It looks good as it stands, and i am impressed that it follows the smooth trajectory of the benchmark while beating its returns. However, it looks too good. I need ya'll to poke holes in this algo to find potential weaknesses. Have at it

Clone Algorithm
84
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
import math
import numpy as np
import collections

trendPeriods = 50
triggerPeriods = 5
weightsPctChangeThreshold = 5.0

def initialize(context):
    secMgr = SecurityManager()
    secMgr.Add("XLE", sid(19655), 0) # Energy Select Sector SPDR
    secMgr.Add("XLF", sid(19656), 0) # Financial Select Sector SPDR
    secMgr.Add("XLI", sid(19657), 0) # Industrial Select Sector SPDR
    secMgr.Add("XLK", sid(19658), 0) # Technology Select Sector SPDR
    secMgr.Add("XLP", sid(19659), 0) # Consumer Staples Select Sector SPDR
    secMgr.Add("XLU", sid(19660), 0) # Utilities Select Sector SPDR
    secMgr.Add("XLV", sid(19661), 0) # Healthcare Select Sector SPDR
    secMgr.Add("XLY", sid(19662), 0) # Consumer Discretionary Select Sector SPDR
    
    context.SecMgr = secMgr
    context.period = 0
    context.runningPnL = 0.0
    context.priorWeightMean = 0.0
    set_commission(commission.PerTrade(cost=1.0))
    set_slippage(TradeAtTheOpenSlippageModel(0.1))
    
def handle_data(context, data):
    context.period += 1
    totalWeight = context.SecMgr.Update(data)    
    record(PnL=context.portfolio.pnl, RPnl=context.runningPnL, Outlay=context.portfolio.positions_value, Weight=totalWeight)

    if (context.period < trendPeriods):
        return

    securityList = context.SecMgr.GetRankedEnabledSecurities()
    weightMean = totalWeight / float(len(securityList))
    if (weightMean <= 0.0 or context.priorWeightMean == 0.0):
        context.priorWeightMean = weightMean
        return
    if ((abs(weightMean - context.priorWeightMean) / weightMean) * 100.0 < weightsPctChangeThreshold):
        return

    context.priorWeightMean = weightMean
    context.runningPnL = context.portfolio.pnl
    
    securityCount = float(len(securityList))
    entryFraction = 1.0 / securityCount
    for security in securityList:
        if (weightMean > .45):
            if (security.Weight - security.Trigger >= .20):
                order_target_percent(security.Sid, entryFraction)
            else:
                if (security.Weight > .55):
                    order_target_percent(security.Sid, entryFraction)
                else:
                    order_target_percent(security.Sid, -entryFraction / 2.0)
        else:
            if (security.Trigger - security.Weight >= .15):
                order_target_percent(security.Sid, -entryFraction / 2.0)
            else:
                if (security.Weight > .40):
                    order_target_percent(security.Sid, entryFraction)
                else:
                    order_target_percent(security.Sid, entryFraction / 2.0)

################################################################                
class SecurityManager(object):
    '''Class to wrap securities'''

    def __init__(this):
        this.stockList = {}

    def __str__(this):
        toString = "\tSymbols:{0}\n".format(this.stockList.keys())
        return toString 
    
    def Count(this):
        return len(this.GetSecurities())
    
    def Add(this, symbol, sid, portion):
         this.stockList[symbol] = Security(symbol, sid, portion)

    def Update(this, data):
        totalWeight = 0.0
        for sec in this.stockList.values():
            if sec.Sid not in data:
                sec.Weight = 0.0
                sec.Enabled = False
                continue
            sec.UpdatePrices(data)
            sec.SetWeight()
            totalWeight += sec.Weight
        return totalWeight
    
    def GetRankedEnabledSecurities(this):
        returnList = {}
        for sec in this.stockList.values():
            if (sec.Enabled):
                returnList[sec] = sec.Weight
        # dictionary sorted by value
        ranked = collections.OrderedDict(sorted(returnList.items(), key=lambda t: t[1]))
        items = ranked.items()
        items.reverse()
        ranked = collections.OrderedDict(items)        
        return ranked
       
#################################################################
class Security(object):
    '''Class to wrap security'''

    def __init__(this, symbol, sid, direction):
        this.Symbol = symbol
        this.Sid = sid
        this.Direction = direction
        this.Open = collections.deque(maxlen=trendPeriods)
        this.High = collections.deque(maxlen=trendPeriods)
        this.Low = collections.deque(maxlen=trendPeriods)
        this.Close = collections.deque(maxlen=trendPeriods)
        this.Weight = 0.0
        this.Trigger = 0.0
        this.Enabled = True
            
    def __str__(this):
        toString = "\tSymbol:{0} weight:{1}\n".format(this.Symbol, this.Weight)
        return toString 
    
    def UpdatePrices(this, data):
        this.Open.append(data[this.Sid].open_price)
        this.High.append(data[this.Sid].high)
        this.Low.append(data[this.Sid].low)
        this.Close.append(data[this.Sid].close_price)
            
    def SetWeight(this):
        upPortion = 0
        dnPortion = 0
        span = len(this.Close)
        for i in range(0, span):
            if (this.Close[i] > this.Open[i]):
                upPortion += (i + 1) * (this.High[i] - this.Low[i])
            else:
                dnPortion += (i + 1) * (this.High[i] - this.Low[i])
        if (upPortion + dnPortion > 0):
            factor = upPortion / (upPortion + dnPortion)
            this.Weight = factor
            
        upPortion = 0
        dnPortion = 0
        if (span > triggerPeriods):
            newStart = span - triggerPeriods
            for i in range(0, triggerPeriods):
                if (this.Close[newStart + i] > this.Open[newStart + i]):
                    upPortion += (i + 1) * (this.High[newStart + i] - this.Low[newStart + i])
                else:
                    dnPortion += (i + 1) * (this.High[newStart + i] - this.Low[newStart + i])
            if (upPortion + dnPortion > 0):
                factor = upPortion / (upPortion + dnPortion)
                this.Trigger = factor
                
########################################################    
class TradeAtTheOpenSlippageModel(slippage.SlippageModel):
    def __init__(this, fractionOfOpenCloseRange):
        this.fractionOfOpenCloseRange = fractionOfOpenCloseRange

    def process_order(this, trade_bar, order):
        firstPrice = trade_bar.open_price
        lastPrice = trade_bar.close_price
        hlRange = lastPrice - firstPrice
        hlRange = hlRange * this.fractionOfOpenCloseRange
        targetExecutionPrice = firstPrice + hlRange
            
        # Create the transaction using the new price we've calculated.
        return slippage.create_transaction(
            trade_bar,
            order,
            targetExecutionPrice,
            order.amount
        )    
There was a runtime error.
3 responses

If you turn on only the "Outlay" in the Custom data, (or add record(Leverage = context.account.leverage) to handle_data), you can see that there is probably some borrowing involved. This can be due to timing in regards to overlapped positions during daily trade rebalancing. Some work would have to be done to eliminate these margin dips. And, this is daily data. Typically shifting to minutely data destroys these favorable daily returns. I've personally never seen a match between daily and minutely. To trade this, you understand, you have to run this minutely.

Good point. I overlooked that aspect. I'm still getting practice down on the process here at Quantopian, as I'm back on the site after having been gone for a bit.

Just because you're using minutely data doesn't mean you need to trade more than once a day / at all per day.