design(C10): retire the realistic broker — cost is a cost-model graph in R (gross→net), money to the live deploy edge (refs #116)
Reworks the C10 contract per a ratified in-context design discussion (#116). The historical "realistic broker" with authored frictions is retired as horseshoe-throwing: real friction (slippage, swaps, spread) is not historically knowable; the only reliable friction is measured forward against a real broker. In its place: - Cost is a composable C9 graph of cost nodes, in R, that approximates (never claims) realism — generalizing/subsuming the scalar round_trip_cost. Output is a net-R equity curve (gross R = signal, net R = after cost). - Research is pure feed-forward: the Sizer and compounding leave the loop; the z^-1 fill-edge register and the flat-1R-vs-compounding structural axis are gone. Compounding/Kelly/drawdown are post-hoc transforms at the deploy/account layer. - Executor = stop-rule + position-management in R. size/volume is a deploy concept; the position-event table is demoted to a deploy/reconciliation artifact. - Conviction-based risk allocation survives as an R-aggregation axis (flat vs |bias|-weighted), distinct from the removed money-Sizer. - Money, a real broker, and cTrader Open API are a separate, later live/deploy edge (C11 record-then-replay; C13 frozen deploy) — the only ground truth. Stage-1 R realizations preserved as history; the #117 reframe's R spine survives, its Stage-2/currency/register/compounding portions superseded.
This commit is contained in:
+340
-225
@@ -477,7 +477,7 @@ distinguishability check, if ever wanted (e.g. for the WASM graph view's #21
|
||||
thread), is decoupled from param-space injectivity. Construction-phase only; the
|
||||
flat graph stays name-free (C23).
|
||||
|
||||
### C10 — Strategy output is a bias stream; signal quality is measured in R; risk-based execution and realistic brokers are decoupled downstream layers
|
||||
### C10 — Strategy output is a bias stream; signal quality is measured in R; cost is a composable downstream graph (gross R → net R); money is decoupled to the live deploy edge
|
||||
**Guarantee.** A strategy's primary, backtestable output is **not** an equity
|
||||
curve, **nor a position-event table**, **nor a position size**, but a **bias
|
||||
stream**: the DAG expresses exactly one *state* at time t (C8 — a node emits at
|
||||
@@ -488,245 +488,360 @@ case). Bias is **unsized**: position sizing and the protective stop do **not**
|
||||
live in the strategy. The chain is `signals (scores) → decision node → bias
|
||||
stream`.
|
||||
|
||||
**Risk-based execution is a decoupled downstream layer.** Turning a bias into a
|
||||
position is the job of a **risk-based execution** chain — `bias → stop-rule →
|
||||
Sizer → Veto → position-management` — never the strategy's. The **stop-rule** sets
|
||||
a protective stop, which **defines the risk unit R** (1R = the loss taken if
|
||||
stopped). The **Sizer** sizes the trade in R; the **Veto** is a distinct
|
||||
pre-trade gate (position / exposure caps), kept separate from sizing. These wire
|
||||
into a **RiskExecutor** composite (per symbol); RiskExecutors nest inside a
|
||||
**Broker / Account** composite that aggregates per-symbol P&L into account equity.
|
||||
The account **mode is a composition constraint**: a *netting* account allows ≤1
|
||||
RiskExecutor per (strategy, symbol); a *hedging* account (the European CFD
|
||||
default) allows many per symbol, even oppositely directed.
|
||||
**Risk-based execution is a decoupled downstream layer; in research it is Stop +
|
||||
position-management in R, no Sizer.** Turning a bias into a tracked trade is the
|
||||
job of a downstream **execution** chain, never the strategy's. The **stop-rule**
|
||||
sets a protective stop, which **defines the risk unit R** (1R = the loss taken if
|
||||
stopped). In the research loop the executor is **stop-rule → position-management**,
|
||||
operating **directly in R**, with the **Veto** an optional documented pre-trade-gate
|
||||
seam (a pass-through identity DCE'd away under C19/C23 when absent): there is **no
|
||||
Sizer**. Sizing in *currency* (`size` / `volume`) is a **deploy** concept. C8's
|
||||
wiring totality (cycle-0040 `check_ports_connected`: no optional-input concept —
|
||||
every declared port is covered by exactly one wiring act) forbids a *dangling*
|
||||
`size` port, so the resolution is concrete: research `PositionManagement` either
|
||||
**drops its `size` input port** from its node signature, or has that port **driven
|
||||
by a constant unit node** (the flat-1R degenerate) — never an unwired "vestigial"
|
||||
port; the record's `size` field is held at unit and carries no research
|
||||
information, and the position-event table's `volume` column likewise. Consequently
|
||||
the **position-event table is demoted to a deploy / reconciliation artifact** (real
|
||||
volume lives there), no longer a research artifact and no longer "fed to a broker".
|
||||
The three-way decoupling of **direction (bias) / sizing / fill** stands as a
|
||||
*structure*; sizing and fill are simply pushed entirely to the deploy edge, out of
|
||||
the research loop.
|
||||
|
||||
**Signal quality is measured in R — Stage 1.** A downstream **R-evaluator**
|
||||
consumes a RiskExecutor run under **flat-1R** sizing (every trade risks exactly
|
||||
1R) and integrates the per-trade R-outcomes into an **R-expectancy** / R-curve:
|
||||
the account- and instrument-agnostic yardstick for *"how much R out per 1R
|
||||
risked?"*. Flat-1R needs no equity, so this stage is **feed-forward** (no
|
||||
feedback, no cycle) — the primary research loop, maximally parallel and
|
||||
deterministic (C1). R, not pips, is the unit: pips are not risk-normalized.
|
||||
**Signal quality is measured in R — gross R and net R.** A downstream
|
||||
**R-evaluator** consumes the executor run and integrates the per-trade R-outcomes
|
||||
into an **R-expectancy** / R-curve: the account- and instrument-agnostic yardstick
|
||||
for *"how much R out per 1R risked?"*. R, not pips, is the unit (pips are not
|
||||
risk-normalized). Two readings of the same unit: **gross R** (signal only) vs **net
|
||||
R** (after the cost model), with `net R = gross R − cost-in-R`. The headline
|
||||
artifact is the **net-R equity curve** — the cost-drag drawn onto the R curve,
|
||||
recorded through a named **`net_r_equity`** tap/sink (sibling of the existing
|
||||
`r_equity` tap; a sink is the only thing the registry can display, C8/C18/C22). No
|
||||
new unit is invented: it is R, gross and net, **continuous with the existing
|
||||
`net_expectancy_r`**. A bare **gross-R run with no cost model attached is valid**:
|
||||
the cost layer is optional and additively composed-on (the zero-cost baseline is
|
||||
the "default simple" floor).
|
||||
|
||||
**Currency P&L is Stage 2 — deploy viability.** Entered only once `E[R] > 0` is
|
||||
established. Here sizing is **fixed-fractional** (size = risk-fraction · equity /
|
||||
stop-distance), so it **reads account equity** and **compounds**; **realistic
|
||||
broker** nodes apply real spread / commission / slippage / lot / margin and emit a
|
||||
**currency** equity stream. This introduces the *only* feedback in the whole
|
||||
design — equity → Sizer — cut by a **`z⁻¹` register on the fill edge** (the
|
||||
realized/fill component is delayed one cycle; mark-to-market off the current price
|
||||
stays a legal same-cycle read, C2), **encapsulated inside the executor
|
||||
composite**. Whether that register is live is the **flat-1R vs compounding
|
||||
structural axis** (instantiating a feedback edge is topology, fixed at bootstrap
|
||||
(C19), hence a structural axis (C20), never a numeric sweep param): flat-1R DCEs
|
||||
it away (Stage 1), compounding keeps it (Stage 2). The explicit register is
|
||||
**mandatory** — C9's "only feedback is an explicit delay node" made concrete —
|
||||
because the bootstrap's optimiser reorders the flat graph (C23 CSE/DCE): the
|
||||
implicit read-before-fill ordering by which conventional sequential backtesters
|
||||
cut this loop would not survive that reordering.
|
||||
**Cost is a COST MODEL — a composable C9 graph of cost nodes, in R, that
|
||||
approximates (never claims) realism.** The realistic broker is *retired* (see
|
||||
Forbids / the 2026-06-28 reframe): real friction — slippage (live
|
||||
liquidity / order-size / volatility at fill), swaps (broker-set, time-varying),
|
||||
even recorded feed spread (often a fake constant) — is **not historically
|
||||
knowable**, so an authored-friction historical broker is "horseshoe-throwing". Its
|
||||
replacement is a **cost model**: an ordinary downstream **C9 graph of cost nodes**
|
||||
that *approximates* the cost side a broker would produce, explicitly as an
|
||||
approximation. The cost nodes live in `aura-std` (the cost-graph composite-builder
|
||||
in `aura-composites`), never in the domain-free `aura-engine` (C14/C16). They are
|
||||
**not** "additive on the R stream" in isolation: a cost node **reads the state it
|
||||
depends on** — the price stream, a realized-volatility tap, a C11-recorded
|
||||
interest-rate source, and the executor's per-cycle R-record / trade events — and
|
||||
emits a **cost-in-R** stream that is subtracted from gross R to yield net R. Cost
|
||||
attaches at the structurally correct grain: **per-trade** factors (commission, a
|
||||
flat cost-per-trade) deduct from `realized_r` at close; **per-cycle-held** factors
|
||||
(carry / funding / swap) accrue over the holding duration. The model **generalizes**
|
||||
the existing scalar `round_trip_cost` / `net_expectancy_r` into a possibly
|
||||
**state-dependent graph**: the scalar `round_trip_cost` is the degenerate
|
||||
constant-per-trade special case, **subsumed** by the cost graph — the post-run
|
||||
`summarize_r` fold no longer recomputes cost independently but folds the
|
||||
cost-model's net-R stream into `net_expectancy_r` (one home for cost, no
|
||||
double-count). Discipline: every cost factor is **either** a clearly-labelled
|
||||
**stress-parameter** (e.g. a flat cost-per-trade is a breakeven-threshold probe)
|
||||
**or** **data-grounded / falsifiable** (e.g. realized-volatility → slippage;
|
||||
recorded interest-rate data via C11 → funding / swap). **Default simple;
|
||||
complexity is earned per grounded factor.** Stacking unfalsifiable guesses
|
||||
(over-modelling) is the anti-pattern.
|
||||
|
||||
**The position-event table** — turning the executed book into a broker-independent,
|
||||
time-ordered **table of position events** (pure scalar columns, C7: `event_ts:
|
||||
timestamp`, `action: i64` buy / sell / close, `position_id: i64`,
|
||||
`instrument_id: i64`, `volume: f64` unsigned — direction is the `action`; a
|
||||
position's open time is its opening event's `event_ts`, no separate `open_ts`; a
|
||||
`close` references a `position_id` and may be partial via its own `volume`) — is a
|
||||
**decoupled, derived, downstream** Stage-2 layer. The events are the **first
|
||||
difference of the book** under book-tracking (`deal = target − book − in_flight`;
|
||||
a close sizes the actual book, never an exposure delta), materialized as a
|
||||
*computed table* (multiple events may share one `event_ts` — e.g. a reversal's
|
||||
close + open), **never as a per-`eval` node output** (C8 caps one record per
|
||||
cycle). This table feeds the realistic broker nodes; one table feeds many, giving
|
||||
directly comparable currency curves. Live: a realistic broker node consumes the
|
||||
events in real time and routes orders as a side effect; reconciliation with the
|
||||
real account is an external adapter.
|
||||
**The research loop is pure feed-forward — compounding is removed.** Flat-1R-style
|
||||
R accounting needs no equity, and with the Sizer gone there is **no equity → size
|
||||
edge at all** in research: the loop is **feed-forward, maximally parallel and
|
||||
deterministic (C1)**, the cost model a feed-forward subtraction on the R stream.
|
||||
**Compounding is removed from research**: it is a **post-strategy money-management
|
||||
transform** — a pure function of the per-trade **net-R sequence** and a
|
||||
bet-fraction *f*, multiplicative and path / order-dependent, the **sole** source of
|
||||
feedback — so compounding, Kelly-*f*, and drawdown-under-compounding are derived
|
||||
**post-hoc, analytically, at the deploy / account layer** from the net-R
|
||||
distribution. Consequently the `z⁻¹` fill-edge register **and** the
|
||||
flat-1R-vs-compounding structural axis are **gone from the research loop** (with no
|
||||
in-loop feedback there is nothing for a register to cut, so the C23-reordering
|
||||
hazard the old text invoked to make the register mandatory no longer exists — this
|
||||
is strictly C9 / C23-cleaner).
|
||||
|
||||
**Conviction-based risk allocation survives — as an R-aggregation axis, not a
|
||||
Sizer.** Scaling risk by bias strength is **signal-side and R-denominated**.
|
||||
Because per-trade R is **size-invariant**, conviction cannot be expressed by
|
||||
scaling position size (that is invisible to R); it is expressed by **weighting the
|
||||
per-trade R-contribution** in the R-equity: **flat** (sum of `realized_r`, sign
|
||||
only) vs **conviction-weighted** (sum of `|bias| · realized_r`, sign + magnitude).
|
||||
This is a **feed-forward, additive, order-independent research axis** (flat vs
|
||||
conviction-weighted R-aggregation), distinct from the removed money-Sizer; it is
|
||||
tested via the existing `conviction_at_entry` field and `conviction_terciles_r`
|
||||
metric, and may sit in-graph or as a post-hoc fold.
|
||||
|
||||
**Money / real broker / cTrader Open API = a separate, later live / deploy-edge
|
||||
concern — the only `belastbare` (reliable) ground truth, measured never modelled.**
|
||||
Reliable friction statistics require **forward-trading against a real broker**
|
||||
(e.g. cTrader Open API), and are non-stationary even then. This fits C11
|
||||
(record-then-replay: real fills are recorded live, then replayable) and the
|
||||
frozen-deploy invariant (C13: deploy = frozen bot + broker connection);
|
||||
reconciliation with the real account is an **external I/O adapter** at the
|
||||
recording / deploy edge, not an in-graph node. **This is the only place account
|
||||
money appears.** (Currency-denominated *reference geometry* — pip value,
|
||||
stop-distance-in-currency from the C15 `instrument_geometry` sidecar — is still
|
||||
read at the **ingestion** edge to normalize a currency / pip cost factor into R;
|
||||
notional size cancels in `cost_in_R = cost_in_currency / (size · stop_dist)`, so
|
||||
the cost model is R-pure without ever holding *equity*. It is equity / account
|
||||
money, not reference geometry, that lives only at the deploy edge.) The
|
||||
broker-independent **position-event table** (`event_ts, action[buy/sell/close],
|
||||
position_id, instrument_id, volume`) is the **deploy / reconciliation** record at
|
||||
this edge (real volume), not a research artifact.
|
||||
|
||||
**Honesty principle.** The net-R curve under the cost model is a **research /
|
||||
ranking tool and a hypothesis**; the **forward / live run against a real broker is
|
||||
the ground truth**. The cost model *approximates*; it **never claims realism**.
|
||||
`SimBroker` (the pip-equity, unsized-exposure node) is the **pre-reframe pip
|
||||
ancestor** — with the net-R cost model it is **redundant as a quality measure** (R
|
||||
supersedes pips). It is retained as a **legacy / simple optional pip yardstick**
|
||||
(still wired in the `stage1-r` harness for an honest dual readout), **not part of
|
||||
the new model and not to be expanded**.
|
||||
|
||||
So the layers attach at distinct points: the **R-evaluator** on a flat-1R
|
||||
RiskExecutor (signal quality, R, Stage 1); **realistic brokers** on the derived
|
||||
position-event table (execution viability, currency, Stage 2). All are ordinary
|
||||
downstream nodes (C8/C9); several can attach at once for directly comparable
|
||||
curves.
|
||||
**Forbids.** Putting **sizing or the stop in the strategy** (bias is unsized; the
|
||||
Sizer and stop-rule own them); treating an equity curve as the strategy's output;
|
||||
making the **position-event table the strategy's direct DAG output** (it is
|
||||
derived, not emitted per `eval` — a decision instant may need >1 event, which C8
|
||||
forbids) or the measure of signal quality; **measuring signal quality in currency
|
||||
before R is established** (Stage 2 before Stage 1); a **combinational equity → size
|
||||
cycle** (the fill-edge register is mandatory — an implicit execution ordering does
|
||||
not survive C23 reordering); **fusing the Sizer and the Veto** into one node (the
|
||||
size / veto / fill roles stay distinct); baking a broker into the strategy; a
|
||||
special external broker subsystem (a broker is an ordinary node); storing
|
||||
`open_ts` (derive it from the opening event); a signed-volume direction trick in
|
||||
the event table (use `action`); broker-specific assumptions leaking into the
|
||||
strategy logic.
|
||||
stop-rule owns the stop, R is the unit); treating an **equity curve** (R or
|
||||
currency) as the strategy's direct output; **putting a Sizer / currency size /
|
||||
`volume` into the research loop** (size is a deploy concept; research is in R);
|
||||
leaving a **dangling `size` input port** on the research executor (C8 wiring
|
||||
totality forbids it — drop the port or drive it with a constant unit); **any
|
||||
equity → size / equity → anything feedback in research** (the research loop is pure
|
||||
feed-forward; compounding is a post-hoc money-management transform, not an in-loop
|
||||
edge); modelling an **authored-friction "realistic broker" over historical data**
|
||||
(real friction is not historically knowable — use the approximating cost model, and
|
||||
treat the real broker as the live-edge ground truth only); **claiming the cost
|
||||
model is realism** (it is an explicit approximation); **stacking unfalsifiable cost
|
||||
guesses** (each cost factor is a labelled stress-parameter *or* data-grounded —
|
||||
over-modelling is the anti-pattern); **computing cost in two homes** (the cost
|
||||
graph owns cost; the post-run fold subsumes the old scalar `round_trip_cost`, never
|
||||
double-counts it); expressing **conviction by scaling position size**
|
||||
(size-invisible to R; conviction is an R-aggregation weight); making the
|
||||
**position-event table the strategy's direct DAG output** (it is derived, not
|
||||
emitted per `eval` — a decision instant may need >1 event, which C8 forbids) or a
|
||||
**research** measure of signal quality (it is a deploy / reconciliation artifact);
|
||||
**measuring signal quality in currency / account money** in the research loop at all
|
||||
(account money lives only at the live deploy edge; reference geometry at ingestion
|
||||
is not account money); baking a broker into the strategy or an **in-graph broker
|
||||
subsystem** (the in-graph realistic broker is retired; the only broker is the
|
||||
live-edge I/O adapter, C11 / C13); a signed-volume direction trick in the event
|
||||
table (use `action`); storing `open_ts` (derive it from the opening event).
|
||||
|
||||
**Why.** A strategy's edge is a *procedure*, not a currency outcome ("focus on the
|
||||
procedure, not the money"): the right primary question is *"how much R out per 1R
|
||||
risked?"*, and R — defined by the stop — is the only account- and
|
||||
instrument-agnostic, risk-normalized unit (pips are not). Separating **direction
|
||||
(bias) from sizing (Sizer) from fill (broker)** is the decomposition every mature
|
||||
system converges on (LEAN's Alpha → Portfolio-Construction → Execution is a near
|
||||
isomorphism; backtrader, QSTrader, zipline all emit an unsized directional signal
|
||||
sized downstream). The **Stage-1 / Stage-2 cleave** quarantines the *only*
|
||||
feedback (equity → sizing) into Stage 2, leaving the signal-quality layer
|
||||
feed-forward, parallel and deterministic (C1); its closest external precedent is
|
||||
vectorbt's split of vectorized signal generation from sequential money-management
|
||||
— it is aura's own synthesis, not an industry standard. The DAG holds exactly one
|
||||
state at t and a node emits ≤1 record per `eval` (C8), so the faithful per-cycle
|
||||
output is the **bias** (one value); position *events* are its book's first
|
||||
difference, a derived consequence — decoupling them resolves the C8↔C10 impedance.
|
||||
Modelling brokers and executors as nodes (not a bespoke subsystem) keeps them
|
||||
within the one Node/graph abstraction (C9). This supersedes the **exposure**
|
||||
framing (the strategy's output was a signed fractional *position*) of the
|
||||
cycle-0007 reframe and its realization notes below; that in turn superseded the
|
||||
still-earlier "the strategy's output is the position-event table" framing.
|
||||
(bias) from sizing from fill** is the decomposition every mature system converges
|
||||
on (LEAN's Alpha → Portfolio-Construction → Execution is a near isomorphism;
|
||||
backtrader, QSTrader, zipline all emit an unsized directional signal sized
|
||||
downstream) — and aura pushes *sizing* and *fill* off the research loop for two
|
||||
**distinct** reasons: **sizing** is off because per-trade R is **size-invariant**
|
||||
(size carries no information in R — flat-1R is perfectly knowable, it just does not
|
||||
matter), and **fill / friction** is off because real friction is **not
|
||||
historically knowable** and therefore not honest over history. Keeping research
|
||||
**pure feed-forward** leaves the signal-quality layer parallel and deterministic
|
||||
(C1); the **only** real feedback (equity → bet-fraction) is **compounding**, a
|
||||
closed-form, path-dependent transform of the net-R sequence, and therefore belongs
|
||||
**after** the strategy, at the deploy / account layer, not as an in-loop register.
|
||||
The **cost model as a C9 graph** keeps cost within the one Node / graph abstraction
|
||||
(C9) and generalizes the scalar `net_expectancy_r` continuously, while the
|
||||
**gross-R / net-R** split states the cost-drag honestly without inventing a unit.
|
||||
Refusing the historical realistic broker is an **honesty** stance: the only
|
||||
`belastbare` friction is **measured forward** against a real broker (cTrader Open
|
||||
API) — the live deploy edge, the sole place account money and ground truth appear.
|
||||
The DAG holds exactly one state at t and a node emits ≤1 record per `eval` (C8), so
|
||||
the faithful per-cycle output is the **bias** (one value); position *events* are a
|
||||
derived, deploy-side consequence. This supersedes the **realistic-broker /
|
||||
currency / compounding / Stage-1-vs-Stage-2** framing of the #117 reframe (see the
|
||||
2026-06-28 reframe note), which itself superseded the **exposure** framing of
|
||||
cycle-0007 and the still-earlier "strategy's output is the position-event table"
|
||||
framing.
|
||||
|
||||
**Reframe (2026-06-28, #116 — realistic broker retired; cost-model graph in R; money to the live edge).**
|
||||
This is the live contract above. Ratified in an in-context design discussion
|
||||
(reference issue #116). It **preserves** the durable spine — unsized bias stream
|
||||
(sign = direction, magnitude = conviction), signal quality in R, the stop defining
|
||||
R, and the decoupling of direction from sizing from fill — and the shipped Stage-1
|
||||
realizations (the `Bias` node, `FixedStop` / `vol_stop`, `PositionManagement`,
|
||||
`summarize_r` / `RMetrics` / `sqn_normalized`, SQN as ranking objective). The
|
||||
**RiskExecutor composite survives in shape** (bias + price → stop-rule →
|
||||
position-management) but with its **Sizer interior removed** and its `risk_budget`
|
||||
argument dropped / vestigial (it sized the now-removed Sizer); the Veto remains an
|
||||
optional documented seam. The contract **supersedes** the following, which were
|
||||
**design intent, largely unbuilt** (#116 — the realistic broker and the whole
|
||||
Stage-2 currency layer were never implemented; the Stage-1 R chain was) and are
|
||||
retired:
|
||||
- the **"realistic broker"** concept — an authored-friction historical broker —
|
||||
rejected as "horseshoe-throwing" (real friction is not historically knowable),
|
||||
replaced by the **cost model**: a composable C9 graph of cost nodes (in
|
||||
`aura-std` / `aura-composites`), approximating not claiming realism,
|
||||
generalizing / subsuming `round_trip_cost` into `net_expectancy_r`;
|
||||
- the **"Currency P&L is Stage 2"** paragraph in full, the **Stage-1-vs-Stage-2
|
||||
hard sequencing gate**, and **currency / fixed-fractional / compounding** in
|
||||
research — compounding is now a post-hoc money-management transform of the
|
||||
net-R sequence at the deploy / account layer;
|
||||
- the **`z⁻¹` register on the fill edge** and the **flat-1R-vs-compounding
|
||||
structural axis** as research mechanism — there is no equity → size edge in
|
||||
research, so no register and no such axis in the loop;
|
||||
- the **Sizer in research** and currency **size / `volume`** — size is a deploy
|
||||
concept; the research executor is stop + position-management in R;
|
||||
`PositionManagement`'s `size` port is dropped or constant-driven (no dangling
|
||||
port, C8), its `size` field and the event table's `volume` are vestigial in
|
||||
research;
|
||||
- the **"a broker is an ordinary in-graph node / no special external broker
|
||||
subsystem"** forbid, **for the live edge only**: in-graph brokers are retired
|
||||
outright, and the live broker is now an explicit **I/O adapter** at the C11
|
||||
recording / C13 deploy edge — not an in-graph node and not part of the research
|
||||
graph (the no-in-graph-broker-subsystem prohibition still holds inside the
|
||||
graph);
|
||||
- the **position-event table as the realistic-broker input** and its
|
||||
first-difference-of-the-book (`deal = target − book − in_flight`) execution
|
||||
framing — the table (schema 0063 #114, derive 0068 #115) **survives** as the
|
||||
**deploy / reconciliation** artifact (real volume), not a research artifact and
|
||||
not "fed to a broker" in research.
|
||||
Money, a real broker, and cTrader Open API are a **separate, later live /
|
||||
deploy-edge** concern (C11 record-then-replay; C13 frozen-deploy invariant) — the
|
||||
only `belastbare` ground truth, measured, never modelled. The honesty principle is
|
||||
explicit: the net-R curve is a research / ranking hypothesis; the forward / live
|
||||
run is ground truth. `SimBroker` is downgraded to a legacy / optional pip
|
||||
yardstick, not to be expanded. (Terminology note: with Stage 2 gone, the
|
||||
**"Stage-1"** label below is no longer one half of a two-stage gate — it survives
|
||||
only as a historical cycle / identifier name, like the `exposure` → `bias` on-disk
|
||||
alias, denoting the shipped feed-forward R chain.)
|
||||
|
||||
**Reframe (2026-06-23, #117 — exposure → bias, R as the signal-quality unit).**
|
||||
The contract above is the R-reframe; the realization notes below describe the
|
||||
**pre-reframe code** (`Exposure`, `SimBroker`, pip-equity) and are retained as
|
||||
history and as the **Stage-1 ancestor**: `Exposure { scale }` is the ancestor of
|
||||
the unsized `bias` node, and `SimBroker`'s pip integral is the ancestor of the
|
||||
R-evaluator (which additionally requires a stop, since R is stop-defined, so it
|
||||
consumes a flat-1R RiskExecutor rather than raw bias). The `exposure → bias`
|
||||
rename, the RiskExecutor / Sizer / Veto nodes, and the R-evaluator **landed in
|
||||
cycle 0065** (Stage-1; see the realization note below); the position-event schema (0063, #114) survives
|
||||
unchanged as the Stage-2 audit layer, though its derivation is now the **book**
|
||||
first-difference (`deal = target − book − in_flight`), not the flawed 0064
|
||||
exposure-integral derive (abandoned). Industry grounding for this reframe: LEAN /
|
||||
nautilus_trader / backtrader / QSTrader / vectorbt / zipline (see #117 decision
|
||||
log).
|
||||
**Realization (cycle 0007).** The signal-quality half of this contract is now
|
||||
realized at the substrate, as two `aura-std` nodes composed on the unchanged
|
||||
engine (the engine stays domain-free — it routes only `f64` records, never
|
||||
"exposure" or "equity"). The **exposure stream** is realized as `Exposure { scale
|
||||
}`: the decision/sizing node, `clamp(signal / scale, -1, +1)`, one `f64` per fired
|
||||
cycle (`None` until warmed up). The **sim-optimal broker** is realized as
|
||||
`SimBroker { pip_size }`: a two-input node (exposure, price) that accumulates
|
||||
`prev_exposure · (price − prev_price) / pip_size` and emits cumulative pip equity
|
||||
— the exposure held *into* a cycle (decided at t-1) earns that cycle's return, so
|
||||
the integration is causal (C2), and `pip_size` is held reference metadata
|
||||
(C7/C15), never streamed. An end-to-end harness (SMA-cross → `Exposure` →
|
||||
`SimBroker` → recording sink) produces a recorded pip-equity curve, bit-identical
|
||||
across runs (C1). The **position-management half** — deriving the position-event
|
||||
table (buy/sell/close, `position_id`, partial closes) from the exposure history,
|
||||
and the realistic broker nodes that consume it — is deliberately **not** built
|
||||
this cycle; it remains the decoupled, derived, deferred layer described above.
|
||||
**Realization (per-instrument pip channel, 2026-06, #22).** `SimBroker`'s
|
||||
`pip_size` is now sourced **per instrument**, not from one global literal. The
|
||||
divisor is resolved from the recorded geometry sidecar (`instrument_geometry`, over
|
||||
data-server's `symbol_meta`), at the ingestion/source edge where the symbol still
|
||||
exists (never `Aura.toml`, never `Ctx`); the engine stays domain-free (no instrument
|
||||
identity reaches the hot path). (The original cycle-0022 form was a Rust-authored
|
||||
vetted floor `InstrumentSpec { pip_size }` + `instrument_spec(symbol)`; cycle 0074
|
||||
removed that floor — see the C15 note below — once the sidecar geometry made it
|
||||
redundant for the real path. Refuse-don't-guess on absent geometry.)
|
||||
**Realization (position-event schema, cycle 0063, #114).** The position-management
|
||||
half's **schema** now landed (its derivation and the brokers remain deferred): a
|
||||
closed `PositionAction { Buy, Sell, Close }` enum + the `PositionEvent` row
|
||||
(`event_ts`, `action`, `position_id`, `instrument_id`, unsigned `volume`; no
|
||||
`open_ts`; direction *is* the action) live beside `RunMetrics` as a
|
||||
post-run value type (not a per-`eval` node — C8) — both in the `aura-analysis` crate
|
||||
since cycle 0079 (#136); originally `aura-engine`, see the C16 cycle-0079 note. `action` serde-encodes as a bare
|
||||
`i64` (Buy=0, Sell=1, Close=2), the C7 scalar column form the ledger's table spec
|
||||
requires, with an out-of-range code rejected on read. Still deferred:
|
||||
`derive_position_events` (the first-difference reduction over the exposure history,
|
||||
#115) and the realistic broker nodes that consume the table (#116). The table stays
|
||||
broker-independent.
|
||||
The honesty rule is **refuse, don't guess**: a real-data run for a symbol with no
|
||||
vetted spec is a usage error (`exit 2`), so cross-asset pip equity is comparable by
|
||||
construction rather than by researcher discipline. Threaded through the CLI
|
||||
`aura run --real` path; the manifest broker label records the looked-up pip. The
|
||||
runnable GER40 examples (already at the correct `1.0`) are routed through the same
|
||||
lookup separately (#98).
|
||||
[HISTORY — its R spine survives into the 2026-06-28 contract; its Stage-2 currency /
|
||||
realistic-broker / register / flat-1R-vs-compounding portions are SUPERSEDED by that
|
||||
reframe.] The contract was reframed from **exposure** (a signed fractional
|
||||
position) to an **unsized bias** plus **R** as the signal-quality unit. The
|
||||
realization notes below describe the **pre-reframe code** (`Exposure`, `SimBroker`,
|
||||
pip-equity), retained as history and as the **ancestor** of the current chain:
|
||||
`Exposure { scale }` is the ancestor of the unsized `bias` node, and `SimBroker`'s
|
||||
pip integral is the ancestor of the R-evaluator (which additionally requires a
|
||||
stop, since R is stop-defined). The `exposure → bias` rename, the RiskExecutor /
|
||||
Sizer / Veto nodes, and the R-evaluator **landed in cycle 0065**; the
|
||||
position-event schema (0063, #114) survives as the audit layer. Industry grounding
|
||||
for this reframe: LEAN / nautilus_trader / backtrader / QSTrader / vectorbt /
|
||||
zipline (see #117 decision log).
|
||||
|
||||
**Realization (cycle 0007).** [HISTORY — pre-reframe; `SimBroker` is now legacy per
|
||||
the 2026-06-28 reframe.] The signal-quality half was realized at the substrate as
|
||||
two `aura-std` nodes on the unchanged engine (the engine stays domain-free — it
|
||||
routes only `f64` records). The **exposure stream** was realized as `Exposure {
|
||||
scale }`: `clamp(signal / scale, -1, +1)`, one `f64` per fired cycle. The
|
||||
**sim-optimal broker** was realized as `SimBroker { pip_size }`: a two-input node
|
||||
(exposure, price) accumulating `prev_exposure · (price − prev_price) / pip_size`
|
||||
and emitting cumulative pip equity — the exposure held *into* a cycle (decided at
|
||||
t-1) earns that cycle's return (causal, C2); `pip_size` is held reference metadata
|
||||
(C7/C15). An end-to-end harness (SMA-cross → `Exposure` → `SimBroker` → recording
|
||||
sink) produced a recorded pip-equity curve, bit-identical across runs (C1).
|
||||
|
||||
**Realization (per-instrument pip channel, 2026-06, #22).** [HISTORY — pertains to
|
||||
the legacy `SimBroker` pip channel.] `SimBroker`'s `pip_size` is sourced **per
|
||||
instrument** from the recorded geometry sidecar (`instrument_geometry`, over
|
||||
data-server's `symbol_meta`), at the ingestion / source edge (never `Aura.toml`,
|
||||
never `Ctx`); the engine stays domain-free. (The original cycle-0022 form was a
|
||||
Rust-authored vetted floor `InstrumentSpec { pip_size }` + `instrument_spec(symbol)`;
|
||||
cycle 0074 removed that floor — see the C15 note — once the sidecar geometry made it
|
||||
redundant for the real path. Refuse-don't-guess on absent geometry.) The honesty
|
||||
rule is **refuse, don't guess**: a real-data run for a symbol with no vetted spec
|
||||
is a usage error (`exit 2`). Threaded through the CLI `aura run --real` path; the
|
||||
manifest broker label records the looked-up pip.
|
||||
|
||||
**Realization (position-event schema, cycle 0063, #114).** [Survives as the
|
||||
**deploy / reconciliation** schema per the 2026-06-28 reframe — no longer a
|
||||
broker-input research artifact.] A closed `PositionAction { Buy, Sell, Close }`
|
||||
enum + the `PositionEvent` row (`event_ts`, `action`, `position_id`,
|
||||
`instrument_id`, unsigned `volume`; no `open_ts`; direction *is* the action) live
|
||||
beside `RunMetrics` as a post-run value type (not a per-`eval` node — C8) — both in
|
||||
the `aura-analysis` crate since cycle 0079 (#136); originally `aura-engine`, see the
|
||||
C16 cycle-0079 note. `action` serde-encodes as a bare `i64` (Buy=0, Sell=1,
|
||||
Close=2), the C7 scalar column form, with an out-of-range code rejected on read.
|
||||
The table stays broker-independent.
|
||||
|
||||
**Realization (cycle 0065 — Stage-1 R signal quality, #119/#126/#127/#128/#129).**
|
||||
The Stage-1 chain above is now built. `Exposure → Bias` renames the unsized strategy
|
||||
output (the node + its output field); the persisted `exposure_sign_flips` metric key
|
||||
(kept as a serde alias) and the `SimBroker` `exposure` input slot + the on-disk
|
||||
`exposure` **tap** label (a deliberate permanent keep, #117) retain the old name. The
|
||||
strategy-output **param namespace** — the `Bias` instance, its `bias.scale` knob, and
|
||||
the `bias_scale` manifest param — was the deferred rename tail, **completed in #134**
|
||||
(the knob path is a runtime address, so no on-disk back-compat break). A **stop-rule**
|
||||
defines 1R: `FixedStop` (a triggered-constant primitive) and a `vol_stop(length, k)`
|
||||
**composition** `k·√EMA(Δ²)` (the originally-fused node was corrected to a composition
|
||||
of new `Mul`/`Sqrt` primitives — a node is a primitive only if not DAG-expressible
|
||||
from others). **`PositionManagement`** (`aura-std`) is the stateful heart: it latches
|
||||
the entry-cycle stop distance as the immutable R-denominator, marks against the
|
||||
[The R spine here is the live model; the *Stage-2 deferral* clause at its end is
|
||||
SUPERSEDED by the 2026-06-28 reframe — there is no Stage-2 currency / compounding
|
||||
layer; cost is now the cost-model graph and money lives only at the live deploy
|
||||
edge. "Stage-1" reads as a historical identifier, not a gate half.] `Exposure →
|
||||
Bias` renamed the unsized strategy output (node + output field); the persisted
|
||||
`exposure_sign_flips` metric key (serde alias), the `SimBroker` `exposure` input
|
||||
slot, and the on-disk `exposure` **tap** label retain the old name. The
|
||||
strategy-output **param namespace** (the `Bias` instance, its `bias.scale` knob,
|
||||
the `bias_scale` manifest param) was completed in #134. A **stop-rule** defines 1R:
|
||||
`FixedStop` (a triggered-constant primitive) and a `vol_stop(length, k)`
|
||||
**composition** `k·√EMA(Δ²)` (a composition of `Mul` / `Sqrt` primitives).
|
||||
**`PositionManagement`** (`aura-std`) is the stateful heart: it latches the
|
||||
entry-cycle stop distance as the immutable R-denominator, marks against the
|
||||
one-cycle-lagged fill (no look-ahead, C2), and emits a **dense 14-column per-cycle
|
||||
R-record** (one row per eval, C8; the trade ledger is the `closed_this_cycle` subset,
|
||||
the R-equity is `cum_realized_r + unrealized_r`, the window-end open trade is the last
|
||||
`open=true` row). The **`Sizer`** (`size = risk_budget / stop_distance`, flat-1R) is
|
||||
the feed-forward sizing seam, and **R is size-invariant** — scaling `risk_budget`
|
||||
leaves every `realized_r` unchanged (pinned by a RED test). **`summarize_r`** is a
|
||||
R-record** (one row per eval, C8; the trade ledger is the `closed_this_cycle`
|
||||
subset, the R-equity is `cum_realized_r + unrealized_r`). The **`Sizer`** (`size =
|
||||
risk_budget / stop_distance`, flat-1R) was the feed-forward sizing seam, and **R is
|
||||
size-invariant** — scaling `risk_budget` leaves every `realized_r` unchanged
|
||||
(pinned by a RED test); per the 2026-06-28 reframe the Sizer and `size` / `volume`
|
||||
are removed from research (size is a deploy concept). **`summarize_r`** is a
|
||||
post-run fold (sibling of `summarize`, **not** an in-graph node) → `RMetrics` (E[R],
|
||||
SQN, win-rate, profit-factor, max-R-drawdown, conviction terciles, net-of-cost). The
|
||||
**RiskExecutor** ships as a public `aura-composites` composite-builder
|
||||
(`risk_executor(StopRule, risk_budget)` — bias+price roles embedding
|
||||
`stop-rule → Sizer → PositionManagement`, beside `vol_stop`) with a `StopRule{Fixed,Vol}`
|
||||
**structural axis** (C11); the **Veto** stays a documented seam, not a runtime node (a
|
||||
pass-through identity is exactly what C19/C23 DCE deletes). The layer is **operable
|
||||
from the CLI**: `aura run --harness <sma|macd|stage1-r>` — a **compile-time** selector
|
||||
over Rust-authored harnesses (C9/C17: the CLI *runs*, it does not wire) — folds
|
||||
`summarize_r` into `RunMetrics.r` (additive; `skip_serializing_if` keeps pip-only and
|
||||
legacy `runs.jsonl` JSON byte-unchanged), the `stage1-r` harness fanning one bias into
|
||||
both `SimBroker` (pip) and the RiskExecutor (R) for an honest dual yardstick; an
|
||||
`r_equity` tap charts the by-trade R-equity through the existing `aura chart --tap`.
|
||||
The R-record redundancy `debug_assert` uses a **scale-robust relative tolerance** (an
|
||||
absolute `1e-9` panicked on tiny-pip FX where the `entry − stop` denominator
|
||||
reconstruction loses precision; the stored `realized_r` is exact). Composites live in
|
||||
the dedicated `aura-composites` crate (the engine's convenience layer over the standard
|
||||
nodes — extracted to its own crate after this note), so `aura-engine`'s runtime
|
||||
dependency stays `aura-core`-only and `aura-std` is now an `aura-engine`
|
||||
`[dev-dependencies]` — the graph stays acyclic. **Stage 2** (currency P&L, fixed-fractional compounding through the z⁻¹
|
||||
fill-edge register, realistic brokers consuming the position-event table) remains the
|
||||
deferred downstream layer, entered only after `E[R] > 0`.
|
||||
**Realization (cycle 0066).** SQN is now the operational single-number objective for
|
||||
ranking a Stage-1 sweep family by signal quality — C12 **axis-2 (argmax-metric)** over
|
||||
the C18 family store. `metric_cmp` (`aura-registry`) learns the higher-is-better R
|
||||
metrics `sqn`, `expectancy_r`, `net_expectancy_r`; a member with no `r` block sorts
|
||||
last (`NEG_INFINITY`), so a pip-only family ranked by an R metric degrades to ordinal
|
||||
order rather than erroring. `aura sweep --strategy stage1-r` produces the rankable R
|
||||
family, each member folding `summarize_r` into `RunMetrics.r`. The default grid varies
|
||||
**only the signal** (`fast`/`slow` SMA lengths), holding the stop and sizing fixed:
|
||||
`risk_budget` is R-invariant and `bias.scale` is sign-only under flat-1R (both
|
||||
degenerate axes for the ranked metric), and — load-bearing — the **stop defines 1R**,
|
||||
so varying it would change what R *means* per member and break cross-member SQN
|
||||
comparability (the motivation for the deferred n-normalized SQN100, #130). Each swept
|
||||
member's manifest records the fixed R-defining params (stop, scale) beside the floated
|
||||
knobs, so a member is reproducible from its own manifest (C18) and the constant-stop
|
||||
basis of comparability is auditable from the family record. Per-member `--trace` for
|
||||
the stage1-r sweep is refused explicitly (not a silent no-op) pending #135.
|
||||
SQN, win-rate, profit-factor, max-R-drawdown, conviction terciles, net-of-cost);
|
||||
per the 2026-06-28 reframe it folds the cost-model's net-R rather than recomputing
|
||||
a scalar cost. The **RiskExecutor** ships as a public `aura-composites`
|
||||
composite-builder (`risk_executor(StopRule, risk_budget)`) with a
|
||||
`StopRule{Fixed,Vol}` **structural axis** (C20); per the 2026-06-28 reframe its
|
||||
Sizer interior and `risk_budget` arg are dropped / vestigial, the **Veto** stays a
|
||||
documented seam, not a runtime node. The layer is operable from the CLI: `aura run
|
||||
--harness <sma|macd|stage1-r>` — a compile-time selector over Rust-authored
|
||||
harnesses (C9/C17) — folds `summarize_r` into `RunMetrics.r`, the `stage1-r`
|
||||
harness fanning one bias into both `SimBroker` (legacy pip) and the RiskExecutor
|
||||
(R); an `r_equity` tap charts the by-trade R-equity. Composites live in the
|
||||
dedicated `aura-composites` crate, so `aura-engine`'s runtime dependency stays
|
||||
`aura-core`-only and `aura-std` is an `aura-engine` `[dev-dependencies]` (the graph
|
||||
stays acyclic).
|
||||
|
||||
**Realization (cycle 0067, #130 + #135).** The two follow-ups the 0066 note flagged
|
||||
are now shipped. **#130 (SQN100):** `RMetrics` gains `sqn_normalized =
|
||||
(mean_R/stdev_R)·√(min(n, 100))` — the n-normalized "SQN score" (van Tharp's cap,
|
||||
`SQN_CAP = 100`), turnover-robust where the raw `sqn` rewards sheer trade count. It is
|
||||
an **opt-in** rank key (`Metric::SqnNormalized`); the raw `sqn` and the default ranker
|
||||
stay byte-unchanged, and the field carries `#[serde(default)]` (C18 — a pre-0067 `r:`
|
||||
block reads back with `sqn_normalized = 0`). Below the cap (`n ≤ 100`) it equals the raw
|
||||
`sqn` exactly. **#135 (stage1-r `--trace`):** `stage1_r_sweep_family` now persists each
|
||||
member's equity/exposure/r_equity under `runs/traces/<n>/<member_key>/` via the same
|
||||
`persist_traces_r` the single run uses (mirroring `momentum_sweep_family`); the 0066
|
||||
refusal guard is gone, so per-member `--trace` is symmetric across all three sweep
|
||||
strategies and a swept member charts (`chart <n>/<member> --tap r_equity`).
|
||||
**Realization (cycle 0066).** SQN is the operational single-number objective for
|
||||
ranking a Stage-1 sweep family by signal quality — C12 **axis-2 (argmax-metric)**
|
||||
over the C18 family store. `metric_cmp` (`aura-registry`) learns the
|
||||
higher-is-better R metrics `sqn`, `expectancy_r`, `net_expectancy_r`; a member
|
||||
with no `r` block sorts last (`NEG_INFINITY`). `aura sweep --strategy stage1-r`
|
||||
produces the rankable R family, each member folding `summarize_r` into
|
||||
`RunMetrics.r`. The default grid varies **only the signal** (`fast` / `slow` SMA
|
||||
lengths), holding the stop and sizing fixed: `risk_budget` is R-invariant and
|
||||
`bias.scale` is sign-only under flat-1R, and — load-bearing — the **stop defines
|
||||
1R**, so varying it would change what R *means* per member and break cross-member
|
||||
SQN comparability. Each swept member's manifest records the fixed R-defining params
|
||||
beside the floated knobs (reproducible from its own manifest, C18).
|
||||
|
||||
**Realization (cycle 0068, #115 — position-event derive).** The Stage-2 audit layer's
|
||||
*derivation* now landed (the schema was 0063, #114; the realistic brokers consuming it
|
||||
remain #116). `derive_position_events(record, instrument_id) -> Vec<PositionEvent>`
|
||||
(in `aura-analysis` since cycle 0079, #136; originally `aura-engine`, beside
|
||||
`summarize_r`) is the **first difference of the executed book**:
|
||||
a pure post-run reduction over the `PositionManagement` dense record (read positionally
|
||||
as type-erased `Scalar`s, C7 SoA — no in-graph node, so the hot path stays domain-free,
|
||||
C14), emitting a `Buy`/`Sell` at each open and a `Close` at each exit, a reversal (or a
|
||||
stop-then-same-cycle reopen) emitting **Close then the opposite open at one `event_ts`**
|
||||
(close first — the C8 ">1 event per instant" case that forces this to be a *derived*
|
||||
table, not a per-`eval` output). The close sizes the **actual book** (the closed
|
||||
position's stored volume), never an exposure delta — the post-reframe replacement for the
|
||||
rolled-back 0064 exposure-integral derive (#117). `instrument_id` is a caller-supplied
|
||||
scalar (`aura-analysis` depends only on `aura-core`, so it never imports an instrument
|
||||
spec from `aura-ingest`).
|
||||
A position open at window end emits its open with **no synthetic `Close`** (the table
|
||||
records actual executed events; `summarize_r`'s force-close is for the R metric only).
|
||||
The `r_col` ⟷ PM-record lockstep is now guard-pinned for `direction` too. **Still #116:**
|
||||
fixed-fractional / currency / equity-feedback sizing (the equity→Sizer z⁻¹ fill-edge
|
||||
register) and the realistic brokers that consume the table.
|
||||
**Realization (cycle 0067, #130 + #135).** **#130 (SQN100):** `RMetrics` gains
|
||||
`sqn_normalized = (mean_R/stdev_R)·√(min(n, 100))` — the n-normalized "SQN score"
|
||||
(`SQN_CAP = 100`), turnover-robust where raw `sqn` rewards trade count. It is an
|
||||
**opt-in** rank key (`Metric::SqnNormalized`); raw `sqn` and the default ranker
|
||||
stay byte-unchanged, and the field carries `#[serde(default)]` (C18). Below the cap
|
||||
(`n ≤ 100`) it equals raw `sqn` exactly. **#135 (stage1-r `--trace`):**
|
||||
`stage1_r_sweep_family` persists each member's equity / exposure / r_equity under
|
||||
`runs/traces/<n>/<member_key>/` via the same `persist_traces_r` the single run uses;
|
||||
per-member `--trace` is symmetric across all three sweep strategies.
|
||||
|
||||
**Realization (cycle 0068, #115 — position-event derive).** [The derivation
|
||||
survives as the **deploy / reconciliation** layer per the 2026-06-28 reframe — its
|
||||
"realistic brokers consuming it" goal is retired; money is the live-edge concern.]
|
||||
`derive_position_events(record, instrument_id) -> Vec<PositionEvent>` (in
|
||||
`aura-analysis` since cycle 0079, #136; originally `aura-engine`, beside
|
||||
`summarize_r`) is the **first difference of the executed book**: a pure post-run
|
||||
reduction over the `PositionManagement` dense record (read positionally as
|
||||
type-erased `Scalar`s, C7 SoA — no in-graph node, so the hot path stays
|
||||
domain-free, C14), emitting a `Buy` / `Sell` at each open and a `Close` at each
|
||||
exit, a reversal (or stop-then-same-cycle reopen) emitting **Close then the
|
||||
opposite open at one `event_ts`** (close first — the C8 ">1 event per instant" case
|
||||
that forces a *derived* table, not a per-`eval` output). The close sizes the
|
||||
**actual book** (the closed position's stored volume), never an exposure delta.
|
||||
`instrument_id` is a caller-supplied scalar (`aura-analysis` depends only on
|
||||
`aura-core`). A position open at window end emits its open with **no synthetic
|
||||
`Close`** (the table records actual executed events; `summarize_r`'s force-close is
|
||||
for the R metric only). The `r_col` ⟷ PM-record lockstep is guard-pinned for
|
||||
`direction` too.
|
||||
|
||||
### C11 — Generalized sources; record-then-replay determinism boundary
|
||||
**Guarantee.** A source is anything that produces timestamped scalar streams —
|
||||
|
||||
Reference in New Issue
Block a user