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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 60 additions & 11 deletions docs/native-settlement.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
# Native resolved execution settlement

`BacktestEngine::settle_resolved_execution` is a protected C++ extension for
trusted matching adapters. It applies an already admitted, matched execution
to the engine's existing physical lot book and emits the resulting close and
open observations. It adds no second position ledger or persistent selector.
Callers that supply transient EXIT lifecycle effects use the separately named
`settle_execution_with_lifecycle` seam; the two-argument symbol is unchanged
and forwards empty effects.
`BacktestEngine::settle_native_execution_at` is a protected C++ extension for
trusted native matching adapters. It applies an already admitted, matched
execution using a supplied `PhysicalExecutionContext` and the engine's one
physical lot book. Scoped and selected variants use the same financial owner.
Use `PhysicalExecutionContext{}` when zero time/index and no preceding-path
facts are appropriate; native settlement has no implicit source chart clock.

The original two-argument `settle_resolved_execution` symbol is a source
compatibility alias of `settle_execution_with_lifecycle` with empty effects.
It uses current chart context and preserves source-day checks and observations.
It is distinct from the explicit native/context seams, which do not depend on
those source fields. Both routes book physical and financial effects once.

The caller supplies an `execution::Action` and `execution::Fill` from
`<pineforge/execution.hpp>`. These values describe immediate effects:
Expand Down Expand Up @@ -65,7 +70,7 @@ nonvirtual methods:
| `inspect_native_reversal_v1(reversal, fill)` | Non-mutating `SettlementInspection` of physical effects and the current ticket. |
| `project_native_reversal_v1(reversal, fill)` | Non-mutating `AccountEffectProjection`, including account effects and the next cycle without consuming it. |
| `settle_native_reversal_at_v1(reversal, fill, context)` | `Result` from settlement using the supplied `PhysicalExecutionContext` and empty lifecycle effects. |
| `settle_reversal_with_lifecycle_v1(reversal, fill, lifecycle)` | `Result` from settlement using the engine's current time, interval and preceding exit-path facts with supplied transient lifecycle effects. |
| `settle_reversal_with_lifecycle_v1(reversal, fill, lifecycle)` | Source-coordinated `Result` using current chart context, supplied lifecycle effects and source-day preflight/observation. |

For example, reversing a held long position of `1` unit to a short target of
`0.1` opens the exact binary64 quantity supplied as `-0.1`. The opening quantity
Expand Down Expand Up @@ -162,6 +167,49 @@ and paid cost in roster order. A projection is data, not commit authority: later
settlement revalidates against the current book. Source percent sizing can consume
a close-only projection's equity before resolving one reversal opening quantity.

## Source-day observation boundary

Native Book/scoped/selected/ReverseTo settlement and the generic
`settle_with_context` seam do not read or update Pine intraday PnL or
consecutive-loss-day counters. Their inspection and account projection methods
are likewise independent of these source fields. An exhausted source day count
or nonfinite cached source intraday value cannot refuse an otherwise valid
native execution. Financial win/loss/even, entry/cycle/stream capacity and
lifecycle checks remain generic and still run before physical effects.

Three source coordinators preserve the existing source behavior:
`settle_execution_with_lifecycle`, `settle_execution_selected_with_lifecycle`
and `settle_reversal_with_lifecycle_v1`. They share the native stage and quote,
prepare this execution's close rows once, validate source observations before
effects, and use the same physical/financial commit. Only after Applied do they
observe the newly committed slice identified by the Result. Historical and
range-end report rows are not observed again.

Source preflight first completes the full ordered intraday-PnL sum and checks
that it is finite. It then simulates consecutive-loss-day counters, using the
existing chart timezone and `dayofmonth * 100 + month` key. That day is captured
before effects, so the committed-row observer performs no timezone work. A loss
increments the count only on a different last-loss day; a win resets the count
without clearing the last-loss-day key; zero PnL leaves the count unchanged.
The observer updates source fields only and never applies cash or fees again.

Source intraday invalidity remains `InvalidAccounting`, before source or
financial close-counter overflow. Source day exhaustion retains
`"closed trade counter exhausted"` before later lifecycle/stream checks.
Generic cycle readiness still precedes source preflight. A Ready opening-only
source call also requires finite cached intraday PnL, despite having no close
rows; it performs no observer writes. Invalid and NoEffect stages return before
source preflight. These checks and observations are private to the synchronous
call and introduce no saved settlement plan or persistent state.

Source risk/halt evaluation stays at its existing fill-loop call sites after
the coordinator returns. Native admission remains its own direction, resulting
position and initial-margin gate; Pine entry/risk/quota rules stay in source
adapters. Physical positions and marked equity continue to follow actual lots,
while the existing script/C position view may remain frozen. Range-end reporting
can update report rows/equity-curve state without committing physical closes or
source-day observations. Existing source fields remain represented in hashes.

## Integration and limits

The kernel serves native execution and the source adapters' full, partial,
Expand Down Expand Up @@ -209,6 +257,7 @@ run rather than retry a partially committed execution in place. Strong
rollback on allocation failure is not promised.

The work retains `ShortSeedCollisionRole` while its source-policy consumers
remain. Admission, source day/quota counters, script-visible observations and
complete Pine lowering remain separate refactor work. Existing executable state
remains represented in ABI projections and fingerprints.
remain. Source quota, TV-money/day-loss policy and complete Pine lowering remain
separate refactor work. Source-day observation ownership is separated from the
native financial owner; existing executable state remains represented in ABI
projections and fingerprints.
43 changes: 36 additions & 7 deletions include/pineforge/engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1807,16 +1807,17 @@ class BacktestEngine {
// --- Per-trade extreme tracking ---
void update_per_trade_extremes();

// Trusted native matching-adapter extension: settle an already resolved
// execution into this engine's one physical book. This does not place an
// order, perform admission/slippage, or provide cancellation/replay. The
// owning run must abort on an exception; failed commits are not retryable
// in place. A saved arithmetic plan is not execution authority.
// Source compatibility extension: settle an already resolved execution
// using the current chart context and source-day preflight/observation.
// This does not place an order, perform admission/slippage, or provide
// cancellation/replay. Explicit native/context seams below are source-day
// independent. The owning run must abort on an exception; failed commits
// are not retryable in place. A saved plan is not execution authority.
// Empty lifecycle effects. Member-pointer type is the original two-argument
// symbol; it forwards to settle_execution_with_lifecycle.
execution::Result settle_resolved_execution(
const execution::Action& action, const execution::Fill& fill);
// Trusted matching-adapter extension for one execution plus lifecycle effects.
// Source coordinator for one execution plus lifecycle effects and source days.
// Pre-close operations, then close observations and old-cycle unbind, then
// the listed pending removals, then the quoted opening path which binds
// only remaining exits. Native settlement does not call compat::pine
Expand Down Expand Up @@ -1853,7 +1854,8 @@ class BacktestEngine {
const execution::Fill& fill,
const execution::PhysicalExecutionContext& context,
const execution::SelectedOpeningSet& selection);
// Legacy lifecycle copy of current_bar_/fold flags, then selected commit.
// Source lifecycle coordinator: current_bar_/fold context, source-day
// preflight, selected commit, then observation of its committed close rows.
// Empty lifecycle is valid. Do not fold selection into LifecycleEffects.
execution::Result settle_execution_selected_with_lifecycle(
const execution::Action& action,
Expand Down Expand Up @@ -1886,6 +1888,7 @@ class BacktestEngine {
const execution::ReverseTo& reversal,
const execution::Fill& fill,
const execution::PhysicalExecutionContext& context);
// Source reversal coordinator with current chart context and source days.
execution::Result settle_reversal_with_lifecycle_v1(
const execution::ReverseTo& reversal,
const execution::Fill& fill,
Expand Down Expand Up @@ -3910,6 +3913,7 @@ class BacktestEngine {
execution::CloseScope book_or_opening,
const execution::SelectedOpeningSet* selected);
struct NativeSettlementStage;
struct NativeSettlementRows;
execution::Status validate_native_settlement_book(double& held) const;
execution::Status allocate_native_settlement_closes(
NativeSettlementStage& stage,
Expand All @@ -3925,6 +3929,27 @@ class BacktestEngine {
NativeSettlementStage& stage, const execution::Fill& fill,
const execution::LifecycleEffects& lifecycle,
const execution::PhysicalExecutionContext& context);
void build_native_settlement_close_rows(
const NativeSettlementStage& stage, const execution::Fill& fill,
const execution::PhysicalExecutionContext& context,
NativeSettlementRows& rows) const;
execution::Status prepare_native_settlement_commit(
const NativeSettlementStage& stage, const execution::Fill& fill,
const execution::PhysicalExecutionContext& context,
NativeSettlementRows& rows) const;
execution::Status preflight_native_settlement_effects(
const NativeSettlementStage& stage,
const execution::LifecycleEffects& lifecycle,
const NativeSettlementRows& rows);
execution::Result commit_prepared_native_settlement_stage(
NativeSettlementStage& stage, const execution::Fill& fill,
const execution::LifecycleEffects& lifecycle,
const execution::PhysicalExecutionContext& context,
NativeSettlementRows& rows);
execution::Result settle_source_staged_execution(
NativeSettlementStage& stage, const execution::Fill& fill,
const execution::LifecycleEffects& lifecycle,
const execution::PhysicalExecutionContext& context);
void stage_native_settlement(
NativeSettlementStage& stage,
const execution::Action& action,
Expand Down Expand Up @@ -4339,6 +4364,10 @@ class BacktestEngine {
double fill_price, bool was_long);
void record_close_trade(Trade trade);
void validate_close_trade_counters(const Trade* rows, size_t count) const;
execution::Status preflight_source_close_observation(
const Trade* rows, size_t count, std::optional<int>& loss_day) const;
void observe_source_close_rows(
const Trade* rows, size_t count, std::optional<int> loss_day);
// Quote one resolved execution's current charges. Entry costs on the
// closed rows are historical allocations. Returns close shares in FIFO
// order followed by the opening share (zero when there is no opening).
Expand Down
Loading
Loading