A minimal command-line tool and Python library for fetching EUR foreign exchange rates directly from the ECB SDMX API.
pip install ecbfxFor development (includes test dependencies):
git clone https://github.com/edvinassvedas-dev/ecbfx.git
cd ecbfx
pip install -e ".[dev]"
pytest# 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
ecbfx USD 2025-01-15
# Multiple currencies, specific date
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
ecbfx USD --latest --quiet
RATE=$(ecbfx USD --quiet)
# Control decimal precision (default: 4)
ecbfx USD 2025-01-15 --decimal 6
# Strict mode — error instead of gap-filling on weekends/holidays
ecbfx USD 2025-01-15 --no-gap-fill
# CSV output (pipe-friendly)
ecbfx USD --from 2025-01-01 --to 2025-01-31 --csv
ecbfx USD --from 2025-01-01 --to 2025-01-31 --csv > rates.csv
# Read pairs from a file — one HTTP call per currency
ecbfx --pairs transactions.csv --direct --csv
# Read pairs from stdin
cat transactions.csv | ecbfx --pairs - --direct --csv
# Combine with date filter
ecbfx --pairs transactions.csv --from 2025-01-01 --to 2025-03-31 --csv| 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.
| Flag | Default | Description |
|---|---|---|
--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 |
4 | Decimal places in output rate |
--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 |
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.
Use --no-gap-fill to disable this and receive an explicit error instead —
useful in audit workflows where a substituted rate is not acceptable.
| 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 $?"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"),
(date(2025, 2, 3), "CHF"),
]
rows = fetch_rates_for_pairs(pairs, direct=True)
# Custom decimal precision
rows = fetch_rates(["USD"], date(2025, 1, 15), date(2025, 1, 15), decimals=6)
# Strict mode — raises ECBError on weekends/holidays
rows = fetch_rates(["USD"], date(2025, 1, 13), date(2025, 1, 13), gap_fill=False)
rows = fetch_rates_for_pairs([(date(2025, 1, 13), "USD")], gap_fill=False)
for r in rows:
print(r["date"], r["currency"], r["convention"], r["rate"])Each row is a dict: {date, currency, rate, convention}.
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 2–4 ASCII letters.Use ECBError in try/except blocks when calling any ecbfx function
to handle API failures, network errors, or invalid inputs cleanly.
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 |
| Best for | daily pipelines, backfill | transaction enrichment, broker CSVs |
| HTTP calls | one per currency | one per currency (full span, regardless of gaps) |
Note:
fetch_rates_for_pairsalways fetches the full date span from the earliest to the latest date per currency in a single HTTP call. For very sparse data (e.g. two transactions 10 years apart), this fetches the entire intervening range. A warning is logged when the span exceeds one year.
MIT