2026-07-20 interface change (mock-as-real removal, YG order): the hardcoded 1.2000 anchor and the Math.random() price_simulation path are DELETED — the operator now emits fx_change_percent + volatility + decomposition only (fully deterministic).
What (One-liner)
FX market weighted-factor model: interest-rate differential + risk sentiment + economic surprise combined additively → fx_rate + volatility + 30-day simulation path. Stylized heuristic, NOT strict CIP.
How to use
Drop fx_market_v1 on Canvas, fill base_currency_rate (base-currency rate %) + quote_currency_rate (quote-currency rate %) + optional risk_sentiment [0,1] + economic_data_surprise [-1,1]. Output is fx_rate + volatility + price_simulation.
Core formulas
rate_differential = base - quote
carry_effect = diff * 0.10 # 10% weight
risk_adj = (risk - 0.5) * 0.05
surprise = surprise * 0.02
fx_change_pct = carry_effect + risk_adj + surprise
# (2026-07-20) absolute-rate anchor removed
volatility = 0.15 + |diff| * 0.02 + (1 - risk) * 0.05Rounding contract: fx_rate → 4; volatility → 3; rate_differential_impact → 2.
Assumptions & applicability
Assumptions: currency-pair anchor 1.2000 (EUR/USD-style) + three factors additive/independent + 30-day fixed window + risk ∈ [0,1] + surprise ∈ [-1,1].
Applies to: macro FX scenario simulation, cross-asset what-if input, teaching demo.
Does not apply to: real-time trading signals, strict CIP, long-term forecasting, non-EUR/USD pairs, scenarios requiring reproducible simulation paths.
Input / Output contract
{base_currency_rate, quote_currency_rate, risk_sentiment?, economic_data_surprise?} → {fx_rate, volatility, price_simulation[30], rate_differential_impact}.
price_simulation uses Math.random(); values differ across calls.
Known limitations
- base_fx_rate = 1.2 hard-coded (misuse on non-EUR/USD-style pairs)
- price_simulation non-deterministic (Math.random; seeded RNG is future work)
- Three-factor weights are empirical (10%/5%/2%, no empirical tuning)
- 30-day window is hard-coded
- Model name-vs-implementation mismatch: fx_market is NOT CIP; it is a heuristic factor sum
- rate_differential_impact is easily misread as "rate change"; it is actually carry_trade_effect × 100
References
Fama (1984) forward-rate bias + Engel (2014) CIP survey + Brunnermeier-Nagel-Pedersen (2008) carry crashes. See frontmatter.
Golden Test
tests/golden/fixtures/tier2/fx_market/ — cross-language golden (TypeScript impl × Python reference). 10 tests:
- 6 scenarios deterministic byte-equal (neutral / high_carry_hawkish / risk_off / risk_on_surprise / negative_rd / max_neg_surprise)
- determinism (5 reruns; deterministic fields byte-identical; price_simulation not asserted)
- base_fx_rate anchor (all-neutral inputs → fx_rate = 1.2 exactly)
- rate_differential_impact semantic lock (= carry_trade_effect × 100)
- validation contract (missing required fields → throws)
Golden only locks deterministic fields. price_simulation only asserts structure (length=30, reasonable band); Math.random cannot be referenced.
Test location: tests/golden/node/fx_market_golden.test.ts.
Changelog
- 1.0.0 (2026-04-20) — First Active (Tier 2 golden #4)

