| Overall Statistics |
|
Total Orders 3301 Average Win 0.07% Average Loss -0.07% Compounding Annual Return 53.761% Drawdown 6.900% Expectancy 0.361 Start Equity 1000000 End Equity 1532182.53 Net Profit 53.218% Sharpe Ratio 2.31 Sortino Ratio 2.77 Probabilistic Sharpe Ratio 94.636% Loss Rate 36% Win Rate 64% Profit-Loss Ratio 1.14 Alpha 0.191 Beta 0.851 Annual Standard Deviation 0.131 Annual Variance 0.017 Information Ratio 1.757 Tracking Error 0.098 Treynor Ratio 0.357 Total Fees $5904.35 Estimated Strategy Capacity $260000000.00 Lowest Capacity Asset ROOT XJ24U3H4NQZP Portfolio Turnover 22.58% |
# region imports
from AlgorithmImports import *
from itertools import groupby
# endregion
class LiquidAssetsUniverseSelectionModel(FineFundamentalUniverseSelectionModel):
def __init__(self, algorithm, universe_settings, universe_size):
self.algorithm = algorithm
self.universe_size = universe_size
self.month = -1
super().__init__(self._select_coarse, self._select_fine, universe_settings)
def _select_coarse(self, coarse):
# Monthly universe refresh
if self.algorithm.time.month == self.month:
return Universe.UNCHANGED
self.month = self.algorithm.time.month
selected = sorted([c for c in coarse if c.has_fundamental_data],
key=lambda c: c.dollar_volume, reverse=True)[:self.universe_size]
return [c.symbol for c in selected]
def _select_fine(self, fine):
return [f.symbol for f in fine]
class SectorRotationAlphaModel(AlphaModel):
def __init__(self, num_sectors=3, stocks_per_sector=5):
self.num_sectors = num_sectors
self.stocks_per_sector = stocks_per_sector
self.symbol_data = {} # Stores symbol indicators and sector
def update(self, algorithm, data):
insights = []
# Filter symbols with ready indicators
ready_symbols = {symbol: sd for symbol, sd in self.symbol_data.items()
if all([indicator.IsReady for indicator in sd['indicators'].values()])}
if len(ready_symbols) == 0:
return insights
# Group by sector and calculate average momentum
sectors = {}
for symbol, sd in ready_symbols.items():
sector = sd['sector']
momentum = sd['indicators']['momentum'].Current.Value
if sector not in sectors:
sectors[sector] = []
sectors[sector].append((symbol, momentum))
# Calculate sector scores (average momentum)
sector_scores = {sector: np.mean([m for _, m in symbols])
for sector, symbols in sectors.items()}
top_sectors = sorted(sector_scores.items(), key=lambda x: x[1], reverse=True)[:self.num_sectors]
# Select top stocks per sector
for sector, _ in top_sectors:
sector_stocks = sorted(sectors[sector], key=lambda x: x[1], reverse=True)[:self.stocks_per_sector]
weight = 1 / (self.num_sectors * len(sector_stocks))
for symbol, _ in sector_stocks:
insights.append(Insight.Price(symbol, Expiry.END_OF_DAY, InsightDirection.UP, weight=weight))
return insights
def on_securities_changed(self, algorithm, changes):
for security in changes.AddedSecurities:
# Initialize indicators
roc = algorithm.ROC(security.Symbol, 20, Resolution.Daily)
self.symbol_data[security.Symbol] = {
'indicators': {'momentum': roc},
'sector': security.Fundamentals.AssetClassification.MorningstarSectorCode
}
for security in changes.RemovedSecurities:
self.symbol_data.pop(security.Symbol, None)
class SectorRotation(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2024, 1, 1)
self.SetEndDate(2024, 12, 31)
self.SetCash(1000000)
self.SetWarmUp(20, Resolution.Daily) # Warm up for momentum indicator
# Universe setup
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverseSelection(LiquidAssetsUniverseSelectionModel(self, self.UniverseSettings, 100))
# Alpha model (rotates top 3 sectors, 5 stocks each)
self.AddAlpha(SectorRotationAlphaModel(num_sectors=3, stocks_per_sector=8))
# Portfolio construction (daily rebalance)
self.SetPortfolioConstruction(InsightWeightingPortfolioConstructionModel(Expiry.EndOfDay))
self.SetExecution(ImmediateExecutionModel())
self.SetRiskManagement(NullRiskManagementModel())
def OnData(self, data):
pass # Handled by alpha and portfolio models