| Overall Statistics |
|
Total Trades 24328 Average Win 0.01% Average Loss -0.07% Compounding Annual Return 0% Drawdown 131.700% Expectancy -0.171 Net Profit -157.969% Sharpe Ratio -0.196 Probabilistic Sharpe Ratio 0.041% Loss Rate 25% Win Rate 75% Profit-Loss Ratio 0.10 Alpha -0.255 Beta 1.2 Annual Standard Deviation 0.622 Annual Variance 0.387 Information Ratio -0.39 Tracking Error 0.597 Treynor Ratio -0.102 Total Fees $1831283.55 Estimated Strategy Capacity $2000.00 Lowest Capacity Asset QGLY R735QTJ8XC9X |
from itertools import groupby
from AlgorithmImports import *
import pandas as pd
import numpy as np
import scipy
from scipy import stats
class SectorMomentumTest(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1) # Set Start Date
self.SetEndDate(2022, 9, 21) # Set End Date
self.SetCash(100000000) # Set Strategy Cash
self.UniverseSettings.Resolution = Resolution.Daily
self.UniverseSettings.Leverage = 2.0
self.RebalanceDays = 0
self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol
self.AddUniverseSelection(FineFundamentalUniverseSelectionModel(self.SelectCoarse, self.SelectFine))
self.universe: List[Symbol] = []
self.dataHistory: dict[Symbol, RollingWindow[float]] = {}
self.SetBenchmark(self.spy)
def SelectCoarse(self, coarse: List[CoarseFundamental]) -> List[Symbol]:
return [x.Symbol for x in coarse if x.DollarVolume > 0]
def SelectFine(self, fine: List[FineFundamental]) -> List[Symbol]:
fine_ = [x for x in fine if x.CompanyReference.PrimaryExchangeID in ["NYS","NAS"]
and x.CompanyReference.IsLimitedPartnership == 0
and x.SecurityReference.IsPrimaryShare == 1
and x.MarketCap > 0]
sectorGroups = {key: list(group) for key, group in groupby(sorted(fine_, key=lambda x: x.AssetClassification.MorningstarSectorCode), key=lambda x: x.AssetClassification.MorningstarSectorCode)}
sectorValuations = {}
for sector, group in sectorGroups.items():
sectorValuations.update(self.MomentumScore(group))
valuationSorted = sorted(fine_, key=lambda x: sectorValuations[x.Symbol], reverse=True)[:round(len(fine_) / 5)]
self.universe = [x.Symbol for x in valuationSorted]
return self.universe
def MomentumScore(self, fine: List[FineFundamental]) -> Dict[Symbol, float]:
for x in fine:
if x.Symbol in self.dataHistory:
self.dataHistory[x.Symbol].Add(x.Price)
addedSymbols = [x.Symbol for x in fine if x.Symbol not in self.dataHistory]
if len(addedSymbols) > 0:
history: List[TradeBars] = self.History[TradeBar](addedSymbols, 253, Resolution.Daily)
newData = {symbol: RollingWindow[float](252) for symbol in addedSymbols}
for slice in history:
for symbol, bar in slice.items():
newData[symbol].Add(bar.Close)
self.dataHistory.update(newData)
activeSymbols = {x.Symbol: x for x in fine}
inactiveSymbols = [symbol for symbol in self.dataHistory.keys() if symbol not in activeSymbols]
for symbol in inactiveSymbols:
self.dataHistory.pop(symbol, None)
twelveMonthScore = stats.rankdata([(self.dataHistory[x.Symbol][0] - self.dataHistory[x.Symbol][251]) / self.dataHistory[x.Symbol][251] if x.Symbol in self.dataHistory and self.dataHistory[x.Symbol].IsReady else -np.inf for x in fine]) / len(fine)
nineMonthScore = stats.rankdata([(self.dataHistory[x.Symbol][0] - self.dataHistory[x.Symbol][188]) / self.dataHistory[x.Symbol][188] if x.Symbol in self.dataHistory and self.dataHistory[x.Symbol].IsReady else -np.inf for x in fine]) / len(fine)
sixMonthScore = stats.rankdata([(self.dataHistory[x.Symbol][0] - self.dataHistory[x.Symbol][125]) / self.dataHistory[x.Symbol][125] if x.Symbol in self.dataHistory and self.dataHistory[x.Symbol].IsReady else -np.inf for x in fine]) / len(fine)
threeMonthScore = stats.rankdata([(self.dataHistory[x.Symbol][0] - self.dataHistory[x.Symbol][62]) / self.dataHistory[x.Symbol][62] if x.Symbol in self.dataHistory and self.dataHistory[x.Symbol].IsReady else -np.inf for x in fine]) / len(fine)
compositeScore = np.sum([twelveMonthScore, nineMonthScore, sixMonthScore, threeMonthScore], axis=0)
compositeRank = stats.rankdata(compositeScore) / len(fine)
result: Dict[Symbol, float] = {x.Symbol: compositeRank[i] for i, x in enumerate(fine)}
return result
def OnSecuritiesChanged(self, changes: SecurityChanges):
pass
def DailyRoutine(self):
if self.RebalanceDays > 0:
self.RebalanceDays -= 1
return
if self.RebalanceDays == 0:
numSecurities = len(self.universe)
targetHoldings = [PortfolioTarget(symbol, 0.995/numSecurities) for symbol in self.universe]
for key in self.Portfolio.keys():
if key not in self.universe and self.Portfolio[key].Invested:
targetHoldings.append(PortfolioTarget(key, 0.0))
self.SetHoldings(targetHoldings)
self.RebalanceDays = 21
def OnData(self, data):
self.DailyRoutine()
self.Plot('Days', 'Rebalance', self.RebalanceDays)
self.Plot('Number of Securities', 'Universe', len(self.universe))
self.Plot("Number of Securities", "Invested", len([x for x in self.Portfolio.Values if x.Invested]))
def OnOrderEvent(self, orderEvent: OrderEvent) -> None:
if orderEvent.Status == OrderStatus.Invalid and orderEvent.Direction == OrderDirection.Buy:
self.MarketOrder(orderEvent.Symbol, orderEvent.Quantity - 1)