Magnitude Regression
Predicts the size of the next 20-day move, not just the sign.
Direction tells you which way the price will move. This bot tells you how big the move will be. A 0.5% move is just noise. A 5% move is real. The size of the predicted move IS the conviction.
Direction's quieter sibling. Instead of asking 'up or down?', it predicts the size of the next 20-day move. A 0.5% expected return is meaningless noise; a 5% expected return is a real signal worth sizing up on. The sign tells you direction, the magnitude tells you conviction.
HistGradientBoosting regressor · 5-fold ensemble
Real Magnitude Regression bot, running on real Yahoo data when the symbol is available. Drag the params — the bot re-runs instantly.
This is the actual code the bot runs — not a re-explanation, not a simplified version. Whatever ships here is what executes when you press Run All in the workbench. Read it, copy it, fork it, build a better one.
const magnitudeReg: BotDef = aiBot<DirReq, MagRes>(
{
id: "ai-magnitude",
name: "Magnitude Regression",
category: "ai",
glyph: "Δ%",
tagline: "Predicts the size of the next 20-day move, not just the sign.",
formula: "HistGradientBoosting regressor · 5-fold ensemble",
endpoint: "/api/magnitude",
module: "ai quants/models/magnitude/train.py",
params: [
{ key: "horizon", label: "Horizon (days)", kind: "number", default: 20, min: 5, max: 60, step: 1 },
{ key: "macro", label: "Use macro features", kind: "boolean", default: true, hint: "VIX · DXY · 10Y · WTI" },
],
},
{
request: dirRequest,
build: (data, _ctx, p) => {
const expRet = data.expected_return;
const macro = p["macro"] !== false;
const band = data.magnitude_band;
const expAcc = data.expected_dir_accuracy;
return {
signals: [],
metrics: [
{ key: "exp", label: "Expected return", value: `${(expRet * 100).toFixed(2)}%`, tone: expRet > 0 ? "bull" : "bear" },
{ key: "band", label: "Magnitude", value: band.toUpperCase(), tone: band === "ultra" || band === "extreme" ? "bull" : "info" },
{ key: "dir", label: "Direction", value: data.direction.toUpperCase(), tone: data.direction === "up" ? "bull" : "bear" },
{ key: "std", label: "Ensemble σ", value: fmtNum(data.ensemble_std, 3) },
{ key: "exp_acc", label: "Dir accuracy", value: `${(expAcc * 100).toFixed(0)}%`, tone: "info" },
{ key: "macro", label: "Macro features", value: macro ? "ON" : "OFF", tone: macro ? "bull" : "neutral" },
],
summary: `Real model predicts ${(expRet * 100).toFixed(2)}% over ${data.horizon_days}d (${band}). ${macro ? "Macro features on" : "Pure price"}.`,
beginner: "Beyond direction — predicts how BIG the move will be. Sizing depends on this.",
verdict: {
side: expRet > 0.02 ? "buy" : expRet < -0.02 ? "sell" : "hold",
text: `Expected ${(expRet * 100).toFixed(2)}% over ${data.horizon_days}d (${band}).`,
confidence: Math.min(1, Math.abs(expRet) * 12),
},
};
},
mock: (ctx, p) => {
const horizon = num(p, "horizon", 20);
const macro = p["macro"] !== false;
const px = closes(ctx.candles);
const trend = trendStrength(px);
const rv = realisedVol(px);
const seed = hashStr(ctx.symbol + "magnitude" + horizon);
const rand = seedRand(seed);
const expRet = trend * 0.6 + (rand() - 0.5) * rv * 0.4;
const std = rv * 0.18;
const dir = expRet > 0 ? "up" : "down";
const absMag = Math.abs(expRet);
const band = absMag > 0.07 ? "ultra" : absMag > 0.045 ? "extreme" : absMag > 0.027 ? "high" : absMag > 0.015 ? "medium" : "low";
const expAcc = band === "ultra" ? 0.66 : band === "extreme" ? 0.64 : band === "high" ? 0.61 : band === "medium" ? 0.60 : 0.555;
return {
signals: [],
metrics: [
{ key: "exp", label: "Expected return", value: `${(expRet * 100).toFixed(2)}%`, tone: expRet > 0 ? "bull" : "bear" },
{ key: "band", label: "Magnitude", value: band.toUpperCase(), tone: band === "ultra" || band === "extreme" ? "bull" : "info" },
{ key: "dir", label: "Direction", value: dir.toUpperCase(), tone: dir === "up" ? "bull" : "bear" },
{ key: "std", label: "Ensemble σ", value: fmtNum(std, 3) },
{ key: "exp_acc", label: "Dir accuracy", value: `${(expAcc * 100).toFixed(0)}%`, tone: "info" },
{ key: "macro", label: "Macro features", value: macro ? "ON" : "OFF", tone: macro ? "bull" : "neutral" },
],
summary: `Predicts ${(expRet * 100).toFixed(2)}% over ${horizon}d (${band}).`,
beginner: "Beyond direction — predicts how BIG the move will be.",
verdict: {
side: expRet > 0.02 ? "buy" : expRet < -0.02 ? "sell" : "hold",
text: `Expected ${(expRet * 100).toFixed(2)}% over ${horizon}d (${band}).`,
confidence: Math.min(1, absMag * 12),
},
};
},
},
);id— unique key the workbench uses to find the bot.params— the sliders + inputs you see on the cell.run(ctx, p)— the function that gets called with candles + your params and returns the verdict.verdict— the BUY / SELL / HOLD pill at the top of the cell.metrics— the small stat boxes shown in the cell body.
- Copy the whole block above.
- On /quant, click + Import your bot in the bot library.
- Paste, hit save. It hot-loads into your workspace.
- Edit any param defaults or logic to your taste — it's now yours.
- ·Position sizing decisions. Direction alone tells you which way to bet; magnitude tells you how much.
- ·Filtering Direction Ensemble's signals — when both bots agree on direction AND magnitude clears 3%, it's a stronger setup than either alone.
- ·Names with persistent edges (sector ETFs, large-cap growth). The regression learns dampened magnitudes that match real return distributions.
- ·When 'macro features' is on. VIX and 10Y rate alignment lifts accuracy by ~2pts.
- ·Turning points. Regression smooths over inflections; magnitude underestimates reversals.
- ·High-vol assets. The MSE objective penalises large errors disproportionately, so the model under-bets on tail moves.
- ·Out-of-distribution names. Like Direction, needs ≥250 bars and US-equity-shaped data.
BUY when expected return > +2%, SELL when < -2%, HOLD in between. Magnitude band classifies |expRet| into LOW (<1.5%), MEDIUM (1.5-2.7%), HIGH (2.7-4.5%), EXTREME (4.5-7%), ULTRA (>7%). Confidence scales linearly with |expRet| up to a cap of 1.0 at 8%.
This bot tries to call the FastAPI service first. When it's up, you get real model output. When it's down, the bot transparently falls back to a deterministic TS surrogate.
cd "ai quants" && uvicorn serve:app --reload --port 8000What's the difference between this and Direction Ensemble?+
Why does it sometimes contradict Direction?+
Will the stock be up or down 20 days from now? GBM ensemble vote.
All AI quants vote. Tier emerges from agreement, not opinion.
Honest confidence interval — not just a point prediction.
Attention over a full year of OHLCV. Spots seasonality + regime.