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 |
| 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);
}