Metadata-Version: 2.4
Name: ecbfx
Version: 0.2.0
Summary: ECB foreign exchange rate lookup — CLI and Python API with gap-fill, strict mode, and scripting support
Project-URL: Homepage, https://github.com/edvinassvedas-dev/ecbfx
Project-URL: Repository, https://github.com/edvinassvedas-dev/ecbfx
Project-URL: Issues, https://github.com/edvinassvedas-dev/ecbfx/issues
Author: Edvinas Švedas
License: MIT
License-File: LICENSE
Keywords: cli,currency,ecb,exchange-rate,finance,forex,fx
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Utilities
Requires-Python: >=3.9
Requires-Dist: requests>=2.28
Provides-Extra: dev
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: responses; extra == 'dev'
Description-Content-Type: text/markdown

# ecbfx

[![PyPI version](https://img.shields.io/pypi/v/ecbfx.svg)](https://pypi.org/project/ecbfx/)
[![Python](https://img.shields.io/pypi/pyversions/ecbfx.svg)](https://pypi.org/project/ecbfx/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/edvinassvedas-dev/ecbfx/blob/main/LICENSE)

A minimal command-line tool and Python library for fetching EUR foreign exchange
rates directly from the [ECB SDMX API](https://data-api.ecb.europa.eu).

---

## Install

```bash
pip install ecbfx
```

**For development** (includes test dependencies):

```bash
git clone https://github.com/edvinassvedas-dev/ecbfx.git
cd ecbfx
pip install -e ".[dev]"
pytest
```

## CLI usage

```bash
# 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
```

### 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 |
|---|---|---|
| `--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 |

### 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.

Use `--no-gap-fill` to disable this 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:
```bash
ecbfx USD --quiet || echo "fetch failed, exit $?"
```

## Python API

```python
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}`.

### Input validation

```python
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 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 |
| 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_pairs` always 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.


---

## License

MIT
