ChartnautDocs

EMA cross

Fires when a fast EMA crosses above a slow EMA. It reads the library Moving Average indicator twice instead of computing EMAs itself.

What it defines

An event ema_cross_up on the bar where the fast EMA moves from at or below the slow EMA to above it.

Key decisions

Piece

Why it matters

Two indicators.declare calls on slug ma

Same indicator, different as alias and settings.

ctx.indicators.<as>.ma

Reads are always nested by the dependency's output key: ma for the Moving Average.

.at(1) for the previous value

The MA plots every bar, so .at(1) is one bar back. No ctx.accum, so no warmup to declare.

Guard == null

The MA has no value until its period has filled. Skip those bars.

No oncePer

A cross is already one bar. Every cross is its own event.

Full script

meta({
  name: "EMA cross up",
  kind: "flow",
  slug: "ema-cross-up",
  shortName: "EmaXUp",
});

indicators.declare({
  slug: "ma",
  as: "fast",
  version: 1,
  settings: { period: 9, maType: "EMA" },
});

indicators.declare({
  slug: "ma",
  as: "slow",
  version: 1,
  settings: { period: 21, maType: "EMA" },
});

events.declare({
  id: "ema_cross_up",
  intent: "signal",
  payload: {
    fast: { type: "number", description: "Fast EMA on the cross bar" },
    slow: { type: "number", description: "Slow EMA on the cross bar" },
    close: { type: "number", description: "Close on the cross bar" },
  },
  paint: "pin",
});

outputs.declare({ id: "fast_line", kind: "line", name: "Fast", color: "#26a69a" });
outputs.declare({ id: "slow_line", kind: "line", name: "Slow", color: "#ef5350" });

export function onBar(ctx) {
  const fast = ctx.indicators.fast.ma.value;
  const slow = ctx.indicators.slow.ma.value;
  if (fast == null || slow == null) return;

  ctx.plot("fast_line", fast);
  ctx.plot("slow_line", slow);

  const prevFast = ctx.indicators.fast.ma.at(1);
  const prevSlow = ctx.indicators.slow.ma.at(1);
  if (prevFast == null || prevSlow == null) return;

  if (prevFast <= prevSlow && fast > slow) {
    ctx.emit(
      "ema_cross_up",
      { fast, slow, close: ctx.close },
      {
        label: "EMA cross ↑",
        subtitle: `${fast.toFixed(2)} > ${slow.toFixed(2)}`,
        price: ctx.close,
      }
    );
  }
}

version pins the Moving Average version the definition reads. Set it to the version the indicator library shows: pinning a version that does not exist fails lint with indicators/version-not-found. See Declaring indicator dependencies.

Next, Session OR break keeps its own state across bars.