# Equity/ETF FX & Market-Data Phase C Pattern

Use this reference when hardening local FX and market-data foundations after initial broker/True-Wealth `initial_position_snapshot` imports and before broader Equity/ETF imports or valuation dashboards.

## Scope and guardrails

- Do not import additional positions while FX/market-price quality for the existing mini-import set is unresolved.
- Do not write `positions_snapshot` during validation. Calculate positions locally to surface data-quality alerts only.
- Do not call live market/FX APIs from dashboard/report rendering. Providers belong in explicit CLI/scheduled jobs.
- Report only aggregate counts; never output quantities, prices, CHF values, balances, raw rows, or account-level values.
- CHF remains base currency. `CHF/CHF = 1`, `fx_source=not_needed`, `fx_status=not_needed` for original-currency CHF transactions.

## C0 — Missing-FX alert validation

1. Identify the imported mini-import transactions by actual runtime `source_type`; in this repo True-Wealth mini-imports used `broker_import_reviewed_snapshot` rather than a literal `true_wealth` source type.
2. Aggregate:
   - total `initial_position_snapshot` rows
   - CHF original-currency rows
   - foreign-currency rows
   - active `missing_fx` alerts
   - false-positive `missing_fx` alerts
3. Correct only genuine false positives:
   - If original currency is CHF, set transaction FX fields to `rate=1`, `source=not_needed`, `status=not_needed`.
   - Resolve matching `missing_fx` alerts.
   - Write an audit event for every correction.
4. If all rows are foreign currency and no matching historical FX exists, keep `missing_fx` active. This is a valid data-quality state, not an error.

## FX module implementation pattern

- Store FX rates locally in `fx_rates` as exact Decimal strings/TEXT where precision matters.
- Provide `latest_fx_rate()` / `get_fx_rate_to_chf()` helpers that read local DB only.
- Add an explicit `update-fx-rates` CLI supporting historical date/latest, missing-only, dry-run, provider selection, conservative retry/backoff controls, and transaction recheck.
- Manual overrides require a non-empty note and an audit-log event.
- Tests should cover CHF/CHF not-needed, USD/EUR to CHF storage, missing/resolved FX alerts, manual override audit, dry-run no-write, and local dashboard reads.

## Instrument price mapping pattern

- ISIN remains the primary instrument identity. Provider symbol is only a market-data mapping.
- Never auto-map market prices by name alone.
- Ticker without exchange remains review-needed / ambiguous.
- Missing provider symbol should create a deduplicated data-quality alert.
- Manual mapping confirmation must create an audit-log event and should resolve missing-provider-symbol alerts for that instrument.

## Equity/ETF market-price skeleton

- Store local market prices in `market_prices`; no dashboard live API.
- Provider abstraction should be mockable in unit tests.
- CLI should support `--asset-class`, `--only-missing`, `--only-stale`, `--only-isin`, `--limit`, `--dry-run`, retry/backoff controls.
- If no confirmed provider mappings exist, the productive run should skip provider calls and report aggregate zero-mapping status.
- Missing or stale prices create deduplicated data-quality alerts and suppress precise CHF valuation/Total Return.

## Runtime verification checklist

Run after implementation:

1. FX recheck for existing mini-import source type.
2. Mapping quality pass for affected instruments.
3. Market-price update in dry-run or only for confirmed mappings.
4. Local position calculation to surface alerts, without saving `positions_snapshot`.
5. Compile, full pytest, remove generated caches, Git-safety scan, Git status.
6. Commit and push if code changed.

Aggregate report fields:

- positions total
- positions with FX OK/not_needed
- positions with missing FX
- positions with market price OK
- positions with missing market price
- positions with complete CHF valuation
- active alerts by category/rule
- tests, Git-safety, commit, push status

## Common pitfall

A correct Phase C result may be “all three positions still incomplete”: e.g. all foreign-currency positions with no local historical FX and no provider-symbol mappings. Do not force resolution or invent prices/FX; preserve the alerts and proceed to manual mapping / approved FX-rate population next.