ecbfx
A minimal command-line tool and Python library for fetching EUR foreign exchange rates directly from the ECB SDMX API.
Install
pip install ecbfx
Working on ecbfx:
git clone https://github.com/edvinassvedas-dev/ecbfx.git
cd ecbfx && pip install -e ".[dev]"
pytest
CLI usage
# Today's rate for USD (indirect: USD per 1 EUR — ECB native)
# Note: ECB publishes rates ~16:00 CET on trading days.
# If today's rate isn't available yet, use --latest instead.
ecbfx USD
# Specific date, and multiple currencies at once
ecbfx USD 2025-01-15
ecbfx USD GBP CHF 2025-01-15
# Date range
ecbfx USD GBP --from 2025-01-01 --to 2025-03-31
# Direct convention: EUR per 1 foreign unit (inverted)
ecbfx USD 2025-01-15 --direct
# Most recent available rate (ignores today being a weekend/holiday)
ecbfx USD --latest
# Single value only — ideal for shell scripting
RATE=$(ecbfx USD --quiet)
# CSV output (pipe-friendly)
ecbfx USD --from 2025-01-01 --to 2025-01-31 --csv > rates.csv
# Read (date, currency) pairs from a file or stdin — one HTTP call per currency
ecbfx --pairs transactions.csv --direct --csv
cat transactions.csv | ecbfx --pairs - --direct --csv
Convention
| Flag | Formula | Example |
|---|---|---|
| (default) | foreign units per 1 EUR | 1 EUR = 1.0830 USD |
--direct |
EUR per 1 foreign unit | 1 USD = 0.9234 EUR |
ECB publishes indirect natively. --direct inverts the rate.
The convention column in CSV output (USD/EUR or EUR/USD) makes the
direction explicit for downstream pipelines.
Flags reference
| Flag | Default | Description |
|---|---|---|
--from YYYY-MM-DD |
— | Start of date range (inclusive). Also filters --pairs input |
--to YYYY-MM-DD |
— | End of date range (inclusive). Requires --from in range mode |
--direct |
off | EUR per 1 foreign unit instead of ECB native |
--latest |
off | Most recent available rate, regardless of date |
--quiet / -q |
off | Print rate value(s) only — ideal for scripting |
--decimal N / --decimals N |
4 | Output precision. Decimal places normally; with --direct a floor on significant digits, so high-ratio currencies (JPY, HUF, KRW) keep full precision instead of collapsing to 0.0007 |
--timeout N |
30 | HTTP timeout in seconds, per attempt (3 attempts with backoff) |
--no-gap-fill |
off | Raise an error on weekends/holidays instead of substituting the nearest rate |
--csv |
off | CSV output instead of formatted table |
--pairs FILE|- |
— | Read date,currency pairs from a file or stdin. Comma, tab or space separated; surrounding quotes are tolerated, so spreadsheet exports work as-is |
--version |
— | Print the installed version and exit |
Weekend and holiday gap-filling
ECB only publishes rates on trading days. By default, ecbfx automatically
uses the most recent prior trading day's rate (Last Observation Carried Forward)
when a requested date falls on a weekend or public holiday — including the first
date in a range that starts on a holiday such as January 1st.
A future date is never substituted, so a rate is never influenced by information that did not exist on the date requested. If no trading day exists within 5 calendar days before the target — the widest real gap in the ECB calendar — the lookup fails rather than reaching further back for a staler rate.
Use --no-gap-fill to disable substitution entirely and receive an explicit
error instead — useful in audit workflows where a substituted rate is not
acceptable.
Exit codes
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Runtime error (ECB API failure, network issue, no data returned) |
2 |
Usage error (invalid arguments, bad date format, missing required flag) |
Useful for scripting:
ecbfx USD --quiet || echo "fetch failed, exit $?"
Python API
from datetime import date
from ecbfx import fetch_rates, fetch_rates_for_pairs, fetch_latest
# Indirect (default) — foreign units per 1 EUR, contiguous range
rows = fetch_rates(["USD", "GBP"], date(2025, 1, 1), date(2025, 1, 31))
# Direct — EUR per 1 foreign unit
rows = fetch_rates(["USD"], date(2025, 1, 15), date(2025, 1, 15), direct=True)
# Most recent available rate
rows = fetch_latest(["USD", "CHF"])
# Sparse transaction dates — one HTTP call per currency regardless of pair count
pairs = [
(date(2025, 1, 15), "USD"),
(date(2025, 1, 20), "GBP"),
(date(2025, 2, 3), "USD"),
]
rows = fetch_rates_for_pairs(pairs, direct=True)
# Strict mode — raises ECBError on weekends/holidays instead of substituting
rows = fetch_rates(["USD"], date(2025, 1, 13), date(2025, 1, 13), gap_fill=False)
for r in rows:
print(r["date"], r["currency"], r["convention"], r["rate"])
Both fetch functions accept direct, decimals, gap_fill, timeout and an
optional session for connection reuse.
Behaviour worth knowing
fetch_ratesis all-or-nothing across currencies. A malformed code raises before any request. A well-formed code the ECB will not serve raises partway through: rows already fetched are discarded and later currencies are never requested. Call once per currency if you need per-currency isolation.- Duplicate currencies collapse.
["USD", "usd"]is one HTTP call and one row per date. - Output order differs between the two functions — see the table below.
Input validation
from ecbfx import validate_currency, ECBError
# Normalises and validates a currency code — raises ECBError if invalid
print(validate_currency("usd")) # → "USD"
print(validate_currency(" GBP ")) # → "GBP"
try:
validate_currency("US$")
except ECBError as e:
print(e)
# Invalid currency code 'US$'. Expected a 3-letter ISO 4217 code, e.g. USD, GBP, CHF.
Use ECBError in try/except blocks when calling any ecbfx function
to handle API failures, network errors, or invalid inputs cleanly.
fetch_rates vs fetch_rates_for_pairs
fetch_rates |
fetch_rates_for_pairs |
|
|---|---|---|
| Input | currency list + date range | list of (date, currency) tuples |
| Returns | every calendar day in range | exactly the requested dates |
| Order | sorted by (date, currency) |
matches input order, repeats included |
| Best for | daily pipelines, backfill | transaction enrichment, broker CSVs |
Both make one HTTP call per currency.
Note:
fetch_rates_for_pairsfetches the full span from the earliest to the latest date per currency in that single call. For very sparse data (e.g. two transactions 10 years apart) this pulls the entire intervening range; a warning is logged when the span exceeds one year.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ecbfx-0.3.0.tar.gz.
File metadata
- Download URL: ecbfx-0.3.0.tar.gz
- Upload date:
- Size: 35.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
95e23d4a452d9bd49638dc58be03d498ca7d352a2ed201e74582d79e974e5e6d
|
|
| MD5 |
ea02a7a2612d6e664baffdabba2e5ca0
|
|
| BLAKE2b-256 |
84b4298b7c5dd8e80a4147688409d857eab7ead642c64c5a69e47c7fd35cf82b
|
File details
Details for the file ecbfx-0.3.0-py3-none-any.whl.
File metadata
- Download URL: ecbfx-0.3.0-py3-none-any.whl
- Upload date:
- Size: 21.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fe1325c66c9aa339e8551d5a7721bfde106cc342bef504931f7974f199020e52
|
|
| MD5 |
a14a6da8f8fba0cd278adb4cf6a996e3
|
|
| BLAKE2b-256 |
cc0b5d1a0359a03612a0a7039006867e51fc1cc94157f6d5e1c8eb005c4726b8
|