Overall Statistics
Total Trades
230
Average Win
4.18%
Average Loss
-1.54%
Compounding Annual Return
24.946%
Drawdown
15.700%
Expectancy
1.775
Net Profit
1665.916%
Sharpe Ratio
1.859
Probabilistic Sharpe Ratio
99.062%
Loss Rate
25%
Win Rate
75%
Profit-Loss Ratio
2.72
Alpha
0.254
Beta
0.072
Annual Standard Deviation
0.141
Annual Variance
0.02
Information Ratio
0.61
Tracking Error
0.238
Treynor Ratio
3.645
Total Fees
$4530.91
# From:  https://www.quantconnect.com/forum/discussion/9597/the-in-amp-out-strategy-continued-from-quantopian/p1
# v1.1:  Remove variation in days out of market to reduce # of parameters - lines commented out with #***
import pandas as pd
import numpy as np

class InOut(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2008,1,1)  # Set Start Date
        # self.SetEndDate(2015,1,3)  # Set End Date
        self.SetCash(100000)  # Set Strategy Cash
        
        # Algo Parameters
        self.shiftprds = range(55,66)  # momentum periods
        self.hprd = 252  # history period (days)
        # initialize out of trading period to 3 weeks, days out to 0; maximum days out; waiting period decay, and percentile for extreme indicator
        #*** self.init_wait, self.adj_wait, self.daysout, self.maxwait, self.decay, self.pctile = [15,15,0,60,0.5,1]
        self.init_wait, self.pctile = [15,1]
        self.be_in = True
        
        # Assets
        self.ineq = ['QQQ']                                         # Risk on securities (equal allocation)
        self.outeq = ['TLT','IEF']                                  # Risk off securities (equal allocation)
        self.sigeqs = ['DBB','IGE','SHY','XLI']                     # Indicator securities
        self.other = ['FXA','FXF','GLD','SLV','SPY','UUP','XLU']    # Indicator securities with mod
        self.eqs = list(set(self.ineq+self.outeq+self.sigeqs+self.other))
        for eq in self.eqs:
            self.AddEquity(eq,Resolution.Minute)

        # Initialize weights
        self.wts = pd.Series(0,self.ineq+self.outeq)
        self.wtson, self.wtsoff, self.lastwts = [self.wts.copy(),self.wts.copy(),self.wts.copy()]
        self.wtson[self.ineq] = 1/len(self.ineq)
        self.wtsoff[self.outeq] = 1/len(self.outeq)

        # Schedule functions
        self.Schedule.On(self.DateRules.EveryDay(self.ineq[0]),self.TimeRules.AfterMarketOpen(self.ineq[0],90),self.rebalout)
        self.Schedule.On(self.DateRules.WeekEnd(self.ineq[0]),self.TimeRules.AfterMarketOpen(self.ineq[0],90),self.rebalin)

    def rebalout(self):
        # average of historical values over shiftprds for each security
        hist = self.History(self.eqs,self.hprd,Resolution.Daily)['close'].unstack(level=0).dropna()  # close history
        h_shift = sum([hist.shift(x) for x in self.shiftprds])/len(self.shiftprds) #pd.concat([hist.shift(x) for x in self.shiftprds]).groupby(level=0).mean()
        # return over shiftprds average
        ret = (hist/h_shift-1)
        # key return indicators, negative of UUP, and differences: silver less gold, industrial - utility, AUS$ less Swiss Franc
        retkey = pd.concat([ret.loc[:,x] for x in self.sigeqs]+
                            [-ret.loc[:,'UUP'],ret.loc[:,'SLV']-ret.loc[:,'GLD'],
                            ret.loc[:,'XLI']-ret.loc[:,'XLU'],ret.loc[:,'FXA']-ret.loc[:,'FXF']],
                            axis=1)
        # extreme returns if less than percentile
        extreme = retkey.iloc[-1] < np.nanpercentile(retkey,self.pctile,axis=0)
        #*** cond = [(ret[x].iloc[-1]>0)&(ret[y].iloc[-1]<0)&(ret[y].iloc[-2]>0) for x,y in [['GLD','SLV'],['XLU','XLI'],['FXF','FXA']]]
        #*** self.adj_wait = int(max(self.decay*self.adj_wait,self.init_wait*np.where(any(cond),self.init_wait,1)))
        #*** adjwaitdays = min(self.maxwait, self.adj_wait)
        adjwaitdays = self.init_wait
        # in market indicator
        if extreme.any():
            self.daysout = 0
            self.be_in = False
            self.wts = self.wtsoff
        if self.daysout >= adjwaitdays:
            self.be_in = True
        self.daysout += 1
        
    def rebalin(self):
        if self.be_in: self.wts = self.wtson

    def OnData(self, data):
        # Trade if allocation has changed
        if not self.wts.equals(self.lastwts):
            port_tgt = [PortfolioTarget(x,y) for x,y in zip(self.wts.index,self.wts.values)]
            self.SetHoldings(port_tgt)
            self.lastwts = self.wts.copy()