Support per-instrument cost factors in a campaign #260

Closed
opened 2026-07-13 18:06:14 +02:00 by claude · 3 comments
Collaborator

ConstantCost.cost_per_trade is a price-unit numerator, R-normalized as cost / |entry − stop| (crates/aura-std/src/constant_cost.rs), so one constant cannot be right across instruments of different price scales (GER40 ~2e4 vs EURUSD ~1.1). The campaign cost list applies to every cell, which makes any multi-instrument campaign with a constant cost unit-inconsistent.

Consequences observed in practice: realistic-spread stress testing forces N per-instrument campaign copies that differ only in one number, and "generalize under constant costs" is inexpressible — std::generalize needs ≥ 2 instruments, which the per-instrument split removes.

A per-instrument binding (an instrument-keyed map in the cost entry, or an instrument-relative unit such as pips resolved via the recorded sidecar geometry) would restore the one-campaign form.

`ConstantCost.cost_per_trade` is a price-unit numerator, R-normalized as `cost / |entry − stop|` (`crates/aura-std/src/constant_cost.rs`), so one constant cannot be right across instruments of different price scales (GER40 ~2e4 vs EURUSD ~1.1). The campaign `cost` list applies to every cell, which makes any multi-instrument campaign with a constant cost unit-inconsistent. Consequences observed in practice: realistic-spread stress testing forces N per-instrument campaign copies that differ only in one number, and "generalize under constant costs" is inexpressible — `std::generalize` needs ≥ 2 instruments, which the per-instrument split removes. A per-instrument binding (an instrument-keyed map in the cost entry, or an instrument-relative unit such as pips resolved via the recorded sidecar geometry) would restore the one-campaign form.
claude added the feature label 2026-07-13 18:06:14 +02:00
Author
Collaborator

Design reconciliation (specify)

Spec: per-instrument-cost. The issue leaves two shapes open ("an instrument-keyed map in the cost entry, or an instrument-relative unit such as pips resolved via the recorded sidecar geometry"); resolutions below are derived orchestrator decisions (recorded 2026-07-13).

  • Fork: map vs instrument-relative unit → the instrument-keyed map, at the knob-value level: each cost component's single knob accepts either one number (every cell, today's form) or an instrument-keyed object ("cost_per_trade": { "GER40": 2.0, "EURUSD": 0.00012 }), resolved per cell at member construction.
    Basis: derived — the design ledger explicitly defers the currency→R normalization tier the pip route needs ("#124 collapses to recorded sidecar geometry → refuse in the meantime", the cost model being the named deferred consumer), so the map is the only shape whose semantics are fully decidable today; value-level (rather than a per-instrument cost list) lets one component vary per instrument while another stays scalar, which is the realistic case (spreads differ per instrument, a vol-slippage multiplier does not). The pip/currency-relative unit stays a #124-gated future extension, untouched by this cycle.
  • Fork: validation semantics of the map keys → strict both ways, checked at the intrinsic validate tier (doc-internal: map keys vs the doc's own data.instruments): a map missing a campaign instrument is a fault naming the missing instruments; a key naming no campaign instrument is a fault naming the extras. No silent default, no partial coverage.
    Basis: derived — the issue's whole complaint is silent unit inconsistency; a partial map with an implicit zero or fallback for uncovered instruments would reproduce exactly that silence, and an extra key is almost always a symbol typo the run should refuse up front (the archive-inventory lesson of the Copper abort).
  • Fork: wire/back-compat shape → a CostValue type with custom serde mirroring the in-repo Axis precedent: the scalar case serializes as the bare number, byte-identical to today's f64 field, so every stored scalar-shaped campaign document keeps its content id; only documents opting into the map form mint new ids. No format_version bump (additive widening, per the schema's own risk/cost precedent).
    Basis: derived — content-id parity for unchanged documents is the C18 identity discipline; the Axis custom-(de)serializer is the proven in-repo pattern for a richer Rust type behind a bare wire form.
## Design reconciliation (specify) Spec: per-instrument-cost. The issue leaves two shapes open ("an instrument-keyed map in the cost entry, or an instrument-relative unit such as pips resolved via the recorded sidecar geometry"); resolutions below are derived orchestrator decisions (recorded 2026-07-13). - **Fork: map vs instrument-relative unit** → the instrument-keyed map, at the knob-value level: each cost component's single knob accepts either one number (every cell, today's form) or an instrument-keyed object (`"cost_per_trade": { "GER40": 2.0, "EURUSD": 0.00012 }`), resolved per cell at member construction. Basis: derived — the design ledger explicitly defers the currency→R normalization tier the pip route needs ("#124 collapses to recorded sidecar geometry → refuse in the meantime", the cost model being the named deferred consumer), so the map is the only shape whose semantics are fully decidable today; value-level (rather than a per-instrument cost list) lets one component vary per instrument while another stays scalar, which is the realistic case (spreads differ per instrument, a vol-slippage multiplier does not). The pip/currency-relative unit stays a #124-gated future extension, untouched by this cycle. - **Fork: validation semantics of the map keys** → strict both ways, checked at the intrinsic validate tier (doc-internal: map keys vs the doc's own `data.instruments`): a map missing a campaign instrument is a fault naming the missing instruments; a key naming no campaign instrument is a fault naming the extras. No silent default, no partial coverage. Basis: derived — the issue's whole complaint is silent unit inconsistency; a partial map with an implicit zero or fallback for uncovered instruments would reproduce exactly that silence, and an extra key is almost always a symbol typo the run should refuse up front (the archive-inventory lesson of the Copper abort). - **Fork: wire/back-compat shape** → a `CostValue` type with custom serde mirroring the in-repo `Axis` precedent: the scalar case serializes as the bare number, byte-identical to today's `f64` field, so every stored scalar-shaped campaign document keeps its content id; only documents opting into the map form mint new ids. No `format_version` bump (additive widening, per the schema's own `risk`/`cost` precedent). Basis: derived — content-id parity for unchanged documents is the C18 identity discipline; the `Axis` custom-(de)serializer is the proven in-repo pattern for a richer Rust type behind a bare wire form.
Author
Collaborator

Spec auto-signed (specify Step 6, autonomous run)

Spec: per-instrument-cost (each campaign cost knob takes one number or an instrument-keyed map, resolved per cell; strict intrinsic validation of map keys against the campaign instruments; scalar wire form byte-identical so stored documents keep their content ids).

The signature is the Step-5 grounding-check PASS — an independent fresh-context agent ratified twelve load-bearing assumption clusters against currently-green tests (report in the session transcript). No human signed; the fork decisions live at issues/260#issuecomment-3535. Status: design settled, spec signed — proceeding to implementation planning.

## Spec auto-signed (specify Step 6, autonomous run) Spec: per-instrument-cost (each campaign cost knob takes one number or an instrument-keyed map, resolved per cell; strict intrinsic validation of map keys against the campaign instruments; scalar wire form byte-identical so stored documents keep their content ids). The signature is the Step-5 grounding-check PASS — an independent fresh-context agent ratified twelve load-bearing assumption clusters against currently-green tests (report in the session transcript). No human signed; the fork decisions live at `issues/260#issuecomment-3535`. Status: design settled, spec signed — proceeding to implementation planning.
Author
Collaborator

Plan-defect note for the audit trail (2026-07-14): the first implementation run of the per-instrument-cost plan blocked on its own fixture — the plan's inline validate-test JSON omitted the mandatory presentation field (CampaignDoc.presentation has no serde default), which the loop's spec gate correctly refused as a byte-deviation the implementer was forced into. The plan was corrected (fixture carries the field) and re-run from a clean tree; no code from the blocked attempt was kept.

Plan-defect note for the audit trail (2026-07-14): the first implementation run of the per-instrument-cost plan blocked on its own fixture — the plan's inline validate-test JSON omitted the mandatory `presentation` field (`CampaignDoc.presentation` has no serde default), which the loop's spec gate correctly refused as a byte-deviation the implementer was forced into. The plan was corrected (fixture carries the field) and re-run from a clean tree; no code from the blocked attempt was kept.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/Aura#260