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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ endif()
# === pineforge library =================================================
add_library(pineforge STATIC
src/c_abi.cpp
src/compat/pine/order_priority.cpp
src/engine_aux_security.cpp
src/engine_fills.cpp
src/engine_lower_tf.cpp
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ live behavior or real broker fills.

## Validation scoreboard

**Native policy refactoring · 2026-09-10:** engine [#234](https://github.com/pineforge-4pass/pineforge-engine/pull/234) and codegen [#127](https://github.com/pineforge-4pass/pineforge-codegen-oss/pull/127) preserve **4,182 excellent / 8 strong**. Both the old-engine/new-codegen and new-engine/new-codegen Cloud runs retain all **4,190 raw trade CSVs, counts and full grades** unchanged. The target bands have zero entrants and zero leavers: **net 0, no individual regression**. These changes remove an unused source flag and give Pine cap behavior explicit ownership; they do not claim complete engine independence. The formal gates remain zero-improvement FAILs and the official baseline remains unchanged.

**Round 39 · 2026-09-09:** **4,182 excellent / 8 strong / zero moderate** across all **4,190 scored probes**. This round adds one excellent result, with zero regressions on any canonical metric.

| Board | Test set | Result | TradingView trades evaluated |
Expand Down Expand Up @@ -169,7 +171,7 @@ Every script is exported from TradingView as-is (its own inputs, its own default
- **strong** — ≥ 95% matched, trade count within 6%, entries within 0.1% and exits within 0.5% at p90;
- **moderate / weak** — ≥ 75% coverage, or less.

A grade is never one lucky run. Every candidate build is measured over the whole population on a reproducible cloud pipeline, each graded snapshot is published by content hash, and a build ships only through a deterministic gate: **no regression on any metric of any ETH 15m probe**, and, pooled over the other lanes, strictly more probes entering the excellent and excellent+strong bands than leaving them. Merges are fast-forwarded, so the commit that was gated is the commit on `main`.
Published parity results use a fixed population and reproducible Cloud Run measurements. The formal gate requires **no hard-surface regression** and strictly positive pooled movement across the target excellent and excellent+strong bands. A documented native-correctness exception permits exactly zero target-band movement with no individual regression, after full comparison, independent review and CI; its actual FAIL remains recorded and baseline promotion is deferred. Negative movement is outside this exception. Baseline promotion requires a recorded PASS and an exact-head merge with green CI.

### What the closed test taught the engine

Expand Down Expand Up @@ -206,6 +208,10 @@ PyneCore's 15 non-excellent strategies involve `strategy.exit(stop=…, limit=

---

Explicit Pine frontends must use the [execution attachment and regeneration
contract](docs/pine-order-priority-boundary.md) for retained-parent priority.
Bare native engines and cap-only generated constructors do not opt into it.

## What ships here

- `libpineforge.a` — the static runtime: order matching and fills, sizing and margin, the bar magnifier, 66 indicator classes, `request.security()`, time and session math.
Expand Down
18 changes: 9 additions & 9 deletions docs/pages/abi-stability.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,12 @@ notice:
- The shape of internal log lines (use them for humans, not parsers).

Rebuild generated and native C++ objects against matching engine headers and
runtime. The cap compatibility extraction changes the v2 object layout and
therefore uses the internal `engine_script_run_v3` inline namespace. An object
runtime. The cap and priority extractions change the v2/v3 object layouts and
therefore use the internal `engine_script_run_v4` inline namespace. An object
built from base `38dc73e` headers references v2 out-of-line members and must
fail to link to this v3 runtime. The source-pairing check compiles frozen exact
fail to link to this v4 runtime. The source-pairing check compiles frozen exact
base headers separately before testing the link, so a missing include or
compile failure cannot masquerade as mismatch protection. Both generated-style
compile failure cannot masquerade as mismatch protection. Frozen f864/v3 headers are also rejected by this v4 runtime. Both generated-style
and native-style current callers must still link.

`PINEFORGE_HAS_SCRIPT_RUN_PREPARE_V1` remains 1: it describes the existing hook
Expand All @@ -129,13 +129,13 @@ retrofit an old module. Public C function signatures, POD layouts,
Namespace versioning protects referenced internal C++ symbols; it does not
validate an erased `pf_strategy_t` handle. Use a handle only with functions from
its creating strategy module. A fully self-contained old module can still use
its own matching runtime; this check does not turn it into a v3 module.
its own matching runtime; this check does not turn it into a v4 module.

The cap boundary also advances the broker fingerprint domain to
`pineforge-broker-state/v3` and stream fingerprint version to 3. These identify
changed serialized state, including the Pine configuration/quota/cause and
The cap and priority boundaries advance the broker fingerprint domain to
`pineforge-broker-state/v4` and stream fingerprint version to 4. These identify
changed serialized state, including Pine priority attachment/configuration, cap quota/cause and
separate generic close request. The Pine component schema remains 1; it is
independent of the aggregate fingerprint version. Prior v2 fingerprints are
independent of the aggregate fingerprint version. Prior v2/v3 fingerprints are
not comparable. Fingerprints are replay checks, not serialized checkpoints or
complete hashes of private strategy state. The native runner already binds
its strategy-library SHA; its ledger format and Python provenance fingerprints
Expand Down
19 changes: 11 additions & 8 deletions docs/pine-intraday-cap-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ extracts those decisions from existing execution paths; it does not make the
engine independent of Pine or establish broader TradingView parity.

The paired codegen explicitly selects this compatibility component in the
`GeneratedStrategy` constructor, guarded by
`PINEFORGE_HAS_EXPLICIT_PINE_CAP_V1`. Older supported engines do not define
that capability and keep their established default. Risk statements still
`GeneratedStrategy` constructor. Current codegen uses the scoped
`attach_pine_execution_adapter()` hook for cap and retained-parent priority,
with a guarded cap-only fallback for older engines; see
[pairing and regeneration](pine-order-priority-boundary.md). Risk statements still
assign `max_intraday_filled_orders_ = (int)(expression)` at execution time;
selecting a component does not evaluate or hoist a risk statement.

Expand Down Expand Up @@ -117,18 +118,18 @@ Independent Grok, unchanged fixed Cloud controls and full regression/gate
verification remain root-owned requirements before any parity or publication
claim.

## Internal pairing and observable-state version 3
## Internal pairing and observable-state version 4

Relative to base `38dc73e`, the new policy/obligation members change the
`BacktestEngine` layout. Its inline namespace is now `engine_script_run_v3`;
`BacktestEngine` layout. Its inline namespace is now `engine_script_run_v4`;
base-header v2 native/generated objects must fail to link against this runtime.
The exact source-pairing test compiles frozen base headers and checks explicit
undefined v2 symbols, alongside matched v3 positive controls. Its isolated v2
undefined v2 symbols, alongside matched v4 positive controls. Its isolated v2
symbol stub is a reverse-link control, not a build of the entire old runtime.
No mismatch test program is executed.

Broker hashes use `pineforge-broker-state/v3`; stream fingerprints begin with
version3. The Pine policy's own schema stays1. Public C ABI version4, stream API
Broker hashes use `pineforge-broker-state/v4`; stream fingerprints begin with
version 4. Each Pine component's own schema remains 1. Public C ABI version4, stream API
version1, POD layouts, exports and `PINEFORGE_HAS_SCRIPT_RUN_PREPARE_V1` remain
unchanged. This version correction changes linking and serialized hash bytes,
not cap charging, fill prices or other economic behavior. It does not make
Expand All @@ -151,3 +152,5 @@ separating requests, execution facts and exposure state; they do not establish
Pine quota policy or predictable live fills. See [MQL5 OrderSend](https://www.mql5.com/en/docs/trading/ordersend)
and [OnTradeTransaction](https://www.mql5.com/en/docs/event_handlers/ontradetransaction).
This patch adds no external execution adapter or callback-ordering model.

The priority attachment adds a v3/f864 frozen-header rejection control; the cap-only API and its financial rules retain their scope. See [order-priority ownership](pine-order-priority-boundary.md).
76 changes: 76 additions & 0 deletions docs/pine-order-priority-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Explicit Pine execution attachment and order priority

`BacktestEngine::attach_pine_execution_adapter()` explicitly selects two
compatibility components: the existing intraday cap and retained-parent order
priority. It is idempotent, preserves configuration and outstanding cap state,
and does not imply that all Pine behavior has been extracted from the engine.
`enable_pine_intraday_cap()`, the cap constructor argument and legacy integer
cap assignments remain cap-only operations.

Bare construction leaves order priority detached. Syminfo metadata, including
`flat_retained_child_fresh_parent_order=1`, cannot attach it. The Pine component
owns that key's finite-positive interpretation and retains a supplied value
while detached; explicit attachment preserves a prior off override. Under Pine
attachment, an absent key enables the exact historical exception. Zero, negative
and nonfinite supplied values disable it. Copy/reset preserve attachment and
configuration without retaining a cached scheduling decision.

The full predicate lives in `src/compat/pine/order_priority.cpp`: exact two-object
book, ordinary POOC context, flat placement, named-cancel and exact child-reissue
receipts, adjacent incarnations, default quantities, full absolute stop+limit
child, pure-stop parent, prior-bar age and all OCA/callback/magnifier/stream
exclusions. Three/four unrelated orders and an intervening accepted-and-canceled
order remain exclusions. This is one active rule transferred to Pine ownership,
not a claim that its source-shape policy disappeared globally.

The comparator consumes an immutable `broker::OrderPriorityDecision` bound to
exact incarnations. It substitutes two sequence tie-breaks at their former
location, after fill phase. It does not change eligibility, fill prices, OCA
mutation, callbacks, admission or financial accounting. Shared incarnation
receipts and their other compatibility consumers remain unchanged.

## Pairing and regeneration

New codegen calls the fully qualified attachment method in the constructor,
before `strategy_create` returns and host metadata arrives:

```cpp
#if defined(PINEFORGE_HAS_EXPLICIT_PINE_EXECUTION_ADAPTER_V1)
pineforge::BacktestEngine::attach_pine_execution_adapter();
#elif defined(PINEFORGE_HAS_EXPLICIT_PINE_CAP_V1)
pineforge::BacktestEngine::enable_pine_intraday_cap();
#endif
```

The fallback preserves existing cap-only engines' native-to-Pine cap selection
and their old default retained-parent rule. Engines lacking both capabilities
use their established defaults. This is generated-source portability with
matching headers/runtime, not binary compatibility across engine versions.

Old cap-only generated source can compile with current headers but does **not**
select the priority exception. Regenerate it with the paired codegen, or
explicitly update a maintained C++ Pine frontend to call the attachment method
before metadata. Neither metadata nor an implicit constructor default repairs
old source. Existing checked-in corpus/tutorial C++ must likewise be regenerated
before using it for Pine behavior measurements; compilation alone is insufficient.

Rebuild every C++ consumer with matching current headers and archive. The class
namespace is `engine_script_run_v4`; frozen v2/base38 and v3/f864 callers are
compile/link rejection controls. Broker fingerprint domain and stream fingerprint
version are now 4 and include priority attachment/configuration. Prior hashes
are not comparable. Public C functions, PODs and ABI/API versions are unchanged;
an erased handle still belongs to its creating module.

## Remaining native gap and evidence limits

Bare native execution retains the ordinary phase/sequence scan. An older child
visited while flat can still be skipped until a later pass, exposing a transient
position to the intervening close-time observer. No identity-bound child
activation/event queue has been added. That causal scheduler is a separate next
slice, not an unused reducer in this extraction.

Literal C++ controls preserve the old Pine price, quantity, timing and financial
assertions with explicit fixture attachment; they separately pin native metadata
inertness, exclusions, OCA, copy/reset/hash and current lifetime safety. Codegen
checks compile source only. None establishes TV truth, measured campaign
neutrality, actualZERO, absence of population regressions or readiness to publish.
45 changes: 45 additions & 0 deletions include/pineforge/compat/pine/order_priority.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#pragma once
#include "../../order_priority.hpp"
#include <cmath>
#include <optional>
#include <string>
#include <vector>

namespace pineforge { struct PendingOrder; }
namespace pineforge::compat::pine {

struct OrderPriorityContext {
bool broker_flat;
bool process_orders_on_close;
bool calc_on_order_fills;
bool coof_scheduler_active;
bool bar_magnifier_enabled;
bool stream_warmup_mode;
bool stream_idle;
int bar_index;
};

// Pine's bounded retained-child/recreated-parent exception. The complete
// source-shape rule lives in this component; this is an ownership transfer,
// not a generic native activation/dependency scheduler.
class OrderPriority {
public:
static constexpr uint64_t schema_version = 1;
void attach() { attached_ = true; }
bool attached() const { return attached_; }
bool retained_parent_first() const { return retained_parent_first_; }
void metadata(const std::string& key, double value) {
// One declaration owner, including values received while detached.
// Metadata never selects Pine execution. Reattachment preserves off.
if (key == "flat_retained_child_fresh_parent_order")
retained_parent_first_ = std::isfinite(value) && value > 0.0;
}
std::optional<broker::OrderPriorityDecision> select(
const OrderPriorityContext& ctx,
const std::vector<PendingOrder>& book) const;
private:
bool attached_ = false;
bool retained_parent_first_ = true;
};

} // namespace pineforge::compat::pine
35 changes: 18 additions & 17 deletions include/pineforge/engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "bar.hpp"
#include "broker_events.hpp"
#include "compat/pine/intraday_cap.hpp"
#include "compat/pine/order_priority.hpp"
#include "series.hpp"
#include "timeframe.hpp"
#include "magnifier.hpp"
Expand All @@ -36,6 +37,8 @@
// Generated constructors can explicitly select Pine cap compatibility before
// any host metadata setter. Older engines keep their legacy default behavior.
#define PINEFORGE_HAS_EXPLICIT_PINE_CAP_V1 1
// Explicitly selects cap and retained-parent priority compatibility only.
#define PINEFORGE_HAS_EXPLICIT_PINE_EXECUTION_ADAPTER_V1 1

namespace pineforge {

Expand Down Expand Up @@ -1100,10 +1103,10 @@ struct StrategyOverrides {

// The C++ subclass contract is internal, unlike pineforge.h's stable C ABI.
// Changing its layout or vtable requires all generated/native C++ objects to be rebuilt.
// v3 replaces the v2 cap scalars with a Pine policy and a close obligation.
// Version the mangled class name so a v2 header's member offsets/vtable cannot
// v4 adds detached Pine order-priority ownership to the v3 cap boundary.
// Version the mangled class name so older headers' member offsets/vtable cannot
// silently bind out-of-line members of this different object layout.
inline namespace engine_script_run_v3 {
inline namespace engine_script_run_v4 {
class BacktestEngine {
protected:
// --- Position state ---
Expand Down Expand Up @@ -1282,15 +1285,9 @@ class BacktestEngine {
// Historical fill-triggered recalculation is strictly opt-in. The false
// branch in dispatch_bar remains the legacy control path.
bool calc_on_order_fills_ = false;
// Narrow ordinary-POOC broker ordering rule for one exact book shape:
// while truly flat, a single reissued from_entry bracket can retain an
// older sequence slot than its same-source-bar pure-stop parent after the
// prior parent was explicitly cancelled and freshly recreated.
// That exact two-order book scans the parent first so the child can inspect
// the post-entry path before the close-time script body. The metadata key
// remains as an explicit A/B override; ordinary execution enables the
// TV-pinned rule by default.
bool flat_retained_child_fresh_parent_order_ = true;
// Detached on bare native construction. Only the explicit Pine frontend
// attachment can select its source-shape priority interpretation.
compat::pine::OrderPriority pine_order_priority_;
QtyType default_qty_type_ = QtyType::FIXED;
double default_qty_value_ = 1.0;
int pyramiding_ = 1; // max additional entries in same direction
Expand Down Expand Up @@ -4497,6 +4494,13 @@ class BacktestEngine {
// Explicit frontend selection, not a generic native risk switch. This
// preserves any prior declaration values and does not reset quota/state.
void enable_pine_intraday_cap() { max_intraday_filled_orders_.attach(); }
// Current execution-adapter scope: intraday cap + retained-parent priority.
// Idempotent configuration attachment, not a reset or universal Pine mode.
// Generated constructors call this before any host metadata is forwarded.
void attach_pine_execution_adapter() {
max_intraday_filled_orders_.attach();
pine_order_priority_.attach();
}
virtual ~BacktestEngine() = default;
virtual void on_bar(const Bar& bar) = 0;

Expand Down Expand Up @@ -4795,10 +4799,7 @@ class BacktestEngine {
margin_zero_cover_full_liquidation_ =
std::isfinite(value) && value > 0.0;
}
if (key == "flat_retained_child_fresh_parent_order") {
flat_retained_child_fresh_parent_order_ =
std::isfinite(value) && value > 0.0;
}
pine_order_priority_.metadata(key, value);
// Forward through the real base setter used by the C ABI. A selected
// frontend owns recognition and numeric validation; no derived shadow
// setter or cap-key interpretation belongs in this transport.
Expand Down Expand Up @@ -5141,5 +5142,5 @@ class BacktestEngine {
void trace(const std::string& name, int value) { trace(name, static_cast<double>(value)); }
};

} // inline namespace engine_script_run_v3
} // inline namespace engine_script_run_v4
} // namespace pineforge
23 changes: 23 additions & 0 deletions include/pineforge/order_priority.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once
#include <array>
#include <cstdint>

namespace pineforge::broker {

// Immutable assignments bound to exact pending objects, not reusable user IDs.
// A decision changes only the sequence tie-break, never phase or eligibility.
struct OrderPriorityAssignment {
uint64_t incarnation;
int64_t sequence;
};
struct OrderPriorityDecision {
std::array<OrderPriorityAssignment, 2> assignments;
int64_t sequence(uint64_t incarnation, int64_t fallback) const {
for (const auto& assignment : assignments) {
if (assignment.incarnation == incarnation) return assignment.sequence;
}
return fallback;
}
};

} // namespace pineforge::broker
Loading
Loading