Add FXMacroData macro provider - #16
Conversation
luisleo526
left a comment
There was a problem hiding this comment.
Thanks for porting this over, and for the clear write-up on release versus vintage times. The snapshot-dated-by-observed_at_ns choice is the right one for the no-lookahead contract, and the offline fixture tests are exactly what we want. I ran the full suite, ruff, mypy, mkdocs strict and the build locally against main and everything the PR adds passes.
A few changes before merge:
- Tracking parameters.
docs/providers/fxmacrodata.mdline 5 links to the FXMacroData reference withutm_*query parameters. Please drop the query string so the docs link to the plain URL. - Empty
revisionslist. A record with"revisions": []currently produces no observation and nolast_skippedentry (_vintagesbuilds zero entries). Could you fall back to the record's ownvalin that case, the wayrevisions: nulldoes, or at least count it under a skip reason? - Redirects.
UrllibTransportusesurlopen, whose default redirect handler forwards every header, includingX-API-Key, to the redirect target and will follow a redirect to plainhttp://. Please build an opener whoseredirect_requestreturnsNone, so a 3xx surfaces as anFxMacroDataHTTPErrorinstead of being followed. - Page count. The pagination loop ends only when the server stops saying
has_more. Please add a ceiling on the number of pages (a constant or constructor argument) and raiseFxMacroDataDataErrorwhen it is exceeded. Retry-After. The header value goes straight intoasyncio.sleep; a server sendingRetry-After: 86400would park the caller for a day. Please clamp it (for example to 60 seconds) or fall back to the backoff schedule above a cap.
Smaller points, take or leave:
quote(currency)andquote(key)use the defaultsafe="/", so a key containing/or..becomes extra path segments.safe=""or a slug check would close that.- Server error text is passed into the exception message verbatim. Redacting the key from it would make the "never in exception messages" guarantee hold even if the upstream echoes it.
- In
docs/providers.mdthe new "Macro providers" section lands above the paragraph starting "CSV, SQLite, and SQLAlchemy share runtime schema discovery", which now reads as part of the macro section. Moving the section below that paragraph fixes it. The new page is also missing the breadcrumb line the other provider pages open with. sourceis set to the publisher (BLS). Our other adapters put the adapter identity there (ccxt,sqlalchemy:<venue>#<table>).fxmacrodataorfxmacrodata:BLSwould keep provenance consistent. Happy to hear if you see it differently.UrllibTransportand theRetry-Afterand JSON-decoding helpers have no tests. A small test against a localhosthttp.serverwould cover headers, parsing and the redirect behaviour once changed. A test for thewithheld_countwarning would be good too.- The docs table of skipped records does not mention that
publication_time_status: unverifiedrecords are kept. One line saying so, and that a planned time can precede the actual release, would help users judge the data.
We can't reach the live API from CI, so the review takes the field semantics (date as period end, epoch on later source vintages being their own publication time, val always appearing in revisions) on your word. If any of those does not hold in practice, the doc page is the place to say so.
…acy snapshots by observation
|
Thanks for the careful review. All of it is in 1b904c2.
Smaller points:
On the field semantics, I checked the live keyless USD responses (16 series, 119 records, 226 revisions) against the spec:
Checks: pytest (27 in |
luisleo526
left a comment
There was a problem hiding this comment.
Thanks, Robert. Every point from the review is addressed in 1b904c2, and the follow-up went further than asked: the legacy_snapshot handling and the per-series date semantics came out of checking the live responses, which is exactly the kind of care the vintage contract needs. I re-ran the suite, ruff, mypy, mkdocs strict and the build against the new head, and probed the redirect refusal, page cap, Retry-After clamp and empty revisions handling independently; all hold. Merging. One small optional follow-up if you ever touch the tests again: clearing http_proxy in the local-server fixture would keep those three tests hermetic on machines with a proxy configured. Welcome aboard as the first macro provider in pineforge-data.
Summary
Port of the FXMacroData integration from pineforge-4pass/pineforge-engine#82, as @luisleo526 suggested there.
FxMacroDataProvider, aMacroDataProviderthat turns FXMacroData announcements intoMacroObservationrecordsreleased_at_msfrom the record'sannouncement_datetimeandvintage_at_msfrom when that value became availablerevisions,vintage_status,observed_at_ns,publication_time_status, pagination) stay inside the adapter; nothing is added to the normalized modelsurllibrun in a thread, and anFxMacroDataTransportcan be injected for offline tests or another HTTP clientdocs/providers/fxmacrodata.md, a "Macro providers" section in the catalog, mkdocs nav and API reference entries, and update the data-model line that said no macro provider shipsDisclosure: I maintain FXMacroData. It is a commercial API. Without a key only USD is available, limited to the most recent 90 days and delayed by 15 minutes; other currencies and full history need a key.
Release and vintage times
MacroObservationneeds a release time and has no "unknown" value, so records whoseannouncement_datetimeis null are skipped rather than given one.release_time_assumed) are also skipped by default.include_assumed_release_times=Truekeeps them.epochrepeats the original release time, so it is dated byobserved_at_nsinstead. Usingepochwould put a later revision at the first print's timestamp, which is the lookahead the contract is meant to prevent. The trade-off is that history collected after the fact is only visible from its collection date.released_at_ms >= period_end_msis enforced.Skipped records are counted by reason in
provider.last_skipped, so they are visible rather than silently dropped.Safety and behavior
X-API-Keyheader, never in the URL, exception messages orreprmax_retriestimes with backoff, honoringRetry-After; timeouts are explicitpagination.has_more/next_offset; a cursor that does not advance raisesFxMacroDataAccessWarningpineforge-backtestexpect aMarketDataProviderValidation
pytest— all passed except Docker-gated skips.tests/test_server.pywas not run because I ran the suite without thedevextra: the sandbox I test in blockshttpx22.5.x over open advisories (GHSA-8xx6-hgc6-gc2m, GHSA-7mj9-2mp8-4m2p onhttpcore2)tests/test_fxmacrodata.py: 14 tests, offline, using a trimmed two-page fixture of realrevisions=allresponsesruff check .andruff format --checkon the changed filesmypy src— no errors in the new module (the only errors were inserver.py, fromfastapi/pydanticnot being installed)mkdocs build --strict