feature

Moving Average

Public library indicator Moving Average (ma). Same source as the Chartnaut-owned row in the live indicator table.

What it plots

A single overlay line on price: SMA, EMA, WMA, or RMA of close over period.

Key decisions

Piece

Why it matters

kind: "overlay"

Draws on the price pane.

input.select for maType

One script exposes four MA formulas without separate indicators.

ctx.accum("closeBuf", [])

Rolling close history; seed EMA/RMA from an SMA when the buffer first fills.

Early return when length < period

Avoid plotting half-warmed averages.

Full script

meta({
  shortName: "MA",
  kind: "overlay",
  dataSource: "candles",
  category: "custom",
});

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

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

const maType = input.select({
  id: "maType",
  label: "Type",
  default: "SMA",
  options: [
    { value: "SMA", label: "SMA" },
    { value: "EMA", label: "EMA" },
    { value: "WMA", label: "WMA" },
    { value: "RMA", label: "RMA" },
  ],
});

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, maType),
    section({ id: "display", title: "Display Options" }, labelsEnabled),
  ),
  tab({ id: "appearance", title: "Appearance" },
    section({ id: "line", title: "Line", columns: 2 }, lineColor, lineStyle),
  ),
);

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

/** @param {ScriptedCtx} ctx */
export function onBar(ctx) {
  var p = ctx.params.period || 20;
  var typ = ctx.params.maType || "SMA";

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

  if (closeBuf.length < p) return;

  var val;

  if (typ === "SMA") {
    var sum = 0;
    for (var i = closeBuf.length - p; i < closeBuf.length; i++) sum += closeBuf[i];
    val = sum / p;
  } else if (typ === "EMA") {
    var k = 2 / (p + 1);
    var prevEma = ctx.accum("ema", 0, function (prev) { return prev; });
    if (closeBuf.length === p) {
      var smaSum = 0;
      for (var j = 0; j < p; j++) smaSum += closeBuf[j];
      val = smaSum / p;
    } else {
      val = ctx.close * k + prevEma * (1 - k);
    }
    ctx.accum("ema", 0, function () { return val; });
  } else if (typ === "WMA") {
    var wSum = 0;
    var wDenom = p * (p + 1) / 2;
    for (var w = 0; w < p; w++) {
      wSum += closeBuf[closeBuf.length - p + w] * (w + 1);
    }
    val = wSum / wDenom;
  } else {
    var alpha = 1 / p;
    var prevRma = ctx.accum("rma", 0, function (prev) { return prev; });
    if (closeBuf.length === p) {
      var rmaSum = 0;
      for (var r = 0; r < p; r++) rmaSum += closeBuf[r];
      val = rmaSum / p;
    } else {
      val = ctx.close * alpha + prevRma * (1 - alpha);
    }
    ctx.accum("rma", 0, function () { return val; });
  }

  ctx.plot("ma", val);
}

Next

  1. Indicator examples

  2. Indicator series types

  3. Declaring indicator dependencies