HF Market Data — Upgrade majeur (v2) : plan d'exécution
Date : 2026-09-04 · Auteur : Simon-Pierre Boucher · Statut : livrable 1 (schéma · plan de doc · maquettes), puis implémentation chantier par chantier.
Contraintes retenues : aucun breaking change (/v1/* existants inchangés, erreurs enrichies mais detail conservé), UTC/ISO 8601, erreurs JSON uniformes {"error":{"code","message","docs"}}, p95 < 300 ms pour 10 000 barres, clés hashées, rate limit aussi sur l'inscription.
0. Architecture cible
hfmarketdata/
api/ FastAPI (Python 3.14, DuckDB sur le lac Parquet, SQLite métadonnées, Redis quotas)
main.py app + endpoints historiques (inchangés) + montage des routeurs v2
core/config.py env (HFMD_*) · core/errors.py (format uniforme) · core/db.py (SQLite/SQLAlchemy)
futures/ chantier 1 : symbols.py (parsing), specs.py (référentiel racines), rolls.py, backfill.py, routes.py
accounts/ chantier 2 : models.py, security.py (hash/PAT), routes_auth.py, routes_me.py, routes_admin.py, mailer.py, cli.py
ratelimit/ chantier 2 : redis_limiter.py (Lua), middleware.py, tiers.py
openapi.py spec 3.1 enrichie (tags, exemples, codes d'erreur) = source unique de la doc
web/ Vite + React 18 + react-router · dark par défaut · SPA code-splittée (pré-rendu des pages statiques au build)
src/app/ shell, nav, thème, auth context
src/pages/{home,docs,playground,integrations,pricing,status,auth,dashboard,admin}
src/docs/ générateur 3 colonnes depuis /openapi.json + guides MDX (content/guides/*.mdx) + changelog
src/playground/ constructeur de requêtes, exécution, tableau, mini-graphique, export de code, headers quota
mcp/ chantier 5 : serveur MCP `hfmarketdata-mcp` (TypeScript, stdio) + README vitrine
skills/ chantier 5 : pack de skills (SKILL.md + scripts) → web/public/downloads/hfmarketdata-skills.zip
tests/ pytest (unit + intégration TestClient + Redis) · web/e2e (Playwright)
scripts/backfill_contracts.py chantier 1 : backfill idempotent des échéances (2010 →)
frd_downloader.py inchangéStockage : SQLite (HFMD_STATE_DB, défaut ~/firstratedata/state/hfmd.db) pour comptes, clés, tiers, usage journalier et futures_contracts ; Redis (HFMD_REDIS_URL) pour les fenêtres glissantes de quotas (requêtes + lignes), scripts Lua atomiques.
1. Schéma de base de données
1.1 futures_roots (référentiel des produits)
| colonne | type | note |
|---|---|---|
| root | TEXT PK | ex. ES, CL, 6E (FirstRate : E6 → alias 6E accepté) |
| name | TEXT | « E-mini S&P 500 » |
| exchange | TEXT | CME, CBOT, NYMEX, COMEX, ICE, EUREX, … |
| asset_class | TEXT | equity_index, energy, metals, rates, ags, fx, crypto, volatility, softs, livestock |
| currency | TEXT | USD, EUR, … |
| contract_size | REAL / TEXT | « 50 × index », 1 000 bbl… (valeur numérique + contract_size_unit) |
| tick_size | REAL | |
| tick_value | REAL | |
| settlement_type | TEXT | cash / physical |
| expiry_rule | TEXT | clé de règle (ex. third_friday, cl_rule, last_business_day, data) |
| month_cycle | TEXT | ex. HMUZ, FGHJKMNQUVXZ |
| first_data_date, last_data_date | DATE | dérivés du lac (min/max sur tous les contrats) |
| contracts_count | INT | |
| source | TEXT | reference (table statique) / derived |
1.2 futures_contracts
| colonne | type | note |
|---|---|---|
| symbol | TEXT PK | forme courte ESZ25 ; forme longue ESZ2025 acceptée en entrée |
| root | TEXT FK | |
| month_code | CHAR(1) | F G H J K M N Q U V X Z |
| contract_month | INT (1-12) · contract_year | INT |
| expiration_date | DATE | règle par racine, sinon last_data_date ; expiration_source = rule / data |
| last_trading_date | DATE | |
| first_notice_date | DATE | NULL si cash-settled |
| settlement_type, contract_size, tick_size, tick_value, currency, exchange | copiés de la racine | |
| first_data_date, last_data_date | DATE | plage réelle (1day) |
| status | TEXT | active / expired (expired si last_data_date < aujourd'hui − 7 j ET mois d'échéance passé) |
| volume_avg_daily | REAL | 20 dernières séances |
| open_interest_last | REAL | dernier OI non nul |
| timeframes | TEXT | JSON des granularités disponibles |
| files | TEXT | JSON {tf: [archive_path, update_path]} |
| updated_at | TIMESTAMP | |
| Index : (root, expiration_date), (status), (root, contract_year, contract_month). |
1.3 futures_contract_gaps — symbol, timeframe, gap_start, gap_end, bars_missing (gaps > 3 jours ouvrés, calculés au backfill).
1.4 Comptes / clés / quotas
| table | colonnes |
|---|---|
| users | id PK, email UNIQUE, name, password_hash (argon2), email_verified_at, role (user/admin), tier (free/high_usage), status (invited/active/disabled), created_at, last_login_at |
| email_tokens | id, user_id FK, kind (verify/reset/invite), token_hash, expires_at, used_at |
| api_keys | id PK, user_id FK, name, prefix (8 car. affichés : hfmd_live_ab12cd34), key_hash (sha256 + sel serveur), tier_override (nullable), status (active/revoked), created_at, last_used_at, revoked_at |
| usage_daily | day, principal (key:<id> ou ip:<hash>), requests, rows, rows_parquet, bytes, status_2xx, status_429 — PK (day, principal) — agrégé depuis Redis toutes les minutes |
| usage_minute | minute, principal, requests, rows — 7 j glissants (graphes 24 h / 7 j) |
| audit_log | ts, actor, action, target, meta |
Format d'une clé : hfmd_live_<32 car. base62> ; on n'en stocke que le hash et le préfixe ; affichée une seule fois à la création ; révocable ; « régénérer » = révoque + crée.
1.5 Tiers (constantes ratelimit/tiers.py)
| tier | fenêtre | requêtes | lignes | lignes max / requête |
|---|---|---|---|---|
| keyless (IP) | 1 h | 30 | 100 000 | 5 000 |
| free (compte + clé) | 1 min | 120 | 1 000 000 | 50 000 |
| high_usage (sur demande à contact@spboucher.ai) | 1 min | 600 | 10 000 000 | 200 000 |
Redis : clé rl:{principal}:{req|rows} — fenêtre glissante par buckets de 1 s (hash) évaluée en Lua : EVALSHA limiter <key> <now_ms> <window_ms> <cost> <limit> → {allowed, remaining, reset_ms} ; deux appels dans un même script pour requêtes et lignes. Parquet = coût lignes ÷ 2 ; /v1/bulk/* et réponses 304 = 0 ligne. Headers X-RateLimit-* sur toute réponse ; 429 Retry-After + error.type.
2. Contrat d'API — nouveautés (toutes additives)
Chantier 1 · GET /v1/futures/roots · GET /v1/futures/{root}/contracts · GET /v1/futures/contract/{symbol}/bars · GET /v1/futures/contract/{symbol}/coverage · GET /v1/futures/{root}/chain?as_of= · GET /v1/futures/{root}/continuous?roll=&adjust=&depth= · GET /v1/futures/{root}/term-structure?as_of=.
Paramètres communs : interval (1m|5m|30m|1h|1d, alias de timeframe), from/to, session=rth|eth|all, cursor/limit, format=json|csv|parquet. Réponses : {"data":[...], "meta":{"symbol","interval","count","next_cursor","roll_dates":[...]}}.
Erreurs : 400 INVALID_CONTRACT_SYMBOL, 404 CONTRACT_NOT_FOUND, 404 ROOT_NOT_FOUND, 400 INVALID_PARAMETER, 429 RATE_LIMIT_EXCEEDED, 401 INVALID_API_KEY.
Chantier 2 · /v1/auth/{signup,verify,login,logout,forgot,reset,accept-invite} · /v1/me · /v1/me/keys (POST/GET/DELETE/POST …/rotate) · /v1/me/usage?range=24h|7d|30d · /v1/admin/users (CRUD, tier, invitation) · /v1/admin/usage · /v1/limits (public : tiers + limites courantes du principal).
3. Plan de la documentation (/docs)
Génération : /openapi.json (3.1, exemples réels capturés au build) → référence ; content/guides/*.mdx → guides ; content/changelog.mdx. Layout 3 colonnes (nav · texte · code curl/Python/JS/R avec langue persistante), Cmd+K, « Try it » → /playground?ep=…¶ms…, lien « Edit / Report issue » (mailto contact@spboucher.ai + lien repo), dark par défaut, mobile : nav en tiroir, colonne code repliable.
Getting started Guides Reference (OpenAPI) More
Quickstart (30 s) Futures individual contracts Meta & status Changelog
Authentication Options chains & Greeks Bars (stocks/ETF/crypto/…) Versioning & deprecation
Rate limits Time zones & sessions (RTH/ETH) Futures v2 (7 endpoints) Limits & pricing
Data formats Bulk downloads Options Integrations (MCP, skills)
Recipes: pandas backtest · Accounts & keys · Usage Status
custom continuous · CL term structure Errors4. Maquettes
4.1 Playground (/playground, aussi intégré au dashboard avec clé injectée)
┌ HF Market Data ─ Docs · Playground · Integrations · Pricing · Status ─────────────── [Sign in] ┐
│ ⓘ Keyless mode: 30 req/h. Create a free account for 120 req/min → [Create free account] │
├──────────────────────────────┬─────────────────────────────────────────────────────────────────┤
│ Request type ▾ │ GET https://www.hfmarketdata.io/v1/futures/ES/continuous?roll=… │ [Copy]
│ ○ Stock/ETF bars │ ──────────────────────────────────────────────────────────────── │
│ ○ Crypto · FX │ [Run ▶] 200 OK · 184 ms · 2 512 rows │
│ ● Futures continuous │ ┌ Table ─┬ JSON ─┬ Chart ──────────────────────────────────────┐ │
│ ○ Individual contract │ │ ▂▃▅▆▇█▇▆▅ candlesticks (lightweight-charts) roll markers ▲ │ │
│ ○ Contract chain │ └─────────────────────────────────────────────────────────────┘ │
│ ○ Term structure │ Rate limit: requests 29/30 · rows 97 488/100 000 · reset 41 min │
│ ○ Options chain (Greeks) │ ┌ curl ─┬ Python (requests+pandas) ─┬ JavaScript (fetch) ─────┐ │
│ ○ Symbols / roots │ │ import requests, pandas as pd … │ │
│ ─ Form (dynamic) ─ │ └─────────────────────────────────────────────────────────────┘ │
│ root [ES ▾ autocomplete] │ Examples: AAPL 1-min today · CL chain · ES continuous back-adj │
│ roll [volume ▾] adjust [back_adjusted ▾] depth [1 ▾] 2015-2025 · NG term structure │
│ from [2015-01-01] to [2025-12-31] interval [1d ▾] format [json ▾] session [all ▾] │
└──────────────────────────────┴─────────────────────────────────────────────────────────────────┘ 4.2 Dashboard (/dashboard)
┌ Sidebar: Overview · API keys · Usage · Playground · Account Tier: Free · Need more? contact@spboucher.ai ┐
│ Overview ─ Requests today 1 240 / (120/min) · Rows today 3.1 M · Last request 2 min ago │
│ API keys ─ ┌ name ─ prefix ─ created ─ last used ─ status ─ actions ┐ [+ Create key] │
│ │ default hfmd_live_ab12cd34… 2026-09-04 just now active [Rotate] [Revoke] │ (clé montrée 1 fois) │
│ Usage ─ [24h] [7d] [30d] ▁▂▃▅▆▇█ requests / min ▁▁▂▃▅▆ rows / min · table par jour · export CSV │
│ Playground─ même composant qu'en public, clé injectée automatiquement, bandeau « authenticated as … » │
│ Admin (rôle admin) ─ users (créer · inviter · tier · désactiver) · consommation globale · top principals │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ 4.3 Access & limits (/limits) — tout est gratuit
Tableau 3 colonnes (Keyless · Free account · High usage) avec les 6 chiffres, CTA « Create free account » et « Request high usage → contact@spboucher.ai », encart « incitations » (Parquet = ½ coût, bulk hors quota, ETag/304 gratuits).
4.4 Homepage
Hero (« Open high-frequency market data. 1-minute to daily, since 2010. ») · bloc « Get an API key » en 3 étapes (keyless → free → high usage) · aperçu live du playground (requête ES continu) · Integrations (Claude Code · Cursor · Codex · MCP) · datasets live (compteurs /v1/status).
5. Ordre d'implémentation
- Socle : config, erreurs uniformes, SQLite, OpenAPI 3.1 enrichie, tests de non-régression des endpoints v1.
- Chantier 1 (futures) + backfill + tests unitaires symboles/rolls.
- Chantier 2 (Redis limiter, clés, comptes, dashboard API, CLI
hfmd users add, seed des 3 utilisateurs). - Chantier 6 + 3 + 4 (site, docs 3 colonnes, playground) — frontend.
- Chantier 5 (MCP + skills + page Integrations).
- Tests E2E Playwright, déploiement via
mldsur M3U96b (Redis + backfill dans le manifeste).