Skip to content

ADR-021: Currency-Aware Path-Profile (Date-Aligned FX Conversion) ​

Date: 2026-06-25 Status: accepted Continuation: ADR-020 (Concentration Default Off) has no direct relationship; works in conjunction with the path-profile system (core/regime_probability.py) + historical backfill (scripts/backfill_history.py).

Context ​

The committee's path-profile (forward path distribution after selling/holding, fed to CIO for TRIM buyback points + downside metrics) has always been calculated in the asset's quote currency: GC=F is quoted in USD/oz, so regime classification, forward return distribution, and downside percentiles are all for USD Gold. CNY conversion is only done at the display layer (Zheshang Accumulated Gold ¥/g).

However, the user actually holds Zheshang Accumulated Gold priced in ¥/g — the risk they bear is XAU/CNY (= XAU/USD × USDCNY). FX changes the picture: during gold price declines, the Renminbi often strengthens, amplifying losses for CNY holders. Measured (downtrend):

90dp_below20th Percentile Downside
USD Denomination (Original Committee)0.37−5.1%
CNY Denomination (Actual User)0.40−5.9%

→ The USD denomination systematically underestimates the downside for CNY holders.

Why can't we simply "build an XAU/CNY series and run it": The Renminbi only truly floated in 2005 (USDCNY data from 2001, pegged at 8.28 before 2005), so the continuous XAU/CNY market history is only ~20 years → downtrend 90d independent samples are only ~5 (effective_n=5), which is both insufficient and biased (those few samples all come from the CNY bull market). The direct series method cannot solve this.

Decision: Date-Aligned FX Conversion to Synthesize Holding Currency Distribution ​

Instead of waiting for the scarce "merged history", take one sample-rich base currency leg and convert it daily using the actual exchange rate at that time:

  • Base Currency Gold Leg: XAU/USD downtrend forward distribution — 57 years after backfill (ADR-020 / backfill_history), 90d effective_n=16. Thick enough, forms the main distribution.
  • FX Leg: USDCNY forward returns — available in MarketStore from 2001+. No separate sampling, aligned by date to the base currency leg.

Synthesis: For each regime day d, take the actual FX forward return occurring within the same calendar window, point-by-point: r_cny(d) = (1 + r_usd(d)) × (1 + r_fx(d)) − 1, stacking into the CNY denomination distribution.

Why Date-Aligned Instead of "Pairwise Random Combination" (MC Convolution) ​

An early implementation used Monte Carlo to independently pair samples from two bags (200k random draws of r_usd × random draws of r_fx). Code review identified three flaws:

  1. Inconsistent Leg Spans: The FX leg used shift(-d) (trading days), while the base currency leg used calendar day forwards — in a 90d window, FX actually spanned ~126 calendar days, systematically deepening the rolled downside.
  2. Ignored Point-in-Time: The FX leg read the full history, inconsistent with the asof cutoff of the base currency leg → walk-forward would look ahead.
  3. Independent Pairing Averaged Out Co-movement: The fact that gold declines often coincide with Renminbi strengthening (amplifying losses for CNY holders) is the very motivation for this ADR. Independent random pairing precisely averages out this correlation — underestimating the very correlated drawdown it aims to warn about.

Switching to date-aligned (inner join of base currency leg and FX leg by date, pointwise multiplication) fixes all three at once: same searchsorted calendar day → consistent spans; both legs filtered by the same asof → zero lookahead; uses actual same-window co-movement instead of random pairing → preserves the real tail of "gold down × CNY strong". No randomness → naturally deterministic, no seed needed.

Sample count = regime days of base currency leg ∩ days with FX forward returns ≈ base currency leg samples (FX covers the entire period). The bottleneck remains the base currency leg, thickened by the 57-year backfill — not relying on a "16×68 Cartesian product" to inflate independent samples (which is a product of the independence assumption that doesn't hold).

Implementation (Opt-In, Zero Change to Default USD Behavior) ​

  • get_path_profile(asset, regime, …, convert_ccy=None): Default None → completely unchanged. When convert_ccy="CNY" is set, the main windows remain in the quote currency (text/prices), while the date-aligned converted holding currency distribution is attached to currency_overlay (calculated within the same frame, single data load — no longer requiring a secondary get_path_profile read like the early version). Both conditional and unconditional distributions are converted, so calibrate_profile shrinkage stays within the same currency. asof is passed down to the FX leg, ensuring zero lookahead.
  • effective_n still takes the value from the base currency (USD) leg (the bottleneck leg); after conversion, fx_aligned_n (intersection sample count) is stored separately as metadata.
  • quote_currency_iso(asset) covers major exchange suffixes (.AX/.HK/.SS/.SZ/.L/.T/.TO/.PA… + crypto -USD/-EUR); defaults to USD for unknown suffixes, but currency_overlay is only attached when the FX series actually exists — missing labels won't fabricate a fake conversion.
  • convert_ccy_for(asset, holding_cost_ccy): Returns the currency to convert to when the holding denomination currency ≠ the asset's quote currency (quote_currency_iso), otherwise None. Example: GC=F (quoted in USD) + Zheshang Accumulated Gold (cost_currency CNY) → "CNY"; 510300.SS (quoted in CNY) + CNY holding → None.
  • Wiring: core/runner/session.py (Direct/Web/Cron) and core/runner/coordinator.py (Coordinator) both automatically pass convert_ccy based on the holding cost_currency (if holding query fails → falls back to base currency, does not affect the main reentry flow).
  • Output: Structured path_profile with currency_overlay attached (holding currency distribution); a line with ⚠ holding currency denomination downside (compared to the quote currency denomination, e.g., USD/AUD/HKD — not hardcoded to USD) is appended at the end of the reentry text for the CIO.

v1 Approximations (Explicitly Documented, Refinable Later) ​

  1. The FX leg's forward returns themselves are not secondarily filtered by gold-regime: Date-alignment already implicitly selects "the actual FX co-movement on those specific regime days", but the FX series itself is not separately conditioned on regime — CNY is a managed float with much lower volatility than gold, and secondary conditioning would thin the sample too much. Left as a refinement. (The early version's flaw of "independent MC pairing averaging out co-movement" is already fixed by date-alignment, so this is no longer an approximation item.)
  2. The path "shape" (when it bottoms/retraces) still follows the base currency, only the terminal risk numbers are converted — the shape is dominated by gold, FX is just a terminal minor correction.
  3. Prices remain in the asset's quote currency (USD/oz), consistent with the rest of the committee's briefs (Quant's MA, Risk, etc.), avoiding mixed currencies within a single brief; holding currency information is provided as "percentage metrics + ⚠ warning".

Consequences ​

  • The committee for CNY-denominated holdings (Zheshang Accumulated Gold) now sees the real currency downside (deeper), no longer underestimated by the USD denomination.
  • Samples are thickened by the 57-year base currency leg (90d eff_n=16), date-alignment preserves the real gold-FX co-movement, bypassing the "long XAU/CNY history doesn't exist" problem.
  • Fully opt-in: Assets without holding currency mismatch (e.g., 510300.SS quoted in CNY + CNY holding, pure USD assets) have convert_ccy=None, zero change to USD/base currency behavior.
  • Dependency: The thickness of the base currency leg relies on the long historical backfill from ADR-020/backfill_history (57 years of USD Gold); if the base currency leg is still thin, lever-1 shrinkage (calibrate_profile, disabled by default pending fit) can be applied.