Inspiration

Every trading strategy eventually stops working. That's not pessimism, it's the literature — McLean & Pontiff (2016) found that published trading anomalies lose an average of ~58% of their return after publication, as the market learns and crowds in. The problem isn't that strategies decay. It's that firms usually only find out after market close, running rolling-IC or half-life charts in a notebook, hours or days after the capital was already at risk.

We didn't want to take that on faith, so before writing a line of code we asked someone who'd actually know: Peter messaged a bank contact, laid out three candidate directions (backtest slippage, real-time kill-switches, or alpha decay monitoring), and asked which was worth building in three days. His answer was genuinely mixed — he leaned toward the kill-switch direction as the "safe" pick, but described alpha decay as the one that "sits in the middle, seems fine" and worth doing properly if we had the appetite. Santiago's Bloomberg contact, independently, pointed at the same problem. So did a YouTube deep-dive on how pairs trading died at Morgan Stanley in the 80s once the traders who invented it left to start competing funds. Three partially independent signals, converging on the same gap: nobody watches this live. That's what we built.

What it does

Mayday Alpha is a real-time monitor that watches trading strategies while they're live and throttles their capital automatically the moment they start failing — instead of waiting for someone to notice at the next EOD review.

For every incoming trade, it recomputes three things on a rolling window:

  1. Rolling Information Coefficient — the Spearman rank correlation between what the strategy predicted and what actually happened.
  2. Statistical significance — a t-statistic on that correlation, so we're not reacting to noise:

$$t = \frac{IC \cdot \sqrt{N-2}}{\sqrt{1 - IC^2}}$$

If $|t| < 1.96$, the strategy's "edge" is no longer statistically distinguishable from random.

  1. Feature drift — a Kolmogorov-Smirnov test comparing the live distribution of market volatility against a frozen baseline, to catch regime shifts the strategy wasn't built for.

Combine those, and the system emits a live capital allocation scale factor — 1.0 (OPTIMAL), 0.5 (WARNING), or 0.0 (PAUSED) — per strategy, streamed to a dashboard in real time. To prove it actually works, there's a "break it" button that injects a simulated regime shift into a live strategy on demand: press it, and watch the strategy cascade from OPTIMAL to WARNING to PAUSED on screen, capital pulled back automatically, with zero humans in the loop.

How we built it

Three people, three lanes, one shared contract (a TradeSignal schema everyone codes against):

  • Santiago built the strategy engine and live market simulator — two synthetic strategies (mean reversion, momentum), async and ticking every 500ms, plus the regime-shift injector behind the demo button. Before going live, he validated the underlying strategy logic with real backtests against real historical market data (Yahoo Finance), with realistic transaction costs and standard risk metrics, so the strategies weren't toy examples.
  • Phuc built the decay engine — the rolling IC, t-stat, and KS-drift math, plus a hysteresis state machine so the dashboard doesn't flicker between states on every noisy tick.
  • Peter built the FastAPI + WebSocket backend tying it together, and the React + Tailwind + Recharts operator dashboard — live strategy cards, a dual-axis IC/t-stat chart with the 1.96 significance line, a feature-drift panel, and a running audit log of every capital-scaling decision, generated from real state transitions, not scripted text.

Challenges we ran into

A git identity mixup, twice. Early on, decay logic got written and pushed before the team's file-ownership split was fully nailed down, so a placeholder version of decay_engine.py ended up on main under the wrong name. Same thing nearly happened with the strategy simulator. Both got resolved with a plain git pull + manual conflict resolution, keeping the actual owner's version — no history rewritten, no work lost.

Our own monitor was crying wolf. Once the real pipeline was wired end-to-end, we noticed both strategies sat in WARNING/PAUSED almost permanently — even completely untouched, chaos button never pressed. We measured it properly: the KS drift test was flagging "drift" on 91-100% of ticks at rest. The cause was subtle — the volatility feature being tested is a smooth, autocorrelated rolling statistic, not independent noise, so testing it against one frozen baseline snapshot drifts past significance from ordinary wobble alone, not just real regime shifts. We fixed it by tightening the significance threshold, but not by guessing: we measured p-values from real triggered-decay runs vs. real baseline runs across multiple thresholds, picked the one with the cleanest separation, and re-ran the existing test suite to make sure it didn't break anything — which it did on the first attempt, because a small-sample edge case in one test hit a hard mathematical floor in how precise a KS-test p-value can even be. That taught us the fix twice.

What we learned

That the real bottleneck isn't building a better trading signal, it's that nobody's watching the one you already have while it's live. Every source we found — the literature, the video, the bank contact's own description of how PMs actually work — pointed at the same gap: decay research exists, decay monitoring while it's happening, live, doesn't. We also relearned a very old lesson the hard way: a statistical test is only as good as the assumptions behind it, and the only way to know it's actually working is to measure it against real data, not trust the textbook default.

What's next

  • Test the engine against a strategy we design ourselves, not just the two example strategies given in the brief.
  • Extend the drift test from one feature (volatility) to multiple features jointly.
  • Replace the discrete 1.0 / 0.5 / 0.0 allocation steps with something continuous, scaled to how severely a strategy is decaying rather than a fixed step function.

Built With

Share this project:

Updates