LIVE·
fetching live quotes from Yahoo Finance…
--:--:--UTC
Learn/Bots/RSI Reversion
Trend & Momentumid · rsi-rev

RSI Reversion

Catch oversold bounces, fade overbought spikes.

In plain English

RSI is a 0-to-100 thermometer. Below 30 means everyone panic-sold and the price will probably bounce. Above 70 means everyone's cheering and a pullback is overdue. The bot fires BUY when RSI exits the oversold zone and SELL when it exits overbought.

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

RSI is a 0-to-100 thermometer that asks: how stretched is recent buying versus selling? Below 30 = panic-selling overshot. Above 70 = euphoria overshot. The bot fires BUY when RSI exits the oversold zone (recovery starts) and SELL when it exits overbought (cooling).

The math
formula
RSI = 100 − 100 / (1 + avgGain/avgLoss)
parameters
periodPeriodrange 5 → 30default · 14
buyBelowBuy belowrange 10 → 45default · 30
sellAboveSell aboverange 55 → 90default · 70
Live demo

Real RSI Reversion 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 96164
TypeScript · MIT-licensed
const rsiBot: BotDef = {
  id: "rsi-rev",
  name: "RSI Reversion",
  category: "trend",
  glyph: "≈",
  tagline: "Catch oversold bounces, fade overbought spikes.",
  formula: "RSI = 100 − 100 / (1 + avgGain/avgLoss)",
  params: [
    { key: "period", label: "Period", kind: "number", default: 14, min: 5, max: 30, step: 1 },
    { key: "buyBelow", label: "Buy below", kind: "number", default: 30, min: 10, max: 45, step: 1 },
    { key: "sellAbove", label: "Sell above", kind: "number", default: 70, min: 55, max: 90, step: 1 },
  ],
  run: (ctx, p): BotResult => {
    const period = Math.round(num(p, "period", 14));
    const lo = num(p, "buyBelow", 30);
    const hi = num(p, "sellAbove", 70);
    const px = closes(ctx.candles);
    const r = rsi(px, period);
    const signals: Signal[] = [];
    const sigArr: (1 | -1 | 0)[] = px.map(() => 0);
    for (let i = 1; i < r.length; i++) {
      const a = r[i - 1], b = r[i];
      if (a == null || b == null) continue;
      if (a < lo && b >= lo) {
        signals.push({ i, kind: "buy", price: px[i], label: "exit oversold" });
        sigArr[i] = 1;
      } else if (a > hi && b <= hi) {
        signals.push({ i, kind: "sell", price: px[i], label: "exit overbought" });
        sigArr[i] = -1;
      }
    }
    const bt = backtestLongOnly(px, sigArr);
    const last = r[r.length - 1] ?? 50;
    const state = last < lo ? "oversold" : last > hi ? "overbought" : "neutral";
    return {
      signals,
      metrics: [
        { key: "rsi", label: "RSI", value: fmtNum(last, 1), tone: last < lo ? "bull" : last > hi ? "bear" : "neutral" },
        { key: "state", label: "State", value: state.toUpperCase(), tone: state === "oversold" ? "bull" : state === "overbought" ? "bear" : "neutral" },
        { key: "trades", label: "Trades", value: String(bt.trades), tone: "neutral" },
        { key: "ret", label: "Return", value: fmtPct(bt.equity[bt.equity.length - 1] - 1), tone: "info" },
      ],
      pane: {
        kind: "line",
        series: [{ values: r, color: "var(--cyan)", label: "RSI" }],
        refLines: [
          { value: hi, color: "var(--bear)", label: `${hi} OB` },
          { value: lo, color: "var(--bull)", label: `${lo} OS` },
          { value: 50, color: "var(--fg-faint)" },
        ],
        height: 80,
      },
      summary: `RSI ${last.toFixed(1)} (${state}). ${signals.length} reversion triggers in window.`,
      beginner:
        "RSI is a 0-to-100 thermometer. Below 30 means everyone panicked and the price is probably going to bounce. Above 70 means everyone's cheering and a pullback is overdue.",
      verdict: {
        side: state === "oversold" ? "buy" : state === "overbought" ? "sell" : "hold",
        text:
          state === "oversold"
            ? "RSI is in the 'panic' zone — bounces happen here often."
            : state === "overbought"
            ? "RSI is in the 'cheering' zone — fade or take profit."
            : "RSI in the middle — no edge, sit tight.",
        confidence: Math.abs(last - 50) / 50,
      },
      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
  • ·Range-bound stocks and ETFs (REITs, utilities, sector rotators). RSI extremes mark the band edges.
  • ·Single-name large caps that mean-revert intraday after a news pop.
  • ·Combined with a trend filter — only take BUYs when the longer-term trend is up. Cuts whipsaws in half.
✗ Fails when
  • ·Strong trends. RSI can sit above 70 for weeks during a parabolic move; the bot keeps firing SELLs that lose every time.
  • ·Earnings-driven gaps. RSI smooths over them, missing the real signal.
  • ·Crypto. The 14-period RSI is too slow for the volatility; bumping it to 6 helps but defeats the textbook calibration.
How to read its verdict

BUY when RSI just exited the oversold band (was < 30, now ≥ 30). SELL when it just exited overbought (was > 70, now ≤ 70). Confidence is the distance from 50 — the further into the extreme zone before the cross, the more conviction.

FAQ
Should I use 30/70 or 20/80?+
30/70 fires more often with lower precision. 20/80 fires rarely with much higher precision per fire. For trending markets, 20/80 is safer; for choppy markets, 30/70 catches more reversions. Backtest both on your asset.
Why does the bot say BUY when the price is still falling?+
RSI moves on relative strength of UP days vs DOWN days, not on absolute price. Sometimes price keeps drifting down while the down-momentum slows — that's RSI exiting oversold. The bot is calling the bottom of the move, not the bottom of the bar.