LIVE·
fetching live quotes from Yahoo Finance…
--:--:--UTC
Learn/Bots/LinReg Channel
Statisticalid · linreg
λ

LinReg Channel

Linear regression line + std-deviation envelopes.

In plain English

Draws the best-fit straight line through recent prices, then puts dotted lines a few standard deviations above and below. Touching the top line is overextended — sell. Touching the bottom is washed-out — buy.

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

Draws the best-fit line through recent prices, then puts dotted lines a few standard deviations above and below. Touching the top is overextended; the bottom is washed out. The trend slope of the line itself is its own signal — positive slope = uptrend, negative = down.

The math
formula
y = β·x + α; bands = y ± k·σ(residuals)
parameters
periodPeriodrange 20 → 200default · 60
bandsStd bandsrange 1 → 3default · 2
Live demo

Real LinReg Channel 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/bots.ts·lines 532590
TypeScript · MIT-licensed
const linregBot: BotDef = {
  id: "linreg",
  name: "LinReg Channel",
  category: "stats",
  glyph: "λ",
  tagline: "Linear regression line + std-deviation envelopes.",
  formula: "y = β·x + α; bands = y ± k·σ(residuals)",
  params: [
    { key: "period", label: "Period", kind: "number", default: 60, min: 20, max: 200, step: 1 },
    { key: "bands", label: "Std bands", kind: "number", default: 2, min: 1, max: 3, step: 0.1 },
  ],
  run: (ctx, p): BotResult => {
    const period = Math.round(num(p, "period", 60));
    const k = num(p, "bands", 2);
    const px = closes(ctx.candles);
    const r = linregChannel(px, period, k);
    const signals: Signal[] = [];
    const sigArr: (1 | -1 | 0)[] = px.map(() => 0);
    for (let i = 1; i < px.length; i++) {
      const u = r.upper[i], l = r.lower[i];
      if (u == null || l == null) continue;
      if (px[i - 1] < (r.lower[i - 1] ?? -Infinity) && px[i] >= l) {
        signals.push({ i, kind: "buy", price: px[i] });
        sigArr[i] = 1;
      } else if (px[i - 1] > (r.upper[i - 1] ?? Infinity) && px[i] <= u) {
        signals.push({ i, kind: "sell", price: px[i] });
        sigArr[i] = -1;
      }
    }
    const bt = backtestLongOnly(px, sigArr);
    const last = px[px.length - 1];
    const lm = r.mid[r.mid.length - 1] ?? last;
    const lu = r.upper[r.upper.length - 1] ?? last;
    const ll = r.lower[r.lower.length - 1] ?? last;
    return {
      signals,
      metrics: [
        { key: "fair", label: "Trend line", value: fmtNum(lm), tone: "info" },
        { key: "upper", label: "Upper", value: fmtNum(lu), tone: "bear" },
        { key: "lower", label: "Lower", value: fmtNum(ll), tone: "bull" },
        { key: "trades", label: "Trades", value: String(bt.trades) },
      ],
      overlay: [
        { values: r.mid, color: "var(--fg-dim)", label: "LinReg" },
        { values: r.upper, color: "var(--bear-dim)", label: "Upper", dashed: true },
        { values: r.lower, color: "var(--bull-dim)", label: "Lower", dashed: true },
      ],
      summary: `Trend ${lm.toFixed(2)}, channel ${ll.toFixed(2)}–${lu.toFixed(2)}.`,
      beginner:
        "Draws the best-fit line through recent prices, then puts dotted lines a few standard deviations above and below. Touching the top is overextended; the bottom is washed out.",
      verdict: {
        side: last < ll ? "buy" : last > lu ? "sell" : "hold",
        text: last < ll ? "Below lower channel." : last > lu ? "Above upper channel." : "Inside the channel.",
        confidence: Math.min(1, Math.abs(last - lm) / (lu - lm || 1)),
      },
      equity: bt.equity,
    };
  },
};
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
  • ·Visualising trend strength. Slope and channel width together summarise the regime.
  • ·Setting stops. Lower channel for long stops, upper channel for short stops.
  • ·Combining with momentum. Trade reversions inside the channel; trend-follow when price pierces the band.
✗ Fails when
  • ·Sharp regime changes. The regression refits; the channel snaps to the new trend, briefly leaving recent price action outside the band.
  • ·Curved trends (parabolic moves). Linear fits a curved trajectory poorly.
  • ·Window too short. <30 bars gives unstable slope estimates.
How to read its verdict

BUY when price reclaims the lower band from below. SELL when it rejects the upper band. Confidence scales with distance from the mid-line. Slope of the regression line is published in the metrics — sustained positive slope means the trend bot interpretation is the safer one.

FAQ
How is this different from Bollinger?+
Bollinger's mid-line is an SMA — slope-agnostic. LinReg's mid-line is a least-squares fit — it explicitly captures slope. In trending markets they diverge sharply; in ranges they look similar.
What's the right window?+
Match the window to your trade horizon. 60 bars (~3 months daily) for swing trades. 252 bars for position trades. Shorter windows over-fit recent noise.