2008-06-042026-02-20 · 4,343 trading days

Multi-Horizon Transformer for Nifty 50 Direction Forecasting

Multi-horizon Transformer for Nifty 50 direction, with rigorous evidence it has no edge.

A single Transformer encoder predicts whether the Nifty 50 index will close higher — simultaneously across 20 forward horizons (1 to 20 days). Trained on 19 engineered features over 4,263 sequences with a strict temporal split. Every number below comes from a real trained model on held-out test data.

Transformer encoder · 2 blocks · 4 heads · d64No look-ahead · 8 audited leakage rulesReal India costs · 9.58bps round-tripLive paper trading · updated every weekday
Mean AUC (test)
0.5033
20-horizon average · 0.5 = coin flip
Mean IC (Spearman)
+0.0210
signal vs realized return
Timing Sharpe (net)
+0.00
95% CI [-0.58, 1.94] · 9.6bps round-trip
Max Drawdown
0.0%
long/flat timing equity
The honest verdict — read this first
This model has no statistically detectable edge. Mean AUC is 0.5033 against a 0.50 coin flip, and 0 of 20 horizons survive multiple-testing correction. The site is published as a negative result: the contribution is the measurement apparatus — leakage audits, overlap-corrected intervals, deflated Sharpe — not alpha. Nothing here should be traded.
Overview

What this system does

One model, 20 answers

A shared Transformer encoder outputs 20 independent logits — one per horizon — so short- and long-range signal inform each other through shared gradients instead of 20 separate models.

No look-ahead leakage

Chronological 70/15/15 split, no shuffling. StandardScaler is fit on training data only. Targets are strictly forward-looking.

Signal, not just accuracy

All 20 horizon logits are ensembled into one signal, Platt-calibrated on validation data, and stress-tested as a long/flat timing strategy with India costs, bootstrap confidence intervals, decile attribution, and walk-forward validation.

^NSEI price context
2008-06-04 → 2026-02-20
Live

Paper trading — the strategy, forward, on real prices

Paper trading — simulated, no real money. Runs the backtest's primary strategy forward on real NSE closes, charged real India futures costs. The model has NO validated edge (see the results above), so this is an honest live demonstration, not a profit engine. Not investment advice.
Paper return
19.5%
684 trading days
vs Buy & Hold
-5.3%
BH 24.8%
Sharpe
+0.72
13 round-trips
Position now
LONG
41% time in market
Paper equity (start = 100) vs buy-and-hold
timing_rolling (primary) · 9.58bps round-trip · seeded from frozen model trained through 2023-10-19 (all later days are OOS) · as of 2026-07-31

This trades the exact primary strategy forward, out-of-sample, on real Nifty closes with the full India futures cost stack. It is not tuned to look good — it under-performs simply holding the index, which is what the no-edge finding predicts. That is the point: honest forward proof, updated as new data arrives.

Predictions

What the model says today — and what that is worth

Stance
LONG
as of 2026-07-31
Signal
+1.14
87th pct of 250d
Entry threshold
+0.32
70th-pct rule
Actionable horizons
0 / 20
after multiple-testing correction
Read the intervals, not the probabilities. No horizon carries statistically distinguishable skill. The probabilities below are the model's honest output, not an edge. The boldest row below reads 71.4% P(up) at 9d, yet its AUC interval [0.420, 0.682] spans 0.50 — a confident-looking number with no evidence behind it. The table is laid out so that is visible at a glance rather than buried.
Forward probabilities across all 20 horizons
frozen ensemble (3 seeds, trained through 2023-10-19) · per-horizon Platt, fit on held-out data only · skill measured on this model's own 683 out-of-sample days · last close 24384
HorizonP(up)OOS AUC95% CI on AUC — overlap-correctedEff. nSkill
1d48.2%0.443
683indistinct
2d57.3%0.480
341indistinct
3d57.9%0.497
227indistinct
4d58.1%0.460
170indistinct
5d58.8%0.533
136indistinct
6d53.9%0.457
113indistinct
7d66.1%0.468
97indistinct
8d59.3%0.437
85indistinct
9d71.4%0.551
75indistinct
10d70.3%0.479
67indistinct
11d59.2%0.435
61indistinct
12d59.7%0.475
56indistinct
13d59.3%0.585
52indistinct
14d59.1%0.590
48indistinct
15d60.0%0.573
45indistinct
16d60.0%0.567
42indistinct
17d56.8%0.516
39indistinct
18d56.0%0.543
37indistinct
19d54.7%0.539
35indistinct
20d64.0%0.557
33indistinct
Confidence intervals use the effective sample size (test days / horizon), not the raw day count, because overlapping forward labels are not independent observations. Effective n is labelled days ÷ horizon: at 20 days, 664 overlapping windows carry only ~33 independent observations, which is why the intervals widen with horizon.

today's ensemble signal (+1.14) sits at the 87th percentile of the trailing 250 days; the rule goes long above the 70th. The stance is a mechanical rule, not a conviction call: it fires whenever the trailing-quantile condition is met, independent of whether any horizon shows measurable skill. Publishing both together is deliberate — it shows exactly how much (or how little) statistical backing the live position has.

Architecture

Model architecture

Data flow
1
Input sequence (60 days × 19 features)
2
Dense projection → d_model = 64
3
+ Sinusoidal positional encoding
4
2 × Encoder block · 4 heads · FFN 128
5
Attention pooling (learned softmax over the 60 steps)
6
Dense(20) → 20 raw logits
Specification
Lookback window
60 days
Input features
19
d_model
64
Attention heads
4
Encoder blocks
2
FFN hidden dim
128
Pooling
Attention (learned)
Output horizons
20
Loss
BCE (from logits)
Input features
19 stationary signals per day — raw OHLCV price levels are excluded (non-stationary out-of-sample)
daily_retroll_mean_ret_5roll_mean_ret_10roll_mean_ret_20roll_vol_5roll_vol_10roll_vol_20momentum_10log_volumevol_roll_mean_5ma_diff_10vix_chgvix_z60spx_retspx_ret_5usdinr_retcrude_retbreadth_above_ma20xs_dispersion
Results

Per-horizon predictive skill (test set)

AUC measures ranking ability independent of any threshold; 0.50 is a coin flip. Read these beside the confidence intervals in the Predictions section — a bar above 0.50 is not evidence of skill on its own.

ROC-AUC by horizon
teal = above the 0.50 coin-flip baseline · rose = below
Information Coefficient by horizon
Spearman rank corr: signal vs realized forward return

Daily index direction is close to efficient — AUCs hover near 0.5 and IC is small, as expected for a liquid benchmark. The value is in the aggregate ranking of the signal, tested next in a cost-aware backtest.

Probability calibration · horizon 20
reliability diagram on test — closer to the diagonal is better
Why calibrate?

The model is trained for classification, so its raw sigmoid outputs rank market states well but are not trustworthy probabilities. A Platt scaler is fit per horizon on the validation set only and applied to test predictions, so "P(up) = 0.6" means the market actually rose about 60% of the time at that score. All probability thresholds on this page use the calibrated values; rank metrics (AUC, IC) are unaffected.

Backtest

Long/flat timing backtest — calibrated 20-horizon ensemble

All 20 logits are z-scored on validation statistics and averaged into one signal. The strategy goes long when that signal clears a trailing percentile, and is charged the full India futures cost stack on every switch.

On a single index, long-short quantile spreads are a cross-sectional idea that does not transfer; the honest framing is market timing: hold the index when the ensemble signal is in its top 30% (threshold fixed on validation data), sit in cash otherwise.

Selection caveat (read this): Model architecture/optimizer were selected on VALIDATION only (scripts/select_model.py); the test set was evaluated once. The entry-threshold RULE, however, was not: the frozen validation cutoff (timing_ensemble) turned out degenerate on test - the signal's level shifts (validation mean 0.00 vs test mean -0.79), so it never trades and returns exactly 0. The rolling-window rule (past-only, level-invariant) was adopted after observing that, so its +0.62 Sharpe carries selection optimism and should be read as an upper bound, not a clean out-of-sample estimate. Its 95% CI spans zero and it still loses to buy-and-hold. All three rules are published unmodified.
Honest out-of-sample outcome: on this run the ensemble signal never crossed its validation-fixed entry threshold during the test window, so the disciplined strategy stayed entirely in cash — zero trades, zero Sharpe. Not a bug: with a threshold fixed on past data and only 32 non-overlapping test periods, "never good enough to trade" is a legitimate result, and a truer verdict on the edge than forcing positions. The individual horizon and cross-sectional signals below still carry the analysis; the Sharpe explorer lets you compare the strategies that did trade.
Net Sharpe
+0.00
95% CI [-0.58, 1.94]
Total Return
0.0%
32 non-overlapping trades
Hit Rate
0.0%
profitable trades
Avg Exposure
0%
time in market vs cash
Equity curve — long/flat timing vs buy-and-hold
net of 9.58bps round-trip India futures costs · non-overlapping 20-day holds
vs passive Nifty benchmark
does the signal beat simply holding the index?
Strategy
0.0%
Sharpe +0.00
Buy & Hold
30.2%
Sharpe +0.94
Excess
-19.1%
over holding

The Nifty has a strong upward drift, so a long-biased signal can look good on its own. The honest test is the excess over passively holding the index, net of costs.

India cost model · futures
round-trip 9.58bps charged on every trade
STT (securities transaction tax)2.00 bps
Stamp duty0.20 bps
Slippage (bid-ask + impact)6.00 bps
Exchange transaction0.35 bps
Brokerage0.80 bps
GST0.21 bps
SEBI turnover0.02 bps
Round-trip total9.58 bps
Strategy comparison
StrategySharpeReturn
Timing · ensemble+0.000.0%
Timing · h20 only+0.000.0%
Timing · best-val h9+0.631.0%
Quantile L/S (ref)-1.08-24.6%
Sign (ref)+0.8426.4%
Buy & Hold+0.9430.2%

Quantile L/S and sign are cross-sectional constructs kept for reference — shorting a structurally drifting index is not a meaningful strategy.

Threshold sweep
long/flat Sharpe vs calibrated P(up) threshold (thresholds from validation)
Decile attribution
mean forward return by calibrated-probability decile
Yearly Sharpe
regime stability of the long/flat timing strategy
Interactive

Sharpe explorer — sweep the cost, watch the ratios move

A Sharpe ratio is only meaningful next to its assumptions. Drag the transaction cost and every strategy's net Sharpe and equity curve recompute live from the raw per-trade returns — so you can see exactly how fragile (or robust) each ratio is to costs, and how the strategies re-rank against each other. The default is the model's India per-side cost.

Index-track strategies (horizon 20)
net Sharpe = mean/std of non-overlapping period returns, annualized; recomputed in-browser
Net Sharpe by strategy at this cost
Equity curves at this cost (net)
StrategyNet SharpeTotal return
Timing · rolling thr+0.6311.2%
Timing · frozen thr+0.000.0%
Vol-targeted+0.3710.0%
Timing · h20+0.000.0%
Quantile L/S-1.08-24.6%
Sign+0.8426.4%
Robustness

Walk-forward validation

Eight expanding-window folds, each retrained from scratch. If an edge were real and stable, fold Sharpes would cluster above zero. The spread across folds is the honest measure of regime robustness.

The single split can get lucky. Here the model is retrained from scratch on an expanding window and evaluated out-of-sample on the next block — 8 folds, each using the long/flat ensemble timing rule with thresholds fixed on its own training carve. Mean net Sharpe: +0.28 ± 0.36 across folds.

Fold 1
-0.32
AUC h20: 0.403
Return: -7.0%
Train n: 1705
Fold 2
+0.69
AUC h20: 0.594
Return: 0.5%
Train n: 2024
Fold 3
+0.24
AUC h20: 0.452
Return: 3.2%
Train n: 2343
Fold 4
+0.00
AUC h20: 0.205
Return: -9.5%
Train n: 2662
Fold 5
+0.63
AUC h20: 0.661
Return: 4.2%
Train n: 2981
Fold 6
+0.18
AUC h20: 0.481
Return: 1.7%
Train n: 3300
Fold 7
+0.78
AUC h20: 0.487
Return: 4.1%
Train n: 3619
Fold 8
+0.01
AUC h20: 0.659
Return: -0.0%
Train n: 3938
Cross-Section

Where direction models can earn: ranking 85 stocks against each other

Timing one near-efficient index failed the honest test above. This track asks a better question: on the same date, which of 85 NSE large caps will do relatively better? The same shared-weight Transformer scores every stock; each rebalance goes long the top 20% and short the bottom 20% — a genuine cross-sectional quantile spread, the construct the single-index section could only imitate. Targets here are relative: did the stock beat the cross-sectional median return that date — the canonical label for a ranking task (absolute direction labels saturate to "up" in a bull window). The model also sees 29 features including cross-sectional ones — momentum and returns demeaned by the universe, per-date percentile ranks, and sector-relative momentum — the relative signals a ranking task needs. Every configuration (absolute/relative targets, per-stock/cross-sectional features, classification/regression head) is published; none is cherry-picked.

Mean daily IC
-0.029
IR -0.17 · 42% days positive
L/S Spread Sharpe
-0.84
95% CI [-2.32, 0.40] · net futures
Long-only Top 20%
19.1%
vs EW universe 41.6% (gross)
Test Window
33 rebal.
2023-10-30 → 2026-06-16 · 20-day holds
Equity curves — spread, long-only, and the passive benchmark
legs charged real India costs: futures 9.6bps, delivery 28.2bps round-trip
Quintile attribution
mean 20-day forward return by signal quintile — monotonic = signal ranks correctly
Daily cross-sectional IC
Spearman rank corr across stocks, per test date · pooled rank IC -0.026 (overlapping horizon — disclosed)
Honest caveats
  • Universe is (mostly) today's large caps backtested into the past: survivorship bias inflates absolute returns; the long-short spread is partially insulated but still favored.
  • Daily IC uses overlapping 20-day forward returns (standard practice); all P&L is computed on non-overlapping rebalances.
  • Equal-weight benchmark is gross of costs; strategy legs are net.
  • Targets are relative. Both formulations were run and both results are published (cross_section.json / cross_section_absolute.json) - no cherry-picking.
Live signals

Per-stock predictions across all 20 horizons — and pick your risk

The cross-sectional model's latest output for every name in the universe: each row is a stock, each column a forecast horizon (1–20 days), colored by the calibrated probability that the stock outperforms the universe median. Click any stock for its horizon curve. Then choose a risk profile — each is a real backtested construction labelled by its actual historical Sharpe, showing the basket it would hold from today's signal.

Signal heatmap · risk profiles
objective: classification · calibrated probabilities
Research demonstration — not investment advice. This model has no validated predictive edge (see the results above: information coefficients are within noise and no strategy beats a passive equal-weight benchmark after costs). These are the model's raw outputs on the most recent historical window, not a recommendation to buy or sell any security. The universe carries survivorship bias. Do not trade on this.
Model signal as of 2026-07-14 · 85 names · calibrated P(outperform universe median) per horizon outperform underperform
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
TORNTPHARM (Pharma) · P(outperform) across horizons
Pick a risk profile — each is a real backtested construction, labelled by its historical Sharpe
Long the top 17 names by signal (top 20%)
Hist. return
19.1%
Sharpe
0.49
Max DD
-27.5%
Long today
TORNTPHARMVEDLHINDALCOMUTHOOTFINEICHERMOTCOALINDIAFEDERALBNKONGCBAJAJ-AUTOLUPINAPOLLOHOSPBOSCHLTD+5 more
Interpretability

What the model attends to

Average attention by lookback distance
2nd encoder block · averaged over samples, heads, queries
Training history
binary cross-entropy loss · early stopping restores best weights
Feedback

Learning from mistakes — only where a mistake can be identified

Every closed trade is decomposed into signal error, cost drag, and noise. The distinction matters: cost drag is arithmetic and can be acted on immediately, while a loss inside the noise floor is not a mistake at all, and 'learning' from it means fitting randomness.

Closed trades
6
noise floor 3.8%
Hit rate
67%
binomial p = 0.688
Cost drag
0.57%
0 trades right but fee-eaten
Inside noise floor
1
carry no directional information
Is this evidence of learning?
Hit rate 4/6 = 66.7%, binomial p = 0.688. NOT distinguishable from a coin flip - with 6 trades, nothing here supports 'the model learned from a mistake'. The only reliable lessons in this journal are the deterministic ones (cost drag, exposure).
Where the P&L actually came from
categories are assigned by size of move relative to the noise floor, not by profit alone
win
4
noise
1
signal error
1

Cost drag is deterministic - these trades had the direction right and lost to fees. Fewer, longer holds fix it without any model change. This is the only component that can be acted on without a significance test.

Bandit over the published strategy rules
Thompson sampling
Reinforcement learning sized to the data: it allocates between fixed, already-validated rules rather than learning a policy, because policy learning needs ~1,000,000 decisions and this book has made a few dozen.
sign
41%
timing_rolling
20%
quantile
16%
timing_ensemble
7%
timing_best
7%
risk_targeted
3%
timing_expanding
3%
timing_h20
3%

P(arm is best) from the posteriors. Coin-flip baseline is 13%. No arm is distinguishable: the leader (sign) is best with only probability 0.41 against a 0.12 coin-flip baseline. The argmax here is noise, not a decision.

6 closed trades: hit rate 67%, not distinguishable from chance
deterministic
Commentary explains realised history only — it is never shown a forward prediction, so it cannot give trading advice.

The book closed 6 round trips. Gross return summed to +15.70% and costs removed 0.57%.

Hit rate 4/6 = 66.7%, binomial p = 0.688. NOT distinguishable from a coin flip - with 6 trades, nothing here supports 'the model learned from a mistake'. The only reliable lessons in this journal are the deterministic ones (cost drag, exposure).

  • 6 trades is far too few to establish skill either way.
  • 1 trades fell inside the noise floor and carry no directional information.
  • Bandit separability: No arm is distinguishable: the leader (sign) is best with only probability 0.41 against a 0.12 coin-flip baseline. The argmax here is noise, not a decision.
Adaptation

Retraining without manufacturing an edge

Layers are sized by parameter count against the independent observations their cadence actually delivers - not by clock speed. A week carries ~0.25 independent observations at a 20-day horizon, so weekly gradient updates to a 69,589-parameter backbone would fit noise, not signal.

Drift alarms
12
over 934 OOS days
Recalibrations
33
250d trailing window
Champion
v1-frozen
trained through 2023-10-19
Trials counted
1
feeds the deflated Sharpe
Drift detection
Daily
0 params

monitors only, never retrains

Entry threshold
Daily
0 params

past-only rolling percentile

Platt calibration
Monthly
~40 params

trailing window, label-embargoed

Backbone refit
Quarterly
69,589 params

purged, embargoed, gated

Drift alarms on the live signal
ADWIN + Page-Hinkley over 934 out-of-sample days. Thresholds were calibrated empirically to zero false alarms on stationary input while still catching a 3-sigma shift.
DateDetectorLevel beforeAfterShift in signal level
2023-02-03adwin0.410.36
2023-05-22adwin0.14-0.75
2023-08-09adwin-0.76-0.56
2023-12-19adwin-1.03-3.43
2024-03-04adwin-3.27-1.43
2024-04-16adwin-3.04-0.58
2024-11-01adwin-0.44-0.63
2025-01-17adwin-0.44-0.56
2025-08-29adwin-0.430.27
2025-09-09page-hinkley-0.220.00
2025-12-04adwin0.05-1.11
2026-06-24adwin-0.751.27

Detectors observe only - they never trigger a parameter update on their own. An alarm is evidence to inspect, not permission to retrain: at ~250 observations a year, drift alarms and false alarms are hard to tell apart from a single firing.

n_trials counts every model ever trained in this lineage, including rejected challengers, and is passed to deflated_sharpe. Reporting the best of N retrains without deflating by N is how a retraining schedule manufactures an edge that was never there.

Documentation

Read the whole thing

Every file, every metric formula, every leakage rule and every number is documented in the repository — written so someone who has never seen the project can reproduce it end to end.

Method

How to read these results — honestly

What is real here
  • Every metric is computed on held-out test data from a freshly trained model.
  • India futures costs (9.6bps round-trip: STT, stamp, slippage, brokerage, exchange, GST) are charged on every trade.
  • Entry thresholds, signal z-scoring, and Platt calibration are all fit on validation data only — nothing is tuned on the test set.
  • Sharpe is reported with a bootstrap 95% CI; results are benchmarked against passively holding the Nifty.
  • Inputs are restricted to stationary features; returns are non-overlapping; 8-fold walk-forward retraining checks it is not a single-split fluke.
Honest limitations
  • A liquid index is near-efficient at daily frequency — skill is thin by design.
  • ~30 non-overlapping test trades: the CI is wide and any point estimate is fragile.
  • Single-asset backtest; no borrow costs or capacity modeling (slippage is included).
  • FinBERT sentiment fusion is wired in (config-gated) but off: NewsAPI can't backfill history, so no fabricated features enter the backtest.
  • Research artifact, not a deployed trading system.