Skip to content

Pine adapter: TradingView's margin, sizing and admission rules for one-position books - #330

Merged
luisleo526 merged 1 commit into
mainfrom
px/n2-margin-rules
Oct 4, 2026
Merged

luisleo526 merged 1 commit into
mainfrom
px/n2-margin-rules

Conversation

@luisleo526

Copy link
Copy Markdown
Collaborator

TradingView sizes, admits and margin-calls a strategy's orders on its own
ten-significant-digit money. This change teaches the Pine adapter the rules
that synthetic TradingView tapes pin. Each rule sits behind its own switch in
MarginRuleSwitches (include/pineforge/source/pine_adapter.hpp), so a
regression can be bisected rule by rule. The switches are process-wide and are
not installed API; only tests change them. No kernel file changes.

Rules, and the switches that ship on:

  • decimal_sizing: a default percent-of-equity quantity is the floor of the
    shortest decimal of sig10(E) / price, not of its binary quotient
    (6.279 rather than 6.2789).
  • pooc_fee_sizing: under process_orders_on_close, a default percent market
    order is sized on its slipped execution price, grossed up by a percentage
    commission on the ten-digit grid. The grossed unit is computed in one
    function, tv_pooc_fee_grossed_unit.
  • slipped_signal_admission: an explicit quantity opening from flat at full
    margin is judged at its signal close, on the money scale unslipped and on
    the price scale slipped.
  • gain_loss_money: realized money is kept as two sums, gains and losses,
    folded into the broker-state hash. It stands in for the engine's equity
    only inside the regime the tapes pin: no fee anywhere (configured, in the
    run's fee model, or recorded at an opening), point value 1, no FX series,
    a power-of-ten lot step and at most one open lot. One predicate,
    gain_loss_regime, holds that regime. Elsewhere the engine's equity stands.
  • dust_unit_call: a dust restore calls one unit.
  • long_open_close_checks and negative_free_cash_call: a long's one-unit
    residual call at the open, the low and the close, compared exactly.
  • coof_next_point_close: under calc_on_order_fills, a close placed after a
    call fills at the bar's next point.
  • close_point_reversal: a close_all sized before the close point's call.

Shipped off: fill_price_recheck. It gates both the unified placement half for
default orders and reversals and the fill re-check. With it off, those orders
keep the earlier admission. The scraped population showed reversals and
openings that TradingView fills at a gap of several ticks and then
margin-calls, a regime no tape pins. The tapes that need the fill re-check
are listed in their tests and asserted with every rule on. Splitting the
switch is a precondition for ever turning it on.

Evidence and tests:

  • tests/fixtures/margin_call_rules holds 266 single-position tapes: 199
    asserted, plus 67 recorded tapes of a short's call cut-off and slippage
    gate, whose rule is not pinned. It also holds 10 order controls replayed
    through handwritten hosts. tests/fixtures/margin_ledger_rules holds 381
    event-ledger tapes. TradingView's exports and the Pine sources are
    committed byte for byte.
  • test_margin_call_rules_tapes and test_margin_ledger_rules_tapes replay
    every tape:
    • with the shipped switches: 170 of 199 and 269 of 381 match, and every
      unmatched tape is listed with its reason;
    • with every rule on: 172 and 373 match;
    • with each rule off alone: every switch costs at least one tape;
    • with every rule off: 133 match, the tapes the engine matched before.
  • test_margin_rules_forward_replay replays a sample as a backtest and as a
    bar-by-bar stream, with every rule on, every rule off and the shipped
    switches. It requires byte-identical trades between the two modes on every
    split.
  • test_publication_witness adds two checks:
    • nine fuzz run-passes where the G + L money once parted from the engine's
      equity are regression tests: their closed trades must equal, in every
      Trade field, those with gain_loss_money off;
    • no closed trade of the battery leaves its lot grid.

Re-pinned tests:

  • tests/oracle/test_oracle_frozen_size.cpp and
    tests/test_default_qty_signal_freeze.cpp: 6.2789 -> 6.279. TradingView's
    own tape of the same quotient,
    tests/fixtures/margin_call_rules/sizing/sizing-decimal-floor-oracle-control,
    buys 6.279 in two byte-identical exports. tests/oracle.sha256 is re-pinned.
  • tests/test_explicit_qty_fill_admission_l4b.cpp: the GREEN-G no-op moves to
    a funded capital of 10001, and a new assertion pins TradingView's drop at
    the original operands, citing boundary/long-admit-m1u and
    admit/admit-eth-s3-m07u.
  • Four witness tables, re-harvested with each TU's own switch. Only hash
    fields move: quiet_bar 110 of 276 rows, publication 43 of 156,
    host_view_memo 13 of 13, and lookup_index's kFlip rows, reads and final.
    Every closed trade of every battery is unchanged in every Trade field.
  • tests/test_adapter_sizing_relower.cpp guard (e) keeps the kernel's
    generic fee-divisor and sizing restatements forbidden in the adapter, with
    one explicit exception: the TradingView fee-grossed unit inside
    tv_pooc_fee_grossed_unit, which must exist once and cite its tapes.

Docs: docs/pine-adapter-kernel-notes.md section 1 states the rules, their
implemented scope, the switch table, the evidence and the known gaps. The
documentation anchors that the adapter's line shifts move are re-anchored.

Known gaps, listed in the tests:

  • a carried short's call schedule, a short's call quantum at margin 50, and
    a multi-lot short's call at the high;
  • a multi-lot long at margin 50 with a per-contract fee;
  • the slipped long's call at the low;
  • the residual call after a trade history on the G + L money;
  • the money basis of a long's one-unit call after a history.

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

Measurement

Full population against the active baseline, after the exit-child-order merge: 7,989 probes, all measured, 7 up, 0 down, all strong → excellent:

  • Two hard-lane ETH 15m probes (proozac98, Blackelon) and job-2550 on BTCUSDT 15m.
  • vasudev on EURUSD 15m.
  • antoniolinux, Willow and Blackelon on NYSE:F 15m.

One-rule-off measurements attribute every move to its rule. One NYSE:F probe stays within its tier, with a worse ladder rung. Replayed at the reference's rung, its trades are byte-identical: the move is a rung switch, not a regression. CI profiles are posted as pineforge/verify; the sweep is posted as pineforge/parity.

🤖 Generated with Claude Code

…e-position books

TradingView sizes, admits and margin-calls a strategy's orders on its own
ten-significant-digit money. This change teaches the Pine adapter the rules
that synthetic TradingView tapes pin. Each rule sits behind its own switch in
MarginRuleSwitches (include/pineforge/source/pine_adapter.hpp), so a
regression can be bisected rule by rule. The switches are process-wide and are
not installed API; only tests change them. No kernel file changes.

Rules, and the switches that ship on:
- decimal_sizing: a default percent-of-equity quantity is the floor of the
  shortest decimal of sig10(E) / price, not of its binary quotient
  (6.279 rather than 6.2789).
- pooc_fee_sizing: under process_orders_on_close, a default percent market
  order is sized on its slipped execution price, grossed up by a percentage
  commission on the ten-digit grid. The grossed unit is computed in one
  function, tv_pooc_fee_grossed_unit.
- slipped_signal_admission: an explicit quantity opening from flat at full
  margin is judged at its signal close, on the money scale unslipped and on
  the price scale slipped.
- gain_loss_money: realized money is kept as two sums, gains and losses,
  folded into the broker-state hash. It stands in for the engine's equity
  only inside the regime the tapes pin: no fee anywhere (configured, in the
  run's fee model, or recorded at an opening), point value 1, no FX series,
  a power-of-ten lot step and at most one open lot. One predicate,
  gain_loss_regime, holds that regime. Elsewhere the engine's equity stands.
- dust_unit_call: a dust restore calls one unit.
- long_open_close_checks and negative_free_cash_call: a long's one-unit
  residual call at the open, the low and the close, compared exactly.
- coof_next_point_close: under calc_on_order_fills, a close placed after a
  call fills at the bar's next point.
- close_point_reversal: a close_all sized before the close point's call.

Shipped off: fill_price_recheck. It gates both the unified placement half for
default orders and reversals and the fill re-check. With it off, those orders
keep the earlier admission. The scraped population showed reversals and
openings that TradingView fills at a gap of several ticks and then
margin-calls, a regime no tape pins. The tapes that need the fill re-check
are listed in their tests and asserted with every rule on. Splitting the
switch is a precondition for ever turning it on.

Evidence and tests:
- tests/fixtures/margin_call_rules holds 266 single-position tapes: 199
  asserted, plus 67 recorded tapes of a short's call cut-off and slippage
  gate, whose rule is not pinned. It also holds 10 order controls replayed
  through handwritten hosts. tests/fixtures/margin_ledger_rules holds 381
  event-ledger tapes. TradingView's exports and the Pine sources are
  committed byte for byte.
- test_margin_call_rules_tapes and test_margin_ledger_rules_tapes replay
  every tape:
  - with the shipped switches: 170 of 199 and 269 of 381 match, and every
    unmatched tape is listed with its reason;
  - with every rule on: 172 and 373 match;
  - with each rule off alone: every switch costs at least one tape;
  - with every rule off: 133 match, the tapes the engine matched before.
- test_margin_rules_forward_replay replays a sample as a backtest and as a
  bar-by-bar stream, with every rule on, every rule off and the shipped
  switches. It requires byte-identical trades between the two modes on every
  split.
- test_publication_witness adds two checks:
  - nine fuzz run-passes where the G + L money once parted from the engine's
    equity are regression tests: their closed trades must equal, in every
    Trade field, those with gain_loss_money off;
  - no closed trade of the battery leaves its lot grid.

Re-pinned tests:
- tests/oracle/test_oracle_frozen_size.cpp and
  tests/test_default_qty_signal_freeze.cpp: 6.2789 -> 6.279. TradingView's
  own tape of the same quotient,
  tests/fixtures/margin_call_rules/sizing/sizing-decimal-floor-oracle-control,
  buys 6.279 in two byte-identical exports. tests/oracle.sha256 is re-pinned.
- tests/test_explicit_qty_fill_admission_l4b.cpp: the GREEN-G no-op moves to
  a funded capital of 10001, and a new assertion pins TradingView's drop at
  the original operands, citing boundary/long-admit-m1u and
  admit/admit-eth-s3-m07u.
- Four witness tables, re-harvested with each TU's own switch. Only hash
  fields move: quiet_bar 110 of 276 rows, publication 43 of 156,
  host_view_memo 13 of 13, and lookup_index's kFlip rows, reads and final.
  Every closed trade of every battery is unchanged in every Trade field.
- tests/test_adapter_sizing_relower.cpp guard (e) keeps the kernel's
  generic fee-divisor and sizing restatements forbidden in the adapter, with
  one explicit exception: the TradingView fee-grossed unit inside
  tv_pooc_fee_grossed_unit, which must exist once and cite its tapes.

Docs: docs/pine-adapter-kernel-notes.md section 1 states the rules, their
implemented scope, the switch table, the evidence and the known gaps. The
documentation anchors that the adapter's line shifts move are re-anchored.

Known gaps, listed in the tests:
- a carried short's call schedule, a short's call quantum at margin 50, and
  a multi-lot short's call at the high;
- a multi-lot long at margin 50 with a per-contract fee;
- the slipped long's call at the low;
- the residual call after a trade history on the G + L money;
- the money basis of a long's one-unit call after a history.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@luisleo526
luisleo526 marked this pull request as ready for review October 4, 2026 09:49
@luisleo526
luisleo526 merged commit 8640bca into main Oct 4, 2026
15 of 17 checks passed
@luisleo526
luisleo526 deleted the px/n2-margin-rules branch October 4, 2026 09:58
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