Files
Aura/docs/design/contracts/c10-bias-r-cost.md
T
claude 8688a60ded docs(ledger): split the design ledger into an INDEX map, per-contract live files, and history sidecars
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.
2026-07-21 16:40:36 +02:00

362 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)