Forward return
For each EMA cross up, measures the return from the cross close to the close a set number of minutes later, then publishes the spread of those returns.
What it measures
Forward return in basis points after ema_cross_up, over a horizon the trader sets before each run.
Key decisions
Piece | Why |
|---|---|
| The trader changes the horizon in the run dialog without editing code. |
| The event joins the sample from what is known when it fires, even when the horizon bar is not loaded yet. |
| Returns the one bar at or just before event time plus the horizon, or nothing past the end of the data. |
Entry from | The close the definition emitted at the cross. |
| Basis points compare across instruments and price levels. |
| Takes a plain array of numbers. |
Full script
meta({
name: "EMA cross forward return",
kind: "study",
params: {
horizonMin: { type: "number", def: 60, min: 1, max: 1440, step: 1 },
},
});
flows.declare({ slug: "ema-cross-up", as: "xup", version: 2 });
results.declare({
id: "readout__summary",
kind: "prose",
role: "answer",
title: "Summary",
description: "The typical move after an EMA cross up, in plain words.",
});
results.declare({
id: "readout__events",
kind: "metric",
title: "Crosses measured",
description: "EMA crosses up with a bar at the horizon.",
});
results.declare({
id: "readout__median_bp",
kind: "metric",
title: "Median return (bp)",
description: "Median close-to-close return over the horizon, in basis points.",
});
results.declare({
id: "fwd_ret",
kind: "distribution",
size: "lg",
title: "Forward returns (bp)",
description: "Close-to-close return from the cross to the horizon bar, in basis points.",
});
export function scope(ctx) {
if (ctx.event.flow !== "xup") return null;
return {};
}
export function onEvent(ctx, scope) {
const entry = Number(ctx.event.payload.close);
if (!Number.isFinite(entry) || entry === 0) return;
ctx.mark(ctx.event.time, entry, { color: "#26a69a", shape: "circle" });
const after = ctx.candles.forward({ minutes: ctx.params.horizonMin });
if (!after) return;
ctx.collect("rets", ctx.priceUnits.inBP(after.close, entry));
}
export function onFinish(ctx) {
const rets = ctx.collected("rets");
const median = rets.length ? ctx.stat.median(rets) : 0;
ctx.publish("readout__events", { value: rets.length, format: "integer" });
ctx.publish("readout__median_bp", { value: median, n: rets.length });
ctx.publish("fwd_ret", rets);
ctx.publish("readout__summary", {
body: rets.length
? `${rets.length} crosses. Median return after ${ctx.params.horizonMin} minutes: ${median.toFixed(1)} bp.`
: "No crosses had a bar at the horizon.",
role: "answer",
});
}Because it reads forward, this study can be attached to the chart. With no dimensions it pins as one global card. To get a reading per session or trend state, add a scope dimension as in Stop / target first hit.
