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)