Skip to content

Pine adapter: TradingView's exit-child order at one point and the callback lifecycle (trail tolerance, revived exits, tick-built limit reach) - #328

Merged
luisleo526 merged 3 commits into
mainfrom
px/wave3-integration
Oct 4, 2026
Merged

luisleo526 merged 3 commits into
mainfrom
px/wave3-integration

Conversation

@luisleo526

@luisleo526 luisleo526 commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Two Pine adapter changes, each pinned against TradingView exports of synthetic scripts that are committed as fixtures with provenance. Each rule sits behind an internal test-only switch (on by default). There is no public API or input change, no kernel mechanics change and no new stream-mode gate.

  1. Global exit children at one point (25ed895b):
    • exit children that trigger at the same point fill in TradingView's entry-id order;
    • TradingView's rule for a stop placed on the bar its entry is born.
    • Fixtures: tests/fixtures/global_exit_children (127 tapes), ctest test_global_exit_children_tapes.
  2. Callback lifecycle (cad267f2, 6c536337):
    • the trail activation tolerates one mintick;
    • an exit that was killed is revived when the script re-issues it;
    • a callback's limit reach is measured on the tick-built price.
    • Fixtures: tests/fixtures/callback_lifecycle (87 tapes), ctest test_callback_lifecycle_tapes. The switches are in src/compat/pine/callback_lifecycle_rules.hpp.

Verification

On the head (rebased onto v1.1.0):

  • Full ctest: 828/828, including the receipt rows and test_ci_verify.
  • CI profiles (pineforge/verify): release 847, sanitizers 827, debug 828, kernel 305 with 0 boundary findings, preflight 44 stages, docs, corpus parity 312/312 with 0 moved, and the subset.
  • Tapes: the global-exit-children rows pass 841, with 34 known divergences listed in the fixture. The callback-lifecycle tapes are 66 equal and 21 known. Turning a switch off fails exactly its own fixtures.
  • Witness tables: re-harvested on the rebased tree and compared field by field (every trade field at full precision) against main. Exactly 5 rows move, all from the exit-child order rule: the TradingView-backed shape.
  • Full-population measurement (pineforge/parity), against the active baseline: 7,989 probes, all measured, 2 up, 0 down. One hard-lane probe, ETH 15m ORB, goes strong → excellent, and NYSE:F Nirav goes strong → excellent. No coverage is lost, there are no hard regressions and no leavers. 28 probes change trades. A one-rule-off bisect attributes 20 to the exit-child order and 8 to the callback lifecycle.

🤖 Generated with Claude Code

sys.path.insert(0, str(scripts))
from run_strategy import Strategy, _check_abi, inputs_run_kwargs # noqa: E402

class TapeStrategy(Strategy):
luisleo526 and others added 3 commits October 4, 2026 15:37
…id order, and the newborn stop

When one path point triggers the children of a strategy.exit that names no
entry, TradingView fills them one at a time in the order of its entry-id
table (a java.util.HashMap over the entry ids: String.hashCode spread over
16 buckets, 32 once the keys pass 13, the newest key first in a bucket; keys
are entry ids from their first fill plus one per waiting close order). Each
child is a transaction of its entry's quantity, settled against its own entry
under close_entries_rule ANY and against the oldest lots first otherwise, one
row per settled slice with its share of the ten-significant-digit fees.

The kernel fills such a leg once for the whole position. The host now re-cuts
that one fill's rows, while they are still the last rows and not final, into
the children's rows. Every shape the TradingView tapes do not reach keeps the
kernel's rows: a fill that is not the tail or leaves part of the position
open, a trailing leg, fewer than two ids, calc_on_order_fills,
process_orders_on_close or an intrabar feed, a non-unit account rate, a
cash-per-order or negative fee, a live leg of any other exit the price also
reaches, a table past 32 buckets or with a tie in a grown table, a waiting
strategy.order market order that would size the table differently, and a lot
quantity off ten significant digits. A re-cut that adds rows re-counts
strategy.wintrades / losstrades / eventrades and the gross sums; each kernel
member it writes has an ADR-0001 row.

A named stop whose limit entry fills at a point where the stop is already
through now fills at once at the entry's fill with the stop's slippage, not
at the level the bar crossed before the entry (no calc_on_order_fills, no
intrabar feed).

The entry-id table is hashed adapter state once it holds two ids or its peak
passes 13 keys, and is cleared per run. Nine source-layer witnesses are
re-pinned: seven hash-only, and two fuzz runs each move one trade by the
newborn-stop rule (quiet-bar Random28 with the magnifier off, publication
Config16).

tests/test_global_exit_children_tapes.cpp replays all 127 TradingView tapes
of the synthetic scripts: 93 asserted row for row, 34 recorded known
divergences with the rule each needs and the first departure, and six
unreached shapes pinned at the previous engine's own rows.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…sue, and tick-built callback limit reach

Three TradingView rules, taped on synthetic scripts (4 identical exports
each), each behind its own switch in src/source/pine_n6_rules.hpp
(`PineN6Rule`, `set_pine_n6_rule` / `pine_n6_rule`, atomic flags, all on;
no public API, C ABI or strategy input, and no new durable state):

- trail_points become ceil(trail_points - 8e-3 * mintick) ticks: the
  tolerance scales with mintick (ETH 0.01, XAUUSD 0.001, EURUSD 1e-5)
  instead of the constant 5e-5 ticks, which stays as the no-mintick
  fallback. Applied at every trail conversion site.
- A declined reversal kills the held position's stop and limit exits; a
  later re-issue is a new live order. The replacement path already started
  fresh legs for a changed re-issue; the unchanged-reissue shortcuts kept
  the killed row. The revive applies to every side, sizing and stop or
  limit exit, and only to the kill: a row held behind the entry/close pair
  barrier keeps it. An exit issued once stays dead, as the declined-reversal
  trail-leg tests pin. calc_on_order_fills keeps its own decline path.
- A limit exit born in a mid-leg calc_on_order_fills recalculation reaches
  its leg's end only on that waypoint's tick-built print; the level stays
  raw.

tests/fixtures/n6_callback_lifecycle commits all 82 tapes with their
generated strategies, plus five TradingView controls predicted before
export (the short mirror, a partial stop, fixed and cash sizing, and the
entry/close pair hold). test_n6_callback_lifecycle_tapes builds them into
one module, replays them row for row (66 equal, 21 known divergences with
their reason and first departure in known_divergences.json), clears each
switch in turn and requires exactly the departures rule_off_departures.json
records, and reads the pair-hold control's book to require that the stop
re-issued in the pair's calculation stays behind the barrier.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
A pure rename and comment rewording; no behaviour changes.

- The rule switches become PineCallbackLifecycleRule,
  kPineCallbackLifecycleRuleCount, set_pine_callback_lifecycle_rule and
  pine_callback_lifecycle_rule, with their storage callback_lifecycle_rules.
  Their header moves to the private src/compat/pine/callback_lifecycle_rules.hpp,
  so the compat unit that reads one no longer includes a src/source header; the
  adapter includes it from there.
- The tape module, its generated include, entry-point prefixes, exported test
  hooks, CMake variables, the ctest row and the fixture directory become
  callback_lifecycle_tapes, callback_lifecycle_<tape>__,
  callback_lifecycle_tapes_set_rule / _rule_count, _pf_clt_*,
  test_callback_lifecycle_tapes and tests/fixtures/callback_lifecycle.
- Comments, fixture READMEs, generated fixture headers and the free-text
  provenance fields name the TradingView tapes instead of internal campaign
  labels. Every edited line keeps its place, so no documentation anchor moves.

The bytes TradingView ran (each tape's strategy.pine, its exports and the
metrics that pin their hashes) are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@luisleo526
luisleo526 force-pushed the px/wave3-integration branch from 6c53633 to 0f7c529 Compare October 4, 2026 07:38
@luisleo526
luisleo526 marked this pull request as ready for review October 4, 2026 08:31
@luisleo526
luisleo526 merged commit 14b05c9 into main Oct 4, 2026
17 checks passed
@luisleo526
luisleo526 deleted the px/wave3-integration branch October 4, 2026 09:54
luisleo526 added a commit that referenced this pull request Oct 5, 2026
* docs: release notes and version scoping for v1.2.0

CHANGELOG: the Unreleased section becomes 1.2.0 (2026-10-05), a minor
release that adds the compiled execution-capabilities extension to the C
ABI (PF_CAPABILITIES_API_VERSION, strategy_capabilities_api_version,
strategy_capabilities_receipt). One bullet per change since v1.1.0 with
its PR (#328, #330, #331, #332, #333, #334, #335, #336, #337, #338 and the
session-clock change, written #VCP until its PR number exists), the
recipe additions under State hashes (#328, #330, #335; domain tags and
epoch unchanged), Report keys (docker/run_json.py is byte-identical to
v1.1.0's, so no key changes) and Migration.

Version scoping: README, install and public-contract pages name v1.2.0
and codegen 1.2.0; "since the next release" and "fixed on main" in the
capabilities guide and the runner guide become 1.2.0 and 1.1.0 (#325).
Lines that state what an earlier release shipped stay.

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

* docs: 1.2.0's grades from the facts tokens, #339, and codegen's license

- CHANGELOG: the session-clock change is #339 (merged as 52292db), in place
  of the #VCP token of 3082fbd; the "What a script author sees" bullet of
  3082fbd now states release 1.2.0's grades (registry baseline
  pineforge-parity-baseline-20261005-engine-52292db9: 7,970 excellent / 19
  strong of 7,989, none below strong) instead of the main scoreboard at
  6b77f06.
- README: the release sentence names 1.2.0 and renders releases[1.2.0], and
  the main scoreboard renders the same baseline, from pineforge-release
  facts/facts.json at 0c2f6838 (the facts export for the 1.2.0 release).
- README, LEGAL.md, docs/pages/index.md: pineforge-codegen is under the
  PineForge Source License 1.1 from its release 1.2.0; releases up to 1.1.0
  keep the PolyForm Noncommercial terms they shipped with.

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