Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ set(PINEFORGE_KERNEL_SOURCES
src/native_calendar.cpp
src/native_run_spec.cpp
src/native_fx_curve.cpp
src/native_bar_views.cpp
src/selected_report_view.cpp
src/selected_window_plan.cpp
)
Expand Down
80 changes: 40 additions & 40 deletions docs/adr/0001-kernel-adapter-boundary.md

Large diffs are not rendered by default.

140 changes: 117 additions & 23 deletions docs/design/cr150-contracts.md

Large diffs are not rendered by default.

248 changes: 124 additions & 124 deletions docs/design/native-feature-parity.md

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions docs/native-refactor-progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ them for the record only. "Linux aarch64" is the maintainers' Cortex-A725 host.

| Item | What it is | Measured size | Deferred by | Prototype and evidence | Reopens when |
| --- | --- | --- | --- | --- | --- |
| D2-B, the quiet-bar protocol and one receipt read per bar | PERF-D2's lane: a per-bar bar-open request, a bulk fold of quiet driver points, the adapter's quiet-open predicate with lazy open bookkeeping, and a gate before the receipt poll. D2-A landed the hook declarations and the O(1) `has_command_after` (`src/native_execution_consumer.hpp:321`). PERF-L4 gated the read inside `observe_terminal_receipts` (`src/source/pine_adapter.cpp:6446`), which still runs about three times a bar | PERF-D2, Linux aarch64 (Cortex-A725), callgrind and GBench: about ×0.88 on the 14 probes (an estimate from a measured −13 to −14 % floor bound for the bar-open request alone); after PB the receipt poll still costs about 250 instructions a bar on the no-order floor. On this tree the quiet-bar probe asks the receipt gate 40,996 times and each once-per-bar-open gate 13,248 times: 3.09 reads a bar, 82 % of them stopped at the gate (macOS arm64, AppleClang 17, Release) | "R5 PERF-D2 DONE 2026-09-24 19:56" (D2-B after V19-D); "R5 SUPERVISOR RULING 2026-09-25 00:15 Taipei": "D2-B (quiet-bar protocol) waits for PERF-D3's verdict"; TRIAGE1 §D | An unpublished prototype branch and its patches. PERF-D3's S1 (next row) prototyped the request and the lazy opening | A post-release performance epoch. Done when the receipt gate is asked as often as the once-per-bar gates |
| D2-B, the quiet-bar protocol and one receipt read per bar | PERF-D2's lane: a per-bar bar-open request, a bulk fold of quiet driver points, the adapter's quiet-open predicate with lazy open bookkeeping, and a gate before the receipt poll. D2-A landed the hook declarations and the O(1) `has_command_after` (`src/native_execution_consumer.hpp:321`). PERF-L4 gated the read inside `observe_terminal_receipts` (`src/source/pine_adapter.cpp:6448`), which still runs about three times a bar | PERF-D2, Linux aarch64 (Cortex-A725), callgrind and GBench: about ×0.88 on the 14 probes (an estimate from a measured −13 to −14 % floor bound for the bar-open request alone); after PB the receipt poll still costs about 250 instructions a bar on the no-order floor. On this tree the quiet-bar probe asks the receipt gate 40,996 times and each once-per-bar-open gate 13,248 times: 3.09 reads a bar, 82 % of them stopped at the gate (macOS arm64, AppleClang 17, Release) | "R5 PERF-D2 DONE 2026-09-24 19:56" (D2-B after V19-D); "R5 SUPERVISOR RULING 2026-09-25 00:15 Taipei": "D2-B (quiet-bar protocol) waits for PERF-D3's verdict"; TRIAGE1 §D | An unpublished prototype branch and its patches. PERF-D3's S1 (next row) prototyped the request and the lazy opening | A post-release performance epoch. Done when the receipt gate is asked as often as the once-per-bar gates |
| S1a, kernel quiet-bar driving (PERF-D3) | A bar with nothing live, a flat book and a declined opening is driven in a quiet form: the kernel writes the bookkeeping its four price points would leave (ordinals, the last two driver marks, the floor, the point epochs) without walking them or calling the bar-open callback. Additive host cadence declarations; C hosts declare from their callback table; one C symbol under the PF_API checklist | PERF-D3, Linux aarch64 (Cortex-A725), GBench time and `perf stat` instructions, on the L3 tree `b33ea659`, S1a with S1b: no-order floor 0.764 / 0.660 in time (0.718 / 0.656 in instructions), 14 probes 0.958 / 0.921 (0.892 / 0.856), 100 slots 0.972 / 0.956 per slot. A bare kernel host that opts in: idle 0.545× (35 % fewer instructions a bar), market 0.81×, bracket 0.92×. Byte-identical: corpus 312/312, the 132-configuration battery with its hash columns | "FINDING PERF-D3 (filed for a post-release epoch per the owner's cap)" (ledger, 2026-09-24 16:58 UTC); "OWNER DECISION 2026-09-25 00:20 Taipei"; TRIAGE1 §D: "PERF-D3 S1/S2 (post-release epoch)" | An unpublished prototype branch (five commits on L3's tree) and its patches | The post-release epoch, with the owner calls PERF-D3 lists (the API under the existing ruling, the restated gate count) |
| S1b, the Pine lazy opening | The Pine host records what its skipped opening and its uncalled input callback would have written, at the bar's first host callback. Member functions only, no layout change | Measured with S1a. A quiet bar then asks 15 gates, not 30, so the quiet-bar cost check `CHECK(asked >= 20 * o.boundary.size())` (`tests/test_adapter_quiet_bar.cpp:1054`) must be restated | As S1a | As S1a. The in-script hash witnesses caught the prototype's one miss (`awaiting_legacy_script_open_ms_`) | As S1a |
| S1c, the held-position wake band | S1 for bars that hold a position with nothing live, 41 % of the median public slot's bars. The host declares a price band, and the kernel calls the opening only when the bar's range leaves it. The adapter derives the band from its margin model and declines when it cannot bound it | PERF-D3, a measurement-only upper bound without the band, which is not sound in general: 100 slots 0.776 / 0.716 per slot, 14 probes 0.830 / 0.761, cumulative against L3 with S1, S2 and D2-A | As S1a; the owner call "Fund the price band" | An unpublished measurement-only script | As S1a. Size L, parity risk high: TradingView's margin money rules |
Expand All @@ -183,7 +183,7 @@ them for the record only. "Linux aarch64" is the maintainers' Cortex-A725 host.
| Compact journal records and a history ring buffer (L3) | Smaller command records and a ring buffer for the request core's history. `WorkingRequestCore::history` (`include/pineforge/native_order.hpp:1709`) hands out the whole vector and the core holds `history_` (`include/pineforge/native_order.hpp:2280`) inside `native_order_v7`, so both are a layout and API change | L3, Linux aarch64 (Cortex-A725), `perf` call graphs, inclusive shares: at most 3.68 % of bracket, 11.99 % of re-issue, 4.26 % of market and 3.42 % of the 14 probes (retiring the history alone: 1.64, 4.51, 2.53 and 1.00 %); only part is realisable, since records must still be written | "R5 L3 DONE + ACCEPTED 2026-09-24 22:10 Taipei", ruling 1: "DEFERRED to the PERF-D3 structural plan"; TRIAGE1 §D: "compact journal records" | Lane L3's code, merged by INT22 (#285); its profiles are unpublished | A `native_order` layout epoch |
| A checked-values handoff for executions (L3) | `execution_values` runs twice, in `check_execution` (`src/native_order.cpp:4887`) and again in `apply_execution` (`src/native_order.cpp:4910`); handing the checked values across needs a public type, an API addition with no layout change | L3, as above: 2.21 % of bracket for the two together, about 1.1 % recoverable | L3 ruling 2: "a public checked-values handoff for executions (~1.1% bracket) -> DEFERRED" | As above | An owner decision on the API addition |
| A chunked `trades_` store (L3) | Closed trades in chunks rather than one vector; `trades_` (`include/pineforge/engine.hpp:546`) is protected `BacktestEngine` state, so script ABI | L3, as above: on bracket, vector growth 0.64 %, `Trade` moves 0.74 % and `record_close_trade` 0.43 % (at most about 1.4 %); at most 0.35 % on the 14 probes | L3 ruling 4: "chunked trades_ store (ABI) -> DEFERRED" | As above | An `engine_script_run` epoch that changes the engine's protected layout |
| A zoned chart-day memo (D2-D finding 4) | On a chart with a timezone, `libc_chart_day_key` (`src/source/pine_adapter.cpp:17351`) takes the process timezone lock through `ScopedTimezone` and calls `localtime_r` on every bar, because `on_bar_open` (`src/source/pine_adapter.cpp:22250`) reads `chart_day_key` (`src/source/pine_adapter.cpp:22487`) unconditionally. The non-UTC monthly Sharpe/Sortino walk, `month_key_local` (`src/engine_metrics.cpp:33`), pays one `localtime_r` per equity point. UTC charts use D2-D's memo, `civil_chart_day_key` (`src/source/pine_adapter.cpp:17335`) | Not measured on a zoned chart. The UTC path cost 134 instructions a bar before D2-D's memo and 68 after (D2-D, Linux aarch64, callgrind on the floor). PERF-K1: `ScopedTimezone` copies the zone name, which allocates for names of 16 or more characters under libstdc++ | TRIAGE1 §D: "D2-D finding 4 (the timezone `localtime_r` memo)", under the owner's cap. D2-D's report: an exact zoned memo "would need the zone's transition table, which belongs in `timezone.cpp`" | None; the UTC memo's witness is `tests/test_chart_day_memo.cpp` | A lane that owns `src/timezone.cpp` and builds an exact zone-transition memo (PERF-K1's Sitka case shows why a shortcut is not exact), byte-identical, with the zoned keys of `test_chart_day_memo` still equal to the computed ones |
| A zoned chart-day memo (D2-D finding 4) | On a chart with a timezone, `libc_chart_day_key` (`src/source/pine_adapter.cpp:17353`) takes the process timezone lock through `ScopedTimezone` and calls `localtime_r` on every bar, because `on_bar_open` (`src/source/pine_adapter.cpp:22252`) reads `chart_day_key` (`src/source/pine_adapter.cpp:22489`) unconditionally. The non-UTC monthly Sharpe/Sortino walk, `month_key_local` (`src/engine_metrics.cpp:33`), pays one `localtime_r` per equity point. UTC charts use D2-D's memo, `civil_chart_day_key` (`src/source/pine_adapter.cpp:17337`) | Not measured on a zoned chart. The UTC path cost 134 instructions a bar before D2-D's memo and 68 after (D2-D, Linux aarch64, callgrind on the floor). PERF-K1: `ScopedTimezone` copies the zone name, which allocates for names of 16 or more characters under libstdc++ | TRIAGE1 §D: "D2-D finding 4 (the timezone `localtime_r` memo)", under the owner's cap. D2-D's report: an exact zoned memo "would need the zone's transition table, which belongs in `timezone.cpp`" | None; the UTC memo's witness is `tests/test_chart_day_memo.cpp` | A lane that owns `src/timezone.cpp` and builds an exact zone-transition memo (PERF-K1's Sitka case shows why a shortcut is not exact), byte-identical, with the zoned keys of `test_chart_day_memo` still equal to the computed ones |
| Thread-local reads that remain per call | D2-C moved the three scopes' writes onto the pump's block, `pump_ambient` (`src/native_execution_consumer.hpp:496`). A reader still reaches the block through `tl_runtime_ambient` (`src/ta_extremes_volume.cpp:29`): `bar_context` (`src/ta_extremes_volume.cpp:82`), read by every `ExtremeRing::update` (`src/ta_extremes_volume.cpp:117`), which backs `ta.highest`, `ta.lowest`, `ta.highestbars` and `ta.lowestbars` and, through an embedded `Highest` and `Lowest`, `ta.stoch`, `ta.wpr` and `ta.range`; and `matching_day_partition` (`src/timeframe.cpp:628`), read by the default-anchored `ta.vwap` through `session_day_index`, by `crosses_boundary` on a DAY period (`timeframe.change("D")`) and by the session-period helpers. `decompose_ms_local` (`src/session_time.cpp:250`) reads two thread-locals per call on a zoned clock, and the prepared order storage's `free_blocks` (`src/native_order.cpp:620`) one per prepared mutation. `ema_na_warmup_flag` is read once per EMA, at its first compute | One thread-local access per reader call. AUDIT4, macOS arm64, a dlopen'd tutorial-MACD probe, counted by `_tlv_get_addr` breakpoints and by descriptor patching: 1.000 a bar for one `ta::Highest` and for one default `ta::VWAP`, 1.999 for one `pine_hour` on a New York clock, 0.000 on the no-order floor and with orders. On Linux a TLS-descriptor call costs about 20 instructions: D2-C's 13 → 4 calls a bar was 260 → 80 instructions (Linux aarch64, GCC 13, callgrind). INT23: 2.00 a bar on the zoned probe 064, 0.08 on probe 008 | None for the TA and day-partition readers: "RULING: INT23 ACCEPTED" (ledger, 2026-09-24 23:30 UTC) reads "dlopen TLS 13->0/bar", and INT23's report names only the timezone and order-core residuals. This row is their record | Unpublished (AUDIT4's scratch) | A byte-identical lane that hands these readers the pump's block without a thread-local read. `ta::bar_context()` is script ABI, so its public declaration stays as it is |
| "audit-off mode", "LTO", "data-oriented layout" | Named in "OWNER DECISION 2026-09-25 00:20 Taipei" among the post-release structural items: "S1 bulk quiet bars, S2 per-strategy specialization, audit-off mode, LTO+PGO, data-oriented layout". No engine or campaign document defines, designs or measures the three | None | The owner decision | None | Struck from this inventory until a design note defines one of them |

Expand All @@ -194,9 +194,9 @@ them for the record only. "Linux aarch64" is the maintainers' Cortex-A725 host.
K4, the batch log pre-size. Since V19-B's step 6 (`08e71bae`) a run under the
default `NativeEventRetention::Window` keeps no driver point and retires its
journal at every script-bar boundary, so K4 has nothing to size.
`reserve_driver_log` (`src/native_execution_consumer.cpp:3243-3248`) returns
`reserve_driver_log` (`src/native_execution_consumer.cpp:3311-3316`) returns
unless the retention is Full, and `presize_logs`
(`src/native_execution_consumer.cpp:10278-10289`) sizes no journal under Window.
(`src/native_execution_consumer.cpp:10350-10361`) sizes no journal under Window.
`NativeRunSpec::event_retention` (`include/pineforge/native_run_spec.hpp:714`)
defaults to Window, and the Pine adapter declares
`NativeEventRetention::Window` (`src/source/pine_adapter.cpp:2996`). K4 still
Expand All @@ -210,7 +210,7 @@ or more, and the same run under Full holds 66,000 points in one block of
**PERF-L4 reads the receipts about three times a bar, not once.** PERF-L4's
brief asked it to "read the receipts ONCE per bar instead of three times". The
lane gated the read instead: `observe_terminal_receipts`
(`src/source/pine_adapter.cpp:6446`) returns at its receipt gate when no
(`src/source/pine_adapter.cpp:6448`) returns at its receipt gate when no
command lies above its cursor. `every_gate_is_taken_both_ways`
(`tests/test_adapter_quiet_bar.cpp:1061`) prints
`gate ReceiptRead asked 40996 quiet 33552 live 7444`, against `asked 13248`
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/contributing-llm.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ decision, and removing one is a regression:

**kernel** — the Pine-agnostic half of the engine: matching, fills, sizing,
margin, settlement, indicators, calendars. Buildable alone as
`PineForge::kernel` CMakeLists.txt:159 with `PINEFORGE_BUILD_SOURCE_LAYER`
`PineForge::kernel` CMakeLists.txt:160 with `PINEFORGE_BUILD_SOURCE_LAYER`
CMakeLists.txt:43 off.

**adapter** — `src/source/` and `src/compat/pine/`: the TradingView-parity
Expand Down
18 changes: 9 additions & 9 deletions docs/pages/native-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ spec fields.
The rich `run(bars, n, input_tf, script_tf, inputs, syminfo, overrides, …)`
overload (`engine.hpp:1800-1812`) is **not** refused as a source mutation: it
reaches `NativeExecutionConsumer::run_rich`
(`native_execution_consumer.cpp:10429-10471`), which admits the begin, checks the
(`native_execution_consumer.cpp:10501-10543`), which admits the begin, checks the
timeframe arguments against the spec, preflights and pumps the batch exactly
like the plain overload. `inputs` / `syminfo` / `overrides` are carried only as
`NativeBeginArgs` fields to `prepare_native_begin` — the overrides as the
Expand Down Expand Up @@ -638,13 +638,13 @@ a host reacts to its own execution and may submit again. A request born there,
mid-bar on a continuous segment, is eligible on the **remaining path suffix** of
that segment — the birth is admitted at the current cursor and the geometric
search then sees only the unconsumed suffix (`born_on_remaining_path`,
`native_execution_consumer.cpp:6439-6443`). Requests accepted before the
`native_execution_consumer.cpp:6507-6511`). Requests accepted before the
segment, and discrete points, keep the ordinary birth gate above.

`on_native_bar_open` fires at the modeled opening, before that point's matching
pass (`native_execution_consumer.cpp:7955-7957`). **Lookahead warning:** the
pass (`native_execution_consumer.cpp:8023-8025`). **Lookahead warning:** the
`Bar` it receives is the *complete* script bar — the consumer has already set
`engine.current_bar_ = open_view` (`native_execution_consumer.cpp:7428`), the
`engine.current_bar_ = open_view` (`native_execution_consumer.cpp:7496`), the
complete bar unless the spec asks for `NativeOpenBarView::OpenOnly` — so its
high, low and close are the finished bar's, not what is known at the open. A
host that must decide on open-only information reads
Expand Down Expand Up @@ -1496,7 +1496,7 @@ adapter answers `margin_check_allowed` with TradingView's scheduling — which
includes the post-exit re-size: when a priced bracket leg of the script bar
fills, the slice resting at that bar's adverse extreme was sized on the
pre-exit book, and the legacy broker cancelled and re-scheduled it there
(`margin_check_allowed` `pine_adapter.cpp:17830-17863`), so the adapter admits
(`margin_check_allowed` `pine_adapter.cpp:17832-17865`), so the adapter admits
the kernel's own point for that driver point while (and only while) a slice
rests —
`resolve_margin_requirement` with its ten-significant-digit money and
Expand Down Expand Up @@ -2112,7 +2112,7 @@ default, set while no run is active — because each row is a full
the live state, not the run's length: the closed rows enter through a running
digest). With the switch on,
one row follows each point, after the extremes that point just folded
(`record_script_report_point`, `native_execution_consumer.cpp:8841`), so
(`record_script_report_point`, `native_execution_consumer.cpp:8909`), so

```text
broker_state_hash_len == equity_curve_len == script_bars_processed
Expand Down Expand Up @@ -3086,7 +3086,7 @@ Only completed buckets are published, so this recipe has no lookahead by
construction. It is the same class the kernel's own subscription evaluator
aggregates with, and the one the kernel's `script_bucket_completions` query
feeds when the Pine scheduler asks how its input span buckets
(`TimeframeAggregator` `native_execution_consumer.cpp:8925`). What it does
(`TimeframeAggregator` `native_execution_consumer.cpp:8993`). What it does
**not** give you is what a
declared subscription does: an `authoritative_bars` feed, the `gaps` and
`lookahead` delivery rules, the lazy-seal chronology, a C spelling, and the
Expand Down Expand Up @@ -3150,7 +3150,7 @@ These are existing refusals, not implied future features:
- In-session gaps on a realtime stream, and in a `Canonical` stream warmup
- Source `calc_on_every_tick` / `calc_on_order_fills` enabled (the runner
rejects an explicit true override, and the Pine host refuses a stream begin
with `calc_on_order_fills`, `pine_strategy_host.cpp:291-294`). This is a
with `calc_on_order_fills`, `pine_strategy_host.cpp:295-298`). This is a
**source-route** refusal, not a limit on the native hooks: `on_native_tick`
and `on_native_applied` are delivered on a stream, and a native host's own
`NativeRunSpec::calculation` is accepted there, where `EveryModeledPoint`
Expand Down Expand Up @@ -4550,7 +4550,7 @@ Nor is there a build-level one. The two source sets are disjoint:
`PINEFORGE_SOURCE_LAYER_SOURCES` (`CMakeLists.txt:91`) holds all six
`src/compat/pine/` units and all twelve `src/source/` ones, and
`PINEFORGE_KERNEL_SOURCES` (`CMakeLists.txt:115`) holds the kernel's own
thirty-five, which are what `add_library` (`CMakeLists.txt:158`) compiles into
thirty-five, which are what `add_library` (`CMakeLists.txt:159`) compiles into
`pineforge_kernel`. `libpineforge.a` still carries both sets when
`PINEFORGE_BUILD_SOURCE_LAYER` is ON, which is the default; the kernel archive
exists either way. The installed-header closure is clean too, which the
Expand Down
Loading
Loading