from AlgorithmImports import *
import xgboost as xgb
import pandas as pd
import numpy as np
from datetime import time, timedelta
class IntradayXGBoostES(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2024, 1, 1)
self.SetEndDate(2024, 2, 1)
self.SetCash(100000)
future = self.AddFuture(Futures.Indices.SP500EMini, Resolution.Minute)
future.SetFilter(timedelta(0), timedelta(180))
self.future_symbol = future.Symbol
self.model = None
self.lookback_days = 30
self.retrain_every_n_days = 5
self.bar_buffer = {}
self.training_data = []
self.labels = []
self.last_training_date = None
self.current_day = None
self.history = None
self.SetWarmUp(timedelta(days=30))
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(9, 0), self.ResetDailyCache)
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(10, 0), self.TradeSignal)
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(12, 0), self.ExitPosition)
def ResetDailyCache(self):
self.current_day = self.Time.date()
self.bar_buffer[self.current_day] = []
def OnData(self, data):
if self.IsWarmingUp or self.future_symbol not in data: return
bar = data[self.future_symbol]
if self.current_day not in self.bar_buffer:
self.bar_buffer[self.current_day] = []
self.bar_buffer[self.current_day].append(bar)
def TradeSignal(self):
if self.model is None and self.history is None:
self.history = self.History(self.future_symbol, timedelta(days=60), Resolution.Minute)
self.Debug(f"Loaded historical data: {len(self.history)} rows")
self.TrainModel()
self.last_training_date = self.Time.date()
if self.current_day not in self.bar_buffer:
return
bars = [b for b in self.bar_buffer[self.current_day] if time(9, 0) <= b.EndTime.time() < time(10, 0)]
if len(bars) < 30:
return
df = pd.DataFrame([{
'time': b.EndTime,
'open': b.Open,
'high': b.High,
'low': b.Low,
'close': b.Close,
'volume': b.Volume
} for b in bars]).set_index('time')
vwap = (df['close'] * df['volume']).sum() / df['volume'].sum()
features = {
'ret_5m': df['close'].pct_change(5).iloc[-1],
'ret_10m': df['close'].pct_change(10).iloc[-1],
'open_close_range': (df['close'].iloc[-1] - df['close'].iloc[0]) / df['close'].iloc[0],
'vwap': vwap,
'price_above_vwap': df['close'].iloc[-1] / vwap,
'vwap_slope': (df['close'][-10:] * df['volume'][-10:]).sum() / df['volume'][-10:].sum()
- (df['close'][:10] * df['volume'][:10]).sum() / df['volume'][:10].sum(),
'vwap_deviation_at_close': df['close'].iloc[-1] - vwap,
'early_range': df['high'][:6].max() - df['low'][:6].min(),
'volume_imbalance': df['volume'][-5:].sum() / max(1, df['volume'][:5].sum()),
'reversal_from_high': df['high'].max() - df['close'].iloc[-1],
'reversal_from_low': df['close'].iloc[-1] - df['low'].min()
}
X_today = pd.DataFrame([features])
if self.model and (self.Time.date() - self.last_training_date).days >= self.retrain_every_n_days:
self.TrainModel()
self.last_training_date = self.Time.date()
if self.model:
pred = self.model.predict(X_today)[0]
self.Debug(f"{self.Time} Prediction: {pred}")
if pred == 1:
self.MarketOrder(self.future_symbol, 1)
def ExitPosition(self):
self.Liquidate(self.future_symbol)
def TrainModel(self):
self.Debug(f"[{self.Time.date()}] Starting training routine...")
self.training_data = []
self.labels = []
df = self.history.copy()
df = df.sort_index()
# Handle MultiIndex correctly
if isinstance(df.index, pd.MultiIndex):
df.index = df.index.get_level_values(1)
# ✅ Final, bulletproof datetime extraction
dates = sorted(set([x.date() for x in df.index]))
recent_days = dates[-self.lookback_days:]
for day in recent_days:
df_day = df[df.index.date == day]
df_day = df_day.between_time("09:00", "12:00")
if len(df_day) < 60:
self.Debug(f"[{day}] Skipped: not enough data")
continue
try:
entry_bar = df_day.between_time("10:00", "10:00").iloc[0]
exit_bar = df_day.between_time("12:00", "12:00").iloc[0]
except IndexError:
self.Debug(f"[{day}] Skipped: missing entry or exit bar")
continue
label = 1 if (exit_bar['close'] / entry_bar['open'] - 1) > 0.0 else 0 # relaxed for debug
df_feat = df_day.between_time("09:00", "09:59")
if len(df_feat) < 30:
self.Debug(f"[{day}] Skipped: insufficient pre-market bars")
continue
vwap = (df_feat['close'] * df_feat['volume']).sum() / df_feat['volume'].sum()
feat = {
'ret_5m': df_feat['close'].pct_change(5).iloc[-1],
'ret_10m': df_feat['close'].pct_change(10).iloc[-1],
'open_close_range': (df_feat['close'].iloc[-1] - df_feat['close'].iloc[0]) / df_feat['close'].iloc[0],
'vwap': vwap,
'price_above_vwap': df_feat['close'].iloc[-1] / vwap,
'vwap_slope': (df_feat['close'][-10:] * df_feat['volume'][-10:]).sum() / df_feat['volume'][-10:].sum()
- (df_feat['close'][:10] * df_feat['volume'][:10]).sum() / df_feat['volume'][:10].sum(),
'vwap_deviation_at_close': df_feat['close'].iloc[-1] - vwap,
'early_range': df_feat['high'][:6].max() - df_feat['low'][:6].min(),
'volume_imbalance': df_feat['volume'][-5:].sum() / max(1, df_feat['volume'][:5].sum()),
'reversal_from_high': df_feat['high'].max() - df_feat['close'].iloc[-1],
'reversal_from_low': df_feat['close'].iloc[-1] - df_feat['low'].min()
}
self.training_data.append(feat)
self.labels.append(label)
if self.training_data:
df_train = pd.DataFrame(self.training_data)
pos = sum(self.labels)
total = len(self.labels)
self.Debug(f"[{self.Time.date()}] Training samples: {total}, Positives: {pos} ({pos/total:.1%})")
self.model = xgb.XGBClassifier(
n_estimators=100,
max_depth=3,
learning_rate=0.05,
subsample=0.8,
colsample_bytree=0.8,
random_state=42,
use_label_encoder=False,
eval_metric='logloss'
)
self.model.fit(df_train, self.labels)
else:
self.Debug(f"[{self.Time.date()}] No valid training data — model not built.")