| Area | Change |
|---|---|
| Leakage fix | Each ticker is split chronologically before concatenation — eliminates cross-ticker temporal leakage |
| Execution delay | Backtest now executes at next-day open, not same-day close |
| New features | SPY regime (above/below 200MA), VIX z-score, relative strength vs. SPY, earnings proximity |
| Walk-forward CV | --walkforward flag trains across rolling yearly windows for honest multi-regime evaluation |
| Confidence gate | --confidence 0.55 skips low-conviction signals in both backtesting and live inference |
| Ensemble inference | predict.py automatically averages softmax probs across all walk-forward fold checkpoints |
| Broader tickers | Default ticker list now includes defensive/old-economy names to reduce survivorship bias |
Input (34 features, seq_len=30)
└─► Linear Projection + LayerNorm + GELU
└─► Bidirectional LSTM (2 layers, hidden=128)
└─► Positional Encoding
└─► Multi-Head Self-Attention (4 heads)
└─► Residual + LayerNorm
└─► Mean Pool ⊕ Last Token Pool
└─► MLP Head (128 → 64 → 3)
└─► Sell / Hold / Buy
34 engineered features:
- Returns: 1d, log, 5d, overnight gap
- Momentum: RSI(14), RSI(7), ROC(10), ROC(20)
- Trend: EMA(9/21) cross, EMA(21/50) cross, MACD line/signal/histogram
- Volatility: ATR(14), Bollinger %B + width, HV(20)
- Volume: OBV delta, volume z-score, VWAP ratio
- Microstructure: close/open ratio, HL range, upper/lower wick
- Calendar: day-of-week (sin/cos encoded)
- [NEW] Regime: SPY above/below 200-day MA, VIX z-score
- [NEW] Relative strength vs. SPY (1-day excess return)
- [NEW] Earnings proximity (days to nearest earnings, normalised)
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install torch --index-url https://download.pytorch.org/whl/cu121
pip install yfinance pandas numpy scikit-learn# Standard single-split training
python train.py
# Walk-forward cross-validation (recommended for realistic evaluation)
python train.py --walkforward
# Custom tickers
python train.py --tickers AAPL MSFT NVDA AMZN TSLA
# Custom output dir
python train.py --output_dir runs/experiment_1
# With confidence gating in backtest (only trade signals > 55% confidence)
python train.py --config my_config.json
# (set "confidence_threshold": 0.55 in config)Outputs in outputs/:
| File | Description |
|---|---|
best_model.pt |
Best checkpoint by macro F1 |
training_log.csv |
Per-epoch train/val metrics |
test_preds.npy |
Test set predictions |
test_labels.npy |
Test set ground truth |
test_probs.npy |
Softmax probabilities |
backtest_equity.npy |
Equity curve |
scaler.pkl |
Fitted RobustScaler |
feature_names.json |
Feature list |
config_used.json |
Run configuration |
walkforward/fold_XX/ |
Per-fold outputs (walk-forward mode) |
# Single ticker (auto-ensembles walk-forward folds if available)
python predict.py --ticker AAPL
# With confidence threshold (UNCERTAIN shown if below threshold)
python predict.py --ticker AAPL --confidence 0.55
# Watchlist scan
python predict.py --watchlist AAPL MSFT NVDA GOOGL TSLA
# Force single model (no ensemble)
python predict.py --ticker AAPL --no_ensembleExample output:
================================================
Ticker : AAPL
Date : 2025-01-15
Signal : BUY 🟢 (ensemble of 4 folds)
Confidence :
Sell : 12.3%
Hold : 31.4%
Buy : 56.3%
================================================
{
"tickers": ["AAPL", "MSFT", "NVDA", "XOM", "JNJ"],
"start": "2015-01-01",
"end": "2024-12-31",
"seq_len": 30,
"forward_days": 5,
"buy_threshold": 0.02,
"sell_threshold": -0.02,
"include_earnings": true,
"hidden_dim": 128,
"lstm_layers": 2,
"num_heads": 4,
"epochs": 100,
"batch_size": 256,
"lr": 3e-4,
"patience": 15,
"confidence_threshold": 0.55,
"wf_train_years": 4,
"wf_test_years": 1
}Each ticker is split into train/val/test before concatenation with other tickers. In v1, concatenating first then splitting meant that ticker B's 2023 data could appear in the training set while ticker A's 2022 data was in the test set — a subtle but real form of look-ahead leakage.
Signals are generated from day N's close and executed at day N+1's open. v1 executed at the same close that generated the signal, which is impossible in practice and overstates returns.
A single train/test split gives one data point on generalisation and can happen to land on an easy or hard market period. Walk-forward trains on rolling 4-year windows and tests on 1-year windows, giving multiple out-of-sample readings across different regimes (low-vol bull, COVID crash, 2022 rate-hike bear, etc.).
The model outputs a probability distribution. A Buy signal at 85% confidence
is fundamentally different from one at 42%. Setting confidence_threshold: 0.55
instructs both the backtest and live inference to treat low-conviction
signals as Hold, which often improves risk-adjusted returns.
After walk-forward training, predict.py automatically detects fold checkpoints
and averages their softmax probabilities. This reduces variance on live signals
by 20-30% compared to a single model.
For educational and research purposes only. Past model performance does not guarantee future results. Paper-trade for at least 3–6 months before considering real capital. Financial markets are inherently unpredictable and no model eliminates that risk.