LIVE
connecting to the live quote stream…
--:--:--UTC
Learn/Bots/RSI Reversion
Trend & Momentum

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.

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 95163
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: "long", price: px[i], label: "exit oversold" });
        sigArr[i] = 1;
      } else if (a > hi && b <= hi) {
        signals.push({ i, kind: "short", 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.