broker-sync/tests/fixtures/ecb_2026-04-01.xml

17 lines
603 B
XML
Raw Normal View History

Add ECB FX fetcher + cache population Context ------- Phase 1 needs live FX rates for USD-denominated RSU vestings (Schwab), EUR-denominated deposits, and any multi-currency dividend. The FxCache from an earlier commit stores (currency, date) → rate_to_gbp but was intentionally left empty — this commit wires the ingestion path. Design decisions ---------------- - ECB publishes EUR→X, not X→GBP. Everything pivots through EUR: rate(X→GBP) = rate(EUR→GBP) / rate(EUR→X) GBP goes into the result at 1.0 so callers iterating the dict get a consistent shape; `populate_fx_cache` then skips GBP because `convert_to_gbp` has a dedicated passthrough branch. - `on_date` parameter is accepted for API symmetry with the future historical fetcher even though the daily endpoint only serves the most recent publication. The docstring calls this out explicitly. - XML is parsed with stdlib `xml.etree.ElementTree`. No `lxml` — the file is 30 lines, no performance concern, and keeping deps minimal matters for the container image. - The HTTP layer takes an optional `httpx.AsyncBaseTransport` the same way WealthfolioSink does — MockTransport drives all tests, the production caller just leaves it None. This change ----------- - broker_sync/fx_ecb.py: * `fetch_ecb_rates(on_date, *, transport=None)` — GETs the daily XML, parses, pivots through EUR, returns `{ccy: rate_to_gbp}`. Raises `RuntimeError` on non-2xx or if GBP is missing (cannot pivot). No retries — caller handles resilience. * `populate_fx_cache(cache, rates, on_date)` — writes every non-GBP rate with `FxRateSource.ECB_LIVE`. * `fetch_ecb_rates_historical(start, end)` — `NotImplementedError` stub; filed as beads task code-thw.2.2. Needed for backfilling years of T212 history (daily endpoint only covers today). - tests/fixtures/ecb_2026-04-01.xml: realistic 5-currency ECB snapshot. - tests/test_fx_ecb.py: fixture-driven tests covering the pivot math, the 503 failure path, the cache-skip-GBP rule, and the NotImplemented guard on the historical stub. Test plan --------- ## Automated - poetry run pytest -q → 52 passed in 0.50s - poetry run mypy broker_sync tests → Success: no issues found in 26 source files - poetry run ruff check . → All checks passed! ## Manual Verification Live daily endpoint hit deferred to the CLI integration commit — the fetcher is pure + transport-injectable, so the unit tests cover the parsing and pivot logic, and the CLI wiring will be the place where the live call is exercised end-to-end. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 19:32:23 +00:00
<?xml version="1.0" encoding="UTF-8"?>
<gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
<gesmes:subject>Reference rates</gesmes:subject>
<gesmes:Sender>
<gesmes:name>European Central Bank</gesmes:name>
</gesmes:Sender>
<Cube>
<Cube time="2026-04-01">
<Cube currency="USD" rate="1.0823"/>
<Cube currency="GBP" rate="0.8564"/>
<Cube currency="JPY" rate="163.45"/>
<Cube currency="CHF" rate="0.9654"/>
<Cube currency="PLN" rate="4.2850"/>
</Cube>
</Cube>
</gesmes:Envelope>