Selected work
In progress

RankAlpha

A cross-sectional equity ranker: it learns to order S&P 500 stocks, turns the ranking into a risk-managed, self-explaining portfolio, and proves it with a leakage-controlled, cost-aware backtest — honest about a modest, not-yet-significant edge.

  • Python
  • LightGBM · LambdaMART
  • Learning-to-rank
  • Walk-forward + embargo
  • SHAP
  • Streamlit
OOS Sharpe (model vs baseline)
1.14 vs 0.82
Mean Rank IC
0.050 (t=1.64)
Forward paper-track
+19% ann · 23 mo

What it is

RankAlpha ranks the S&P 500 every month, trades the top and bottom of that ranking as a market-neutral book, and turns the result into a risk-managed, self-explaining portfolio. The key word is ranking: it doesn't predict prices, it predicts relative order — which stocks will outperform their peers — because that's the only thing the strategy actually uses.

The trap I escaped first

The first version did the obvious thing: predict a single stock's direction from its own technical features. It posted a Sharpe of 0.72 — until I audited it. Most of that edge was look-ahead leakage: training labels that quietly peeked into the test window. After purging the leakage and charging realistic costs, accuracy fell to ~0.50 — a coin flip.

That dead end was the most useful result in the project. Single-stock direction is dominated by market-wide noise nobody can predict. The honest failure is what forced the real design.

The cross-sectional pivot

Instead of "will this stock go up?" the question became "will it outperform the other 499?". You rank all the stocks each month, go long the top decile and short the bottom, and size the two legs equally so the market's move cancels out. What's left is the part that's actually about the individual stock — relative strength — which is where systematic equity edge tends to live.

The model

The ranker is LightGBM with a learning-to-rank objective (LambdaMART) — it optimises the ordering of stocks directly, not each stock's return. Seven price-based factors (momentum, volatility, size, reversal, liquidity), normalised cross-sectionally within each day. It's validated with walk-forward retraining plus a 21-day embargo, so no training label can overlap the test window — the same leakage discipline the first version failed.

The bar it had to clear was a dumb baseline: plain 12-month momentum, no learning. It cleared it — modestly and honestly.

After-cost Sharpe — same out-of-sample window
Learned ranker
1.14
Momentum baseline
0.82

A modest, honestly-measured edge of +0.32 Sharpe over a plain-momentum baseline, after 10 bps/side costs. Not yet statistically significant (Rank IC t = 1.64) and untested through a momentum crash — the believability is the point, not the headline.

What it actually learned — and a mistake I caught

Feature importance said volatility dominated, and I first wrote that up as "the model discovered the low-volatility anomaly." That was wrong, and checking the SHAP direction caught it: the long picks have high volatility with positive contribution — it tilts toward higher-vol names paired with momentum, the opposite of low-vol.

The lesson is worth more than the result: importance tells you which feature matters, never which direction. You confirm the sign before you name the effect.

What the model leans on — LightGBM gain %
6-month volatility
59.7%
Size (log price)
14%
12–1 momentum
10.3%
6-month momentum
6.7%
3-month momentum
5.8%
1-month reversal
2.9%
Liquidity
0.4%

Volatility dominates by importance — but SHAP direction shows the long book tilts toward higher-volatility names paired with momentum, not the low-volatility anomaly. Importance tells you which feature matters, never which direction.

Does the ranking hold?

The real test isn't the headline Sharpe — it's whether stocks the model ranks high actually outperform. Sort by the model's score and look at what happened next month: the returns climb cleanly through the top deciles. The top decile is the strongest; the bottom (short) decile is the weak spot, so the edge leans long.

Mean next-month return by model-score decile
1.12%
0
0.67%
1
1.19%
2
0.97%
3
1.07%
4
1.30%
5
1.48%
6
1.55%
7
2.39%
8
4.04%
9

Sorted by the model's score (known in advance), then measured against realised forward returns. The clean rise from decile 5 → 9 is the real signal; the short leg (decile 0) still sits slightly high, so the edge is concentrated on the long side.

From signal to portfolio

The score becomes an actual portfolio: long-only top picks, weighted inverse to volatility, capped per name, and scaled to a target volatility with the remainder held as cash. Every holding carries the factor reasons that put it there, and a local LLM turns those reasons into plain English — fed only the model's real numbers, so it explains rather than invents. It's framed throughout as an educational simulation, never investment advice, and it quotes no return probabilities.

Forward paper-trading

To test it honestly, I froze the model and traded it forward — no refitting with hindsight — for 23 months. It returned +19% annualised (Sharpe 1.25 vs 1.21 for an equal-weight benchmark), with realised volatility landing right on the 14% target, so the risk control is well-calibrated.

Honest limitations