Stop / target first hit
After each opening range break, sets a hypothetical target and stop one opening range away from the break close and asks which one price reached first. It reports the rates overall and for up and down breaks separately.
What it measures
For every or_break_up and or_break_dn: did price reach the target, the stop, or neither before the scan ended. The distance is the opening range height times a multiple the trader sets.
Key decisions
Piece | Why |
|---|---|
| Known the moment the break fires, so the results split by it and a Forward Insight can show the reading for the break you click. |
Payload check in | A break with no usable range returns |
Distance from | Risk scales with the day's range instead of a fixed tick count. |
| The runtime walks forward bar by bar and returns the bar where a level was touched, with |
| Reads |
Table keyed by |
|
Full script
meta({
name: "OR break first hit",
kind: "study",
params: {
rMultiple: { type: "number", def: 1, min: 0.25, max: 5, step: 0.25 },
},
});
flows.declare({ slug: "or-break", as: "orb", version: 2 });
dimensions.declare({
id: "direction",
role: "scope",
title: "Break direction",
description: "Which side of the opening range the close broke.",
values: ["up", "down"],
});
results.declare({
id: "readout__summary",
kind: "prose",
role: "answer",
title: "Summary",
description: "Which level price usually reached first after a break.",
});
results.declare({
id: "readout__events",
kind: "metric",
title: "Breaks",
description: "Opening range breaks with at least one bar after the break.",
});
results.declare({
id: "readout__target_rate",
kind: "metric",
title: "Target first",
description: "Share of breaks where the hypothetical target was touched before the stop.",
});
results.declare({
id: "by_direction",
kind: "table",
title: "By break direction",
description: "Target-first and stop-first rates for up and down breaks.",
indexedBy: ["direction"],
});
function levels(ctx) {
const entry = Number(ctx.event.payload.close);
const risk = Number(ctx.event.payload.orHigh) - Number(ctx.event.payload.orLow);
return { entry, risk };
}
export function scope(ctx) {
if (ctx.event.flow !== "orb") return null;
if (ctx.event.id !== "or_break_up" && ctx.event.id !== "or_break_dn") return null;
const { entry, risk } = levels(ctx);
if (!Number.isFinite(entry) || !Number.isFinite(risk) || risk <= 0) return null;
return { direction: ctx.event.id === "or_break_up" ? "up" : "down" };
}
export function onEvent(ctx, scope) {
const { entry, risk } = levels(ctx);
const direction = scope.direction;
ctx.mark(ctx.event.time, entry, { color: direction === "up" ? "#26a69a" : "#ef5350", shape: "circle" });
const dist = risk * ctx.params.rMultiple;
const targetAt = direction === "up" ? entry + dist : entry - dist;
const stopAt = direction === "up" ? entry - dist : entry + dist;
const res = ctx.candles.forward({ stopAt, targetAt, firstHit: true });
if (!res) return;
ctx.collect("rows", { direction, hit: res.hit });
}
function share(rows, hit) {
return rows.length ? rows.filter((r) => r.hit === hit).length / rows.length : 0;
}
export function onFinish(ctx) {
const rows = ctx.collected("rows");
const up = rows.filter((r) => r.direction === "up");
const down = rows.filter((r) => r.direction === "down");
ctx.publish("by_direction", [
{ direction: "up", breaks: up.length, target_first_pct: share(up, "target") * 100, stop_first_pct: share(up, "stop") * 100 },
{ direction: "down", breaks: down.length, target_first_pct: share(down, "target") * 100, stop_first_pct: share(down, "stop") * 100 },
]);
const targetRate = share(rows, "target");
ctx.publish("readout__events", { value: rows.length, format: "integer" });
ctx.publish("readout__target_rate", { value: targetRate, format: "percent", n: rows.length });
ctx.publish("readout__summary", {
body: rows.length
? `${rows.length} breaks. The target was touched first in ${(targetRate * 100).toFixed(0)}% of them. These are hypothetical levels, not orders.`
: "No breaks in this run.",
role: "answer",
});
}"neither" means the scan reached the end of the loaded data, or its bar limit, without touching either level. On a long run those are usually the most recent breaks.
This study can be attached to the chart, and Break down by offers direction. See Show studies on charts.
