Overall Statistics |
Total Orders 244 Average Win 1.11% Average Loss -1.48% Compounding Annual Return 11.106% Drawdown 23.200% Expectancy 0.151 Start Equity 100000 End Equity 152342.04 Net Profit 52.342% Sharpe Ratio 0.45 Sortino Ratio 0.512 Probabilistic Sharpe Ratio 14.488% Loss Rate 34% Win Rate 66% Profit-Loss Ratio 0.75 Alpha 0.077 Beta -0.107 Annual Standard Deviation 0.151 Annual Variance 0.023 Information Ratio -0.05 Tracking Error 0.256 Treynor Ratio -0.633 Total Fees $259.35 Estimated Strategy Capacity $600000000.00 Lowest Capacity Asset GOOG T1AZ164W5VTX Portfolio Turnover 1.40% |
# region imports from AlgorithmImports import * # endregion class SimpleDynamicMomentumAlgorithm(QCAlgorithm): def Initialize(self): self.SetStartDate(2020, 1, 1) # Set start date self.SetEndDate(2024, 1, 1) # Set end date self.SetCash(100000) # Set initial capital # Define the universe of assets self.symbols = [self.AddEquity(ticker, Resolution.Daily).Symbol for ticker in ["AAPL", "MSFT", "GOOGL", "AMZN", "META"]] self.lookback = 90 # Lookback period for momentum calculation (e.g., 3 months) self.rebalance_period = 30 # Rebalance period (e.g., monthly) self.next_rebalance = self.Time + timedelta(days=self.rebalance_period) self.stop_loss_percentage = 0.1 # 10% 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) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(10, 0), self.Rebalance) def OnData(self, data): self.CheckStopLoss(data) def CheckStopLoss(self, data): for symbol in list(self.entry_prices.keys()): if symbol in data and data[symbol] is not None: if data[symbol].Price < self.entry_prices[symbol] * (1 - self.stop_loss_percentage): self.Liquidate(symbol) self.Debug(f"Stop-loss triggered for {symbol.Value} at {data[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 = int(len(sorted_symbols) * long_weight) num_short = int(len(sorted_symbols) * short_weight) # 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.")