OfflineCurrencyConverter 2026.11.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- offlinecurrencyconverter-2026.11.9/OfflineCurrencyConverter.egg-info/PKG-INFO +262 -0
- offlinecurrencyconverter-2026.11.9/OfflineCurrencyConverter.egg-info/SOURCES.txt +17 -0
- offlinecurrencyconverter-2026.11.9/OfflineCurrencyConverter.egg-info/dependency_links.txt +1 -0
- offlinecurrencyconverter-2026.11.9/OfflineCurrencyConverter.egg-info/entry_points.txt +2 -0
- offlinecurrencyconverter-2026.11.9/OfflineCurrencyConverter.egg-info/requires.txt +3 -0
- offlinecurrencyconverter-2026.11.9/OfflineCurrencyConverter.egg-info/top_level.txt +1 -0
- offlinecurrencyconverter-2026.11.9/PKG-INFO +262 -0
- offlinecurrencyconverter-2026.11.9/README.md +252 -0
- offlinecurrencyconverter-2026.11.9/offline_currency_converter/__init__.py +32 -0
- offlinecurrencyconverter-2026.11.9/offline_currency_converter/__main__.py +3 -0
- offlinecurrencyconverter-2026.11.9/offline_currency_converter/_version.py +1 -0
- offlinecurrencyconverter-2026.11.9/offline_currency_converter/assets.json +429 -0
- offlinecurrencyconverter-2026.11.9/offline_currency_converter/assets.py +139 -0
- offlinecurrencyconverter-2026.11.9/offline_currency_converter/cli.py +76 -0
- offlinecurrencyconverter-2026.11.9/offline_currency_converter/conversion.py +264 -0
- offlinecurrencyconverter-2026.11.9/offline_currency_converter/core.py +852 -0
- offlinecurrencyconverter-2026.11.9/offline_currency_converter/data/eurofxref_and_crypto_rates.2026.11.09.csv +10172 -0
- offlinecurrencyconverter-2026.11.9/pyproject.toml +21 -0
- offlinecurrencyconverter-2026.11.9/setup.cfg +4 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: OfflineCurrencyConverter
|
|
3
|
+
Version: 2026.11.9
|
|
4
|
+
Summary: ECB and cryptocurrency historical rates with EUR cross-conversion
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: pandas<3.0,>=2.2
|
|
8
|
+
Requires-Dist: requests<3.0,>=2.32
|
|
9
|
+
Requires-Dist: openpyxl<4.0,>=3.1
|
|
10
|
+
|
|
11
|
+
# OfflineCurrencyConverter
|
|
12
|
+
|
|
13
|
+
Historical fiat and cryptocurrency conversion for offline Python applications.
|
|
14
|
+
Requires Python 3.10 or newer.
|
|
15
|
+
|
|
16
|
+
OfflineCurrencyConverter follows the familiar
|
|
17
|
+
[CurrencyConverter API](https://github.com/alexprengere/currencyconverter),
|
|
18
|
+
including `CurrencyConverter`, `convert()`, `date=`, currency bounds, and
|
|
19
|
+
optional fallback settings. It extends this interface with cryptocurrency
|
|
20
|
+
coverage, configurable date windows, and exchange-rate quotes with snapshot
|
|
21
|
+
metadata. See [API compatibility](#api-compatibility) for supported behavior.
|
|
22
|
+
|
|
23
|
+
**Exchange-rate data is included in every package.** Automated daily builds
|
|
24
|
+
refresh the bundled historical dataset and produce a date-versioned wheel.
|
|
25
|
+
Applications receive updated rates by installing a newer package build;
|
|
26
|
+
conversion and rate lookup never require access to external data services.
|
|
27
|
+
This supports air-gapped deployments using an internal Python package index
|
|
28
|
+
or a local wheelhouse.
|
|
29
|
+
|
|
30
|
+
## Currency data
|
|
31
|
+
|
|
32
|
+
Fiat reference rates come from the European Central Bank. Cryptocurrency and
|
|
33
|
+
stablecoin rates come from CoinMarketCap. All source rates are stored as units
|
|
34
|
+
per EUR, enabling conversion between supported currencies through EUR.
|
|
35
|
+
|
|
36
|
+
Each wheel contains one cumulative CSV snapshot. Its calendar starts on
|
|
37
|
+
1999-01-04 and ends on the day before the snapshot's run date. Individual
|
|
38
|
+
currencies have different periods of coverage. Crypto uses completed daily
|
|
39
|
+
closing prices; unfinished current-day observations are excluded.
|
|
40
|
+
|
|
41
|
+
A scheduled GitHub Action generates updated data and a wheel daily at
|
|
42
|
+
16:30 Europe/Amsterdam. Successful builds are committed to [`output/`](output/)
|
|
43
|
+
and made available as workflow artifacts. Versions follow the snapshot date:
|
|
44
|
+
`2026.11.9` identifies the November 9, 2026 snapshot. Publication to public PyPI
|
|
45
|
+
is a separate, manually triggered workflow.
|
|
46
|
+
|
|
47
|
+
Fiat observations may be absent on weekends and holidays. Missing or failed
|
|
48
|
+
source observations remain unavailable until a subsequent update obtains them.
|
|
49
|
+
The installed dataset stays fixed until the package or local snapshot is
|
|
50
|
+
replaced; the converter does not refresh it automatically.
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
Download a wheel from [`output/`](output/), then install it in a virtual environment:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
python3 -m venv .venv
|
|
58
|
+
source .venv/bin/activate
|
|
59
|
+
python -m pip install /path/to/offlinecurrencyconverter-2026.11.9-py3-none-any.whl
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
For air-gapped systems, make the wheel and its dependencies available through
|
|
63
|
+
an internal PyPI mirror or a local wheelhouse. For example:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
python -m pip install --no-index --find-links=/path/to/wheelhouse OfflineCurrencyConverter
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The wheel bundles the rates, but its dependencies must also be installed.
|
|
70
|
+
To replace an installed build with an updated wheel of the same version, use
|
|
71
|
+
`python -m pip install --force-reinstall /path/to/package.whl`.
|
|
72
|
+
|
|
73
|
+
## Python API
|
|
74
|
+
|
|
75
|
+
Create a converter once and reuse it:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from offline_currency_converter import CurrencyConverter
|
|
79
|
+
|
|
80
|
+
c = CurrencyConverter()
|
|
81
|
+
euros = c.convert(100, "USD") # EUR is the default destination.
|
|
82
|
+
yen = c.convert(100, "USD", "JPY") # Latest common observation.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Choose a historical date with `date=`:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from datetime import date
|
|
89
|
+
|
|
90
|
+
amount = c.convert(100, "USD", "EUR", date=date(2026, 11, 2))
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Omitting `date`, or passing `None`, uses the latest common observation in the
|
|
94
|
+
loaded snapshot. An explicit date requires an exact observation unless a date
|
|
95
|
+
window or fallback is enabled. Dates also accept `datetime` objects,
|
|
96
|
+
`YYYY-MM-DD` strings, and `D.M.YYYY` strings.
|
|
97
|
+
|
|
98
|
+
### Decimal amounts and rounding
|
|
99
|
+
|
|
100
|
+
Conversions return floats by default. Enable `decimal=True` for `Decimal`
|
|
101
|
+
results, and pass string or `Decimal` amounts to preserve input precision:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from decimal import Decimal, ROUND_HALF_UP
|
|
105
|
+
from offline_currency_converter import CurrencyConverter
|
|
106
|
+
|
|
107
|
+
c = CurrencyConverter(decimal=True)
|
|
108
|
+
eur = c.convert("100", "USD", date="2026-11-02")
|
|
109
|
+
eur = eur.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The converter does not apply monetary rounding. Round the final amount to the
|
|
113
|
+
destination currency's minor unit: EUR and USD use two decimal places, while
|
|
114
|
+
some currencies use zero or three. Formatting with `f"{eur:.2f}"` changes only
|
|
115
|
+
the displayed text; `quantize()` returns a rounded `Decimal` value.
|
|
116
|
+
|
|
117
|
+
### Date windows
|
|
118
|
+
|
|
119
|
+
Pass `date_window` to select the nearest usable observation within an inclusive
|
|
120
|
+
window of calendar days before or after the requested date:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
amount = c.convert("100", "USD", "EUR", date="2026-11-02", date_window=30)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
An equally distant pair of observations resolves to the earlier date. Both
|
|
127
|
+
currencies must have valid rates on the same observation date. Zero requires
|
|
128
|
+
an exact match. An explicit window overrides fallback settings, including
|
|
129
|
+
out-of-range fallback, and raises `RateNotFoundError` if no match exists.
|
|
130
|
+
|
|
131
|
+
Applications requiring a 30-day policy must explicitly pass their selected
|
|
132
|
+
`date` and `date_window=30`.
|
|
133
|
+
|
|
134
|
+
### Fallbacks
|
|
135
|
+
|
|
136
|
+
Enable last-known fallback for missing dates within the currencies' date bounds:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
c = CurrencyConverter(fallback_on_missing_rate=True, decimal=True)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Enable boundary fallback for requested dates outside the available range:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
c = CurrencyConverter(
|
|
146
|
+
fallback_on_missing_rate=True,
|
|
147
|
+
fallback_on_wrong_date=True,
|
|
148
|
+
decimal=True,
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Last-known fallback selects a common observation on or before the requested
|
|
153
|
+
date. Boundary fallback uses the first or last common available observation.
|
|
154
|
+
The supported method is `fallback_on_missing_rate_method="last_known"`;
|
|
155
|
+
linear interpolation is not implemented. All quotes identify actual observations.
|
|
156
|
+
|
|
157
|
+
### Rate quotes
|
|
158
|
+
|
|
159
|
+
`get_rate()` returns an immutable `RateQuote` containing:
|
|
160
|
+
|
|
161
|
+
| Field | Meaning |
|
|
162
|
+
| --- | --- |
|
|
163
|
+
| `rate` | `Decimal` multiplier: destination units per one source unit |
|
|
164
|
+
| `rate_date` | Actual selected observation date, as `datetime.date` |
|
|
165
|
+
| `snapshot_version` | Snapshot filename date, such as `"2026.11.09"`; `None` for unrecognized custom filenames |
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from decimal import Decimal
|
|
169
|
+
|
|
170
|
+
quote = c.get_rate("USD", "EUR", date="2026-11-02", date_window=30)
|
|
171
|
+
euro_amount = Decimal("100") * quote.rate
|
|
172
|
+
print(quote.rate_date, quote.snapshot_version)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The multiplier is destination-units-per-EUR divided by source-units-per-EUR.
|
|
176
|
+
EUR has an implicit rate of 1. For identical supported currencies, the rate is
|
|
177
|
+
1 and `rate_date` is the resolved requested date, without requiring a historical
|
|
178
|
+
observation. The snapshot version describes the dataset, not per-observation
|
|
179
|
+
source provenance.
|
|
180
|
+
|
|
181
|
+
### Supported currencies and date bounds
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
print(sorted(c.currencies))
|
|
185
|
+
first_date, last_date = c.bounds["USD"]
|
|
186
|
+
print(first_date, last_date)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`currencies` includes EUR, fiat, crypto, and historical currency symbols present
|
|
190
|
+
in the snapshot. A listed currency may have no rate for a particular date.
|
|
191
|
+
`bounds` includes only currencies with usable observations. EUR bounds cover
|
|
192
|
+
the snapshot calendar, although EUR identity conversions also work outside it.
|
|
193
|
+
|
|
194
|
+
### Custom data
|
|
195
|
+
|
|
196
|
+
Load an explicit local CSV, using the same units-per-EUR format:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
c = CurrencyConverter("/path/to/snapshot.csv", decimal=True)
|
|
200
|
+
# Also accepted: CurrencyConverter(currency_file="/path/to/snapshot.csv")
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
By default, the newest filename-dated snapshot is selected across local
|
|
204
|
+
`output/` and the package's bundled data. `output_dir=` restricts selection to
|
|
205
|
+
a chosen directory. URL and ZIP inputs are not supported.
|
|
206
|
+
|
|
207
|
+
### Exceptions
|
|
208
|
+
|
|
209
|
+
- `UnknownCurrencyError`: a requested symbol is not supported by the snapshot.
|
|
210
|
+
- `RateNotFoundError`: no snapshot or suitable observation is available.
|
|
211
|
+
- `ValueError`: invalid amounts, dates, date windows, or unsupported fallback options.
|
|
212
|
+
|
|
213
|
+
Blank, `ERROR`, zero, negative, and non-finite rates are never used for conversion.
|
|
214
|
+
|
|
215
|
+
## Command line
|
|
216
|
+
|
|
217
|
+
```sh
|
|
218
|
+
offline-currency-converter 100 USD --to EUR
|
|
219
|
+
offline-currency-converter 100 USD --to EUR --date 2026-11-02 --date-window 30
|
|
220
|
+
offline-currency-converter currencies
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
The equivalent module command is `python -m offline_currency_converter` followed
|
|
224
|
+
by the same arguments. Use `--rates-path /path/to/snapshot.csv` for an explicit
|
|
225
|
+
CSV. Commands use the latest common observation when no date is supplied;
|
|
226
|
+
with `--date`, supply `--date-window` if an exact match is not required.
|
|
227
|
+
Amounts are printed without monetary rounding.
|
|
228
|
+
|
|
229
|
+
## API compatibility
|
|
230
|
+
|
|
231
|
+
This package implements the common CurrencyConverter calling interface with
|
|
232
|
+
offline fiat and crypto data. It is not a complete drop-in implementation:
|
|
233
|
+
network sources, ZIP inputs, and linear interpolation are not supported. Rates
|
|
234
|
+
for cross-currency conversion always come from the same observation date.
|
|
235
|
+
`date_window` and `get_rate()` are additional capabilities.
|
|
236
|
+
|
|
237
|
+
The original `Converter`, module-level `convert()`, and module-level `get_rate()`
|
|
238
|
+
remain available. They use `transaction_date=`, default to yesterday in
|
|
239
|
+
Europe/Amsterdam, interpret explicit `None` as today, and default to a +/-10-day
|
|
240
|
+
window. They always use `Decimal` amounts and rates:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from offline_currency_converter import get_rate, convert
|
|
244
|
+
|
|
245
|
+
quote = get_rate("USD", "EUR", transaction_date="2026-11-02", date_window=30)
|
|
246
|
+
amount = convert("100", "USD", "EUR", transaction_date="2026-11-02", date_window=30)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Development and data maintenance
|
|
250
|
+
|
|
251
|
+
See [Data generation and package maintenance](docs/operations.md) for source
|
|
252
|
+
configuration, daily scheduling, CSV/Excel formats, caching, wheel builds,
|
|
253
|
+
and optional PyPI publication.
|
|
254
|
+
|
|
255
|
+
```sh
|
|
256
|
+
python -m pip install -r requirements.txt -r requirements-release.txt
|
|
257
|
+
python -m unittest discover -v
|
|
258
|
+
python build_wheel.py
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`build_wheel.py` packages the newest existing snapshot without downloading data.
|
|
262
|
+
`python rates.py` performs the network-enabled data update separately.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
OfflineCurrencyConverter.egg-info/PKG-INFO
|
|
4
|
+
OfflineCurrencyConverter.egg-info/SOURCES.txt
|
|
5
|
+
OfflineCurrencyConverter.egg-info/dependency_links.txt
|
|
6
|
+
OfflineCurrencyConverter.egg-info/entry_points.txt
|
|
7
|
+
OfflineCurrencyConverter.egg-info/requires.txt
|
|
8
|
+
OfflineCurrencyConverter.egg-info/top_level.txt
|
|
9
|
+
offline_currency_converter/__init__.py
|
|
10
|
+
offline_currency_converter/__main__.py
|
|
11
|
+
offline_currency_converter/_version.py
|
|
12
|
+
offline_currency_converter/assets.json
|
|
13
|
+
offline_currency_converter/assets.py
|
|
14
|
+
offline_currency_converter/cli.py
|
|
15
|
+
offline_currency_converter/conversion.py
|
|
16
|
+
offline_currency_converter/core.py
|
|
17
|
+
offline_currency_converter/data/eurofxref_and_crypto_rates.2026.11.09.csv
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
offline_currency_converter
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: OfflineCurrencyConverter
|
|
3
|
+
Version: 2026.11.9
|
|
4
|
+
Summary: ECB and cryptocurrency historical rates with EUR cross-conversion
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: pandas<3.0,>=2.2
|
|
8
|
+
Requires-Dist: requests<3.0,>=2.32
|
|
9
|
+
Requires-Dist: openpyxl<4.0,>=3.1
|
|
10
|
+
|
|
11
|
+
# OfflineCurrencyConverter
|
|
12
|
+
|
|
13
|
+
Historical fiat and cryptocurrency conversion for offline Python applications.
|
|
14
|
+
Requires Python 3.10 or newer.
|
|
15
|
+
|
|
16
|
+
OfflineCurrencyConverter follows the familiar
|
|
17
|
+
[CurrencyConverter API](https://github.com/alexprengere/currencyconverter),
|
|
18
|
+
including `CurrencyConverter`, `convert()`, `date=`, currency bounds, and
|
|
19
|
+
optional fallback settings. It extends this interface with cryptocurrency
|
|
20
|
+
coverage, configurable date windows, and exchange-rate quotes with snapshot
|
|
21
|
+
metadata. See [API compatibility](#api-compatibility) for supported behavior.
|
|
22
|
+
|
|
23
|
+
**Exchange-rate data is included in every package.** Automated daily builds
|
|
24
|
+
refresh the bundled historical dataset and produce a date-versioned wheel.
|
|
25
|
+
Applications receive updated rates by installing a newer package build;
|
|
26
|
+
conversion and rate lookup never require access to external data services.
|
|
27
|
+
This supports air-gapped deployments using an internal Python package index
|
|
28
|
+
or a local wheelhouse.
|
|
29
|
+
|
|
30
|
+
## Currency data
|
|
31
|
+
|
|
32
|
+
Fiat reference rates come from the European Central Bank. Cryptocurrency and
|
|
33
|
+
stablecoin rates come from CoinMarketCap. All source rates are stored as units
|
|
34
|
+
per EUR, enabling conversion between supported currencies through EUR.
|
|
35
|
+
|
|
36
|
+
Each wheel contains one cumulative CSV snapshot. Its calendar starts on
|
|
37
|
+
1999-01-04 and ends on the day before the snapshot's run date. Individual
|
|
38
|
+
currencies have different periods of coverage. Crypto uses completed daily
|
|
39
|
+
closing prices; unfinished current-day observations are excluded.
|
|
40
|
+
|
|
41
|
+
A scheduled GitHub Action generates updated data and a wheel daily at
|
|
42
|
+
16:30 Europe/Amsterdam. Successful builds are committed to [`output/`](output/)
|
|
43
|
+
and made available as workflow artifacts. Versions follow the snapshot date:
|
|
44
|
+
`2026.11.9` identifies the November 9, 2026 snapshot. Publication to public PyPI
|
|
45
|
+
is a separate, manually triggered workflow.
|
|
46
|
+
|
|
47
|
+
Fiat observations may be absent on weekends and holidays. Missing or failed
|
|
48
|
+
source observations remain unavailable until a subsequent update obtains them.
|
|
49
|
+
The installed dataset stays fixed until the package or local snapshot is
|
|
50
|
+
replaced; the converter does not refresh it automatically.
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
Download a wheel from [`output/`](output/), then install it in a virtual environment:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
python3 -m venv .venv
|
|
58
|
+
source .venv/bin/activate
|
|
59
|
+
python -m pip install /path/to/offlinecurrencyconverter-2026.11.9-py3-none-any.whl
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
For air-gapped systems, make the wheel and its dependencies available through
|
|
63
|
+
an internal PyPI mirror or a local wheelhouse. For example:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
python -m pip install --no-index --find-links=/path/to/wheelhouse OfflineCurrencyConverter
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The wheel bundles the rates, but its dependencies must also be installed.
|
|
70
|
+
To replace an installed build with an updated wheel of the same version, use
|
|
71
|
+
`python -m pip install --force-reinstall /path/to/package.whl`.
|
|
72
|
+
|
|
73
|
+
## Python API
|
|
74
|
+
|
|
75
|
+
Create a converter once and reuse it:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from offline_currency_converter import CurrencyConverter
|
|
79
|
+
|
|
80
|
+
c = CurrencyConverter()
|
|
81
|
+
euros = c.convert(100, "USD") # EUR is the default destination.
|
|
82
|
+
yen = c.convert(100, "USD", "JPY") # Latest common observation.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Choose a historical date with `date=`:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from datetime import date
|
|
89
|
+
|
|
90
|
+
amount = c.convert(100, "USD", "EUR", date=date(2026, 11, 2))
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Omitting `date`, or passing `None`, uses the latest common observation in the
|
|
94
|
+
loaded snapshot. An explicit date requires an exact observation unless a date
|
|
95
|
+
window or fallback is enabled. Dates also accept `datetime` objects,
|
|
96
|
+
`YYYY-MM-DD` strings, and `D.M.YYYY` strings.
|
|
97
|
+
|
|
98
|
+
### Decimal amounts and rounding
|
|
99
|
+
|
|
100
|
+
Conversions return floats by default. Enable `decimal=True` for `Decimal`
|
|
101
|
+
results, and pass string or `Decimal` amounts to preserve input precision:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from decimal import Decimal, ROUND_HALF_UP
|
|
105
|
+
from offline_currency_converter import CurrencyConverter
|
|
106
|
+
|
|
107
|
+
c = CurrencyConverter(decimal=True)
|
|
108
|
+
eur = c.convert("100", "USD", date="2026-11-02")
|
|
109
|
+
eur = eur.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The converter does not apply monetary rounding. Round the final amount to the
|
|
113
|
+
destination currency's minor unit: EUR and USD use two decimal places, while
|
|
114
|
+
some currencies use zero or three. Formatting with `f"{eur:.2f}"` changes only
|
|
115
|
+
the displayed text; `quantize()` returns a rounded `Decimal` value.
|
|
116
|
+
|
|
117
|
+
### Date windows
|
|
118
|
+
|
|
119
|
+
Pass `date_window` to select the nearest usable observation within an inclusive
|
|
120
|
+
window of calendar days before or after the requested date:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
amount = c.convert("100", "USD", "EUR", date="2026-11-02", date_window=30)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
An equally distant pair of observations resolves to the earlier date. Both
|
|
127
|
+
currencies must have valid rates on the same observation date. Zero requires
|
|
128
|
+
an exact match. An explicit window overrides fallback settings, including
|
|
129
|
+
out-of-range fallback, and raises `RateNotFoundError` if no match exists.
|
|
130
|
+
|
|
131
|
+
Applications requiring a 30-day policy must explicitly pass their selected
|
|
132
|
+
`date` and `date_window=30`.
|
|
133
|
+
|
|
134
|
+
### Fallbacks
|
|
135
|
+
|
|
136
|
+
Enable last-known fallback for missing dates within the currencies' date bounds:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
c = CurrencyConverter(fallback_on_missing_rate=True, decimal=True)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Enable boundary fallback for requested dates outside the available range:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
c = CurrencyConverter(
|
|
146
|
+
fallback_on_missing_rate=True,
|
|
147
|
+
fallback_on_wrong_date=True,
|
|
148
|
+
decimal=True,
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Last-known fallback selects a common observation on or before the requested
|
|
153
|
+
date. Boundary fallback uses the first or last common available observation.
|
|
154
|
+
The supported method is `fallback_on_missing_rate_method="last_known"`;
|
|
155
|
+
linear interpolation is not implemented. All quotes identify actual observations.
|
|
156
|
+
|
|
157
|
+
### Rate quotes
|
|
158
|
+
|
|
159
|
+
`get_rate()` returns an immutable `RateQuote` containing:
|
|
160
|
+
|
|
161
|
+
| Field | Meaning |
|
|
162
|
+
| --- | --- |
|
|
163
|
+
| `rate` | `Decimal` multiplier: destination units per one source unit |
|
|
164
|
+
| `rate_date` | Actual selected observation date, as `datetime.date` |
|
|
165
|
+
| `snapshot_version` | Snapshot filename date, such as `"2026.11.09"`; `None` for unrecognized custom filenames |
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from decimal import Decimal
|
|
169
|
+
|
|
170
|
+
quote = c.get_rate("USD", "EUR", date="2026-11-02", date_window=30)
|
|
171
|
+
euro_amount = Decimal("100") * quote.rate
|
|
172
|
+
print(quote.rate_date, quote.snapshot_version)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The multiplier is destination-units-per-EUR divided by source-units-per-EUR.
|
|
176
|
+
EUR has an implicit rate of 1. For identical supported currencies, the rate is
|
|
177
|
+
1 and `rate_date` is the resolved requested date, without requiring a historical
|
|
178
|
+
observation. The snapshot version describes the dataset, not per-observation
|
|
179
|
+
source provenance.
|
|
180
|
+
|
|
181
|
+
### Supported currencies and date bounds
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
print(sorted(c.currencies))
|
|
185
|
+
first_date, last_date = c.bounds["USD"]
|
|
186
|
+
print(first_date, last_date)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`currencies` includes EUR, fiat, crypto, and historical currency symbols present
|
|
190
|
+
in the snapshot. A listed currency may have no rate for a particular date.
|
|
191
|
+
`bounds` includes only currencies with usable observations. EUR bounds cover
|
|
192
|
+
the snapshot calendar, although EUR identity conversions also work outside it.
|
|
193
|
+
|
|
194
|
+
### Custom data
|
|
195
|
+
|
|
196
|
+
Load an explicit local CSV, using the same units-per-EUR format:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
c = CurrencyConverter("/path/to/snapshot.csv", decimal=True)
|
|
200
|
+
# Also accepted: CurrencyConverter(currency_file="/path/to/snapshot.csv")
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
By default, the newest filename-dated snapshot is selected across local
|
|
204
|
+
`output/` and the package's bundled data. `output_dir=` restricts selection to
|
|
205
|
+
a chosen directory. URL and ZIP inputs are not supported.
|
|
206
|
+
|
|
207
|
+
### Exceptions
|
|
208
|
+
|
|
209
|
+
- `UnknownCurrencyError`: a requested symbol is not supported by the snapshot.
|
|
210
|
+
- `RateNotFoundError`: no snapshot or suitable observation is available.
|
|
211
|
+
- `ValueError`: invalid amounts, dates, date windows, or unsupported fallback options.
|
|
212
|
+
|
|
213
|
+
Blank, `ERROR`, zero, negative, and non-finite rates are never used for conversion.
|
|
214
|
+
|
|
215
|
+
## Command line
|
|
216
|
+
|
|
217
|
+
```sh
|
|
218
|
+
offline-currency-converter 100 USD --to EUR
|
|
219
|
+
offline-currency-converter 100 USD --to EUR --date 2026-11-02 --date-window 30
|
|
220
|
+
offline-currency-converter currencies
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
The equivalent module command is `python -m offline_currency_converter` followed
|
|
224
|
+
by the same arguments. Use `--rates-path /path/to/snapshot.csv` for an explicit
|
|
225
|
+
CSV. Commands use the latest common observation when no date is supplied;
|
|
226
|
+
with `--date`, supply `--date-window` if an exact match is not required.
|
|
227
|
+
Amounts are printed without monetary rounding.
|
|
228
|
+
|
|
229
|
+
## API compatibility
|
|
230
|
+
|
|
231
|
+
This package implements the common CurrencyConverter calling interface with
|
|
232
|
+
offline fiat and crypto data. It is not a complete drop-in implementation:
|
|
233
|
+
network sources, ZIP inputs, and linear interpolation are not supported. Rates
|
|
234
|
+
for cross-currency conversion always come from the same observation date.
|
|
235
|
+
`date_window` and `get_rate()` are additional capabilities.
|
|
236
|
+
|
|
237
|
+
The original `Converter`, module-level `convert()`, and module-level `get_rate()`
|
|
238
|
+
remain available. They use `transaction_date=`, default to yesterday in
|
|
239
|
+
Europe/Amsterdam, interpret explicit `None` as today, and default to a +/-10-day
|
|
240
|
+
window. They always use `Decimal` amounts and rates:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from offline_currency_converter import get_rate, convert
|
|
244
|
+
|
|
245
|
+
quote = get_rate("USD", "EUR", transaction_date="2026-11-02", date_window=30)
|
|
246
|
+
amount = convert("100", "USD", "EUR", transaction_date="2026-11-02", date_window=30)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Development and data maintenance
|
|
250
|
+
|
|
251
|
+
See [Data generation and package maintenance](docs/operations.md) for source
|
|
252
|
+
configuration, daily scheduling, CSV/Excel formats, caching, wheel builds,
|
|
253
|
+
and optional PyPI publication.
|
|
254
|
+
|
|
255
|
+
```sh
|
|
256
|
+
python -m pip install -r requirements.txt -r requirements-release.txt
|
|
257
|
+
python -m unittest discover -v
|
|
258
|
+
python build_wheel.py
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`build_wheel.py` packages the newest existing snapshot without downloading data.
|
|
262
|
+
`python rates.py` performs the network-enabled data update separately.
|