feature

EMA cross

Fires when a fast EMA crosses above a slow EMA. Uses the public Moving Average indicator as a dependency instead of re-implementing EMA math.

What it defines

Event ema_cross_up on the bar where fast MA crosses from at-or-below slow MA to above it.

Key decisions

Piece

Why it matters

Two indicators.declare with different as / settings

Same slug (ma) twice with locked periods.

Guard fast == null \|\| slow == null

Deps are empty during warmup — skip, do not emit.

Read-then-write ctx.accum

Identity read returns last bar’s values; second call stores today’s.

oncePer: { keys: ["session"], windowBars: 1 }

At most one emit per session window (optional throttle).

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: "Fast EMA crosses above slow EMA",
  payload: {
    fast: { type: "number", description: "Fast EMA" },
    slow: { type: "number", description: "Slow EMA" },
    close: { type: "number", description: "Close at cross" },
  },
  paint: "pin",
  oncePer: { keys: ["session"], windowBars: 1 },
});

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?.value;
  const slow = ctx.indicators.slow?.value;
  if (fast == null || slow == null) return;

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

  // Identity update returns the stored prior values without changing them.
  const prevFast = ctx.accum("prevFast", null, (prev) => prev);
  const prevSlow = ctx.accum("prevSlow", null, (prev) => prev);

  if (prevFast != null && prevSlow != null && 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,
        timeStart: ctx.time,
      }
    );
  }

  ctx.accum("prevFast", null, () => fast);
  ctx.accum("prevSlow", null, () => slow);
}

Pin version to the library MA version you resolve in the builder when you can — agents should call resolve_dependency_contract before inventing shapes. See Declaring indicator dependencies.

Next

  1. Examples

  2. Session OR break

  3. Declaring indicator dependencies