| Overall Statistics |
|
Total Orders 3612 Average Win 0.14% Average Loss -0.27% Compounding Annual Return 10.891% Drawdown 35.900% Expectancy 0.179 Start Equity 100000 End Equity 237025.43 Net Profit 137.025% Sharpe Ratio 0.337 Sortino Ratio 0.375 Probabilistic Sharpe Ratio 3.712% Loss Rate 22% Win Rate 78% Profit-Loss Ratio 0.51 Alpha 0.005 Beta 1.006 Annual Standard Deviation 0.179 Annual Variance 0.032 Information Ratio 0.067 Tracking Error 0.084 Treynor Ratio 0.06 Total Fees $3708.29 Estimated Strategy Capacity $120000000.00 Lowest Capacity Asset ALD R735QTJ8XC9X Portfolio Turnover 1.70% Drawdown Recovery 1449 |
#region imports
from AlgorithmImports import *
from Execution.ImmediateExecutionModel import ImmediateExecutionModel
from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel
from Risk.MaximumDrawdownPercentPerSecurity import MaximumDrawdownPercentPerSecurity
#endregion
"""
COMPREHENSIVE STRATEGY EXPLANATION
This strategy uses the QuantConnect Algorithm Framework. It selects stocks from a
fixed Dow Jones Industrial Average universe and invests in the highest P/E names
within that universe.
The framework structure is preserved:
1. Universe Selection
The strategy uses coarse and fine fundamental universe selection. The starting
universe is restricted to a static list of 30 DJIA constituents. This is a
simplified pedagogical DJIA universe and is not point-in-time adjusted. Once per
month, the universe selection process filters the DJIA names for valid fundamental
data, valid price data, and valid positive P/E ratios. It then selects the five
stocks with the highest P/E ratios, subject to a reasonable P/E range.
This is a growth-oriented screen, not a value screen. High P/E stocks are usually
companies where the market is pricing in stronger expected future growth.
2. Alpha Model
The custom alpha model emits long-only Up insights for the selected universe. It
also emits Flat insights for securities removed from the universe so the framework
can exit old positions.
3. Portfolio Construction
The strategy uses QuantConnect's EqualWeightingPortfolioConstructionModel. Active
long insights are equal-weighted across the portfolio.
4. Execution and Risk
Execution is handled by ImmediateExecutionModel. Risk management is handled by
MaximumDrawdownPercentPerSecurity. If a security breaches the drawdown threshold,
the risk model liquidates it and cancels related insights.
The benchmark is DIA, the Dow Jones Industrial Average ETF. This is appropriate
because the strategy selects from the DJIA universe.
"""
class MonthlyLongOnlySelectedSymbolsAlphaModel(AlphaModel):
def __init__(self, insight_duration_days=35):
self.insight_duration = timedelta(days=insight_duration_days)
self.active_symbols = []
self.removed_symbols = []
self.last_emit_month = None
def Update(self, algorithm, data):
insights = []
current_month = (algorithm.Time.year, algorithm.Time.month)
# Emit Flat insights for securities removed from the selected universe.
for symbol in self.removed_symbols:
insights.append(
Insight.Price(
symbol,
timedelta(days=1),
InsightDirection.Flat
)
)
self.removed_symbols = []
# Emit long insights only once per month.
if self.last_emit_month == current_month:
return insights
if len(self.active_symbols) == 0:
return insights
for symbol in self.active_symbols:
if algorithm.Securities.ContainsKey(symbol):
if algorithm.Securities[symbol].HasData:
insights.append(
Insight.Price(
symbol,
self.insight_duration,
InsightDirection.Up
)
)
self.last_emit_month = current_month
return insights
def OnSecuritiesChanged(self, algorithm, changes):
for security in changes.AddedSecurities:
if security.Symbol not in self.active_symbols:
self.active_symbols.append(security.Symbol)
for security in changes.RemovedSecurities:
if security.Symbol in self.active_symbols:
self.active_symbols.remove(security.Symbol)
if security.Symbol not in self.removed_symbols:
self.removed_symbols.append(security.Symbol)
class DJIATopPEStrategy(QCAlgorithm):
def Initialize(self):
# ------------------------------------------------------------
# 1. BACKTEST SETTINGS
# ------------------------------------------------------------
self.SetStartDate(2018, 1, 1)
self.SetEndDate(2026, 5, 5)
self.initial_cash = 100000
self.SetCash(self.initial_cash)
# ------------------------------------------------------------
# 2. STATIC DJIA UNIVERSE
# ------------------------------------------------------------
self.djia_tickers = [
"AAPL", "AMGN", "AXP", "BA", "CAT", "CRM", "CSCO", "CVX", "DIS", "DOW",
"GS", "HD", "HON", "IBM", "INTC", "JNJ", "JPM", "KO", "MCD", "MMM",
"MRK", "MSFT", "NKE", "PG", "TRV", "UNH", "V", "VZ", "WBA", "WMT"
]
self.djia_symbols = [
Symbol.Create(ticker, SecurityType.Equity, Market.USA)
for ticker in self.djia_tickers
]
self.UniverseSettings.Resolution = Resolution.Daily
self.top_count = 5
self.last_selection_month = None
# ------------------------------------------------------------
# 3. FRAMEWORK UNIVERSE SELECTION
# ------------------------------------------------------------
self.SetUniverseSelection(
FineFundamentalUniverseSelectionModel(
self.CoarseSelectionFunction,
self.FineSelectionFunction
)
)
# ------------------------------------------------------------
# 4. FRAMEWORK MODELS
# ------------------------------------------------------------
self.AddAlpha(
MonthlyLongOnlySelectedSymbolsAlphaModel(
insight_duration_days=35
)
)
self.SetPortfolioConstruction(
EqualWeightingPortfolioConstructionModel()
)
self.SetExecution(
ImmediateExecutionModel()
)
self.SetRiskManagement(
MaximumDrawdownPercentPerSecurity(0.15)
)
# ------------------------------------------------------------
# 5. BENCHMARK
# ------------------------------------------------------------
self._benchmark = self.AddEquity(
"DIA",
Resolution.Daily,
Market.USA
).Symbol
self.SetBenchmark(self._benchmark)
self.initial_benchmark_price = None
# ------------------------------------------------------------
# 6. COARSE SELECTION
# ------------------------------------------------------------
def CoarseSelectionFunction(self, coarse):
current_month = (self.Time.year, self.Time.month)
# Re-select only once per month.
if self.last_selection_month == current_month:
return Universe.Unchanged
self.last_selection_month = current_month
filtered = [
c for c in coarse
if c.Symbol in self.djia_symbols
and c.HasFundamentalData
and c.Price is not None
and c.Price > 5
and c.DollarVolume is not None
and c.DollarVolume > 0
]
return [
c.Symbol
for c in filtered
]
# ------------------------------------------------------------
# 7. FINE SELECTION
# ------------------------------------------------------------
def FineSelectionFunction(self, fine):
candidates = []
for stock in fine:
pe_ratio = stock.ValuationRatios.PERatio
if pe_ratio is None:
continue
# Avoid invalid or extreme P/E values.
if pe_ratio <= 5:
continue
if pe_ratio >= 80:
continue
candidates.append(stock)
if len(candidates) == 0:
return Universe.Unchanged
# Highest P/E first.
sorted_by_pe = sorted(
candidates,
key=lambda f: f.ValuationRatios.PERatio,
reverse=True
)
selected = [
stock.Symbol
for stock in sorted_by_pe[:self.top_count]
]
selected_text = []
for stock in sorted_by_pe[:self.top_count]:
selected_text.append(
stock.Symbol.Value
+ " PE "
+ str(round(stock.ValuationRatios.PERatio, 2))
)
self.Debug(
"Monthly DJIA high PE selection "
+ str(selected_text)
)
return selected
# ------------------------------------------------------------
# 8. PLOTS
# ------------------------------------------------------------
def OnData(self, data):
if self._benchmark not in data or data[self._benchmark] is None:
return
benchmark_price = self.Securities[self._benchmark].Price
if benchmark_price <= 0:
return
if self.initial_benchmark_price is None:
self.initial_benchmark_price = benchmark_price
self.Plot(
"Strategy Equity",
"Portfolio Value",
self.Portfolio.TotalPortfolioValue
)
benchmark_value = (
self.initial_cash
* benchmark_price
/ self.initial_benchmark_price
)
self.Plot(
"Strategy Equity",
"Buy Hold DIA",
benchmark_value
)