Skip to content

fix(engine): stop retro-arming explicit-zero-offset trails from the carried peak - #148

Merged
luisleo526 merged 2 commits into
mainfrom
ship/trail-retro-arming
Aug 12, 2026
Merged

luisleo526 merged 2 commits into
mainfrom
ship/trail-retro-arming

Conversation

@luisleo526

Copy link
Copy Markdown
Collaborator

Gate

pr-gate PASS — engine corpus zero regressions, scraped entering excellent+strong >= leaving.

Verdict: /Users/haoliangwen/code/pineforge-lab/pr-gate-verdict.json

🤖 Generated with Claude Code

luisleo526 and others added 2 commits August 12, 2026 23:32
…arried peak

An EXPLICIT trail_offset=0 is TV's one-shot exit-at-activation trail: it
fills AT its activation crossing, so it can never survive into a later bar
in the armed state — an armed zero-offset trail is an executed one. The
per-bar ExitTrailState reconstruction nevertheless derived "armed" from the
carried post-entry extreme, so when a strategy re-issues strategy.exit each
bar with trail_points derived from close (level_t = entry ± prevBarClose *
perc, trail_offset = 0), an activation level refreshed from a NEWER close
could drop under a peak set beneath an older, higher level: the stop
retro-armed at that stale peak and the position exited at the next bar's
open (or at the stale level on an against-direction segment) — an exit
TradingView never prints.

Scope the carried-best pre-arming away from explicit-zero-offset trails
only. An OMITTED trail_offset keeps the carried arming even though it
shares the exit-at-activation fill rule: TV treats its activation as
durable order state measured against the position's running extreme —
pinned by corpus bracket-exit-stop-limit-trail-same-bar-01 2025-08-30
08:45 (entry 4392.08, activation 4392.26, entry-bar high 4396.01, first
live bar opens 4392.25 one tick BELOW the level, TV fills at that open,
which only a carried armed state produces; the corpus stays byte-identical
under this change). Offset>0 trails likewise keep the reconstruction.

TV rule for the explicit-zero shape (fitted 219/219 + 147/147 clean
trailing exits on the boztilkiserhan serhan1 WMA scalp / ADX tapes, both
of which pass trail_offset=0 literally): level_t = entry ± prevBarClose *
perc, live from the bar after entry, intrabar cross fills at the level, at
the open when the bar opens past it. Discriminating tape: scalp 2025-04-09
— the engine exited at the 14:15 open 1475.99 off a 1501.03 peak under the
refreshed 1500.98 level, while TV holds to 16:30 @1501.19 (entry +
C[16:15]*1.5%, to the cent).

Co-Authored-By: Claude Fable 5 <[email protected]>
@luisleo526
luisleo526 merged commit a6e46ca into main Aug 12, 2026
7 checks passed
@luisleo526
luisleo526 deleted the ship/trail-retro-arming branch August 12, 2026 15:59
pull Bot referenced this pull request in Malumbo21/pineforge-engine Sep 4, 2026
Two trailing-exit rules diagnosed from TradingView tapes (TV is ground
truth). Family: winthetrade-ema-9-vwap-strategy-with-atr-trailing-stop
(strategy.exit("x", trail_points = atr*2, trail_offset = atr*2) -- a
price-unit ATR passed as TICKS) on 7 lanes: matchPct 100, entry/exit
timestamps exact, exit PRICE wrong on the same bar, so PnL / equity /
qty diverge from there.

(B) The bar's open is the first price a resting trail observes.

resolve_exit_path_fill only folded prices into the trail state AFTER
each O->L / O->H segment (update_exit_trail_state). With a POOC close
fill the carried best is the entry price itself (apply_market_order_fill
same_bar_close_fill guard), so a trail whose activation level the next
bar's open already sits past stayed dormant through an adverse-first
leg, armed only at the favourable extreme and filled at extreme -/+
offset. TV's emulator arms at the open with best = open and fills the
retrace at open -/+ offset.

  OANDA:XAUUSD 15m long 2025-04-04 10:45Z @3110.31; 11:00Z bar
    O 3110.40 H 3136.775 L 3109.24 C 3134.46, atr*2 ~ 15 ticks
    (activation 3110.325, offset 0.015): TV "Long Exit" 3110.385 =
    open - 0.015 (PnL 24.47); engine 3136.76 = high - 0.015 (PnL 8631).
    194 of the 195 differing XAUUSD exits fit TV = open -/+ k ticks vs
    engine = extreme -/+ the same k, always adverse-leg-first
    (|O-L| < |H-O| for longs) with the open beyond the activation.
  NASDAQ:AAPL 15m short 2025-04-02 18:45Z @222.93; 04-03 13:30Z bar
    O 205.54 L 202.52: TV 205.55 (open + 1 tick), engine 202.53.
  NYSE:F 1D long 2025-04-21 @9.47; 04-22 O 9.55 H 9.72: TV 9.54,
    engine 9.71.

Fix: after try_exit_open_gap_fill declines (same liveness condition --
path cursor at the open, non-entry bar unless magnifier / cascade
waypoint), observe_exit_trail_open folds bar.open into the running best
and arms the trail iff the OPEN itself is past the activation. It never
arms from the carried best, so the #148 one-shot rule (an explicit zero
offset never retro-arms) is untouched; for exit-at-activation shapes an
open past the level was already gap-filled, so it is a pure best-price
observation there.

(A) A trail_offset in [0, 1) ticks is TV's explicit-zero one-shot trail.

compute_exit_trail_state gated the zero rule on trail_offset != 0.0 and
only then floored, so an offset in (0, 1) became a FINITE zero distance:
exits_at_activation = false and the stop rode exactly on the running
extreme, filling at the bar's best price. TV floors first and applies
the zero-offset rule: fill at the activation crossing, or at the open
when the bar opens past it, and no carried arming.

  Pinned with `lab tv` on OANDA:EURUSD 15m (2025-04-01 -> 05-01): the
    tapes for strategy.exit("x", "L", trail_points=3, trail_offset=0),
    trail_offset=0.5 and trail_offset=0.9 are byte-identical
    (190 rows, sha256 36aa80ac...).
  EURUSD 15m short 2025-03-31 03:45Z @1.08330, atr*2 ~ 0.0006 "ticks"
    (activation ceil -> 1 tick = 1.08329, floor(offset) = 0); exit bar
    O 1.08330 L 1.08314: TV 1.08329 (the activation), engine 1.08314.

Fix: floor(trail_offset) first; a zero-tick result takes the existing
explicit-zero path (trail_offset_price NaN, one_shot_zero_offset_trail).
Whole-tick and larger fractional offsets keep the nils123456-orb /
legalrice floor rule unchanged; negative offsets are untouched. The
trail-peak fold in engine_fills.cpp already floors, so it agrees.

Tests: tests/test_trail_open_arm_subtick_offset.cpp (105 checks) pins
the XAUUSD / AAPL / F shapes at the resolver and, for XAUUSD and AAPL,
end to end through a POOC close-fill entry; 0 == 0.5 == 0.9 == 0.6 at
the activation, at a gapped open, and for the #148 no-retro-arm hold
(resolver and engine level, with the omitted-offset durable-arming
control); whole-tick 1.0 / 1.4 keep the floored trailing distance.
Against the pre-fix source 29 of those checks fail, all of them the
tape cells.

One existing pin contradicted the TV rule and is re-pinned, not
dropped: test_path_resolve_extra.cpp fl_sub (0f1f06e, a factorial
round, 2026-08-25) asserted that a 0.6-tick offset "rides the extreme
itself once armed (distinct from an explicit 0)" -- fill at the peak
102.00 -- as an extrapolation of the floor rule with no tape behind
the sub-tick cell. It now expects the activation 101.00 and equality
with the explicit-0 fill, with the `lab tv` evidence in the comment.
Every other trail pin (test_path_resolve_extra, the #148 retro-arm
cells, test_margin_call_trail_exit_chronology D, cascade G4) passes
unchanged. ctest 153/153 (was 152/152 before the new binary), scripts
unittest 102/102.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_014vizwdJMsDkHsjmS4StvZY
pull Bot referenced this pull request in Malumbo21/pineforge-engine Sep 6, 2026
…rs; re-pin the one-shot fixtures

test_zero_offset_trail_rides: 32 `lab tv` tapes (NYSE:F 15m feed
80f404ae85ef, NASDAQ:AAPL 15m feed ae2b03d3736f) replayed end to end
through BacktestEngine, each under the sensor's own issue mode (re-issued
per bar, once, alongside the entry, or the probe's close*1.5%/mintick
re-issue), asserting TV's exit price and bar.

Re-pinned to the tape-confirmed rule: test_trail_fill_snap (the explicit-0
AAPL 196.135 open is a path level fill at position 0, still 196.13
end to end), test_path_resolve_extra and test_trail_open_arm_subtick_offset
(the #148 fixtures now carry the best family Z actually carries — the
issuing close — and HOLD; a carried best past the activation arms every
zero-tick offset, the contract the command layer's restart relies on; a
gapped open arms the trail and it rides), test_margin_call_trail_exit_
chronology fixture D (the explicit zero arms from the placement close like
the omitted offset). ctest 190/190.

(cherry picked from commit 5f87ccb)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant