Skip to Content

Alpha-Investing — Sequential FDR Control

What goes wrong with a naive approach

The runtime maintains dozens of simultaneous monitors: conformal frame-guard alerts, allocation-budget CUSUMs, flake detectors, BOCPD regime flips, fairness interventions. Each one tests at some significance level α\alpha. If they all test at α=0.05\alpha = 0.05 independently and you have 20 monitors, the expected number of false positives per frame is 11. Multiply by 60 fps and you are drowning.

The usual answers are all bad:

  • Bonferroni correction — divide α\alpha by the number of tests. Works for fixed-nn testing but is catastrophically conservative when tests run continuously and new ones come online at runtime.
  • Manual prioritisation — pick three “important” alerts, mute the rest. Fragile: the muted alerts are exactly where the next outage will come from.
  • Unthrottled — accept the noise. Users stop trusting the alert system, which is worse than not having one.

The right abstraction is sequential false-discovery-rate control: a global budget that is spent when you reject (fire an alert) and recovered when rejections are themselves rejected in their own local tests.

Mental model

Think of it as a gambling game:

  • You start with wealth W0W_0 (an α\alpha budget).
  • To run a test at level αi\alpha_i, you spend αi\alpha_i from your wealth.
  • If the test rejects (alert fires), you earn back a reward ψαi\psi \alpha_i — you were “right” to spend on it.
  • If the test accepts, the αi\alpha_i is lost.

As long as the wealth stays non-negative and you stay within the rules, the marginal false discovery rate (mFDR) is bounded:

mFDR=E[V]E[R]W0\operatorname{mFDR} = \frac{E[V]}{E[R]} \le W_0

where VV is the number of false rejections and RR the total rejections.

The beauty of alpha-investing is that it respects the alert authors. Each subsystem still owns its test and picks its own αi\alpha_i. The wealth process enforces a global budget without imposing global coordination.

The math

Wealth update

For test ii with level αi\alpha_i:

Wi=Wi1αi+RiψαiW_i = W_{i-1} - \alpha_i + R_i \cdot \psi \alpha_i

where Ri{0,1}R_i \in \{0, 1\} is the rejection indicator and ψ(0,1)\psi \in (0, 1) is the reward fraction.

Admissibility

A test at level αi\alpha_i is admissible iff:

αiϕWi1\alpha_i \le \phi \cdot W_{i-1}

where ϕ(0,1)\phi \in (0, 1) is the investment fraction. This is the “don’t bet the farm” rule — you can’t spend your entire wealth on one test.

mFDR guarantee

If ψ+ϕ1\psi + \phi \le 1 and W0αW_0 \le \alpha, then:

mFDRα\operatorname{mFDR} \le \alpha

for any stopping rule, any test order, any number of tests — the guarantee is sequential.

Defaults

ParameterDefaultMeaning
W0W_00.5Starting α\alpha budget.
ψ\psi (reward fraction)0.5Earnings per rejection.
ϕ\phi (investment fraction)0.1Max fraction of current wealth per test.

With these, ψ+ϕ=0.61\psi + \phi = 0.6 \le 1 and W0=0.5W_0 = 0.5 so the guarantee holds with α=0.5\alpha = 0.5.

Why you need it when running dozens of monitors

E[false alerts per frame] = 20 × 0.05 = 1.0 E[false alerts per second at 60 fps] = 60 → alert log is 99% noise. → users ignore alerts, outages slip through.

Rust interface

crates/ftui-runtime/src/alpha_investing.rs
use ftui_runtime::alpha_investing::{AlphaInvestor, InvestmentConfig}; let mut investor = AlphaInvestor::new(InvestmentConfig { initial_wealth: 0.5, reward_fraction: 0.5, investment_fraction: 0.1, }); // Subsystem proposes a test at level 0.03 if let Some(ticket) = investor.request(0.03) { let rejected = run_local_test(); investor.resolve(ticket, rejected); } else { // Not enough wealth — defer or pick a cheaper test. }

Every subsystem that wants to raise an alert first asks the investor for a ticket. If the wealth can’t support the αi\alpha_i, the test is suppressed for this frame — the budget speaks.

How to debug

Each admissible test emits an alpha_invest line:

{"schema":"alpha_invest","test_id":"budget_alert", "alpha_i":0.03,"wealth_before":0.48, "rejected":true,"wealth_after":0.465, "reward":0.015}
FTUI_EVIDENCE_SINK=/tmp/ftui.jsonl cargo run -p ftui-demo-showcase # Which monitors are bleeding wealth? jq -c 'select(.schema=="alpha_invest" and .rejected==false) | .test_id' /tmp/ftui.jsonl | sort | uniq -c | sort -rn

If wealth trends to zero and stays there, the system is chronically “unhealthy” — the monitors are firing real alerts. Investigate the subsystem with the highest rejection rate first.

Pitfalls

Wealth can pin at zero. Once wealth is depleted, new tests are rejected without running. That is the correct FDR behaviour, but it means you lose visibility during outages when you most want it. Pair with a watchdog that emits a single “wealth exhausted” event at the sink level so you notice.

Rewards must correspond to real signal. If a subsystem games the wealth by rejecting under trivial conditions, its rewards accumulate and subsequent tests become effectively unbounded. Code review the test implementations; the mFDR guarantee assumes they are honest.

Cross-references

Where next