ADR-009: tracked_pairs Table as the Canonical Symbol Registry
Status: Accepted
Context
Before this change, the set of tracked market symbols was defined entirely in code. Two separate constant arrays — TRACKED_SYMBOLS (the main app) and YAHOO_FINANCE_SYMBOLS (ohlc-service) — were hardcoded and duplicated across 6+ files. This created several compounding problems:
- Scatter. Adding or removing a symbol required coordinated changes in multiple files across two independent services.
- Drift. The two lists were not guaranteed to stay in sync.
ETH/USDappeared twice in the backend list — a silent duplicate that went undetected because nothing validated the arrays. - No metadata. The arrays held symbols as plain strings. Per-symbol metadata (price format, enabled/disabled state) had to be inferred from the symbol string or held in separate parallel structures.
- Redeploys required. Any change to the tracked set meant a code change, PR, and redeploy of one or both services.
Options considered:
| Option | Pros | Cons |
|---|---|---|
| Keep hardcoded arrays (status quo) | Zero infrastructure overhead | Scatter, drift, duplicates; redeploy to change anything |
| Environment variable list | No extra schema; easy to override per-environment | Not UI-manageable; still requires a redeploy to change; no per-symbol metadata |
| Shared config file on disk | Simple read path | Couples both services at the filesystem level; not viable across containers; no UI |
tracked_pairs DB table (chosen) | Single source of truth; UI-manageable; rich metadata; no redeploys | ohlc-service gains a soft startup dependency on mi-casa’s HTTP API |
Decision
Replace all hardcoded symbol arrays with a single tracked_pairs PostgreSQL table in the main app database. This table is the sole source of truth for which symbols are tracked and how each one behaves.
Schema
tracked_pairs (
symbol TEXT PRIMARY KEY, -- e.g. "EUR/USD"
label TEXT NOT NULL, -- display name, e.g. "Euro / US Dollar"
price_format TEXT NOT NULL, -- "forex5" | "forex4" | "forex3" | "price2" | "price0"
enabled BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
sourcewas removed later once cTrader became the sole supported market data provider.
All 13 original pairs are seeded idempotently by backend/db/trackedPairsSeed.ts.
Admin CRUD API (mi-casa backend)
| Method | Route | Description |
|---|---|---|
GET | /api/admin/tracked-pairs | List all pairs |
POST | /api/admin/tracked-pairs | Create a new pair |
PATCH | /api/admin/tracked-pairs/:symbol | Update label, format, or enabled state |
DELETE | /api/admin/tracked-pairs/:symbol | Remove a pair |
All routes require admin authentication.
Admin UI
A new AdminTrackedPairsPanel React component lets admin users create, edit, enable/disable, and delete pairs from the web UI with no code changes or redeploys.
getSymbols() endpoint
The public GET /api/symbols endpoint now calls trackedPairsRepository.getAllEnabled() instead of returning a hardcoded array.
Internal API for ohlc-service
A new protected endpoint GET /api/internal/tracked-pairs is added to mi-casa. It is guarded by the existing OHLC_SERVICE_API_KEY shared secret (passed as X-API-Key). ohlc-service calls this endpoint at startup via trackedSymbolsCache.ts and caches the result in memory.
ohlc-service graceful degradation
If mi-casa is unreachable at startup, trackedSymbolsCache stays empty and ohlc-service starts anyway. Symbol-dependent operations degrade gracefully until the cache is populated on a successful retry.
Consequences
Positive
- Adding, removing, or reconfiguring a tracked pair is a single DB row change, performable from the admin UI — no code change or redeploy required in either service
- Per-symbol metadata (
price_format,enabled) is now first-class, validated by the schema, and impossible to desynchronise between services - Silent bugs like the duplicate
ETH/USDentry are structurally prevented by the primary key constraint - The
TRACKED_SYMBOLSandYAHOO_FINANCE_SYMBOLSconstants are removed from all codebases
Negative / Tradeoffs
- ohlc-service now has a soft startup dependency on mi-casa’s HTTP API; if mi-casa is down when ohlc-service starts, symbol-driven operations are unavailable until the cache warms
- The
tracked_pairstable must be kept in sync with whatever ohlc-service actually supports — unsupported symbols will still fail until cTrader exposes them and the downstream services can consume them GET /api/internal/tracked-pairsis a new internal surface area guarded only by the static pre-shared key (same threat model as the existing ohlc-service auth; see ADR-008)