LIVE·
fetching live quotes from Yahoo Finance…
--:--:--UTC
Learn/Bots/Magnitude Regression
AI QuantsAIid · ai-magnitude
Δ%

Magnitude Regression

Predicts the size of the next 20-day move, not just the sign.

▶ Try it now↗ Browse all 27srcai quants/models/magnitude/train.pyapi/api/magnitude
In plain English

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.

No jargon. Just what this bot does.
The longer version

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.

The math
formula
HistGradientBoosting regressor · 5-fold ensemble
parameters
horizonHorizon (days)range 5 → 60default · 20
macroUse macro featureson / offdefault · true
Live demo

Real Magnitude Regression bot, running on real Yahoo data when the symbol is available. Drag the params — the bot re-runs instantly.

symbolloading…
loading AMZN bars…
Source code · public

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.

lib/quant/ai-bots.ts·lines 674749
TypeScript · MIT-licensed
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),
        },
      };
    },
  },
);
what each piece means
  • 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.
use this code yourself
  1. Copy the whole block above.
  2. On /quant, click + Import your bot in the bot library.
  3. Paste, hit save. It hot-loads into your workspace.
  4. Edit any param defaults or logic to your taste — it's now yours.
Specialty · when it shines, when it fails
✓ Shines when
  • ·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.
✗ Fails when
  • ·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.
How to read its verdict

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%.

Python service

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.

FastAPI·http://localhost:8000·/api/magnitudeCHECKING…
data flow
01
BotCell.run()
User clicks Run on this bot in /quant
02
callApi()
POST to localhost:8000/api/magnitude
03
load_surrogate()
ai quants/models/magnitude/train.py
04
predict()
Forward pass on the inputs you provided
05
BotResult
JSON returned, card flips green Source: Python NN
spin it upcd "ai quants" && uvicorn serve:app --reload --port 8000
FAQ
What's the difference between this and Direction Ensemble?+
Direction is a binary classifier — its output is P(up). This is a regression — its output is expected return. They use different loss functions, different model architectures, and different conviction bands. Stack both and use the agreement.
Why does it sometimes contradict Direction?+
Different objective functions. Classification is forced to commit to a side; regression can output a number near zero. When they disagree, the magnitude is usually small and HOLD is the right call.