Skip to content

Pine adapter: explicit entry quantity floor, script equity mark, process-orders-on-close bar fills and the session clock - #334

Merged
luisleo526 merged 1 commit into
mainfrom
px/n789-sizing-session
Oct 4, 2026
Merged

luisleo526 merged 1 commit into
mainfrom
px/n789-sizing-session

Conversation

@luisleo526

Copy link
Copy Markdown
Collaborator

Four TradingView rules, each pinned on synthetic TradingView tapes with 0
counterexamples and each behind its own test-only switch (default on; no
public API, input or strategy setting reaches it): ScriptRuleSwitches in
include/pineforge/source/pine_adapter.hpp, SessionClockSwitches in
include/pineforge/session_time.hpp.

  • explicit_qty_decimal_floor: an explicit strategy.entry quantity that
    floor_quantity_grid's 1e-6 tolerance snaps UP to a grid point used to come
    back raw and off the grid, and then never filled. TradingView floors its
    shortest decimal (decimal_floor_lot, shared with the default sizing):
    3124.9999999999973 trades 3124. Only that snap changes; strategy.order
    quantities and lots that are not a power of ten keep the grid floor.
  • equity_tick_mark: script-visible strategy.equity / strategy.openprofit
    (PineStrategyHost::open_profit, which generated code calls) mark an open
    position at floor(close / mintick + 0.5) * mintick when the close lies off
    the tick grid (NYSE:F half-cent closes). The tick is syminfo_mintick_ (the
    engine's 0.01 default when a host sets none, the tick fills already use).
    On-grid closes, and the kernel's own marks, are unchanged; the adapter's
    money gates name BacktestEngine::open_profit explicitly.
  • pooc_bracket_skips_inert_exits: under process_orders_on_close the
    fresh-parent-first rank of a re-issued bracket no longer counts an exit
    whose entry is neither open nor working as part of the book, so a stop
    entry and its bracket that both fill inside one bar settle before the
    close-time script.
  • transition_wall_clock: time() / time_close() with a script's own session
    argument read TradingView's session clock across a daylight-saving switch:
    an occurrence that starts or ends at the switch's pre-transition wall time
    runs on the post-transition offset throughout; every other occurrence
    follows the wall clock. Taped in New York and London only. The chart's own
    bar close, read through the SYMBOL's session, keeps the plain windows
    (detail::symbol_session_time_close) until a tape pins a symbol session
    with an endpoint at its zone's switch hour.

Tests: 41 TradingView tapes committed as fixtures (explicit_qty_floor,
pooc_close_bar_fills, session_transition_clock), each with its exact Pine
source and export. The explicit-quantity and bar-fill tapes run through
their frozen generated strategies in one module
(test_explicit_qty_floor_generated_tapes,
test_pooc_close_bar_fills_generated_tapes); the hand-ported hosts stay for
the script readouts trades cannot show (strategy.equity / openprofit /
netprofit at the read bar, the guarded branch). Each switch turned off fails
exactly its own tapes. test_qty_step_lot_grid_case pins the release
harness's lot-grid case byte for byte, with and without the grid. Docs:
line citations re-anchored, digits only.

Measurement

Full population against the active baseline: 4 up, 0 down, all strong → excellent:

  • the explicit-quantity floor on an NYSE:F 1D probe;
  • the process-orders-on-close bar fills, twice: vasudev on BTCUSDT 15m and on XAUUSD 15m;
  • the session clock on an ETH 15m hard-lane probe.

One-rule-off measurements attribute every trade-set move to its rule. The witness tables are byte-identical to main, with zero moved trades. CI profiles are posted as pineforge/verify; the sweep is posted as pineforge/parity.

🤖 Generated with Claude Code

… bar fills and the session clock

Four TradingView rules, each pinned on synthetic TradingView tapes with 0
counterexamples and each behind its own test-only switch (default on; no
public API, input or strategy setting reaches it): ScriptRuleSwitches in
include/pineforge/source/pine_adapter.hpp, SessionClockSwitches in
include/pineforge/session_time.hpp.

- explicit_qty_decimal_floor: an explicit strategy.entry quantity that
  floor_quantity_grid's 1e-6 tolerance snaps UP to a grid point used to come
  back raw and off the grid, and then never filled. TradingView floors its
  shortest decimal (decimal_floor_lot, shared with the default sizing):
  3124.9999999999973 trades 3124. Only that snap changes; strategy.order
  quantities and lots that are not a power of ten keep the grid floor.
- equity_tick_mark: script-visible strategy.equity / strategy.openprofit
  (PineStrategyHost::open_profit, which generated code calls) mark an open
  position at floor(close / mintick + 0.5) * mintick when the close lies off
  the tick grid (NYSE:F half-cent closes). The tick is syminfo_mintick_ (the
  engine's 0.01 default when a host sets none, the tick fills already use).
  On-grid closes, and the kernel's own marks, are unchanged; the adapter's
  money gates name BacktestEngine::open_profit explicitly.
- pooc_bracket_skips_inert_exits: under process_orders_on_close the
  fresh-parent-first rank of a re-issued bracket no longer counts an exit
  whose entry is neither open nor working as part of the book, so a stop
  entry and its bracket that both fill inside one bar settle before the
  close-time script.
- transition_wall_clock: time() / time_close() with a script's own session
  argument read TradingView's session clock across a daylight-saving switch:
  an occurrence that starts or ends at the switch's pre-transition wall time
  runs on the post-transition offset throughout; every other occurrence
  follows the wall clock. Taped in New York and London only. The chart's own
  bar close, read through the SYMBOL's session, keeps the plain windows
  (detail::symbol_session_time_close) until a tape pins a symbol session
  with an endpoint at its zone's switch hour.

Tests: 41 TradingView tapes committed as fixtures (explicit_qty_floor,
pooc_close_bar_fills, session_transition_clock), each with its exact Pine
source and export. The explicit-quantity and bar-fill tapes run through
their frozen generated strategies in one module
(test_explicit_qty_floor_generated_tapes,
test_pooc_close_bar_fills_generated_tapes); the hand-ported hosts stay for
the script readouts trades cannot show (strategy.equity / openprofit /
netprofit at the read bar, the guarded branch). Each switch turned off fails
exactly its own tapes. test_qty_step_lot_grid_case pins the release
harness's lot-grid case byte for byte, with and without the grid. Docs:
line citations re-anchored, digits only.
@luisleo526
luisleo526 marked this pull request as ready for review October 4, 2026 13:58
@luisleo526
luisleo526 merged commit 16c3d08 into main Oct 4, 2026
17 checks passed
@luisleo526
luisleo526 deleted the px/n789-sizing-session branch October 4, 2026 14:18
luisleo526 added a commit that referenced this pull request Oct 5, 2026
…ents resolved without copies (#339)

## What

`time(tf, session, tz)` with a script's own session argument reads TradingView's session clock outside UTC (#334). That path asked the per-day session cache about nine times a bar, and copied the session and zone strings on every `time()` / `time_close()` call. This PR reads each day's occurrences once per call, caches `utc_zone` per session-cache entry, and resolves the call's strings by reference. It changes one file, `src/session_time.cpp`: the language calendar functions under rule 1 of ADR 0001. It adds no switch, input, API or hashed state.

## Value identity (on this tree, against main)

- the old and new session functions run side by side over the whole suite: 0 mismatches;
- a differential over generated sessions, zones and timeframes, walking every DST switch of 2024–2026 minute by minute: 16.6 M queries, identical digest;
- an independent review repeated it with 33.2 M queries: 0 different answers, identical cache and zone-memo history, clean under ASan + UBSan;
- the 30 witness harvests: byte-identical, 0 re-pins;
- the whole-suite per-trade dump (all 21 Trade fields at `%.17g`, every state hash): identical apart from rows that differ between two runs of one tree;
- all 307 tape and witness executables: byte-identical;
- corpus 312/312, 0 moved.

## Timing (on this tree, against main 44eab7b)

Measured on a Xeon 8375C with 4 independent builds per tree, paired rotating blocks (81 per shape), and every process under exactly 22 environment variables. Positive means slower; `*` means the 95% interval covers 0. The **padded control** is this commit with every function outside `session_time.o` placed at main's address, checked symbol by symbol.

| | native | ta-sma | bracket | udt | composite-vcp |
|---|---|---|---|---|---|
| batch median, ungathered (1 m / 1 m) | +1.23 | +1.26 | +0.87 | +0.83 | **−9.54** |
| — padded control | +0.03* | +0.06* | +0.02* | −0.00* | **−7.93** |
| per call, ungathered | +0.21 | +0.04* | +0.19 | +0.10 | **−5.16** |
| — padded control | +0.05* | +0.01* | −0.02* | −0.02* | **−4.84** |
| stream p95, ungathered | +0.46 | +1.53 | +0.46 | +0.17 | **−3.07** |
| — padded control | +0.04* | +0.03* | −0.05* | +0.01* | **−3.46** |
| batch median, gathered (1 m / 3 m) | +0.60 | +0.09* | +0.54 | +0.41 | **−5.80** |
| — padded control | +0.06* | +0.06* | +0.05* | +0.09* | **−5.25** |

- **Only composite-vcp's work changes.** Its instruction count drops from 232.5 M to 220.1 M over 2 × 5,000 stream pushes, and from 2,512 to 1,275 per `timeframe_time` call.
- **The other four cases execute identical instructions.** Their differences against main are code placement: this file shrinks by 263 bytes and moves the hot functions by 192–256 bytes. With the layout held fixed (padded control), all four read 0 within noise on every metric. The commit message's table was measured with #336's tree as the base; the table above is this tree.

## Verification

- Engine verify bundles on this exact tree: release 859, sanitizers 837, debug 840, kernel 307 with 0 boundary findings, parity 312/312, subset, docs.
- One parity sweep of the full population on this head, paired with codegen main, is neutral: 7,989 comparable, no moves.
- An independent review returned MERGE; its message fixes are applied.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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