SimBroker pip value is a global constructor literal, not per-asset #22

Closed
opened 2026-06-04 22:58:11 +02:00 by Brummel · 6 comments
Owner

Surfaced while running the walking-skeleton pipeline (SMA-cross -> Exposure -> SimBroker) over the full history of all 28 data-server assets as a sequential consumer loop.

Observation

SimBroker::new(pip_size) takes a single global pip_size (0.0001 in the sample/CLI harness). That value is correct only for 5-decimal FX. Over non-FX assets the synthetic pip equity becomes nonsensical:

asset class example total_pips (full history)
5-dp FX EURUSD, GBPUSD, USDCAD -37.6, -57.2, -28.7 (correctly scaled)
equities (USD) AAPL.US, TSLA.US -8,693,884, -27,203,872 (wrong unit)
crypto / indices BTCUSD, JPN225, US30 -3.2e9, -2.5e9, -9.9e8 (wildly wrong)

Engine mechanics are fine (deterministic, finite, 92.8M bars in ~75s); only the unit is wrong off-FX. A cross-asset signal-quality comparison is meaningless until the pip/point value is per-instrument.

Direction (not prescriptive)

The pip/point value (and tick size) is per-instrument metadata and belongs beside the hot path (C4 non-scalars-as-metadata), keyed by instrument, not a single f64 baked into the broker node at construction. Most relevant to the realistic-broker layer (C10), but the sim-optimal broker already needs it to make multi-asset pip equity comparable.

Found via a throwaway multi-asset probe (since discarded); no code change in tree.

Surfaced while running the walking-skeleton pipeline (SMA-cross -> Exposure -> SimBroker) over the **full history of all 28 data-server assets** as a sequential consumer loop. ## Observation `SimBroker::new(pip_size)` takes a single global `pip_size` (`0.0001` in the sample/CLI harness). That value is correct only for 5-decimal FX. Over non-FX assets the synthetic pip equity becomes nonsensical: | asset class | example | total_pips (full history) | |---|---|---| | 5-dp FX | EURUSD, GBPUSD, USDCAD | -37.6, -57.2, -28.7 (correctly scaled) | | equities (USD) | AAPL.US, TSLA.US | -8,693,884, -27,203,872 (wrong unit) | | crypto / indices | BTCUSD, JPN225, US30 | -3.2e9, -2.5e9, -9.9e8 (wildly wrong) | Engine mechanics are fine (deterministic, finite, 92.8M bars in ~75s); only the *unit* is wrong off-FX. A cross-asset signal-quality comparison is meaningless until the pip/point value is per-instrument. ## Direction (not prescriptive) The pip/point value (and tick size) is per-instrument metadata and belongs beside the hot path (C4 non-scalars-as-metadata), keyed by instrument, not a single `f64` baked into the broker node at construction. Most relevant to the realistic-broker layer (C10), but the sim-optimal broker already needs it to make multi-asset pip equity comparable. Found via a throwaway multi-asset probe (since discarded); no code change in tree.
Brummel added the idea label 2026-06-04 22:58:11 +02:00
Author
Owner

Now reproducible in-tree (no throwaway probe needed)

Since aura run --real <SYMBOL> [--from <ms>] [--to <ms>] landed (0ce843b, pushed) the off-FX pip-unit symptom is reproducible straight from the shipped CLI — the sample harness still bakes SimBroker::new(0.0001), so any non-FX instrument streamed through it shows the wrong unit.

Fresh numbers, same SMA-cross sample, calendar year 2021 (release build, ~12 backtests in <2s):

instrument total_pips note
EURUSD (5-dp FX) -1.03 sane — 0.0001 is the right pip here (the anchor)
AAPL.US (equity) -3,094,417 wrong unit
TSLA.US (equity) -14,305,113 wrong unit
XAUUSD (gold) -8,406,494 wrong unit

Repro:

aura run --real EURUSD  --from 1609459200000 --to 1640995199000   # ~ -1.03
aura run --real AAPL.US --from 1609459200000 --to 1640995199000   # ~ -3.09M

Edit surface a fix touches: the constructor literal SimBroker::new(pip_size) (crates/aura-std/src/sim_broker.rs:47), and the 0.0001 baked at the two CLI call sites (sample_harness() and the sim_optimal_manifest broker label in crates/aura-cli/src/main.rs). The body's direction still holds: pip/point is per-instrument metadata keyed by instrument, not a constructor f64 — most naturally sourced from the per-instrument Aura.toml metadata layer.

Deferred — parked here for when the per-asset pip / instrument-metadata work is picked up.

## Now reproducible in-tree (no throwaway probe needed) Since `aura run --real <SYMBOL> [--from <ms>] [--to <ms>]` landed (0ce843b, pushed) the off-FX pip-unit symptom is reproducible straight from the shipped CLI — the sample harness still bakes `SimBroker::new(0.0001)`, so any non-FX instrument streamed through it shows the wrong unit. Fresh numbers, same SMA-cross sample, calendar year 2021 (release build, ~12 backtests in <2s): | instrument | total_pips | note | |---|---|---| | EURUSD (5-dp FX) | -1.03 | sane — 0.0001 is the right pip here (the anchor) | | AAPL.US (equity) | -3,094,417 | wrong unit | | TSLA.US (equity) | -14,305,113 | wrong unit | | XAUUSD (gold) | -8,406,494 | wrong unit | Repro: aura run --real EURUSD --from 1609459200000 --to 1640995199000 # ~ -1.03 aura run --real AAPL.US --from 1609459200000 --to 1640995199000 # ~ -3.09M Edit surface a fix touches: the constructor literal `SimBroker::new(pip_size)` (`crates/aura-std/src/sim_broker.rs:47`), and the `0.0001` baked at the two CLI call sites (`sample_harness()` and the `sim_optimal_manifest` broker label in `crates/aura-cli/src/main.rs`). The body's direction still holds: pip/point is per-instrument metadata keyed by instrument, not a constructor `f64` — most naturally sourced from the per-instrument `Aura.toml` metadata layer. Deferred — parked here for when the per-asset pip / instrument-metadata work is picked up.
Author
Owner

Confirmed by the GER40 research deep-dive fieldtest (docs/specs/fieldtest-research-breakout-deepdive.md): the gap bites at the first real cross-asset comparison. GER40 (+203.4) vs FRA40 (+83.7) over 2024 is honest ONLY because the researcher hand-supplied matching pip_size=1.0 for both index instruments — there is no registry, and nothing flags or prevents comparing GER40 index-pips against EURUSD fx-pips (0.0001). The sim-optimal broker's cross-asset yardstick is currently honest by researcher discipline, not by construction.

Confirmed by the GER40 research deep-dive fieldtest (docs/specs/fieldtest-research-breakout-deepdive.md): the gap bites at the first real cross-asset comparison. GER40 (+203.4) vs FRA40 (+83.7) over 2024 is honest ONLY because the researcher hand-supplied matching pip_size=1.0 for both index instruments — there is no registry, and nothing flags or prevents comparing GER40 index-pips against EURUSD fx-pips (0.0001). The sim-optimal broker's cross-asset yardstick is currently honest by researcher discipline, not by construction.
Brummel added this to the Runway — real-data ergonomics & honesty hardening milestone 2026-06-18 11:02:34 +02:00
Author
Owner

Design reconciliation (specify, in-context entry)

Spec: 0054-per-instrument-pip. The forks below are still listed open on this
issue (the body frames the direction as "not prescriptive"); they were resolved
in the in-context design discussion, not by the orchestrator.

  • Fork: where per-instrument pip lives → a typed InstrumentSpec { pip_size }
    in aura-std, looked up by symbol via aura_ingest::instrument_spec(symbol)
    at the ingestion/source edge (where the symbol still exists). The engine stays
    domain-free: no instrument_id on Ctx, pip never streamed (C4/C7/C15 — pip is
    non-scalar reference metadata beside the hot path). NOT via Aura.toml (a
    Rust-authored table, which the later Aura.toml schema can subsume). Explicitly
    portfolio-agnostic: only keys pip per-instrument; introduces no instrument_id
    flow, no basket/aggregation node, no position-event table (that is the carved-out
    realistic-execution milestone). SimBroker is and stays by-construction
    single-instrument (slot0 exposure + slot1 price → one equity).
    Provenance: resolved by the user in the interactive session on 2026-06-18 — chose
    "B-core-with-A-home" (typed spec + source-edge home) over the bare-f64 option,
    and confirmed the portfolio-agnostic scope boundary verbatim ("ja, bleibt stabil")
    after a multi-instrument clarification ("two instruments ⇒ two brokers + a
    not-yet-built aggregator, out of scope here").

  • Fork: manifest/label scope → minimal: the sim-optimal broker label
    (RunManifest.broker) carries the real per-instrument pip on the real-data
    path, so the C18 manifest stays a faithful reproduction recipe; NO separate
    instrument_id manifest axis (deferred to later registry-instrument-axis work).
    Provenance: user accepted the recommended "minimal in scope" for Fork 2 in the
    same session, with no objection.

## Design reconciliation (specify, in-context entry) Spec: `0054-per-instrument-pip`. The forks below are still listed open on this issue (the body frames the direction as "not prescriptive"); they were resolved in the in-context design discussion, not by the orchestrator. - **Fork: where per-instrument pip lives** → a typed `InstrumentSpec { pip_size }` in `aura-std`, looked up by `symbol` via `aura_ingest::instrument_spec(symbol)` at the ingestion/source edge (where the symbol still exists). The engine stays domain-free: no `instrument_id` on `Ctx`, pip never streamed (C4/C7/C15 — pip is non-scalar reference metadata beside the hot path). NOT via `Aura.toml` (a Rust-authored table, which the later `Aura.toml` schema can subsume). Explicitly **portfolio-agnostic**: only keys pip per-instrument; introduces no `instrument_id` flow, no basket/aggregation node, no position-event table (that is the carved-out realistic-execution milestone). `SimBroker` is and stays by-construction single-instrument (slot0 exposure + slot1 price → one equity). Provenance: resolved by the user in the interactive session on 2026-06-18 — chose "B-core-with-A-home" (typed spec + source-edge home) over the bare-f64 option, and confirmed the portfolio-agnostic scope boundary verbatim ("ja, bleibt stabil") after a multi-instrument clarification ("two instruments ⇒ two brokers + a not-yet-built aggregator, out of scope here"). - **Fork: manifest/label scope** → minimal: the sim-optimal broker label (`RunManifest.broker`) carries the *real* per-instrument pip on the real-data path, so the C18 manifest stays a faithful reproduction recipe; NO separate `instrument_id` manifest axis (deferred to later registry-instrument-axis work). Provenance: user accepted the recommended "minimal in scope" for Fork 2 in the same session, with no objection.
Author
Owner

Design reconciliation — correction (specify, in-context entry)

Correcting the crate-home stated in my earlier reconciliation comment. The typed
InstrumentSpec lives in aura-ingest (the ingestion / source edge), NOT
aura-std. The earlier comment's "in aura-std" was a transcription slip; the
agreed decision was the source-edge home ("A-home"), which is the aura-ingest
crate. SimBroker (aura-std) does not reference the type; the consumers are
aura-cli and the aura-ingest examples, and the dependency direction
(aura-cli → aura-ingest → aura-std, and aura-std does NOT depend on aura-ingest)
makes aura-ingest the only home consistent with "source edge".

  • Fork: crate-home of InstrumentSpecaura-ingest.
    Provenance: confirmed by the user in the interactive session on 2026-06-18
    ("ja" to "Bestätigst du aura-ingest als Home?", after the spec-skeptic
    scope-fork lens flagged the comment-vs-spec mismatch).

The substantive resolution (typed spec, source edge, keyed by symbol, engine
domain-free, not Aura.toml, portfolio-agnostic) is unchanged from the prior
comment.

## Design reconciliation — correction (specify, in-context entry) Correcting the crate-home stated in my earlier reconciliation comment. The typed `InstrumentSpec` lives in **`aura-ingest`** (the ingestion / source edge), NOT `aura-std`. The earlier comment's "in aura-std" was a transcription slip; the agreed decision was the **source-edge home** ("A-home"), which is the aura-ingest crate. `SimBroker` (aura-std) does not reference the type; the consumers are aura-cli and the aura-ingest examples, and the dependency direction (`aura-cli → aura-ingest → aura-std`, and aura-std does NOT depend on aura-ingest) makes aura-ingest the only home consistent with "source edge". - **Fork: crate-home of `InstrumentSpec`** → `aura-ingest`. Provenance: confirmed by the user in the interactive session on 2026-06-18 ("ja" to "Bestätigst du `aura-ingest` als Home?", after the spec-skeptic scope-fork lens flagged the comment-vs-spec mismatch). The substantive resolution (typed spec, source edge, keyed by `symbol`, engine domain-free, not `Aura.toml`, portfolio-agnostic) is unchanged from the prior comment.
Author
Owner

Design decision — #22 metadata channel & scope (user-directed, 2026-06-18)

Root cause (user): the data-server symbol stream carries only price bars — NO
instrument metadata (pip size). That absence is the bug. The fix is a channel to
specify per-instrument pip metadata, since the data does not carry it.

Decisions (user-directed this session — "Das Problem ist doch, dass die Symboldaten
keine Metadaten (wie Pipsize) mitbringen. DAS ist der Bug. Also sieh zu, dass man die
IRGENDWIE angeben kann und werd mal fertig!"):

  • Metadata channel → a typed InstrumentSpec { pip_size } + an
    aura_ingest::instrument_spec(symbol) lookup — a Rust-authored vetted table
    (NOT Aura.toml). Home: aura-ingest (source edge; user-confirmed earlier).
  • Scope → narrow #22 to the actual bug: the CLI aura run --real path (which
    bakes 0.0001 for every symbol) + the lookup + the refusal. The runnable-example
    pip centralization (GER40/FRA40 already bake the correct 1.0 — not buggy) is
    DEFERRED to a follow-up idea issue.
  • Unknown-symbol behaviour → a real-data run for a symbol with no vetted spec
    REFUSES (exit 2) rather than guessing a pip — honest by construction, consistent
    with "you must be able to specify it".
  • Seed set → GER40/FRA40 = 1.0 (index points); 5-decimal FX majors = 0.0001
    (note: JPY pairs are 0.01, not 0.0001 — seed only vetted values). Extend as vetted.

These resolve the spec-skeptic round-2 scope-fork (refusal provenance) and criterion
(the CLI example — --real GER40=1.0 vs --real EURUSD=0.0001 vs --real BTCUSD
refuse — is the cross-asset evidence) blocks. The deferred example refactor removes
the ambiguity/plan-readiness blocks from this cycle's scope.

## Design decision — #22 metadata channel & scope (user-directed, 2026-06-18) **Root cause (user):** the data-server symbol stream carries only price bars — NO instrument metadata (pip size). That absence is the bug. The fix is a channel to *specify* per-instrument pip metadata, since the data does not carry it. Decisions (user-directed this session — "Das Problem ist doch, dass die Symboldaten keine Metadaten (wie Pipsize) mitbringen. DAS ist der Bug. Also sieh zu, dass man die IRGENDWIE angeben kann und werd mal fertig!"): - **Metadata channel** → a typed `InstrumentSpec { pip_size }` + an `aura_ingest::instrument_spec(symbol)` lookup — a Rust-authored vetted table (NOT `Aura.toml`). Home: `aura-ingest` (source edge; user-confirmed earlier). - **Scope** → narrow #22 to the actual bug: the CLI `aura run --real` path (which bakes `0.0001` for every symbol) + the lookup + the refusal. The runnable-example pip centralization (GER40/FRA40 already bake the correct `1.0` — not buggy) is DEFERRED to a follow-up `idea` issue. - **Unknown-symbol behaviour** → a real-data run for a symbol with no vetted spec REFUSES (exit 2) rather than guessing a pip — honest by construction, consistent with "you must be able to specify it". - **Seed set** → GER40/FRA40 = `1.0` (index points); 5-decimal FX majors = `0.0001` (note: JPY pairs are 0.01, not 0.0001 — seed only vetted values). Extend as vetted. These resolve the spec-skeptic round-2 scope-fork (refusal provenance) and criterion (the CLI example — `--real GER40`=1.0 vs `--real EURUSD`=0.0001 vs `--real BTCUSD`→ refuse — is the cross-asset evidence) blocks. The deferred example refactor removes the ambiguity/plan-readiness blocks from this cycle's scope.
Author
Owner

Landed in 26bec6d (feat: per-instrument pip metadata channel) + cycle-close 77358d2 (C10/C15 realization note). InstrumentSpec + instrument_spec lookup in aura-ingest; CLI aura run --real threads the looked-up pip and refuses un-specced symbols (exit 2). SimBroker unchanged. Audit clean, full suite green. Runnable-example centralization deferred to #98.

Landed in 26bec6d (feat: per-instrument pip metadata channel) + cycle-close 77358d2 (C10/C15 realization note). InstrumentSpec + instrument_spec lookup in aura-ingest; CLI aura run --real threads the looked-up pip and refuses un-specced symbols (exit 2). SimBroker unchanged. Audit clean, full suite green. Runnable-example centralization deferred to #98.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/Aura#22