feature

RSI

Public library indicator RSI (rsi). Same source as the Chartnaut-owned row in the live indicator table.

What it plots

A 0–100 pane oscillator (Wilder RSI) plus constant overbought/oversold guide lines.

Key decisions

Piece

Why it matters

Gain/loss buffers + Wilder averages

Classic RSI smoothing, not a plain SMA of changes.

Plot levels every ready bar

Overbought/oversold are constant series, not hline helpers.

avgLoss === 0 → 100

Avoids divide-by-zero when there were no down moves.

Full script

meta({
  shortName: "RSI",
  kind: "pane",
  dataSource: "candles",
  category: "custom",
});

dialog({
  title: "RSI Configuration",
  description: "Configure Relative Strength Index (RSI) settings",
  width: 400,
  height: 400,
  resizable: false,
  showEnabledCheckbox: true,
  resetToDefaults: {
    enabled: true,
    confirmationMessage: "Are you sure you want to reset all RSI settings to their default values?",
  },
});

const period = input.number({
  id: "period",
  label: "Period",
  default: 14,
  min: 1,
  step: 1,
});

const overbought = input.number({
  id: "overbought",
  label: "Overbought Level",
  default: 70,
  min: 0,
  max: 100,
  step: 1,
});

const oversold = input.number({
  id: "oversold",
  label: "Oversold Level",
  default: 30,
  min: 0,
  max: 100,
  step: 1,
});

const lineColor = input.color({
  id: "lineColor",
  label: "Line Color",
  default: "#2962FF",
  allowAlpha: true,
});

const lineStyle = input.lineStyle({
  id: "lineStyle",
  label: "Line Style",
  default: LineStyle.Solid,
});

const labelsEnabled = input.boolean({
  id: "labelsEnabled",
  label: "Show Labels",
  default: true,
});

layout(
  tab({ id: "general", title: "General" },
    section({ id: "parameters", title: "Parameters" }, period, overbought, oversold),
    section({ id: "display", title: "Display Options" }, labelsEnabled),
  ),
  tab({ id: "appearance", title: "Appearance" },
    section({ id: "line", title: "Line", columns: 2 }, lineColor, lineStyle),
  ),
);

output.line({
  id: "rsi",
  lineWidth: 2,
  colorFrom: lineColor,
  lineStyleFrom: lineStyle,
  labelFrom: labelsEnabled,
  titleWhenLabeled: "RSI",
});

output.line({
  id: "overbought",
  lineWidth: 1,
  colorFrom: lineColor,
  lineStyleFrom: lineStyle,
  labelFrom: labelsEnabled,
  titleWhenLabeled: "Overbought",
});

output.line({
  id: "oversold",
  lineWidth: 1,
  colorFrom: lineColor,
  lineStyleFrom: lineStyle,
  labelFrom: labelsEnabled,
  titleWhenLabeled: "Oversold",
});

/** @param {ScriptedCtx} ctx */
export function onBar(ctx) {
  var p = ctx.params.period || 14;
  var obLevel = ctx.params.overbought || 70;
  var osLevel = ctx.params.oversold || 30;

  var closeBuf = ctx.accum("closeBuf", [], function (prev) {
    prev.push(ctx.close);
    return prev;
  });

  if (closeBuf.length < 2) return;

  var prevClose = closeBuf[closeBuf.length - 2];
  var change = ctx.close - prevClose;
  var gain = change > 0 ? change : 0;
  var loss = change < 0 ? -change : 0;

  var gainBuf = ctx.accum("gainBuf", [], function (prev) {
    prev.push(gain);
    return prev;
  });
  var lossBuf = ctx.accum("lossBuf", [], function (prev) {
    prev.push(loss);
    return prev;
  });

  if (gainBuf.length < p) return;

  ctx.plot("overbought", obLevel);
  ctx.plot("oversold", osLevel);

  if (gainBuf.length === p) {
    var sumG = 0;
    var sumL = 0;
    for (var i = 0; i < p; i++) {
      sumG += gainBuf[i];
      sumL += lossBuf[i];
    }
    var avgG = sumG / p;
    var avgL = sumL / p;
    ctx.accum("avgGain", 0, function () { return avgG; });
    ctx.accum("avgLoss", 0, function () { return avgL; });

    var rsi;
    if (avgL === 0) {
      rsi = 100;
    } else {
      var rs = avgG / avgL;
      rsi = 100 - 100 / (1 + rs);
    }
    ctx.plot("rsi", rsi);
    return;
  }

  var prevAvgGain = ctx.accum("avgGain", 0, function (prev) { return prev; });
  var prevAvgLoss = ctx.accum("avgLoss", 0, function (prev) { return prev; });

  var avgGain = (prevAvgGain * (p - 1) + gain) / p;
  var avgLoss = (prevAvgLoss * (p - 1) + loss) / p;

  ctx.accum("avgGain", 0, function () { return avgGain; });
  ctx.accum("avgLoss", 0, function () { return avgLoss; });

  var rsiVal;
  if (avgLoss === 0) {
    rsiVal = 100;
  } else {
    var rs2 = avgGain / avgLoss;
    rsiVal = 100 - 100 / (1 + rs2);
  }
  ctx.plot("rsi", rsiVal);
}

Next

  1. Indicator examples

  2. Indicator series types

  3. Declaring indicator dependencies