Overall Statistics
Total Trades
224
Average Win
4.44%
Average Loss
-1.40%
Compounding Annual Return
25.539%
Drawdown
16.500%
Expectancy
2.051
Net Profit
1771.160%
Sharpe Ratio
1.902
Probabilistic Sharpe Ratio
99.327%
Loss Rate
27%
Win Rate
73%
Profit-Loss Ratio
3.18
Alpha
0.26
Beta
0.068
Annual Standard Deviation
0.141
Annual Variance
0.02
Information Ratio
0.631
Tracking Error
0.238
Treynor Ratio
3.924
Total Fees
$4750.33
# From:  https://www.quantconnect.com/forum/discussion/9597/the-in-amp-out-strategy-continued-from-quantopian/p1
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 Start 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.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],120),self.rebalout)
        self.Schedule.On(self.DateRules.WeekEnd(self.ineq[0]),self.TimeRules.AfterMarketOpen(self.ineq[0],120),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 1 percentile
        extreme = retkey.iloc[-1] < np.nanpercentile(retkey,self.pctile,axis=0)
        # days to wait before getting back in market
        self.adj_wait = int(max(self.decay * self.adj_wait,self.init_wait * max(1, 
                        np.where((ret['GLD'].iloc[-1]>0) & (ret['SLV'].iloc[-1]<0) & (ret['SLV'].iloc[-2]>0), self.init_wait, 1),
                        np.where((ret['XLU'].iloc[-1]>0) & (ret['XLI'].iloc[-1]<0) & (ret['XLI'].iloc[-2]>0), self.init_wait, 1),
                        np.where((ret['FXF'].iloc[-1]>0) & (ret['FXA'].iloc[-1]<0) & (ret['FXA'].iloc[-2]>0), self.init_wait, 1))))
        adjwaitdays = min(self.maxwait, self.adj_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()