Overall Statistics |
Total Orders 1741 Average Win 0.31% Average Loss -0.24% Compounding Annual Return 38.647% Drawdown 36.700% Expectancy 0.593 Start Equity 1000000 End Equity 3694122.83 Net Profit 269.412% Sharpe Ratio 0.877 Sortino Ratio 1.127 Probabilistic Sharpe Ratio 30.059% Loss Rate 30% Win Rate 70% Profit-Loss Ratio 1.29 Alpha 0.256 Beta 0.8 Annual Standard Deviation 0.378 Annual Variance 0.143 Information Ratio 0.68 Tracking Error 0.349 Treynor Ratio 0.414 Total Fees $9476.35 Estimated Strategy Capacity $55000000.00 Lowest Capacity Asset JNJ R735QTJ8XC9X Portfolio Turnover 5.09% |
from AlgorithmImports import * import numpy as np import pandas as pd from sklearn.preprocessing import MinMaxScaler from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from sklearn.model_selection import train_test_split import math import matplotlib.pyplot as plt from sklearn.metrics import mean_squared_error class MomentumVolumeTrendStrategy(QCAlgorithm): def Initialize(self): self.SetStartDate(2019, 1, 1) self.SetEndDate(2023, 1, 1) self.SetCash(1000000) # Add equities with daily resolution self.symbols = [] self.symbols.append(self.AddEquity("AMD", Resolution.Daily).Symbol) self.symbols.append(self.AddEquity("JNJ", Resolution.Daily).Symbol) # Dictionary to store indicators for each stock self.indicators = {} for symbol in self.symbols: self.indicators[symbol] = { "sma50": self.SMA(symbol, 50, Resolution.Daily), "sma200": self.SMA(symbol, 200, Resolution.Daily), "rsi": self.RSI(symbol, 14, MovingAverageType.Simple, Resolution.Daily), "macd": self.MACD(symbol, 12, 26, 9, MovingAverageType.Exponential, Resolution.Daily), "bb": self.BB(symbol, 20, 2.0, Resolution.Daily), "obv": self.OBV(symbol, Resolution.Daily), "volume_ma": self.SMA(symbol, 20, Resolution.Daily, Field.Volume) } # Warm up indicators self.SetWarmUp(200) # Initialize LSTM models for each symbol self.models = {} for symbol in self.symbols: self.models[symbol] = self.build_lstm_model() def OnData(self, data): if self.IsWarmingUp: return for symbol in self.symbols: ind = self.indicators[symbol] # Ensure indicators are ready if not all([ind["sma50"].IsReady, ind["sma200"].IsReady, ind["rsi"].IsReady, ind["macd"].IsReady, ind["bb"].IsReady, ind["obv"].IsReady, ind["volume_ma"].IsReady]): continue # Get values of indicators price = self.Securities[symbol].Price sma50 = ind["sma50"].Current.Value sma200 = ind["sma200"].Current.Value rsi = ind["rsi"].Current.Value macd = ind["macd"].Current.Value bb = ind["bb"] upperBB = bb.UpperBand.Current.Value middleBB = bb.MiddleBand.Current.Value lowerBB = bb.LowerBand.Current.Value obv = ind["obv"].Current.Value avg_volume = ind["volume_ma"].Current.Value current_volume = self.Securities[symbol].Volume # Define base position (trend-following component) baseWeight = 0.75 if sma50 > sma200 else -0.75 # Volume-based confirmation volume_confirmation = current_volume > 1.5 * avg_volume # Significant volume spike # LSTM prediction # Use the last 30 days of data to predict the next logreturn and risk historical_data = self.History(symbol, 30, Resolution.Daily) if len(historical_data) < 30: return # Ensure we have enough data points for LSTM (skip if not enough data) df = pd.DataFrame(historical_data) features = ['logclose', 'close', 'volume'] df['logclose'] = df['close'].apply(lambda x: math.log(x)) df['logclose'] = df['logclose'] - df['logclose'].iloc[0] scaled_features = MinMaxScaler(feature_range=(0, 1)).fit_transform(np.array(df[features])) # Ensure there are exactly 30 rows (time steps) and 3 features (close, volume, logclose) if scaled_features.shape[0] != 30 or scaled_features.shape[1] != 3: return # Skip if data shape is incorrect X = scaled_features.reshape(1, 30, 3) # Reshape for LSTM input (1 batch, 30 time steps, 3 features) prediction = self.models[symbol].predict(X) predicted_return = prediction[0][0] # The predicted logreturn predicted_risk = prediction[0][1] # New risk prediction output # Bullish and Bearish signals bullishSignal = (price > upperBB and rsi > 55 and predicted_return > 0) bearishSignal = (price < lowerBB and rsi < 45 and predicted_return < 0) # Volume spike and MACD confirmation volume_signal = volume_confirmation and predicted_return > 0 macd_signal = macd > 0 # Define position adjustment based on signals moveFactor = 1 # Default "small move" # Check for medium or big moves if bullishSignal and macd_signal: moveFactor = 1.5 # Medium move for BB, RSI, LSTM & MACD if bullishSignal and macd_signal and volume_signal: moveFactor = 2 # Big move for BB, RSI, LSTM, MACD & Volume if bearishSignal and macd_signal: moveFactor = -1.5 # Medium bearish move if bearishSignal and macd_signal and volume_signal: moveFactor = -2 # Big bearish move # Dynamic Position Scaling based on predicted risk # If risk is high, reduce the position size risk_factor = max(0.1, 1 - predicted_risk) # Ensure the risk factor doesn't go below 0.1 if baseWeight > 0: targetWeight = min(baseWeight * risk_factor * moveFactor, 1) if baseWeight < 0: targetWeight = max(baseWeight * risk_factor * moveFactor, -1) # Set holdings based on final calculated weight self.SetHoldings(symbol, targetWeight) # Debugging output self.Debug(f"{self.Time} {symbol.Value}: Price={price:.2f}, SMA50={sma50:.2f}, " f"SMA200={sma200:.2f}, RSI={rsi:.2f}, MACD={macd:.2f}, " f"UpperBB={upperBB:.2f}, MiddleBB={middleBB:.2f}, LowerBB={lowerBB:.2f}, " f"OBV={obv:.2f}, Volume={current_volume}, Avg Volume={avg_volume}, " f"Predicted Return={predicted_return:.2f}, Predicted Risk={predicted_risk:.2f}, " f"Target Weight={targetWeight:.2f}") def build_lstm_model(self): # Build and return the LSTM model (modified for dual output: return and risk prediction) model = Sequential() model.add(LSTM(40, return_sequences=True, input_shape=(10, 3))) model.add(Dropout(0.2)) model.add(LSTM(40, return_sequences=False)) model.add(Dropout(0.2)) model.add(Dense(2)) # 2 outputs: return and risk model.compile(optimizer='adam', loss='mean_squared_error') return model def OnEndOfDay(self): # You can use this function to save models or do post-analysis for symbol in self.symbols: self.models[symbol].save(f'{symbol.Value}_lstm_model.h5')