Skip to content

Commit a943d46

Browse files
committed
fix(forex): replace CurrencyFreaks with ECB
- Fetch GBP/USD through Frankfurter with a seven-day backward-only fallback and strict validation - Preserve saved observations and backfill August 26–31, 2026 - Fail incomplete fetches and remove the unused cron API key dependency Agent-Session: codex/01a080b3-6571-7580-9a47-1d7202ec529f
1 parent 42e9f35 commit a943d46

9 files changed

Lines changed: 244 additions & 207 deletions

File tree

.github/workflows/cron-update-data.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ jobs:
2525
COINGECKO_API_KEY: ${{ secrets.COINGECKO_API_KEY }}
2626
run: just fetch-crypto-recent --currency all
2727

28-
- name: Fetch forex rates
29-
env:
30-
CURRENCY_FREAKS_API_KEY: ${{ secrets.CURRENCY_FREAKS_API_KEY }}
28+
- name: Fetch ECB forex rates
3129
run: just fetch-forex
3230

3331
- name: Validate TSV files

AGENTS.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This document contains instructions for AI agents working on the Sablier Price D
66

77
This repo provides centralized cryptocurrency and forex exchange rate data used across Sablier projects. The data is
88
stored in TSV format and sourced from [CoinGecko](https://coingecko.com/api) (crypto prices) and
9-
[CurrencyFreaks](https://currencyfreaks.com/) (forex rates).
9+
[Frankfurter](https://frankfurter.dev/) with `providers=ECB` (forex rates; no API key required).
1010

1111
## Development
1212

@@ -18,7 +18,7 @@ stored in TSV format and sourced from [CoinGecko](https://coingecko.com/api) (cr
1818
- `just tsv-check` - Validate TSV files against schema
1919
- `ni <package>` - Install dependency (`ni -D` for dev dependency)
2020
- `just fetch-crypto --currency <symbol>` - Fetch crypto prices from CoinGecko
21-
- `just fetch-forex` - Fetch GBP/USD rates from CurrencyFreaks
21+
- `just fetch-forex` - Append missing GBP/USD rates from Frankfurter (ECB)
2222

2323
## TSV Format
2424

@@ -28,3 +28,13 @@ stored in TSV format and sourced from [CoinGecko](https://coingecko.com/api) (cr
2828
- **Price format**: Decimal number (high precision), no quotes
2929
- **Structure**: `id` (date) and `output` (price) columns
3030
- **Validation**: Use `just tsv-check` to validate TSV files
31+
32+
## Forex provider policy
33+
34+
`fetch-forex` preserves existing observations and fills missing dates only. CurrencyFreaks observations through
35+
2026-08-25 remain unchanged; new observations use ECB via Frankfurter v2. GBP is the base, USD is the quote.
36+
Weekend/holiday dates use the nearest earlier ECB observation, at most seven days before the requested date. Never use a
37+
future observation, change provider implicitly, or turn an API failure into a successful partial month. Only HTTP 404
38+
triggers backward lookup; other errors fail the command. Current-month runs stop at yesterday in UTC.
39+
40+
Run provider tests with `node --import tsx --test src/cli/fetch-forex/frankfurter-client.test.ts`.

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@ By centralizing the data here, we avoid duplication and ensure consistency acros
1818
All price data is sourced from industry-standard APIs:
1919

2020
- **Cryptocurrency Prices**: [CoinGecko API](https://coingecko.com/api) - Daily historical prices in USD
21-
- **Forex Exchange Rates**: [CurrencyFreaks API](https://currencyfreaks.com/) - Daily GBP/USD exchange rates
21+
- **Forex Exchange Rates**: [Frankfurter API](https://frankfurter.dev/) - ECB GBP/USD reference rates, no API key
22+
required
23+
24+
Forex observations through August 25, 2026 retain their original CurrencyFreaks values. From August 26, missing dates
25+
are filled from Frankfurter v2 with the provider pinned to ECB. Weekend and holiday dates carry the nearest earlier ECB
26+
observation, up to seven days back. Fetches preserve saved observations and fail if a requested date cannot be resolved.
27+
Run `just fetch-forex --year 2026 --month 08` to fill a specific month.
2228

2329
## Data Structure
2430

@@ -47,7 +53,7 @@ We use this data structure to make it easier to use the files in our
4753
- Dates are in UTC timezone
4854
- Dates are sorted chronologically
4955
- No duplicate dates within a file
50-
- Prices are daily closing prices (00:00 UTC)
56+
- Crypto prices use daily closing observations; forex uses ECB reference rates with the fallback policy above
5157

5258
## Installation
5359

forex/GBP_USD.tsv

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1695,4 +1695,10 @@ id output
16951695
"2026-08-22" 1.3634
16961696
"2026-08-23" 1.3653
16971697
"2026-08-24" 1.3638
1698-
"2026-08-25" 1.3647
1698+
"2026-08-25" 1.3647
1699+
"2026-08-26" 1.363
1700+
"2026-08-27" 1.3582
1701+
"2026-08-28" 1.3583
1702+
"2026-08-29" 1.3583
1703+
"2026-08-30" 1.3583
1704+
"2026-08-31" 1.3539

justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ YEAR := ```
4848
--currency {{ currency }} \
4949
--recent-days {{ days }}
5050

51-
# Fetch daily GBP/USD forex rates from CurrencyFreaks
51+
# Fetch daily GBP/USD forex rates from Frankfurter (ECB)
5252
[arg("year", long)]
5353
[arg("month", long)]
5454
@fetch-forex year=YEAR month="all" *args:

src/cli/fetch-forex.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import dayjs from "dayjs";
99
import utc from "dayjs/plugin/utc.js";
1010
import ora from "ora";
1111
import { toFloat } from "radash";
12-
import { fetchDailyForexRates } from "./fetch-forex/currencyfreaks-client.js";
12+
import { fetchDailyForexRates } from "./fetch-forex/frankfurter-client.js";
1313
import { getForexTsvPath, updateTsvFile } from "./fetch-forex/tsv-utils.js";
1414

1515
dayjs.extend(utc);
@@ -257,7 +257,7 @@ async function fetchForexRatesAction(options: FetchForexRatesOptions): Promise<v
257257
const spinner = ora(`Fetching ${yearStr}-${monthStr}`).start();
258258

259259
try {
260-
// Fetch daily rates from CurrencyFreaks API
260+
// Fetch daily rates from Frankfurter API (ECB)
261261
const forexRates = await fetchDailyForexRates(year, month);
262262

263263
// Update TSV file with new data
@@ -299,6 +299,9 @@ async function fetchForexRatesAction(options: FetchForexRatesOptions): Promise<v
299299

300300
// Display summary table
301301
displaySummaryTable(results);
302+
if (results.some((result) => result.status === "error")) {
303+
throw new Error("One or more forex months failed to fetch");
304+
}
302305
return;
303306
}
304307

@@ -317,7 +320,7 @@ async function fetchForexRatesAction(options: FetchForexRatesOptions): Promise<v
317320
const spinner = ora(`Fetching GBP/USD forex rates for ${year}-${month}`).start();
318321

319322
try {
320-
// Fetch daily rates from CurrencyFreaks API
323+
// Fetch daily rates from Frankfurter API (ECB)
321324
const forexRates = await fetchDailyForexRates(yearNum, monthNum);
322325

323326
if (forexRates.length === 0) {
@@ -353,7 +356,7 @@ function createFetchForexRatesCommand(): Command {
353356
const command = new Command();
354357

355358
command
356-
.description("Fetch daily GBP/USD forex rates from CurrencyFreaks API")
359+
.description("Fetch daily GBP/USD forex rates from Frankfurter API (ECB)")
357360
.option("--year <YYYY>", "Year in YYYY format (defaults to current year)")
358361
.option(
359362
"--month <MM>",

src/cli/fetch-forex/currencyfreaks-client.ts

Lines changed: 0 additions & 194 deletions
This file was deleted.

0 commit comments

Comments
 (0)