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
8 changes: 4 additions & 4 deletions docs/Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ WARN_LOGFILE = site/doxygen-warnings.log
# RECURSIVE reaches include/pineforge/source and include/pineforge/compat;
# EXCLUDE below keeps the generated and vendored trees out.
#
# pineforge.h is named FIRST, before the directory that also contains it, for
# the reason engine.hpp:37 states about C translation units: engine.hpp defines
# PINEFORGE_NO_STRATEGY_DECLS, and Doxygen's preprocessor carries a define
# across the whole run. Parsed after engine.hpp, pineforge.h loses
# pineforge.h is named FIRST, before the directory that also contains it, because
# engine.hpp defines `PINEFORGE_NO_STRATEGY_DECLS` at engine.hpp:44, and Doxygen's
# preprocessor carries that define across the whole run. Parsed after engine.hpp,
# pineforge.h loses
# strategy_create / strategy_free / run_backtest / run_backtest_full /
# report_free, and the ~50 `#run_backtest` references in the headers and the
# pages resolve to nothing. Order is the fix, exactly as it is in src/c_abi.cpp.
Expand Down
46 changes: 23 additions & 23 deletions docs/adr/0001-kernel-adapter-boundary.md

Large diffs are not rendered by default.

72 changes: 36 additions & 36 deletions docs/design/native-feature-parity.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/native-refactor-progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ them for the record only. "Linux aarch64" is the maintainers' Cortex-A725 host.
| PGO | Profile-guided optimisation of the library objects: an instrumented build, a training run over the bench strategies, a `-fprofile-use` rebuild and the identity gates, per compiler and target, with a staleness gate on the profile | LAYOUT1, Linux aarch64 (Cortex-A725), GCC 13.3, `perf stat`, trained on 86 slots and timed on the 14 held-out probes: 14.5 % fewer cycles, CPU time 0.865, 7 % fewer instructions, 26 % fewer L1i refills, 36 % fewer front-end stalls, layout spread 9.0 % → 5.1 %, fingerprints identical | "R5 LAYOUT1 DONE + ACCEPTED 2026-09-24 21:45 Taipei": "PGO APPROVED as a lane (PGO1) scheduled LAST in the perf wave after the code lanes (profile staleness)"; the supervisor's cap: "PGO folds into the release lane"; the owner: "release with PGO in the release build"; TRIAGE1 §D | Unpublished. The tree carries only LAYOUT1's `-falign-functions=64` | The release lane. BENCH4 timed the standard Release build without PGO, and neither BENCH4's nor REL10-ENGINE's brief names it |
| 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 chunked `trades_` store (L3) | Closed trades in chunks rather than one vector; `trades_` (`include/pineforge/engine.hpp:547`) 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: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 Down
20 changes: 10 additions & 10 deletions docs/pages/native-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ byte-identical** to the spec. Conflicting values are a preflight refusal:
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
overload (`engine.hpp:1801-1813`) is **not** refused as a source mutation: it
reaches `NativeExecutionConsumer::run_rich`
(`native_execution_consumer.cpp:10501-10543`), which admits the begin, checks the
timeframe arguments against the spec, preflights and pumps the batch exactly
Expand Down Expand Up @@ -2081,10 +2081,10 @@ host that marks its own equity keeps this default and owns the whole series.

**The scalar extremes are not part of that bargain.** `max_drawdown_`,
`max_runup_` and `max_contracts_held_all_` / `_long_` / `_short_` — read back
through `max_drawdown_percent()` (`engine.hpp:1507`), `max_runup_percent()`
(`engine.hpp:972`) and `max_contracts_held_all/long/short()`
(`max_contracts_held_all` `engine.hpp:1852-1854`) — are a property of the RUN: what it drew down, what
it ran up, the most it ever held. The kernel folds them (`update_equity_extremes`, `engine.hpp:1362`) at every script
through `max_drawdown_percent()` (`engine.hpp:1508`), `max_runup_percent()`
(`engine.hpp:973`) and `max_contracts_held_all/long/short()`
(`max_contracts_held_all` `engine.hpp:1853-1855`) — are a property of the RUN: what it drew down, what
it ran up, the most it ever held. The kernel folds them (`update_equity_extremes`, `engine.hpp:1363`) at every script
calculation under `HostRecorded` and `KernelRecorded`. Under
`KernelRecordedAtHostMarks`, it folds them at the host-mark callbacks instead, so
the host controls the cadence. A `HostRecorded` host reads them truthfully without
Expand All @@ -2106,7 +2106,7 @@ drawdown/run-up walk, and metrics computed over a real series.
The per-bar **broker-state hash** is a row of that same report, so
`KernelRecorded` records it too. It stays behind the recording switch it
always had — `set_broker_state_hash_recording(true)`
(`engine.hpp:2143`; C: `strategy_set_broker_state_hash_recording`), off by
(`engine.hpp:2144`; C: `strategy_set_broker_state_hash_recording`), off by
default, set while no run is active — because each row is a full
`broker_state_hash()` over the lots and the closed rows (since v19 a row costs
the live state, not the run's length: the closed rows enter through a running
Expand Down Expand Up @@ -2238,7 +2238,7 @@ bare host's own rows get theirs: the margin model's liquidation books
under the ticket the model or the run named. A host running its own forced
close states the cause on the row it produced.

`closed_trade_close_cause(i)` (`engine.hpp:1849`) is the C++ read and
`closed_trade_close_cause(i)` (`engine.hpp:1850`) is the C++ read and
`strategy_closed_trade_close_cause` (`pineforge.h:1309`) the C one, with the
same numbering: `-1` for a bad index or a NULL handle, `0` UNKNOWN, `1`
SCRIPT, `2` BRACKET, `3` MARGIN_CALL, `4` INTRADAY_LOSS_CAP, `5`
Expand Down Expand Up @@ -2270,11 +2270,11 @@ struct RegimeHost : pineforge::NativeStrategyHost {
};
```

- `hash_host_extension` (`engine.hpp:405`, protected virtual on
- `hash_host_extension` (`engine.hpp:406`, protected virtual on
`BacktestEngine`) is called exactly once per hash, last, after the kernel's
fold. What it writes is part of the scalar `broker_state_hash()`, of every
per-bar row a `KernelRecorded` run records, and of `stream_state_hash()`.
- `BrokerStateHashSink` (`engine.hpp:350`) is a complete public type: FNV-1a
- `BrokerStateHashSink` (`engine.hpp:351`) is a complete public type: FNV-1a
over a canonical byte spelling — `d` (a double; `-0.0` folds as `0.0`, every
NaN as one quiet NaN), `i`, `u`, `b`, `s` (length, then bytes), `bytes`.
- An override **replaces** the default. A host that overrides nothing folds
Expand Down Expand Up @@ -3093,7 +3093,7 @@ declared subscription does: an `authoritative_bars` feed, the `gaps` and
series' place in the run's continuous identity. Prefer `subscriptions` unless
you want none of those.

`set_native_security_feed` (`engine.hpp:1731`) is the host ingress for
`set_native_security_feed` (`engine.hpp:1732`) is the host ingress for
`authoritative_bars` installed before a run — see *Authoritative bars* above —
and not a way to register a series: registration is
`NativeRunSpec::subscriptions` or `declare_timeframe_subscriptions`. In-run the
Expand Down
Loading
Loading