SPB Git forge

spb/hfmarketdata

Public

Open high-frequency market data platform — FirstRate full-history downloader, DuckDB/Parquet lake, open REST API and React docs platform (www.hfmarketdata.io)

127commits 1branches 0releases
24.7 MBsize
maindefault branch
11 days agolast push
JavaScript 53.7% Python 38.3% CSS 4.6% TypeScript 3.1%

tests: client authentifié high_usage (client_hu) pour les tests futures volumineux — le quota keyless (5 000 lignes) s'applique désormais

Simon-Pierre Boucher committed 20 days ago (Sep 4, 2026) parent c994834

3 changed files +36 −27

modified tests/conftest.py +9 −0
@@ -81,3 +81,12 @@ def make_user(app):
81 81 uid = u.id
82 82 return uid, raw, email, password
83 83 return _make
84 +
85 +
86 +@pytest.fixture
87 +def client_hu(app, make_user):
88 + """Client authenticated with a high_usage API key (600 req/min, 200 000 rows/request) — for data-heavy tests."""
89 + from fastapi.testclient import TestClient
90 + _, raw, _, _ = make_user(tier="high_usage")
91 + with TestClient(app, headers={"Authorization": f"Bearer {raw}"}) as c:
92 + yield c
modified tests/test_futures_api.py +15 −15
@@ -150,19 +150,19 @@ def test_contract_bars_from_to_datetime_bounds(client, backfilled):
150 150 assert r.status_code == 400 and r.json()["error"]["code"] == "INVALID_PARAMETER"
151 151
152 152
153 −def test_cursor_pagination(client, backfilled):
154 − r1 = client.get("/v1/futures/contract/ESH25/bars?interval=5m&limit=7")
153 +def test_cursor_pagination(client_hu, backfilled):
154 + r1 = client_hu.get("/v1/futures/contract/ESH25/bars?interval=5m&limit=7")
155 155 b1 = r1.json()
156 156 assert len(b1["data"]) == 7 and b1["meta"]["next_cursor"]
157 − r2 = client.get(f"/v1/futures/contract/ESH25/bars?interval=5m&limit=7&cursor={b1['meta']['next_cursor']}")
157 + r2 = client_hu.get(f"/v1/futures/contract/ESH25/bars?interval=5m&limit=7&cursor={b1['meta']['next_cursor']}")
158 158 b2 = r2.json()
159 159 assert len(b2["data"]) == 7 and b2["data"][0]["datetime"] > b1["data"][-1]["datetime"]
160 − full = client.get("/v1/futures/contract/ESH25/bars?interval=5m&limit=14").json()["data"]
160 + full = client_hu.get("/v1/futures/contract/ESH25/bars?interval=5m&limit=14").json()["data"]
161 161 assert [x["datetime"] for x in b1["data"] + b2["data"]] == [x["datetime"] for x in full]
162 − last = client.get("/v1/futures/contract/ESH25/bars?interval=1d&limit=100000").json()
162 + last = client_hu.get("/v1/futures/contract/ESH25/bars?interval=1d&limit=100000").json()
163 163 assert last["meta"]["next_cursor"] is None
164 − assert client.get("/v1/futures/contract/ESH25/bars?cursor=%%%").json()["error"]["code"] == "INVALID_PARAMETER"
165 − assert client.get("/v1/futures/contract/ESH25/bars?limit=0").json()["error"]["code"] == "INVALID_PARAMETER"
164 + assert client_hu.get("/v1/futures/contract/ESH25/bars?cursor=%%%").json()["error"]["code"] == "INVALID_PARAMETER"
165 + assert client_hu.get("/v1/futures/contract/ESH25/bars?limit=0").json()["error"]["code"] == "INVALID_PARAMETER"
166 166
167 167
168 168 def test_bars_csv_and_parquet(client, backfilled):
@@ -178,9 +178,9 @@ def test_bars_csv_and_parquet(client, backfilled):
178 178 assert len(df) == 50 and str(df["datetime"].dtype).endswith("UTC]") and list(df.columns)[:2] == ["symbol", "datetime"]
179 179
180 180
181 −def test_bars_merge_archive_and_update_dedup(client, backfilled):
181 +def test_bars_merge_archive_and_update_dedup(client_hu, backfilled):
182 182 """ESH25 exists in both buckets (archive ≤ 2024-12-31, update full): no duplicate dates."""
183 − d = client.get("/v1/futures/contract/ESH25/bars?interval=1d&limit=100000").json()["data"]
183 + d = client_hu.get("/v1/futures/contract/ESH25/bars?interval=1d&limit=100000").json()["data"]
184 184 dates = [x["datetime"] for x in d]
185 185 assert len(dates) == len(set(dates)) and dates[0] < "2024-12-31" < dates[-1]
186 186
@@ -290,8 +290,8 @@ def test_continuous_depth(client, backfilled):
290 290 assert client.get("/v1/futures/ES/continuous?depth=4").json()["error"]["code"] == "INVALID_PARAMETER"
291 291
292 292
293 −def test_continuous_intraday_applies_daily_schedule(client, backfilled):
294 − r = client.get("/v1/futures/ES/continuous?interval=1m&roll=volume&from=2025-06-10&to=2025-06-25&session=rth&limit=100000")
293 +def test_continuous_intraday_applies_daily_schedule(client_hu, backfilled):
294 + r = client_hu.get("/v1/futures/ES/continuous?interval=1m&roll=volume&from=2025-06-10&to=2025-06-25&session=rth&limit=100000")
295 295 assert r.status_code == 200
296 296 body = r.json()
297 297 syms = [x["symbol"] for x in body["data"]]
@@ -300,12 +300,12 @@ def test_continuous_intraday_applies_daily_schedule(client, backfilled):
300 300 assert ts == sorted(ts) and all(t.endswith("Z") for t in ts) and ts[0] >= "2025-06-10T13:30:00Z"
301 301 assert len(body["meta"]["roll_dates"]) == 1 and body["meta"]["roll_dates"][0]["to_symbol"] == "ESU25"
302 302 assert "open_interest" not in body["data"][0]
303 − page = client.get("/v1/futures/ES/continuous?interval=1m&roll=volume&from=2025-06-10&to=2025-06-25&session=rth&limit=100").json()
304 − nxt = client.get(f"/v1/futures/ES/continuous?interval=1m&roll=volume&from=2025-06-10&to=2025-06-25&session=rth&limit=100&cursor={page['meta']['next_cursor']}").json()
303 + page = client_hu.get("/v1/futures/ES/continuous?interval=1m&roll=volume&from=2025-06-10&to=2025-06-25&session=rth&limit=100").json()
304 + nxt = client_hu.get(f"/v1/futures/ES/continuous?interval=1m&roll=volume&from=2025-06-10&to=2025-06-25&session=rth&limit=100&cursor={page['meta']['next_cursor']}").json()
305 305 assert nxt["data"][0]["datetime"] > page["data"][-1]["datetime"]
306 − r = client.get("/v1/futures/ES/continuous?interval=1m&from=2025-06-10&to=2025-06-12&format=parquet")
306 + r = client_hu.get("/v1/futures/ES/continuous?interval=1m&from=2025-06-10&to=2025-06-12&format=parquet")
307 307 assert r.status_code == 200 and int(r.headers["X-Row-Count"]) > 0
308 − r = client.get("/v1/futures/ES/continuous?from=2025-06-10&to=2025-06-12&format=csv")
308 + r = client_hu.get("/v1/futures/ES/continuous?from=2025-06-10&to=2025-06-12&format=csv")
309 309 assert r.text.splitlines()[0].startswith("symbol,datetime")
310 310
311 311
modified tests/test_futures_perf.py +12 −12
@@ -9,38 +9,38 @@ import pytest
9 9 BUDGET_MS = 300
10 10
11 11
12 −@pytest.fixture(scope="module")
13 −def warm(client, app):
12 +@pytest.fixture
13 +def warm(client_hu, app):
14 14 from futures.backfill import run_backfill
15 15 run_backfill(today=date(2025, 7, 1))
16 − client.get("/v1/futures/contract/ESU25/bars?interval=1m&limit=10") # warm DuckDB / caches
17 − client.get("/v1/futures/ES/continuous?interval=1m&from=2025-05-01&limit=10")
16 + client_hu.get("/v1/futures/contract/ESU25/bars?interval=1m&limit=10") # warm DuckDB / caches
17 + client_hu.get("/v1/futures/ES/continuous?interval=1m&from=2025-05-01&limit=10")
18 18 return True
19 19
20 20
21 −def _timed(client, url: str, runs: int = 5) -> float:
21 +def _timed(client_hu, url: str, runs: int = 5) -> float:
22 22 best = float("inf")
23 23 for _ in range(runs):
24 24 t = time.perf_counter()
25 − r = client.get(url)
25 + r = client_hu.get(url)
26 26 best = min(best, (time.perf_counter() - t) * 1000)
27 27 assert r.status_code == 200 and r.headers["X-Row-Count"] == "10000"
28 28 return best
29 29
30 30
31 31 @pytest.mark.slow
32 −def test_contract_10k_bars_under_budget(client, warm):
33 − ms = _timed(client, "/v1/futures/contract/ESU25/bars?interval=1m&limit=10000")
32 +def test_contract_10k_bars_under_budget(client_hu, warm):
33 + ms = _timed(client_hu, "/v1/futures/contract/ESU25/bars?interval=1m&limit=10000")
34 34 assert ms < BUDGET_MS, f"10 000 contract bars took {ms:.0f} ms"
35 35
36 36
37 37 @pytest.mark.slow
38 −def test_continuous_10k_bars_under_budget(client, warm):
39 − ms = _timed(client, "/v1/futures/ES/continuous?interval=1m&from=2025-05-01&limit=10000")
38 +def test_continuous_10k_bars_under_budget(client_hu, warm):
39 + ms = _timed(client_hu, "/v1/futures/ES/continuous?interval=1m&from=2025-05-01&limit=10000")
40 40 assert ms < BUDGET_MS, f"10 000 continuous bars took {ms:.0f} ms"
41 41
42 42
43 43 @pytest.mark.slow
44 −def test_parquet_10k_under_budget(client, warm):
45 − ms = _timed(client, "/v1/futures/contract/ESU25/bars?interval=1m&limit=10000&format=parquet")
44 +def test_parquet_10k_under_budget(client_hu, warm):
45 + ms = _timed(client_hu, "/v1/futures/contract/ESU25/bars?interval=1m&limit=10000&format=parquet")
46 46 assert ms < BUDGET_MS, f"10 000 parquet bars took {ms:.0f} ms"
47 47