|
PineForge v0.13.1-379-g9b50973
Deterministic PineScript v6 backtest runtime — C ABI reference
|
pineforge-engine (this repo) and pineforge-codegen (separate)README.md "Architecture: kernel vs. parity"; docs/pages/native-engine.md; docs/design/native-feature-parity.md (gap inventory + lane roadmap)path:line in the grammar scripts/check_doc_anchors.py enforces, so a citation that stops naming its symbol fails a gate rather than rotting quietly.pineforge-engine grew up serving one job — reproducing TradingView's Pine strategy results bit-for-bit (the parity campaign) — so it looks like a "Pine engine." The intended architecture is broader: a generic backtest + forward-execution kernel with TradingView parity as an optional layer. The goal this ADR commits to: a user can eventually build on the kernel without PineScript — a C++ strategy with order matching, sizing, fees, margin/settlement, the magnifier, indicators, higher/lower-timeframe data and session/time math, Pine parity opt-in.
This ADR records that boundary, the rules keeping new work aligned to it, and where the code stands against it. Earlier drafts were written from memory and overstated the separation; later ones went stale in the other direction, denying capabilities that had since landed. Every claim here is read off the code, and three checkers hold the parts of it that can be mechanised: scripts/check_kernel_residuals.py (the residual-name tables), scripts/check_native_feature_rulings.py (the ruling table) and scripts/check_doc_anchors.py (the citations).
Three layers, two of them meant to be optional:
pineforge::NativeStrategyHost (native_host.hpp:778), an abstract subclass of BacktestEngine. Kernel translation units are named, not inferred: they are the thirty-five members of PINEFORGE_KERNEL_SOURCES (CMakeLists.txt:113) — the engine_*, native_*, ta_*, market_driver, magnifier, math, matrix, session_time, timeframe, timezone, str_utils, c_abi, native_c_host, reservation_expansion (CMakeLists.txt:123) and pending_order_mirror (CMakeLists.txt:129) units — plus the headers directly under include/pineforge/. That list is what add_library (CMakeLists.txt:153) compiles into pineforge_kernel / PineForge::kernel, and it and PINEFORGE_SOURCE_LAYER_SOURCES are disjoint: no file is in both, and none is outside both. Two stems exist twice; the kernel copy is the generic half — ReservationExpansion (src/reservation_expansion.cpp:10) and include/pineforge/order_birth.hpp — the TradingView-selection halves keep the stem under compat/pine/ (include/pineforge/compat/pine/order_birth.hpp:3-7 includes the kernel header). The market-admission stem is not one of them: pineforge::admission — the observation journal whose Configuration fields are strategy() declaration parameters and whose events are TradingView admission reviews — is source-layer state (include/pineforge/source/market_admission.hpp, src/source/market_admission.cpp; no kernel translation unit ever consumed it), and src/compat/pine/market_admission.cpp holds the scope predicates over it, inside the source-layer set.PINEFORGE_SOURCE_LAYER_SOURCES set (CMakeLists.txt:91): the eleven src/source/ units and all six src/compat/pine/ units, with headers under include/pineforge/source/ and include/pineforge/compat/pine/. It maps Pine execution semantics onto the kernel. -DPINEFORGE_BUILD_SOURCE_LAYER=OFF compiles none of them.pineforge-codegen repo). Translates a Pine v6 script into a C++ GeneratedStrategy and emits the code that attaches the adapter. Translation only; no execution runtime.Why the adapter stays in the engine, not codegen. It is shared runtime — one execution semantics for every strategy — while codegen emits per-strategy code: emitting it per strategy duplicates it, shipping it as a codegen library is "in the engine" renamed. The R4 lowering put this runtime on the kernel; it did not move it to codegen.
Subclass NativeStrategyHost: a zero-argument host (native_host.hpp:778) carrying the callbacks, the answering hooks and the request API. The CapAttachment constructor belongs to the adapter class PineStrategyHost (pine_strategy_host.hpp:241), not here. The whole surface, field by field, is docs/pages/native-engine.md; this section is the boundary's summary of it.
Unconfigured → Ready → Running → Completed | Failed (native_host.hpp:20-26); phases Batch | Warmup | Realtime (native_host.hpp:28-32) — one path runs the batch, then continues live. Configure with configure_native(spec) (native_host.hpp:1056), then feed.on_native_bar is the pure-virtual script-bar calculation (native_host.hpp:825); also on_native_input (native_host.hpp:808), on_native_tick (native_host.hpp:811), on_native_timeframe_bar (native_host.hpp:818), on_native_bar_open — at the modeled opening, before its matching pass (native_host.hpp:821, on_native_bar_open native_execution_consumer.cpp:6083-6085) — on_native_recalculate for every calculation of the run (native_host.hpp:844), on_native_sub_bar (native_host.hpp:859) and post-fill on_native_applied (native_host.hpp:869). on_bar is final (native_host.hpp:790).submit / replace / cancel / submit_market / replace_market (native_host.hpp:1089-1094), plus cancel_all (native_host.hpp:1122) and cancel_where (native_host.hpp:1127). A request carries one of five triggers — Market, Limit (with a generic market-if-touched fill_through, native_order.hpp:239-242), Stop, StopLimit, Trail (Trigger native_order.hpp:271); one of six intents — Flatten, Reduce, Transact, ReverseTo, HostSized and the kernel-resolved Sized (OrderIntent native_order.hpp:229); one of five owner kinds — Independent, WaitForApplied, BindOpening, BindOpenings, BindCohort (Owner native_order.hpp:413); an OCA-style group with cancel-or-reduce effect; a capacity, ImmediateRemaining or PointBudget; and a TriggerAnchor (native_order.hpp:311) that is either the absolute level or FromOwnerFill, the anchored bracket child.Sized (native_order.hpp:164) is resolved by the kernel from a cash or equity-fraction basis, so a bare host needs no override at all, while HostSized hands the units to the host — and an unresolved HostSized whose resolve_execution_terms (native_host.hpp:878) returns no units is a TermsUnresolved rejection (TermsUnresolved native_execution_consumer.cpp:4313).on_native_applied (native_host.hpp:869) — notifications drain FIFO after the outer callback returns — and/or poll native_events(after_ordinal) (native_host.hpp:1181). Do not implement NativeExecutionConsumer: it is the kernel's internal matcher, declared final (src/native_execution_consumer.hpp:17) and bound by the host.NativeStrategyHost:| What | Surface |
|---|---|
| Run state | native_state() → NativeStateView — kind, phase, NativeCompletion BatchComplete / StreamEnded, failure, high water (native_host.hpp:50, :211-219, :34-37) |
| Failure model | NativeFailureCode (native_host.hpp:63-77); Failed is latched, nothing resumes. A throwing callback latches CallbackException (CallbackException native_execution_consumer.cpp:4286) |
| Cohorts | cohort_open (native_host.hpp:1137) / cohort_add (:1140) / cohort_remove (:1143) — the handle a BindCohort owner names |
| Mid-callback execution | current_execution_point (native_host.hpp:988), inspect_current_execution (native_host.hpp:1000) / execute_current (native_host.hpp:1005) |
| Margin seams | four, not one: the run spec's own margin model (NativeMarginModel native_run_spec.hpp:239) with its three hooks — resolve_margin_requirement (native_host.hpp:906), margin_check_allowed (native_host.hpp:918), resolve_margin_call_units (native_host.hpp:925) — and validate_execution_precommit (native_host.hpp:890), whose AdmitWithHostMargin verdict hands the opening check to the host |
| Risk seam | NativeRiskLimits (native_run_spec.hpp:309) with native_risk_state() (native_host.hpp:1178); the breach appends a NativeRiskEvent to the command history |
| State reads | physical_position() (native_host.hpp:1147) / native_marked_equity(mark) (native_host.hpp:1156) / native_open_lots(mark) (native_host.hpp:1152), trail_state(handle) (native_host.hpp:993), and the rest of the query surface in docs/pages/native-engine.md, "Reading the run back" |
| Lookahead hazard | the Bar given to on_native_bar_open is the complete script bar; current_partial_bar() (native_host.hpp:974) is the lookahead-free bar so far, and NativeOpenBarView::OpenOnly (native_run_spec.hpp:108) masks that one callback |
NativeRunSpec (native_run_spec.hpp:521) owns instrument and clock facts, fees (NativeFeeKind native_run_spec.hpp:19), a scalar account FX, close timing, the quantity grid, direction and size caps, the intrabar path — and, each opt-in and each folded into the continuation identity only when set: the instrument price grid (NativePriceGrid native_run_spec.hpp:117), the per-side broker margin model with its liquidation policy (NativeMarginModel native_run_spec.hpp:239), the account risk block (NativeRiskLimits native_run_spec.hpp:309), the report policy (NativeReportPolicy native_run_spec.hpp:60), calculation timing (NativeCalculationTrigger native_run_spec.hpp:93) and the open-bar view, declared higher-timeframe series (NativeTimeframeSubscription native_run_spec.hpp:481) and the auxiliary finer feed (NativeAuxiliaryFeed native_run_spec.hpp:510). initial_margin_fraction (native_run_spec.hpp:565) remains the one-scalar admission-only spelling and is mutually exclusive with margin. Percent and cash sizing are a request value, not a spec one (Sized native_order.hpp:164). What the spec still owns none of is source strategy policy: the field comments say so, and scripts/check_adapter_spec_shadowing.py holds the adapter to the fields it actually needs.run(bars, n) (engine.hpp:2254), the timeframe run (engine.hpp:2256), the rich begin (engine.hpp:2329), or stream_begin (engine.hpp:2300) / stream_push_bar (engine.hpp:2306) / stream_push_tick(s) / stream_advance_time / stream_end (engine.hpp:2310). market_driver.hpp:61 is driver types, not a feed API; read examples/native/native_market_strategy.cpp for a host that drives both a batch and a stream.magnifier.hpp) reconstructs intrabar fills. request.security-style series are reachable from a bare host, as declared series of the run's own symbol: NativeRunSpec::subscriptions, or declare_timeframe_subscriptions (native_host.hpp:1025) from inside on_native_run_begin, with on_native_timeframe_bar (native_host.hpp:818) and native_series_bar (native_host.hpp:1012) reading them back, and NativeAuxiliaryFeed + NativeSeriesSource::AuxiliaryFeed for a series finer than the input. The kernel owns the aggregation, the lookahead/gaps delivery rules and the lazy-seal chronology; TradingView's request.security semantics are not in it. prepare_native_security_feeds stays protected (engine.hpp:2164) and configure_security_evaluators stays an empty virtual (engine.hpp:1813) — a host does not register an evaluator by hand — while set_native_security_feed (engine.hpp:2275) is the pre-run door for a series' authoritative bars and still throws in-run (guard_native_mutation engine_aux_security.cpp:78). Indicators are ta_*.cpp, session/calendar session_time.cpp / native_calendar.cpp, FX configure_native_fx_curve (native_host.hpp:1062).fill_report (engine.hpp:2388, src/engine_report.cpp:40) gives a bare host trade stats, and the equity curve is a run-spec choice rather than a host chore. NativeReportPolicy::HostRecorded (the default) leaves the series to the host, whose record_equity_point / update_equity_extremes remain protected (update_equity_extremes engine.hpp:1853, record_equity_point engine.hpp:1871); KernelRecorded has the consumer mark one point per script calculation, and KernelRecordedAtHostMarks records the same series at the host's own marks. report_open_position_at_end books the still-open position as a mark-to-market row under KernelRecorded. Under HostRecorded an unrecorded curve still degenerates — compute_equity_stats answers its all-NaN value with no error (src/engine_metrics.cpp:153, consumed at src/engine_report.cpp:119-139) — which is why the policy exists.native_continuation_hash() (native_host.hpp:1199) returns the consumer's event/continuation hash. broker_state_hash() (engine.hpp:2592) folds that hash with position and lot state under the pinned domain pineforge-broker-state/v18 (engine_state_hash.cpp:32); it is the one exported to C (c_abi.cpp:436). A host folds its own durable state into it through hash_host_extension (engine.hpp:379), whose deprecated spelling hash_source_extension (engine.hpp:384) the generic default forwards to. Per-bar rows of the same hash are recorded under KernelRecorded with the recording switch on.src/source/ + src/compat/pine/ reproduce TradingView's Pine broker by translating each TV rule into kernel driving — never by special-casing the kernel for a specific script. PineExecutionAdapter / PineStrategyHost sit between the generated strategy.* calls and the kernel and own these quirks, each at its site:
PineStrategyHost overrides resolve_execution_terms (pine_strategy_host.cpp:609) for TV sizing and fill spelling; validate_execution_precommit (pine_strategy_host.cpp:634), whose validate_precommit (pine_adapter.cpp:11323) returns AdmitWithHostMargin (pine_adapter.cpp:11568) to own the opening margin decision; owns_lot_excursions() = true (pine_strategy_host.hpp:283-284) with closed_lot_excursion (pine_strategy_host.cpp:719), so MFE/MAE are measured on TV tick-quantized prices; and on_native_tick (pine_strategy_host.cpp:358) / on_native_applied (pine_strategy_host.cpp:448) for calc_on_order_fills re-entry. process_orders_on_close becomes NativeCloseExecution::AfterCalculation in the projected spec (pine_adapter.cpp:1471-1472); calc cadence and language publication are PineScheduler's (pine_scheduler.hpp:20).pine_adapter.hpp:1219-1232); the retained parent-before-child ordering of live handles — an exactly-shaped entry and its from_entry exit, re-created after a named cancel, on a flat book under process_orders_on_close (src/compat/pine/order_priority.cpp:13-59, applied by pine_adapter.cpp:821-880). Kernel priority is acceptance/incarnation order.src/compat/pine/exit_activation.cpp:42-60) and historical birth reach (src/compat/pine/order_birth.cpp:5-14).pine_adapter.cpp:7655-7660); the half-tick trigger threshold (pine_adapter.cpp:328-340) and raw-vs-booked fill spelling, behind the adapter's terms seam (pine_adapter.cpp:9310).pine_adapter.cpp:396-403, twin pine_policy_support.hpp:9-15).strategy.close callsite batching, two-call provenance and the entry-id ledger (pine_adapter.hpp:1247-1258); the POOC reservation-growth population predicate (src/compat/pine/reservation_expansion.cpp:9-20).margin_check_allowed (pine_adapter.cpp:12303) for the scheduling, resolve_margin_requirement (pine_adapter.cpp:12336) for the ten-significant-digit money, and resolve_margin_call_units for the lot-floored 4x restore. What has no kernel check point at all stays adapter-side: the process_orders_on_close chronology exception (non_pooc_commissioned_short pine_adapter.cpp:14882), the account-currency FX rollover slice, the pre-open admission slice and the 1x-long money call — plus the TV admission scopes (src/compat/pine/market_admission.cpp:33-46) and the review fold they feed (:67-72, :79-136).calc_on_order_fills strategy projects NativeCalculationTrigger::BarCloseAndFills with TradingView's guard literal as max_recalculations_per_point. What stays are the specifics COOF adds on top — the language-state snapshot/restore around a recalculation, the waypoint-only refill deferral (next_source_path_waypoint pine_adapter.cpp:6635), the first-open execution chain and its own loop guard (pine_scheduler_native.cpp:650), and the two fills Pine refuses to recalculate on (suppress_grouped_stop_recalc pine_adapter.cpp:3985).pine_language_state.hpp:12-24); live-tail / probe-suppress overrides (pine_strategy_host.cpp:270-272).Each rule matches a specific TradingView behavior and is cited in source as ab9714be FILE:LINE. ab9714be is a commit of this repository (#253, the parent of the adapter-lowering slice) — the last tree holding the retired legacy Pine loop, so a citation is read with git show ab9714be:src/source/pine_fills.cpp, not against TradingView source. Of the 270 citations, 264 are in src/source/ and its headers; 6 sit in kernel files (engine_execution.cpp twice, engine_consumer.cpp, market_driver.cpp, native_execution_consumer.cpp and engine.hpp once each).
add_library (CMakeLists.txt:153) builds pineforge_kernel / PineForge::kernel from PINEFORGE_KERNEL_SOURCES alone, with the same flags pineforge uses, in both configurations. libpineforge.a still carries the adapter beside the kernel by default; -DPINEFORGE_BUILD_SOURCE_LAYER=OFF (CMakeLists.txt:44) compiles no source-layer translation unit, installs no include/pineforge/source/ or compat/ header, and drops every Pine-bound target and every test TU that reaches one. scripts/ci_verify.py kernel is that configuration's profile, with a row floor (KERNEL_MIN_TESTS), and scripts/check_native_include_independence.py --kernel-archive proves the archive links standalone with nm over its defined and undefined symbols and no whitelisted symbol. Every src/compat/pine/ unit — market_admission.cpp included — is inside PINEFORGE_SOURCE_LAYER_SOURCES (CMakeLists.txt:91).engine_aux_security.cpp:5-12, for instance, pulls only engine_internal.hpp, ta.hpp and std headers. No kernel signature names an adapter type either: the rich begin bridge takes the host overrides as a plain const void* (engine.hpp, execution_consumer.hpp) and forwards it as overrides_opaque (native_host.hpp:693), which the kernel never dereferences; the source host casts it back (pine_strategy_host.cpp). check_native_include_independence.py therefore whitelists no source symbol at all.engine.hpp:73-180); the arithmetic is adapter-side (pine_adapter.cpp:396-403). strategy.close batching and the entry-id ledger are gone from the kernel — the ledger is adapter state (pine_adapter.hpp:1247-1258) and only names survive in comments. The TradingView margin-call toggle is gone: there is no set_margin_call_enabled in the tree, and what enables the model is the presence of NativeRunSpec::margin. So is the string-sentinel decoding of adapter-written comments: closed_trade_close_cause (src/engine_trade_accessors.cpp:157) reads a typed execution::CloseCause off the row, compares no string, and answers 3 for a kernel liquidation as readily as for the adapter's. What is live is one pair of hashed Pine-shaped lot flags: skip_entry_bar_high / skip_entry_bar_low (engine.hpp:130), hashed (skip_entry_bar_high src/engine_state_hash.cpp:65) and set by the adapter (skip_entry_bar_low pine_strategy_host.cpp:484) — the intrabar-fill excursion mask. Rule 5 is now scoped to that pair and to the comment residue.docs/design/native-feature-parity.md §2.ii lists the kernel↔TradingView couplings; three of the ones earlier drafts named are closed. The legacy path resolver is gone — the repository holds one fill simulation (src/native_matching.hpp driven by NativeExecutionConsumer), resolve_exit_path_fill has no declaration, no body and no test-only copy, and src/engine_path_resolve.cpp is down to the two generic functions (bar_path_uses_high_first, entry_stop_first_touch is the source layer's own). The Pine request.security semantics left the kernel for src/source/pine_security_eval.cpp; what stays is the generic evaluator registry (SecurityEvalState engine.hpp:1654) and the authoritative-feed store, both ruled below. What remains, each with a ruling in this document: the comparison band in kernel TA (float_band_eq ta_compare_band.hpp:38), the TV-named fields of the frozen pf_pending_order_v1_t mirror, and the lot-flag pair above.src/c_abi.cpp implements the 57 codegen-facing runtime PF_API symbols that scripts/check_c_abi_runtime.py pins by name, and src/native_c_host.cpp implements the 32 additive symbols of <pineforge/native_c_api.h>. A C host creates a host from a callback table (strategy_native_host_create_v1 native_c_api.h:1391), runs a batch (strategy_native_run_v1 native_c_api.h:1404), and submits, replaces, cancels and executes orders — strategy_native_submit_v1 (native_c_api.h:1431), _replace_v1 (:1449), _cancel_v1 (:1456), _cancel_all_v1 (:1460), _cancel_where_v1 (:1481), _execute_current_v1 (:1502) — under the kernel's own legality rule. Streaming needs no new symbol: the whole strategy_stream_* family takes that handle unchanged. The header's COVERAGE block carries one line per public member of NativeStrategyHost with either its C spelling or the reason it has none, and scripts/check_native_c_api_surface.py proves the block is exactly that class's public surface. strategy_create / run_backtest stay codegen-emitted (include/pineforge/pineforge.h:530), which is why a C host frees its report with strategy_native_report_free_v1.examples/native/ — thirteen C++ and two C — each including only <pineforge/native_host.hpp> (or native_c_api.h), linking PineForge::kernel, and checking its own numbers before it prints its summary line. PINEFORGE_BUILD_EXAMPLES (CMakeLists.txt:37, default OFF) builds them (add_subdirectory CMakeLists.txt:305) and registers each as a CTest row that runs through examples/native/run_example.cmake, which fails on a nonzero exit, a signal, a timeout or a missing summary line — so a row asserts both, which a bare PASS_REGULAR_EXPRESSION cannot. scripts/test_example_runner.py proves the runner fails in each of those ways and that no example_* row sets a property that would override its verdict. Each example is compiled -UNDEBUG, so an assert() in one aborts its row instead of vanishing under Release. The release and kernel profiles of scripts/ci_verify.py both turn the option on and both refuse a configure in which an example's compile line leaves NDEBUG defined.Measured on this tree over libpineforge_kernel.a (the archive built with PINEFORGE_BUILD_SOURCE_LAYER=OFF) by the checker the kernel profile of scripts/ci_verify.py runs right after the build (stage kernel-residuals; the same check is the CTest row test_kernel_residuals in every profile):
The checker reads the archive's linkable surface — nm -C over the archive (defined and undefined symbols, demangled) and strings -a over a copy whose debug information has been stripped, so a -g build's DWARF names are not mistaken for residue (gap lane P2b) — and matches the residual vocabulary, whole identifiers only, against the first column of the tables in this section. The vocabulary is fixed in the script (IDENTIFIER_PATTERNS, PHRASE_PATTERNS): an identifier containing pine (not pineforge), tradingview, barmerge, coof, pooc, market_admission, calc_on_order_fills, process_orders_on_close or a tv segment; a text containing strategy.<name>, ta.<name>, request.security, barmerge.<name> or __margin_call__, where <name> is a Pine member and not a C/C++ file suffix (ta.ema is a call, ta.hpp is this project's header, which a sanitizer build writes into rodata as a real literal). Every match must be listed exactly — an identifier by its name, a text by a phrase it contains — and every listed match must still be in the archive, so a lane that adds a name adds its row and a lane that removes one removes its row. A name that is not in these tables fails the kernel profile. The audit's original probe is a subset of that vocabulary and still reads exactly the seventeen pine_* names:
Every match is a field name of the frozen pending-row POD or one of the three last_error texts below, and nothing else; the kernel-only archive defines no symbol the vocabulary matches. Each is listed here with its ruling.
| archive string | where it comes from | ruling |
|---|---|---|
pine_exit_activation_present, pine_exit_activation_direction_at_birth, pine_exit_activation_entry_bar_at_birth, pine_exit_activation_cursor_price_at_birth, pine_exit_activation_owner_cycle_at_birth, pine_exit_activation_stop_level_at_birth, pine_exit_activation_limit_level_at_birth, pine_exit_activation_limit_continuation_present, pine_exit_activation_limit_continuation_fill, pine_exit_activation_limit_continuation_cause | reflection table of pf_pending_order_v1_t (src/pending_order_mirror.cpp, include/pineforge/pending_order_mirror.hpp) | frozen by ruling (design §2.ii row i): the POD is an append-only C ABI contract read by strategy_pending_order_get; its field names are the contract, and strategy_pending_order_layout publishes them so an FFI consumer builds its struct from the runtime's own table. The neutral view is native_working_requests() / strategy_native_working_*. |
pine_frozen_market_instruction_kind, pine_frozen_market_instruction_own_units, pine_frozen_market_instruction_target_id, pine_frozen_market_instruction_target_id_hash64, pine_frozen_market_instruction_target_id_truncated, pine_frozen_market_instruction_transaction_units | same reflection table | frozen by ruling (§2.ii row i), as above. |
pine_birth_reach | same reflection table | frozen by ruling (§2.ii row i), as above. |
tv_carry_qty | same reflection table | frozen by ruling (§2.ii row i), as above. |
coof_suppress_stop_on_entry_bar, coof_suppress_limit_on_entry_bar, created_during_coof_recalc, coof_born_at_close_recalc, coof_born_mid_bar, coof_cascade_seg_i, coof_cascade_inflight_fires | same reflection table | frozen by ruling (§2.ii row i), as above. coof abbreviates calc-on-order-fills, the adapter's fill re-entry schedule (PineStrategyHost::on_native_applied); the kernel translation unit holds the reflection row — name, C type, offset, size — and never a value: the values are projected by the source layer alone (PendingIntentView::copy_v1, src/source/pine_adapter.cpp), and on a bare host strategy_pending_orders_len is 0, so no kernel decision reads or writes these fields. |
pooc_global_full_exit_dynamic_qty, pooc_global_full_exit_tracks_bound_adds, pooc_global_full_exit_bound_add | same reflection table | frozen by ruling (§2.ii row i), as the coof_* row: pooc abbreviates process-orders-on-close, the adapter's NativeCloseExecution::AfterCalculation projection; values are source-layer projections, the kernel holds the row. |
market_admission_observation_present, market_admission_observation_command, market_admission_observation_kind, market_admission_observation_birth_cause, market_admission_observation_birth_bar, market_admission_observation_birth_timestamp, market_admission_observation_birth_cursor_domain, market_admission_observation_birth_cursor_position, market_admission_observation_birth_cursor_index, market_admission_observation_birth_cursor_count, market_admission_observation_birth_cursor_price, market_admission_observation_birth_first_fill, market_admission_observation_birth_last_fill, market_admission_observation_birth_evaluation_ordinal, market_admission_observation_id, market_admission_observation_id_truncated, market_admission_observation_id_hash64, market_admission_observation_requested_quantity, market_admission_observation_quantity_type, market_admission_observation_buy, market_admission_observation_prices_limit, market_admission_observation_prices_stop, market_admission_observation_oca_name, market_admission_observation_oca_name_truncated, market_admission_observation_oca_name_hash64, market_admission_observation_oca_type, market_admission_observation_configuration_process_on_close, market_admission_observation_configuration_calc_on_fills, market_admission_observation_configuration_magnifier, market_admission_observation_configuration_fill_recalculation, market_admission_observation_configuration_scheduler, market_admission_observation_configuration_slippage, market_admission_observation_configuration_pyramiding, market_admission_observation_configuration_default_quantity_type, market_admission_observation_configuration_default_quantity_value, market_admission_observation_configuration_long_margin, market_admission_observation_configuration_short_margin, market_admission_observation_configuration_commission_value, market_admission_observation_configuration_commission_type, market_admission_observation_configuration_pointvalue, market_admission_observation_configuration_fx, market_admission_observation_configuration_quantity_step, market_admission_observation_configuration_mintick, market_admission_observation_configuration_risk_direction, market_admission_observation_configuration_loss_days_limit, market_admission_observation_configuration_drawdown_limit, market_admission_observation_configuration_intraday_loss_limit, market_admission_observation_configuration_position_limit, market_admission_observation_configuration_fill_cap_active, market_admission_observation_configuration_risk_halted, market_admission_observation_bar, market_admission_observation_placement_side, market_admission_observation_placement_cycle, market_admission_observation_prior_close_quantity, market_admission_observation_held_quantity, market_admission_observation_held_entries, market_admission_observation_realized_equity, market_admission_observation_placement_equity, market_admission_observation_signal_close, market_admission_observation_quantized_fixed_quantity, market_admission_observation_original_sizing_present, market_admission_observation_original_sizing_quantity, market_admission_observation_original_sizing_equity, market_admission_observation_original_sizing_price, market_admission_observation_original_sizing_mark, market_admission_observation_original_sizing_fx, market_admission_observation_explicit_equity, market_admission_observation_explicit_price | same reflection table: the frozen mirror of the source-layer admission journal (pineforge::admission, include/pineforge/source/market_admission.hpp, source layer since lane N14; design §2.ii row o) | frozen by ruling (§2.ii rows i and o): the sixty-eight names are the C contract for the journal's Draft observation — its Configuration fields are strategy() declaration parameters, its events TradingView admission reviews — while the journal itself, its fold into the state hash and every writer are source-layer code (src/source/market_admission.cpp, src/source/pine_state_hash.cpp, PendingIntentView::copy_v1). No kernel translation unit reads or writes a market_admission_* field; the kernel holds the reflection rows and nothing else. |
market_admission_review_present, market_admission_review_sequence, market_admission_review_checkpoint, market_admission_review_bar, market_admission_review_target_command, market_admission_sizing_revision_present, market_admission_sizing_revision_sequence, market_admission_sizing_revision_cause_fill, market_admission_sizing_revision_bar, market_admission_sizing_revision_target_command | same reflection table: the journal's review receipt and sizing revision | frozen by ruling (§2.ii rows i and o), as the observation row. |
native request.security feed requires a parseable timeframe, native request.security feed requires bars and a positive count, native request.security feed timestamps must be strictly increasing | src/engine_aux_security.cpp set_native_security_feed (last_error texts) | retained: the texts name the feature the frozen C export strategy_set_native_security_feed documents, and the prefix is pinned by the ab9714be base test test_native_security_feed (twin parity). The store itself is the generic authoritative-feed mechanism. |
Names the vocabulary does not match but the audits named, with their rulings:
| name | where | ruling |
|---|---|---|
| the W/M-from-dailies partition and trade-date rule | src/engine_aux_security.cpp | retained as the generic contract (design §2.iv item 6, §2.ii row s): a feed is the venue's own bars of one timeframe; its stamps are the period partition, a coarser calendar period without its own feed is the aggregate of the finest installed calendar feed, and the policy knob is installing the feed or not. TradingView pins are the calibration evidence, not the mechanism. |
session_template_knows_early_close ("forex" / "cfd" / "crypto") | src/engine_security.cpp | retained (§2.ii row t): SymInfo::type's vocabulary is fixed by the frozen C ABI (strategy_set_syminfo_type), so the instrument-class classification is the kernel's own; continuous-session OTC classes complete a calendar period on the next session's first bar. |
is_fixed_intraday_minute_tf, supports_lower_tf_emulation, synthesize_lower_tf_bars | src/engine_lower_tf.cpp | retained as generic primitives (§2.ii row r): a timeframe parser, an integer ratio and evenly sampled sub-bars, pinned by kernel-only tests; the merge-flag rule that used to throw TradingView's sentence from this TU moved to the source evaluator. |
inputs_, get_input_*, syminfo_metadata_, set_syminfo_metadata, enum class QtyType, SymInfo | include/pineforge/engine.hpp | retained: the run-time ingress the frozen C ABI exposes (strategy_set_input*, strategy_set_syminfo_*). Their comments explain the vocabulary; no kernel decision reads a Pine name. |
[pineforge] WARNING: …, on_margin_call | src/session_time.cpp, native_c_api.h | not residue: the project's own name and a broker term. |
Kernel state the adapter sets — the mechanism rulings (the vocabulary gate cannot see a mechanism, so these rows are held by reading, not by the checker):
| kernel state | who writes it | ruling |
|---|---|---|
ta::ema_na_warmup_flag() — the thread-local ambient default for ta::EmaSeeding, false = FirstValue, true = SimpleAverage (include/pineforge/ta.hpp, src/ta_moving_averages.cpp) | three source-layer RAII scopes (PineStrategyHost chart dispatch, pine_security_eval.cpp, pine_aux_security.cpp) raise it around one evaluation context under the opt-in chart_ema_na_warmup / security_range_start_na_warmup run flags; nothing in the kernel raises it | retained as a generic ambient indicator option (R5 lane P2). An EMA seeds from its first finite input or from the simple average of its first length inputs; both are textbook, and the kernel names them per instance (EMA(length, EmaSeeding)), which is a bare host's spelling and touches no global. The ambient default is what an instance that names no seeding latches on its first compute(): a thread-local default for a per-computation option, the shape of a floating-point rounding mode, and the only zero-wiring way to seed every instance of one evaluation context when the generated strategy constructs its own ta::EMA members (so relocating it into the source layer would take a codegen change). The adapter uses it the way any host may; the kernel's spelling and comments carry no TradingView vocabulary. The accessor's name is kept: na is the kernel's own NaN spelling and eight twin-parity-frozen CHECK texts of test_chart_ema_na_warmup spell it. |
What N14 moved out of the archive, so the tables above are complete: the barmerge merge-flag rule (→ src/source/pine_security_eval.cpp), the PineMatrix / PineGenericMatrix names (→ NumericMatrix / GenericMatrix<T> with deprecated aliases), the pine_float_* shim (→ include/pineforge/source/), the market-admission journal pineforge::admission (→ source layer), TradingView's trail tick arithmetic (→ compat/pine/trail_ticks.hpp) and the live-tail / probe-suppress overrides (→ source::PineStrategyHost, behind two kernel virtual seams the frozen C setters call; on a bare host the seams keep the L1 ingress contract, accepted and inert). The Codex audit's remaining rows — the dead fill helpers round_to_mintick_directional / apply_slippage and the observe_* source-only observers — are gap lane N10's and the C-surface lane's, not residue of this table. What lane P2 added: the second audit found the N14 probe structurally blind to the eighty-nine pending-row names whose spelling contains no pine (coof_*, pooc_*, market_admission_*, tv_carry_qty) and to the one kernel global the adapter sets; the families are ruled above by name, the global by mechanism, and scripts/check_kernel_residuals.py holds the tables.
Two public surfaces still spelled a TradingView name, and both are load-bearing: a public C ABI field a compiled consumer reads by offset, and an enumerator of a shipped standalone C++ ABI. The lane's decision is alias and deprecate, never break. The generic name is the primary spelling in code and in the documentation; the old name stays a valid, value-identical alias, and its removal is scheduled for the next epoch of the ABI that carries it. Nothing here changes a value, an offset, a size, a serialized key or a behaviour: PF_ABI_VERSION stays 4 and lifecycle_v1 stays lifecycle_v1, because an alias needs no epoch.
This table is deliberately OUTSIDE the residual-vocabulary section above. scripts/check_kernel_residuals.py rules by presence: it reads the first column of every table in the section "Residual TradingView-named surface in the kernel-only archive" and fails on a ruled name that is no longer in the archive. None of the four names below is a symbol or a string literal in libpineforge_kernel.a — a struct field name and an enumerator exist only at compile time, and neither is written into any last_error text — so a row here would be a stale row there. Verified on a Release and on a Debug archive: 0 findings, both profiles, with these aliases in the tree.
| name | generic spelling | ABI that carries it | removal epoch | why retained |
|---|---|---|---|---|
pf_equity_stats_t::sharpe_tv | sharpe_monthly | public C ABI (include/pineforge/pineforge.h, PF_ABI_VERSION 4) | the next PF_ABI_VERSION (5) | The field is month-end-resampled equity simple returns (chart timezone, open-time bucketing), risk-free 2 %/yr, annualized ×√12, sample (N−1) stddev — a construction whose name is its resampling period, not its calibration source. The published header is the contract of every compiled FFI consumer, so both names are one double behind a C11 anonymous union of two same-typed members: identical offset (48), identical sizeof(pf_equity_stats_t) (120), identical offsetof(pf_metrics_t, equity) (648), pinned by static_assert in src/c_abi.cpp and exercised from C by tests/test_c_abi.c. |
pf_equity_stats_t::sortino_tv | sortino_monthly | public C ABI (as above) | the next PF_ABI_VERSION (5) | Same resampling as sharpe_monthly, population downside deviation vs the monthly risk-free. Aliased on the same terms; offset 56. |
| exit_legs::Domain::Coof | FillRecalc | standalone C++ ABI pineforge::exit_legs::lifecycle_v1 (include/pineforge/exit_leg_lifecycle.hpp) | lifecycle_v2 | The domain is the fill-recalculation re-entry pass: the host re-runs its script after a fill and observes the rest of the same bar. coof abbreviates calc_on_order_fills, the Pine adapter's name for it (the same abbreviation the coof_* reflection rows carry, ruled above). An enumerator alias adds a name and no value: Coof == FillRecalc == 1, the underlying type stays uint8_t, Domain::RawTicks stays 4 (so valid_frame's range check is unchanged), and no switch gains a case. Twin-parity-frozen TUs keep compiling their old spelling unchanged. | | exit_legs::Domain::MagnifierCoof | MagnifierFillRecalc | standalone C++ ABI lifecycle_v1 (as above) | lifecycle_v2 | The same re-entry pass on a magnified sub-bar. MagnifierCoof == MagnifierFillRecalc == 3. |
The serialized report keys do not change. sharpe_tv and sortino_tv remain the JSON keys of the report dictionaries built by docker/run_json.py (_stats_dict, driven off the ctypes _fields_ list) and of the engine dict scripts/crossvalidate_metrics.py builds the same way. They are ruled report-schema names: a schema key is a wire format, not an identifier, and renaming it would break every Python/FFI consumer for no mechanical gain. The ctypes mirrors in scripts/run_strategy.py, docker/run_json.py, tutorial/run.py, benchmarks/throughput/grid_search_repro.py and docs/pages/ffi-python.md therefore keep the historical member name (ctypes matches by offset, never by the C field's name); each carries a comment saying it is the historical spelling of sharpe_monthly / sortino_monthly.
Portability note for the C alias. An anonymous union is C11. This project already compiles C at C11 (CMAKE_C_STANDARD 11, and the native C examples document cc -std=c11), and C++ has had anonymous unions since C++98, so every consumer in this repository is covered. A consumer compiling the public header as strict C99 gets a warning (-Wc11-extensions), not an error. The rejected alternative was #define sharpe_tv sharpe_monthly: a macro leaks into every translation unit that includes the header and would rewrite an unrelated consumer's own sharpe_tv.
Rule 3 below makes every bare-host capability opt-in, so that adapter runs stay byte-identical by construction. Its cost is a standing question, which the second R5 audit asked of the L8 price grid and the L9 risk limits: is a run-spec feature the adapter never sets a boundary decision, or dead weight? This section answers it for every such field, not only those two, and scripts/check_native_feature_rulings.py (CTest rows test_native_feature_rulings and test_native_feature_rulings_mutations) holds the answer. It reads struct NativeRunSpec, reads what PineExecutionAdapter::project() assigns — the source layer's one spec construction site — and fails when a field is neither declared by the adapter nor ruled in the table below, when a ruling names no executed native consumer that spells the field, or when a ruling outlives an adapter that has started declaring the field.
A kernel capability has two legitimate consumers: the adapter, when TradingView's outcome can be reproduced through it byte for byte, and a native host (C++ or C), which is whom the kernel is for. "The adapter does not use it" is therefore not a defect by itself; it is one when nobody decided it. Three kinds of decision:
Every line number in this section names its symbol on this tree, which scripts/check_doc_anchors.py re-verifies on every preflight.
NativeRunSpec field | ruling | what the adapter runs instead | measurement and ruling of record | executed native consumers |
|---|---|---|---|---|
price_grid, grid_rounding | native-only | TradingView's per-order-kind tick rules on top of None: source_trigger_threshold (pine_adapter.cpp:329), source_level_on_price_grid (:315), nearest_tick (:253) / source_bar_fill_tick (:274) / directional_tick (:298), behind the terms seam | R5-3 and design risk E7 ruled None for the adapter before the lane ran; lanes R7 and N13 measured the alternative anyway (raw levels submitted, QuantizeFillsAndTriggers with HalfUp declared). L8b closed the first blocker (no run aborts). The second has no remedy on either side: TradingView quantizes per order kind (stop and limit legs and a trail's activation on the quantized bar; the trail stop, the running best, stop-limit entries and the calc_on_order_fills cursors raw), the grid is one rule for the run, and it still moves 30 pinned checks in 4 units; a per-kind mask would spell that inconsistency into the kernel. The corpus cannot arbitrate (every probe runs a 0.01 tick on an on-grid feed; 5 of 312 differ in an engine-only column). Permanent witness: tests/test_adapter_grid_relower.cpp, whose section 5 pins the trail stop the grid fires a bar early. Design row PG and §3.6 | examples/native/native_price_grid_strategy.cpp, examples/native/native_price_grid_c.c, tests/test_native_price_grid.cpp |
risk | native-only | all of strategy.risk.* but the direction: update_risk_state (pine_adapter.cpp:12101), SourceDayLedger, submit_intraday_loss_close (pine_adapter.cpp:13181), chart_day_key (pine_adapter.cpp:11995), compat::pine::IntradayCap with IntradayOrderBudget (337 lines) | Structural first: Pine's risk calls are per-bar statements, so a limit reaches the adapter on script bar 0, after project() (pine_strategy_host.cpp:315) and configure_native (pine_strategy_host.cpp:316) have fixed and digested the spec. In substance (lane N12, tests/test_adapter_risk_relower.cpp, 62 checks over nine paired scenarios): the drawdown latch samples at the close only and still admits a reversal; the loss-day streak counts trades, not days; the intraday loss closes at the path's adverse extreme, refuses every placement and withdraws the book; the fill cap charges slots, transfers quota and closes at the bar's better extreme on the chart timezone's day. With the kernel seeded on the corpus, 3 of 4 cap probes diverge (3840 of 3916, 312 of 604, 2370 of 2384 rows). strategy.risk.allow_entry_in is the one rule that is the kernel's already (allowed_open_directions). Design §3.6 | examples/native/native_risk_limits_strategy.cpp, examples/native/native_trail_risk_strategy.cpp, tests/test_native_risk_limits.cpp, tests/test_native_c_api.c |
max_abs_units | adapter-policy | strategy.risk.max_position_size as a gate on the LIVE book before the fill (pine_adapter.cpp:11499-11500): an entry is refused once the book already holds the limit | design row MG2 (R5-1): the resulting-book cap is the generic one. Measured by N12's scenario PS in tests/test_adapter_risk_relower.cpp: two-unit entries against a limit of 3 leave the adapter at 4 and the kernel cap at 2 | tests/test_native_resting_matching_contract.cpp, tests/test_native_run_spec.cpp |
max_open_lots | adapter-policy | Pine pyramiding is a per-cycle entry count in the adapter's command policy; a resting source entry must not consume a physical-lot cap before it fills (pine_adapter.cpp:1494-1496) | design row MG3 (R5-1); the contract comment in project() | tests/test_native_resting_matching_contract.cpp, tests/test_native_margin_model.cpp |
initial_margin_fraction | adapter-policy | TradingView's ten-significant-digit money admission against the signal-time tuple, answered as AdmitWithHostMargin; the margin model the adapter does declare is maintenance-only (NativeMarginModel pine_adapter.cpp:1539-1547, margin.maintenance_long pine_adapter.cpp:1554) | design row MG4 and the wave-4 ruling recorded in project(): a positive initial requirement would decline openings TradingView takes | tests/test_native_precommit_view.cpp, tests/test_native_margin_model.cpp |
report_open_position_at_end | adapter-policy | TradingView's range-end report re-marks the curve's last point and re-folds every extreme from it (scheduler_record_range_end): report shape, not a mark-to-market row (pine_adapter.cpp:1486-1493) | design row RP5; the kernel reads the field under KernelRecorded only, which scripts/check_adapter_spec_shadowing.py gates | examples/native/native_sized_report_strategy.cpp, tests/test_native_report_truth.cpp |
open_bar_view | adapter-policy | Complete: TradingView's bar-open scheduling and its calc_on_order_fills callback read the whole script bar (pine_adapter.cpp:1477-1478) | design rows CT4 and E5: the open-only view is opt-in because the adapter needs the full bar | examples/native/native_calc_on_fills_strategy.cpp, tests/test_native_calc_timing.cpp |
subscriptions | adapter-hook | declare_timeframe_subscriptions from the begin-time hook (pine_strategy_host.cpp:1488): a plain request.security site is a kernel subscription | lane R3b over the L6c hook: 21 of the corpus's 23 request.security probes run their sites on the kernel, byte-identical; the sites the predicate leaves out (lower timeframe, lookahead, auxiliary, streams) keep the source evaluator | examples/native/native_htf_strategy.cpp, tests/test_native_htf_subscriptions.cpp |
auxiliary_feed | adapter-policy | the adapter's own auxiliary drive: the chart-slice mapping and the deferred first bucket (src/source/pine_aux_security.cpp) | lane N7, retained on three measurements: 0 of 312 corpus probes install an auxiliary feed; TradingView's chart slice leaves pre-range coverage inert where the kernel folds it by time, and evaluates after the bar's matching pass where the kernel delivers before it (tests/test_native_auxiliary_feed_twin.cpp, rows B and C). Design §2.iv | tests/test_native_auxiliary_feed.cpp, tests/test_native_auxiliary_feed_stream.cpp |
What the table does not cover, on purpose: request kinds and host members the adapter never emits or calls (Sized, ScopeFraction, native_open_lots(), …). Their rulings live beside the feature (design §4.1 E3 for Sized; the C header's COVERAGE block for the host surface), and the audit scoped this section to what a run declares.
Rule 2's amended test asks two things of a kernel site whose comment names TradingView: is the mechanism generic, and would the alternative be hash-visible surface for a choice the run has already made? This section answers both for every such site, so that the rule as written is one this tree passes.
How the population was taken. Over the kernel's own compile closure — the thirty-five translation units of PINEFORGE_KERNEL_SOURCES plus the headers directly under include/pineforge/ and src/*.hpp — a case-insensitive search of comment lines for TradingView, a TV word, Pine or barmerge finds 433 lines. Grouped by file, the population is engine.hpp 90, engine_orders.cpp 45, timeframe.cpp 43, pineforge.h 29, timeframe.hpp 27, the five ta_*.cpp 70, session_time.cpp 15 and session_time.hpp 12, engine_aux_security.cpp 14, ta.hpp 13, map.hpp 11, native_execution_consumer.cpp 7, and single digits in twenty further files. The first question to ask of each is not what it says but whether it documents live code: for every hit, the next non-comment, non-blank line was read. That splits the population in two, and the split is the finding.
Four of the audit's named "compiled kernel sites" document functions the R5 lowerings deleted. The comment survived the code. They are not rule-2 questions at all; they are rule 5's "legacy to be extracted over time", and what is left to extract is the text.
| site | what it documents | the next code after it | verdict |
|---|---|---|---|
src/engine_orders.cpp:203-210 | strategy.oca.reduce: a filled sibling reduces every other sibling's remaining quantity | build_close_trade_with_costs at :225, unrelated; the block's own header line reads "Internal helper: cancel OCA group members", a helper that no longer exists | residue. The live mechanism is native_order's GroupEffect (native_order.hpp:415) with ReservationReducedEvent, resolved in src/native_order.cpp, and it is generic: a group member's effect on its siblings is cancel-or-reduce, named by the request |
src/engine_orders.cpp:561-588 | TradingView's deferred-flip growth rule (tv_carry_qty) | nothing: from :560 to :697 the file contains no code at all | residue. The carry survives only as a frozen POD field name, ruled in the residual table |
src/engine_orders.cpp:590-600 | the opening margin guard required_margin = qty * fill_price * margin_pct / 100 | as above, no code | residue. The live opening gate is NativeMarginModel::initial_long / initial_short and initial_margin_fraction, with AdmitWithHostMargin for a host that owns the check |
src/engine_orders.cpp:651-697 | TradingView same-tick multi-entry sequential-fill semantics | as above, no code | residue. Same-point fill order is the consumer's, by acceptance and incarnation ordinal |
src/engine_run.cpp:162-186 | process_orders_on_close semantics | reset_run_state() at :187, unrelated | residue. The live mechanism is the spec field NativeCloseExecution::AfterCalculation (native_run_spec.hpp:27) |
include/pineforge/engine.hpp:1262-1330 | TradingView freezes default market-order sizing at the signal bar | net_profit() at :1382; :1262-:1381 is one comment block | residue. The live mechanism is native_order::SizePrice (Signal, SignalOnTick) with SizeTime::AtAcceptance — a request value that names no platform |
include/pineforge/engine.hpp:1352-1381 | TradingView liquidates intrabar, before the bar-close script body | net_profit() at :1382 | residue. The live mechanism is NativeLiquidationCheck (native_run_spec.hpp:157), whose three values are three broker models |
include/pineforge/engine.hpp:73-180 | the ten-significant-digit money rule | the PyramidEntry aggregate | residue (already recorded above): the arithmetic is pine_adapter.cpp:396-403 |
For a code lane, not this document: these blocks total 312 lines — engine_orders.cpp :200-224 and :556-697, engine_run.cpp :162-186, engine.hpp :1262-1381 — and not one line of code sits inside any of those four spans. They describe deleted functions. Deleting them is a comment-only change to kernel files, which no documentation lane may make. Until then rule 5 covers them, and this table is what a reader consults before believing one.
| kernel site | the mechanism | (a) generic because | (b) a knob would be | pinned by |
|---|---|---|---|---|
utc_floor_day_ms src/timeframe.cpp:152 (comment :146) | daily boundaries are anchored at symbol-local midnight | the anchor is spec.timezone's data, not a platform's; another venue is driven by naming its zone | a "which midnight" field over a zone the spec already names | tests/test_native_calendar.cpp, tests/test_session_day_anchors.cpp |
session_period_last_traded_close_ms src/timeframe.cpp:690 (comment :681) | a D/W/M period is finished at the last traded close of its last session | every venue calendar must answer "is this period over"; the answer is computed from spec.session and spec.timezone | a per-run completion-rule switch over a choice the session template already makes | tests/test_calendar_aggregation_wm.cpp, tests/test_session_calendar_extra.cpp |
the session-day split src/timeframe.cpp:744-748 | calendar periods split on the session-day clock, not the civil one | continuous-session instruments need a session-day clock whatever platform asks; the class comes from spec.type | hash-visible surface for what spec.type decides | tests/test_session_day_anchors.cpp |
the aggregated bar's label src/timeframe.cpp:904-911 | an aggregated bar is dated by the period it opens | a label rule is needed to key any bucket; this one is the period key | a label-policy field for a key the aggregator owns | tests/test_calendar_aggregation_wm.cpp, tests/test_calendar_wm_open_utc_fastpath.cpp |
real-end and chart-close completion src/timeframe.cpp:1030-1051, :1070-1090 | a thin or session-clipped intraday bucket completes on the bar whose end reaches the bucket end, or on the session's last bar | both are "the period is over" tests over feed data; a gap-free 24x7 feed is bit-identical either way, which the comment states and the tests pin | a completion-mode field over a bucket the calendar already closes | tests/test_calendar_aggregation_wm.cpp, tests/test_native_htf_subscriptions.cpp |
the holiday session walk src/timeframe.cpp:1472 (comment :1468) | a session that pauses and reopens the same day is one session day | the rule reads the feed's own stamps; the holiday is data | a holiday table in the kernel | tests/test_calendar_aggregation_wm.cpp |
bar_path_uses_high_first src/engine_path_resolve.cpp:37 (comment :40), compute_ohlc_path_legs src/magnifier.cpp:53 (comment :49), src/engine_internal.hpp:120 | with only OHLC the intrabar walk order is unknowable, so the open-proximity heuristic picks the first leg | and the knob exists: NativeRunSpec::path_order (NativePathOrder native_run_spec.hpp:334) states HighFirst / LowFirst explicitly, so a replay host does not depend on the inference | already spelled, and Auto is the default that moves no identity | tests/test_native_run_spec.cpp, tests/test_native_price_grid.cpp |
round_to_mintick include/pineforge/engine.hpp:913 (comment :897) | nearest-tick rounding, ties away from zero | a tick ladder is the instrument's, declared as price_tick; the generic surface over it is NativePriceGrid + NativeGridRounding | TradingView's per-order-kind quantization is exactly the knob that would not be generic — it is the measured content of the price_grid native-only ruling above | tests/test_native_price_grid.cpp |
ta::ATR include/pineforge/ta.hpp:321 (comment :334) | average true range over Wilder's RMA | Wilder's ATR is the textbook definition; the comment records which convention and the tape that pinned the recompute behaviour | a seeding/averaging field on an indicator whose definition is the convention | tests/test_ta_rma_warmup.cpp, tests/test_recompute.cpp |
ta::STDEV include/pineforge/ta.hpp:421-428 | the population (biased) standard deviation | biased vs sample is a textbook choice, and the kernel states which | a per-instance switch is the right answer where two conventions genuinely compete, which is why ta::EmaSeeding exists and this one does not need it | tests/test_ta_indicators_extras.cpp |
float_band_eq include/pineforge/ta_compare_band.hpp:38 | a relative-epsilon float comparison for indicator equality | an epsilon band is needed by any float comparison; the width is the calibration | a band-width field on a comparison the indicators share | tests/test_dmi_parity.cpp, tests/test_ta_osc_edge.cpp |
the two delivery rules src/native_execution_consumer.cpp:7262, :7347 | deliver a completed bucket at its first contributing bar (lookahead), and clear the series on a bar it publishes nothing on (gaps) | both are delivery rules over a bucket the aggregator already closed; neither reads a platform. They are spec fields: NativeTimeframeSubscription::lookahead / ::gaps | already spelled, and both default to the rule that moves no identity | tests/test_native_htf_subscriptions.cpp |
pf_native_subscription_v1::lookahead include/pineforge/native_c_api.h:1083, ::gaps :1087 | the C spelling of those two rules | the doxygen names barmerge.lookahead_off / gaps_on so a migrating reader finds the field — that is a pointer to the reader's vocabulary, which is what a migration surface is for, not a justification | — | tests/test_native_c_api.c |
the authoritative-feed partition src/engine_aux_security.cpp | a feed is the venue's own bars of one timeframe; its stamps are the period partition | already ruled: design §2.ii row s, and the residual table's own row | the policy knob is installing the feed or not | tests/test_native_htf_subscriptions.cpp, tests/test_native_security_feed.cpp |
session_template_knows_early_close src/engine_security.cpp | the instrument-class classification | already ruled: design §2.ii row t; SymInfo::type's vocabulary is fixed by the frozen C ABI | — | tests/test_native_wm_buckets.cpp |
skip_entry_bar_high / skip_entry_bar_low include/pineforge/engine.hpp:130 | the intrabar-fill excursion mask: the part of a bar traversed before a priced entry filled is not that trade's excursion | the mask is generic — an excursion is measured from the fill — but the kernel's own default is to sample the whole bar, and only the adapter raises the flags | already spelled the generic way: owns_lot_excursions() + closed_lot_excursion hand the whole measurement to a host | tests/test_l11a_host_excursion.cpp |
the frozen pf_pending_order_v1_t field names | the reflection table of an append-only C ABI POD | already ruled: the residual tables above, held by scripts/check_kernel_residuals.py | renaming is an ABI break | tests/test_kernel_residuals (the gate itself) |
the C ABI's documented vocabulary — src/c_abi.cpp, include/pineforge/pineforge.h, src/engine_metrics.cpp, src/engine_report.cpp, src/engine_trade_accessors.cpp | the exports are the contract; the comments say what a Pine consumer called the same number | already ruled: the inputs_ / get_input_* / syminfo_metadata_ row above. No kernel decision reads a Pine name | the export names are frozen | scripts/check_c_abi_runtime.py, tests/test_native_c_api.c |
the neutral utility spellings — map.hpp, drawing.hpp, series.hpp, window_sum.hpp, math.hpp, matrix.hpp, session_time.hpp, str_utils.hpp | neutral public names with exact deprecated pine_* aliases for generated code | already ruled: "Neutral spellings" above | dropping the aliases breaks generated code | the ruled-name gate |
No site is left unaccounted for, and no site's justification is "because TradingView does it" over a mechanism that is not generic. Two entries are recorded as open questions rather than as violations:
pf_native_subscription_v1::lookahead and ::gaps are uint32_t 0/1 words where every other enum-valued C field is translated by an exhaustive switch onto a pf_native_*_e. The mechanism is generic and the values are validated (any value but 0 or 1 is PF_NATIVE_E_TAG), so this is a C-surface shape question, not a boundary one. Recorded for the C-surface lane.owns_lot_excursions), so a host is never forced through the flags; but two hashed booleans whose only writer is the source layer are the last of rule 5's pair, and the move — a host-owned excursion policy that subsumes the mask — is a kernel lane, not a documentation one.src/source/ / src/compat/pine/ or in codegen — never add a new Pine-specific rule to the kernel.The kernel changes only for a generic capability with a recorded ruling (e.g. per-lot excursion ownership, owns_lot_excursions native_host.hpp:953; the market-if-touched fill_through flag, native_order.hpp:239-242). The test is not whether the word "TradingView" appears — a calibrated number has to say what calibrated it. The test is this pair, and both halves must pass:
A site that passes (a) and (b) belongs in the kernel with its calibration recorded: the comment names the pin that fixed the number, and the mechanism gets a row in "TradingView-calibrated kernel mechanisms" below. A site that passes (a) and fails (b) gets a hook, not a spec field. A site that fails (a) is the adapter's. This is the standard the R5 waves actually applied — it is what ruled the authoritative-feed partition and the SymInfo::type vocabulary generic (design §2.ii rows s and t) while the per-order-kind tick rules and all of strategy.risk.* stayed adapter-side — and the section below accounts for every TradingView-justified kernel site against it, with no site left unruled.
docs/design/native-feature-parity.md §3.1).engine.hpp is legacy to be extracted into the adapter over time, not a precedent to copy.NativeRunSpec field the adapter does not declare is a decision, not an omission: the change that adds the field adds its row to "Kernel capabilities the Pine adapter does not declare" — native-only (with a native example and a test), adapter-policy or adapter-hook — and the change that makes project() declare a ruled field removes the row. scripts/check_native_feature_rulings.py fails either way until the table is true."Use the engine without Pine" means, on this tree: program against NativeStrategyHost in C++ or against <pineforge/native_c_api.h> in C; link PineForge::kernel, which is a separately buildable archive with no source-layer object in it; size with Sized or with your own terms; let the kernel record the equity curve, the per-bar broker hashes and the open-position row, or record them yourself; declare higher-timeframe series and an auxiliary finer feed in the run spec; declare a margin model, a risk block and a price grid; and submit, replace, cancel, bracket and trail through one request type. Fifteen example hosts do exactly that, each asserting its own numbers in a gated CTest row, and the kernel profile of scripts/ci_verify.py runs the whole suite with no adapter compiled.
What "without Pine" still does not mean:
docs/pages/pine-to-native.md; the namespace-by-namespace count is docs/pine_v6_coverage_detail.md. A native host has no strategy.* statement surface and none is planned: the kernel's surface is a run spec, a request and a hook.docs/design/native-feature-parity.md §3.6 / §3.7. Where the kernel's generic rule and TradingView's differ, the difference is measured and pinned, not papered over.engine_orders.cpp, engine_run.cpp and engine.hpp still describe deleted functions. Section A above lists them; rule 5 covers them until a code lane deletes them.Three gates hold the parts of this document that can be held mechanically, and all three run in scripts/ci_preflight.py and in scripts/ci_verify.py's profiles: scripts/check_kernel_residuals.py (every TradingView-vocabulary name in libpineforge_kernel.a is listed here, and every name listed here is still in the archive), scripts/check_native_feature_rulings.py (every NativeRunSpec field is either declared by project() or ruled here, with an executed native consumer) and scripts/check_doc_anchors.py (every file:line above names its symbol). What they cannot hold — the mechanism rulings, and rule 2's two-part test — is held by reading, which is what the tables above are for.