Writing a Market Atlas connector
A connector is one technical integration of a source (connectors/src/sources.ts). It declares metadata (type, rights,
real-time class, family), optionally seeds instruments, and implements either a streaming lifecycle (start/stop) or a polling
one (poll + schedule), plus a pure normalize(raw).
export const example = defineConnector({
metadata: { id: "example-xhr", name: "…", version: "1.0.0", sourceId: "example", organization: "…", sourceType: "XHR",
jurisdiction: "US", rightsStatus: "PUBLIC_ATTRIBUTED", realtimeStatus: "DELAYED", expectedLatencyMs: 900_000,
supportsStreaming: false, supportsHistorical: false, assetClasses: ["EQUITY"], exchanges: ["xnys"], homepage: "https://…",
description: "What it observes, how often, what is delayed.", rightsNotes: "Attribution…", termsUrl: "https://…",
sourceFamily: "example", enabled: true },
seeds: [{ symbol: "ABC", hint: { assetClass: "EQUITY", name: "ABC Corp", exchangeId: "xnys", mic: "XNYS", currency: "USD", country: "US" } }],
defaultSymbols: ["ABC"],
rateLimits: { "api.example.com": 2 }, // requests/second, shared bucket
schedule: { openMs: 60_000, closedMs: 900_000, weekendMs: 3_600_000, exchangeId: "xnys" }, // or { intervalMs }
async poll(ctx) { const { data, response } = await ctx.http.getJson(url); return response.notModified ? [] : [raw(id, sourceId, "quote", data)]; },
normalize(r) { /* payload → { observations, events?, instruments?, filings?, holidays?, bars? } */ },
fixturesDir: "fixtures",
});Rules (enforced by tests and the runtime):
- Metadata is complete —
ConnectorMetadataSchema;rightsStatusmust not be UNKNOWN for production connectors; describe delays honestly. - normalize() is pure — no network, no clock other than
raw.receivedAt; deterministic so raw payloads can be replayed (ma replay). - Timestamps — always convert to UTC ms; declare
timestampTrust(EXCHANGE / SOURCE / CONNECTOR). Naive local times →zonedTimeToUtc(local, tz). - Symbols — emit the source-native symbol; provide an
instrumentHintso the resolver can create the instrument deterministically (makeInstrumentId). Aliases (BRK.B/BRK-B) are declared in seeds/proposals. - Never sleep in connector code; use
rateLimitsand the sharedctx.http. One WebSocket carries many symbols. - Fixtures + tests — at least one valid payload, one malformed/unexpected payload; tests run offline (
makeTestContext,fakeFetch). - README per connector: endpoint, type, rights, real-time class, fields, quirks, schedule.
- Register in
connectors/src/index.tsand, for a new source, insources.ts(family = upstream provider when known).
Side outputs of normalize():
| key | consumer |
|---|---|
observations |
pipeline → observations, consensus, quotes, events |
events (ProposedEvent, dedupeKey) |
event engine (halts, document changes…) |
instruments (ProposedInstrument, createIfMissing) |
instrument master (directories) |
filings (metadata.material) |
filings table + FILING_PUBLISHED for material forms |
holidays |
market-hours engine (exchange_holidays) |
bars |
historical backfill (bars, producer = connector id) |
Production connectors (v0.2, 22): coinbase-ws, kraken-ws (+12 fiat pairs), binance-ws, okx-ws, bitstamp-ws (+EUR/USD, GBP/USD), gemini-ws,
bitfinex-ws (+EUR/GBP/JPY vs USD), bybit-ws, gate-ws, cryptocom-ws, kucoin-ws (WEBSOCKET) · cboe-delayed-quotes, nasdaq-quote-api (validator)
(XHR) · ecb-frankfurter, bank-of-canada-valet, hfmarketdata-daily (OFFICIAL_API) · us-treasury-yield-curve (XML) · sec-edgar-filings,
nasdaq-trade-halts (RSS) · sec-company-tickers, nasdaq-symbol-directory (BULK_FILE) · nasdaq-market-calendar (HTML). All keyless
(hfmarketdata accepts an optional key of the sister platform). Declare observationType on every observation (TRADE / QUOTE / OFFICIAL_FIX /
REFERENCE_RATE / EOD_CLOSE / STABLECOIN_PROXY…); use _shared/pairs.ts (planPair, pairObservations) for venue pairs so fiat/fiat markets
become FOREX TRADE observations and stable/fiat markets become STABLECOIN_PROXY observations (inverted when needed). See each README.md.
Onboarding workflow for a discovered source: POST /v1/admin/discovery {url} → candidate report (endpoints, embedded state, price/symbol-like
fields) → manual rights review → connector + fixtures → staging (enabled: false or MA_DISABLED_CONNECTORS) → production.