Overall Statistics
Total Trades
124
Average Win
2.68%
Average Loss
-0.37%
Compounding Annual Return
21.246%
Drawdown
12.200%
Expectancy
6.377
Net Profit
295.410%
Sharpe Ratio
1.667
Probabilistic Sharpe Ratio
95.737%
Loss Rate
10%
Win Rate
90%
Profit-Loss Ratio
7.18
Alpha
0.127
Beta
0.2
Annual Standard Deviation
0.088
Annual Variance
0.008
Information Ratio
0.32
Tracking Error
0.144
Treynor Ratio
0.735
Total Fees
$417.36
Estimated Strategy Capacity
$4100000.00
Lowest Capacity Asset
XLK RGRPZX100F39
## SIMON LesFlex June 2021 ##
## Modified by Vladimir and Frank

### Key Short—Term Economic Indicators. The Key Economic Indicators (KEI) database contains monthly and quarterly statistics 
### (and associated statistical methodological information) for the 33 OECD member and for a selection of non—member countries 
### on a wide variety of economic indicators, namely: quarterly national accounts, industrial production, composite leading indicators, 
### business tendency and consumer opinion surveys, retail trade, consumer and producer prices, hourly earnings, employment/unemployment,
### interest rates, monetary aggregates, exchange rates, international trade and balance of payments. Indicators have been prepared by national statistical 
### agencies primarily to meet the requirements of users within their own country. In most instances, the indicators are compiled in accordance with 
### international statistical guidelines and recommendations. However, national practices may depart from these guidelines, and these departures may 
### impact on comparability between countries. There is an on—going process of review and revision of the contents of the database in order to maximise 
### the relevance of the database for short—term economic analysis.
### For more information see: http://stats.oecd.org/OECDStat_Metadata/ShowMetadata.ashx?Dataset=KEI&Lang=en
### Reference Data Set: https://www.quandl.com/data/OECD/KEI_LOLITOAA_OECDE_ST_M-Leading-indicator-amplitude-adjusted-OECD-Europe-Level-ratio-or-index-Monthly

# Further links:
# https://app.hedgeye.com/insights/77156-chart-of-the-day-what-works-in-which-quad?type=macro
# https://stockcharts.com/freecharts/rrg/
# https://seekingalpha.com/article/4434713-sector-rotation-strategy-using-the-high-yield-spread
# https://www.oecd.org/sdd/compositeleadingindicatorsclifrequentlyaskedquestionsfaqs.htm
# https://www.quantconnect.com/forum/discussion/11566/kei-based-strategy/p1


import numpy as np
from QuantConnect.Python import PythonQuandl

class QuandlImporterAlgorithm(QCAlgorithm):

    def Initialize(self):
        
        # Leading Indicator, Amplitude Adjusted, Oecd — EUROPE, Level, Ratio Or Index
        #self.quandlCode = "OECD/KEI_LOLITOAA_OECDE_ST_M"
        
        # Leading Indicator, Amplitude Adjusted, Oecd — TOTAL, Level, Ratio Or Index
        self.quandlCode = "OECD/KEI_LOLITOAA_OECD_ST_M"
        
        ## Optional argument - your personal token necessary for restricted dataset
        Quandl.SetAuthCode("RXk7Mxue6oH1TM-U8b7c")
        
        self.SetStartDate(2015,1, 1)                                 #Set Start Date
        self.SetEndDate(datetime.today() - timedelta(1))            #Set End Date
        self.SetCash(100000)                                        #Set Strategy Cash
    
    # LIVE TRADING
        if self.LiveMode:
            self.Debug("Trading Live!")
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
        
        # Group Trading
        # Use a default FA Account Group with an Allocation Method
        self.DefaultOrderProperties = InteractiveBrokersOrderProperties()
        
        # account group created manually in IB/TWS
        self.DefaultOrderProperties.FaGroup = "KEI1x"

        # supported allocation methods are: EqualQuantity, NetLiq, AvailableEquity, PctChange
        self.DefaultOrderProperties.FaMethod = "AvailableEquity"

        # set a default FA Allocation Profile
        # Alex: I commented the following line out, since it would "reset" the previous settings
        #self.DefaultOrderProperties = InteractiveBrokersOrderProperties()

        # allocation profile created manually in IB/TWS
        # self.DefaultOrderProperties.FaProfile = "TestProfileP"
        
    #Algo Start
               
        # Benchmark using qqq & bond only?
        self.use_qqq_tlt_only = False
        
        # Tickers
        self.SetBenchmark("SPY")
        self.SPY = self.AddEquity('SPY', Resolution.Hour).Symbol
        self.stock = self.AddEquity('QQQ', Resolution.Hour).Symbol
        self.bond = self.AddEquity('TLT', Resolution.Hour).Symbol
        self.XLF = self.AddEquity('XLF', Resolution.Hour).Symbol
        self.XLE = self.AddEquity('XLE', Resolution.Hour).Symbol
        self.XLB = self.AddEquity('XLB', Resolution.Hour).Symbol
        self.XLI = self.AddEquity('XLI', Resolution.Hour).Symbol
        self.XLY = self.AddEquity('XLY', Resolution.Hour).Symbol
        self.XLP = self.AddEquity('XLP', Resolution.Hour).Symbol
        self.XLU = self.AddEquity('XLU', Resolution.Hour).Symbol
        self.XLK = self.AddEquity('XLK', Resolution.Hour).Symbol
        self.XLV = self.AddEquity('XLV', Resolution.Hour).Symbol
        self.XLC = self.AddEquity('XLC', Resolution.Hour).Symbol
        self.GLD = self.AddEquity('GLD', Resolution.Hour).Symbol
        self.AGG = self.AddEquity('AGG', Resolution.Hour).Symbol
        self.TIPS = self.AddEquity('TIPs', Resolution.Hour).Symbol
        symbols = ['QQQ', 'TLT', 'XLF', 'XLE', 'XLB', 'XLI', 'XLY', 'XLP', 'XLU', 'XLK', 'XLV', 'XLC','SPY','GLD','AGG','TIPS']
        
        # Rate of Change for plotting
        self.sharpe_dict = {}
        for symbol in symbols:
            self.sharpe_dict[symbol] = SharpeRatio(symbol, 42, 0.)
            self.RegisterIndicator(symbol, self.sharpe_dict[symbol], Resolution.Daily)
        self.SetWarmup(42)
        
        # Vars
        self.init = True
        self.regime = 0
        self.kei = self.AddData(QuandlCustomColumns, self.quandlCode, Resolution.Daily, TimeZones.NewYork).Symbol
        self.sma = self.SMA(self.kei, 1)
        self.mom = self.MOMP(self.kei, 2)
        
        self.Schedule.On(self.DateRules.EveryDay(self.stock), self.TimeRules.AfterMarketOpen(self.stock, 31), 
            self.Rebalance)
        
        
    def Rebalance(self):
        
        if self.IsWarmingUp or not self.mom.IsReady or not self.sma.IsReady: return
        initial_asset = self.stock if self.mom.Current.Value > 0 else self.bond
        
        if self.init:
            self.SetHoldings(initial_asset, 1)
            self.init = False
        
        # Return the historical data for custom 90 day period
        #keihist = self.History([self.kei],self.StartDate-timedelta(100),self.StartDate-timedelta(10))
        
        # Return the last 1400 bars of history
        keihist = self.History([self.kei], 6*220)
        #keihist = keihist['Value'].unstack(level=0).dropna()
        
        # Define adaptive tresholds
        keihistlowt = np.nanpercentile(keihist,  15.)
        keihistmidt = np.nanpercentile(keihist,  50.)
        keihisthight = np.nanpercentile(keihist, 90.)
        kei = self.sma.Current.Value
        keimom = self.mom.Current.Value
        
        if self.use_qqq_tlt_only == True:
            
            # KEI momentum
            if (keimom >= 0) and (not self.regime == 1):
                self.regime = 1
                self.Liquidate()
                self.SetHoldings(self.stock, .99)
            elif (keimom < 0) and (not self.regime == 0):
                self.regime = 0
                self.Liquidate()
                self.SetHoldings(self.bond,  .99)

        else:
            
            if (keimom > 0 and kei <= keihistlowt) and (not self.regime == 1):
                # RECOVERY
                self.regime = 1
                self.Debug(f'{self.Time} 1 RECOVERY: INDUSTRIAL / MATERIALS / CUSTOMER DISCR / TECH')
                self.Liquidate()
                self.SetHoldings(self.XLI, .24)
                self.SetHoldings(self.XLK, .25)
                self.SetHoldings(self.XLB, .25)
                self.SetHoldings(self.XLY, .25)
                
            elif (keimom > 0 and kei >= keihistlowt and kei < keihistmidt) and (not self.regime == 2):
                # EARLY EXPANSION - Technology, Transporation
                self.regime = 2
                self.Debug(f'{self.Time} 2 EARLY: INDUSTRIAL / CUSTOMER DISCR / FINANCIAL')
                self.SetHoldings(self.XLI, .29)
                self.SetHoldings(self.XLK, .20)
                self.SetHoldings(self.XLB, .10)
                self.SetHoldings(self.XLY, .25)
                self.SetHoldings(self.XLF, .10)
                
            elif (keimom > 0 and kei >= keihistmidt and kei < keihisthight) and (not self.regime == 3):
                # REBOUND - Basic Materials, Metals, Energy, High Interest Finance
                self.regime = 3
                self.Debug(f'{self.Time} 3 REBOUND: INDUSTRIAL / TECH / MATERIALS')
                self.Liquidate()
                self.SetHoldings(self.XLI, .39)
                self.SetHoldings(self.XLK, .40)
                self.SetHoldings(self.XLB, .10)
                self.SetHoldings(self.XLF, .10)
                
            elif (keimom > 0 and kei >= keihisthight) and (not self.regime == 4):
                # TOP RISING - High Interest Finance, Real Estate, IT, Commodities, Precious Metals
                self.regime = 4
                self.Debug(f'{self.Time} 4 TOP RISING: INDUSTRIAL / TECH / FINANCIAL')
                self.Liquidate()
                self.SetHoldings(self.XLI, .33)
                self.SetHoldings(self.XLK, .33)
                self.SetHoldings(self.XLF, .33)
                
            elif (keimom < 0 and kei >= keihisthight) and (not self.regime == 3.7):
                # TOP DECLINING - Utilities
                self.regime = 3.7
                self.Debug(f'{self.Time} 4 TOP DECLINING: BOND / UTILITIES')
                self.Liquidate()
                self.SetHoldings(self.bond, .94)
                self.SetHoldings(self.XLU,  .05)
                
            elif (keimom < 0 and kei <= keihisthight and kei > keihistmidt) and (not self.regime == 2.7):
                # LATE - 
                self.regime = 2.7
                self.Debug(f'{self.Time} 5 LATE: HEALTH / TECH / CUSTOMER DISCR')
                self.Liquidate()
                self.SetHoldings(self.XLV,  .35)
                self.SetHoldings(self.XLK,  .20)
                self.SetHoldings(self.XLY,  .20)
                self.SetHoldings(self.AGG,  .20)
                
            elif (keimom < 0 and kei <= keihistmidt and kei > keihistlowt) and (not self.regime == 1.7):
                # DECLINE - Defensive Sectors, Utilities, Consumer Staples
                self.regime = 1.7
                self.Debug(f'{self.Time} 6 DECLINE: BOND / UTILITIES')
                self.Liquidate()
                self.SetHoldings(self.bond, .40)
                self.SetHoldings(self.AGG, .15)
                self.SetHoldings(self.TIPS, .15)
                self.SetHoldings(self.XLU,  .10)
                self.SetHoldings(self.GLD,  .10)
                
            elif (keimom < 0 and kei <= keihistlowt) and (not self.regime == 0.7):
                # BOTTOM DECLINING
                self.regime = 0.7
                self.Debug(f'{self.Time} 7 BOTTOM DECLINING: BOND / UTILITIES')
                self.Liquidate()
                self.SetHoldings(self.bond, .40)
                self.SetHoldings(self.AGG, .15)
                self.SetHoldings(self.TIPS, .15)
                self.SetHoldings(self.XLU,  .10)
                self.SetHoldings(self.GLD,  .10)
        
        self.Plot("LeadInd", "SMA(LeadInd)", 100. * self.sma.Current.Value)
        self.Plot("LeadInd", "keihistlowt",  100. * keihistlowt)
        self.Plot("LeadInd", "keihistmidt",  100. * keihistmidt)
        self.Plot("LeadInd", "keihisthight", 100. * keihisthight)
        self.Plot("MOMP", "MOMP(LeadInd)", min(2., max(-2., self.mom.Current.Value)))
        self.Plot("MOMP", "Regime", self.regime)
        
        #self.Plot("MOM", "XLF", self.sharpe_dict['XLF'].Current.Value)
        #self.Plot("MOM", "XLE", self.sharpe_dict['XLE'].Current.Value)
        #self.Plot("MOM", "XLB", self.sharpe_dict['XLB'].Current.Value)
        #self.Plot("MOM", "XLI", self.sharpe_dict['XLI'].Current.Value)
        #self.Plot("MOM", "XLY", self.sharpe_dict['XLY'].Current.Value)
        #self.Plot("MOM", "XLP", self.sharpe_dict['XLP'].Current.Value)
        #self.Plot("MOM", "XLU", self.sharpe_dict['XLU'].Current.Value)
        #self.Plot("MOM", "XLK", self.sharpe_dict['XLK'].Current.Value)
        #self.Plot("MOM", "XLV", self.sharpe_dict['XLV'].Current.Value)
        #self.Plot("MOM", "XLC", self.sharpe_dict['XLC'].Current.Value)


# Quandl often doesn't use close columns so need to tell LEAN which is the "value" column.
class QuandlCustomColumns(PythonQuandl):
    def __init__(self):
        # Define ValueColumnName: cannot be None, Empty or non-existant column name
        self.ValueColumnName = "Value"