feature

Stop / target first hit

After an OR break, asks whether price hit a +R target or −R stop first, then reports hit rates overall and by session key.

What it studies

Win / loss / neither rates for symmetric barriers around the break price, plus a table sliced by sessionKey from the event payload.

Key decisions

Piece

Why it matters

stopAt / targetAt + firstHit: true

Runtime walks forward until one barrier (or neither).

Barriers from event price × R

Risk distance comes from study params, not hard-coded ticks.

Collect structured rows

onFinish can aggregate rates and session slices from one bucket.

hit: "target" \| "stop" \| "neither"

Matches the forward barrier result shape.

Full script

meta({
  name: "OR break first hit",
  kind: "study",
  params: {
    rTicks: { type: "number", def: 20, min: 1, max: 500 },
    tickSize: { type: "number", def: 0.25, min: 0.0001 },
  },
});

flows.declare({
  slug: "or-break",
  as: "orb",
});

results.declare({ id: "events", kind: "metric", title: "Events" });
results.declare({ id: "target_rate", kind: "metric", title: "Target rate" });
results.declare({ id: "stop_rate", kind: "metric", title: "Stop rate" });
results.declare({
  id: "by_session",
  kind: "table",
  title: "By session",
});

export function onEvent(ctx) {
  if (ctx.event.flow !== "orb") return;

  const entry = Number(ctx.event.payload.close);
  if (!Number.isFinite(entry)) return;

  const dist = ctx.params.rTicks * ctx.params.tickSize;
  const isUp = ctx.event.id === "or_break_up";
  const targetAt = isUp ? entry + dist : entry - dist;
  const stopAt = isUp ? entry - dist : entry + dist;

  const res = ctx.candles.forward({
    targetAt,
    stopAt,
    firstHit: true,
  });
  if (!res) return;

  ctx.collect("rows", {
    sessionKey: ctx.event.payload.sessionKey || "unknown",
    hit: res.hit,
    side: isUp ? "up" : "dn",
  });
}

export function onFinish(ctx) {
  const rows = ctx.collected("rows");
  const n = rows.length;
  const targets = rows.filter((r) => r.hit === "target").length;
  const stops = rows.filter((r) => r.hit === "stop").length;

  ctx.publish("events", { value: n });
  ctx.publish("target_rate", {
    value: n ? targets / n : 0,
    format: "percent",
  });
  ctx.publish("stop_rate", {
    value: n ? stops / n : 0,
    format: "percent",
  });

  const keys = [];
  for (const r of rows) {
    if (!keys.includes(r.sessionKey)) keys.push(r.sessionKey);
  }

  ctx.publish(
    "by_session",
    keys.map((sessionKey) => {
      const slice = rows.filter((r) => r.sessionKey === sessionKey);
      const sn = slice.length;
      const st = slice.filter((r) => r.hit === "target").length;
      return {
        sessionKey,
        events: sn,
        target_rate_pct: sn ? Number(((st / sn) * 100).toFixed(1)) : 0,
      };
    })
  );
}

Showing these numbers on the Terminal uses Put on chart / the Forward insight agent — not the study authoring agent. See Show studies on charts.

Next

  1. Examples

  2. Show studies on charts

  3. First study guide