| Overall Statistics |
|
Total Orders 674 Average Win 0.52% Average Loss -0.50% Compounding Annual Return 28.347% Drawdown 24.100% Expectancy 0.480 Start Equity 100000 End Equity 271175.77 Net Profit 171.176% Sharpe Ratio 1.061 Sortino Ratio 1.295 Probabilistic Sharpe Ratio 56.501% Loss Rate 27% Win Rate 73% Profit-Loss Ratio 1.04 Alpha 0.196 Beta -0.11 Annual Standard Deviation 0.176 Annual Variance 0.031 Information Ratio 0.389 Tracking Error 0.272 Treynor Ratio -1.694 Total Fees $781.11 Estimated Strategy Capacity $140000000.00 Lowest Capacity Asset CSCO R735QTJ8XC9X Portfolio Turnover 1.06% |
# region imports
from AlgorithmImports import *
# endregion
class SimpleDynamicMomentumAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020, 1, 1)
self.SetEndDate(2024, 1, 1)
self.SetCash(100000)
self.lookback = 90 # Lookback period for momentum calculation
self.rebalance_period = 15 # Rebalance period (every 15 days)
self.stop_loss_percentage = 0.15 # 15% stop-loss
self.entry_prices = {} # Store the entry prices for positions
# Market index to gauge overall market conditions
self.market = self.AddEquity("SPY", Resolution.Daily).Symbol
# Moving averages for market condition
self.short_sma = self.SMA(self.market, 50, Resolution.Daily)
self.long_sma = self.SMA(self.market, 200, Resolution.Daily)
# Smaller universe of highly liquid stocks
self.stock_universe = ["AAPL", "MSFT", "GOOGL", "AMZN", "META", "TSLA", "NVDA", "NFLX", "INTC", "CSCO"]
self.symbols = [self.AddEquity(ticker, Resolution.Daily).Symbol for ticker in self.stock_universe]
self.next_rebalance = self.Time + timedelta(days=self.rebalance_period)
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(10, 0), self.Rebalance)
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(15, 0), self.CheckStopLoss)
def OnData(self, data):
pass
def CheckStopLoss(self):
for symbol in list(self.entry_prices.keys()):
if self.Securities.ContainsKey(symbol) and self.Securities[symbol].Price < self.entry_prices[symbol] * (1 - self.stop_loss_percentage):
self.Liquidate(symbol)
self.Debug(f"Stop-loss triggered for {symbol.Value} at {self.Securities[symbol].Price}")
del self.entry_prices[symbol]
def Rebalance(self):
if self.Time < self.next_rebalance:
return
# Determine market condition
if self.short_sma.Current.Value > self.long_sma.Current.Value:
# Bullish market condition
long_weight = 0.8
short_weight = 0.2
else:
# Bearish market condition
long_weight = 0.2
short_weight = 0.8
# Calculate momentum for each stock in the universe
momentum = {}
for symbol in self.symbols:
history = self.History(symbol, self.lookback, Resolution.Daily)
if not history.empty:
momentum[symbol] = history['close'].pct_change(self.lookback).iloc[-1]
# Sort symbols based on momentum
sorted_symbols = sorted(momentum.items(), key=lambda x: x[1], reverse=True)
# Determine the number of long and short positions
num_long = 8
num_short = 2
# Go long on top-ranked symbols
long_symbols = [symbol for symbol, mom in sorted_symbols[:num_long]]
# Go short on bottom-ranked symbols
short_symbols = [symbol for symbol, mom in sorted_symbols[-num_short:]]
# Set target holdings
long_weight_per_position = long_weight / num_long if num_long > 0 else 0
short_weight_per_position = short_weight / num_short if num_short > 0 else 0
for symbol in self.symbols:
if symbol in long_symbols:
self.SetHoldings(symbol, long_weight_per_position)
self.entry_prices[symbol] = self.Securities[symbol].Price # Set entry price
elif symbol in short_symbols:
self.SetHoldings(symbol, -short_weight_per_position)
self.entry_prices[symbol] = self.Securities[symbol].Price # Set entry price
else:
self.Liquidate(symbol)
if symbol in self.entry_prices:
del self.entry_prices[symbol] # Remove entry price
# Update next rebalance time
self.next_rebalance = self.Time + timedelta(days=self.rebalance_period)
def OnEndOfAlgorithm(self):
self.Debug("Algorithm finished running.")