| Overall Statistics |
|
Total Orders 9265 Average Win 0.10% Average Loss -0.10% Compounding Annual Return 14.264% Drawdown 22.500% Expectancy 0.064 Start Equity 100000 End Equity 132614.34 Net Profit 32.614% Sharpe Ratio 0.453 Sortino Ratio 0.534 Probabilistic Sharpe Ratio 26.213% Loss Rate 48% Win Rate 52% Profit-Loss Ratio 1.04 Alpha 0.072 Beta 0.734 Annual Standard Deviation 0.166 Annual Variance 0.028 Information Ratio 0.561 Tracking Error 0.126 Treynor Ratio 0.103 Total Fees $9842.11 Estimated Strategy Capacity $170000000.00 Lowest Capacity Asset P R735QTJ8XC9X Portfolio Turnover 79.63% |
# region imports
from AlgorithmImports import *
# endregion
from QuantConnect import Resolution, TimeZones
from QuantConnect.Algorithm import QCAlgorithm
from QuantConnect.Algorithm.Framework.Portfolio import EqualWeightingPortfolioConstructionModel
from QuantConnect.Algorithm.Framework.Execution import ImmediateExecutionModel
from QuantConnect.Algorithm.Framework.Alphas import Insight, InsightDirection
from QuantConnect.Orders import OrderStatus
from datetime import timedelta
class MatureSmallCapsWithStopsStrategy(QCAlgorithm):
def Initialize(self):
# Backtest window & cash
self.SetStartDate(2022,1,1)
self.SetEndDate(2024,2,12)
self.SetCash(100000)
self.SetTimeZone(TimeZones.NewYork)
# Mature small-cap universe
self.tickers = [
# Technology
"AAPL", "MSFT", "NVDA",
# Consumer Discretionary
"AMZN", "TSLA", "NKE",
# Consumer Staples
"KO", "PEP", "PG",
# Healthcare
"UNH", "JNJ", "PFE",
# Financials
"JPM", "V", "MA",
# Energy
"XOM", "CVX", "COP",
# Industrials
"HON", "BA", "CAT",
# Utilities
"NEE", "DUK", "SO",
# Materials
"LIN", "APD", "SHW"
]
# Strategy params
self.SL_mult = 0.5
self.TP_mult = 2.0
# Will hold ATR per symbol at insight time
self.insightAtr = {}
# 1) Subscriptions
for sym in self.tickers:
self.AddEquity(sym, Resolution.Daily)
self.AddEquity("SPY", Resolution.Daily) # scheduler anchor
# 2) Framework Setup
self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
self.SetExecution(ImmediateExecutionModel())
# 3) Schedule daily insight generation 1′ after SPY close (~16:01 ET)
self.Schedule.On(
self.DateRules.EveryDay("SPY"),
self.TimeRules.AfterMarketClose("SPY", 1),
self.GenerateInsights
)
def GenerateInsights(self):
"""Compute 2-day up signal + lagged ATR, emit Up Insights and store ATR."""
history = self.History(self.tickers, 16, Resolution.Daily)
ts = self.Time
insights = []
for sym in self.tickers:
if sym not in history.index.get_level_values(0): continue
df = history.loc[sym].copy()
if df.shape[0] < 16: continue
df["ret"] = df["close"].pct_change()
signal = (df["ret"].shift(1)>0) & (df["ret"].shift(2)>0)
# True Range + lagged ATR14
df["prev_close"] = df["close"].shift(1)
df["tr"] = df[["high","low","prev_close"]].apply(
lambda row: max(row["high"]-row["low"],
abs(row["high"]-row["prev_close"]),
abs(row["low"] -row["prev_close"])),
axis=1
)
df["atr"] = df["tr"].rolling(14).mean().shift(1)
if signal.iloc[-1] and not df["atr"].iloc[-1] != df["atr"].iloc[-1]:
atr = df["atr"].iloc[-1]
self.insightAtr[sym] = atr
insights.append(Insight.Price(sym, timedelta(days=1), InsightDirection.Up))
self.Log(f"{sym} INSIGHT @ {ts:yyyy-MM-dd HH:mm} ET ATR={atr:.2f}")
if insights:
self.EmitInsights(insights)
def OnOrderEvent(self, orderEvent):
"""Place ATR-based SL/TP immediately after our market entry fills."""
if orderEvent.Status != OrderStatus.Filled:
return
symbol = orderEvent.Symbol
# only act on our entry orders (Insight-triggered market orders)
if orderEvent.FillQuantity <= 0:
return
# get entry price & ATR
price = orderEvent.FillPrice
atr = self.insightAtr.get(symbol)
if atr is None:
return
qty = orderEvent.FillQuantity
sl = price - self.SL_mult * atr
tp = price + self.TP_mult * atr
self.StopMarketOrder(symbol, -qty, sl)
self.LimitOrder (symbol, -qty, tp)
self.Log(f"{symbol} SL@{sl:.2f} TP@{tp:.2f} qty={qty}")
def OnEndOfAlgorithm(self):
self.Log(f"Final Portfolio Value: ${self.Portfolio.TotalPortfolioValue:0.2f}")