| Overall Statistics |
|
Total Trades 2129 Average Win 0.88% Average Loss -0.77% Compounding Annual Return 17.844% Drawdown 42.100% Expectancy 0.207 Net Profit 396.218% Sharpe Ratio 0.871 Probabilistic Sharpe Ratio 25.530% Loss Rate 44% Win Rate 56% Profit-Loss Ratio 1.14 Alpha 0.023 Beta 1.041 Annual Standard Deviation 0.206 Annual Variance 0.042 Information Ratio 0.22 Tracking Error 0.132 Treynor Ratio 0.172 Total Fees $2673.30 Estimated Strategy Capacity $76000.00 Lowest Capacity Asset HAS R735QTJ8XC9X |
##########################################################################################
#
# QQQ Index Rebalancer
# ------------------------
#
# Inspired by:
# https://www.quantconnect.com/forum/discussion/12347/creating-our-own-index-fund/p1
#
# Values for External Params
# ---------------------------------------
# maxHoldings = 10 # Number of positions to hold, max
# lookbackInDays = 30 # look at performance over last 30 days
# perfMetricIndex = 0 # '0' corresponds to Momentum Pct
# rebalancePeriodIndex = 0 # '0' corresponds to Monthly rebalancing
# useETFWeights = 0 = 0 # Honor the ETF's weighting (even if a few holdings)
#
##########################################################################################
class ETFUniverse(QCAlgorithm):
## Main entry point for the algo
## ==================================================================================
def Initialize(self):
self.InitAssets()
self.InitBacktestParams()
self.InitExternalParams()
self.InitAlgoParams()
self.ScheduleRoutines()
## Init assets: Symbol, broker model, universes, etc. Called from Initialize().
## ==================================================================================
def InitAssets(self):
self.ticker = "QQQ"
self.etfSymbol = self.AddEquity(self.ticker, Resolution.Hour).Symbol
self.AddUniverse(self.Universe.ETF(self.etfSymbol, self.UniverseSettings, self.ETFConstituentsFilter))
self.UniverseSettings.Resolution = Resolution.Hour
self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))
## Set backtest params: dates, cash, etc. Called from Initialize().
## ==================================================================
def InitBacktestParams(self):
self.SetStartDate(2012, 1, 1) # Set Start Date
# self.SetEndDate(2019, 1, 1) # Set end Date
self.SetCash(10000) # Set Strategy Cash
self.EnableAutomaticIndicatorWarmUp = True
self.SetWarmup(31, Resolution.Daily)
# ====================================================================
# Initialize external parameters. Called from Initialize().
# ====================================================================
def InitExternalParams(self):
self.maxHoldings = int(self.GetParameter("maxHoldings"))
self.perfMetricIndex = int(self.GetParameter("perfMetricIndex"))
self.rebalancePeriodIndex = int(self.GetParameter("rebalancePeriodIndex"))
self.lookbackInDays = int(self.GetParameter("lookbackInDays"))
self.useETFWeights = bool(self.GetParameter("useETFWeights") == 1)
# ==================================================================================
# Set algo params: Symbol, broker model, ticker, etc. Called from Initialize().
# ==================================================================================
def InitAlgoParams(self):
# Flags to track and trigger rebalancing state
self.timeToRebalance = True
self.universeRepopulated = False
# State vars
self.weights = {}
self.symDataDict = {}
self.SelectedSymbols = []
self.SelectedSymbolData = []
# Values related to our alpha (rebalancing period and preferred perf metric
self.PerfMetrics = [MetricsEnum.MOMENTUM_PCT, MetricsEnum.SHARPE]
self.rankPerfMetric = self.PerfMetrics[self.perfMetricIndex]
self.RebalancePeriods = [IntervalEnum.MONTHLY, IntervalEnum.WEEKLY, IntervalEnum.DAILY]
self.rebalancePeriod = self.RebalancePeriods[self.rebalancePeriodIndex]
# ==================================
def ScheduleRoutines(self):
if( self.rebalancePeriod == IntervalEnum.MONTHLY ):
self.Schedule.On( self.DateRules.MonthStart(self.etfSymbol),
self.TimeRules.AfterMarketOpen(self.etfSymbol, 31),
self.SetRebalanceFlag )
elif( self.rebalancePeriod == IntervalEnum.WEEKLY ):
self.Schedule.On( self.DateRules.WeekStart(self.etfSymbol),
self.TimeRules.AfterMarketOpen(self.etfSymbol, 31),
self.SetRebalanceFlag )
# ====================================
def ETFConstituentsFilter(self, constituents):
if( self.timeToRebalance ):
self.weights = {c.Symbol: c.Weight for c in constituents}
## set flags
self.universeRepopulated = True
self.timeToRebalance = False
for symbol in self.weights:
if symbol not in self.symDataDict:
# symbol, algo, etfWeight, lookbackInDays, MetricsEnum.MOMENTUM_PCT):
self.symDataDict[symbol] = SymbolData(self, symbol, self.weights[symbol], \
self.lookbackInDays, self.rankPerfMetric)
symbolData = self.symDataDict[symbol]
history = self.History(symbol, 31, Resolution.Daily)
symbolData.SeedHistory(history)
# Sort by momentum
sortedByMomentum = sorted(self.symDataDict.items(), key=lambda x: x[1].PerfMetricValue, reverse=False)[:10]
# Get Symbol object (the key of dict)
self.SelectedSymbols = [x[0] for x in sortedByMomentum]
self.SelectedSymbolData = [x[1] for x in sortedByMomentum]
#### TODO:
#### Remove Symboldata from self.symDataDict that didnt make the cut
return self.SelectedSymbols
else:
return []
# ====================================
def SetRebalanceFlag(self):
self.timeToRebalance = True
# ====================================
def OnData(self,data):
if (self.universeRepopulated):
self.Liquidate()
self.weights = {}
# calculate sum of weights
weightsSum = sum(self.symDataDict[symbol].etfWeight for symbol in self.SelectedSymbols)
for symbol in self.SelectedSymbols:
# Either use ETF Weights or equal weights
if(self.useETFWeights):
symbolWeight = self.symDataDict[symbol].etfWeight / weightsSum # respect weighting
else:
symbolWeight = 1 / len(self.SelectedSymbols) # Equally weighted
self.SetHoldings(symbol, symbolWeight)
self.universeRepopulated = False
# for symbol, weight in self.weights.items():
# if symbol in self.ActiveSecurities:
# self.SetHoldings(symbol, weight) # Market cap weighted
# # self.SetHoldings(symbol, 1 / len(self.weights)) # Equally weighted
# =================================================================
# Periodically check if a security is no longer in the ETF
# =================================================================
# def RemoveDelistedSymbols(self, changes):
# for investedSymbol in [x.Key for x in self.Portfolio if x.Value.Invested]:
# if( investedSymbol not in self.weights.keys() ):
# self.Liquidate(symbol, 'No longer in universe')
##########################
# Symbol Data Class
##########################
class SymbolData():
def __init__(self, algo, symbol, etfWeight, lookbackInDays, perfMetric):
self.algo = algo
self.symbol = symbol
self.etfWeight = etfWeight
self.perfMetric = perfMetric
self.momp = MomentumPercent(lookbackInDays)
def SeedHistory(self,history):
for row in history.loc[self.symbol].itertuples():
self.momp.Update(row.Index, row.close)
@property
def PerfMetricValue(self):
if( (self.perfMetric == MetricsEnum.MOMENTUM_PCT) and self.momp.IsReady):
return self.momp.Current.Value
else:
return float('-inf')
###############################
# Perf Enum
###############################
class MetricsEnum(Enum):
MOMENTUM_PCT = "MOMENTUM PCT"
SHARPE = "SHARPE"
DRAWDOWN = "DRAWDOWN"
RET_DD_RATIO = "RET/DD"
THARP_EXP = "THARP EXP"
###############################
# Interval Enum
###############################
class IntervalEnum(Enum):
MONTHLY = "MONTHLY"
WEEKLY = "WEEKLY"
DAILY = "DAILY"