From 149f77ce16ef84c6da77e67d812bf8fa88e51cde Mon Sep 17 00:00:00 2001 From: luisleo526 Date: Thu, 10 Sep 2026 18:33:24 +0800 Subject: [PATCH] Replace seven pending-order flags with quantity and event facts Track quantity requests and reservation bases, exact replacement predecessors, and callback birth/cursor provenance instead of independent Boolean labels. Capture resolved targets for deferred closes before later reservation binding. Keep the remaining Pine historical reach permission explicit. Preserve the v1 C mirror prefix with derived legacy outputs and appended facts. Version PendingOrder and BacktestEngine together to reject stale C++ objects. --- CMakeLists.txt | 1 + docs/pages/abi-stability.md | 32 +- docs/pages/quantity-intent.md | 61 +++ docs/pending-placement-receipts.md | 53 +++ include/pineforge/compat/pine/order_birth.hpp | 21 + .../pineforge/compat/pine/order_priority.hpp | 2 +- include/pineforge/engine.hpp | 92 ++-- include/pineforge/order_birth.hpp | 106 +++++ include/pineforge/pending_order_mirror.hpp | 38 +- include/pineforge/quantity_intent.hpp | 74 ++++ scripts/check_broker_state_hash_coverage.py | 91 +++- scripts/check_script_cpp_abi.py | 53 ++- scripts/gen_pending_order_mirror.py | 65 ++- scripts/pending_order_v1_prefix.json | 397 ++++++++++++++++++ scripts/test_broker_state_hash_coverage.py | 67 ++- src/compat/pine/order_birth.cpp | 23 + src/compat/pine/order_priority.cpp | 13 +- src/engine_fills.cpp | 126 +++--- src/engine_path_resolve.cpp | 4 +- src/engine_run.cpp | 164 +++++--- src/engine_state_hash.cpp | 36 +- src/engine_strategy_commands.cpp | 99 ++--- src/engine_stream.cpp | 2 +- src/pending_order_mirror.cpp | 56 ++- tests/CMakeLists.txt | 4 + .../pending_order_prefix/c45-fields.inc | 108 +++++ .../fixtures/pending_order_prefix/c45-v1.hpp | 113 +++++ tests/fixtures/pending_quantity/README.md | 3 + .../c45_pending_order_mirror.hpp | 136 ++++++ .../fixtures/script_cpp_abi/basec45/README.md | 1 + .../script_cpp_abi/basec45/headers.json.gz | Bin 0 -> 140384 bytes .../script_cpp_abi/basec45/manifest.json | 69 +++ tests/test_calc_on_order_fills.cpp | 4 +- ...st_default_flat_market_gross_admission.cpp | 6 +- tests/test_integer_short_margin_state.cpp | 6 +- tests/test_live_state_hash.cpp | 25 +- tests/test_order_birth_provenance.cpp | 284 +++++++++++++ tests/test_pending_order_core.cpp | 153 +++++++ tests/test_pending_placement_receipts.cpp | 172 ++++++++ tests/test_pending_quantity_intent.cpp | 367 ++++++++++++++++ tests/test_pooc_global_full_exit.cpp | 2 +- tests/test_prearmed_exit_path_cursor.cpp | 17 +- tests/test_stop_open_margin_script_state.cpp | 6 +- 43 files changed, 2801 insertions(+), 351 deletions(-) create mode 100644 docs/pages/quantity-intent.md create mode 100644 docs/pending-placement-receipts.md create mode 100644 include/pineforge/compat/pine/order_birth.hpp create mode 100644 include/pineforge/order_birth.hpp create mode 100644 include/pineforge/quantity_intent.hpp create mode 100644 scripts/pending_order_v1_prefix.json create mode 100644 src/compat/pine/order_birth.cpp create mode 100644 tests/fixtures/pending_order_prefix/c45-fields.inc create mode 100644 tests/fixtures/pending_order_prefix/c45-v1.hpp create mode 100644 tests/fixtures/pending_quantity/README.md create mode 100644 tests/fixtures/pending_quantity/c45_pending_order_mirror.hpp create mode 100644 tests/fixtures/script_cpp_abi/basec45/README.md create mode 100644 tests/fixtures/script_cpp_abi/basec45/headers.json.gz create mode 100644 tests/fixtures/script_cpp_abi/basec45/manifest.json create mode 100644 tests/test_order_birth_provenance.cpp create mode 100644 tests/test_pending_order_core.cpp create mode 100644 tests/test_pending_placement_receipts.cpp create mode 100644 tests/test_pending_quantity_intent.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6de6d2f7..d8503f29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,7 @@ endif() # === pineforge library ================================================= add_library(pineforge STATIC src/c_abi.cpp + src/compat/pine/order_birth.cpp src/compat/pine/order_priority.cpp src/engine_aux_security.cpp src/engine_fills.cpp diff --git a/docs/pages/abi-stability.md b/docs/pages/abi-stability.md index 7b614659..97186c61 100644 --- a/docs/pages/abi-stability.md +++ b/docs/pages/abi-stability.md @@ -112,30 +112,32 @@ 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 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 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. Frozen f864/v3 headers are also rejected by this v4 runtime. Both generated-style -and native-style current callers must still link. +runtime. The integrated quantity, predecessor and order-birth representation +changes `PendingOrder` and uses internal `engine_script_run_v5`. Exact frozen +c45/v4 headers are compiled before link tests: both native and generated-style +v4 callers must fail to link to the v5 archive, while matched v5 callers link. +Earlier exact base38/v2 and f864/v3 mismatch controls remain. A compile failure +cannot masquerade as mismatch protection; no pairing executable is run. `PINEFORGE_HAS_SCRIPT_RUN_PREPARE_V1` remains 1: it describes the existing hook capability, not the class layout version. Regenerate and rebuild a strategy module to obtain complete script-state reset; replacing an archive does not -retrofit an old module. Public C function signatures, POD layouts, -`PF_ABI_VERSION` (4), and `strategy_stream_api_version()` (1) are unchanged. +retrofit an old module. Public C function signatures, `PF_ABI_VERSION` (4), +and `strategy_stream_api_version()` (1) are unchanged. The pending-order v1 +mirror keeps all 108 existing field names/types/offsets and its full old prefix; +new typed facts append, and removed native booleans survive only as read-only +derived outputs. Size-limited reads keep old callers within their buffers. 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 v4 module. +its own matching runtime; this check does not turn it into a v5 module. -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/v3 fingerprints are +The integrated representation advances the broker fingerprint domain to +`pineforge-broker-state/v5` and stream fingerprint version to 5. These identify +changed serialized quantity/reservation, predecessor and birth facts, alongside +existing Pine policy state. The Pine component schema remains 1; it is +independent of the aggregate fingerprint version. Prior v2/v3/v4 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 diff --git a/docs/pages/quantity-intent.md b/docs/pages/quantity-intent.md new file mode 100644 index 00000000..d575c0e8 --- /dev/null +++ b/docs/pages/quantity-intent.md @@ -0,0 +1,61 @@ +`PendingOrder::quantity_request` separates an exit's original requested amount +from the numeric reservation made for that request. It replaces two stored +booleans that mixed those lifetimes. + +`QuantityIntent` contains exactly one of `Units(amount)`, +`Fraction(numerator, denominator)` or `All`. Fractional requests retain their +original representation; the Pine boundary supplies percentages as `P / 100` +without a floating-point divide/multiply round trip. An absent request belongs +to pending commands outside this exit-request contract. These are request +descriptors, not an additional numerical-admission policy. + +`QuantityReservation` records admitted units and the exposure basis used when +reserving them. A new request clears the old reservation; a reservation cannot +exist without an original request. Deferred exits acquire this receipt when a +live owner is bound. Additional per-entry bindings copy the original request +and record their own admitted amount and basis. Copying is by value. + +`qty` and `qty_percent` retain their current executable/reserved meaning in the +existing order paths. OCA can reduce executable quantity without changing +the original intent or its prior reservation receipt. Whether that receipt +covered less than its basis is a derived numerical comparison, using the +existing caller-supplied tolerances. A deferred fraction can be classified +before resolution; an unbound Units request cannot infer coverage without a +numeric basis. + +Consequently, a half-position request rounded to a minimum one-unit slot may +have `Fraction(50, 100)` intent and a full `1 / 1` reservation. Conversely an +`All` request clipped behind another reservation may hold only `3 / 4` of its +basis. Neither case can be represented correctly by one “partial” label. + +Deferred market-close instructions are another producer: `strategy.close` +has already resolved its source amount to a placement target before +`queue_deferred_close_order` runs. The request records that resolved source +target as `Units(qty_to_close)`, without an exposure reservation until a later +layered binding occurs. This is not a fixed executable-quantity promise: the +preserved Pine ANY-relative rule can turn target 1 on E2 into reservation 2 +with basis E4 after reversal. The initial target remains 1 while executable +`qty` and the later reservation are 2. It does not claim to retain the original +Pine percentage expression; that conversion already occurred. This preserves +the old initial nonpartial classification without fabricating a basis. + +The Pine percentage rounding, minimum-slot, reservation retention, one-shot +exit-ID and deferred/replacement policies remain explicit compatibility debt +in their existing callers. This change does not generalize those policies or +change their thresholds, financial assertions or execution order. + +The public `pf_pending_order_v1_t` retains its complete existing field prefix. +Its `requested_partial` and `full_percent_exit_request` fields are deprecated +read-only projections derived from the new authoritative values; native +decisions never read those output fields. New fields append: + +- `quantity_intent_kind`: 0 absent, 1 Units, 2 Fraction, 3 All. +- The relevant units or fraction numerator/denominator; inactive values are 0. +- Reservation presence, admitted units and basis units. + +All new facts and optional-presence discriminators participate in broker +hashing and mirror output. They are not waived. C ABI version 4 and stream +API version 1 remain unchanged, and size-limited mirror reads preserve older +callers. The internal C++ layout changes, so all consumers require a matching +rebuild and the integrated representation change requires a new internal +namespace/fingerprint epoch before publication. diff --git a/docs/pending-placement-receipts.md b/docs/pending-placement-receipts.md new file mode 100644 index 00000000..ff23599a --- /dev/null +++ b/docs/pending-placement-receipts.md @@ -0,0 +1,53 @@ +# Pending-order placement and replacement facts + +`PendingOrder::replaced_order_incarnation` identifies the immediate live +predecessor whose priority slot the newly accepted order retains. Zero means +fresh construction. The new order still receives its own fresh `incarnation`; +`created_seq` remains its scheduling priority, not identity. + +The receipt is populated by high-level MARKET/ENTRY, RAW, and primary EXIT +replacement. Named cancel followed by recreation is fresh; its separate +cancel/recreate receipt does not become a replacement. When a Pine exit +reissue materializes multiple legs, the primary leg inherits the preceding +primary's priority and predecessor; additional legs are fresh. This receipt +does not claim to enumerate all sibling objects erased by that reissue. + +The former native `created_by_same_id_replacement` Boolean and redundant +`replaced_exit_order_incarnation` scalar are removed. Replacement readers use +the authoritative predecessor. The conditional +`replaced_default_market_incarnation` remains a Pine qualification receipt: +it records additional predecessor kind, sizing, side, source-bar and cycle +conditions that generic replacement identity alone cannot establish later. + +The former `created_while_in_position` Boolean is also removed. Its production +meaning was EXIT-only: `strategy.exit` derived it from the same `effectively_flat` +calculation used for `created_position_side`; a positive deferred close used the +nonflat side it targeted. EXIT consumers now read that existing placement side. +The two non-EXIT checks were vacuous because their producers always left the +old Boolean false; those checks are removed without requiring flat placement. + +`created_position_side` is not renamed or reinterpreted as a universal physical +snapshot. MARKET/ENTRY/RAW capture physical exposure; a Pine EXIT captures +exposure after earlier same-evaluation close claims. The physical position can +still be open when such an EXIT captures FLAT. Existing cycle/carry fields and +their scopes are unchanged. A complete physical placement/close-claim model is +separate work. + +The public size-aware `pf_pending_order_v1_t` retains every existing field at +its original offset. Its old replacement Boolean, EXIT predecessor scalar and +in-position Boolean are deprecated derived **output projections**, never core +state or inputs. The legacy RAW projections remain false/zero; the appended +`replaced_order_incarnation` reports the true RAW predecessor. Dynamic-layout +readers can discover the new field; older prefix readers retain their layout. +The generator emits these projections explicitly without native storage or a +readback path. Native hashing includes the predecessor once and placement side +once; removed redundant fields need no independent hash state. + +This removes two of the 32 direct PendingOrder Boolean members, leaving 30 in +this component. It replaces one bit with factual identity and removes one +duplicate placement value; it is not a Boolean wrapper or a renamed policy +mask. Existing Pine priority/admission/close policies still read these facts +and retain their existing qualification rules. Public C ABI version 4 and +pending mirror version 1 remain unchanged. Final aggregate internal C++/hash +versioning and stale-object pairing are owned by the integrated refactor; +this component must not be published separately without that boundary. diff --git a/include/pineforge/compat/pine/order_birth.hpp b/include/pineforge/compat/pine/order_birth.hpp new file mode 100644 index 00000000..78906582 --- /dev/null +++ b/include/pineforge/compat/pine/order_birth.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "../../order_birth.hpp" + +namespace pineforge { inline namespace engine_script_run_v5 { struct PendingOrder; } } +namespace pineforge::compat::pine { + +// Historical Pine permissions remain policy, not physical birth facts. +enum class HistoricalBirthReach : int32_t { Standard, ExtremeWaypoints }; +inline bool first_open_fill_evaluation(const OrderBirth& birth) { + return birth.from_fill() && birth.evaluation_ordinal() == 1 + && birth.cursor().first_point(); +} +HistoricalBirthReach select_historical_birth_reach(const OrderBirth& birth, + bool requested_trailing_exit); +bool historical_cascade_reach(const PendingOrder& order); + +} // namespace pineforge::compat::pine + +namespace pineforge { +using PineHistoricalBirthReach = compat::pine::HistoricalBirthReach; +} diff --git a/include/pineforge/compat/pine/order_priority.hpp b/include/pineforge/compat/pine/order_priority.hpp index 2e22275b..3621990f 100644 --- a/include/pineforge/compat/pine/order_priority.hpp +++ b/include/pineforge/compat/pine/order_priority.hpp @@ -5,7 +5,7 @@ #include #include -namespace pineforge { struct PendingOrder; } +namespace pineforge { inline namespace engine_script_run_v5 { struct PendingOrder; } } namespace pineforge::compat::pine { struct OrderPriorityContext { diff --git a/include/pineforge/engine.hpp b/include/pineforge/engine.hpp index 2c7eb76f..7a75d98a 100644 --- a/include/pineforge/engine.hpp +++ b/include/pineforge/engine.hpp @@ -13,6 +13,9 @@ #include "na.hpp" #include "bar.hpp" #include "broker_events.hpp" +#include "quantity_intent.hpp" +#include "order_birth.hpp" +#include "compat/pine/order_birth.hpp" #include "compat/pine/intraday_cap.hpp" #include "compat/pine/order_priority.hpp" #include "series.hpp" @@ -399,6 +402,9 @@ enum class ShortSeedCollisionRole : uint8_t { FINAL_SHORT, }; +// PendingOrder crosses out-of-line helper boundaries independently of the +// engine class, so its changed layout must carry the same internal epoch. +inline namespace engine_script_run_v5 { struct PendingOrder { std::string id; std::string from_entry; // for exit orders @@ -426,12 +432,12 @@ struct PendingOrder { // which intentionally survives same-id replacement to keep broker ordering // stable, incarnation is never copied or reused by a replacement. uint64_t incarnation = 0; - // True when this pending object was created by reissuing an id that was - // already live. The fresh incarnation above identifies the new call, but - // created_seq intentionally retains the replaced order's broker priority. - // Exact clean-room two-call rules must fail closed on this provenance - // rather than mistaking retained priority for current source order. - bool created_by_same_id_replacement = false; + // Exact live object whose priority slot this newly accepted order replaces. + // Fresh and cancel-then-recreate orders carry zero. This is causal identity, + // not a Boolean source-shape label: every entry, RAW and primary exit path + // records its immediate predecessor before that object is erased. Reissued + // extra exit legs are fresh objects and do not share the primary receipt. + uint64_t replaced_order_incarnation = 0; // Exact default MARKET replaced on this source bar. A priced order or // a prior-bar carry with the same id does not prove this call topology. uint64_t replaced_default_market_incarnation = 0; @@ -440,11 +446,6 @@ struct PendingOrder { // Mark only the exact later MARKET objects after that fill; a reissue // creates a fresh object, and cancelled siblings spend no broker event. bool declined_by_replaced_short_market = false; - // For a strategy.exit replacement, the unique incarnation of the exact - // matching (id, from_entry) EXIT object it replaced. Zero for a fresh - // child. This correlates retained broker priority with a concrete prior - // child rather than a replacement-shaped call sequence created later. - uint64_t replaced_exit_order_incarnation = 0; // Incarnation of the live priced ENTRY removed by strategy.cancel(id) // earlier in the same source evaluation, copied only onto the first fresh // same-id strategy.entry call and then consumed. Zero means there is no @@ -466,32 +467,14 @@ struct PendingOrder { // suppressed leg carries into the next bar as an ordinary order. bool coof_suppress_stop_on_entry_bar = false; bool coof_suppress_limit_on_entry_bar = false; - // True only when this order was emitted by a historical - // calc_on_order_fills execution. POOC must not confuse that intrabar - // origin with an order emitted by the ordinary close-time execution. - bool created_during_coof_recalc = false; - // Stronger provenance for orders born specifically in the recalculation - // triggered by a close-point (C) fill. C has already been consumed: no - // order from that recalculation may refill at C or inspect the elapsed - // wick. A POOC market instruction has missed its only eligible close and - // expires unless an ordinary execution reissues it; priced GTC orders - // become ordinary carried orders on the next bar. - bool coof_born_at_close_recalc = false; - // KI-67 cascade provenance: true when this order was placed by a MID-BAR - // fill recalc (a recalc chain that did not own the first fill event at the - // bar-open tick). Such "cascade" orders are eligible ONLY at the remaining - // EXTREME waypoints (W1/W2) of the historical 4-tick path — never - // intra-segment at an exact level and never at C — for the bar they are - // born on; at bar end they convert to ordinary resting orders. Orders born - // in that first BAR-OPEN recalc (or resting at bar start) keep standard - // exact-level semantics and leave this false. Scoped to the historical - // path; the magnifier path (bar_magnifier_enabled_) ignores this bit. - // - // ENTRY cascade orders use the plain "extreme-waypoint only" reach above. - // strategy.exit cascade orders follow the finer KI-67 Model S rule - // ("R-cascade-gapjump") captured by the two fields below. - bool coof_born_mid_bar = false; - // KI-67 exit cascade (Model S). Set at birth for a coof_born_mid_bar + // Immutable evaluation/fill origin, captured once for this incarnation. + // Historical extreme-only/trailing permissions live in compat::pine. + OrderBirth birth; + // Disclosed Pine historical fill permission. Trigger fields can be + // neutralized before deferred compaction; those mutations must not + // rewrite a birth-time permission or masquerade as a different origin. + PineHistoricalBirthReach pine_birth_reach = PineHistoricalBirthReach::Standard; + // KI-67 exit cascade (Model S). Set at birth for a Pine historical-cascade // strategy.exit order: the historical-path LEG index (0 = O->W1, 1 = W1->W2, // 2 = W2->C) the triggering intrabar fill (coof_cursor_price_ "ap") landed // on — the "in-flight" leg. -1 when the order is not a mid-bar cascade exit, @@ -506,6 +489,9 @@ struct PendingOrder { // 0 and gets its gap attempt at W1. Marketable STOP never uses that extension. // Otherwise subsequent legs exact-fill, while a terminal/off-path order rolls. bool coof_cascade_inflight_fires = false; + // Placement exposure used by this order. ENTRY/RAW capture the physical + // side; Pine EXIT captures the exposure after earlier same-evaluation + // close claims. This is not a universal physical-position snapshot. PositionSide created_position_side = PositionSide::FLAT; // Monotonic identity of the live position instance at placement. Side // alone is insufficient: a resting order can survive LONG -> SHORT -> @@ -869,10 +855,10 @@ struct PendingOrder { double signal_close_mc_remaining_qty = std::numeric_limits::quiet_NaN(); std::string comment; // order comment for trade reporting - bool requested_partial = false; // true iff caller passed qty_percent < 100 - // Preserve the original default/full-percent EXIT call before reservation - // normalization can turn a sub-lot partial request into a full-size order. - bool full_percent_exit_request = false; + // Original exit amount and its latest resolved reservation basis. qty and + // qty_percent remain the executable/reserved values used by existing Pine + // reservation rules; their later reduction cannot rewrite caller intent. + QuantityRequest quantity_request; // Narrow POOC global-full-exit candidate. ``qty`` deliberately keeps the // normal finite reservation so sibling exits see and respect its capacity. // At fill time this bit upgrades that one reservation to the full live @@ -890,7 +876,6 @@ struct PendingOrder { // tracking global EXIT is placed. Same-id replacement constructs a fresh // PendingOrder and therefore drops the relation; later orders never get it. bool pooc_global_full_exit_bound_add = false; - bool created_while_in_position = false; // true if position was open when order was placed // round 8 family S — TradingView's same-bar MARKET transaction (ledger // note log-20260905t143024z-76025577; 15 lab tv sensor tapes famS-dbl-*, // famS-rev-plus-close, famS-adm-{es,nq}-{1e6,500k} on CME_MINI:ES1!/NQ1! @@ -1050,6 +1035,8 @@ struct PendingOrder { ShortSeedCollisionRole::NONE; }; + } // inline namespace engine_script_run_v5 (PendingOrder) + // default_qty_type constants (matches TradingView) enum class QtyType { FIXED = 0, PERCENT_OF_EQUITY = 1, CASH = 2 }; @@ -1103,10 +1090,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. -// v4 adds detached Pine order-priority ownership to the v3 cap boundary. +// v5 integrates typed quantity, replacement identity and causal order birth. // 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_v4 { +inline namespace engine_script_run_v5 { class BacktestEngine { protected: // --- Position state --- @@ -2106,7 +2093,7 @@ class BacktestEngine { || order.created_bar != bar_index_ || order.created_position_side != PositionSide::FLAT || order.created_after_position_close_in_bar - || order.created_by_same_id_replacement + || (order.replaced_order_incarnation != 0) || order.oca_type != 0 || !order.oca_name.empty() || position_side_ != PositionSide::FLAT || position_entry_count_ != 0 || !pyramid_entries_.empty() @@ -2150,7 +2137,7 @@ class BacktestEngine { || order.created_bar != bar_index_ || order.created_position_side != PositionSide::FLAT || order.created_after_position_close_in_bar - || order.created_during_coof_recalc || order.created_by_same_id_replacement + || order.birth.from_fill() || (order.replaced_order_incarnation != 0) || !order.oca_name.empty() || order.oca_type != 0 || position_side_ != PositionSide::FLAT || position_entry_count_ != 0 || !pyramid_entries_.empty() || pyramiding_ < 0 || pyramiding_ > 1 @@ -3162,7 +3149,7 @@ class BacktestEngine { // KI-67: true only while the active fill recalc owns the FIRST fill event // at the bar-open tick (O). Orders placed while this holds keep STANDARD // exact-level semantics. Later fills at that same O, like fills at every - // other path point, are MID-BAR cascades (PendingOrder::coof_born_mid_bar). + // other path point, are MID-BAR cascades (the Pine historical cascade permission). bool coof_recalc_at_bar_open_ = false; // True only while executing a fill recalc triggered by a later fill event // at O, after the first O fill has already consumed bar-open provenance. @@ -4409,6 +4396,7 @@ class BacktestEngine { // Shared by run(), run_simple_bar_loop, and the no-magnifier aggregation // path. The magnifier tick loop does NOT use this — it gates the sequence // on is_last_tick_ and forces is_first_tick_ before on_bar. + OrderBirth capture_order_birth() const; void invoke_chart_on_bar(const Bar& bar); void dispatch_bar(); void dispatch_bar_calc_on_order_fills(); @@ -4418,16 +4406,14 @@ class BacktestEngine { uint64_t execute_coof_script_body(const Bar& script_bar, double broker_cursor_price, bool cursor_is_bar_point, - bool is_fill_recalc, - bool cursor_is_bar_close, - bool recalc_at_bar_open, + const OrderBirth& evaluation_origin, uint64_t direct_fill_event_budget, bool opening_money_prefix = false); uint64_t run_coof_recalc_chain(const Bar& script_bar, double broker_cursor_price, bool cursor_is_bar_point, - bool cursor_is_bar_close, - bool recalc_at_bar_open, + BirthCursor cursor, + uint64_t& evaluation_ordinal, uint64_t triggering_events, uint64_t max_events, uint64_t events_already, @@ -5142,5 +5128,5 @@ class BacktestEngine { void trace(const std::string& name, int value) { trace(name, static_cast(value)); } }; -} // inline namespace engine_script_run_v4 +} // inline namespace engine_script_run_v5 } // namespace pineforge diff --git a/include/pineforge/order_birth.hpp b/include/pineforge/order_birth.hpp new file mode 100644 index 00000000..ed4a4038 --- /dev/null +++ b/include/pineforge/order_birth.hpp @@ -0,0 +1,106 @@ +#pragma once +#include +#include +#include +#include + +namespace pineforge { + +// Geometry supplied by the dispatcher, never inferred from equal OHLC prices. +enum class BirthCursorDomain : int32_t { None, HistoricalPath, MagnifierTicks }; +enum class BirthCursorPosition : int32_t { None, Point, Segment }; +enum class OrderBirthCause : int32_t { + Unattributed, // manually constructed records; no event is asserted + DirectCommand, // native API call outside a chart evaluation + ChartEvaluation, + FillEvaluation, +}; + +class BirthCursor { +public: + BirthCursor() = default; + static BirthCursor point(BirthCursorDomain domain, int index, int count) { + return make(domain, BirthCursorPosition::Point, index, count); + } + static BirthCursor segment(BirthCursorDomain domain, int index, int count) { + return make(domain, BirthCursorPosition::Segment, index, count); + } + BirthCursorDomain domain() const { return domain_; } + BirthCursorPosition position() const { return position_; } + int index() const { return index_; } + int count() const { return count_; } + bool first_point() const { return position_ == BirthCursorPosition::Point && index_ == 0; } + bool terminal_point() const { + return position_ == BirthCursorPosition::Point && index_ + 1 == count_; + } + int following_segment() const { return terminal_point() ? -1 : index_; } +private: + static BirthCursor make(BirthCursorDomain domain, BirthCursorPosition position, + int index, int count) { + if ((domain != BirthCursorDomain::HistoricalPath && domain != BirthCursorDomain::MagnifierTicks) + || count <= 0 || (domain == BirthCursorDomain::HistoricalPath && count != 4) + || index < 0 || index >= count + || (position == BirthCursorPosition::Segment && index + 1 >= count)) + throw std::invalid_argument("invalid order-birth dispatch cursor"); + BirthCursor out; + out.domain_ = domain; out.position_ = position; + out.index_ = index; out.count_ = count; + return out; + } + BirthCursorDomain domain_ = BirthCursorDomain::None; + BirthCursorPosition position_ = BirthCursorPosition::None; + int index_ = -1; + int count_ = 0; +}; + +// Immutable value: fields have no setters. Replacement constructs a new value; +// copy/move of an order preserves the complete receipt. A grouped callback may +// cover an actual contiguous fill-event interval; it does not invent one fill. +class OrderBirth { +public: + OrderBirth() = default; + static OrderBirth direct_command(int bar, int64_t timestamp) { + return ordinary(OrderBirthCause::DirectCommand, bar, timestamp); + } + static OrderBirth chart_evaluation(int bar, int64_t timestamp) { + return ordinary(OrderBirthCause::ChartEvaluation, bar, timestamp); + } + static OrderBirth fill_evaluation(int bar, int64_t timestamp, BirthCursor cursor, + double price, uint64_t first_fill, + uint64_t last_fill, uint64_t evaluation_ordinal) { + if (bar < 0 || cursor.domain() == BirthCursorDomain::None || !std::isfinite(price) + || first_fill == 0 || last_fill < first_fill || evaluation_ordinal == 0) + throw std::invalid_argument("invalid order-birth fill evaluation"); + OrderBirth out = ordinary(OrderBirthCause::FillEvaluation, bar, timestamp); + out.cursor_ = cursor; out.cursor_price_ = price; + out.first_fill_ = first_fill; out.last_fill_ = last_fill; + out.evaluation_ordinal_ = evaluation_ordinal; + return out; + } + OrderBirthCause cause() const { return cause_; } + bool from_fill() const { return cause_ == OrderBirthCause::FillEvaluation; } + bool at_terminal_fill() const { return from_fill() && cursor_.terminal_point(); } + int bar() const { return bar_; } + int64_t timestamp() const { return timestamp_; } + const BirthCursor& cursor() const { return cursor_; } + double cursor_price() const { return cursor_price_; } + uint64_t first_fill() const { return first_fill_; } + uint64_t last_fill() const { return last_fill_; } + uint64_t evaluation_ordinal() const { return evaluation_ordinal_; } +private: + static OrderBirth ordinary(OrderBirthCause cause, int bar, int64_t timestamp) { + OrderBirth out; + out.cause_ = cause; out.bar_ = bar; out.timestamp_ = timestamp; + return out; + } + OrderBirthCause cause_ = OrderBirthCause::Unattributed; + int bar_ = -1; + int64_t timestamp_ = 0; + BirthCursor cursor_; + double cursor_price_ = std::numeric_limits::quiet_NaN(); + uint64_t first_fill_ = 0; + uint64_t last_fill_ = 0; + uint64_t evaluation_ordinal_ = 0; +}; + +} // namespace pineforge diff --git a/include/pineforge/pending_order_mirror.hpp b/include/pineforge/pending_order_mirror.hpp index ce672052..0bc79a65 100644 --- a/include/pineforge/pending_order_mirror.hpp +++ b/include/pineforge/pending_order_mirror.hpp @@ -1,5 +1,5 @@ // GENERATED by scripts/gen_pending_order_mirror.py from include/pineforge/engine.hpp -- do not edit. -// 98 PendingOrder members mirrored (108 POD fields incl. struct_version/size). +// 94 PendingOrder members mirrored (128 POD fields incl. struct_version/size). #pragma once #include @@ -39,18 +39,18 @@ typedef struct pf_pending_order_v1_s { int32_t created_bar; int64_t created_seq; uint64_t incarnation; - uint8_t created_by_same_id_replacement; + uint8_t created_by_same_id_replacement; // deprecated, derived output only uint64_t replaced_default_market_incarnation; uint8_t declined_by_replaced_short_market; - uint64_t replaced_exit_order_incarnation; + uint64_t replaced_exit_order_incarnation; // deprecated, derived output only uint64_t recreated_after_named_cancelled_entry_incarnation; uint64_t named_cancel_surviving_exit_incarnation; uint8_t stop_limit_activated; uint8_t coof_suppress_stop_on_entry_bar; uint8_t coof_suppress_limit_on_entry_bar; - uint8_t created_during_coof_recalc; - uint8_t coof_born_at_close_recalc; - uint8_t coof_born_mid_bar; + uint8_t created_during_coof_recalc; // deprecated, derived output only + uint8_t coof_born_at_close_recalc; // deprecated, derived output only + uint8_t coof_born_mid_bar; // deprecated, derived output only int32_t coof_cascade_seg_i; uint8_t coof_cascade_inflight_fires; int32_t created_position_side; @@ -96,12 +96,12 @@ typedef struct pf_pending_order_v1_s { char comment[64]; uint8_t comment_truncated; uint64_t comment_hash64; - uint8_t requested_partial; - uint8_t full_percent_exit_request; + uint8_t requested_partial; // deprecated, derived output only + uint8_t full_percent_exit_request; // deprecated, derived output only uint8_t pooc_global_full_exit_dynamic_qty; uint8_t pooc_global_full_exit_tracks_bound_adds; uint8_t pooc_global_full_exit_bound_add; - uint8_t created_while_in_position; + uint8_t created_while_in_position; // deprecated, derived output only uint8_t sbmt_member; double sbmt_own_qty; double sbmt_tx_qty; @@ -120,6 +120,26 @@ typedef struct pf_pending_order_v1_s { double suppressed_close_consumed_ledger_qty; double suppressed_close_retired_ledger_qty; int32_t short_seed_collision_role; + uint64_t replaced_order_incarnation; + int64_t birth_timestamp; + int32_t birth_cause; + int32_t birth_bar; + int32_t birth_cursor_domain; + int32_t birth_cursor_position; + int32_t birth_cursor_index; + int32_t birth_cursor_count; + double birth_cursor_price; + uint64_t birth_first_fill; + uint64_t birth_last_fill; + uint64_t birth_evaluation_ordinal; + int32_t pine_birth_reach; + uint64_t quantity_intent_kind; + double quantity_intent_units; + double quantity_intent_numerator; + double quantity_intent_denominator; + uint8_t quantity_reservation_present; + double quantity_reservation_units; + double quantity_reservation_basis_units; } pf_pending_order_v1_t; /* One row of the self-describing layout table returned by diff --git a/include/pineforge/quantity_intent.hpp b/include/pineforge/quantity_intent.hpp new file mode 100644 index 00000000..41262fee --- /dev/null +++ b/include/pineforge/quantity_intent.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include +#include + +namespace pineforge { + +enum class QuantityIntentKind { Units, Fraction, All }; + +// A requested amount, before executable quantity is rounded, reserved or +// reduced. A fraction retains its numerator and denominator so a frontend can +// preserve the caller's exact representation (for example 50 / 100). +class QuantityIntent { + struct Units { double amount; }; + struct Fraction { double numerator; double denominator; }; + struct All {}; + using Value = std::variant; +public: + using Kind = QuantityIntentKind; + static QuantityIntent units(double amount) { return QuantityIntent(Units{amount}); } + static QuantityIntent fraction(double numerator, double denominator) { + if (!std::isfinite(denominator) || denominator <= 0.0) + throw std::invalid_argument("quantity fraction requires a positive finite denominator"); + return QuantityIntent(Fraction{numerator, denominator}); + } + static QuantityIntent all() { return QuantityIntent(All{}); } + Kind kind() const { return static_cast(value_.index()); } + double units() const { return std::get(value_).amount; } + double numerator() const { return std::get(value_).numerator; } + double denominator() const { return std::get(value_).denominator; } +private: + explicit QuantityIntent(Value value) : value_(value) {} + Value value_; +}; + +// A reservation is a causal quantity snapshot. Later OCA reductions of the +// executable order do not rewrite its original request or this basis. +struct QuantityReservation { + double units; + double basis_units; +}; + +class QuantityRequest { +public: + const std::optional& intent() const { return intent_; } + const std::optional& reservation() const { return reservation_; } + void request(QuantityIntent intent) { + intent_ = intent; + reservation_.reset(); + } + void reserve(double units, double basis_units) { + if (!intent_) throw std::logic_error("quantity reservation requires an original request"); + reservation_ = QuantityReservation{units, basis_units}; + } + bool requests_all() const { + return intent_ && intent_->kind() == QuantityIntent::Kind::All; + } + // Tolerances belong to the caller's quantity policy. No minimum lot, + // percentage rounding or strategy-specific eligibility lives here. + bool is_partial(double units_tolerance, double fraction_tolerance) const { + if (reservation_) + return reservation_->units < reservation_->basis_units - units_tolerance; + return intent_ && intent_->kind() == QuantityIntent::Kind::Fraction + && intent_->numerator() < intent_->denominator() - fraction_tolerance; + } +private: + // Absent for commands that do not carry this exit-request contract. + std::optional intent_; + std::optional reservation_; +}; + +} // namespace pineforge diff --git a/scripts/check_broker_state_hash_coverage.py b/scripts/check_broker_state_hash_coverage.py index 397ec222..78d35a62 100644 --- a/scripts/check_broker_state_hash_coverage.py +++ b/scripts/check_broker_state_hash_coverage.py @@ -191,6 +191,8 @@ def _class_fields(src: str, name: str) -> dict[str, str]: if ch == ";": decl = " ".join(statement.split()) statement = "" + if re.fullmatch(re.escape(name) + r"\(\)\s*=\s*default", decl): + continue if decl and not decl.startswith("using "): match = re.fullmatch(r"((?:(?:static|constexpr|const)\s+)*[\w:<>]+)\s+(\w+)(?:\s*=\s*[^,]+)?", decl) if not match or match[2] in fields: @@ -339,8 +341,78 @@ def folds(name: str, expression: str) -> list[tuple[str, str]]: raise ValueError("intraday hash block must be unconditional at broker_state_hash function scope") +def _quantity_request_coverage(quantity: str, source: str) -> None: + quantity = _strip_cpp_comments(quantity) + if _class_fields(quantity, "QuantityIntent") != {"value_": "Value"}: + raise ValueError("QuantityIntent data changed; update its complete hash encoding") + if _class_fields(quantity, "QuantityRequest") != { + "intent_": "std::optional", + "reservation_": "std::optional"}: + raise ValueError("QuantityRequest data changed; update its complete hash encoding") + if struct_members(quantity, "Units") != [("double", "amount")]: + raise ValueError("Units intent fields changed") + if struct_members(quantity, "Fraction") != [("double", "numerator"), ("double", "denominator")]: + raise ValueError("Fraction intent fields changed") + if struct_members(quantity, "QuantityReservation") != [("double", "units"), ("double", "basis_units")]: + raise ValueError("QuantityReservation fields changed") + if not re.search(r"struct\s+All\s*\{\s*\}", quantity): + raise ValueError("All intent must have no numeric payload") + if not re.search(r"enum\s+class\s+QuantityIntentKind\s*\{\s*Units,\s*Fraction,\s*All\s*\}", quantity): + raise ValueError("QuantityIntentKind hash encoding changed") + if not re.search(r"using\s+Value\s*=\s*std::variant", quantity): + raise ValueError("QuantityIntent variant discriminator changed") + loop = _collection_loop_body(source, "pending_orders_", "o") + expected = """f.b(o.quantity_request.intent().has_value()); + if (const auto& intent = o.quantity_request.intent()) { + f.i(static_cast(intent->kind())); + if (intent->kind() == QuantityIntent::Kind::Units) f.d(intent->units()); + else if (intent->kind() == QuantityIntent::Kind::Fraction) { + f.d(intent->numerator()); f.d(intent->denominator()); + } + } + f.b(o.quantity_request.reservation().has_value()); + if (const auto& reservation = o.quantity_request.reservation()) { + f.d(reservation->units); f.d(reservation->basis_units); + }""" + compact = re.sub(r"\s+", "", loop) + folded = re.sub(r"\s+", "", expected) + if compact.count(folded) != 1: + raise ValueError("quantity intent/reservation hash requires every field and presence discriminator") + prefix = compact[:compact.index(folded)] + if prefix.count("{") != prefix.count("}"): + raise ValueError("quantity request hash must be unconditional inside its order loop") + +def _birth_coverage(header: str, source: str) -> None: + header = _strip_cpp_comments(header) + expected = { + "BirthCursor": {"domain_": "BirthCursorDomain", "position_": "BirthCursorPosition", "index_": "int", "count_": "int"}, + "OrderBirth": {"cause_": "OrderBirthCause", "bar_": "int", "timestamp_": "int64_t", "cursor_": "BirthCursor", "cursor_price_": "double", "first_fill_": "uint64_t", "last_fill_": "uint64_t", "evaluation_ordinal_": "uint64_t"}, + } + for name, fields in expected.items(): + if _class_fields(header, name) != fields: + raise ValueError(f"{name} fields changed; classify every nested birth fact") + loop = _collection_loop_body(source, "pending_orders_", "o") + compact = re.sub(r"\s+", "", loop) + expressions = [ + "f.i(static_cast(o.birth.cause()));", + "f.i(o.birth.bar());", "f.i(o.birth.timestamp());", + "f.i(static_cast(o.birth.cursor().domain()));", + "f.i(static_cast(o.birth.cursor().position()));", + "f.i(o.birth.cursor().index());", "f.i(o.birth.cursor().count());", + "f.d(o.birth.cursor_price());", "f.u(o.birth.first_fill());", + "f.u(o.birth.last_fill());", "f.u(o.birth.evaluation_ordinal());", + "f.i(static_cast(o.pine_birth_reach));", + ] + expected = "".join(expressions) + if compact.count(expected) != 1: + raise ValueError("birth facts and Pine reach require one complete contiguous hash block") + prefix = compact[:compact.index(expected)] + if prefix.count("{") != prefix.count("}"): + raise ValueError("birth facts must be unconditionally hashed at order-loop scope") + + def _runtime_version_coverage(header: str, source: str, stream: str) -> None: - """The v4 layout and serialized-state contracts must advance together. + """The v5 layout and serialized-state contracts must advance together. Pin the actual hash entry points, rather than accepting a version string mentioned in a comment or an unrelated helper. Public C ABI versions have @@ -348,18 +420,18 @@ def _runtime_version_coverage(header: str, source: str, stream: str) -> None: """ header = _strip_cpp_comments(header) namespaces = re.findall(r"inline\s+namespace\s+(engine_script_run_v\d+)\s*\{", header) - if namespaces != ["engine_script_run_v4"]: - raise ValueError("BacktestEngine layout requires internal namespace engine_script_run_v4") + if namespaces != ["engine_script_run_v5", "engine_script_run_v5"]: + raise ValueError("PendingOrder and BacktestEngine layouts require internal namespace engine_script_run_v5") broker = _one_braced_body(source, r"uint64_t\s+BacktestEngine::broker_state_hash\(\)\s+const\s*\{", "broker hash") - if not re.match(r'\s*Fnv\s+f;\s*f\.s\("pineforge-broker-state/v4"\);', broker): - raise ValueError("broker hash must start with pineforge-broker-state/v4") + if not re.match(r'\s*Fnv\s+f;\s*f\.s\("pineforge-broker-state/v5"\);', broker): + raise ValueError("broker hash must start with pineforge-broker-state/v5") stream_body = _one_braced_body(_strip_cpp_comments(stream), r"uint64_t\s+BacktestEngine::stream_state_hash\(\)\s+const\s*\{", "stream hash") compact = re.sub(r"\s+", "", stream_body) - fold = "integer(4);integer(broker_state_hash());" + fold = "integer(5);integer(broker_state_hash());" if compact.count(fold) != 1: - raise ValueError("stream hash requires version 4 followed by the broker hash") + raise ValueError("stream hash requires version 5 followed by the broker hash") prefix = compact[:compact.index(fold)] if prefix.count("{") != prefix.count("}") or (prefix and prefix[-1] not in ";}"): raise ValueError("stream version fold must be unconditional at function scope") @@ -374,7 +446,9 @@ def main(root: Path = ROOT) -> int: src = _strip_cpp_comments(src_raw) try: _runtime_version_coverage(hpp, src, (root / "src/engine_stream.cpp").read_text()) + _birth_coverage((root / "include/pineforge/order_birth.hpp").read_text(), src) _opening_coverage((root / "include/pineforge/broker_events.hpp").read_text(), src) + _quantity_request_coverage((root / "include/pineforge/quantity_intent.hpp").read_text(), src) _intraday_coverage( (root / "include/pineforge/compat/pine/intraday_cap.hpp").read_text(), (root / "include/pineforge/compat/pine/intraday_order_budget.hpp").read_text(), @@ -390,6 +464,9 @@ def main(root: Path = ROOT) -> int: return 1 waivers = {k: v for k, v in all_waivers.items() if not k.startswith((PENDING_WAIVER_PREFIX, PYRAMID_WAIVER_PREFIX))} + if "pending_order.quantity_request" in all_waivers: + print("check_broker_state_hash_coverage: quantity_request cannot be waived", file=sys.stderr) + return 1 po_waivers = {k[len(PENDING_WAIVER_PREFIX):]: v for k, v in all_waivers.items() if k.startswith(PENDING_WAIVER_PREFIX)} pe_waivers = {k[len(PYRAMID_WAIVER_PREFIX):]: v diff --git a/scripts/check_script_cpp_abi.py b/scripts/check_script_cpp_abi.py index 17ef0b03..32797b35 100644 --- a/scripts/check_script_cpp_abi.py +++ b/scripts/check_script_cpp_abi.py @@ -16,7 +16,7 @@ BASE_COMMIT = "38dc73e5503fe5395458e5f8df2a2ad78054a1ae" BASE_ENGINE_SHA256 = "06c937a1ccd31815ca7775268ac699ffdfddb1a1f19de4628b777f37e9a6d193" -CURRENT_NAMESPACE = "engine_script_run_v4" +CURRENT_NAMESPACE = "engine_script_run_v5" BASE_NAMESPACE = "engine_script_run_v2" FIXTURE = Path(__file__).resolve().parents[1] / "tests/fixtures/script_cpp_abi/base38" @@ -146,6 +146,10 @@ def main(): frozen_headers(cap_include, FIXTURE.parent / "basef864", "f864be590931ba08c8df5af983b33b2c29be9c67", "engine_script_run_v3", "54b35fffaa163a31467f8ba883e44e02f013a37216b558dfe3a4f28fbbe84dc2") + prior_include = root / "basec45/include" + frozen_headers(prior_include, FIXTURE.parent / "basec45", + "c45cf5a4d0e67a2ac098d9066977e1fa21c408a9", "engine_script_run_v4", + "3b4e2937a9b5f275dd119144373b1bf15e433092009500092cd32ea34963b293") common = [args.compiler, "-std=c++17", "-O0", *args.extra_flag] def compile_object(name, source, include): @@ -174,6 +178,32 @@ def compile_object(name, source, include): cap_symbols = compile_object("basef864_symbol_control", BASE_SYMBOL_CONTROL.replace("engine_script_run_v2", "engine_script_run_v3"), cap_include) + prior_native = compile_object("basec45_native", caller("engine_script_run_v4"), prior_include) + prior_generated = compile_object("basec45_generated", caller("engine_script_run_v4", True), prior_include) + prior_symbols = compile_object("basec45_symbol_control", + BASE_SYMBOL_CONTROL.replace("engine_script_run_v2", "engine_script_run_v4"), prior_include) + + priority_caller = """#include +#include +int main() { + pineforge::compat::pine::OrderPriority policy; + policy.attach(); + pineforge::compat::pine::OrderPriorityContext context{}; + std::vector orders(2); + return policy.select(context, orders).has_value() ? 1 : 0; +} +""" + priority_symbols = """#include +#include +namespace pineforge::compat::pine { +std::optional OrderPriority::select( + const OrderPriorityContext&, const std::vector&) const { return std::nullopt; } +} +""" + current_priority = compile_object("current_pending_priority", priority_caller, args.include) + prior_priority = compile_object("basec45_pending_priority", priority_caller, prior_include) + prior_priority_symbols = compile_object("basec45_pending_priority_symbols", priority_symbols, prior_include) + def link(name, obj, runtime, missing_namespace=None): linked = subprocess.run( [*common, str(obj), str(runtime), "-pthread", "-o", str(root / name)], @@ -215,7 +245,26 @@ def link(name, obj, runtime, missing_namespace=None): link("basef864_generated_to_current", cap_generated, args.library, "engine_script_run_v3") link("current_native_to_v3_symbol_control", current_native, cap_symbols, CURRENT_NAMESPACE) link("current_generated_to_v3_symbol_control", current_generated, cap_symbols, CURRENT_NAMESPACE) - print("9 translation units compiled; 6 positive links; 9 rejected links; no executable run") + link("basec45_native_to_v4_symbol_control", prior_native, prior_symbols) + link("basec45_generated_to_v4_symbol_control", prior_generated, prior_symbols) + link("basec45_native_to_current", prior_native, args.library, "engine_script_run_v4") + link("basec45_generated_to_current", prior_generated, args.library, "engine_script_run_v4") + link("current_native_to_v4_symbol_control", current_native, prior_symbols, CURRENT_NAMESPACE) + link("current_generated_to_v4_symbol_control", current_generated, prior_symbols, CURRENT_NAMESPACE) + link("current_pending_priority_to_current", current_priority, args.library) + link("basec45_pending_priority_to_v4_symbols", prior_priority, prior_priority_symbols) + for name, obj, runtime, expected in [ + ("basec45_pending_priority_to_current", prior_priority, args.library, "pineforge::PendingOrder"), + ("current_pending_priority_to_v4_symbols", current_priority, prior_priority_symbols, + "pineforge::engine_script_run_v5::PendingOrder"), + ]: + result = subprocess.run([*common, str(obj), str(runtime), "-pthread", "-o", str(root / name)], + capture_output=True, text=True, timeout=60) + if (result.returncode == 0 or "undefined" not in result.stderr.lower() + or "OrderPriority::select(" not in result.stderr or expected not in result.stderr): + raise RuntimeError(name + " did not reject the expected PendingOrder type: " + result.stderr) + print(name + ": rejected stale standalone PendingOrder argument type (not executed)") + print("15 translation units compiled; 10 positive links; 15 rejected links; no executable run") if __name__ == "__main__": diff --git a/scripts/gen_pending_order_mirror.py b/scripts/gen_pending_order_mirror.py index 57913243..4d127ad7 100644 --- a/scripts/gen_pending_order_mirror.py +++ b/scripts/gen_pending_order_mirror.py @@ -30,6 +30,7 @@ from __future__ import annotations import re +import json import sys from pathlib import Path @@ -55,6 +56,45 @@ "OrderType": ("int32_t", "(int32_t)src.{m}"), "PositionSide": ("int32_t", "(int32_t)src.{m}"), "ShortSeedCollisionRole": ("int32_t", "(int32_t)src.{m}"), + "PineHistoricalBirthReach": ("int32_t", "(int32_t)src.{m}"), +} +# Public v1 is append-only. Removed native fields survive only as one-way +# deprecated output projections at their original offsets. +LEGACY_OUTPUTS = { + "created_during_coof_recalc": "src.birth.from_fill() ? 1 : 0", + "coof_born_at_close_recalc": "src.birth.at_terminal_fill() ? 1 : 0", + "coof_born_mid_bar": "compat::pine::historical_cascade_reach(src) ? 1 : 0", + "created_by_same_id_replacement": "src.type != OrderType::RAW_ORDER && src.replaced_order_incarnation != 0 ? 1 : 0", + "replaced_exit_order_incarnation": "src.type == OrderType::EXIT ? src.replaced_order_incarnation : 0", + "created_while_in_position": "src.type == OrderType::EXIT && src.created_position_side != PositionSide::FLAT ? 1 : 0", + "requested_partial": "src.quantity_request.is_partial(1e-9, 1e-9) ? 1 : 0", + "full_percent_exit_request": "src.quantity_request.requests_all() ? 1 : 0", +} +COMPOSITE_MAP = { + "QuantityRequest": [ + ("intent_kind", "uint64_t", "src.{m}.intent() ? static_cast(src.{m}.intent()->kind()) + 1 : 0"), + ("intent_units", "double", "src.{m}.intent() && src.{m}.intent()->kind() == QuantityIntent::Kind::Units ? src.{m}.intent()->units() : 0.0"), + ("intent_numerator", "double", "src.{m}.intent() && src.{m}.intent()->kind() == QuantityIntent::Kind::Fraction ? src.{m}.intent()->numerator() : 0.0"), + ("intent_denominator", "double", "src.{m}.intent() && src.{m}.intent()->kind() == QuantityIntent::Kind::Fraction ? src.{m}.intent()->denominator() : 0.0"), + ("reservation_present", "uint8_t", "src.{m}.reservation().has_value() ? 1 : 0"), + ("reservation_units", "double", "src.{m}.reservation() ? src.{m}.reservation()->units : 0.0"), + ("reservation_basis_units", "double", "src.{m}.reservation() ? src.{m}.reservation()->basis_units : 0.0"), + ], + "OrderBirth": [ + # Start appended facts at the v1 struct's 8-byte boundary, preserving + # its trailing padding as well as all 108 field offsets. + ("timestamp", "int64_t", "src.{m}.timestamp()"), + ("cause", "int32_t", "(int32_t)src.{m}.cause()"), + ("bar", "int32_t", "src.{m}.bar()"), + ("cursor_domain", "int32_t", "(int32_t)src.{m}.cursor().domain()"), + ("cursor_position", "int32_t", "(int32_t)src.{m}.cursor().position()"), + ("cursor_index", "int32_t", "src.{m}.cursor().index()"), + ("cursor_count", "int32_t", "src.{m}.cursor().count()"), + ("cursor_price", "double", "src.{m}.cursor_price()"), + ("first_fill", "uint64_t", "src.{m}.first_fill()"), + ("last_fill", "uint64_t", "src.{m}.last_fill()"), + ("evaluation_ordinal", "uint64_t", "src.{m}.evaluation_ordinal()"), + ], } STRING_TYPES = frozenset({"std::string"}) @@ -159,7 +199,7 @@ def classify(ms: list[tuple[str, str]], waivers: dict[str, str]): for t, n in ms: if n in waivers: waived.append((t, n, waivers[n])) - elif t in STRING_TYPES or t in TYPE_MAP: + elif t in STRING_TYPES or t in TYPE_MAP or t in COMPOSITE_MAP: mirrored.append((t, n)) else: _fail(f"member {n} has unmapped type {t}; add it to TYPE_MAP or " @@ -172,8 +212,27 @@ def generate() -> tuple[str, str]: fields: list[str] = [] copies: list[str] = [] descs: list[tuple[str, str]] = [("struct_version", "uint32_t"), ("size", "uint32_t")] - for t, m in mirrored: - if t in STRING_TYPES: + prefix = json.loads((ROOT / "scripts/pending_order_v1_prefix.json").read_text())["members"] + native = dict((name, kind) for kind, name in mirrored) + for kind, name in prefix: + if name not in LEGACY_OUTPUTS and native.get(name) != kind: + _fail(f"public v1 prefix member {name} needs an explicit derived projection") + prefix_names = {name for _, name in prefix} + ordered = prefix + [(kind, name) for kind, name in mirrored if name not in prefix_names] + for t, m in ordered: + if m in LEGACY_OUTPUTS: + ct = TYPE_MAP[t][0] + fields.append(f" {ct} {m}; // deprecated, derived output only") + copies.append(f" out->{m} = {LEGACY_OUTPUTS[m]};") + descs.append((m, ct)) + elif t in COMPOSITE_MAP: + for suffix, ct, expr in COMPOSITE_MAP[t]: + prefix = "quantity" if t == "QuantityRequest" else m + name = f"{prefix}_{suffix}" + fields.append(f" {ct} {name};") + copies.append(f" out->{name} = {expr.format(m=m)};") + descs.append((name, ct)) + elif t in STRING_TYPES: fields += [f" char {m}[{STR_CAP}];", f" uint8_t {m}_truncated;", f" uint64_t {m}_hash64;"] diff --git a/scripts/pending_order_v1_prefix.json b/scripts/pending_order_v1_prefix.json new file mode 100644 index 00000000..6d9e0b3d --- /dev/null +++ b/scripts/pending_order_v1_prefix.json @@ -0,0 +1,397 @@ +{ + "source_commit": "c45cf5a4d0e67a2ac098d9066977e1fa21c408a9", + "members": [ + [ + "std::string", + "id" + ], + [ + "std::string", + "from_entry" + ], + [ + "OrderType", + "type" + ], + [ + "bool", + "is_long" + ], + [ + "double", + "limit_price" + ], + [ + "double", + "stop_price" + ], + [ + "double", + "trail_points" + ], + [ + "double", + "trail_price" + ], + [ + "double", + "trail_offset" + ], + [ + "double", + "profit_ticks" + ], + [ + "double", + "loss_ticks" + ], + [ + "double", + "qty" + ], + [ + "int", + "qty_type" + ], + [ + "double", + "qty_percent" + ], + [ + "std::string", + "oca_name" + ], + [ + "int", + "oca_type" + ], + [ + "int", + "created_bar" + ], + [ + "int64_t", + "created_seq" + ], + [ + "uint64_t", + "incarnation" + ], + [ + "bool", + "created_by_same_id_replacement" + ], + [ + "uint64_t", + "replaced_default_market_incarnation" + ], + [ + "bool", + "declined_by_replaced_short_market" + ], + [ + "uint64_t", + "replaced_exit_order_incarnation" + ], + [ + "uint64_t", + "recreated_after_named_cancelled_entry_incarnation" + ], + [ + "uint64_t", + "named_cancel_surviving_exit_incarnation" + ], + [ + "bool", + "stop_limit_activated" + ], + [ + "bool", + "coof_suppress_stop_on_entry_bar" + ], + [ + "bool", + "coof_suppress_limit_on_entry_bar" + ], + [ + "bool", + "created_during_coof_recalc" + ], + [ + "bool", + "coof_born_at_close_recalc" + ], + [ + "bool", + "coof_born_mid_bar" + ], + [ + "int8_t", + "coof_cascade_seg_i" + ], + [ + "bool", + "coof_cascade_inflight_fires" + ], + [ + "PositionSide", + "created_position_side" + ], + [ + "int64_t", + "created_position_cycle_seq" + ], + [ + "bool", + "created_after_position_close_in_bar" + ], + [ + "bool", + "over_pyramiding_cap_at_placement" + ], + [ + "int", + "same_id_stop_deferred_close_all_bar" + ], + [ + "uint64_t", + "same_id_stop_deferred_close_all_incarnation" + ], + [ + "bool", + "reverses_same_bar_market_from_flat" + ], + [ + "bool", + "paired_flat_market_candidate" + ], + [ + "double", + "paired_flat_market_own_qty" + ], + [ + "double", + "paired_flat_market_signal_close" + ], + [ + "double", + "paired_flat_market_signal_equity" + ], + [ + "double", + "paired_flat_market_signal_margin_pct" + ], + [ + "double", + "paired_flat_market_signal_pointvalue" + ], + [ + "double", + "paired_flat_market_signal_fx" + ], + [ + "int64_t", + "paired_flat_market_peer_seq" + ], + [ + "double", + "paired_flat_market_transaction_qty" + ], + [ + "bool", + "default_flat_market_gross_candidate" + ], + [ + "double", + "tv_carry_qty" + ], + [ + "double", + "frozen_default_qty" + ], + [ + "double", + "default_stop_placement_qty" + ], + [ + "double", + "default_stop_placement_equity" + ], + [ + "double", + "default_stop_placement_signal_close" + ], + [ + "double", + "default_stop_sizing_price" + ], + [ + "double", + "sizing_equity" + ], + [ + "double", + "sizing_price" + ], + [ + "double", + "sizing_fx" + ], + [ + "double", + "sizing_mark" + ], + [ + "bool", + "opening_affordability_exemption_candidate" + ], + [ + "bool", + "explicit_flat_admission_candidate" + ], + [ + "double", + "explicit_placement_equity" + ], + [ + "double", + "explicit_slipped_signal_close" + ], + [ + "double", + "affordability_placement_equity" + ], + [ + "double", + "affordability_signal_price" + ], + [ + "double", + "affordability_held_qty" + ], + [ + "bool", + "affordability_close_only" + ], + [ + "bool", + "rounded_signal_cost_close_only" + ], + [ + "int", + "signal_close_mc_bar" + ], + [ + "uint64_t", + "signal_close_mc_entry_incarnation" + ], + [ + "uint64_t", + "signal_close_mc_fill_seq" + ], + [ + "double", + "signal_close_mc_remaining_qty" + ], + [ + "std::string", + "comment" + ], + [ + "bool", + "requested_partial" + ], + [ + "bool", + "full_percent_exit_request" + ], + [ + "bool", + "pooc_global_full_exit_dynamic_qty" + ], + [ + "bool", + "pooc_global_full_exit_tracks_bound_adds" + ], + [ + "bool", + "pooc_global_full_exit_bound_add" + ], + [ + "bool", + "created_while_in_position" + ], + [ + "bool", + "sbmt_member" + ], + [ + "double", + "sbmt_own_qty" + ], + [ + "double", + "sbmt_tx_qty" + ], + [ + "bool", + "sbmt_kept_over_cap" + ], + [ + "double", + "sbmt_close_qty" + ], + [ + "bool", + "sbmt_close_buy" + ], + [ + "bool", + "suppress_as_declined_reversal_close" + ], + [ + "bool", + "dormant_bracket" + ], + [ + "bool", + "dormant_reissue_pending" + ], + [ + "double", + "dormant_original_stop_price" + ], + [ + "int", + "dormant_hold_bar" + ], + [ + "int", + "dormant_reversal_kill_bar" + ], + [ + "double", + "dormant_trail_best" + ], + [ + "double", + "dormant_trail_best_start" + ], + [ + "bool", + "dormant_trail_leg_dead" + ], + [ + "double", + "suppressed_close_consumed_ledger_qty" + ], + [ + "double", + "suppressed_close_retired_ledger_qty" + ], + [ + "ShortSeedCollisionRole", + "short_seed_collision_role" + ] + ] +} diff --git a/scripts/test_broker_state_hash_coverage.py b/scripts/test_broker_state_hash_coverage.py index 462bdcfc..07b450f4 100644 --- a/scripts/test_broker_state_hash_coverage.py +++ b/scripts/test_broker_state_hash_coverage.py @@ -15,7 +15,9 @@ ROOT = Path(__file__).resolve().parents[1] HEADER = (ROOT / "include/pineforge/engine.hpp").read_text() +QUANTITY = (ROOT / "include/pineforge/quantity_intent.hpp").read_text() EVENTS = (ROOT / "include/pineforge/broker_events.hpp").read_text() +BIRTH = (ROOT / "include/pineforge/order_birth.hpp").read_text() INTRADAY = (ROOT / "include/pineforge/compat/pine/intraday_order_budget.hpp").read_text() POLICY = (ROOT / "include/pineforge/compat/pine/intraday_cap.hpp").read_text() OBLIGATION = (ROOT / "include/pineforge/position_close_obligation.hpp").read_text() @@ -26,12 +28,14 @@ class PhysicalLotCoverage(unittest.TestCase): def check(self, header=HEADER, source=SOURCE, waivers=WAIVERS, events=EVENTS, - intraday=INTRADAY, policy=POLICY, obligation=OBLIGATION, stream=STREAM): + intraday=INTRADAY, policy=POLICY, obligation=OBLIGATION, stream=STREAM, quantity=QUANTITY, birth=BIRTH): with tempfile.TemporaryDirectory(prefix="pf-lot-hash-check-") as temp: root = Path(temp) for name, content in [ ("include/pineforge/engine.hpp", header), ("include/pineforge/broker_events.hpp", events), + ("include/pineforge/quantity_intent.hpp", quantity), + ("include/pineforge/order_birth.hpp", birth), ("include/pineforge/compat/pine/intraday_order_budget.hpp", intraday), ("include/pineforge/compat/pine/intraday_cap.hpp", policy), ("include/pineforge/position_close_obligation.hpp", obligation), @@ -56,26 +60,51 @@ def test_actual_source_and_named_parser(self): self.assertEqual(len(members(HEADER, "PyramidEntry")), 18) self.assertEqual(members(HEADER), members(HEADER, "PendingOrder")) - def test_layout_and_hash_versions_must_match_v4_contract(self): - self.assertIn("engine_script_run_v4", HEADER) - self.assertIn('f.s("pineforge-broker-state/v4");', SOURCE) - self.assertIn("integer(4); integer(broker_state_hash());", STREAM) - self.assertEqual(self.check(header=HEADER.replace("engine_script_run_v4", "engine_script_run_v2"))[0], 1) + def test_quantity_request_presence_and_every_value_are_hashed(self): + for fold in ["f.b(o.quantity_request.intent().has_value());", + "f.i(static_cast(intent->kind()));", + "f.d(intent->units());", "f.d(intent->numerator());", + "f.d(intent->denominator());", + "f.b(o.quantity_request.reservation().has_value());", + "f.d(reservation->units);", "f.d(reservation->basis_units);"]: + with self.subTest(fold=fold): + self.assertIn(fold, SOURCE) + self.assertEqual(self.check(source=SOURCE.replace(fold, ""))[0], 1) + self.assertEqual(self.check(waivers=WAIVERS + + "\npending_order.quantity_request # attempted omission\n")[0], 1) + + def test_quantity_model_additions_and_variant_drift_fail_closed(self): + for old, new in [ + ("struct Units { double amount; };", "struct Units { double amount; double extra; };"), + ("double basis_units;", "double basis_units; double extra;"), + ("Units, Fraction, All", "Units, All, Fraction"), + ("Value value_;", "Value value_; double extra_;"), + ("std::optional intent_;", "std::optional intent_; double extra_;"), + ]: + with self.subTest(old=old): + self.assertIn(old, QUANTITY) + self.assertEqual(self.check(quantity=QUANTITY.replace(old, new))[0], 1) + + def test_layout_and_hash_versions_must_match_v5_contract(self): + self.assertIn("engine_script_run_v5", HEADER) + self.assertIn('f.s("pineforge-broker-state/v5");', SOURCE) + self.assertIn("integer(5); integer(broker_state_hash());", STREAM) + self.assertEqual(self.check(header=HEADER.replace("engine_script_run_v5", "engine_script_run_v2"))[0], 1) for replacement in ['f.s("pineforge-broker-state/v2");', '', - '// f.s("pineforge-broker-state/v4");']: + '// f.s("pineforge-broker-state/v5");']: self.assertEqual(self.check(source=SOURCE.replace( - 'f.s("pineforge-broker-state/v4");', replacement))[0], 1) + 'f.s("pineforge-broker-state/v5");', replacement))[0], 1) for replacement in ["integer(2); integer(broker_state_hash());", "integer(broker_state_hash());", - "if (false) { integer(4); integer(broker_state_hash()); }"]: + "if (false) { integer(5); integer(broker_state_hash()); }"]: self.assertEqual(self.check(stream=STREAM.replace( - "integer(4); integer(broker_state_hash());", replacement))[0], 1) + "integer(5); integer(broker_state_hash());", replacement))[0], 1) def test_version_folds_in_unrelated_helpers_do_not_cover_entry_points(self): - broker_fold = 'f.s("pineforge-broker-state/v4");' + broker_fold = 'f.s("pineforge-broker-state/v5");' altered = SOURCE.replace(broker_fold, '') + '\nvoid other() { ' + broker_fold + ' }\n' self.assertEqual(self.check(source=altered)[0], 1) - stream_fold = "integer(4); integer(broker_state_hash());" + stream_fold = "integer(5); integer(broker_state_hash());" altered = STREAM.replace(stream_fold, '') + '\nvoid other() { ' + stream_fold + ' }\n' self.assertEqual(self.check(stream=altered)[0], 1) @@ -133,6 +162,20 @@ def test_unclassified_nested_declaration_refuses(self): header = HEADER.replace("struct PyramidEntry {", "struct PyramidEntry {\n " + declaration) self.assertNotEqual(self.check(header=header)[0], 0) + def test_complete_birth_hash_block_cannot_be_conditional(self): + start = SOURCE.index(" f.i(static_cast(o.birth.cause()));") + end = SOURCE.index(" f.i(static_cast(o.pine_birth_reach));", start) + end += len(" f.i(static_cast(o.pine_birth_reach));") + block = SOURCE[start:end] + self.assertEqual(self.check(source=SOURCE[:start] + "if (false) {\n" + block + "\n}" + SOURCE[end:])[0], 1) + + def test_birth_fields_and_nested_cursor_cannot_escape_hash_coverage(self): + for fold in ["f.u(o.birth.first_fill());", "f.i(o.birth.cursor().index());", + "f.i(static_cast(o.pine_birth_reach));"]: + self.assertNotEqual(self.check(source=SOURCE.replace(fold, ""))[0], 0) + altered = BIRTH.replace(" int count_ = 0;", " int count_ = 0;\n int hidden_cursor_fact = 0;") + self.assertNotEqual(self.check(birth=altered)[0], 0) + def test_pending_order_coverage_still_refuses_omission(self): source = SOURCE.replace("f.d(o.stop_price);", "") self.assertNotEqual(source, SOURCE) diff --git a/src/compat/pine/order_birth.cpp b/src/compat/pine/order_birth.cpp new file mode 100644 index 00000000..9680bfa7 --- /dev/null +++ b/src/compat/pine/order_birth.cpp @@ -0,0 +1,23 @@ +#include +#include + +namespace pineforge::compat::pine { + +HistoricalBirthReach select_historical_birth_reach(const OrderBirth& birth, + bool requested_trailing_exit) { + if (!birth.from_fill() || first_open_fill_evaluation(birth)) + return HistoricalBirthReach::Standard; + // Existing later-same-open trailing-exit exception: the origin is still + // the later fill callback at O. Only its Pine historical reach differs. + const bool later_open_trailing_exit = + birth.cursor().domain() == BirthCursorDomain::HistoricalPath + && birth.cursor().first_point() && requested_trailing_exit; + return later_open_trailing_exit ? HistoricalBirthReach::Standard + : HistoricalBirthReach::ExtremeWaypoints; +} + +bool historical_cascade_reach(const PendingOrder& order) { + return order.pine_birth_reach == HistoricalBirthReach::ExtremeWaypoints; +} + +} // namespace pineforge::compat::pine diff --git a/src/compat/pine/order_priority.cpp b/src/compat/pine/order_priority.cpp index ae6d4f5c..8e7536ec 100644 --- a/src/compat/pine/order_priority.cpp +++ b/src/compat/pine/order_priority.cpp @@ -30,7 +30,7 @@ std::optional OrderPriority::select( const bool parent_is_exact_fresh_stop = parent->type == OrderType::ENTRY && parent->created_position_side == PositionSide::FLAT - && !parent->created_by_same_id_replacement + && (parent->replaced_order_incarnation == 0) && cancelled_incarnation != 0 && cancelled_incarnation < parent->incarnation && cancelled_incarnation != child->incarnation @@ -38,7 +38,7 @@ std::optional OrderPriority::select( && surviving_exit_incarnation < parent->incarnation && parent->created_bar == ctx.bar_index - 1 && std::isnan(parent->qty) - && !parent->created_during_coof_recalc + && !parent->birth.from_fill() && !parent->created_after_position_close_in_bar && !parent->over_pyramiding_cap_at_placement && !parent->stop_limit_activated @@ -54,15 +54,14 @@ std::optional OrderPriority::select( const bool child_is_exact_retained_bracket = child->type == OrderType::EXIT && !child->from_entry.empty() - && child->created_by_same_id_replacement - && child->replaced_exit_order_incarnation + && (child->replaced_order_incarnation != 0) + && child->replaced_order_incarnation == surviving_exit_incarnation - && !child->created_while_in_position && child->created_position_side == PositionSide::FLAT && child->created_bar == ctx.bar_index - 1 - && !child->created_during_coof_recalc + && !child->birth.from_fill() && !child->created_after_position_close_in_bar - && !child->requested_partial + && !child->quantity_request.is_partial(1e-9, 1e-9) && std::isnan(child->qty) && child_qp >= 100.0 - 1e-9 && std::isfinite(child->stop_price) diff --git a/src/engine_fills.cpp b/src/engine_fills.cpp index 7eae1107..e124c43c 100644 --- a/src/engine_fills.cpp +++ b/src/engine_fills.cpp @@ -534,7 +534,7 @@ BacktestEngine::CoofFillResult BacktestEngine::process_next_pending_order( traversed.low = std::min(bar.open, cursor_price); traversed.close = cursor_price; for (PendingOrder& pending : pending_orders_) { - if (pending.coof_born_at_close_recalc + if (pending.birth.at_terminal_fill() && pending.created_bar == bar_index_) { continue; } @@ -598,7 +598,7 @@ BacktestEngine::CoofFillResult BacktestEngine::process_next_pending_order( // its own tick model and is scoped out. coof_cascade_force_wp_gap_ = false; if (!bar_magnifier_enabled_ && coof_scheduler_active_ - && order.coof_born_mid_bar + && compat::pine::historical_cascade_reach(order) && order.created_bar == bar_index_) { // Model S governs only PRICED (stop/limit, non-trail) // strategy.exit cascade orders — the class the probe pinned. @@ -665,8 +665,8 @@ BacktestEngine::CoofFillResult BacktestEngine::process_next_pending_order( && position_open_bar_ >= 0 && position_open_bar_ < bar_index_ && position_entry_count_ == 1 && pyramid_entries_.size() == 1 && pyramiding_ == 0 && order.type == OrderType::EXIT - && order.created_bar < bar_index_ && !order.requested_partial - && order.created_while_in_position && !order.dormant_bracket + && order.created_bar < bar_index_ && !order.quantity_request.is_partial(kFullQtyEps, kFullPercentEps) + && (order.created_position_side != PositionSide::FLAT) && !order.dormant_bracket && !order.from_entry.empty() && order.from_entry == pyramid_entries_.front().entry_id && std::isnan(order.trail_points) && std::isnan(order.trail_price) @@ -798,7 +798,7 @@ BacktestEngine::CoofFillResult BacktestEngine::process_next_pending_order( // siblings, invalidating references into the pending-order vector. const bool fresh_coof_market_entry = pending_orders_[order_index].type == OrderType::MARKET - && pending_orders_[order_index].created_during_coof_recalc + && pending_orders_[order_index].birth.from_fill() && pending_orders_[order_index].created_bar == bar_index_ && side_before_fill == PositionSide::FLAT; const uint64_t opening_incarnation = candidate.order.incarnation; @@ -1226,9 +1226,8 @@ void BacktestEngine::process_short_margin_before_script(const Bar& bar) { double pending_touch = 0.0; if (order.created_bar >= bar_index_ || order.created_position_side != PositionSide::FLAT - || order.created_while_in_position || order.created_after_position_close_in_bar - || order.created_during_coof_recalc + || order.birth.from_fill() || !std::isfinite(order.stop_price) || !std::isnan(order.limit_price) || order.stop_limit_activated @@ -2226,7 +2225,7 @@ bool BacktestEngine::tv_money_long_margin_call(const Bar& bar, && pending.created_position_side == PositionSide::LONG && pending.created_position_cycle_seq == close_mc_cycle && !pending.created_after_position_close_in_bar - && !pending.created_during_coof_recalc + && !pending.birth.from_fill() && pending.tv_carry_qty == qty && std::isfinite(pending.frozen_default_qty)) { pending.signal_close_mc_bar = bar_index_; @@ -2297,7 +2296,7 @@ void BacktestEngine::revive_position_brackets_after_margin_call_partial( // remainder next bar @214.68. const bool full_pct = std::isnan(o.qty) ? o.qty_percent >= 100.0 - internal::kFullPercentEps - : (!o.requested_partial + : (!o.quantity_request.is_partial(kFullQtyEps, kFullPercentEps) || o.qty >= position_qty_ - kQtyEpsilon); if (!full_pct || std::isnan(revive_stop) || !std::isfinite(mc_price)) continue; @@ -3262,9 +3261,9 @@ void BacktestEngine::finalize_default_flat_market_gross_admission() { && std::isnan(other.trail_points) && std::isnan(other.trail_price) && std::isnan(other.trail_offset) - && !other.created_during_coof_recalc - && !other.coof_born_at_close_recalc - && !other.coof_born_mid_bar; + && !other.birth.from_fill() + && !other.birth.at_terminal_fill() + && !compat::pine::historical_cascade_reach(other); if (!same_bar_market_close) { consume_source_tombstones(); return; @@ -3297,10 +3296,10 @@ void BacktestEngine::finalize_default_flat_market_gross_admission() { && order.created_bar == source_bar && order.incarnation > 0 && order.created_seq > 0 - && !order.created_by_same_id_replacement - && !order.created_during_coof_recalc - && !order.coof_born_at_close_recalc - && !order.coof_born_mid_bar + && (order.replaced_order_incarnation == 0) + && !order.birth.from_fill() + && !order.birth.at_terminal_fill() + && !compat::pine::historical_cascade_reach(order) && std::isfinite(order.sizing_equity) && order.sizing_equity > 0.0 && std::isfinite(order.sizing_mark) @@ -3469,12 +3468,12 @@ void BacktestEngine::apply_pooc_coof_explicit_flat_market_gross_admission() { && order.oca_name.empty() && order.created_bar == bar_index_ && order.incarnation > 0 - && !order.created_by_same_id_replacement + && (order.replaced_order_incarnation == 0) && order.created_position_side == PositionSide::FLAT && !order.created_after_position_close_in_bar - && !order.created_during_coof_recalc - && !order.coof_born_at_close_recalc - && !order.coof_born_mid_bar + && !order.birth.from_fill() + && !order.birth.at_terminal_fill() + && !compat::pine::historical_cascade_reach(order) && std::isfinite(order.explicit_placement_equity) && order.explicit_placement_equity > 0.0 && std::isfinite(order.explicit_slipped_signal_close) @@ -3758,7 +3757,7 @@ void BacktestEngine::sort_orders_by_fill_phase(const Bar& bar) { && !order.id.empty() && order.created_position_side == PositionSide::FLAT && order.created_bar < bar_index_ - && !order.created_during_coof_recalc + && !order.birth.from_fill() && std::isfinite(order.limit_price) && std::isnan(order.stop_price) && std::isnan(order.trail_points) @@ -3793,9 +3792,8 @@ void BacktestEngine::sort_orders_by_fill_phase(const Bar& bar) { // parent filled AT the open already shared the open phase. const bool exact_relative_child = order.type == OrderType::EXIT - && !order.created_while_in_position && order.created_position_side == PositionSide::FLAT - && !order.created_during_coof_recalc + && !order.birth.from_fill() && exit_children_by_parent[order.from_entry] == 1 && order.created_bar >= parent->second.created_bar && parent->second.created_seq < order.created_seq @@ -3871,13 +3869,12 @@ void BacktestEngine::sort_orders_by_fill_phase(const Bar& bar) { return order.created_bar == source_bar && source_bar + 1 == bar_index_ && order.created_position_side == PositionSide::SHORT - && !order.created_by_same_id_replacement - && order.replaced_exit_order_incarnation == 0 + && (order.replaced_order_incarnation == 0) && order.recreated_after_named_cancelled_entry_incarnation == 0 && order.named_cancel_surviving_exit_incarnation == 0 - && !order.created_during_coof_recalc - && !order.coof_born_at_close_recalc - && !order.coof_born_mid_bar + && !order.birth.from_fill() + && !order.birth.at_terminal_fill() + && !compat::pine::historical_cascade_reach(order) && order.oca_name.empty() && order.oca_type == 0; }; @@ -3912,8 +3909,7 @@ void BacktestEngine::sort_orders_by_fill_phase(const Bar& bar) { && std::isnan(order.trail_offset) && std::isnan(order.profit_ticks) && std::isnan(order.loss_ticks) - && !order.created_after_position_close_in_bar - && !order.created_while_in_position; + && !order.created_after_position_close_in_bar; }; const auto exact_full_fifo_close_short = [&](const PendingOrder& order, const std::string& held_id) { @@ -3921,8 +3917,8 @@ void BacktestEngine::sort_orders_by_fill_phase(const Bar& bar) { && order.id == "__close__" + held_id && order.from_entry.empty() && !order.is_long - && order.created_while_in_position - && !order.requested_partial + && (order.created_position_side != PositionSide::FLAT) + && !order.quantity_request.is_partial(kFullQtyEps, kFullPercentEps) && std::isnan(order.qty) && std::abs(order.qty_percent - 100.0) <= kFullPercentEps && std::isnan(order.limit_price) @@ -4614,8 +4610,8 @@ bool BacktestEngine::prearmed_market_parent_bracket_gaps_at_open( || order.type != OrderType::EXIT || order.from_entry.empty() || order.created_bar != bar_index_ - 1 - || order.created_during_coof_recalc - || order.requested_partial + || order.birth.from_fill() + || order.quantity_request.is_partial(kFullQtyEps, kFullPercentEps) || order.qty_percent < 100.0 - kFullPercentEps || (!std::isfinite(order.stop_price) && !std::isfinite(order.limit_price))) { @@ -5772,7 +5768,7 @@ void BacktestEngine::apply_filled_order_to_state( && position_side_ == PositionSide::FLAT && (order.created_position_side == PositionSide::FLAT || order.created_after_position_close_in_bar) - && !order.created_by_same_id_replacement + && (order.replaced_order_incarnation == 0) && order.created_bar == bar_index_ - 1 && sole_opening_after_closes() && default_qty_type_ == QtyType::PERCENT_OF_EQUITY && default_qty_value_ == 100 @@ -5846,7 +5842,7 @@ void BacktestEngine::apply_filled_order_to_state( && !process_orders_on_close_ && !calc_on_order_fills_ && !bar_magnifier_enabled_ && !coof_scheduler_active_ && !stream_warmup_mode_ && stream_phase_ == StreamPhase::IDLE - && !order.created_during_coof_recalc + && !order.birth.from_fill() && !order.created_after_position_close_in_bar && std::isfinite(order.sizing_equity) && std::isfinite(order.frozen_default_qty) @@ -6181,7 +6177,7 @@ void BacktestEngine::apply_filled_order_to_state( && position_side_ == PositionSide::FLAT && order.created_position_side == PositionSide::FLAT && !order.created_after_position_close_in_bar - && !order.created_by_same_id_replacement + && (order.replaced_order_incarnation == 0) // FIXED/no-fee orders carry the transaction marker even // when no sibling exists. Exclude an expanded transaction, // not an otherwise-unused default sizing declaration. @@ -6387,7 +6383,7 @@ void BacktestEngine::apply_filled_order_to_state( || child.created_bar != order.created_bar || child.suppress_as_declined_reversal_close || !actionable - || child.requested_partial + || child.quantity_request.is_partial(kFullQtyEps, kFullPercentEps) || !std::isnan(child.qty) || qp < 100.0 - kFullPercentEps) { continue; @@ -6486,8 +6482,8 @@ void BacktestEngine::apply_filled_order_to_state( && order.created_bar == bar_index_ && order.created_position_side == PositionSide::FLAT && !order.created_after_position_close_in_bar - && !order.created_during_coof_recalc - && !order.created_by_same_id_replacement + && !order.birth.from_fill() + && (order.replaced_order_incarnation == 0) && order.oca_name.empty() && order.oca_type == 0 && position_side_before_fill == PositionSide::FLAT && order.incarnation != 0 && pyramid_entries_.size() == 1 @@ -6519,8 +6515,7 @@ void BacktestEngine::apply_filled_order_to_state( && !process_orders_on_close_ && !calc_on_order_fills_ && !bar_magnifier_enabled_ && !coof_scheduler_active_ && !stream_warmup_mode_ && stream_phase_ == StreamPhase::IDLE - && !order.created_during_coof_recalc - && !order.created_while_in_position + && !order.birth.from_fill() && !order.created_after_position_close_in_bar && order.created_position_side == PositionSide::FLAT && order.created_bar < bar_index_ @@ -6635,7 +6630,7 @@ void BacktestEngine::apply_filled_order_to_state( && !bar_magnifier_enabled_ && !stream_warmup_mode_ && stream_phase_ == StreamPhase::IDLE - && !order.created_during_coof_recalc + && !order.birth.from_fill() && order.created_bar < bar_index_ && order.oca_name.empty() && order.oca_type == 0; @@ -6680,7 +6675,7 @@ void BacktestEngine::apply_filled_order_to_state( && !bar_magnifier_enabled_ && !stream_warmup_mode_ && stream_phase_ == StreamPhase::IDLE - && !order.created_during_coof_recalc + && !order.birth.from_fill() && order.created_bar < bar_index_ && order.oca_name.empty() && order.oca_type == 0; @@ -6923,12 +6918,12 @@ static void set_entry_fill_excursion_masks(PyramidEntry& pe, const Bar& bar, bool BacktestEngine::replaced_percent_short_market_is_live( const PendingOrder& order) const { if (order.type != OrderType::MARKET || order.is_long - || !order.created_by_same_id_replacement + || (order.replaced_order_incarnation == 0) || order.replaced_default_market_incarnation == 0 || !std::isnan(order.qty) || order.qty_type >= 0 || order.affordability_close_only || order.sbmt_member || order.created_bar != bar_index_ - 1 - || order.created_during_coof_recalc + || order.birth.from_fill() || order.created_after_position_close_in_bar || order.created_position_side != PositionSide::LONG || position_side_ != PositionSide::LONG @@ -6963,7 +6958,7 @@ bool BacktestEngine::replaced_percent_short_market_is_live( || std::isfinite(other.limit_price) || std::isfinite(other.profit_ticks) || std::isfinite(other.loss_ticks); - if (other.from_entry.empty() || other.requested_partial + if (other.from_entry.empty() || other.quantity_request.is_partial(kFullQtyEps, kFullPercentEps) || !std::isnan(other.qty) || other.qty_percent != 100 || !bracket || other.suppress_as_declined_reversal_close || !other.oca_name.empty() @@ -7213,7 +7208,7 @@ void BacktestEngine::apply_market_order_fill(PendingOrder& order, double fill_pr // intrabar retrace price. See apply_entry_order_fill's matching guard. bool same_bar_close_fill = process_orders_on_close_ && order.created_bar == bar_index_ - && !order.created_during_coof_recalc; + && !order.birth.from_fill(); if (!same_bar_close_fill) { if (position_side_ == PositionSide::LONG) trail_best_price_ = std::max(trail_best_price_, bar.high); @@ -7364,7 +7359,7 @@ void BacktestEngine::apply_entry_order_fill(PendingOrder& order, double fill_pri // the bar's close (a POOC entry created and filled this same bar). bool same_bar_close_fill = process_orders_on_close_ && order.created_bar == bar_index_ - && !order.created_during_coof_recalc; + && !order.birth.from_fill(); if (!same_bar_close_fill) { if (position_side_ == PositionSide::LONG) trail_best_price_ = std::max(trail_best_price_, bar.high); @@ -7637,7 +7632,7 @@ void BacktestEngine::apply_exit_order_fill(PendingOrder& order, double fill_pric // Consuming the id on the FIRST leg's fill would make the surviving sibling // unre-issuable while the position is still open. Mark the id consumed only // when the last leg carrying it is gone. - if (order.requested_partial && trades_.size() > trades_before_exit) { + if (order.quantity_request.is_partial(kFullQtyEps, kFullPercentEps) && trades_.size() > trades_before_exit) { bool sibling_leg_still_live = false; for (const PendingOrder& sibling : pending_orders_) { if (sibling.type != OrderType::EXIT) continue; @@ -7748,7 +7743,7 @@ void BacktestEngine::reconcile_deferred_layered_exits( // to flatten the whole position. Mirrors the live-armed normalization // at engine_strategy_commands.cpp (reserved_qty_out / live_pos * 100). if (live_pos > kQtyEpsilon) o.qty_percent = (res / live_pos) * 100.0; - o.requested_partial = res < live_pos - kFullQtyEps; + o.quantity_request.reserve(res, live_pos); reserved += res; } } @@ -8075,14 +8070,13 @@ double BacktestEngine::pooc_short_exit_trigger_close( && position_entry_count_ == 1 && pyramiding_ == 0 && pyramid_entries_.size() == 1 && order.type == OrderType::EXIT && !order.is_long - && order.created_bar == bar_index_ && !order.created_during_coof_recalc - && order.created_by_same_id_replacement - && order.replaced_exit_order_incarnation != 0 - && order.created_while_in_position && !order.dormant_bracket + && order.created_bar == bar_index_ && !order.birth.from_fill() + && (order.replaced_order_incarnation != 0) + && (order.created_position_side != PositionSide::FLAT) && !order.dormant_bracket && !order.from_entry.empty() && order.from_entry == pyramid_entries_.front().entry_id - && order.full_percent_exit_request - && !order.requested_partial && order.qty_percent == 100.0 + && order.quantity_request.requests_all() + && !order.quantity_request.is_partial(kFullQtyEps, kFullPercentEps) && order.qty_percent == 100.0 && std::isfinite(order.qty) && std::abs(order.qty - position_qty_) <= kQtyEpsilon && order.oca_name.empty() @@ -8183,7 +8177,7 @@ BacktestEngine::OrderEligibility BacktestEngine::classify_order_eligibility( // point and expires unless a later ordinary-close execution reissues it; // carrying it creates Delta's spurious out-of-session lifecycle. if (calc_on_order_fills_ && coof_scheduler_active_ - && order.coof_born_at_close_recalc) { + && order.birth.at_terminal_fill()) { if (order.created_bar == bar_index_) { return OrderEligibility::Skip; } @@ -8198,7 +8192,7 @@ BacktestEngine::OrderEligibility BacktestEngine::classify_order_eligibility( bool stale_close_order_for_new_position = order.type == OrderType::EXIT - && order.created_while_in_position + && (order.created_position_side != PositionSide::FLAT) && order.id.rfind("__close__", 0) == 0 && position_side_ != PositionSide::FLAT && position_open_bar_ > order.created_bar @@ -8212,7 +8206,7 @@ BacktestEngine::OrderEligibility BacktestEngine::classify_order_eligibility( // was open. This prevents old strategy.exit brackets from leaking into // future positions after a market close/reversal. if (order.type == OrderType::EXIT && position_side_ == PositionSide::FLAT) { - return order.created_while_in_position + return (order.created_position_side != PositionSide::FLAT) ? OrderEligibility::Remove : OrderEligibility::Skip; } @@ -8228,7 +8222,7 @@ BacktestEngine::OrderEligibility BacktestEngine::classify_order_eligibility( // touched together — TV emits both as separate trades). const bool coof_fill_recalc_entry = calc_on_order_fills_ && coof_scheduler_active_ - && order.created_during_coof_recalc + && order.birth.from_fill() && order.created_bar == bar_index_; if (priced_entry_filled_this_bar_ && order.type == OrderType::ENTRY && !coof_fill_recalc_entry) { @@ -8368,7 +8362,7 @@ BacktestEngine::OrderEligibility BacktestEngine::classify_order_eligibility( // from the next bar on. See evaluate_fill_price's has_limit/has_stop // branches for the matching same-bar fill-price rules. if (process_orders_on_close_ && order.created_bar == bar_index_ - && !order.created_during_coof_recalc) { + && !order.birth.from_fill()) { bool has_stop_or_trail = !std::isnan(order.stop_price) || !std::isnan(order.trail_points) || !std::isnan(order.trail_price); @@ -8464,7 +8458,7 @@ BacktestEngine::OrderEligibility BacktestEngine::classify_order_eligibility( order, broker_trigger_bar(bar)); if (!prearmed_market_gap && !bar_magnifier_enabled_ && !(calc_on_order_fills_ && coof_scheduler_active_ - && order.created_during_coof_recalc)) { + && order.birth.from_fill())) { double ep = position_entry_price_; if (position_side_ == PositionSide::LONG) { if (!std::isnan(order.stop_price) && order.stop_price > ep) return OrderEligibility::Skip; @@ -8555,7 +8549,7 @@ BacktestEngine::FillEvaluation BacktestEngine::evaluate_fill_price( bool exit_same_bar_reissue = exit_style && !has_trail && process_orders_on_close_ && order.created_bar == bar_index_ - && !order.created_during_coof_recalc; + && !order.birth.from_fill(); if (!should_fill && exit_same_bar_reissue && (has_stop || has_limit)) { // A mid-trade exit re-issue (e.g. a break-even stop moved by a // time-gated block) that's already marketable against THIS bar's @@ -8599,7 +8593,7 @@ BacktestEngine::FillEvaluation BacktestEngine::evaluate_fill_price( if (is_entry_bar && order.type == OrderType::EXIT && !order.from_entry.empty() - && !order.created_while_in_position + && (order.created_position_side == PositionSide::FLAT) && std::isnan(order.trail_points) && std::isnan(order.trail_price) && !bar_magnifier_enabled_ @@ -8767,7 +8761,7 @@ BacktestEngine::FillEvaluation BacktestEngine::evaluate_fill_price( } else if (!should_fill && has_limit) { // Entry limit order if (process_orders_on_close_ && order.created_bar == bar_index_ - && !order.created_during_coof_recalc) { + && !order.birth.from_fill()) { // Same-bar pure-limit entry (see classify_order_eligibility's // matching carve-out): TV evaluates it against THIS bar's // close (the moment it was placed), not the bar's full diff --git a/src/engine_path_resolve.cpp b/src/engine_path_resolve.cpp index 11c22363..3ddd64cf 100644 --- a/src/engine_path_resolve.cpp +++ b/src/engine_path_resolve.cpp @@ -195,7 +195,7 @@ bool opposing_stop_entry_hits_first(const Bar& bar, bool high_first, const PendingOrder& current = orders[current_idx]; auto deferred_at_consumed_close = [&](const PendingOrder& order) { return current_bar_index >= 0 - && order.coof_born_at_close_recalc + && order.birth.at_terminal_fill() && order.created_bar == current_bar_index; }; if (deferred_at_consumed_close(current)) return false; @@ -252,7 +252,7 @@ DualEntryStopPathWinner dual_entry_stop_path_winner(const Bar& bar, bool high_fi const PendingOrder* short_ord = nullptr; for (const PendingOrder& o : orders) { if (current_bar_index >= 0 - && o.coof_born_at_close_recalc + && o.birth.at_terminal_fill() && o.created_bar == current_bar_index) { continue; } diff --git a/src/engine_run.cpp b/src/engine_run.cpp index 614bd99d..ce41472c 100644 --- a/src/engine_run.cpp +++ b/src/engine_run.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -17,6 +18,23 @@ namespace pineforge { using namespace internal; namespace { +// A callback owns its origin, not an engine clone. Nesting and exceptions restore +// the previous owner; copying an engine cannot inherit a live callback token. +thread_local const BacktestEngine* birth_context_owner = nullptr; +thread_local std::optional birth_context; +class ScopedBirthContext { +public: + ScopedBirthContext(const BacktestEngine* owner, const OrderBirth& birth) + : previous_owner_(birth_context_owner), previous_(birth_context) { + birth_context_owner = owner; birth_context = birth; + } + ~ScopedBirthContext() { + birth_context_owner = previous_owner_; birth_context = previous_; + } +private: + const BacktestEngine* previous_owner_; + std::optional previous_; +}; [[noreturn]] void reject_chart_bar(int index, const char* rule) { throw std::invalid_argument("chart bar[" + std::to_string(index) + "]." + rule); } @@ -134,7 +152,15 @@ double BacktestEngine::active_account_currency_fx() const { // cross-engine contamination and leakage between chart and request.security // evaluation. The latter installs its own scope around every security // evaluator dispatch and restores the prior thread-local value on return. +OrderBirth BacktestEngine::capture_order_birth() const { + if (birth_context_owner == this && birth_context) return *birth_context; + return OrderBirth::direct_command(bar_index_, current_bar_.timestamp); +} + void BacktestEngine::invoke_chart_on_bar(const Bar& bar) { + const OrderBirth origin = birth_context_owner == this && birth_context + ? *birth_context : OrderBirth::chart_evaluation(bar_index_, bar.timestamp); + ScopedBirthContext origin_scope(this, origin); process_short_margin_before_script(bar); struct ChartEmaNaWarmupScope { bool previous; @@ -360,9 +386,7 @@ uint64_t BacktestEngine::execute_coof_script_body( const Bar& script_bar, double broker_cursor_price, bool cursor_is_bar_point, - bool is_fill_recalc, - bool cursor_is_bar_close, - bool recalc_at_bar_open, + const OrderBirth& evaluation_origin, uint64_t direct_fill_event_budget, bool opening_money_prefix) { restore_coof_script_state(); @@ -389,16 +413,18 @@ uint64_t BacktestEngine::execute_coof_script_body( } coof_scheduler_active_ = true; - coof_fill_recalc_active_ = is_fill_recalc; - coof_cursor_is_bar_close_ = cursor_is_bar_close; + coof_fill_recalc_active_ = evaluation_origin.from_fill(); + coof_cursor_is_bar_close_ = evaluation_origin.from_fill() + ? evaluation_origin.cursor().terminal_point() : true; // KI-67: only the first fill event at O owns "bar-open" provenance and // places standard orders. A later fill at the same O, like a fill at any // segment/extreme/close point, is mid-bar and places cascade orders. - coof_recalc_at_bar_open_ = is_fill_recalc && recalc_at_bar_open; + coof_recalc_at_bar_open_ = compat::pine::first_open_fill_evaluation(evaluation_origin); coof_cursor_price_ = broker_cursor_price; coof_cursor_is_bar_point_ = cursor_is_bar_point; coof_direct_fill_events_remaining_ = direct_fill_event_budget; const uint64_t before = broker_fill_event_seq_; + ScopedBirthContext origin_scope(this, evaluation_origin); invoke_chart_on_bar(current_bar_); if (process_orders_on_close_) { // A same-bar close batch is a broker fill at the current monotonic @@ -416,43 +442,49 @@ uint64_t BacktestEngine::execute_coof_script_body( } uint64_t BacktestEngine::run_coof_recalc_chain( - const Bar& script_bar, - double broker_cursor_price, - bool cursor_is_bar_point, - bool cursor_is_bar_close, - bool recalc_at_bar_open, - uint64_t triggering_events, - uint64_t max_events, - uint64_t events_already, - bool grouped_stop_recalc, - uint64_t market_entry_incarnation, + const Bar& script_bar, double broker_cursor_price, + bool cursor_is_bar_point, BirthCursor cursor, + uint64_t& evaluation_ordinal, uint64_t triggering_events, + uint64_t max_events, uint64_t events_already, + bool grouped_stop_recalc, uint64_t market_entry_incarnation, bool opening_money_prefix) { + // Bind callbacks to the actual simulator events that scheduled them. Direct + // fills append their exact sequence interval to this FIFO; a later callback + // never borrows the newest global sequence as its alleged triggering fill. + using Interval = std::pair; + std::deque pending; + auto append_events = [&](uint64_t first, uint64_t last, bool grouped) { + if (first == 0 || last < first) throw std::logic_error("invalid callback fill interval"); + if (grouped) pending.emplace_back(first, last); + else for (uint64_t seq = first;; ++seq) { + pending.emplace_back(seq, seq); + if (seq == last) break; + } + }; + if (triggering_events > broker_fill_event_seq_) + throw std::logic_error("callback interval exceeds committed fills"); + if (triggering_events > 0) + append_events(broker_fill_event_seq_ - triggering_events + 1, + broker_fill_event_seq_, grouped_stop_recalc); uint64_t total_events = triggering_events; - uint64_t pending_recalcs = grouped_stop_recalc ? 1 : triggering_events; uint64_t handled = 0; - while (pending_recalcs > 0 && events_already + handled < max_events) { - --pending_recalcs; - ++handled; + while (!pending.empty() && events_already + handled < max_events) { + const auto trigger = pending.front(); pending.pop_front(); ++handled; + const auto origin = OrderBirth::fill_evaluation( + bar_index_, script_bar.timestamp, cursor, broker_cursor_price, + trigger.first, trigger.second, ++evaluation_ordinal); const uint64_t used = events_already + total_events; - const uint64_t direct_budget = - used < max_events ? max_events - used : 0; - // Only the first fill event at O owns bar-open provenance. A direct or - // separately-dispatched later fill at that same O is a KI-67 cascade - // recalc whose remaining path starts on leg 0 (O->W1). - const bool first_open_fill_recalc = - recalc_at_bar_open && events_already == 0 && handled == 1; - coof_recalc_after_first_open_fill_ = - recalc_at_bar_open && !first_open_fill_recalc; - coof_market_entry_recalc_incarnation_ = - handled == 1 ? market_entry_incarnation : 0; + const uint64_t direct_budget = used < max_events ? max_events - used : 0; + coof_recalc_after_first_open_fill_ = cursor.first_point() + && !compat::pine::first_open_fill_evaluation(origin); + coof_market_entry_recalc_incarnation_ = handled == 1 ? market_entry_incarnation : 0; coof_market_entry_recalc_fill_seq_ = broker_fill_event_seq_; + const uint64_t before = broker_fill_event_seq_; const uint64_t direct = execute_coof_script_body( script_bar, broker_cursor_price, cursor_is_bar_point, - /*is_fill_recalc=*/true, - cursor_is_bar_close, first_open_fill_recalc, - direct_budget, opening_money_prefix); + origin, direct_budget, opening_money_prefix); total_events += direct; - pending_recalcs += direct; + if (direct > 0) append_events(before + 1, broker_fill_event_seq_, false); } return total_events; } @@ -495,6 +527,7 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() { constexpr uint64_t kNoFillEventBudget = std::numeric_limits::max(); constexpr int kCoofLoopGuard = 1 << 20; uint64_t fill_events = 0; + uint64_t evaluation_ordinal = 0; int exit_closed_from_bar = -1; uint64_t exit_closed_from_incarnation = 0; bool exit_closed_was_long = false; @@ -540,8 +573,8 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() { coof_cascade_recalc_leg_ = 0; fill_events += run_coof_recalc_chain( script_bar, cursor, /*cursor_is_bar_point=*/true, - /*cursor_is_bar_close=*/false, /*recalc_at_bar_open=*/true, - broker_fill_event_seq_ - before, kNoFillEventBudget, 0, + BirthCursor::point(BirthCursorDomain::HistoricalPath, 0, 4), + evaluation_ordinal, broker_fill_event_seq_ - before, kNoFillEventBudget, 0, /*grouped_stop_recalc=*/false, /*market_entry_incarnation=*/0, /*opening_money_prefix=*/true); // The ordinary O exception permits just the first follow-up @@ -551,7 +584,7 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() { } auto consume_fill = [&](const CoofFillResult& fill, - bool cursor_is_close, + BirthCursor birth_cursor, bool filled_at_bar_open_point) { const uint64_t before = fill_events; const bool chart_tick_touch = std::isfinite(fill.chart_waypoint_price); @@ -559,11 +592,10 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() { cursor_is_bar_point = chart_tick_touch; // The recalc chain receives O-point provenance, but only its first fill // event is classified as bar-open. A later fill at the same O is a - // leg-0 cascade (PendingOrder::coof_born_mid_bar). + // leg-0 cascade (the Pine historical cascade permission). fill_events += run_coof_recalc_chain( - script_bar, fill.fill_price, /*cursor_is_bar_point=*/false, cursor_is_close, - filled_at_bar_open_point, - fill.fill_events, kNoFillEventBudget, fill_events, + script_bar, fill.fill_price, /*cursor_is_bar_point=*/false, + birth_cursor, evaluation_ordinal, fill.fill_events, kNoFillEventBudget, fill_events, fill.grouped_stop_recalc, fill.market_entry_incarnation); // The carried order's open fill triggers one execution at O, and the // order born in that first execution may also fill at O. Every later @@ -576,7 +608,6 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() { int loop_guard = 0; while (++loop_guard <= kCoofLoopGuard) { if (evaluate_current_point) { - const bool cursor_is_close = next_waypoint >= 4; // Cascade orders fill only AT an extreme waypoint (W1 = next_waypoint // 2, W2 = next_waypoint 3); the O point (1) and the C point (>=4) do // not admit them. @@ -598,7 +629,7 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() { // i.e. leg (next_waypoint-1) — the leg the loop traverses next. coof_cascade_recalc_leg_ = next_waypoint - 1; consume_fill( - fill, cursor_is_close, + fill, BirthCursor::point(BirthCursorDomain::HistoricalPath, next_waypoint - 1, 4), /*filled_at_bar_open_point=*/next_waypoint == 1); continue; } @@ -627,15 +658,15 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() { if (fill.filled) { const bool reached_target = std::abs(fill.fill_price - target) <= kSegmentDenomEps; - const bool cursor_is_close = next_waypoint == 3 - && reached_target; // A fill mid-leg leaves the in-flight leg at (next_waypoint-1); a fill // that reaches the leg-end waypoint (path[next_waypoint]) advances to // the NEXT leg (next_waypoint) — the loop's ++next_waypoint below. coof_cascade_recalc_leg_ = reached_target ? next_waypoint : (next_waypoint - 1); consume_fill( - fill, cursor_is_close, + fill, reached_target + ? BirthCursor::point(BirthCursorDomain::HistoricalPath, next_waypoint, 4) + : BirthCursor::segment(BirthCursorDomain::HistoricalPath, next_waypoint - 1, 4), /*filled_at_bar_open_point=*/false); // H/L/C itself has been consumed by this priced fill. Only O has // the same-point two-fill exception; a market order born in the @@ -673,8 +704,8 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() { cursor = path[3]; cursor_is_bar_point = true; uint64_t direct = execute_coof_script_body( - script_bar, cursor, cursor_is_bar_point, /*is_fill_recalc=*/false, - /*cursor_is_bar_close=*/true, /*recalc_at_bar_open=*/false, + script_bar, cursor, cursor_is_bar_point, + OrderBirth::chart_evaluation(bar_index_, script_bar.timestamp), kNoFillEventBudget); // C is the terminal historical tick. Direct fills produced by this // ordinary-close execution are real broker fills, but do not trigger @@ -720,8 +751,8 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() { if (margin_events > 0) { fill_events += run_coof_recalc_chain( script_bar, cursor, cursor_is_bar_point, - /*cursor_is_bar_close=*/true, - /*recalc_at_bar_open=*/false, margin_events, + BirthCursor::point(BirthCursorDomain::HistoricalPath, 3, 4), + evaluation_ordinal, margin_events, kNoFillEventBudget, fill_events); } @@ -1328,6 +1359,7 @@ void BacktestEngine::run_magnified_bar_calc_on_order_fills( // actual lower-timeframe broker ticks supplied by the magnifier. const uint64_t max_fill_events = static_cast(ticks.size()); uint64_t fill_events = 0; + uint64_t evaluation_ordinal = 0; int exit_closed_from_bar = -1; uint64_t exit_closed_from_incarnation = 0; bool exit_closed_was_long = false; @@ -1346,25 +1378,23 @@ void BacktestEngine::run_magnified_bar_calc_on_order_fills( bool evaluate_current_point = true; auto consume_fill = [&](const CoofFillResult& fill, - bool cursor_is_close, + BirthCursor birth_cursor, bool filled_at_first_tick) { const uint64_t before = fill_events; cursor = fill.fill_price; cursor_is_bar_point = false; - // Magnifier path: coof_born_mid_bar is inert here (the cascade gate is + // Magnifier path: historical cascade permission is inert here (the cascade gate is // guarded by !bar_magnifier_enabled_), but keep provenance consistent — // a first-tick fill is the magnifier analogue of a bar-open recalc. fill_events += run_coof_recalc_chain( - script_bar, cursor, cursor_is_bar_point, cursor_is_close, - filled_at_first_tick, - fill.fill_events, max_fill_events, fill_events); + script_bar, cursor, cursor_is_bar_point, birth_cursor, + evaluation_ordinal, fill.fill_events, max_fill_events, fill_events); evaluate_current_point = filled_at_first_tick && before == 0 && fill_events == 1; }; while (fill_events < max_fill_events) { if (evaluate_current_point) { - const bool cursor_is_close = next_tick >= ticks.size(); Bar point = coof_point_bar(script_bar, cursor); point.timestamp = cursor_ts; current_bar_ = point; @@ -1374,7 +1404,8 @@ void BacktestEngine::run_magnified_bar_calc_on_order_fills( exit_closed_was_long); if (fill.filled) { consume_fill( - fill, cursor_is_close, + fill, BirthCursor::point(BirthCursorDomain::MagnifierTicks, + static_cast(next_tick) - 1, static_cast(ticks.size())), /*filled_at_first_tick=*/next_tick == 1); continue; } @@ -1409,10 +1440,12 @@ void BacktestEngine::run_magnified_bar_calc_on_order_fills( cursor_ts = target.timestamp; const bool reached_target = std::abs(fill.fill_price - target.price) <= kSegmentDenomEps; - const bool cursor_is_close = next_tick + 1 == ticks.size() - && reached_target; consume_fill( - fill, cursor_is_close, + fill, reached_target + ? BirthCursor::point(BirthCursorDomain::MagnifierTicks, + static_cast(next_tick), static_cast(ticks.size())) + : BirthCursor::segment(BirthCursorDomain::MagnifierTicks, + static_cast(next_tick) - 1, static_cast(ticks.size())), /*filled_at_first_tick=*/false); // The real lower-TF endpoint is already consumed. Do not replay // a market-enabled point at the same H/L/C tick; O remains the @@ -1431,8 +1464,8 @@ void BacktestEngine::run_magnified_bar_calc_on_order_fills( cursor = ticks.back().price; cursor_is_bar_point = true; uint64_t direct = execute_coof_script_body( - script_bar, cursor, cursor_is_bar_point, /*is_fill_recalc=*/false, - /*cursor_is_bar_close=*/true, /*recalc_at_bar_open=*/false, + script_bar, cursor, cursor_is_bar_point, + OrderBirth::chart_evaluation(bar_index_, script_bar.timestamp), fill_events < max_fill_events ? max_fill_events - fill_events : 0); commit_coof_script_state(); // The last real lower-TF close is also terminal: count direct fills but do @@ -1465,8 +1498,9 @@ void BacktestEngine::run_magnified_bar_calc_on_order_fills( if (margin_events > 0 && fill_events < max_fill_events) { fill_events += run_coof_recalc_chain( script_bar, cursor, cursor_is_bar_point, - /*cursor_is_bar_close=*/true, - /*recalc_at_bar_open=*/false, margin_events, + BirthCursor::point(BirthCursorDomain::MagnifierTicks, + static_cast(ticks.size()) - 1, static_cast(ticks.size())), + evaluation_ordinal, margin_events, max_fill_events, fill_events); } diff --git a/src/engine_state_hash.cpp b/src/engine_state_hash.cpp index 1834222b..f7222195 100644 --- a/src/engine_state_hash.cpp +++ b/src/engine_state_hash.cpp @@ -86,9 +86,9 @@ void hash_str_set(Fnv& f, const std::unordered_set& s) { uint64_t BacktestEngine::broker_state_hash() const { Fnv f; - // v4 adds explicit Pine order-priority attachment and configuration. + // v5 hashes typed request/reservation, generic predecessor and immutable birth. // It is a serialization boundary, independent of the public C ABI version. - f.s("pineforge-broker-state/v4"); + f.s("pineforge-broker-state/v5"); // --- Position core --- f.i(static_cast(position_side_)); @@ -191,7 +191,18 @@ uint64_t BacktestEngine::broker_state_hash() const { f.b(o.over_pyramiding_cap_at_placement); f.b(o.affordability_close_only); f.i(static_cast(o.created_position_cycle_seq)); - f.b(o.requested_partial); f.b(o.full_percent_exit_request); + f.b(o.quantity_request.intent().has_value()); + if (const auto& intent = o.quantity_request.intent()) { + f.i(static_cast(intent->kind())); + if (intent->kind() == QuantityIntent::Kind::Units) f.d(intent->units()); + else if (intent->kind() == QuantityIntent::Kind::Fraction) { + f.d(intent->numerator()); f.d(intent->denominator()); + } + } + f.b(o.quantity_request.reservation().has_value()); + if (const auto& reservation = o.quantity_request.reservation()) { + f.d(reservation->units); f.d(reservation->basis_units); + } // Round-14 signal-close-margin-call receipt (cross-bar: compared // against broker_fill_event_seq_ on the bar AFTER the one it was // stamped on). @@ -223,25 +234,32 @@ uint64_t BacktestEngine::broker_state_hash() const { // Placement-side position/close provenance. f.i(static_cast(o.created_position_side)); f.b(o.created_after_position_close_in_bar); - f.b(o.created_while_in_position); // Round-14 rounded-signal-cost decline receipt + its remaining qty. f.b(o.rounded_signal_cost_close_only); f.d(o.signal_close_mc_remaining_qty); // Same-id replacement / named-cancel recreate provenance // (clean-room two-call rules fail closed on these). - f.b(o.created_by_same_id_replacement); + f.u(o.replaced_order_incarnation); f.u(o.replaced_default_market_incarnation); f.b(o.declined_by_replaced_short_market); - f.u(o.replaced_exit_order_incarnation); f.u(o.recreated_after_named_cancelled_entry_incarnation); f.u(o.named_cancel_surviving_exit_incarnation); // calc_on_order_fills birth provenance and per-leg suppression // (decide which waypoints / legs the order may fill at). f.b(o.coof_suppress_stop_on_entry_bar); f.b(o.coof_suppress_limit_on_entry_bar); - f.b(o.created_during_coof_recalc); - f.b(o.coof_born_at_close_recalc); - f.b(o.coof_born_mid_bar); + f.i(static_cast(o.birth.cause())); + f.i(o.birth.bar()); + f.i(o.birth.timestamp()); + f.i(static_cast(o.birth.cursor().domain())); + f.i(static_cast(o.birth.cursor().position())); + f.i(o.birth.cursor().index()); + f.i(o.birth.cursor().count()); + f.d(o.birth.cursor_price()); + f.u(o.birth.first_fill()); + f.u(o.birth.last_fill()); + f.u(o.birth.evaluation_ordinal()); + f.i(static_cast(o.pine_birth_reach)); f.i(static_cast(o.coof_cascade_seg_i)); f.b(o.coof_cascade_inflight_fires); // Deferred close_all same-id stop preservation token. diff --git a/src/engine_strategy_commands.cpp b/src/engine_strategy_commands.cpp index 04c2152f..302ea411 100644 --- a/src/engine_strategy_commands.cpp +++ b/src/engine_strategy_commands.cpp @@ -513,10 +513,12 @@ void BacktestEngine::strategy_entry(const std::string& id, bool is_long, } } int64_t preserved_seq = 0; + uint64_t replaced_order_incarnation = 0; uint64_t replaced_default_market_incarnation = 0; for (const auto& o : pending_orders_) { if (o.id == id) { preserved_seq = o.created_seq; + replaced_order_incarnation = o.incarnation; if (o.type == OrderType::MARKET && o.created_bar == bar_index_ && o.is_long == is_long && std::isnan(o.qty) && o.qty_type < 0 && o.created_position_cycle_seq == position_cycle_seq_) { @@ -564,7 +566,7 @@ void BacktestEngine::strategy_entry(const std::string& id, bool is_long, order.created_bar = bar_index_; order.created_seq = preserved_seq > 0 ? preserved_seq : next_order_seq_++; order.incarnation = next_order_incarnation_++; - order.created_by_same_id_replacement = preserved_seq > 0; + order.replaced_order_incarnation = replaced_order_incarnation; order.replaced_default_market_incarnation = replaced_default_market_incarnation; if (preserved_seq == 0) { @@ -573,14 +575,9 @@ void BacktestEngine::strategy_entry(const std::string& id, bool is_long, order.named_cancel_surviving_exit_incarnation = named_cancel_context.surviving_exit_incarnation; } - order.created_during_coof_recalc = coof_fill_recalc_active_; - order.coof_born_at_close_recalc = - coof_fill_recalc_active_ && coof_cursor_is_bar_close_; - // KI-67: a fill recalc without first-O provenance is mid-bar. This includes - // later fills at that same O; orders it places are cascade orders eligible - // only at the remaining extreme waypoints of the historical 4-tick path. - order.coof_born_mid_bar = - coof_fill_recalc_active_ && !coof_recalc_at_bar_open_; + order.birth = capture_order_birth(); + order.pine_birth_reach = compat::pine::select_historical_birth_reach( + order.birth, false); order.created_position_side = position_side_; order.created_position_cycle_seq = position_cycle_seq_; order.created_after_position_close_in_bar = @@ -2090,7 +2087,7 @@ void BacktestEngine::strategy_exit(const std::string& id, const std::string& fro ? PositionSide::LONG : PositionSide::SHORT; const bool qualifies = o.created_bar == bar_index_ && o.type == OrderType::MARKET - && !o.created_during_coof_recalc + && !o.birth.from_fill() && !o.over_pyramiding_cap_at_placement && entry_dir == position_side_ && o.created_position_side == position_side_; @@ -2139,9 +2136,14 @@ void BacktestEngine::strategy_exit(const std::string& id, const std::string& fro order.qty = reserved_qty; order.qty_type = -1; order.qty_percent = qp; - order.requested_partial = is_partial; - order.full_percent_exit_request = !has_explicit_qty - && (std::isnan(qty_percent) || qty_percent == 100.0); + order.quantity_request.request(has_explicit_qty + ? QuantityIntent::units(qty) + : (std::isnan(qty_percent) || qty_percent == 100.0) + ? QuantityIntent::all() : QuantityIntent::fraction(qty_percent, 100.0)); + // A resolved reservation owns its numeric basis. Deferred percentage + // requests retain their original fraction until a live owner binds them. + if (has_explicit_qty || std::isfinite(reserved_qty)) + order.quantity_request.reserve(reserved_qty, live_pos_qty); order.pooc_global_full_exit_dynamic_qty = bind_global_full_exit_dynamic_qty; order.pooc_global_full_exit_tracks_bound_adds = @@ -2163,33 +2165,18 @@ void BacktestEngine::strategy_exit(const std::string& id, const std::string& fro order.created_bar = bar_index_; order.created_seq = preserved_seq > 0 ? preserved_seq : next_order_seq_++; order.incarnation = next_order_incarnation_++; - order.created_by_same_id_replacement = preserved_seq > 0; - order.replaced_exit_order_incarnation = replaced_incarnation; - order.created_during_coof_recalc = coof_fill_recalc_active_; - order.coof_born_at_close_recalc = - coof_fill_recalc_active_ && coof_cursor_is_bar_close_; - // KI-67: a fill recalc that was NOT triggered at the bar-open tick is a - // mid-bar recalc; orders it places are cascade orders (eligible only at the - // remaining extreme waypoints of the historical 4-tick path). The new - // later-same-O refinement is priced/non-trail only: a trail born in that - // exact cell retains its established standard/open path reach. Otherwise - // the refinement would newly hold it to W1/W2 instead of letting it arm and - // cross continuously on the remaining legs. Magnifier keeps its own tick - // model and is unchanged. - const bool preserve_later_same_open_trail_provenance = - !bar_magnifier_enabled_ && has_trail_request - && coof_fill_recalc_active_ && !coof_recalc_at_bar_open_ - && coof_recalc_after_first_open_fill_ - && coof_cascade_recalc_leg_ == 0; - order.coof_born_mid_bar = - coof_fill_recalc_active_ && !coof_recalc_at_bar_open_ - && !preserve_later_same_open_trail_provenance; + order.replaced_order_incarnation = replaced_incarnation; + order.birth = capture_order_birth(); + order.pine_birth_reach = compat::pine::select_historical_birth_reach( + order.birth, has_trail_request); + // Later-open trailing permission is derived by the Pine policy from the + // immutable physical origin. It never rewrites that origin. // A priced exit born after a later fill at the SAME O is already held by // the KI-67 cascade gate for its in-flight leg 0. The pinned exception is // LIMIT-only: a marketable limit may resume at W1. A marketable stop keeps // the established whole-entry-bar suppression (including M1). const bool later_same_open_priced_exit_on_entry_bar = - !bar_magnifier_enabled_ && order.coof_born_mid_bar + !bar_magnifier_enabled_ && compat::pine::historical_cascade_reach(order) && coof_recalc_after_first_open_fill_ && coof_cascade_recalc_leg_ == 0 && position_open_bar_ == bar_index_ @@ -2221,7 +2208,7 @@ void BacktestEngine::strategy_exit(const std::string& id, const std::string& fro const bool first_high_market_limit_recross = !bar_magnifier_enabled_ && !process_orders_on_close_ && !stream_warmup_mode_ && stream_phase_ == StreamPhase::IDLE - && order.coof_born_mid_bar && !coof_hist_is_segment_ + && compat::pine::historical_cascade_reach(order) && !coof_hist_is_segment_ && coof_at_extreme_waypoint_ && coof_hist_path_index_ == 1 && coof_cascade_recalc_leg_ == 1 && coof_market_entry_recalc_incarnation_ != 0 @@ -2261,7 +2248,7 @@ void BacktestEngine::strategy_exit(const std::string& id, const std::string& fro // fill lands exactly on a waypoint ("a fill AT a waypoint starts the NEXT // leg"). current_bar_ is the full script bar during a fill recalc; the // magnifier path owns its own tick model and is scoped out. - if (order.coof_born_mid_bar && !bar_magnifier_enabled_ + if (compat::pine::historical_cascade_reach(order) && !bar_magnifier_enabled_ && coof_scheduler_active_ && std::isfinite(coof_cursor_price_) && position_side_ != PositionSide::FLAT && (!std::isnan(order.stop_price) || !std::isnan(order.limit_price)) @@ -2287,7 +2274,6 @@ void BacktestEngine::strategy_exit(const std::string& id, const std::string& fro order.created_position_side = effectively_flat ? PositionSide::FLAT : position_side_; order.tv_carry_qty = live_pos_qty; order.comment = comment; - order.created_while_in_position = !effectively_flat; // Round 7 family M mechanism 2a: a re-issue that replaces a DORMANT // bracket (finding-311 KILL) inside the close-time script body stays // dormant until this bar's process_margin_call has run — TradingView's @@ -2323,11 +2309,10 @@ void BacktestEngine::strategy_exit(const std::string& id, const std::string& fro extra.qty_percent = (live_pos_qty > kQtyEpsilon) ? (leg_qty / live_pos_qty) * 100.0 : order.qty_percent; - extra.requested_partial = leg_qty < live_pos_qty - kFullQtyEps; + extra.quantity_request.reserve(leg_qty, live_pos_qty); extra.created_seq = next_order_seq_++; extra.incarnation = next_order_incarnation_++; - extra.created_by_same_id_replacement = false; - extra.replaced_exit_order_incarnation = 0; + extra.replaced_order_incarnation = 0; pending_orders_.push_back(std::move(extra)); } } @@ -2424,9 +2409,11 @@ void BacktestEngine::strategy_order(const std::string& id, bool is_long, double bar_index_); } int64_t preserved_seq = 0; + uint64_t replaced_order_incarnation = 0; for (const auto& o : pending_orders_) { if (o.id == id) { preserved_seq = o.created_seq; + replaced_order_incarnation = o.incarnation; break; } } @@ -2476,14 +2463,10 @@ void BacktestEngine::strategy_order(const std::string& id, bool is_long, double order.created_bar = bar_index_; order.created_seq = preserved_seq > 0 ? preserved_seq : next_order_seq_++; order.incarnation = next_order_incarnation_++; - order.created_during_coof_recalc = coof_fill_recalc_active_; - order.coof_born_at_close_recalc = - coof_fill_recalc_active_ && coof_cursor_is_bar_close_; - // KI-67: a fill recalc that was NOT triggered at the bar-open tick is a - // mid-bar recalc; orders it places are cascade orders (eligible only at the - // remaining extreme waypoints of the historical 4-tick path). - order.coof_born_mid_bar = - coof_fill_recalc_active_ && !coof_recalc_at_bar_open_; + order.replaced_order_incarnation = replaced_order_incarnation; + order.birth = capture_order_birth(); + order.pine_birth_reach = compat::pine::select_historical_birth_reach( + order.birth, false); order.created_position_side = position_side_; order.created_after_position_close_in_bar = pending_close_qty_in_bar_ > kQtyEpsilon; @@ -2901,23 +2884,23 @@ uint64_t BacktestEngine::queue_deferred_close_order( order.qty_percent = closes_full_position ? 100.0 : (position_qty_ > eps ? (qty_to_close / position_qty_) * 100.0 : 100.0); } + // Preserve the source close's already-resolved placement target. This is + // not a fixed executable-quantity promise: the existing ANY-relative path + // can later bind its percentage to a replacement position (e.g. target 1 + // against E2 can reserve 2 against new E4). Do not fabricate an original + // Pine percentage, or an exposure-coverage receipt before that binding. + order.quantity_request.request(QuantityIntent::units(qty_to_close)); order.oca_name = ""; order.oca_type = 0; order.created_bar = bar_index_; order.created_seq = next_order_seq_++; order.incarnation = next_order_incarnation_++; - order.created_during_coof_recalc = coof_fill_recalc_active_; - order.coof_born_at_close_recalc = - coof_fill_recalc_active_ && coof_cursor_is_bar_close_; - // KI-67: a fill recalc that was NOT triggered at the bar-open tick is a - // mid-bar recalc; orders it places are cascade orders (eligible only at the - // remaining extreme waypoints of the historical 4-tick path). - order.coof_born_mid_bar = - coof_fill_recalc_active_ && !coof_recalc_at_bar_open_; + order.birth = capture_order_birth(); + order.pine_birth_reach = compat::pine::select_historical_birth_reach( + order.birth, false); order.created_position_side = position_side_; order.tv_carry_qty = position_qty_; order.comment = comment; - order.created_while_in_position = true; // design-declined-reversal-close-leg: the qty this close debited from // id_unclosed_qty_ at CALL time (default-FIFO branch), so a later // suppression can re-credit exactly that amount. NaN when nothing was diff --git a/src/engine_stream.cpp b/src/engine_stream.cpp index a3ca41f8..c121c11f 100644 --- a/src/engine_stream.cpp +++ b/src/engine_stream.cpp @@ -678,7 +678,7 @@ uint64_t BacktestEngine::stream_state_hash() const { integer(static_cast(bar.timestamp)); real(bar.open); real(bar.high); real(bar.low); real(bar.close); real(bar.volume); }; - integer(4); integer(broker_state_hash()); + integer(5); integer(broker_state_hash()); integer(static_cast(stream_phase_)); integer(static_cast(stream_input_mode_)); integer(static_cast(stream_input_tf_ms_)); diff --git a/src/pending_order_mirror.cpp b/src/pending_order_mirror.cpp index bfb8fe92..f0e99c57 100644 --- a/src/pending_order_mirror.cpp +++ b/src/pending_order_mirror.cpp @@ -51,18 +51,18 @@ void fill_pending_order_mirror(const PendingOrder& src, pf_pending_order_v1_t* o out->created_bar = (int32_t)src.created_bar; out->created_seq = src.created_seq; out->incarnation = src.incarnation; - out->created_by_same_id_replacement = src.created_by_same_id_replacement ? 1 : 0; + out->created_by_same_id_replacement = src.type != OrderType::RAW_ORDER && src.replaced_order_incarnation != 0 ? 1 : 0; out->replaced_default_market_incarnation = src.replaced_default_market_incarnation; out->declined_by_replaced_short_market = src.declined_by_replaced_short_market ? 1 : 0; - out->replaced_exit_order_incarnation = src.replaced_exit_order_incarnation; + out->replaced_exit_order_incarnation = src.type == OrderType::EXIT ? src.replaced_order_incarnation : 0; out->recreated_after_named_cancelled_entry_incarnation = src.recreated_after_named_cancelled_entry_incarnation; out->named_cancel_surviving_exit_incarnation = src.named_cancel_surviving_exit_incarnation; out->stop_limit_activated = src.stop_limit_activated ? 1 : 0; out->coof_suppress_stop_on_entry_bar = src.coof_suppress_stop_on_entry_bar ? 1 : 0; out->coof_suppress_limit_on_entry_bar = src.coof_suppress_limit_on_entry_bar ? 1 : 0; - out->created_during_coof_recalc = src.created_during_coof_recalc ? 1 : 0; - out->coof_born_at_close_recalc = src.coof_born_at_close_recalc ? 1 : 0; - out->coof_born_mid_bar = src.coof_born_mid_bar ? 1 : 0; + out->created_during_coof_recalc = src.birth.from_fill() ? 1 : 0; + out->coof_born_at_close_recalc = src.birth.at_terminal_fill() ? 1 : 0; + out->coof_born_mid_bar = compat::pine::historical_cascade_reach(src) ? 1 : 0; out->coof_cascade_seg_i = (int32_t)src.coof_cascade_seg_i; out->coof_cascade_inflight_fires = src.coof_cascade_inflight_fires ? 1 : 0; out->created_position_side = (int32_t)src.created_position_side; @@ -106,12 +106,12 @@ void fill_pending_order_mirror(const PendingOrder& src, pf_pending_order_v1_t* o out->signal_close_mc_fill_seq = src.signal_close_mc_fill_seq; out->signal_close_mc_remaining_qty = src.signal_close_mc_remaining_qty; copy_str(src.comment, out->comment, &out->comment_truncated, &out->comment_hash64); - out->requested_partial = src.requested_partial ? 1 : 0; - out->full_percent_exit_request = src.full_percent_exit_request ? 1 : 0; + out->requested_partial = src.quantity_request.is_partial(1e-9, 1e-9) ? 1 : 0; + out->full_percent_exit_request = src.quantity_request.requests_all() ? 1 : 0; out->pooc_global_full_exit_dynamic_qty = src.pooc_global_full_exit_dynamic_qty ? 1 : 0; out->pooc_global_full_exit_tracks_bound_adds = src.pooc_global_full_exit_tracks_bound_adds ? 1 : 0; out->pooc_global_full_exit_bound_add = src.pooc_global_full_exit_bound_add ? 1 : 0; - out->created_while_in_position = src.created_while_in_position ? 1 : 0; + out->created_while_in_position = src.type == OrderType::EXIT && src.created_position_side != PositionSide::FLAT ? 1 : 0; out->sbmt_member = src.sbmt_member ? 1 : 0; out->sbmt_own_qty = src.sbmt_own_qty; out->sbmt_tx_qty = src.sbmt_tx_qty; @@ -130,6 +130,26 @@ void fill_pending_order_mirror(const PendingOrder& src, pf_pending_order_v1_t* o out->suppressed_close_consumed_ledger_qty = src.suppressed_close_consumed_ledger_qty; out->suppressed_close_retired_ledger_qty = src.suppressed_close_retired_ledger_qty; out->short_seed_collision_role = (int32_t)src.short_seed_collision_role; + out->replaced_order_incarnation = src.replaced_order_incarnation; + out->birth_timestamp = src.birth.timestamp(); + out->birth_cause = (int32_t)src.birth.cause(); + out->birth_bar = src.birth.bar(); + out->birth_cursor_domain = (int32_t)src.birth.cursor().domain(); + out->birth_cursor_position = (int32_t)src.birth.cursor().position(); + out->birth_cursor_index = src.birth.cursor().index(); + out->birth_cursor_count = src.birth.cursor().count(); + out->birth_cursor_price = src.birth.cursor_price(); + out->birth_first_fill = src.birth.first_fill(); + out->birth_last_fill = src.birth.last_fill(); + out->birth_evaluation_ordinal = src.birth.evaluation_ordinal(); + out->pine_birth_reach = (int32_t)src.pine_birth_reach; + out->quantity_intent_kind = src.quantity_request.intent() ? static_cast(src.quantity_request.intent()->kind()) + 1 : 0; + out->quantity_intent_units = src.quantity_request.intent() && src.quantity_request.intent()->kind() == QuantityIntent::Kind::Units ? src.quantity_request.intent()->units() : 0.0; + out->quantity_intent_numerator = src.quantity_request.intent() && src.quantity_request.intent()->kind() == QuantityIntent::Kind::Fraction ? src.quantity_request.intent()->numerator() : 0.0; + out->quantity_intent_denominator = src.quantity_request.intent() && src.quantity_request.intent()->kind() == QuantityIntent::Kind::Fraction ? src.quantity_request.intent()->denominator() : 0.0; + out->quantity_reservation_present = src.quantity_request.reservation().has_value() ? 1 : 0; + out->quantity_reservation_units = src.quantity_request.reservation() ? src.quantity_request.reservation()->units : 0.0; + out->quantity_reservation_basis_units = src.quantity_request.reservation() ? src.quantity_request.reservation()->basis_units : 0.0; } namespace { @@ -247,6 +267,26 @@ const pf_field_desc_t kLayout[] = { PF_PO_FIELD(suppressed_close_consumed_ledger_qty, "double"), PF_PO_FIELD(suppressed_close_retired_ledger_qty, "double"), PF_PO_FIELD(short_seed_collision_role, "int32_t"), + PF_PO_FIELD(replaced_order_incarnation, "uint64_t"), + PF_PO_FIELD(birth_timestamp, "int64_t"), + PF_PO_FIELD(birth_cause, "int32_t"), + PF_PO_FIELD(birth_bar, "int32_t"), + PF_PO_FIELD(birth_cursor_domain, "int32_t"), + PF_PO_FIELD(birth_cursor_position, "int32_t"), + PF_PO_FIELD(birth_cursor_index, "int32_t"), + PF_PO_FIELD(birth_cursor_count, "int32_t"), + PF_PO_FIELD(birth_cursor_price, "double"), + PF_PO_FIELD(birth_first_fill, "uint64_t"), + PF_PO_FIELD(birth_last_fill, "uint64_t"), + PF_PO_FIELD(birth_evaluation_ordinal, "uint64_t"), + PF_PO_FIELD(pine_birth_reach, "int32_t"), + PF_PO_FIELD(quantity_intent_kind, "uint64_t"), + PF_PO_FIELD(quantity_intent_units, "double"), + PF_PO_FIELD(quantity_intent_numerator, "double"), + PF_PO_FIELD(quantity_intent_denominator, "double"), + PF_PO_FIELD(quantity_reservation_present, "uint8_t"), + PF_PO_FIELD(quantity_reservation_units, "double"), + PF_PO_FIELD(quantity_reservation_basis_units, "double"), }; #undef PF_PO_FIELD diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b5b72d63..aab7cf1f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,6 @@ set(TEST_SOURCES + test_pending_order_core + test_pending_quantity_intent test_bulk_preflight test_opening_obligation test_high_value_price_admission @@ -78,6 +80,8 @@ set(TEST_SOURCES test_engine_risk test_strategy_oca test_pending_order_identity + test_pending_placement_receipts + test_order_birth_provenance test_oca_raw_pyramid_add test_strategy_pyramiding test_pyramiding_count_partial_drain diff --git a/tests/fixtures/pending_order_prefix/c45-fields.inc b/tests/fixtures/pending_order_prefix/c45-fields.inc new file mode 100644 index 00000000..37eb4375 --- /dev/null +++ b/tests/fixtures/pending_order_prefix/c45-fields.inc @@ -0,0 +1,108 @@ +PF_PREFIX_FIELD(struct_version) +PF_PREFIX_FIELD(size) +PF_PREFIX_FIELD(id) +PF_PREFIX_FIELD(id_truncated) +PF_PREFIX_FIELD(id_hash64) +PF_PREFIX_FIELD(from_entry) +PF_PREFIX_FIELD(from_entry_truncated) +PF_PREFIX_FIELD(from_entry_hash64) +PF_PREFIX_FIELD(type) +PF_PREFIX_FIELD(is_long) +PF_PREFIX_FIELD(limit_price) +PF_PREFIX_FIELD(stop_price) +PF_PREFIX_FIELD(trail_points) +PF_PREFIX_FIELD(trail_price) +PF_PREFIX_FIELD(trail_offset) +PF_PREFIX_FIELD(profit_ticks) +PF_PREFIX_FIELD(loss_ticks) +PF_PREFIX_FIELD(qty) +PF_PREFIX_FIELD(qty_type) +PF_PREFIX_FIELD(qty_percent) +PF_PREFIX_FIELD(oca_name) +PF_PREFIX_FIELD(oca_name_truncated) +PF_PREFIX_FIELD(oca_name_hash64) +PF_PREFIX_FIELD(oca_type) +PF_PREFIX_FIELD(created_bar) +PF_PREFIX_FIELD(created_seq) +PF_PREFIX_FIELD(incarnation) +PF_PREFIX_FIELD(created_by_same_id_replacement) +PF_PREFIX_FIELD(replaced_default_market_incarnation) +PF_PREFIX_FIELD(declined_by_replaced_short_market) +PF_PREFIX_FIELD(replaced_exit_order_incarnation) +PF_PREFIX_FIELD(recreated_after_named_cancelled_entry_incarnation) +PF_PREFIX_FIELD(named_cancel_surviving_exit_incarnation) +PF_PREFIX_FIELD(stop_limit_activated) +PF_PREFIX_FIELD(coof_suppress_stop_on_entry_bar) +PF_PREFIX_FIELD(coof_suppress_limit_on_entry_bar) +PF_PREFIX_FIELD(created_during_coof_recalc) +PF_PREFIX_FIELD(coof_born_at_close_recalc) +PF_PREFIX_FIELD(coof_born_mid_bar) +PF_PREFIX_FIELD(coof_cascade_seg_i) +PF_PREFIX_FIELD(coof_cascade_inflight_fires) +PF_PREFIX_FIELD(created_position_side) +PF_PREFIX_FIELD(created_position_cycle_seq) +PF_PREFIX_FIELD(created_after_position_close_in_bar) +PF_PREFIX_FIELD(over_pyramiding_cap_at_placement) +PF_PREFIX_FIELD(same_id_stop_deferred_close_all_bar) +PF_PREFIX_FIELD(same_id_stop_deferred_close_all_incarnation) +PF_PREFIX_FIELD(reverses_same_bar_market_from_flat) +PF_PREFIX_FIELD(paired_flat_market_candidate) +PF_PREFIX_FIELD(paired_flat_market_own_qty) +PF_PREFIX_FIELD(paired_flat_market_signal_close) +PF_PREFIX_FIELD(paired_flat_market_signal_equity) +PF_PREFIX_FIELD(paired_flat_market_signal_margin_pct) +PF_PREFIX_FIELD(paired_flat_market_signal_pointvalue) +PF_PREFIX_FIELD(paired_flat_market_signal_fx) +PF_PREFIX_FIELD(paired_flat_market_peer_seq) +PF_PREFIX_FIELD(paired_flat_market_transaction_qty) +PF_PREFIX_FIELD(default_flat_market_gross_candidate) +PF_PREFIX_FIELD(tv_carry_qty) +PF_PREFIX_FIELD(frozen_default_qty) +PF_PREFIX_FIELD(default_stop_placement_qty) +PF_PREFIX_FIELD(default_stop_placement_equity) +PF_PREFIX_FIELD(default_stop_placement_signal_close) +PF_PREFIX_FIELD(default_stop_sizing_price) +PF_PREFIX_FIELD(sizing_equity) +PF_PREFIX_FIELD(sizing_price) +PF_PREFIX_FIELD(sizing_fx) +PF_PREFIX_FIELD(sizing_mark) +PF_PREFIX_FIELD(opening_affordability_exemption_candidate) +PF_PREFIX_FIELD(explicit_flat_admission_candidate) +PF_PREFIX_FIELD(explicit_placement_equity) +PF_PREFIX_FIELD(explicit_slipped_signal_close) +PF_PREFIX_FIELD(affordability_placement_equity) +PF_PREFIX_FIELD(affordability_signal_price) +PF_PREFIX_FIELD(affordability_held_qty) +PF_PREFIX_FIELD(affordability_close_only) +PF_PREFIX_FIELD(rounded_signal_cost_close_only) +PF_PREFIX_FIELD(signal_close_mc_bar) +PF_PREFIX_FIELD(signal_close_mc_entry_incarnation) +PF_PREFIX_FIELD(signal_close_mc_fill_seq) +PF_PREFIX_FIELD(signal_close_mc_remaining_qty) +PF_PREFIX_FIELD(comment) +PF_PREFIX_FIELD(comment_truncated) +PF_PREFIX_FIELD(comment_hash64) +PF_PREFIX_FIELD(requested_partial) +PF_PREFIX_FIELD(full_percent_exit_request) +PF_PREFIX_FIELD(pooc_global_full_exit_dynamic_qty) +PF_PREFIX_FIELD(pooc_global_full_exit_tracks_bound_adds) +PF_PREFIX_FIELD(pooc_global_full_exit_bound_add) +PF_PREFIX_FIELD(created_while_in_position) +PF_PREFIX_FIELD(sbmt_member) +PF_PREFIX_FIELD(sbmt_own_qty) +PF_PREFIX_FIELD(sbmt_tx_qty) +PF_PREFIX_FIELD(sbmt_kept_over_cap) +PF_PREFIX_FIELD(sbmt_close_qty) +PF_PREFIX_FIELD(sbmt_close_buy) +PF_PREFIX_FIELD(suppress_as_declined_reversal_close) +PF_PREFIX_FIELD(dormant_bracket) +PF_PREFIX_FIELD(dormant_reissue_pending) +PF_PREFIX_FIELD(dormant_original_stop_price) +PF_PREFIX_FIELD(dormant_hold_bar) +PF_PREFIX_FIELD(dormant_reversal_kill_bar) +PF_PREFIX_FIELD(dormant_trail_best) +PF_PREFIX_FIELD(dormant_trail_best_start) +PF_PREFIX_FIELD(dormant_trail_leg_dead) +PF_PREFIX_FIELD(suppressed_close_consumed_ledger_qty) +PF_PREFIX_FIELD(suppressed_close_retired_ledger_qty) +PF_PREFIX_FIELD(short_seed_collision_role) diff --git a/tests/fixtures/pending_order_prefix/c45-v1.hpp b/tests/fixtures/pending_order_prefix/c45-v1.hpp new file mode 100644 index 00000000..a5fa49e9 --- /dev/null +++ b/tests/fixtures/pending_order_prefix/c45-v1.hpp @@ -0,0 +1,113 @@ +// Exact c45 v1 POD declarations; only typedef/tag names changed for compile comparison. +#pragma once +#include +typedef struct c45_pending_order_s { + uint32_t struct_version; + uint32_t size; + char id[64]; + uint8_t id_truncated; + uint64_t id_hash64; + char from_entry[64]; + uint8_t from_entry_truncated; + uint64_t from_entry_hash64; + int32_t type; + uint8_t is_long; + double limit_price; + double stop_price; + double trail_points; + double trail_price; + double trail_offset; + double profit_ticks; + double loss_ticks; + double qty; + int32_t qty_type; + double qty_percent; + char oca_name[64]; + uint8_t oca_name_truncated; + uint64_t oca_name_hash64; + int32_t oca_type; + int32_t created_bar; + int64_t created_seq; + uint64_t incarnation; + uint8_t created_by_same_id_replacement; + uint64_t replaced_default_market_incarnation; + uint8_t declined_by_replaced_short_market; + uint64_t replaced_exit_order_incarnation; + uint64_t recreated_after_named_cancelled_entry_incarnation; + uint64_t named_cancel_surviving_exit_incarnation; + uint8_t stop_limit_activated; + uint8_t coof_suppress_stop_on_entry_bar; + uint8_t coof_suppress_limit_on_entry_bar; + uint8_t created_during_coof_recalc; + uint8_t coof_born_at_close_recalc; + uint8_t coof_born_mid_bar; + int32_t coof_cascade_seg_i; + uint8_t coof_cascade_inflight_fires; + int32_t created_position_side; + int64_t created_position_cycle_seq; + uint8_t created_after_position_close_in_bar; + uint8_t over_pyramiding_cap_at_placement; + int32_t same_id_stop_deferred_close_all_bar; + uint64_t same_id_stop_deferred_close_all_incarnation; + uint8_t reverses_same_bar_market_from_flat; + uint8_t paired_flat_market_candidate; + double paired_flat_market_own_qty; + double paired_flat_market_signal_close; + double paired_flat_market_signal_equity; + double paired_flat_market_signal_margin_pct; + double paired_flat_market_signal_pointvalue; + double paired_flat_market_signal_fx; + int64_t paired_flat_market_peer_seq; + double paired_flat_market_transaction_qty; + uint8_t default_flat_market_gross_candidate; + double tv_carry_qty; + double frozen_default_qty; + double default_stop_placement_qty; + double default_stop_placement_equity; + double default_stop_placement_signal_close; + double default_stop_sizing_price; + double sizing_equity; + double sizing_price; + double sizing_fx; + double sizing_mark; + uint8_t opening_affordability_exemption_candidate; + uint8_t explicit_flat_admission_candidate; + double explicit_placement_equity; + double explicit_slipped_signal_close; + double affordability_placement_equity; + double affordability_signal_price; + double affordability_held_qty; + uint8_t affordability_close_only; + uint8_t rounded_signal_cost_close_only; + int32_t signal_close_mc_bar; + uint64_t signal_close_mc_entry_incarnation; + uint64_t signal_close_mc_fill_seq; + double signal_close_mc_remaining_qty; + char comment[64]; + uint8_t comment_truncated; + uint64_t comment_hash64; + uint8_t requested_partial; + uint8_t full_percent_exit_request; + uint8_t pooc_global_full_exit_dynamic_qty; + uint8_t pooc_global_full_exit_tracks_bound_adds; + uint8_t pooc_global_full_exit_bound_add; + uint8_t created_while_in_position; + uint8_t sbmt_member; + double sbmt_own_qty; + double sbmt_tx_qty; + uint8_t sbmt_kept_over_cap; + double sbmt_close_qty; + uint8_t sbmt_close_buy; + uint8_t suppress_as_declined_reversal_close; + uint8_t dormant_bracket; + uint8_t dormant_reissue_pending; + double dormant_original_stop_price; + int32_t dormant_hold_bar; + int32_t dormant_reversal_kill_bar; + double dormant_trail_best; + double dormant_trail_best_start; + uint8_t dormant_trail_leg_dead; + double suppressed_close_consumed_ledger_qty; + double suppressed_close_retired_ledger_qty; + int32_t short_seed_collision_role; +} c45_pending_order_t; diff --git a/tests/fixtures/pending_quantity/README.md b/tests/fixtures/pending_quantity/README.md new file mode 100644 index 00000000..e925c848 --- /dev/null +++ b/tests/fixtures/pending_quantity/README.md @@ -0,0 +1,3 @@ +Exact shipped c45cf5a pending-order mirror header, retained for compile-time prefix-layout checks. +SHA-256: cf4f756ce681dfec396918be6bc03d25e8ed5b1dbf9b2515c40af4c2b3df5236 +No runtime or strategy reference is embedded. diff --git a/tests/fixtures/pending_quantity/c45_pending_order_mirror.hpp b/tests/fixtures/pending_quantity/c45_pending_order_mirror.hpp new file mode 100644 index 00000000..ce672052 --- /dev/null +++ b/tests/fixtures/pending_quantity/c45_pending_order_mirror.hpp @@ -0,0 +1,136 @@ +// GENERATED by scripts/gen_pending_order_mirror.py from include/pineforge/engine.hpp -- do not edit. +// 98 PendingOrder members mirrored (108 POD fields incl. struct_version/size). +#pragma once +#include + +/* C-compatible value snapshot of one resting pineforge::PendingOrder + * (spec 3.6). struct_version identifies the field set (this file: + * 1); size is sizeof(pf_pending_order_v1_t) as the producer + * compiled it. Strings are copied into a NUL-terminated char[64] + * (name_truncated = 1 when the source was longer than 63 bytes) with + * name_hash64 = FNV-1a 64 of the FULL source string. Enums are their + * int32 value; bool is 0/1 in a uint8_t. Append-only, like every + * pineforge.h POD. */ +typedef struct pf_pending_order_v1_s { + uint32_t struct_version; + uint32_t size; + char id[64]; + uint8_t id_truncated; + uint64_t id_hash64; + char from_entry[64]; + uint8_t from_entry_truncated; + uint64_t from_entry_hash64; + int32_t type; + uint8_t is_long; + double limit_price; + double stop_price; + double trail_points; + double trail_price; + double trail_offset; + double profit_ticks; + double loss_ticks; + double qty; + int32_t qty_type; + double qty_percent; + char oca_name[64]; + uint8_t oca_name_truncated; + uint64_t oca_name_hash64; + int32_t oca_type; + int32_t created_bar; + int64_t created_seq; + uint64_t incarnation; + uint8_t created_by_same_id_replacement; + uint64_t replaced_default_market_incarnation; + uint8_t declined_by_replaced_short_market; + uint64_t replaced_exit_order_incarnation; + uint64_t recreated_after_named_cancelled_entry_incarnation; + uint64_t named_cancel_surviving_exit_incarnation; + uint8_t stop_limit_activated; + uint8_t coof_suppress_stop_on_entry_bar; + uint8_t coof_suppress_limit_on_entry_bar; + uint8_t created_during_coof_recalc; + uint8_t coof_born_at_close_recalc; + uint8_t coof_born_mid_bar; + int32_t coof_cascade_seg_i; + uint8_t coof_cascade_inflight_fires; + int32_t created_position_side; + int64_t created_position_cycle_seq; + uint8_t created_after_position_close_in_bar; + uint8_t over_pyramiding_cap_at_placement; + int32_t same_id_stop_deferred_close_all_bar; + uint64_t same_id_stop_deferred_close_all_incarnation; + uint8_t reverses_same_bar_market_from_flat; + uint8_t paired_flat_market_candidate; + double paired_flat_market_own_qty; + double paired_flat_market_signal_close; + double paired_flat_market_signal_equity; + double paired_flat_market_signal_margin_pct; + double paired_flat_market_signal_pointvalue; + double paired_flat_market_signal_fx; + int64_t paired_flat_market_peer_seq; + double paired_flat_market_transaction_qty; + uint8_t default_flat_market_gross_candidate; + double tv_carry_qty; + double frozen_default_qty; + double default_stop_placement_qty; + double default_stop_placement_equity; + double default_stop_placement_signal_close; + double default_stop_sizing_price; + double sizing_equity; + double sizing_price; + double sizing_fx; + double sizing_mark; + uint8_t opening_affordability_exemption_candidate; + uint8_t explicit_flat_admission_candidate; + double explicit_placement_equity; + double explicit_slipped_signal_close; + double affordability_placement_equity; + double affordability_signal_price; + double affordability_held_qty; + uint8_t affordability_close_only; + uint8_t rounded_signal_cost_close_only; + int32_t signal_close_mc_bar; + uint64_t signal_close_mc_entry_incarnation; + uint64_t signal_close_mc_fill_seq; + double signal_close_mc_remaining_qty; + char comment[64]; + uint8_t comment_truncated; + uint64_t comment_hash64; + uint8_t requested_partial; + uint8_t full_percent_exit_request; + uint8_t pooc_global_full_exit_dynamic_qty; + uint8_t pooc_global_full_exit_tracks_bound_adds; + uint8_t pooc_global_full_exit_bound_add; + uint8_t created_while_in_position; + uint8_t sbmt_member; + double sbmt_own_qty; + double sbmt_tx_qty; + uint8_t sbmt_kept_over_cap; + double sbmt_close_qty; + uint8_t sbmt_close_buy; + uint8_t suppress_as_declined_reversal_close; + uint8_t dormant_bracket; + uint8_t dormant_reissue_pending; + double dormant_original_stop_price; + int32_t dormant_hold_bar; + int32_t dormant_reversal_kill_bar; + double dormant_trail_best; + double dormant_trail_best_start; + uint8_t dormant_trail_leg_dead; + double suppressed_close_consumed_ledger_qty; + double suppressed_close_retired_ledger_qty; + int32_t short_seed_collision_role; +} pf_pending_order_v1_t; + +/* One row of the self-describing layout table returned by + * strategy_pending_order_layout(): field name, C type spelling + * ("uint8_t", "int32_t", "int64_t", "uint64_t", "double", "char[64]", + * "uint32_t"), byte offset inside pf_pending_order_v1_t, byte size. */ +typedef struct pf_field_desc_s { + const char* name; + const char* type; + uint32_t offset; + uint32_t size; +} pf_field_desc_t; +#define PF_PENDING_ORDER_STRUCT_VERSION 1 +#define PF_PENDING_ORDER_STR_CAP 64 diff --git a/tests/fixtures/script_cpp_abi/basec45/README.md b/tests/fixtures/script_cpp_abi/basec45/README.md new file mode 100644 index 00000000..5e615d94 --- /dev/null +++ b/tests/fixtures/script_cpp_abi/basec45/README.md @@ -0,0 +1 @@ +Frozen exact header closure of shipped engine c45 (internal C++ namespace v4). Compile/link-only input for rejecting old C++ objects after PendingOrder representation changes. No runtime or strategy reference data is archived; no execution is permitted. version.h is supplied by the current configured build. Each UTF-8 file, original Git blob and deterministic compressed archive are hash-pinned in manifest.json. diff --git a/tests/fixtures/script_cpp_abi/basec45/headers.json.gz b/tests/fixtures/script_cpp_abi/basec45/headers.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..855f472af54f66d9f91818d7cfec5da15999d09f GIT binary patch literal 140384 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

q|iu{=YtoAuTHMw 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 j9W<#llM8q#u>h*H=)&lK8$3Xh1@MpbJ$E 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 +#include +#include +#include +#include +#include +#include +#include "fixtures/pending_order_prefix/c45-v1.hpp" +#define PF_PREFIX_FIELD(name) \ + static_assert(offsetof(pf_pending_order_v1_t, name) == offsetof(c45_pending_order_t, name), "v1 prefix offset changed"); \ + static_assert(sizeof(((pf_pending_order_v1_t*)0)->name) == sizeof(((c45_pending_order_t*)0)->name), "v1 prefix field size changed"); +#include "fixtures/pending_order_prefix/c45-fields.inc" +#undef PF_PREFIX_FIELD +static_assert(offsetof(pf_pending_order_v1_t, birth_cause) >= sizeof(c45_pending_order_t), "new facts must append after the v1 prefix"); +using namespace pineforge; +namespace pineforge { +void fill_pending_order_mirror(const PendingOrder&, pf_pending_order_v1_t*); +} +namespace { +int failed = 0; +#define CHECK(x) do { if (!(x)) { std::fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, #x); ++failed; } } while (0) +const double nan = std::numeric_limits::quiet_NaN(); +const Bar bars[] = {{100, 101, 99, 100, 1, 0}, {100, 110, 95, 108, 1, 60000}}; + +class Probe : public BacktestEngine { +public: + Probe() { + initial_capital_ = 100000; + calc_on_order_fills_ = true; + default_qty_type_ = QtyType::FIXED; + default_qty_value_ = 1; + pyramiding_ = 10; + commission_value_ = 0; + syminfo_mintick_ = 0.01; + } + const PendingOrder& get(const std::string& id) const { + for (const auto& o : pending_orders_) if (o.id == id) return o; + throw std::runtime_error("missing test order " + id); + } + void direct(const std::string& id) { strategy_entry(id, true, 1, nan, 1); } + void set_birth(const std::string& id, const OrderBirth& birth) { + for (auto& o : pending_orders_) if (o.id == id) { o.birth = birth; return; } + throw std::runtime_error("missing mutation target"); + } + void clear_trailing_trigger(const std::string& id) { + for (auto& o : pending_orders_) if (o.id == id) { + o.trail_points = o.trail_price = nan; + return; + } + } + std::size_t pending_count() const { return pending_orders_.size(); } + const std::vector& recorded_hashes() const { return broker_state_hashes_; } +}; + +// The first callback is triggered by fill1. It directly closes that lot, +// advancing the broker to fill2, then emits another order in the SAME callback. +// That order must still name fill1, while the next callback names fill2. +class DirectCascade : public Probe { +public: + int bar_one_calls = 0; + std::vector observed; + OrderBirth after_direct_fill; + OrderBirth cloned_command; + OrderBirth replaced_birth, replacement_birth; + uint64_t replaced_incarnation = 0, replacement_incarnation = 0; + int64_t replaced_priority = 0, replacement_priority = 0; + void on_bar(const Bar&) override { + if (bar_index_ == 0) { + strategy_entry("seed", true, nan, nan, 1); + direct("replace"); + return; + } + if (bar_index_ != 1) return; + const int call = bar_one_calls++; + direct("witness-" + std::to_string(call)); + observed.push_back(get("witness-" + std::to_string(call)).birth); + if (call == 0) { + replaced_birth = get("replace").birth; + replaced_incarnation = get("replace").incarnation; + replaced_priority = get("replace").created_seq; + direct("replace"); + replacement_birth = get("replace").birth; + replacement_incarnation = get("replace").incarnation; + replacement_priority = get("replace").created_seq; + strategy_close("seed", "", nan, nan, true); + direct("after-direct"); + after_direct_fill = get("after-direct").birth; + DirectCascade copy(*this); + copy.direct("clone-command"); + cloned_command = copy.get("clone-command").birth; + } + } +}; + +class LaterOpenPolicy : public Probe { +public: + int bar_one_calls = 0; + OrderBirth trailing_birth, priced_birth; + void on_bar(const Bar&) override { + if (bar_index_ == 0) { + strategy_entry("A", true, nan, nan, 1); + strategy_entry("B", true, nan, nan, 1); + return; + } + if (bar_index_ != 1 || bar_one_calls++ != 1) return; + strategy_exit("trailing", "A", nan, nan, 100000, 1); + strategy_exit("priced", "B", nan, 1); + trailing_birth = get("trailing").birth; + priced_birth = get("priced").birth; + } +}; + +class SegmentOrigin : public Probe { +public: + int bar_one_calls = 0; + OrderBirth receipt; + void on_bar(const Bar&) override { + if (bar_index_ == 0) { strategy_entry("stop", true, nan, 105, 1); return; } + if (bar_index_ == 1 && bar_one_calls++ == 0) { + direct("segment-witness"); + receipt = get("segment-witness").birth; + } + } +}; + +class ProducerOrigins : public Probe { +public: + bool captured = false; + std::vector births; + void on_bar(const Bar&) override { + if (bar_index_ == 0) { strategy_entry("seed", true, nan, nan, 1); return; } + if (bar_index_ != 1 || captured) return; + captured = true; + strategy_entry("entry", true, 1, nan, 1); + strategy_order("raw", true, 1, 1); + strategy_exit("exit", "seed", nan, 1); + for (const std::string id : {"entry", "raw", "exit"}) births.push_back(get(id).birth); + } +}; + +class ThrowsInFill : public Probe { +public: + void on_bar(const Bar&) override { + if (bar_index_ == 0) strategy_entry("seed", true, nan, nan, 1); + else throw std::runtime_error("literal callback failure"); + } +}; + +void rejects(const std::function& f) { + bool rejected = false; + try { f(); } catch (const std::invalid_argument&) { rejected = true; } + CHECK(rejected); +} + +void value_contract() { + const auto open = BirthCursor::point(BirthCursorDomain::HistoricalPath, 0, 4); + const auto close = BirthCursor::point(BirthCursorDomain::HistoricalPath, 3, 4); + const auto first = OrderBirth::fill_evaluation(2, 120000, open, 100, 7, 7, 1); + const auto later = OrderBirth::fill_evaluation(2, 120000, open, 100, 8, 8, 2); + const auto terminal = OrderBirth::fill_evaluation(2, 120000, close, 100, 9, 9, 3); + CHECK(first.from_fill() && !first.at_terminal_fill()); + CHECK(compat::pine::first_open_fill_evaluation(first)); + CHECK(!compat::pine::first_open_fill_evaluation(later)); + CHECK(terminal.at_terminal_fill() && !terminal.cursor().first_point()); + CHECK(terminal.cursor().following_segment() == -1); + // Equal prices do not collapse distinct physical path positions. + CHECK(first.cursor_price() == terminal.cursor_price()); + CHECK(first.cursor().index() != terminal.cursor().index()); + const auto batch = OrderBirth::fill_evaluation(2, 120000, open, 100, 10, 12, 1); + CHECK(batch.first_fill() == 10 && batch.last_fill() == 12); + const auto copy = batch; + CHECK(copy.first_fill() == 10 && copy.last_fill() == 12); + rejects([&] { OrderBirth::fill_evaluation(2, 0, open, 100, 0, 1, 1); }); + rejects([&] { OrderBirth::fill_evaluation(2, 0, open, 100, 3, 2, 1); }); + rejects([&] { OrderBirth::fill_evaluation(2, 0, open, 100, 1, 1, 0); }); + rejects([&] { OrderBirth::fill_evaluation(2, 0, open, nan, 1, 1, 1); }); + rejects([&] { OrderBirth::fill_evaluation(2, 0, BirthCursor{}, 100, 1, 1, 1); }); + rejects([&] { BirthCursor::point(BirthCursorDomain::HistoricalPath, 4, 4); }); + rejects([&] { BirthCursor::point(BirthCursorDomain::HistoricalPath, 0, 3); }); + rejects([&] { BirthCursor::segment(BirthCursorDomain::HistoricalPath, 3, 4); }); + rejects([&] { BirthCursor::point(BirthCursorDomain::None, 0, 4); }); +} +} + +int main() { + value_contract(); + DirectCascade direct; + direct.run(bars, 2); + CHECK(direct.observed.size() == 3); + if (direct.observed.size() >= 3) { + const auto& first = direct.observed[0]; + CHECK(first.from_fill() && first.first_fill() == 1 && first.last_fill() == 1); + CHECK(first.cursor().domain() == BirthCursorDomain::HistoricalPath); + CHECK(first.cursor().first_point() && first.cursor().count() == 4); + CHECK(first.bar() == 1 && first.timestamp() == 60000 && first.cursor_price() == 100); + CHECK(direct.after_direct_fill.first_fill() == 1); + CHECK(direct.observed[1].first_fill() == 2 && direct.observed[1].last_fill() == 2); + CHECK(direct.observed[1].evaluation_ordinal() == 2); + CHECK(direct.observed[2].cause() == OrderBirthCause::ChartEvaluation); + CHECK(direct.observed[2].first_fill() == 0); + CHECK(direct.cloned_command.cause() == OrderBirthCause::DirectCommand); + CHECK(direct.replaced_birth.cause() == OrderBirthCause::ChartEvaluation); + CHECK(direct.replaced_birth.bar() == 0); + CHECK(direct.replacement_birth.from_fill() && direct.replacement_birth.first_fill() == 1); + CHECK(direct.replacement_birth.bar() == 1); + CHECK(direct.replacement_incarnation > direct.replaced_incarnation); + CHECK(direct.replacement_priority == direct.replaced_priority); + } + const uint64_t original_hash = direct.broker_state_hash(); + DirectCascade copied(direct); + CHECK(copied.broker_state_hash() == original_hash); + copied.direct("external-command"); + CHECK(copied.get("external-command").birth.cause() == OrderBirthCause::DirectCommand); + CHECK(direct.broker_state_hash() == original_hash); + const Bar extended[] = {bars[0], bars[1], {108, 109, 107, 108, 1, 120000}}; + DirectCascade prefix, complete; + prefix.set_broker_state_hash_recording(true); + complete.set_broker_state_hash_recording(true); + prefix.run(extended, 2); + complete.run(extended, 3); + CHECK(prefix.recorded_hashes().size() == 2 && complete.recorded_hashes().size() == 3); + if (prefix.recorded_hashes().size() == 2 && complete.recorded_hashes().size() == 3) { + CHECK(prefix.recorded_hashes()[0] == complete.recorded_hashes()[0]); + CHECK(prefix.recorded_hashes()[1] == complete.recorded_hashes()[1]); + } + CHECK(prefix.get("witness-0").birth.first_fill() == complete.get("witness-0").birth.first_fill()); + CHECK(prefix.get("witness-0").birth.cursor().index() == complete.get("witness-0").birth.cursor().index()); + prefix.run(nullptr, 0); + CHECK(prefix.pending_count() == 0 && prefix.recorded_hashes().empty()); + for (int field = 0; field < 9; ++field) { + DirectCascade changed(direct); + const auto receipt = OrderBirth::fill_evaluation( + field == 0 ? 2 : 1, field == 1 ? 60001 : 60000, + field == 2 ? BirthCursor::segment(BirthCursorDomain::HistoricalPath, 0, 4) + : BirthCursor::point(field == 3 ? BirthCursorDomain::MagnifierTicks : BirthCursorDomain::HistoricalPath, + field == 4 ? 1 : 0, field == 3 ? 8 : 4), + field == 5 ? 101 : 100, field == 6 ? 2 : 1, + field == 7 || field == 6 ? 2 : 1, field == 8 ? 2 : 1); + changed.set_birth("witness-0", receipt); + CHECK(changed.broker_state_hash() != original_hash); + } + LaterOpenPolicy policy; + policy.run(bars, 2); + CHECK(policy.trailing_birth.from_fill() && policy.priced_birth.from_fill()); + CHECK(policy.trailing_birth.first_fill() == 2 && policy.priced_birth.first_fill() == 2); + CHECK(policy.trailing_birth.cursor().first_point()); + CHECK(policy.trailing_birth.evaluation_ordinal() == 2); + CHECK(!compat::pine::historical_cascade_reach(policy.get("trailing"))); + CHECK(compat::pine::historical_cascade_reach(policy.get("priced"))); + policy.clear_trailing_trigger("trailing"); + CHECK(!compat::pine::historical_cascade_reach(policy.get("trailing"))); + CHECK(policy.get("trailing").birth.first_fill() == 2); + pf_pending_order_v1_t mirror{}; + fill_pending_order_mirror(policy.get("trailing"), &mirror); + CHECK(mirror.created_during_coof_recalc == 1 && mirror.coof_born_mid_bar == 0); + CHECK(mirror.birth_first_fill == 2 && mirror.birth_cursor_index == 0); + CHECK(mirror.birth_evaluation_ordinal == 2); + SegmentOrigin segment; + segment.run(bars, 2); + CHECK(segment.receipt.from_fill()); + CHECK(segment.receipt.cursor().position() == BirthCursorPosition::Segment); + CHECK(segment.receipt.cursor().index() == 1 && segment.receipt.cursor_price() == 105); + CHECK(segment.receipt.evaluation_ordinal() == 1); + CHECK(!compat::pine::first_open_fill_evaluation(segment.receipt)); + ProducerOrigins producers; + producers.run(bars, 2); + CHECK(producers.births.size() == 3); + for (const auto& birth : producers.births) CHECK(birth.from_fill() && birth.first_fill() == 1); + ProducerOrigins magnified; + magnified.run(bars, 2, "1", "1", true, 4, MagnifierDistribution::ENDPOINTS); + CHECK(magnified.births.size() == 3); + for (const auto& birth : magnified.births) { + CHECK(birth.from_fill() && birth.first_fill() == 1); + CHECK(birth.cursor().domain() == BirthCursorDomain::MagnifierTicks); + CHECK(birth.cursor().first_point() && birth.cursor().count() == 4); + } + ThrowsInFill throwing; + try { throwing.run(bars, 2); } catch (const std::runtime_error&) {} + throwing.direct("after-throw"); + CHECK(throwing.get("after-throw").birth.cause() == OrderBirthCause::DirectCommand); + std::printf("order birth provenance: %d failure(s)\n", failed); + return failed ? 1 : 0; +} diff --git a/tests/test_pending_order_core.cpp b/tests/test_pending_order_core.cpp new file mode 100644 index 00000000..0824249f --- /dev/null +++ b/tests/test_pending_order_core.cpp @@ -0,0 +1,153 @@ +// Literal cross-component controls. No Pine compilation, reference tape, +// external broker, campaign, or generated expected values. +#include +#include +#include +#include +#include +#include + +using namespace pineforge; +namespace { +int checks = 0, failures = 0; +#define CHECK(x) do { ++checks; if (!(x)) { ++failures; std::fprintf(stderr,"FAIL %d %s\n",__LINE__,#x); } } while (0) +constexpr double missing = std::numeric_limits::quiet_NaN(); + +template +struct PrivateMember { friend typename Tag::Type access(Tag) { return Member; } }; +struct BindLayers { + using Type = void (BacktestEngine::*)(const std::string&, std::vector&); + friend Type access(BindLayers); +}; +template struct PrivateMember; + +class Book : public BacktestEngine { +public: + Book() { + initial_capital_ = 100000; + commission_value_ = 0; + margin_long_ = margin_short_ = 0; + pyramiding_ = 10; + default_qty_type_ = QtyType::FIXED; + default_qty_value_ = 4; + close_entries_rule_any_ = true; + current_bar_ = {100,100,100,100,1,0}; + } + void on_bar(const Bar&) override {} + void step() { + ++bar_index_; + current_bar_ = {100,100,100,100,1,int64_t(bar_index_)*60000}; + process_pending_orders(current_bar_); + } + void seed(double qty=2) { strategy_entry("E",true,missing,missing,qty); step(); } + void import_pending(const PendingOrder& order) { pending_orders_.push_back(order); } + void bind_layers() { std::vector retired; (this->*access(BindLayers{}))("E",retired); } + + void reverse() { strategy_entry("E",false); } + void close_one() { strategy_close("E","",1); } + void bracket(double percent) { strategy_exit("X","E",120,80,missing,missing,missing,percent); } + void deferred_bracket() { strategy_exit("X","E",missing,150,missing,missing,missing,50); } + const PendingOrder& order(const std::string& id) const { + for (const auto& o : pending_orders_) if (o.id==id) return o; + throw std::logic_error("missing literal pending order"); + } + double quantity() const { return position_qty_; } + PositionSide side() const { return position_side_; } + pf_pending_order_v1_t mirror(const std::string& id) const { + for (size_t i=0;i(this),int(i),&result,sizeof(result))==0); + return result; + } + throw std::logic_error("missing mirror owner"); + } +}; + +void deferred_close_can_join_later_layer_binding() { + Book b; b.seed(); + b.reverse(); b.close_one(); b.deferred_bracket(); + CHECK(b.side()==PositionSide::LONG && b.quantity()==2); + CHECK(std::isnan(b.order("__close__E").qty)); + CHECK(b.order("__close__E").qty_percent==50); + const auto& initial=b.order("__close__E").quantity_request; + CHECK(initial.intent().has_value()); + if (!initial.intent()) return; + CHECK(initial.intent()->kind()==QuantityIntent::Kind::Units && initial.intent()->units()==1); + CHECK(!initial.reservation().has_value()); + CHECK(!initial.is_partial(1e-9,1e-9) && !initial.requests_all()); + const auto before=b.mirror("__close__E"); + CHECK(before.requested_partial==0 && before.full_percent_exit_request==0); + CHECK(before.quantity_intent_kind==1 && before.quantity_intent_units==1); + CHECK(before.quantity_reservation_present==0); + // The normal pass may retire the deferred market close after binding it. + // Independently inspect that real binder with the exact pending values on + // a synthetic E4 book, before retirement hides its numeric receipt. + Book bound; bound.seed(4); + bound.import_pending(b.order("__close__E")); + bound.import_pending(b.order("X")); + bound.bind_layers(); + bool threw=false; + try { b.step(); } + catch (const std::exception& error) { + threw=true; std::fprintf(stderr,"layer-binding exception: %s\n",error.what()); + } + CHECK(!threw); + if (threw) return; + CHECK(b.side()==PositionSide::SHORT && b.quantity()==4); + const auto& close=bound.order("__close__E"); + CHECK(close.quantity_request.intent().has_value()); + if (!close.quantity_request.intent()) return; + CHECK(close.quantity_request.intent()->kind()==QuantityIntent::Kind::Units); + CHECK(close.quantity_request.intent()->units()==1); + CHECK(close.quantity_request.reservation().has_value()); + if (!close.quantity_request.reservation()) return; + CHECK(close.quantity_request.reservation()->units==2); + CHECK(close.quantity_request.reservation()->basis_units==4); + CHECK(close.qty==2 && bound.order("X").qty==2); + const auto after=bound.mirror("__close__E"); + CHECK(after.requested_partial==1 && after.full_percent_exit_request==0); + CHECK(after.quantity_intent_kind==1 && after.quantity_intent_units==1); + CHECK(after.quantity_reservation_present==1); + CHECK(after.quantity_reservation_units==2 && after.quantity_reservation_basis_units==4); +} + +void replacement_preserves_independent_quantity_and_birth() { + Book b; b.seed(); b.bracket(25); + const PendingOrder first=b.order("X"); + b.step(); b.bracket(50); + const auto& next=b.order("X"); + CHECK(next.incarnation!=first.incarnation); + CHECK(next.replaced_order_incarnation==first.incarnation); + CHECK(next.created_seq==first.created_seq); + CHECK(next.birth.timestamp()>first.birth.timestamp()); + CHECK(next.birth.cause()==OrderBirthCause::DirectCommand); + CHECK(next.quantity_request.intent()->kind()==QuantityIntent::Kind::Fraction); + CHECK(next.quantity_request.intent()->numerator()==50); + CHECK(next.quantity_request.reservation()->basis_units==2); + // Existing partial replacement retains the prior admitted amount, while + // the current caller's original fraction is recorded independently. + CHECK(next.quantity_request.reservation()->units==0.5 && next.qty==0.5); + CHECK(first.quantity_request.intent()->numerator()==25); + const auto mirrored=b.mirror("X"); + CHECK(mirrored.created_by_same_id_replacement==1); + CHECK(mirrored.replaced_exit_order_incarnation==first.incarnation); + CHECK(mirrored.replaced_order_incarnation==first.incarnation); + CHECK(mirrored.quantity_intent_numerator==50); + CHECK(mirrored.quantity_reservation_units==0.5); + CHECK(mirrored.birth_timestamp==next.birth.timestamp()); + CHECK(mirrored.created_during_coof_recalc==0); +} +} + +int main() { + const std::pair cases[] = { + {"deferred close binding",deferred_close_can_join_later_layer_binding}, + {"replacement quantity birth",replacement_preserves_independent_quantity_and_birth}, + }; + for (const auto& test : cases) { + try { test.second(); } + catch (const std::exception& error) { ++failures; std::fprintf(stderr,"case %s exception: %s\n",test.first,error.what()); } + } + std::printf("%d checks, %d failures\n",checks,failures); + return failures ? 1 : 0; +} diff --git a/tests/test_pending_placement_receipts.cpp b/tests/test_pending_placement_receipts.cpp new file mode 100644 index 00000000..7afdbd85 --- /dev/null +++ b/tests/test_pending_placement_receipts.cpp @@ -0,0 +1,172 @@ +// Literal command/identity tests. No Pine source, reference tape or grader. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace pineforge; +namespace { +constexpr double missing = std::numeric_limits::quiet_NaN(); +int passed = 0, failed = 0; +#define CHECK(value) do { if (value) ++passed; else { ++failed; \ + std::printf("FAIL %d: %s\n", __LINE__, #value); } } while (false) + +class Book final : public BacktestEngine { +public: + Book() { + initial_capital_ = 10000; + commission_value_ = 0; + margin_long_ = margin_short_ = 0; + pyramiding_ = 10; + current_bar_ = {100,100,100,100,1,0}; + } + void on_bar(const Bar&) override {} + void entry(const std::string& id, bool buy=true, double qty=1, double stop=missing) { + strategy_entry(id,buy,missing,stop,qty); + } + void raw(const std::string& id, bool buy=true, double qty=1, double stop=missing) { + strategy_order(id,buy,qty,missing,stop); + } + void exit(const std::string& id,const std::string& parent,double qty=missing) { + strategy_exit(id,parent,120,80,missing,missing,missing,100,"",qty); + } + void cancel(const std::string& id) { strategy_cancel(id); } + void cancel_all() { strategy_cancel_all(); } + void close(const std::string& id) { strategy_close(id); } + void pooc(bool value) { process_orders_on_close_=value; } + void capacity(int value) { pyramiding_=value; } + void advance() { + ++bar_index_; + current_bar_.timestamp = int64_t(bar_index_)*60000; + process_pending_orders(current_bar_); + } + void reset() { run(nullptr,0); } + double physical_qty() const { return position_qty_; } + PositionSide physical_side() const { return position_side_; } + int64_t cycle() const { return position_cycle_seq_; } + std::vector orders(const std::string& id) const { + std::vector result; + for(const auto& order:pending_orders_)if(order.id==id)result.push_back(order); + return result; + } + PendingOrder order(const std::string& id) const { + const auto found=orders(id); + if(found.size()!=1)throw std::runtime_error("expected one order "+id); + return found.front(); + } + pf_pending_order_v1_t mirror(const std::string& id) { + for(size_t i=0;i(this), + static_cast(i),&result,sizeof result)==0); + return result; + } + throw std::runtime_error("missing mirror "+id); + } +}; + +void entry_and_raw_predecessors() { + Book b; + b.entry("E");const auto first=b.order("E"); + CHECK(first.type==OrderType::MARKET&&first.replaced_order_incarnation==0); + b.entry("E",true,2,110);const auto stop=b.order("E"); + CHECK(stop.type==OrderType::ENTRY&&stop.replaced_order_incarnation==first.incarnation); + CHECK(stop.created_seq==first.created_seq&&stop.incarnation>first.incarnation); + b.raw("E",false,3,90);const auto raw=b.order("E"); + CHECK(raw.type==OrderType::RAW_ORDER&&raw.replaced_order_incarnation==stop.incarnation); + CHECK(raw.created_seq==first.created_seq&&raw.incarnation>stop.incarnation); + const auto mirrored=b.mirror("E"); + CHECK(mirrored.replaced_order_incarnation==stop.incarnation); + CHECK(mirrored.created_by_same_id_replacement==0); // legacy RAW projection stays false + b.raw("E",true,4);const auto raw_again=b.order("E"); + CHECK(raw_again.replaced_order_incarnation==raw.incarnation); + b.entry("E");const auto market=b.order("E"); + CHECK(market.replaced_order_incarnation==raw_again.incarnation); + CHECK(market.created_seq==first.created_seq); + CHECK(b.mirror("E").created_by_same_id_replacement==1); + b.cancel("E");b.entry("E");const auto fresh=b.order("E"); + CHECK(fresh.replaced_order_incarnation==0&&fresh.created_seq>market.created_seq); + CHECK(fresh.incarnation>market.incarnation); +} + +void named_cancel_is_not_replacement() { + Book b;b.entry("E",true,1,110);const auto original=b.order("E"); + b.exit("X","E");const auto child=b.order("X"); + b.cancel("E");b.entry("E",true,1,115);const auto recreated=b.order("E"); + CHECK(recreated.replaced_order_incarnation==0); + CHECK(recreated.recreated_after_named_cancelled_entry_incarnation==original.incarnation); + CHECK(recreated.named_cancel_surviving_exit_incarnation==child.incarnation); + b.exit("X","E");const auto child_replaced=b.order("X"); + CHECK(child_replaced.replaced_order_incarnation==child.incarnation); + CHECK(b.mirror("X").replaced_exit_order_incarnation==child.incarnation); + CHECK(child_replaced.created_seq==child.created_seq); + b.cancel_all();b.exit("X","E"); + CHECK(b.order("X").replaced_order_incarnation==0); +} + +void physical_and_projected_placement() { + Book b;b.entry("E",true,2);b.advance(); + CHECK(b.physical_qty()==2&&b.cycle()>0); + b.entry("ADD",true,1,110);const auto add=b.order("ADD"); + CHECK(add.created_position_side==PositionSide::LONG); + CHECK(add.created_position_cycle_seq==b.cycle()); + CHECK(b.mirror("ADD").created_while_in_position==0); // legacy label was EXIT-only + b.exit("X","E"); + CHECK(b.order("X").created_position_side==PositionSide::LONG); + CHECK(b.mirror("X").created_while_in_position==1); + b.cancel("X");b.cancel("ADD");b.pooc(true);b.close("E"); + CHECK(b.physical_qty()==2); // batched close claim has not physically executed + b.exit("AFTER_CLOSE","E",1); + CHECK(b.order("AFTER_CLOSE").created_position_side==PositionSide::FLAT); + CHECK(b.mirror("AFTER_CLOSE").created_while_in_position==0); + CHECK(b.physical_side()==PositionSide::LONG); // do not replace projected side with physical +} + +void exit_primary_and_extra_identity() { + Book b;b.entry("E",true,2);b.advance(); + b.entry("E",true,2,110);b.exit("X","E",1); + const auto first=b.order("X"); + b.exit("X","E",1);const auto legs=b.orders("X"); + CHECK(legs.size()==2); + if(legs.size()!=2)return; + CHECK(legs[0].replaced_order_incarnation==first.incarnation); + CHECK(legs[0].created_seq==first.created_seq); + CHECK(legs[1].replaced_order_incarnation==0); + CHECK(legs[1].incarnation!=legs[0].incarnation&&legs[1].created_seq!=legs[0].created_seq); + b.exit("X","E",1);const auto next=b.orders("X"); + CHECK(next.size()==2); + if(next.size()==2) { + CHECK(next[0].replaced_order_incarnation==legs[0].incarnation); + CHECK(next[1].replaced_order_incarnation==0); + } +} + +void copy_reset_and_rejected_replacement() { + Book b;b.raw("R",true,1,110);b.raw("R",true,2,115); + const auto order=b.order("R");Book copy=b; + CHECK(copy.order("R").replaced_order_incarnation==order.replaced_order_incarnation); + CHECK(copy.broker_state_hash()==b.broker_state_hash()); + copy.cancel("R");copy.raw("R",false,1,90); + CHECK(copy.order("R").replaced_order_incarnation==0); + CHECK(b.order("R").incarnation==order.incarnation); + copy.reset();Book fresh;fresh.raw("R");copy.raw("R"); + CHECK(copy.order("R").incarnation==fresh.order("R").incarnation); + CHECK(copy.order("R").replaced_order_incarnation==0); + Book rejected;rejected.entry("E");rejected.advance(); + rejected.raw("X",true,1,110);rejected.capacity(1); + rejected.entry("X",true,1,115); // same-side over-cap priced replacement: old erased, no new order + CHECK(rejected.orders("X").empty()); +} +} +int main() { + entry_and_raw_predecessors();named_cancel_is_not_replacement(); + physical_and_projected_placement();exit_primary_and_extra_identity(); + copy_reset_and_rejected_replacement(); + std::printf("%d passed, %d failed\n",passed,failed);return failed?1:0; +} diff --git a/tests/test_pending_quantity_intent.cpp b/tests/test_pending_quantity_intent.cpp new file mode 100644 index 00000000..eed96f66 --- /dev/null +++ b/tests/test_pending_quantity_intent.cpp @@ -0,0 +1,367 @@ +// Literal native request/reservation contracts. No external tapes, embedded +// platform expected trades, strategy compilation or campaign measurement. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace prior_mirror { +#include "fixtures/pending_quantity/c45_pending_order_mirror.hpp" +} + +using namespace pineforge; +static_assert(offsetof(pf_pending_order_v1_t, struct_version) == offsetof(prior_mirror::pf_pending_order_v1_t, struct_version), "legacy struct_version offset"); +static_assert(offsetof(pf_pending_order_v1_t, size) == offsetof(prior_mirror::pf_pending_order_v1_t, size), "legacy size offset"); +static_assert(offsetof(pf_pending_order_v1_t, id) == offsetof(prior_mirror::pf_pending_order_v1_t, id), "legacy id offset"); +static_assert(offsetof(pf_pending_order_v1_t, id_truncated) == offsetof(prior_mirror::pf_pending_order_v1_t, id_truncated), "legacy id_truncated offset"); +static_assert(offsetof(pf_pending_order_v1_t, id_hash64) == offsetof(prior_mirror::pf_pending_order_v1_t, id_hash64), "legacy id_hash64 offset"); +static_assert(offsetof(pf_pending_order_v1_t, from_entry) == offsetof(prior_mirror::pf_pending_order_v1_t, from_entry), "legacy from_entry offset"); +static_assert(offsetof(pf_pending_order_v1_t, from_entry_truncated) == offsetof(prior_mirror::pf_pending_order_v1_t, from_entry_truncated), "legacy from_entry_truncated offset"); +static_assert(offsetof(pf_pending_order_v1_t, from_entry_hash64) == offsetof(prior_mirror::pf_pending_order_v1_t, from_entry_hash64), "legacy from_entry_hash64 offset"); +static_assert(offsetof(pf_pending_order_v1_t, type) == offsetof(prior_mirror::pf_pending_order_v1_t, type), "legacy type offset"); +static_assert(offsetof(pf_pending_order_v1_t, is_long) == offsetof(prior_mirror::pf_pending_order_v1_t, is_long), "legacy is_long offset"); +static_assert(offsetof(pf_pending_order_v1_t, limit_price) == offsetof(prior_mirror::pf_pending_order_v1_t, limit_price), "legacy limit_price offset"); +static_assert(offsetof(pf_pending_order_v1_t, stop_price) == offsetof(prior_mirror::pf_pending_order_v1_t, stop_price), "legacy stop_price offset"); +static_assert(offsetof(pf_pending_order_v1_t, trail_points) == offsetof(prior_mirror::pf_pending_order_v1_t, trail_points), "legacy trail_points offset"); +static_assert(offsetof(pf_pending_order_v1_t, trail_price) == offsetof(prior_mirror::pf_pending_order_v1_t, trail_price), "legacy trail_price offset"); +static_assert(offsetof(pf_pending_order_v1_t, trail_offset) == offsetof(prior_mirror::pf_pending_order_v1_t, trail_offset), "legacy trail_offset offset"); +static_assert(offsetof(pf_pending_order_v1_t, profit_ticks) == offsetof(prior_mirror::pf_pending_order_v1_t, profit_ticks), "legacy profit_ticks offset"); +static_assert(offsetof(pf_pending_order_v1_t, loss_ticks) == offsetof(prior_mirror::pf_pending_order_v1_t, loss_ticks), "legacy loss_ticks offset"); +static_assert(offsetof(pf_pending_order_v1_t, qty) == offsetof(prior_mirror::pf_pending_order_v1_t, qty), "legacy qty offset"); +static_assert(offsetof(pf_pending_order_v1_t, qty_type) == offsetof(prior_mirror::pf_pending_order_v1_t, qty_type), "legacy qty_type offset"); +static_assert(offsetof(pf_pending_order_v1_t, qty_percent) == offsetof(prior_mirror::pf_pending_order_v1_t, qty_percent), "legacy qty_percent offset"); +static_assert(offsetof(pf_pending_order_v1_t, oca_name) == offsetof(prior_mirror::pf_pending_order_v1_t, oca_name), "legacy oca_name offset"); +static_assert(offsetof(pf_pending_order_v1_t, oca_name_truncated) == offsetof(prior_mirror::pf_pending_order_v1_t, oca_name_truncated), "legacy oca_name_truncated offset"); +static_assert(offsetof(pf_pending_order_v1_t, oca_name_hash64) == offsetof(prior_mirror::pf_pending_order_v1_t, oca_name_hash64), "legacy oca_name_hash64 offset"); +static_assert(offsetof(pf_pending_order_v1_t, oca_type) == offsetof(prior_mirror::pf_pending_order_v1_t, oca_type), "legacy oca_type offset"); +static_assert(offsetof(pf_pending_order_v1_t, created_bar) == offsetof(prior_mirror::pf_pending_order_v1_t, created_bar), "legacy created_bar offset"); +static_assert(offsetof(pf_pending_order_v1_t, created_seq) == offsetof(prior_mirror::pf_pending_order_v1_t, created_seq), "legacy created_seq offset"); +static_assert(offsetof(pf_pending_order_v1_t, incarnation) == offsetof(prior_mirror::pf_pending_order_v1_t, incarnation), "legacy incarnation offset"); +static_assert(offsetof(pf_pending_order_v1_t, created_by_same_id_replacement) == offsetof(prior_mirror::pf_pending_order_v1_t, created_by_same_id_replacement), "legacy created_by_same_id_replacement offset"); +static_assert(offsetof(pf_pending_order_v1_t, replaced_default_market_incarnation) == offsetof(prior_mirror::pf_pending_order_v1_t, replaced_default_market_incarnation), "legacy replaced_default_market_incarnation offset"); +static_assert(offsetof(pf_pending_order_v1_t, declined_by_replaced_short_market) == offsetof(prior_mirror::pf_pending_order_v1_t, declined_by_replaced_short_market), "legacy declined_by_replaced_short_market offset"); +static_assert(offsetof(pf_pending_order_v1_t, replaced_exit_order_incarnation) == offsetof(prior_mirror::pf_pending_order_v1_t, replaced_exit_order_incarnation), "legacy replaced_exit_order_incarnation offset"); +static_assert(offsetof(pf_pending_order_v1_t, recreated_after_named_cancelled_entry_incarnation) == offsetof(prior_mirror::pf_pending_order_v1_t, recreated_after_named_cancelled_entry_incarnation), "legacy recreated_after_named_cancelled_entry_incarnation offset"); +static_assert(offsetof(pf_pending_order_v1_t, named_cancel_surviving_exit_incarnation) == offsetof(prior_mirror::pf_pending_order_v1_t, named_cancel_surviving_exit_incarnation), "legacy named_cancel_surviving_exit_incarnation offset"); +static_assert(offsetof(pf_pending_order_v1_t, stop_limit_activated) == offsetof(prior_mirror::pf_pending_order_v1_t, stop_limit_activated), "legacy stop_limit_activated offset"); +static_assert(offsetof(pf_pending_order_v1_t, coof_suppress_stop_on_entry_bar) == offsetof(prior_mirror::pf_pending_order_v1_t, coof_suppress_stop_on_entry_bar), "legacy coof_suppress_stop_on_entry_bar offset"); +static_assert(offsetof(pf_pending_order_v1_t, coof_suppress_limit_on_entry_bar) == offsetof(prior_mirror::pf_pending_order_v1_t, coof_suppress_limit_on_entry_bar), "legacy coof_suppress_limit_on_entry_bar offset"); +static_assert(offsetof(pf_pending_order_v1_t, created_during_coof_recalc) == offsetof(prior_mirror::pf_pending_order_v1_t, created_during_coof_recalc), "legacy created_during_coof_recalc offset"); +static_assert(offsetof(pf_pending_order_v1_t, coof_born_at_close_recalc) == offsetof(prior_mirror::pf_pending_order_v1_t, coof_born_at_close_recalc), "legacy coof_born_at_close_recalc offset"); +static_assert(offsetof(pf_pending_order_v1_t, coof_born_mid_bar) == offsetof(prior_mirror::pf_pending_order_v1_t, coof_born_mid_bar), "legacy coof_born_mid_bar offset"); +static_assert(offsetof(pf_pending_order_v1_t, coof_cascade_seg_i) == offsetof(prior_mirror::pf_pending_order_v1_t, coof_cascade_seg_i), "legacy coof_cascade_seg_i offset"); +static_assert(offsetof(pf_pending_order_v1_t, coof_cascade_inflight_fires) == offsetof(prior_mirror::pf_pending_order_v1_t, coof_cascade_inflight_fires), "legacy coof_cascade_inflight_fires offset"); +static_assert(offsetof(pf_pending_order_v1_t, created_position_side) == offsetof(prior_mirror::pf_pending_order_v1_t, created_position_side), "legacy created_position_side offset"); +static_assert(offsetof(pf_pending_order_v1_t, created_position_cycle_seq) == offsetof(prior_mirror::pf_pending_order_v1_t, created_position_cycle_seq), "legacy created_position_cycle_seq offset"); +static_assert(offsetof(pf_pending_order_v1_t, created_after_position_close_in_bar) == offsetof(prior_mirror::pf_pending_order_v1_t, created_after_position_close_in_bar), "legacy created_after_position_close_in_bar offset"); +static_assert(offsetof(pf_pending_order_v1_t, over_pyramiding_cap_at_placement) == offsetof(prior_mirror::pf_pending_order_v1_t, over_pyramiding_cap_at_placement), "legacy over_pyramiding_cap_at_placement offset"); +static_assert(offsetof(pf_pending_order_v1_t, same_id_stop_deferred_close_all_bar) == offsetof(prior_mirror::pf_pending_order_v1_t, same_id_stop_deferred_close_all_bar), "legacy same_id_stop_deferred_close_all_bar offset"); +static_assert(offsetof(pf_pending_order_v1_t, same_id_stop_deferred_close_all_incarnation) == offsetof(prior_mirror::pf_pending_order_v1_t, same_id_stop_deferred_close_all_incarnation), "legacy same_id_stop_deferred_close_all_incarnation offset"); +static_assert(offsetof(pf_pending_order_v1_t, reverses_same_bar_market_from_flat) == offsetof(prior_mirror::pf_pending_order_v1_t, reverses_same_bar_market_from_flat), "legacy reverses_same_bar_market_from_flat offset"); +static_assert(offsetof(pf_pending_order_v1_t, paired_flat_market_candidate) == offsetof(prior_mirror::pf_pending_order_v1_t, paired_flat_market_candidate), "legacy paired_flat_market_candidate offset"); +static_assert(offsetof(pf_pending_order_v1_t, paired_flat_market_own_qty) == offsetof(prior_mirror::pf_pending_order_v1_t, paired_flat_market_own_qty), "legacy paired_flat_market_own_qty offset"); +static_assert(offsetof(pf_pending_order_v1_t, paired_flat_market_signal_close) == offsetof(prior_mirror::pf_pending_order_v1_t, paired_flat_market_signal_close), "legacy paired_flat_market_signal_close offset"); +static_assert(offsetof(pf_pending_order_v1_t, paired_flat_market_signal_equity) == offsetof(prior_mirror::pf_pending_order_v1_t, paired_flat_market_signal_equity), "legacy paired_flat_market_signal_equity offset"); +static_assert(offsetof(pf_pending_order_v1_t, paired_flat_market_signal_margin_pct) == offsetof(prior_mirror::pf_pending_order_v1_t, paired_flat_market_signal_margin_pct), "legacy paired_flat_market_signal_margin_pct offset"); +static_assert(offsetof(pf_pending_order_v1_t, paired_flat_market_signal_pointvalue) == offsetof(prior_mirror::pf_pending_order_v1_t, paired_flat_market_signal_pointvalue), "legacy paired_flat_market_signal_pointvalue offset"); +static_assert(offsetof(pf_pending_order_v1_t, paired_flat_market_signal_fx) == offsetof(prior_mirror::pf_pending_order_v1_t, paired_flat_market_signal_fx), "legacy paired_flat_market_signal_fx offset"); +static_assert(offsetof(pf_pending_order_v1_t, paired_flat_market_peer_seq) == offsetof(prior_mirror::pf_pending_order_v1_t, paired_flat_market_peer_seq), "legacy paired_flat_market_peer_seq offset"); +static_assert(offsetof(pf_pending_order_v1_t, paired_flat_market_transaction_qty) == offsetof(prior_mirror::pf_pending_order_v1_t, paired_flat_market_transaction_qty), "legacy paired_flat_market_transaction_qty offset"); +static_assert(offsetof(pf_pending_order_v1_t, default_flat_market_gross_candidate) == offsetof(prior_mirror::pf_pending_order_v1_t, default_flat_market_gross_candidate), "legacy default_flat_market_gross_candidate offset"); +static_assert(offsetof(pf_pending_order_v1_t, tv_carry_qty) == offsetof(prior_mirror::pf_pending_order_v1_t, tv_carry_qty), "legacy tv_carry_qty offset"); +static_assert(offsetof(pf_pending_order_v1_t, frozen_default_qty) == offsetof(prior_mirror::pf_pending_order_v1_t, frozen_default_qty), "legacy frozen_default_qty offset"); +static_assert(offsetof(pf_pending_order_v1_t, default_stop_placement_qty) == offsetof(prior_mirror::pf_pending_order_v1_t, default_stop_placement_qty), "legacy default_stop_placement_qty offset"); +static_assert(offsetof(pf_pending_order_v1_t, default_stop_placement_equity) == offsetof(prior_mirror::pf_pending_order_v1_t, default_stop_placement_equity), "legacy default_stop_placement_equity offset"); +static_assert(offsetof(pf_pending_order_v1_t, default_stop_placement_signal_close) == offsetof(prior_mirror::pf_pending_order_v1_t, default_stop_placement_signal_close), "legacy default_stop_placement_signal_close offset"); +static_assert(offsetof(pf_pending_order_v1_t, default_stop_sizing_price) == offsetof(prior_mirror::pf_pending_order_v1_t, default_stop_sizing_price), "legacy default_stop_sizing_price offset"); +static_assert(offsetof(pf_pending_order_v1_t, sizing_equity) == offsetof(prior_mirror::pf_pending_order_v1_t, sizing_equity), "legacy sizing_equity offset"); +static_assert(offsetof(pf_pending_order_v1_t, sizing_price) == offsetof(prior_mirror::pf_pending_order_v1_t, sizing_price), "legacy sizing_price offset"); +static_assert(offsetof(pf_pending_order_v1_t, sizing_fx) == offsetof(prior_mirror::pf_pending_order_v1_t, sizing_fx), "legacy sizing_fx offset"); +static_assert(offsetof(pf_pending_order_v1_t, sizing_mark) == offsetof(prior_mirror::pf_pending_order_v1_t, sizing_mark), "legacy sizing_mark offset"); +static_assert(offsetof(pf_pending_order_v1_t, opening_affordability_exemption_candidate) == offsetof(prior_mirror::pf_pending_order_v1_t, opening_affordability_exemption_candidate), "legacy opening_affordability_exemption_candidate offset"); +static_assert(offsetof(pf_pending_order_v1_t, explicit_flat_admission_candidate) == offsetof(prior_mirror::pf_pending_order_v1_t, explicit_flat_admission_candidate), "legacy explicit_flat_admission_candidate offset"); +static_assert(offsetof(pf_pending_order_v1_t, explicit_placement_equity) == offsetof(prior_mirror::pf_pending_order_v1_t, explicit_placement_equity), "legacy explicit_placement_equity offset"); +static_assert(offsetof(pf_pending_order_v1_t, explicit_slipped_signal_close) == offsetof(prior_mirror::pf_pending_order_v1_t, explicit_slipped_signal_close), "legacy explicit_slipped_signal_close offset"); +static_assert(offsetof(pf_pending_order_v1_t, affordability_placement_equity) == offsetof(prior_mirror::pf_pending_order_v1_t, affordability_placement_equity), "legacy affordability_placement_equity offset"); +static_assert(offsetof(pf_pending_order_v1_t, affordability_signal_price) == offsetof(prior_mirror::pf_pending_order_v1_t, affordability_signal_price), "legacy affordability_signal_price offset"); +static_assert(offsetof(pf_pending_order_v1_t, affordability_held_qty) == offsetof(prior_mirror::pf_pending_order_v1_t, affordability_held_qty), "legacy affordability_held_qty offset"); +static_assert(offsetof(pf_pending_order_v1_t, affordability_close_only) == offsetof(prior_mirror::pf_pending_order_v1_t, affordability_close_only), "legacy affordability_close_only offset"); +static_assert(offsetof(pf_pending_order_v1_t, rounded_signal_cost_close_only) == offsetof(prior_mirror::pf_pending_order_v1_t, rounded_signal_cost_close_only), "legacy rounded_signal_cost_close_only offset"); +static_assert(offsetof(pf_pending_order_v1_t, signal_close_mc_bar) == offsetof(prior_mirror::pf_pending_order_v1_t, signal_close_mc_bar), "legacy signal_close_mc_bar offset"); +static_assert(offsetof(pf_pending_order_v1_t, signal_close_mc_entry_incarnation) == offsetof(prior_mirror::pf_pending_order_v1_t, signal_close_mc_entry_incarnation), "legacy signal_close_mc_entry_incarnation offset"); +static_assert(offsetof(pf_pending_order_v1_t, signal_close_mc_fill_seq) == offsetof(prior_mirror::pf_pending_order_v1_t, signal_close_mc_fill_seq), "legacy signal_close_mc_fill_seq offset"); +static_assert(offsetof(pf_pending_order_v1_t, signal_close_mc_remaining_qty) == offsetof(prior_mirror::pf_pending_order_v1_t, signal_close_mc_remaining_qty), "legacy signal_close_mc_remaining_qty offset"); +static_assert(offsetof(pf_pending_order_v1_t, comment) == offsetof(prior_mirror::pf_pending_order_v1_t, comment), "legacy comment offset"); +static_assert(offsetof(pf_pending_order_v1_t, comment_truncated) == offsetof(prior_mirror::pf_pending_order_v1_t, comment_truncated), "legacy comment_truncated offset"); +static_assert(offsetof(pf_pending_order_v1_t, comment_hash64) == offsetof(prior_mirror::pf_pending_order_v1_t, comment_hash64), "legacy comment_hash64 offset"); +static_assert(offsetof(pf_pending_order_v1_t, requested_partial) == offsetof(prior_mirror::pf_pending_order_v1_t, requested_partial), "legacy requested_partial offset"); +static_assert(offsetof(pf_pending_order_v1_t, full_percent_exit_request) == offsetof(prior_mirror::pf_pending_order_v1_t, full_percent_exit_request), "legacy full_percent_exit_request offset"); +static_assert(offsetof(pf_pending_order_v1_t, pooc_global_full_exit_dynamic_qty) == offsetof(prior_mirror::pf_pending_order_v1_t, pooc_global_full_exit_dynamic_qty), "legacy pooc_global_full_exit_dynamic_qty offset"); +static_assert(offsetof(pf_pending_order_v1_t, pooc_global_full_exit_tracks_bound_adds) == offsetof(prior_mirror::pf_pending_order_v1_t, pooc_global_full_exit_tracks_bound_adds), "legacy pooc_global_full_exit_tracks_bound_adds offset"); +static_assert(offsetof(pf_pending_order_v1_t, pooc_global_full_exit_bound_add) == offsetof(prior_mirror::pf_pending_order_v1_t, pooc_global_full_exit_bound_add), "legacy pooc_global_full_exit_bound_add offset"); +static_assert(offsetof(pf_pending_order_v1_t, created_while_in_position) == offsetof(prior_mirror::pf_pending_order_v1_t, created_while_in_position), "legacy created_while_in_position offset"); +static_assert(offsetof(pf_pending_order_v1_t, sbmt_member) == offsetof(prior_mirror::pf_pending_order_v1_t, sbmt_member), "legacy sbmt_member offset"); +static_assert(offsetof(pf_pending_order_v1_t, sbmt_own_qty) == offsetof(prior_mirror::pf_pending_order_v1_t, sbmt_own_qty), "legacy sbmt_own_qty offset"); +static_assert(offsetof(pf_pending_order_v1_t, sbmt_tx_qty) == offsetof(prior_mirror::pf_pending_order_v1_t, sbmt_tx_qty), "legacy sbmt_tx_qty offset"); +static_assert(offsetof(pf_pending_order_v1_t, sbmt_kept_over_cap) == offsetof(prior_mirror::pf_pending_order_v1_t, sbmt_kept_over_cap), "legacy sbmt_kept_over_cap offset"); +static_assert(offsetof(pf_pending_order_v1_t, sbmt_close_qty) == offsetof(prior_mirror::pf_pending_order_v1_t, sbmt_close_qty), "legacy sbmt_close_qty offset"); +static_assert(offsetof(pf_pending_order_v1_t, sbmt_close_buy) == offsetof(prior_mirror::pf_pending_order_v1_t, sbmt_close_buy), "legacy sbmt_close_buy offset"); +static_assert(offsetof(pf_pending_order_v1_t, suppress_as_declined_reversal_close) == offsetof(prior_mirror::pf_pending_order_v1_t, suppress_as_declined_reversal_close), "legacy suppress_as_declined_reversal_close offset"); +static_assert(offsetof(pf_pending_order_v1_t, dormant_bracket) == offsetof(prior_mirror::pf_pending_order_v1_t, dormant_bracket), "legacy dormant_bracket offset"); +static_assert(offsetof(pf_pending_order_v1_t, dormant_reissue_pending) == offsetof(prior_mirror::pf_pending_order_v1_t, dormant_reissue_pending), "legacy dormant_reissue_pending offset"); +static_assert(offsetof(pf_pending_order_v1_t, dormant_original_stop_price) == offsetof(prior_mirror::pf_pending_order_v1_t, dormant_original_stop_price), "legacy dormant_original_stop_price offset"); +static_assert(offsetof(pf_pending_order_v1_t, dormant_hold_bar) == offsetof(prior_mirror::pf_pending_order_v1_t, dormant_hold_bar), "legacy dormant_hold_bar offset"); +static_assert(offsetof(pf_pending_order_v1_t, dormant_reversal_kill_bar) == offsetof(prior_mirror::pf_pending_order_v1_t, dormant_reversal_kill_bar), "legacy dormant_reversal_kill_bar offset"); +static_assert(offsetof(pf_pending_order_v1_t, dormant_trail_best) == offsetof(prior_mirror::pf_pending_order_v1_t, dormant_trail_best), "legacy dormant_trail_best offset"); +static_assert(offsetof(pf_pending_order_v1_t, dormant_trail_best_start) == offsetof(prior_mirror::pf_pending_order_v1_t, dormant_trail_best_start), "legacy dormant_trail_best_start offset"); +static_assert(offsetof(pf_pending_order_v1_t, dormant_trail_leg_dead) == offsetof(prior_mirror::pf_pending_order_v1_t, dormant_trail_leg_dead), "legacy dormant_trail_leg_dead offset"); +static_assert(offsetof(pf_pending_order_v1_t, suppressed_close_consumed_ledger_qty) == offsetof(prior_mirror::pf_pending_order_v1_t, suppressed_close_consumed_ledger_qty), "legacy suppressed_close_consumed_ledger_qty offset"); +static_assert(offsetof(pf_pending_order_v1_t, suppressed_close_retired_ledger_qty) == offsetof(prior_mirror::pf_pending_order_v1_t, suppressed_close_retired_ledger_qty), "legacy suppressed_close_retired_ledger_qty offset"); +static_assert(offsetof(pf_pending_order_v1_t, short_seed_collision_role) == offsetof(prior_mirror::pf_pending_order_v1_t, short_seed_collision_role), "legacy short_seed_collision_role offset"); +namespace pineforge { +void fill_pending_order_mirror(const PendingOrder&, pf_pending_order_v1_t*); +} +namespace { +int checks = 0, failures = 0; +#define CHECK(x) do { ++checks; if (!(x)) { ++failures; std::printf("FAIL %d %s\n", __LINE__, #x); } } while (0) +constexpr double nan = std::numeric_limits::quiet_NaN(); +bool partial(const PendingOrder& o) { return o.quantity_request.is_partial(1e-9, 1e-9); } + +void quantity_values_have_distinct_meaning() { + QuantityRequest request; + CHECK(!request.intent() && !request.reservation()); + bool refused = false; + try { request.reserve(1,4); } + catch (const std::logic_error&) { refused = true; } + CHECK(refused && !request.reservation()); + for (double denominator : {0.0, -1.0, std::numeric_limits::infinity()}) { + bool invalid = false; + try { (void)QuantityIntent::fraction(1,denominator); } + catch (const std::invalid_argument&) { invalid = true; } + CHECK(invalid); + } + request.request(QuantityIntent::fraction(1,4)); + CHECK(request.is_partial(0,0)); + request.reserve(4,4); + CHECK(!request.is_partial(0,0)); + CHECK(request.intent()->numerator() == 1 && request.intent()->denominator() == 4); + request.request(QuantityIntent::units(3)); + CHECK(!request.reservation() && !request.requests_all()); + CHECK(request.intent()->units() == 3); + request.reserve(2,4); + CHECK(request.is_partial(0,0) && request.intent()->units() == 3); + request.request(QuantityIntent::all()); + CHECK(request.requests_all() && !request.reservation()); +} + +class Book : public BacktestEngine { +public: + Book() { + initial_capital_ = 100000; + commission_value_ = 0; + margin_long_ = margin_short_ = 0; + pyramiding_ = 10; + current_bar_ = {100,100,100,100,1,0}; + } + void on_bar(const Bar&) override {} + void entry(double qty) { strategy_entry("E", true, nan, nan, qty); } + void step(double price = 100) { + ++bar_index_; + current_bar_ = {price,price,price,price,1,int64_t(bar_index_) * 60000}; + process_pending_orders(current_bar_); + } + void seed(double qty) { entry(qty); step(); } + void exit(const char* id, double percent = 100, double units = nan, + const char* group = "") { + strategy_exit(id,"E",120,90,nan,nan,nan,percent,"",units,group); + } + void cancel(const char* id) { strategy_cancel(id); } + void reduce_group(double qty) { strategy_order("reduce",true,qty,nan,nan,"Q",2); } + void lot_step(double value) { qty_step_ = value; } + void reset() { run(nullptr,0); } + double position() const { return position_qty_; } + const std::vector& orders() const { return pending_orders_; } + PendingOrder& order(const std::string& id) { + for (auto& o : pending_orders_) if (o.id == id) return o; + throw std::logic_error("missing literal order"); + } +}; + +void requested_amount_is_separate_from_reserved_amount() { + Book units; units.seed(4); units.exit("X",100,1); + const auto& u = units.order("X"); + CHECK(u.qty == 1 && partial(u)); + CHECK(u.quantity_request.intent()->kind() == QuantityIntent::Kind::Units); + CHECK(u.quantity_request.intent()->units() == 1); + CHECK(u.quantity_request.reservation()->units == 1); + CHECK(u.quantity_request.reservation()->basis_units == 4); + Book fraction; fraction.seed(4); fraction.exit("X",25); + const auto& f = fraction.order("X"); + CHECK(f.qty == 1 && partial(f)); + CHECK(f.quantity_request.intent()->kind() == QuantityIntent::Kind::Fraction); + CHECK(f.quantity_request.intent()->numerator() == 25); + CHECK(f.quantity_request.intent()->denominator() == 100); + Book clipped; clipped.seed(4); clipped.exit("first",25); clipped.exit("all"); + const auto& a = clipped.order("all"); + CHECK(a.quantity_request.requests_all()); + CHECK(a.qty == 3 && partial(a)); + CHECK(a.quantity_request.reservation()->basis_units == 4); +} + +void minimum_slot_does_not_rewrite_fraction_intent() { + Book b; b.lot_step(1); b.seed(1); b.exit("half",50); + const auto& o = b.order("half"); + CHECK(o.qty == 1 && !partial(o)); + CHECK(!o.quantity_request.requests_all()); + CHECK(o.quantity_request.intent()->numerator() == 50); + CHECK(o.quantity_request.reservation()->units == 1); + CHECK(o.quantity_request.reservation()->basis_units == 1); + b.exit("blocked",50); + CHECK(b.orders().size() == 1); +} + +void normalized_full_amount_does_not_invent_all_intent() { + Book fraction; fraction.seed(4); fraction.exit("X",150); + const auto& f = fraction.order("X"); + CHECK(f.qty == 4 && f.qty_percent == 100 && !partial(f)); + CHECK(!f.quantity_request.requests_all()); + CHECK(f.quantity_request.intent()->numerator() == 150); + Book units; units.seed(4); units.exit("X",50,4); + const auto& u = units.order("X"); + CHECK(u.qty == 4 && !partial(u) && !u.quantity_request.requests_all()); + CHECK(u.quantity_request.intent()->kind() == QuantityIntent::Kind::Units); + CHECK(u.quantity_request.intent()->units() == 4); +} + +void deferred_reservation_binds_and_keeps_original_all() { + Book b; b.entry(4); b.exit("quarter",25); b.exit("rest"); + CHECK(std::isnan(b.order("quarter").qty)); + CHECK(!b.order("quarter").quantity_request.reservation()); + CHECK(partial(b.order("quarter"))); + CHECK(b.order("rest").quantity_request.requests_all()); + CHECK(!partial(b.order("rest"))); + b.step(); + CHECK(b.position() == 4); + CHECK(b.order("quarter").qty == 1); + CHECK(b.order("rest").qty == 3); + CHECK(partial(b.order("quarter")) && partial(b.order("rest"))); + CHECK(b.order("rest").quantity_request.requests_all()); + CHECK(b.order("rest").quantity_request.reservation()->basis_units == 4); +} + +void executable_reduction_does_not_change_reservation_history() { + Book b; b.seed(4); b.exit("all",100,nan,"Q"); + CHECK(!partial(b.order("all"))); + b.reduce_group(1); b.step(); + const auto& o = b.order("all"); + CHECK(o.qty < 4); // Actual OCA reduction, independent of source intent. + CHECK(o.quantity_request.requests_all() && !partial(o)); + CHECK(o.quantity_request.reservation()->units == 4); + CHECK(o.quantity_request.reservation()->basis_units == 4); +} + +void replacement_copy_cancel_and_reset() { + Book b; b.seed(4); b.exit("X",25); + const auto old = b.order("X").incarnation; + Book copy = b; + CHECK(copy.broker_state_hash() == b.broker_state_hash()); + b.exit("X"); + CHECK(b.order("X").incarnation != old); + CHECK(b.order("X").quantity_request.requests_all()); + CHECK(b.order("X").qty == 4); + CHECK(copy.order("X").qty == 1 && partial(copy.order("X"))); + copy.cancel("X"); CHECK(copy.orders().empty()); + CHECK(b.orders().size() == 1); + b.reset(); CHECK(b.orders().empty()); +} + +void partial_fill_preserves_existing_reissue_policy() { + Book b; b.seed(4); b.exit("X",25); b.step(90); + CHECK(b.position() == 3 && b.orders().empty()); + b.exit("X",25); + CHECK(b.orders().empty()); + b.exit("X"); + CHECK(b.orders().size() == 1); + CHECK(b.order("X").qty == 3 && b.order("X").quantity_request.requests_all()); +} + +void per_binding_leg_preserves_request() { + Book b; b.seed(1); b.exit("X",100,1); b.entry(2); b.exit("X",100,1); + int legs = 0; + for (const auto& o : b.orders()) if (o.type == OrderType::EXIT && o.id == "X") { + ++legs; + CHECK(o.quantity_request.intent()->kind() == QuantityIntent::Kind::Units); + CHECK(o.quantity_request.intent()->units() == 1); + CHECK(o.quantity_request.reservation()->units == o.qty); + CHECK(o.quantity_request.reservation()->basis_units == 1); + } + CHECK(legs == 2); +} + +void equal_legacy_flags_do_not_hide_distinct_intents_from_hash() { + Book a; a.seed(4); a.exit("X",25); + Book b = a; + b.order("X").quantity_request.request(QuantityIntent::units(1)); + b.order("X").quantity_request.reserve(1,4); + CHECK(partial(a.order("X")) && partial(b.order("X"))); + CHECK(a.broker_state_hash() != b.broker_state_hash()); + Book c = a; c.order("X").quantity_request.reserve(1,5); + CHECK(a.broker_state_hash() != c.broker_state_hash()); + Book d = a; d.order("X").quantity_request.request(QuantityIntent::fraction(25,100)); + CHECK(a.broker_state_hash() != d.broker_state_hash()); +} + +void legacy_mirror_prefix_and_new_facts() { + static_assert(offsetof(pf_pending_order_v1_t, quantity_intent_kind) + >= sizeof(prior_mirror::pf_pending_order_v1_t), "new fields append after old prefix"); + Book b; b.lot_step(1); b.seed(1); b.exit("half",50); + pf_pending_order_v1_t out{}; + CHECK(strategy_pending_order_get(&b,0,&out,sizeof(out)) == 0); + CHECK(out.requested_partial == 0 && out.full_percent_exit_request == 0); + CHECK(out.quantity_intent_kind == 2); + CHECK(out.quantity_intent_numerator == 50 && out.quantity_intent_denominator == 100); + CHECK(out.quantity_reservation_present == 1); + CHECK(out.quantity_reservation_units == 1 && out.quantity_reservation_basis_units == 1); + std::vector prefix(sizeof(out),0xA5); + CHECK(strategy_pending_order_get(&b,0,prefix.data(),sizeof(prior_mirror::pf_pending_order_v1_t)) == 0); + CHECK(std::memcmp(prefix.data(),&out,sizeof(prior_mirror::pf_pending_order_v1_t)) == 0); + for (size_t i=sizeof(prior_mirror::pf_pending_order_v1_t); i cases[] = { + {"quantity values", quantity_values_have_distinct_meaning}, + {"original versus reserved", requested_amount_is_separate_from_reserved_amount}, + {"minimum slot", minimum_slot_does_not_rewrite_fraction_intent}, + {"normalized full amount", normalized_full_amount_does_not_invent_all_intent}, + {"deferred binding", deferred_reservation_binds_and_keeps_original_all}, + {"OCA reduction", executable_reduction_does_not_change_reservation_history}, + {"replacement/copy/reset", replacement_copy_cancel_and_reset}, + {"partial fill/reissue", partial_fill_preserves_existing_reissue_policy}, + {"extra bindings", per_binding_leg_preserves_request}, + {"hash identity", equal_legacy_flags_do_not_hide_distinct_intents_from_hash}, + {"mirror prefix", legacy_mirror_prefix_and_new_facts}, + }; + for (const auto& test : cases) { + std::fprintf(stderr, "case: %s\n", test.first); + try { test.second(); } + catch (const std::exception& error) { + ++failures; + std::fprintf(stderr, "FAIL %s: %s\n", test.first, error.what()); + } + } + std::printf("%d checks, %d failures\n",checks,failures); + return failures ? 1 : 0; +} diff --git a/tests/test_pooc_global_full_exit.cpp b/tests/test_pooc_global_full_exit.cpp index 91542ed1..01f24f69 100644 --- a/tests/test_pooc_global_full_exit.cpp +++ b/tests/test_pooc_global_full_exit.cpp @@ -243,7 +243,7 @@ class ReservationProbe final : public BacktestEngine { // reservation decision runs. for (auto& order : pending_orders_) { if (order.id == "COOF_ADD" + suffix) { - order.created_during_coof_recalc = true; + order.birth = OrderBirth::fill_evaluation(0, 0, BirthCursor::point(BirthCursorDomain::HistoricalPath, 0, 4), 100.0, 1, 1, 1); } } break; diff --git a/tests/test_prearmed_exit_path_cursor.cpp b/tests/test_prearmed_exit_path_cursor.cpp index 977c7dc0..32fc3d98 100644 --- a/tests/test_prearmed_exit_path_cursor.cpp +++ b/tests/test_prearmed_exit_path_cursor.cpp @@ -224,7 +224,7 @@ class FreshParentProbe final : public BacktestEngine { } pending_book_size_on_reissue = pending_orders_.size(); fresh_parent_shape_seen = parent != nullptr && child != nullptr - && !parent->created_by_same_id_replacement + && (parent->replaced_order_incarnation == 0) && child->created_seq < parent->created_seq && child->created_bar == parent->created_bar; parent_cancel_provenance_seen = parent != nullptr @@ -247,13 +247,13 @@ class FreshParentProbe final : public BacktestEngine { < std::numeric_limits::max() && child->incarnation == parent->incarnation + 1; child_reissue_provenance_seen = child != nullptr - && child->created_by_same_id_replacement; + && (child->replaced_order_incarnation != 0); child_replacement_token_exact = child != nullptr && parent != nullptr && surviving_child_incarnation_at_cancel != 0 && parent->named_cancel_surviving_exit_incarnation == surviving_child_incarnation_at_cancel - && child->replaced_exit_order_incarnation + && child->replaced_order_incarnation == surviving_child_incarnation_at_cancel; } else if (bar_index_ == 2) { position_seen_on_trigger_bar = signed_position_size(); @@ -401,8 +401,7 @@ static bool retained_child_predicate_accepts(SortMutation mutation) { child.type = OrderType::EXIT; child.created_seq = 1; child.incarnation = 12; - child.created_by_same_id_replacement = true; - child.replaced_exit_order_incarnation = 10; + child.replaced_order_incarnation = 10; child.created_bar = 1; child.created_position_side = PositionSide::FLAT; child.qty = kNaN; @@ -459,7 +458,7 @@ static bool retained_child_predicate_accepts(SortMutation mutation) { context.stream_idle = false; break; case SortMutation::ParentReplacement: - parent.created_by_same_id_replacement = true; + parent.replaced_order_incarnation = 1; break; case SortMutation::MissingCancelToken: parent.recreated_after_named_cancelled_entry_incarnation = 0; @@ -468,7 +467,7 @@ static bool retained_child_predicate_accepts(SortMutation mutation) { parent.named_cancel_surviving_exit_incarnation = 0; break; case SortMutation::MismatchedChildReplacementToken: - child.replaced_exit_order_incarnation = 8; + child.replaced_order_incarnation = 8; break; case SortMutation::CancelTokenEqualsParent: parent.recreated_after_named_cancelled_entry_incarnation = @@ -509,10 +508,10 @@ static bool retained_child_predicate_accepts(SortMutation mutation) { child.qty = 1.0; break; case SortMutation::FreshChild: - child.created_by_same_id_replacement = false; + child.replaced_order_incarnation = 0; break; case SortMutation::ChildRequestedPartial: - child.requested_partial = true; + child.quantity_request.request(QuantityIntent::fraction(50.0, 100.0)); break; case SortMutation::ChildPercentPartial: child.qty_percent = 50.0; diff --git a/tests/test_stop_open_margin_script_state.cpp b/tests/test_stop_open_margin_script_state.cpp index 9d203552..05f7a61c 100644 --- a/tests/test_stop_open_margin_script_state.cpp +++ b/tests/test_stop_open_margin_script_state.cpp @@ -311,8 +311,10 @@ class PendingGuard : public BacktestEngine { pyramid_entries_[0].ordinary_stop_open = false; pyramid_entries_[0].ordinary_market_open = true; break; - case 15: pending.created_during_coof_recalc = true; break; - case 16: pending.created_while_in_position = true; break; + case 15: pending.birth = OrderBirth::fill_evaluation(0, 0, BirthCursor::point(BirthCursorDomain::HistoricalPath, 0, 4), 100.0, 1, 1, 1); break; + // A position-bound EXIT is distinct from the flat-born pending STOP. + case 16: pending.type = OrderType::EXIT; + pending.created_position_side = PositionSide::SHORT; break; case 17: pending_orders_.push_back(pending); break; default: break; }