Skip to content

R5 wave 3: sizing fixes + price rule, risk limits, neutral cleanups, C native host API, adapter COOF + sizing re-lowering (L3b/L9/L11/L13/R6/R2) - #264

Merged
luisleo526 merged 14 commits into
mainfrom
r5/int3-20260920
Sep 20, 2026
Merged

luisleo526 merged 14 commits into
mainfrom
r5/int3-20260920

Conversation

@luisleo526

Copy link
Copy Markdown
Collaborator

What

Thirteen commits, one per lane step, on main 04330d4:

Lane Commits Content
L3b 1 native sizing: fee-reserve scale, grid floor, ScopeFraction arithmetic + ScopeBasis, SizePrice { Resolved, Signal }, placement-time units into admission and host hooks, Sized for reversal shapes (three defects measured by the adapter re-lowering, fixed on native-only paths)
L9 1 generic risk limits (NativeRiskLimits: drawdown / intraday loss / consecutive loss days / fills per day; block or flatten-and-block; NativeRiskEvent; native_risk_state())
L11 6 neutral coupling extraction: opaque const void* overrides (no kernel header names source::), ta_compare_band.hpp (old header shimmed), feed-tolerance spellings, orphaned adapter essays moved to docs/pine-adapter-kernel-notes.md, two uncalled fill helpers deleted, neutral aliases for the pine_* utility API
R6 1 adapter re-lowering: calc_on_order_fills runs through the kernel's on_native_recalculate (BarCloseAndFills), Pine-only refusals in one admission predicate
L13 1 C native host API: 19 strategy_native_* symbols, C-compiled test, frozen-header test, C++ twin, hello_kernel_c example; in the kernel-only build
LF2 1 C API translates L9's risk event (PF_NATIVE_EVENT_RISK) and carries risk in the spec extension
R2 1 adapter re-lowering: default-quantity sizing (cash / percent-of-equity, market entries incl. reversals) resolves in the kernel via Sized{SignalOnTick, …}; one generic knob added (round → tick slippage → round)
fixup 1 guard the R2 witness's compile definitions for the kernel-only build (tests/ only, not parsed by the runner)

Epochs unchanged (v3 / v6 / v18). Adapter-side changes (R6, R2) are byte-identical on the whole corpus (312–314 engine_trades.csv identical, verify_corpus line identical) with differential tests pinning the previous resolutions as data.

Evidence

  • Per lane: supervisor fresh ci_verify release PASS (L3b 504, L9 504, L11 503 + kernel, R6 504, L13 release + kernel, R2 505).
  • Integrated tree: ctest 520 (+3 C-API tests after LF2), ci_verify release 29/29 and kernel 24/24 PASS (kernel re-run after the fixup).
  • Campaign sweep exp-r5-int3b-wave3-20260920 on tree e3cff134 (identical engine to this head — the fixup touches only tests/CMakeLists.txt, which the runner never parses with PINEFORGE_BUILD_TESTS=OFF): 4190 graded, coverage-lost 0, hard 0, band leavers 0, score 0 — no regression, net 0 (accepted).

Merge with rebase.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CSmYixVAp4NqWpBZxkrLPT

luisleo526 and others added 14 commits September 20, 2026 15:02
…zing-price rule, placement-time units, Sized reversals (R5 lane L3b)

Fixes three defects in L3's native-only sizing paths, measured by the
adapter re-lowering lane: reserve_percent_fee divided by 1 + fee_value
where fee_value is a percent (100x reserve); floor_to_quantity_grid used
floor(u/step + 1e-6) and could land above the quotient, then refuse the
raw quotient as off-grid (TermsUnresolved) — it is now the largest grid
multiple <= the quotient with the engine's own on-grid tolerance;
ScopeFraction computes units = scope * fraction once and gains
ScopeBasis { AtMatch, AtAcceptance } (the placement-time live basis, so
two 50 % siblings claim 5 + 5 under Gross).

Adds the generic pieces the adapter needs to re-lower its default sizing:
SizePrice { Resolved, Signal } on Sized (Signal = the decision-point price
at placement, slippage-adjusted for the side and rounded onto the run's
price grid when one is on, frozen at acceptance); Sized{AtAcceptance}
resolves its units before the host's terms/precommit hooks run and
carries them in the facts (one pass) and through placement admission
(directions, max_abs_units, max_open_lots, initial margin); Sized is
accepted for ReverseTo / CloseOpposite shapes like HostSized{Open}.

R2's differential harness now matches the adapter bit-for-bit on the
cash and percent-of-equity entries, the grid floor and the reversal;
corpus 312/312 engine_trades.csv identical to main; the adapter is
untouched. No epoch bump, no virtual, no PF_API change.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CSmYixVAp4NqWpBZxkrLPT
(cherry picked from commit c54855e004bac99de668262b67252271c9d9cf28)
native_run_spec_v3: `risk` (NativeRiskLimits: max_drawdown and
max_intraday_loss as NativeLossLimit{value, percent}, max_consecutive_loss_days,
max_fills_per_day, NativeRiskDay SessionDay | CalendarDayInTimezone,
NativeRiskAction BlockOpenings | FlattenAndBlock), folded into the
continuation hash only when present. Evaluated at each script-bar open,
after each applied drain and at the script-bar close calculation; the day
key is the script bar's own open on the session calendar (or the spec
timezone's calendar day). On breach openings are rejected with
MatchRejectReason::RiskLimit while reduces pass; FlattenAndBlock issues one
kernel-originated Flatten (RequestOrigin::KernelRisk, reusing L4's
liquidation request path); a NativeRiskEvent joins CommandEvent and
native_events; the block lasts to the end of the run (drawdown,
consecutive loss days) or of the day (intraday limits); native_risk_state()
exposes the counters. max_fills_per_day counts settled fills and blocks
the next admission — TradingView's cancel-pending behaviour stays an
adapter quirk. The adapter is untouched.

Tests: tests/test_native_risk_limits.cpp (neutrality without `risk` via
event count / report digest / native_run_spec_digest, every limit in
absolute and percent form, FlattenAndBlock, day-basis difference across a
midnight-crossing session, loss-day counting, fills-per-day reset, the
close-point breach, state accessor, twin against the adapter's risk rules).
ci_verify release PASS (504). Design: docs/design/native-feature-parity.md A.8.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CSmYixVAp4NqWpBZxkrLPT
(cherry picked from commit 0e13e75b2bcd6ddcb886fb12d231e3f9aa07a3c2)
…ane L11, 2.ii a)

The rich `BacktestEngine::run` overload and `IExecutionConsumer::run_rich`
declared their host-override parameter as `const source::StrategyOverrides*`,
so two public kernel headers had to forward-declare a type owned by the source
adapter. The kernel never dereferences that pointer: it forwards it verbatim to
`prepare_native_begin` as `NativeBeginArgs::overrides_opaque`, which has been
`const void*` since the v18 relocation, and the source host casts it back.

Declare the parameter `const void*` at every kernel site and drop both forward
declarations. Callers are unaffected: a `const source::StrategyOverrides*`
converts implicitly, so the adapter and generated code compile unchanged.

With no kernel signature naming the source layer, the include-independence
checker no longer needs its single whitelisted symbol; the exception and its
self-test control are removed, so the `nm` sweep over the kernel archive is now
unconditional.

No behaviour, numerics, hash, epoch or virtual changes.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 9837cd9f9bd0d92dc128a17c2a9bfc572e1c698a)
…ii b)

The 1e-10 absolute comparison band is generic engine numerics used by the TA
builtins alone - ta.percentrank (ta_misc.cpp), ta.mfi (ta_oscillators.cpp) and
ta.dmi (ta_volatility_trend.cpp). No execution translation unit includes it,
and nothing about it is a source-language feature, so it carried a `pine_`
name for no reason.

Move it to <pineforge/ta_compare_band.hpp> with neutral names
(`kFloatCompareBand`, `float_band_{eq,ne,gt,lt,ge,le}`) and switch the three TA
call sites. `pine_float_compare.hpp` stays as a thin shim whose `pine_float_*`
inlines forward to the new helpers, so generated code and the adapter compile
unchanged.

Same constants, same expressions, same results: every helper body is copied
verbatim. The band's TradingView calibration evidence moves with it, since it
is what justifies the constant.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 58e20b5126f1976d7d2da59926bfbec0f6655f92)
…, 2.ii c)

`NativeSlotLabelPolicy::LegacyTolerant` and `NativeLegacyTolerance` describe a
feed-shape policy - raw strictly-increasing provider labels, and the zero /
NaN-volume bars a tolerated batch feed carries - not source-language semantics.
Give both a neutral spelling and move every call site that is free to move:

  * `NativeSlotLabelPolicy::FeedTolerant`, with `LegacyTolerant = FeedTolerant`
    kept as a deprecated enumerator alias (same value 1, which is hashed);
  * `using NativeFeedTolerance = NativeLegacyTolerance;` - the same type;
  * `native_feed_tolerance_enabled()`, with `native_legacy_tolerance_enabled()`
    kept as an inline forward.

The consumer, the run-spec validator, the adapter and the native scheduler now
spell the neutral names. Enumerator values, the spec member names and the
continuation fold are untouched, so no hash and no C transport moves.

Residue: `enum class NativeLegacyTolerance`, the `NativeRunSpec::legacy_tolerance`
declared type and market_driver.cpp's `NativeSlotLabelPolicy::LegacyTolerant`
comparison are pinned verbatim by scripts/check_native_cpp_versions.py, which
this lane may not edit. Renaming the definition needs that guard's token list
(and scripts/test_native_cpp_versions.py) updated in the same change; it is
reported to the supervisor rather than loosened here.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit b0b93b89daa2283eb2e2d6c421a88bde8f2134c1)
…11, 2.ii d)

Three comment-only regions survived the declarations they documented:

  * engine.hpp:73-180, TradingView's ten-significant-digit money rule - the
    arithmetic is `tv_money_round` / `tv_money_floor_lot` in the adapter;
  * engine.hpp:506-568, the per-entry-id unclosed-quantity ledger and the
    same-bar `strategy.close` batching contract - that storage moved to
    PineStrategyHost with the v18 relocation;
  * engine.hpp:2649-2848, a 200-line run of paragraphs (KI-62 scratch,
    oca.reduce, finding-311 dormancy, round 7 family M, the fill kernels ...)
    whose private members are all gone;

plus the KI-62 block at engine_orders.cpp:198-208, whose helper bodies were
deleted long ago.

Every removed line is verified comment or blank: no declaration is touched.
The knowledge is not thrown away - it documents live adapter rules, so it moves
verbatim (indentation, campaign notes, `lab tv` pins and tape names intact) to
docs/pine-adapter-kernel-notes.md, next to the adapter that implements it.

Comments only: no code, numerics, hash or ABI change.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit cf0c76510b08421d4a49082cc702a212091bfb53)
…5 lane L11, 2.ii e)

`apply_limit_fill` and `reserve_percent_commission` have no caller anywhere in
src/ or tests/ - only comments mention them. Delete both.

Of the four helpers the design listed, the other two stay: `apply_slippage` is
called by tests/test_tv_fill_rounding{,_l4d}.cpp, and
`round_to_mintick_directional` is called both by `apply_slippage` and by five
tests, so neither is dead on this tree.

The arithmetic was one line each; the TradingView evidence attached to them is
the part worth keeping, so both comment blocks and both bodies move to
docs/pine-adapter-kernel-notes.md. The three comments elsewhere in engine.hpp
that named the deleted helpers are reworded to point there.

No caller, therefore no behaviour, numerics or hash change; both were protected
inline members, so no exported symbol moves.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit d0f28219c39da5fd8b870b52cc7f93564401a960)
…1, 2.ii h)

session_time.hpp, str_utils.hpp, math.hpp and the private timezone.hpp export
generic helpers - timeframe bucket arithmetic, local-calendar decomposition,
session-window predicates, string formatting, a deterministic PRNG and a
process TZ guard - under `pine_` names. Nothing about any of them is
source-language specific.

Rename the definitions to neutral names and keep every `pine_*` spelling as an
exact inline forward in the same header, so generated code and the adapter
compile unchanged:

  pine_time / pine_time_close    -> timeframe_time / timeframe_time_close
  pine_hour ... pine_weekofyear  -> local_hour ... local_weekofyear
  pine_time_tradingday           -> session_trading_day_open_ms
  pine_session_is{,pre,post}market -> session_in_{market,premarket,postmarket}
  pine_str_*                     -> str_format, str_format_time, str_match,
                                    str_split, str_tostring
  pine_random                    -> deterministic_random
  namespace pine_tz              -> namespace tz_util (pine_tz kept as an alias)

Kernel-internal callers (engine_run.cpp, engine_metrics.cpp,
native_calendar.cpp, session_time.cpp, str_utils.cpp, timezone.cpp) move to the
neutral names. The PF_PINE_TIME_HAS_* feature macros, every signature, default
argument and overload set are unchanged, so call resolution and results are
identical.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 7e7bfc11214d7e1882462bf0bf1f19cc92d3c324)
…calculation (R5 lane R6)

The Pine host selects NativeCalculationTrigger::BarCloseAndFills with
max_recalculations_per_point = TradingView's loop-guard literal and
implements on_native_recalculate: the kernel now schedules one
recalculation per applied event from its notification drain (the same
applied-work(e) -> recalc(e) order the adapter ran inline inside
on_native_applied), and the host performs Pine's part — language-state
snapshot / recalc / commit, the complete-script-bar view generated code
expects, the first-open execute_current chain, the intraday-loss direct
price. Pine-only refusals (POOC terminal close fills, the grouped
same-entry stop sibling, replayed ordinals) live in one pure admission
predicate, coof_recalculation_due(), with no new state and no hash fold;
TradingView's waypoint deferral of requests born in a recalculation stays
(measured: born_on_remaining_path does not reproduce it). The kernel is
untouched.

Byte-identity: 312 corpus engine_trades.csv identical to main b0cec54 with
the regenerated corpus; tests/test_adapter_coof_relower.cpp (56 checks,
adapter-before values harvested from the b0cec54 library); ci_verify
release PASS (504).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CSmYixVAp4NqWpBZxkrLPT
(cherry picked from commit fd99d424831a4029598b19682b1043d78cafec8b)
A versioned C API so a non-C++ host can drive the kernel: 19 new PF_API
symbols declared in include/pineforge/native_c_api.h (included by
pineforge.h) and implemented in the new kernel TU src/native_c_host.cpp
(CCallbackHost, a final NativeStrategyHost forwarding each virtual to a
size-prefixed callback table): host create / free, batch run, report
free, submit / replace / cancel / cancel_all / execute_current, position,
working requests, events polling, state, cohort open / add / remove, and
strategy_configure_native_ext_v1 carrying the run-spec fields L2-L8 added
(report policy, price grid, calculation, margin, subscriptions, open bar
view). The C request is translated field by field (never cast); unknown
tags / sizes are refused with documented codes; a non-zero callback
return latches CallbackException. Streaming reuses the existing
strategy_stream_* symbols. The existing 57 runtime symbols are untouched;
check_c_abi_runtime.py counts the new ones.

Tests: tests/test_native_c_api.c (genuinely C-compiled) + the C++ twin
(the C market host equals the C++ NativeStrategyHost on the same bars;
Sized sizes like C++ Sized; replace / cancel / execute_current
round-trips; callback failure latch; unknown struct_size refused; events
polling stable), tests/test_native_c_api_frozen_header.cpp (a C fixture
against the frozen v1 header under tests/fixtures/native_c_api/v1),
examples/native/hello_kernel_c.c. ci_verify release and kernel PASS.
Design: docs/design/native-feature-parity.md §2.iii.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CSmYixVAp4NqWpBZxkrLPT
(cherry picked from commit ec9c551867505c346272b14b404e8e6bd63b922f)
L9 added NativeRiskEvent as the nineteenth CommandEvent alternative, which
left the L13 C host refusing to compile: `std::variant_size_v<CommandEvent>
== 18` and the event visitor's `static_assert(!sizeof(T), "untranslated
native command event")` both fired.

The new alternative gets PF_NATIVE_EVENT_RISK = 21 — past the driver-point
and account observations rather than taking 19 and renumbering them, so a
reader compiled before it skips it by tag and the frozen v1 fixture stays
byte-identical. Translation is field by field, into the flat tagged POD the
other kinds already use: `reason` is a pf_native_risk_limit_e, `price` the
observed value, `raw_price` the limit it reached (already resolved against
its basis equity when the limit was a percent), `cycle_before` the risk day
on the spec's own day basis, `successor` the cursor's matching-point
ordinal, and the cursor fields are set. The event names no request — the
block it opens is an account fact — so `incarnation` stays 0. No new
callback: pf_native_callbacks_v1 does not move, and the event history is the
delivery path.

Configuring the block travels in pf_native_run_spec_ext_v1 under
PF_NATIVE_SPEC_EXT_RISK, as the one additive tail in this header: ten
uint32 and two double fields (the two loss limits as value plus percent
flag, the two counts, the day basis, the action, each limit opt-in through
its own has_ flag) appended past `reserved0`. The struct therefore has two
published lengths and strategy_configure_native_ext_v1 accepts either —
PF_NATIVE_RUN_SPEC_EXT_V1_BASE_SIZE, defined as the offset of the first
appended field so it stays right on every target, and the current sizeof.
A base-layout caller that sets the risk bit it has no fields for is refused
PF_NATIVE_E_STRUCT; any third length is refused outright. No PF_API symbol
is added or removed, so check_c_abi_runtime.py's inventory is unchanged.

Tests: tests/test_native_c_api.c gains check_risk_event() — a limit of one
applied fill per day, driven through strategy_configure_native_ext_v1, whose
breach delivers exactly one PF_NATIVE_EVENT_RISK through
strategy_native_events_v1 with kind MAX_FILLS_PER_DAY, observed 1, limit 1,
day 0, a matching point and no request, while the later openings are blocked
(one APPLIED event) — plus the two base-layout refusal rows;
tests/test_native_c_api_frozen_header.cpp configures a second host from the
frozen v1 copy of the ext struct, so the additive acceptance is executed, and
pins that every tag the frozen reader meets is one its header names.
Fail-before: both new test bodies fail to compile against the pre-change
header, and the tree itself did not build.

cmake --build build-on: exit 0. ctest -j6: 100% (523/523).
check_c_abi_runtime.py: PASS. ci_preflight: PASS.
ci_verify release: PASS (509). ci_verify kernel: PASS (156).

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…R5 lane R2)

The Pine adapter emits native_order::Sized for its declaration-level
default quantity — default_qty_type cash and percent_of_equity on market
entries frozen at the command, including reversals — with
{SizePrice::SignalOnTick, price grid HalfUp at mintick, tick slippage,
the percentage fee reserve, SizeTime::AtAcceptance}; the kernel resolves
units = cash / (signal price * point value * fx) and feeds the frozen
quantity to placement admission in one pass, and the adapter's override
keeps only TradingView's quirks (the money band, affordability, the lot
floor). SizePrice::SignalOnTick is the one generic kernel addition: the
signal price is rounded onto the grid before tick slippage and rounded
again, which keeps the price on the grid (ruling: generic; default
Resolved unchanged). The 25 remaining HostSized emission sites are the
host-only shapes: percent-of-position exits with sibling reservations
(ScopeFraction cannot name Pine's quantizer), the fill-time resize whose
divisor is the adapter's own fill price, deferred and re-armed entries,
and the guard downgrade when the core's price arithmetic would not match.

Byte-identity: 314 corpus engine_trades.csv identical to L3b's tree with
the regenerated corpus; tests/test_adapter_sizing_relower.cpp (163
checks: cash, percent-of-equity with fee and grid, sizing point, the
reversal, placement admission — each pinning the previous resolution as
data); ci_verify release PASS (505).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CSmYixVAp4NqWpBZxkrLPT
(cherry picked from commit d18bc49a44d246eabdcd814413d2482ce93f48be)
… build

The adapter-bound test_adapter_sizing_relower is not registered when
PINEFORGE_BUILD_SOURCE_LAYER is OFF, so its target_compile_definitions
must be conditional (ci_verify kernel: configure failed).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CSmYixVAp4NqWpBZxkrLPT
@luisleo526
luisleo526 merged commit 3f6fd57 into main Sep 20, 2026
11 checks passed
@luisleo526
luisleo526 deleted the r5/int3-20260920 branch October 5, 2026 02:43
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