| Overall Statistics |
|
Total Orders 196 Average Win 14.57% Average Loss -8.06% Compounding Annual Return 47.945% Drawdown 73.600% Expectancy 0.547 Start Equity 10000 End Equity 103703.10 Net Profit 937.031% Sharpe Ratio 0.905 Sortino Ratio 0.835 Probabilistic Sharpe Ratio 24.920% Loss Rate 45% Win Rate 55% Profit-Loss Ratio 1.81 Alpha 0.397 Beta 0.862 Annual Standard Deviation 0.537 Annual Variance 0.289 Information Ratio 0.738 Tracking Error 0.518 Treynor Ratio 0.564 Total Fees $629.60 Estimated Strategy Capacity $340000000.00 Lowest Capacity Asset MSTR RBGP9S2961YD Portfolio Turnover 8.97% |
from AlgorithmImports import *
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import numpy as np
import pandas as pd
class MLTradingAlgorithm(QCAlgorithm):
def Initialize(self):
# 1. Setup Algorithm Parameters
self.SetStartDate(2019, 1, 1) # Start date
self.SetEndDate(2024, 12, 31) # End date
self.SetCash(10000) # Initial capital
# 2. Add Equity (MSTR)
self.symbol = self.AddEquity("MSTR", Resolution.Daily).Symbol
# 3. Rolling Window for 200 Days of TradeBar Data
self.data = RollingWindow[TradeBar](200) # Store last 200 bars
# 4. Warm-Up Period to ensure rolling window is filled
self.SetWarmUp(200)
# 5. Initialize Machine Learning Model
self.model = RandomForestClassifier(n_estimators=100, random_state=42)
self.training_count = 0
self.is_model_trained = False # Flag to check if model is trained
# 6. Schedule Training Every Monday at 10:00 AM
self.Schedule.On(self.DateRules.Every(DayOfWeek.Monday),
self.TimeRules.At(10, 0),
self.TrainModel)
def OnData(self, data):
# 7. Check data existence
if not data.ContainsKey(self.symbol):
return
trade_bar = data[self.symbol]
if trade_bar is None:
return
# 8. Add TradeBar data to rolling window
self.data.Add(trade_bar)
# 9. Ensure rolling window is ready
if not self.data.IsReady or self.data.Count < 200:
return
# Check if the model is trained
if not self.is_model_trained:
self.Debug("Model not trained yet. Skipping prediction.")
return
# 10. Prepare the latest feature row
df = self.GetFeatureDataFrame()
if df is None or len(df) == 0:
return
# Extract the most recent feature row (X)
latest_features = df.iloc[-1, :-1].values.reshape(1, -1)
# 11. Predict (try-catch in case model fails)
try:
prediction = self.model.predict(latest_features)[0] # 1 = Buy, 0 = Sell
except:
self.Debug("Error during prediction.")
return
# 12. Execute Trading Logic
holdings = self.Portfolio[self.symbol].Quantity
# If prediction is Buy (1), and we don't currently hold shares, go long
if prediction == 1 and holdings <= 0:
self.SetHoldings(self.symbol, 1.0)
# If prediction is Sell (0), and we do hold shares, liquidate
elif prediction == 0 and holdings > 0:
self.Liquidate(self.symbol)
def TrainModel(self):
# Prepare features for training
df = self.GetFeatureDataFrame()
if df is None or len(df) < 50:
self.Debug("Insufficient data for training.")
return
# Split into X (features) and y (target)
X = df.iloc[:, :-1]
y = df.iloc[:, -1]
# 80/20 time-based split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, shuffle=False, random_state=42
)
# Fit the model
self.model.fit(X_train, y_train)
self.is_model_trained = True
# Evaluate model accuracy
y_train_pred = self.model.predict(X_train)
train_accuracy = accuracy_score(y_train, y_train_pred)
y_test_pred = self.model.predict(X_test)
test_accuracy = accuracy_score(y_test, y_test_pred)
self.training_count += 1
self.Debug(f"Training #{self.training_count}: "
f"Train Accuracy: {train_accuracy:.2%}, "
f"Test Accuracy: {test_accuracy:.2%}")
def GetFeatureDataFrame(self):
"""
Convert rolling window data to DataFrame and compute:
- Bollinger Bands (20-day)
- Historical Volatility (HV_30)
- Target variable (1 if next day's Close > today's Close, else 0)
"""
if self.data.Count < 200:
return None
# Extract close prices
close_prices = [bar.Close for bar in self.data]
df = pd.DataFrame(close_prices, columns=["Close"])
# -----------------------------------------
# 1) Bollinger Bands (20-day)
# -----------------------------------------
period = 20
df["BB_mid"] = df["Close"].rolling(period).mean()
df["BB_std"] = df["Close"].rolling(period).std()
df["BB_upper"] = df["BB_mid"] + 2 * df["BB_std"]
df["BB_lower"] = df["BB_mid"] - 2 * df["BB_std"]
# -----------------------------------------
# 2) Historical Volatility (HV_30)
# annualized = std dev of daily returns * sqrt(252)
# -----------------------------------------
df["daily_returns"] = df["Close"].pct_change()
df["HV_30"] = df["daily_returns"].rolling(window=30).std() * np.sqrt(252)
# -----------------------------------------
# 3) Target variable
# 1 = next day close > current day close
# 0 = otherwise
# -----------------------------------------
df["Target"] = (df["Close"].shift(-1) > df["Close"]).astype(int)
# Clean up
df.dropna(inplace=True)
# Drop any columns we don't want in the features
# daily_returns is intermediate; keep HV_30, Bollinger bands
df.drop(columns=["daily_returns"], inplace=True)
return df