feature

Stochastic

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

What it plots

Pane %K / %D oscillator (0–100) with overbought, middle, and oversold guide lines.

Key decisions

Piece

Why it matters

Raw %K from highest high / lowest low

Position of close inside the recent range.

Two-stage SMA smooth

%K smoothed, then %D as SMA of %K.

Guide lines from params

Levels are plotted as series so they move with settings.

Full script

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

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

const kLength = input.number({
  id: "kLength",
  label: "%K Length",
  default: 14,
  min: 1,
  step: 1,
});

const kSmoothing = input.number({
  id: "kSmoothing",
  label: "%K Smoothing",
  default: 3,
  min: 1,
  step: 1,
});

const dSmoothing = input.number({
  id: "dSmoothing",
  label: "%D Smoothing",
  default: 3,
  min: 1,
  step: 1,
});

const upperBandValue = input.number({
  id: "upperBandValue",
  label: "Overbought Level",
  default: 80,
  min: 50,
  max: 100,
  step: 1,
});

const middleBandValue = input.number({
  id: "middleBandValue",
  label: "Middle Level",
  default: 50,
  min: 0,
  max: 100,
  step: 1,
});

const lowerBandValue = input.number({
  id: "lowerBandValue",
  label: "Oversold Level",
  default: 20,
  min: 0,
  max: 50,
  step: 1,
});

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

const dColor = input.color({
  id: "dColor",
  label: "%D Color",
  default: "#FF6D00",
  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" }, kLength, kSmoothing, dSmoothing),
    section({ id: "levels", title: "Signal Levels" }, upperBandValue, middleBandValue, lowerBandValue),
    section({ id: "display", title: "Display Options" }, labelsEnabled),
  ),
  tab({ id: "appearance", title: "Appearance" },
    section({ id: "lines", title: "Lines", columns: 2 }, kColor, dColor, lineStyle),
  ),
);

output.line({
  id: "k",
  lineWidth: 2,
  colorFrom: kColor,
  lineStyleFrom: lineStyle,
  labelFrom: labelsEnabled,
  titleWhenLabeled: "%K",
});

output.line({
  id: "d",
  lineWidth: 1,
  colorFrom: dColor,
  lineStyleFrom: lineStyle,
  labelFrom: labelsEnabled,
  titleWhenLabeled: "%D",
});

output.line({
  id: "upperBand",
  lineWidth: 1,
  titleWhenLabeled: "Overbought",
});

output.line({
  id: "middleBand",
  lineWidth: 1,
  titleWhenLabeled: "Middle",
});

output.line({
  id: "lowerBand",
  lineWidth: 1,
  titleWhenLabeled: "Oversold",
});

/** @param {ScriptedCtx} ctx */
export function onBar(ctx) {
  var kLen = ctx.params.kLength || 14;
  var kSm = ctx.params.kSmoothing || 3;
  var dSm = ctx.params.dSmoothing || 3;

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

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

  if (highBuf.length < kLen) return;

  var highest = -Infinity;
  var lowest = Infinity;
  for (var i = highBuf.length - kLen; i < highBuf.length; i++) {
    if (highBuf[i] > highest) highest = highBuf[i];
    if (lowBuf[i] < lowest) lowest = lowBuf[i];
  }

  var range = highest - lowest;
  var rawK = range !== 0 ? ((ctx.close - lowest) / range) * 100 : 50;

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

  if (rawKBuf.length < kSm) return;

  var kSum = 0;
  for (var j = rawKBuf.length - kSm; j < rawKBuf.length; j++) kSum += rawKBuf[j];
  var kVal = kSum / kSm;

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

  ctx.plot("k", kVal);
  ctx.plot("upperBand", ctx.params.upperBandValue != null ? ctx.params.upperBandValue : 80);
  ctx.plot("middleBand", ctx.params.middleBandValue != null ? ctx.params.middleBandValue : 50);
  ctx.plot("lowerBand", ctx.params.lowerBandValue != null ? ctx.params.lowerBandValue : 20);

  if (kBuf.length < dSm) return;

  var dSum = 0;
  for (var d = kBuf.length - dSm; d < kBuf.length; d++) dSum += kBuf[d];
  var dVal = dSum / dSm;

  ctx.plot("d", dVal);
}

Next

  1. Indicator examples

  2. Indicator series types

  3. Declaring indicator dependencies