# 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` **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)` (`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].` 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` 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` 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 ` 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)