Skip to content

Pine adapter: limit reach, exit binding, carried trail, margin-call schedule - #341

Merged
luisleo526 merged 4 commits into
mainfrom
px/wave-f
Oct 5, 2026
Merged

luisleo526 merged 4 commits into
mainfrom
px/wave-f

Conversation

@luisleo526

Copy link
Copy Markdown
Collaborator

What

Four Pine adapter rules, each pinned by synthetic TradingView controls with the predictions written before export and 0 counterexamples. Each sits behind its own internal test-only switch, default on. With every switch off, the engine is identical to main byte for byte over the whole suite.

  1. A resting calc_on_order_fills limit reaches on its tick-built threshold. An on-grid COOF limit rests at its tick-built threshold only when three things hold:

    • it closes the position held when it is placed;
    • the run is on the chart path, with no magnifier intrabars;
    • its placement point does not already reach that threshold.

    It fixes the four newborn-limit tapes. With the owner's approval (2026-10-05), four frozen witness tables were re-harvested on the premise that no closed trade moves. The unrestricted rule did move witness fills, so the rule was narrowed to the scope where none moves. The three exclusions are engine limits, not TradingView rules. In 21-field per-trade dumps, the re-harvested tables show 0 fill moves: hash-only changes, plus entry_incarnation renumbering in one calc-on-fills battery.

  2. strategy.exit binds at its call. It binds to its entry's open lots if there are any; else to the entry id while that id has a working entry order. That binding survives close_all, the close of a sibling, flats and reversals. An add called at the pyramiding cap is not placed. Scope: whole exits at absolute levels with a limit/stop parent and no COOF, with margins off, as the TradingView evidence covers. The 36 controls and the 10 pending-entry tapes are asserted.

  3. The carried trail is reached on the tick-built print, and the level stays raw. This applies only where the tick-built print reaches first, the direction TradingView shows. Separately, the point order counts the stop/limit legs of exits that also trail.

  4. The margin-call schedule. It has six parts:

    • a short call is taken only if units·p'·m > held·p'·m − equity, with p' the slipped print;
    • a lagged follow-up for shorts, at the next waypoint;
    • a one-unit follow-up after the open's close-sized call;
    • a pending-veto check at the next waypoint, with its state hashed;
    • long-call money after history at commission 0;
    • the close leg of a reversal frozen at placement.

    It also prices an add at the signal's tick-built close against fee-charged equity, and books the whole-share-grid follow-up. In ablation, short_path_points is meant to be switched together with the close-call follow-up.

Effect on the population

One parity measurement of this exact tree, full population, paired with codegen main:

  • 5 probes move strong → excellent, one of them on the hard lane: TON grid NYSE:F 1D, Blitz trendshift NYSE:F 15m, Silver Surfer NYSE:F 15m, Legalrice NYSE:F 15m, and job-2847 ETHUSDT.P 15m (hard).
  • 0 move down, and there are 0 hard-lane drops.
  • 25 trade sets change. Each change is the union of the four rules' own measured movers, byte for byte, and has moved toward TradingView. No probe moves only through an interaction between the rules.

Evidence in the tree

  • Every rule's TradingView tapes are asserted, engine equal to TradingView row for row. The callback-lifecycle tapes stand at 71 equal + 16 known divergences of 87, the 4 newborn-limit tapes fixed.
  • Forward replays equal the backtest where a rule has a forward half.
  • Release ctest passes with the receipt rows.

Verification

  • Engine verify bundles on this exact tree: release 862, sanitizers 840, debug 843, kernel 307 with 0 boundary findings, parity 312/312 with 0 moved, subset 54, docs.
  • Independent reviews of each rule and of the integration returned MERGE. Their requested fixes are applied. One follow-up: a committed test for one integration line in the exit-binding cap check, which moves nothing; it lands as a tests-only change.

🤖 Generated with Claude Code

luisleo526 and others added 4 commits October 5, 2026 13:52
…-built bar

Under calc_on_order_fills an on-grid exit limit used to rest at its raw level,
so a 9.73 sell limit born in the opening fill's recalculation missed a bar
whose high of 9.725 prints 9.73. TradingView fills it on that bar
(tests/fixtures/callback_lifecycle newborn-limit-cancel, -owner-absent,
-replace and -rounded, NYSE:F 15m, 4 identical exports each).

exit_limit_trigger now rests such a limit at its tick-built threshold when it
closes the position held when it is placed, on the chart path, and its
placement point does not already reach it. Everywhere else the on-grid level
stays raw, as before:
- a leg placed for a parent not held on its side: its threshold would sit on
  the parent's side, and a settlement booked at the source level then lands
  outside the immutable trigger (MatchRejected InvalidTerms);
- a leg its placement point already reaches: for a point that stands on the
  raw level, the matcher books the level at that point, while a passed
  threshold waits for the next point. A point that is a fill booked at the
  print of a half-tick-short open is not on the raw path: the raw level is
  then never reached, while TradingView fills at once (repeated-rounded,
  TradingView trades 18-19, recorded as a residual);
- a magnifier's intrabars, as for the in-flight reach.

These three exclusions are engine limits, not TradingView rules. The rule
without them, every on-grid calc_on_order_fills limit at its threshold, moved
closed-trade fills in the frozen witness tables below, so it was narrowed to
the scope where no closed trade moves.

The rule has its own test-only switch, RestingLimitTickReach (on).
test_callback_lifecycle_tapes: 70 of 87 tapes equal TradingView row for row,
17 known divergences; clearing the switch moves exactly the four newborn-limit
tapes and repeated-rounded's exit bar; the tapes the rule decides are replayed
forward and a stream refuses each (calc_on_order_fills is unsupported).

Four witness tables re-harvested with their own harvest switches, on the
owner's approval of 2026-10-05, given on the premise that no closed trade
moves (the same harvest against main reproduces every old value):
publication 2 of 156 runs, quiet bar 14 of 276 rows, live state 9 of 108
transcripts, continuation view 2 of 10 k_coof reads. Every closed trade keeps
every fill and money field in every run-pass; the moves are the hashed
trigger price, and CalcOnFills' entry_incarnation renumbering.

Doc citations re-anchored for the moved adapter lines.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…or to the pending entry id

TradingView binds a strategy.exit(id, from_entry = X) when the script calls
it: to X's open lots if X holds any, else to the id X while an order of X is
working, else the call is ignored. An exit bound to the id survives a close,
close_all, the flat and a cancel and re-entry of X, and binds to X's next
fill (36 synthetic controls, two byte-identical exports each, and the ten
pending-* tapes of tests/fixtures/global_exit_children). Each part has its own
switch in ExitBindingRuleSwitches, all on:

- pending_bound_exit_survives_flat: an exit called in position for an id
  with no lot but a limit or stop order working is bound to the id
  (PlacementSnapshot::pending_bound_exit, hashed while set, cleared by the
  id's fill). retire_in_position_exits_at_flat and
  cancel_exit_orders_for_full_close leave it; a strategy.cancel of the id's
  order keeps the call (id_bound_exits_, hashed while non-empty) and the
  id's next entry order re-arms it.
- global_exit_binds_working_entries: a global exit called flat waits for
  the fill of the limit and stop entries working instead of ending on the
  flat book.
- resting_stop_entry_survives_close: under process_orders_on_close an
  earlier bar's stop entry survives a close's flat, as a limit does.
- priced_add_at_cap_not_placed: under process_orders_on_close a priced add
  of an id holding no lot, still at the pyramiding cap once its bar's
  closes are done, leaves the book at the next opening; each add is judged
  at that one opening.

Every part acts only with margin requirements off (margin_long =
margin_short = 0) and without calc_on_order_fills, the tapes' setting; the
two exit parts on whole exits at absolute levels whose parent rests at a
level, the stop-entry and cap parts under process_orders_on_close. Inside
these gates the parts also change shapes no tape covers, as the pin's
reference model predicts them: a parent on the other side of the position,
a strategy.order parent, a market re-entry after the cancel, a cancel and
re-entry on the exit's own bar, a short-side cap and a cap above 1. Open
edges without a tape: a stop-limit entry, which the stop part keeps; a
global exit called flat without process_orders_on_close, which also waits,
and its taking by another id's first fill; an add at the cap marketable at
its own close, which fills there before the cap part judges it, as before;
an add whose own bar's close frees the slot, which stays, as before (the
pin's model rejects it at the call). Every witness table keeps its pins.
tests/test_exit_binding_tapes asserts all 36 controls, each part's tapes
with the part off, the engine before the rule with every part off, and
backtest == forward stream on every control both ways. The ten pending-*
tapes are asserted. Doc anchors re-anchored.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…rder

Two TradingView rules, each behind its own default-on, test-only switch.

Carried trail tick reach (PineCallbackLifecycleRule::CarriedTrailTickReach).
With calc_on_order_fills off, a trailing stop carried from an earlier bar is
reached where the bar's tick-built print reaches it; the level stays the raw
running best +/- the offset, as a static stop's does. The kernel's generic
Trail compares the raw path with the raw level, so on_bar_open walks the bar
as the kernel will and, only where the tick-built point comes before the raw
compare's, re-prices the trail's distance for that bar (ride and handle
kept); on_bar_close_before_script restores it. A raw reach whose tick-built
print falls back inside the stop stays the kernel's, as before: no tape
decides that direction. Tape: tests/fixtures/callback_lifecycle/
carried-trail-14 (NYSE:F, 14 ticks behind a 10.345 best, the 10.485 high
prints 10.49 and exits at 17:45, not 18:15); it leaves known_divergences.json
and pins the switch in rule_off_departures.json. test_callback_lifecycle_tapes
also replays it as a bar-by-bar stream with 1 and 30 warm-up bars: the stream
books the backtest's trades and TradingView's rows, and with the switch off
both depart from TradingView the same way.

The walk's copy of the kernel's trailing-stop level is
compat::pine::kernel_trail_stop_level (include/pineforge/compat/pine/
trail_ticks.hpp, header-only). test_kernel_trail_stop_replica holds it, bit
for bit, to native_matching::checked_trail_stop on every best a trail moves
to on the tape's bars, ladder bests in both spellings on four price ticks,
half-tick and off-grid bests, and whole-tick and fractional distances (45,057
levels). The unit includes a source-layer header, so a kernel-only build
skips it.

Trailing exits in the point order
(MarginRuleSwitches::point_order_trailing_exits). A stop or limit leg of a
strategy.exit that also carries trail_points, trail_offset or trail_price
counts as a protective exit at the open, like any other: the gapped stop
fills before the opening margin check. Tapes (lab tv --no-note, 3
byte-identical exports each, predictions written first):
tests/fixtures/admission_rules/point_order_and_book/
gapstop-percent-trail-points and -trail-offset, c2's script with
trail_points (and a trail_offset) on the exit; TradingView books c2's rows
(112 call, 8744 at the 12.26 gap).

No kernel, grader or codegen change; no stream gate. The new adapter state
(the trails re-priced for the bar in flight) is hashed only while set.
Docs: switch table and evidence counts, the callback-lifecycle README, the
row on per-kind tick quantization; doc anchors re-anchored (digit-only).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…th, lagged follow-ups, pending vetoes, frozen reversal closes, adds at the signal close, long-call G + L money

TradingView's margin-call schedule rules, each behind its own default-on,
test-only switch (detail::MarginScheduleSwitches, appended at the header's
end); adapter only, no kernel, grader or codegen change, no stream gate.

- short_call_gate: a short's call of X units at mark p is taken only where
  X p' m > Q p' m - E(p), p' its slipped print (source_margin_units); the
  one-unit band is its one-unit case. An opening checkpoint at a fill keeps
  the earlier rules, and so does the repeat at one price: a
  process_orders_on_close short's open call repeated at its own fill
  (recheck_at_fill), a follow-up the pin calls ungated, is sized ungated
  (submit_margin_call_slice at an already-resolved price).
- short_path_points: a carried process_orders_on_close short (commissioned
  or slipped, nothing resting) is checked at both extremes and the close
  before the script, not at its high alone (walk_short_path_points). It is
  ablated together with close_call_follow_up_at_open: each corrects its own
  bars, and with one off the other's equity error moves a later open call.
- lagged_short_follow_up: after a short's call, the lot-by-lot check at its
  fill with the called units out and unbooked; the last still-short check's
  restore, four times, at the bar's next path point, ungated (fractional lot
  grids; outside process_orders_on_close only where margin_follow_up_units
  does not already follow the call, pinned there by lag/outside-pooc-close
  and lag/outside-pooc-extreme).
- pending_veto_first: a deficit the open's check vetoed is checked at the
  first path point whose segment touches a resting whole exit, before it;
  after a call there the exit fills at that point's print. It stands down
  unless that exit, with the other leg of its strategy.exit, is the book's
  one resting order, the shape its tapes pin.
- frozen_reversal_close: a reversal entry placed at a close where a margin
  call is booked after the script closes the quantity the script saw and
  opens the excess on the other side (resolve_terms).
- add_signal_close: a same-side add's whole-book requirement at the
  tick-built signal close (validate_precommit), at margin 100 only.
- whole_share_lagged_follow_up: a long's lagged follow-up on whole-share
  grids too (lagged_margin_follow_up_units).
- long_call_gain_loss: a commission-free long's one-unit call reads
  initial + (G + L) inside gain_loss_regime(); fee-bearing books keep the
  closed-trade equity (no tape pins them).
- close_call_follow_up_at_open: a process_orders_on_close short's call sized
  at the previous close and executed at the open is followed there once, at
  its own fill: four times the lot-floored shortfall, or one unit under one
  lot of it (margin_follow_up_units).

New durable state (a follow-up owed at a path point, a close's after-script
call) is folded into the adapter state hash only while set.

Tapes: the 67 short-cutoff-gate tapes of tests/fixtures/margin_call_rules are
asserted now (244/266 match; initial/short-s5-minus01, schedule-q9p1305-s5,
-s6 and literals/literal-21 leave the known list), and
tests/fixtures/margin_schedule_rules holds 39 synthetic controls (each exported
twice, byte-identical): 35 match, 4 are listed; every tape also replays as a
stream with identical closed trades, and three mechanism cases on synthetic or
patched bars reach the branches no tape does (the walk's close call owing the
next open, the pending veto on the open's segment and beside a resting
entry). test_margin_open_print_tapes asserts its three tapes to their last
bar (443, 445 and 667 trades). test_margin_rules_forward_replay samples four
more tapes.

Docs: switch table and evidence counts in docs/pine-adapter-kernel-notes.md;
doc anchors re-anchored (digit-only).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

class TapeStrategy(Strategy):
def __init__(self, lib, prefix): # noqa: super().__init__ loads a path
self.lib = TapeEntryPoints(lib, prefix)
_check_abi(self.lib)
self._setup_signatures()

class StreamStrategy(Strategy):
@luisleo526
luisleo526 merged commit 3c41452 into main Oct 5, 2026
17 checks passed
@luisleo526
luisleo526 deleted the px/wave-f branch October 5, 2026 09:50
luisleo526 added a commit that referenced this pull request Oct 6, 2026
* docs: release notes and version scoping for v1.3.0

CHANGELOG 1.3.0: confirmed-bar admission in pineforge-live (#342), the Pine
adapter changes of #341, #345 and #347, tests and docs (#343, #344); no
report key changes; the state-hash recipe additions of #341 and #347, domain
tags and the v19 epoch unchanged; migration.

Version scoping: README, install.md and public-contract.md name v1.3.0, its
tarballs, the engine1.3.0-codegen1.3.0 image and the 1.3.0 pair. The
confirmed-bar receipt and its admission, marked unreleased after 1.2.0 by
#342, ship in 1.3.0 (README, strategy-capabilities.md, runner/README.md).

Codegen license lines (README, LEGAL.md, docs/pages/index.md): the PineForge
Source License 1.2 from codegen 1.3.0; 1.2.0 keeps 1.1, releases up to 1.1.0
the PolyForm Noncommercial terms.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* docs: release 1.3.0's grades in the README and the changelog, from the facts tokens

The README's release sentence moves to the releases[1.3.0] tokens, and its
main-scoreboard lines re-render the values defe41f (#343) rendered at
pineforge-release 3e39d64, and the 1.3.0 changelog says which registry
baseline grades the release and why that baseline is the release's code,
all rendered from pineforge-release facts/facts.json at ff21de0 (#32). The
changelog sentence extends the last line of the "What a script author sees"
bullet that 0e1ced7 added.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
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