Repository navigation
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
Conversation
…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
…cts unsigned long long* -> uint64_t*) Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01CSmYixVAp4NqWpBZxkrLPT
This was referenced Oct 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Thirteen commits, one per lane step, on main
04330d4:ScopeFractionarithmetic +ScopeBasis,SizePrice { Resolved, Signal }, placement-time units into admission and host hooks,Sizedfor reversal shapes (three defects measured by the adapter re-lowering, fixed on native-only paths)NativeRiskLimits: drawdown / intraday loss / consecutive loss days / fills per day; block or flatten-and-block;NativeRiskEvent;native_risk_state())const void*overrides (no kernel header namessource::),ta_compare_band.hpp(old header shimmed), feed-tolerance spellings, orphaned adapter essays moved todocs/pine-adapter-kernel-notes.md, two uncalled fill helpers deleted, neutral aliases for thepine_*utility APIcalc_on_order_fillsruns through the kernel'son_native_recalculate(BarCloseAndFills), Pine-only refusals in one admission predicatestrategy_native_*symbols, C-compiled test, frozen-header test, C++ twin,hello_kernel_cexample; in the kernel-only buildPF_NATIVE_EVENT_RISK) and carriesriskin the spec extensionSized{SignalOnTick, …}; one generic knob added (round → tick slippage → round)Epochs unchanged (v3 / v6 / v18). Adapter-side changes (R6, R2) are byte-identical on the whole corpus (312–314
engine_trades.csvidentical,verify_corpusline identical) with differential tests pinning the previous resolutions as data.Evidence
ci_verify releasePASS (L3b 504, L9 504, L11 503 + kernel, R6 504, L13 release + kernel, R2 505).ci_verify release29/29 andkernel24/24 PASS (kernel re-run after the fixup).exp-r5-int3b-wave3-20260920on treee3cff134(identical engine to this head — the fixup touches onlytests/CMakeLists.txt, which the runner never parses withPINEFORGE_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