| Overall Statistics |
|
Total Orders 3958 Average Win 0.12% Average Loss -0.12% Compounding Annual Return 14.567% Drawdown 15.400% Expectancy 0.366 Start Equity 100000 End Equity 206937.98 Net Profit 106.938% Sharpe Ratio 0.732 Sortino Ratio 0.766 Probabilistic Sharpe Ratio 45.685% Loss Rate 35% Win Rate 65% Profit-Loss Ratio 1.09 Alpha 0.048 Beta 0.373 Annual Standard Deviation 0.103 Annual Variance 0.011 Information Ratio 0.006 Tracking Error 0.137 Treynor Ratio 0.203 Total Fees $4473.52 Estimated Strategy Capacity $2000000.00 Lowest Capacity Asset SO R735QTJ8XC9X Portfolio Turnover 5.04% |
from AlgorithmImports import *
import numpy as np
class MonthlyVolatilityEffectAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020, 1, 1)
self.SetEndDate(2025, 6, 1)
self.SetCash(100000)
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction)
self.AddAlpha(NullAlphaModel()) # Manual alpha emission
self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
self.SetExecution(ImmediateExecutionModel())
self.SetRiskManagement(TrailingStopRiskManagementModel(0.05))
self.SetRiskManagement(MaximumDrawdownPercentPortfolio(0.1))
self.last_selection_time = datetime.min
self.selected_symbols = []
# Schedule: Emit insights only on first Monday of each month at 10:00 AM
self.Schedule.On(
self.DateRules.Every(DayOfWeek.Monday),
self.TimeRules.At(10, 0),
self.CheckAndEmitMonthlyInsights
)
def CoarseSelectionFunction(self, coarse):
# Limit selection to once per month
if (self.Time - self.last_selection_time).days < 30:
return Universe.Unchanged
self.last_selection_time = self.Time
# Filter for price > $5 and high volume
filtered = [x for x in coarse if x.HasFundamentalData and x.Price > 5]
sorted_by_volume = sorted(filtered, key=lambda x: x.Volume, reverse=True)[:500]
symbols = [x.Symbol for x in sorted_by_volume]
# 60-day historical volatility
history = self.History(symbols, 60, Resolution.Daily)
if history.empty:
return []
vol_dict = {}
for symbol in symbols:
if symbol not in history.index.levels[0]:
continue
close = history.loc[symbol]['close']
if len(close) < 60:
continue
returns = close.pct_change().dropna()
vol = returns.std() * np.sqrt(252)
vol_dict[symbol] = vol
# Select 10 lowest-volatility stocks
sorted_vols = sorted(vol_dict.items(), key=lambda x: x[1])[:30]
self.selected_symbols = [s[0] for s in sorted_vols]
self.Debug(f"{self.Time.date()} - Selected: {[str(s) for s in self.selected_symbols]}")
return self.selected_symbols
def CheckAndEmitMonthlyInsights(self):
today = self.Time.date()
# Emit only on the first Monday of each month
if today.day <= 7 and today.weekday() == 0:
self.Debug(f"{today} is the first Monday of the month. Emitting insights.")
self.EmitMonthlyInsights()
def EmitMonthlyInsights(self):
if not self.selected_symbols:
self.Debug("No symbols to emit insights for.")
return
insights = [
Insight.Price(symbol, timedelta(days=30), InsightDirection.Up)
for symbol in self.selected_symbols
]
self.Debug(f"{self.Time.date()} - Emitting insights for: {[str(i.Symbol) for i in insights]}")
self.EmitInsights(insights)
def OnSecuritiesChanged(self, changes):
for removed in changes.RemovedSecurities:
if self.Portfolio[removed.Symbol].Invested:
self.Liquidate(removed.Symbol)