5b7cd5eb2d
Make data-server's neutral InstrumentGeometry a first-class, broker-agnostic input at the aura-ingest edge: re-export InstrumentGeometry + a thin instrument_geometry(server, symbol) accessor over symbol_meta (the recorded dataset-metadata tier 2 of #124's hierarchy; the tier-composing resolver stays #124). Add the two deferred deploy-grade InstrumentSpec fields tick_size + digits (additive, struct stays Copy; C15), authored for the 5 vetted symbols from the verified sidecars. A new real-data cross-check asserts the authored floor agrees with provider geometry (pip_size, contract_size==lot_size, the C10 pip_value invariant, tick_size, digits, quote_currency), iterating the vetted set so #140's additions are covered for free; skip-if-archive-absent. Scope (derived, recorded on #143): no runtime resolver and no quote_currency type change — both remain #124's. 'quote_currency beyond FX' is satisfied at the geometry tier (InstrumentGeometry.quote: String), not by widening the &'static str floor. Beyond the plan (implementer additions, reviewed and accepted): hoisted the vetted-symbol list to one pub const VETTED_SYMBOLS, collapsing the three drift-prone copies plan-recon flagged (#140 now extends one constant); a tick_size == 10^(-digits) consistency guard (verified to hold across all 30 provider sidecars, so it will not false-trip on #140); a re-export-nameable seam test guarding the InstrumentGeometry re-export from silent removal. Verification: cargo test --workspace all green; cargo clippy --workspace --all-targets -D warnings clean. The cross-check ran against the live Pepperstone archive (not skipped) — all 5 vetted sidecars match the authored table. refs #143
94 lines
4.7 KiB
Rust
94 lines
4.7 KiB
Rust
//! End-to-end verification of the public instrument-spec resolution seam
|
||
//! (#113): a downstream broker resolves deploy-grade metadata by symbol through
|
||
//! `aura_ingest::instrument_spec` — the permanent authored floor of the
|
||
//! milestone's override hierarchy (#124). These tests pin the *consumer-visible*
|
||
//! contract (the public return shape + the refuse-don't-guess arm), distinct
|
||
//! from the crate-internal literal-table unit tests.
|
||
|
||
use aura_ingest::{instrument_spec, InstrumentSpec, VETTED_SYMBOLS};
|
||
|
||
/// Property: every vetted symbol resolves through the *public* lookup to a fully
|
||
/// populated `InstrumentSpec` whose deploy fields are all present and sane
|
||
/// (positive sizes/ids, positive tick geometry, a non-empty quote currency).
|
||
/// This is the floor a broker
|
||
/// reads before sizing a lot; a regression that left any field at a placeholder
|
||
/// (0.0 / "") or dropped a symbol from the table would size positions wrong with
|
||
/// no error. Asserts only the consumer-visible guarantees, not the exact vetted
|
||
/// literals (those are the crate's own unit test's remit).
|
||
#[test]
|
||
fn every_vetted_symbol_resolves_to_a_complete_spec() {
|
||
for sym in VETTED_SYMBOLS {
|
||
let s: InstrumentSpec = instrument_spec(sym).unwrap_or_else(|| {
|
||
panic!("vetted symbol {sym} must resolve through the public lookup")
|
||
});
|
||
assert!(s.instrument_id > 0, "{sym}: instrument_id must be a real (>0) id");
|
||
assert!(s.pip_size > 0.0, "{sym}: pip_size must be positive");
|
||
assert!(s.contract_size > 0.0, "{sym}: contract_size must be positive");
|
||
assert!(s.pip_value_per_lot > 0.0, "{sym}: pip_value_per_lot must be positive");
|
||
assert!(s.min_lot > 0.0, "{sym}: min_lot must be positive");
|
||
assert!(s.lot_step > 0.0, "{sym}: lot_step must be positive");
|
||
assert!(!s.quote_currency.is_empty(), "{sym}: quote_currency must be labelled");
|
||
assert!(s.tick_size > 0.0, "{sym}: tick_size must be positive");
|
||
assert!(s.digits > 0, "{sym}: digits must be positive");
|
||
}
|
||
}
|
||
|
||
/// Property: the cross-field deploy invariant `tick_size == 10^(-digits)` holds
|
||
/// for every vetted instrument as a downstream broker observes it through the
|
||
/// public lookup. `tick_size` and `digits` are two views of the same provider
|
||
/// price geometry; a broker may round a price by either. This pins that the two
|
||
/// agree, so a consistent typo'd *pair* in one table entry (which the positivity
|
||
/// asserts and an equally-typo'd literal unit test would both wave through) is
|
||
/// caught — exactly the silent-wrong-rounding regression this file guards.
|
||
#[test]
|
||
fn tick_size_is_consistent_with_digits_at_the_public_boundary() {
|
||
for sym in VETTED_SYMBOLS {
|
||
let s = instrument_spec(sym).expect("vetted symbol");
|
||
let derived = 10f64.powi(-(s.digits as i32));
|
||
assert!(
|
||
(s.tick_size - derived).abs() < 1e-12,
|
||
"{sym}: tick_size {} must equal 10^(-digits) = 10^(-{}) = {}",
|
||
s.tick_size,
|
||
s.digits,
|
||
derived,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// Property: the cross-field deploy invariant `pip_value_per_lot ==
|
||
/// contract_size * pip_size` holds for every vetted instrument as a downstream
|
||
/// broker observes it through the public lookup. A broker computing pip P&L may
|
||
/// use either `pip_value_per_lot` directly or derive it from contract_size ×
|
||
/// pip_size; this pins that the two paths agree, so the choice is free. A future
|
||
/// table edit that broke the identity (e.g. a typo'd pip_value) would silently
|
||
/// double or halve every realistic-broker P&L.
|
||
#[test]
|
||
fn pip_value_is_consistent_with_contract_and_pip_at_the_public_boundary() {
|
||
for sym in VETTED_SYMBOLS {
|
||
let s = instrument_spec(sym).expect("vetted symbol");
|
||
let derived = s.contract_size * s.pip_size;
|
||
assert!(
|
||
(s.pip_value_per_lot - derived).abs() < 1e-9,
|
||
"{sym}: pip_value_per_lot {} must equal contract_size {} * pip_size {} = {}",
|
||
s.pip_value_per_lot,
|
||
s.contract_size,
|
||
s.pip_size,
|
||
derived,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// Property: an un-vetted symbol resolves to `None` — the refuse-don't-guess
|
||
/// honesty lever (#113). A broker that asked for a symbol with no authored floor
|
||
/// must get nothing back, never a fabricated/defaulted spec, so deploy fails
|
||
/// loudly rather than sizing on a guessed pip value. A regression that returned
|
||
/// a `Some(default)` for unknown symbols is exactly the silent-guess failure
|
||
/// this guards.
|
||
#[test]
|
||
fn unvetted_symbol_refuses_with_none() {
|
||
assert_eq!(instrument_spec("NOT_A_REAL_SYMBOL"), None);
|
||
assert_eq!(instrument_spec(""), None);
|
||
// case sensitivity is part of the refuse arm: the table is exact-match only.
|
||
assert_eq!(instrument_spec("ger40"), None);
|
||
}
|