8688a60ded
The single-file ledger had grown to 2968 lines / ~42k tokens, mixing current design law with accreted history: 59 cycle-stamped realization blocks, 18 [HISTORY] passages, 22 supersession markers, and the C10 / C22 / C24 reframe sagas layered several supersessions deep. A code-grounding audit (31 agents, adversarially verified) confirmed 11 defects stated as current truth: stale crate homes from the C28 #288 roster split (cost nodes, PositionManagement, PositionEvent, Session), the renamed InputSpec->PortSpec, the pre-#241 project model in C16 and the open-threads section, a stale HarnessKind retirement deferral in C24, and three C28-internal inconsistencies. New shape, per the ailang precedent: - INDEX.md stays the sole addressable entry point: foundation, external components, a C-id-keyed contract map (one line per contract), and only the genuinely open architectural threads. - contracts/cNN-<slug>.md carries each contract's current truth only: Guarantee / Forbids / Why with ratified refinements integrated, plus a code-anchored Current state. All confirmed defects are fixed here; crate anchors were re-verified against the tree. - contracts/cNN-<slug>.history.md (18 sidecars) and INDEX.history.md preserve every superseded block verbatim, stamps and issue refs intact, under a frozen-record banner. Nothing was deleted: superseded design intent remains an addressable working-tree artifact, off the per-cycle audit walk. - Ledger discipline is now stated in INDEX.md: live files are edited in place at cycle close, superseded text moves verbatim to the sidecar, and a supersession marker in a live file is itself an audit finding. Every contract file was verified against its old text by an independent zero-loss pass (statement-by-statement) plus a code-accuracy spot check; C-ids and contract titles are unchanged, so existing C-id citations in code, tests, and issues resolve as before.
362 lines
26 KiB
Markdown
362 lines
26 KiB
Markdown
# 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
|
||
most one record per `eval`), so a strategy emits one **signed, bounded bias**
|
||
`f64 ∈ [-1, +1]` per cycle (per instrument) — the **sign is direction, the
|
||
magnitude is conviction**, and conviction is optional (a bare ±1 / 0 is the modal
|
||
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; 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 (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 position-management either **drops
|
||
its `size` input port**, 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 — 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).
|
||
|
||
**Cost is a COST MODEL — a composable C9 graph of cost nodes, in R, that
|
||
approximates (never claims) realism.** The realistic broker is *retired*: 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-strategy`,
|
||
the cost-graph composite-builder (`cost_graph`) in `aura-composites` (C16), 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 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.
|
||
|
||
**Co-temporality contract (load-bearing, generalizes to all cost factors).**
|
||
Because `summarize_r` positionally joins `cost[i] ↔ record[i]`, a cost node is
|
||
gated **only by the position-management trade-geometry**; any not-yet-warm state
|
||
input (a vol proxy warms later than the executor) contributes **0 cost** that
|
||
cycle rather than withholding — the node still emits its row, so the cost stream
|
||
stays co-temporal 1:1 with the executor's R-record. Co-temporality is therefore
|
||
structural and warm-up-independent, preserves the C18 golden, and is honest (no
|
||
slippage estimate yet → no charge). A constant-per-trade factor satisfies it
|
||
trivially; only state-dependent factors need the missing-factor → 0 rule.
|
||
|
||
**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 there is no `z⁻¹` fill-edge register and no
|
||
flat-1R-vs-compounding structural axis in the research loop (with no in-loop
|
||
feedback there is nothing for a register to cut; 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**, distinct
|
||
from the removed money-Sizer; it is tested via the `conviction_at_entry` record
|
||
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 reliable (`belastbare`) 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 legacy pip-equity, unsized-exposure node) is **redundant as a
|
||
quality measure** with the net-R cost model in place (R displaces pips). It is retained as a **legacy / simple optional pip yardstick**
|
||
(still wired alongside the R executor in the r-family member for an honest dual
|
||
readout), **not part of the new model and not to be expanded**.
|
||
|
||
**Forbids.** Putting **sizing or the stop in the strategy** (bias is unsized; the
|
||
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 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
|
||
reliable 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. Industry grounding for the bias/R spine: LEAN /
|
||
nautilus_trader / backtrader / QSTrader / vectorbt / zipline (#117 decision log).
|
||
|
||
## Current state
|
||
|
||
**Bias and executor.** The unsized strategy output is the `Bias` node
|
||
(`aura-strategy/src/bias.rs`). The per-symbol **RiskExecutor** is the
|
||
`risk_executor(StopRule, risk_budget)` composite-builder
|
||
(`aura-composites/src/lib.rs`) with a `StopRule{Fixed, Vol, VolTf}` structural
|
||
axis (C20); `risk_executor_vol_open` is the gridding sibling with the two vol-stop
|
||
knobs left open as sweep axes. Stop rules: `FixedStop` (a triggered constant) and
|
||
the `vol_stop(length, k)` composition `k·√EMA(Δ²)` built from `Mul`/`Sqrt`
|
||
primitives; `VolTfStop` for a resampled-timeframe vol stop (`FixedStop` in
|
||
`aura-strategy/src/stop_rule.rs`, `vol_stop` composite in `aura-composites`). The
|
||
**Veto** is a documented seam, not a runtime node.
|
||
|
||
**The Sizer is a vestige, not yet removed.** The ratified design (`#116`) has *no*
|
||
Sizer in research, but the composite still physically wires
|
||
`stop → Sizer → PositionManagement`, with the `Sizer` bound to a constant
|
||
`risk_budget` (`risk_budget` is a formal argument; the runner calls
|
||
`risk_executor(stop, 1.0)` — `aura-runner/src/member.rs`). This is the
|
||
constant-unit-driven-`size`-port resolution of C8 wiring totality (not the
|
||
drop-the-port resolution): `risk_budget` is a constant, never equity-fed, so no
|
||
`equity → size` edge exists and the pure-feed-forward invariant holds; under R
|
||
size-invariance the `Sizer`'s `size = 1.0 / stop_distance` carries no research
|
||
information. Physically deleting the `Sizer` node and its `risk_budget` argument is
|
||
outstanding cleanup toward the ratified shape.
|
||
|
||
**Position management and R metrics.** `PositionManagement`
|
||
(`aura-backtest/src/position_management.rs`) 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 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`). `summarize_r`
|
||
(`aura-backtest/src/metrics.rs`) is a post-run fold (sibling of `summarize`, **not**
|
||
an in-graph node) → `RMetrics` (E[R], SQN, `sqn_normalized`, win-rate,
|
||
profit-factor, max-R-drawdown, `conviction_terciles_r`, `net_expectancy_r`). It
|
||
folds a co-temporal cost stream (positional 1:1 join) into `net_expectancy_r` —
|
||
one home for cost, byte-identical to the cost = 0 baseline on an empty stream.
|
||
`sqn_normalized = (mean_R / stdev_R)·√(min(n, 100))` (`SQN_CAP = 100`) is an opt-in
|
||
turnover-robust rank key; below the cap it equals raw `sqn` exactly. SQN is the
|
||
operational single-number ranking objective; a sweep's default grid varies **only
|
||
the signal**, holding the stop fixed — the **stop defines 1R**, so varying it
|
||
across members would change what R *means* per member and break cross-member SQN
|
||
comparability.
|
||
|
||
**Cost model.** `aura-strategy/src/cost.rs` owns the abstraction: the 3-field cost
|
||
triple `{cost_in_r, cum_cost_in_r, open_cost_in_r}` is one source of truth
|
||
(`COST_FIELD_NAMES` / `COST_WIDTH = 3`, mirroring the position-management record),
|
||
prefixed by the 4-wide `GEOMETRY_WIDTH` geometry inputs. The `CostNode` **factor
|
||
trait** carries a node's only per-node difference — the price-unit cost numerator
|
||
(`cost_numerator`), plus `extra_inputs`, `label`, and a defaulted `charge_mode()`;
|
||
everything else is the generic `CostRunner<F: CostNode>` **adapter** (a plain
|
||
downstream `Node`, C9; no runtime sub-object), which writes the co-temporality
|
||
skeleton (geometry-only gating, `numerator / latched` R-normalization, the
|
||
closed/open charge, the running `cum`, the 3-field emit) **once**. `ChargeMode
|
||
{AtClose, PerHeldCycle}` selects timing per factor (a commission is intrinsically
|
||
at-close, a carry per-held-cycle); the single `CostRunner` handles both arms, the
|
||
`PerHeldCycle` arm accruing `per` into a per-position `acc` each held cycle and
|
||
dumping it into `cum` at close. Three shipped factors, all `aura-strategy`:
|
||
`ConstantCost` (a labelled stress-parameter, `cost_per_trade / |entry − stop|`),
|
||
`VolSlippageCost` (`slip_vol_mult · vol / |entry − stop|`, reading an independent
|
||
short-horizon realized-range vol proxy distinct from the stop's own vol), and
|
||
`CarryCost` (a `ConstantCost` twin with `charge_mode() = PerHeldCycle`, the flat
|
||
base of the accrual family). `CostSum` (`aura-strategy/src/cost_sum.rs`) is the
|
||
cost-graph **output node**, summing `N` nodes' 3-field records per-field into one
|
||
aggregate (`n = 1` is the identity), so the seam consumes a single cost stream
|
||
regardless of node count. `cost_port` / `intern_port` (`aura-strategy/src/cost.rs`)
|
||
intern runtime port names process-globally (the `COL_PORTS` production pattern,
|
||
#152), reused across per-member rebuilds. `cost_graph(Vec<PrimitiveBuilder>)`
|
||
(`aura-composites/src/lib.rs`) is the authoring primitive: it fans the 4 geometry
|
||
inputs to `N` cost nodes, surfaces each node's extras
|
||
(`schema().inputs[GEOMETRY_WIDTH..]`) as `cost[k].<port>` roles, sums through
|
||
`CostSum`, and exposes the 3-field aggregate at arbitrary arity. The headline sink
|
||
is **`net_r_equity`** = `LinComb(4)[cum_realized_r, unrealized_r, −cum_cost_in_r,
|
||
−open_cost_in_r]` → Recorder (C8/C18), a sibling of `r_equity`, emitted only when a
|
||
cost is authored; a per-held-cycle factor bleeds continuously over the hold because
|
||
the bleed lives in `open_cost_in_r`, which this tap already subtracts (Approach B,
|
||
no `summarize_r` fold change).
|
||
|
||
**Cost on the campaign / sweep path (net is the default).** Cost is authored as
|
||
the campaign document's additive `cost: Vec<CostSpec>` block
|
||
(`aura-research/src/lib.rs`, `CampaignDoc`) — a closed, externally-tagged
|
||
vocabulary over the three shipped nodes (`constant` / `vol_slippage` / `carry`,
|
||
field names = the builders' `ParamSpec` names), mirroring the `risk` block (serde
|
||
`default` + skip-if-empty: cost-less docs hash byte-identically, C18). An absent
|
||
block is the explicit zero-cost model and `summarize_r`'s net family equals gross
|
||
under it — every result is net, no second gross-labelled result kind.
|
||
`cost_nodes_for` (beside `stop_rule_for_regime`, `aura-runner/src/translate.rs`) is
|
||
the one doc→builder binding; `wrap_r` (`aura-runner/src/member.rs`) carries the
|
||
optional cost leg (cost_graph off the executor's four geometry outputs, the vol
|
||
proxy in production, a gated cost recorder in reduce mode as the `summarize_r` join
|
||
input, the `LinComb(4)` `net_r_equity` curve in trace mode). Both re-run sides
|
||
re-derive the model (`cost_specs_from_params`), so the C1 drift alarm compares like
|
||
with like; costed families reproduce bit-identically (incl. `Carry`).
|
||
`TapChannel::Net` (`aura-runner/src/runner.rs`) routes `net_r_equity` to persisted
|
||
curves; a cost-less doc requesting it keeps a remedy-naming skip notice. There are
|
||
no `--cost-*` run-path flags (removed #221/#234); cost travels in the document.
|
||
|
||
**Risk regime as a structural campaign axis.** The `StopRule{Fixed, Vol}` axis is
|
||
realized at the campaign-document level as `CampaignDoc.risk: [RiskRegime]`
|
||
(`aura-research`, variants `Vol{length, k}` and `VolTf{period_minutes, length, k}`
|
||
(#262), the fixed-stop rule additive when needed) — a kept-separate matrix axis,
|
||
peer of instruments and windows. The executor keys the nominee map by `(strategy,
|
||
window, regime)`, so `generalize` aggregates *within* a regime, never across.
|
||
Regimes are **compared** at presentation, never argmax-**selected** across (a
|
||
cross-regime E[R] argmax would compare R-multiples in different R units). Each
|
||
member manifest stamps its resolved stop (default included). Two default
|
||
representations coexist by design (#217): a dissolved sweep binds no regime
|
||
(`risk: []`, late-resolved per member by `stop_rule_for_regime`), while
|
||
`walkforward`/`mc`/`generalize` bind the default regime eagerly
|
||
(`risk: [Vol{length:3, k:2.0}]`) — same R behaviour, deliberately different
|
||
document content-ids. Deferred: regime-aware **trace** persistence — the trace
|
||
re-run and cell-key dir naming still assume the default stop, since
|
||
`CellRealization` carries no regime (#212).
|
||
|
||
**Legacy pip yardstick.** `SimBroker` (`aura-backtest/src/sim_broker.rs`, node kind
|
||
`simbroker`) is a legacy pip-equity node, retained as an optional pip yardstick
|
||
and still wired alongside the R executor in the r-family member for an honest dual
|
||
pip/R readout; not part of the cost model, not to be expanded.
|
||
|
||
**Position-event table (deploy / reconciliation).** 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) and
|
||
`derive_position_events(record, instrument_id) -> Vec<PositionEvent>` live in
|
||
`aura-backtest/src/metrics.rs` as post-run value types (not per-`eval` nodes, C8).
|
||
`action` serde-encodes as a bare `i64` (Buy=0, Sell=1, Close=2), the C7 scalar
|
||
column form, out-of-range rejected on read. `derive_position_events` is the **first
|
||
difference of the executed book** — a pure reduction over the position-management
|
||
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) emits **Close
|
||
then the opposite open at one `event_ts`** (the C8 ">1 event per instant" case that
|
||
forces a *derived* table). The close sizes the **actual book** (the closed
|
||
position's stored volume), never an exposure delta. 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 table stays
|
||
broker-independent.
|
||
|
||
**CLI.** The run/sweep surface is blueprint-driven — `aura <verb> <blueprint.json>`
|
||
over `examples/r_*.json` / `crates/aura-cli/tests/fixtures/`; the built-in
|
||
`--harness` / `--strategy` selectors were retired with the demos → blueprint-data
|
||
cut (#159). The strategy identifier family is the **r-family** — `r-sma` /
|
||
`r-breakout` / `r-meanrev` (renamed from the Stage-1 family in #174). `metric_cmp`
|
||
(`aura-registry`) ranks the higher-is-better R metrics `sqn`, `expectancy_r`,
|
||
`net_expectancy_r`, `sqn_normalized`; a member with no `r` block sorts last.
|
||
|
||
**Deferred work.** Physical removal of the vestigial `Sizer` and its `risk_budget`
|
||
argument (ratified shape, #116). Data-grounded cost factors beyond the shipped
|
||
stress parameters: a notional-based carry (`price × rate`, reads the price tap) and
|
||
the calendar-aware **overnight swap** proper (rollover-boundary timing, 3× Wednesday,
|
||
long/short asymmetry — deploy-edge realism) (#148). The **conviction-weighting**
|
||
R-aggregation axis (flat vs `|bias|·realized_r`), tested via `conviction_at_entry` /
|
||
`conviction_terciles_r` (#148). Regime-aware trace persistence (#212). Money, a
|
||
real broker, and cTrader Open API remain the separate live / deploy-edge concern
|
||
(C11 record-then-replay, C13 frozen deploy).
|
||
|
||
## See also
|
||
- [C1](c01-determinism.md) — determinism / bit-identity (costed families reproduce byte-for-byte)
|
||
- [C2](c02-causality.md) — no look-ahead (one-cycle-lagged fill; the co-temporality 0-cost warm-up rule)
|
||
- [C8](c08-node-contract.md) — ≤1 record per `eval`; sinks are the only displayable surface
|
||
- [C9](c09-fractal-composition.md) — cost nodes are ordinary downstream nodes; the cost model is a C9 graph
|
||
- [C11](c11-sources-record-replay.md) — record-then-replay; the live broker as recorded I/O; bit-identity of re-runs
|
||
- [C13](c13-hot-reload-frozen-deploy.md) — the frozen-deploy invariant (deploy = frozen bot + broker connection)
|
||
- [C14](c14-headless-two-faces.md) — the domain-free engine (no cost / broker logic in `aura-engine`)
|
||
- [C15](c15-resampling-sessions.md) — the `instrument_geometry` sidecar (pip value / stop-distance-in-currency)
|
||
- [C16](c16-engine-project-split.md) — the composites layer (`cost_graph`, `risk_executor`)
|
||
- [C18](c18-registry.md) — the registry / golden (byte-identity, content-addressed docs)
|
||
- [C20](c20-strategy-harness.md) — structural axes (`StopRule`, the risk regime)
|
||
- [C23](c23-graph-compilation.md) — names non-load-bearing; DCE of the absent Veto
|
||
- [C24](c24-blueprint-data.md) / [C25](c25-role-model.md) — experiment intent in the campaign document
|
||
- [C28](c28-stratification.md) — the crate-layer ladder
|
||
|
||
> History: [c10-bias-r-cost.history.md](c10-bias-r-cost.history.md)
|