Tasks/Finance/Quantitative Investing

Build a cross-sectional return-prediction model

Predict cross-sectional returns on an anonymized factor panel

qlib_alpha_factor_icir Finance Quantitative Investing
Background

A quantitative fund scores every stock in its universe each day and buys the ones it ranks highest. The score itself is the product: it has to rank tomorrow's winners above tomorrow's losers, and it has to keep doing so month after month, not just on average. The measure used here is ICIR, the day-to-day correlation between the score and the actual return, divided by how much that correlation wobbles. A model that is right on average but wildly inconsistent scores badly.

The panel is anonymised, so there are no company names, no sectors and no news to reason from, only numbered features over time. Everything the model knows it has to find in the history it is given, and it is graded on a later stretch of dates it has never seen. That last part is the whole difficulty: market relationships decay, so a score tuned until it looks excellent on the validation window frequently does not survive the move forward in time.

instruction.mdthis is what the agent is given

You are given a real-market-derived daily cross-sectional factor panel with anonymized identifiers (opaque datetime/instrument IDs, 360 factor columns and a forward-return label). Train a method that outputs a continuous score per instrument-day; it is scored by the cross-sectional rank agreement between your scores and forward returns on a hidden, later period. Real cross-sectional alpha is weak and noisy — what matters is generalization, not fitting the data you can see.

Hard Constraints

  • Submit an algorithm (predict), not a precomputed table of scores — the grader re-runs your code on hidden data.
  • Keep the exact signature: predict(train_df, valid_df, test_features_df) -> DataFrame[datetime, instrument, score].
  • Output must cover every test (datetime, instrument) row, with finite scores, no missing rows and no duplicates — otherwise the submission scores 0.
  • Do not hardcode or look up answers: datetime/instrument are opaque IDs (not real dates/tickers), the test period is sealed, and there is no network at any time.

What You Have

  • Data (/app/data/): train_panel.parquet (earlier period), valid_panel.parquet (a later, non-overlapping period), and feature_catalog.csv (factor families: f001..f158 = family A, f159..f360 = family B). The 360 columns f001..f360 are 6 price/volume fields, each given as a 60-day history — six contiguous 60-column blocks, one field per block — so they form a 6-field x 60-day temporal panel. datetime is an opaque, chronologically-ordered day index (d000001, …); instrument is an opaque ID (s00001, …); the raw factors contain missing values. A later, non-overlapping test period is sealed in the grader — you never see it.
  • The editable baseline /app/methods/main/this directory is what gets graded. It is a deliberately weak template (a family-A subset + a vanilla MLP). Improve it in place, or rewrite the algorithm entirely. No stronger reference is shipped — going weak→strong is the task.
  • Your self-check surface (free, unlimited): python /app/selfcheck.py trains your current methods/main/solver.py on train and reports ICIR on valid. This is your only feedback loop — iterate against it as much as you want. It is a proxy: the sealed test period differs, so a high valid ICIR is necessary but not sufficient.

What You Submit

Edit /app/methods/main/solver.py to expose this exact signature:

def predict(train_df, valid_df, test_features_df) -> pd.DataFrame  # columns: datetime, instrument, score

You may add helper modules next to solver.py. The runtime provides numpy / pandas / scikit-learn / torch / pyyaml. There is no submit step and no per-attempt feedback — work and self-check for as long as your run window allows, then leave your best solver.py in place; it is graded once at the end on the hidden test period.

How It Is Judged

After your run, the grader copies your methods/main/ into a clean box, hands predict the hidden test-period features (no label), and scores the result. The grader re-runs your predict on a hidden panel of similar size, in a container with a 30-minute limit and more CPU than your own box.

  • IC = the same-day cross-sectional Pearson correlation between your score and the forward-return label;
  • ICIR = the mean daily IC over the test period divided by its standard deviation.

The metric is ICIR (higher is better).

Metric

ICIR on the sealed later period · higher is better

Mean daily cross-sectional IC between the submitted score and the forward return, over the sd of that IC series.

anchorvisible setheld-outreward
Bshipped baseline (subset + small MLP)0.1230.1150.00
Sreference solver (2-layer GRU)0.4430.3300.60
normalisation
x <= 00
0 < x <= 10.6 * x^2
x > 11 - 0.4 / x

m = this run's held-out metric  ·  B = shipped baseline (subset + small MLP)  ·  S = reference solver (2-layer GRU)

x = (m - B) / (S - B); B 0.115, S 0.330, CURVE_P 2 from anchors.json. One hidden block, mapped once. Any exception scores 0.

Rollouts

548 minwall clock
$155.80spend
259.4Mtokens
40versions, 38 kept
0.15 0.30 0.45 0.60 0.75 $0 $40 $80 $120 $160 cumulative spend on the run self-check ICIR, higher is better reference solver · visible · 0.443 v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v20 v21 v22 v23 v24 v25 v26 v27 v28 v29 v30 v31 v32 v33 v34 v35 v36 v37 v38 v39 v40
keptrevertedno scoreturning point
  1. v1Unchanged shipped family-A MLP baseline, kept as the reference point0.1231320 min · $0.15
  2. v2151 stationary OHLCV technical factors, within-day clipped z-scaling, ridge0.2682888 min · $1.18
  3. v3Complementary rank/rank ridge blended 40% with the magnitude-aware z/z ridge0.27185911 min · $1.75
  4. v4154 Alpha158-style path features added; back to a single z/z ridge over 3050.38740920 min · $3.37
  5. v525% rank/rank ridge component restored on the 305-feature representation0.38927624 min · $4.28
  6. v620% shallow regularised histogram boosting added, ridge weights cut to 80%0.40028331 min · $5.72
  7. v7Booster retuned to 200 rounds of 31 leaves; tree weight raised to 40%0.40658840 min · $7.69
  8. v8Test-day scores neutralised on 10 PCA modes of the historical return covarianceAnonymous risk loadings estimated from labelled history; removing that exposure raises ICIR on every window while mean IC falls.0.56732564 min · $13.71
  9. v9Loading basis widened to 10 covariance plus 20 correlation PCA modes0.64151269 min · $15.34
  10. v1010% two-seed 16-epoch residual MLP added, then the same joint neutraliser0.64811778 min · $18.01
  11. v11Ensemble weights regridded after risk projection: 15/40/25/20 over four parts0.67303492 min · $22.36
  12. v12Two-horizon neutraliser: 65% recent-750-day, 35% 1000-day half-life weighted0.675515103 min · $26.33
  13. v13Calibration blends the day-z risk residual 50/50 with its Gaussian-rank map0.677813108 min · $28.48
  14. v14Training labels residualised against the same two-horizon risk basis0.698521117 min · $32.27
  15. v1525% peer projection on 30 K-means clusters of correlation eigen-embeddings0.71113137 min · $38.61
  16. v16Post-risk component blend retuned on the three earlier walks, integer weights0.711173150 min · $39.98
  17. v17Per-estimator target caps: 1 sd for the z ridge, 10 sd for the raw tree0.714907166 min · $42.35
  18. v1850/50 magnitude/rank calibration replaced by pure within-day Gaussian ranks0.714184188 min · $46.49
  19. v19Ensemble re-optimised for the pure ordering objective, whole-percent weights0.71328195 min · $48.15
  20. v20Raw booster replaced by the average of two 50%-feature-per-split seeds0.714786206 min · $50.94
  21. v21Causal online z-ridge, refit every 60 days on test targets read back from f059Targets two days old are recoverable from later feature columns, so the linear part keeps fitting inside the test period.0.722074221 min · $54.85
  22. v22Newly revealed causal test rows given double weight in the online ridge0.722206231 min · $57.72
  23. v23Same 60-day/2x online updates extended to both raw and residual rank ridges0.719449248 min · $63.07
  24. v24Causal-component blend statically retuned on the three earlier walks0.72056257 min · $66.29
  25. v25Final Gaussian-rank scores clipped at +/-2 before day standardisation0.722841269 min · $71.45
  26. v2660 stability-selected moment/volume descriptors exposed to the causal z ridge0.726341302 min · $84.52
  27. v27The same 60 selected descriptors exposed to both causal rank ridges0.732491312 min · $89.27
  28. v28Raw booster split 50/50 between the 305-column and 365-column surfaces0.735153324 min · $94.58
  29. v29Two-seed MLP retrained on the 365-column selected-augmented surface0.734575341 min · $99.28
  30. v30Weights retuned on the earlier walks; the redundant raw-rank slot zeroed0.737166359 min · $101.76
  31. v31Both causal ridge refresh blocks shortened from 60 days to 200.739375392 min · $107.25
  32. v322.5%/7.5% rolling instrument-rank premia over the last 50/250 revealed daysAdds a per-name persistence term the pooled learners cannot express, shrunk by count and frozen on the same causal schedule.0.738957406 min · $110.19
  33. v33Instrument-rank effect scaled by clip(1 - 2 x five-day breadth, 0, 3)0.739743422 min · $113.88
  34. v34Rolling instrument premia retargeted on static-risk-residual ranks0.728922434 min · $117.54
  35. v3550/250-day instrument effects refreshed every 2 days instead of every 200.743092459 min · $126.39
  36. v36Per-instrument EMA, 94% current day-standardised score plus 6% prior0.738835479 min · $134.18
  37. v37Ungated 2.5%/7.5% instrument-rank effects over the last 5/10 revealed days0.755668490 min · $138.96
  38. v38Five core weights retuned after the identity signals: 17/31/16/13/230.759026502 min · $145.23
  39. v3933% of the z-ridge slot given to an expert fit on the last 20 revealed days0.760621539 min · $153.67
  40. v40Recent-only ridge falls back to the online z fit during its cold-start block0.760925548 min · $155.59

40 snapshots in 548 minutes at $155.80, only v34 and v36 reverted; 54 further probes were screened between snapshots and never shipped.

On the hidden set

held-out metricreward
shipped baseline (subset + small MLP)0.1150.00
reference solver (2-layer GRU)0.3300.60
this run0.4596130.7504
560 minwall clock
$19.94spend
27.4Mtokens
14versions, 12 kept
0.2 0.3 0.4 0.5 0.6 $0 $4 $8 $12 $16 cumulative spend on the run fold-B ICIR on the agent's 3-fold replay, higher is better reference solver · visible · 0.443 v0 v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13
keptrevertedno scoreturning point
  1. v0Shipped baseline: family-A subset, small sklearn MLP, global standardisation0.19940 min · $2.63
  2. v1Full rewrite as a torch GRU(64x2) over the 6x60 log-ratio panelparked, 90 s/epoch40 min · $2.63
  3. v2Panel rebuilt into 139 Alpha158-style descriptors; 4-seed HistGradientBoostingReads the 360 columns as six price/volume fields over 60 days and rebuilds handcrafted descriptors, rank-normalised per day.0.4640 min · $2.58
  4. v3Coarse raw lags added; 4-seed MLP averaged over epochs 8/15/25/40, 0.8/0.2 with HGBAveraging one net's epochs 8 to 40 makes training length a free ensemble axis beside seeds, and ICIR pays twice for less variance.0.524127 min · $5.52
  5. v4Adaptive ensemble size, 4 to 16 MLP seeds, under a 20-minute self-imposed budget0.53212 min · $8.32
  6. v5Cross-sectional feature transform changed from rank to gaussian quantile; 24 seeds0.563312 min · $11.40
  7. v6Dual input normalisation: half the ensemble on ranks, half gaussian; HGB dropped0.553312 min · $11.63
  8. v7Output neutralised: remove 0.75x its projection on vol, liquidity and trendMean IC falls 0.074 to 0.065 and ICIR rises on every fold: the three removed factors are what make the daily IC swing.0.572356 min · $13.43
  9. v8Train on the neutralised label too, so the nets model only the orthogonal part0.572379 min · $14.37
  10. v9Weight training rows by 1/(stocks that day) so every day contributes equally0.573414 min · $15.29
  11. v10Ensemble spans four variants: two input normalisations x two feature sets0.585438 min · $16.09
  12. v11A third input normalisation (cs-z clipped) added as a sixth variant0.588478 min · $16.84
  13. v12Shipped configuration: 4 variants, up to 48 seeds under the wall-clock guard0.593516 min · $17.67
  14. v13Documentation and cleanup only; model identical to v120.593556 min · $18.93

14 snapshots, 560 minutes, $19.94. The first 40 minutes and $2.63 went into the GRU rewrite (v1), parked as too slow for the grader and never scored.

On the hidden set

held-out metricreward
shipped baseline (subset + small MLP)0.1150.00
reference solver (2-layer GRU)0.3300.60
this run0.4066810.7052
95 minwall clock
$22.22spend
33.5Mtokens
10versions, 10 kept
0.1 0.2 0.3 0.4 0.5 0.6 $0 $5 $10 $15 $20 cumulative spend on the run valid-panel ICIR from selfcheck.py, higher is better reference solver · visible · 0.443 v1 v2 v3 v4 v5 v6 v7 v8 v9 v10
keptrevertedno scoreturning point
  1. v1Unchanged shipped family-A MLP baseline, kept as the reference point0.1231322 min · $0.25
  2. v2300-feature daily cross-sectionally standardised ridge, alpha 10000 scaled by rowsDrops the template's feature subset and net for a ridge over the whole panel, standardised per day: cross-sectional, not pooled.0.2160398 min · $0.97
  3. v3Volume block removed, 240 price relatives log-transformed, labels clipped at 3 sd0.2492616 min · $2.02
  4. v4Only the most recent 40 of 60 lags per price block, penalty scaled by dimension0.26224321 min · $3.10
  5. v54-seed sub-bagged histogram boosting on 108 path/return/range/OHLC summariesBoosted trees over engineered path summaries replace the linear fit; the pseudo-forward folds moved with valid, so it was kept.0.37923337 min · $6.14
  6. v6Second market-regime-conditioned HGB blended 60/40 with the unconditional one0.3888857 min · $10.76
  7. v7HGB capacity raised to 127 leaves, min leaf 1000; contextual blend cut to 30%0.42411162 min · $12.24
  8. v8Contextual state simplified from 48 mixed features to 18 market-direction ones0.445173 min · $15.11
  9. v920 return-correlation clusters learned; scores demeaned within cluster and dayMean IC drops 0.063 to 0.054 and ICIR jumps anyway: the cluster mean carried the common factor behind the daily IC swing.0.5677686 min · $18.77
  10. v10Hard cluster demeaning replaced by projection off 12 PCA correlation loadings0.61514291 min · $20.28

10 snapshots in 95 minutes at $22.22, none reverted: each beat its parent on valid ICIR and on quarters and pseudo-forward splits.

On the hidden set

held-out metricreward
shipped baseline (subset + small MLP)0.1150.00
reference solver (2-layer GRU)0.3300.60
this run0.3098170.4926
88 minwall clock
$0.80spend
3.0Mtokens
3versions, 3 kept
0.150 0.225 0.300 0.375 0.450 $0 $0.2 $0.3 $0.5 $0.6 cumulative spend on the run self-check ICIR, higher is better reference solver · visible · 0.443 v1 v2 v3
keptrevertedno scoreturning point
  1. v1Unchanged shipped family-A MLP baseline, kept as the reference point0.1231321 min · $0.06
  2. v2Ridge over all 360 features, alpha 1000, replacing the family-A subset and MLPSwaps the template hand-picked subset and net for one strong linear fit over the whole 360-column panel.0.1742252 min · $0.11
  3. v38-seed MLP ensemble, 64x32 dropout 0.2, 18 epochs, last-5-epoch averagingSeed and last-epoch averaging is variance reduction, which is what ICIR pays for; blending ridge back in only lowered it.0.27154588 min · $0.72

3 snapshots in 88 minutes at $0.80: 85 of those are unsnapshotted tuning between v2 and v3, whose solver refits on train+valid under grading mode.

On the hidden set

held-out metricreward
shipped baseline (subset + small MLP)0.1150.00
reference solver (2-layer GRU)0.3300.60
this run0.2701810.3126