← openoms

Instrument model

One canonical instrument catalog in the middle, two independent mappings off it: a broker's tradable handle (broker_instrument, a table) and a data feed's pricing symbol (FeedSymbology, derived in code). Priceable and tradable are independent.

Master

instrument

Canonical identity, keyed (symbol, venue). Asset/instrument class, microstructure, FIGI. Option legs in instrument_derivative.

Execution

broker_instrument

Tradable mapping — one row per instrument per broker: broker_symbol, native_id, limits. Written by sync-broker from the adapter's InstrumentProvider. Its existence means tradable.

Market data

FeedSymbology

Pricing mapping, 1:n — and a pure function, not a table. candidates() picks the catalog slice a feed covers, to_feed_symbol() renames it; one symbol can price the pair on several venues. Evaluated at subscribe time.

End to end

One catalog in the middle, an adapter at each end. Brokers write it: sync-broker asks each broker's InstrumentProvider what it lists and stores that as instrument plus a broker_instrument routing row. Feeds only read it: at subscribe time FeedSymbology.candidates() picks the slice of the catalog that feed covers and to_feed_symbol() renames each hit into whatever that feed calls it.

That second mapping is computed, not stored — there is no feed table anywhere. It is rebuilt from the catalog on every subscription, which is why a broker sync that adds instruments needs no follow-up step to make them priceable, and why nothing can go stale. Each adapter owns its own symbology, so neither end knows the other exists.

no table — recomputed on every subscribe

the catalog — two Postgres tables, survive restarts

sync-broker writes

writes

1:n

read per subscribe

feed's own symbols

Alpaca
equities · options

Binance
crypto spot

InstrumentProvider
list_instruments()

instrument
symbol @ venue
what exists, one row each

broker_instrument
how to route it,
one row per broker

FeedSymbology
candidates() picks the slice
to_feed_symbol() renames it

Databento
OPRA options

Binance
spot book

Bybit
spot book

Brokers write the catalog; feeds read it and rename in memory

Tables & relationships

Foreign keys shown. broker_instrument references the master instrument. There is no feed table: the pricing mapping is derived in code. Two soft links by code (no FK): broker_code → broker_connection, and feed_code is ranked for failover in provider_feed_policy.

lists (MIC)

quoted in

option legs

tradable via

routing target

VENUE

text

code

PK

XNAS, OPRA, BINANCE

INSTRUMENT

bigint

id

PK

text

symbol

UNIQUE(symbol,venue)

text

venue

FK

text

currency

FK

text

asset_class

text

instrument_class

text

figi

CURRENCY

text

code

PK

USD, USDT

INSTRUMENT_DERIVATIVE

bigint

instrument_id

PK,FK

text

underlying_symbol

text

option_kind

numeric

strike_price

date

expiry_date

BROKER_INSTRUMENT

bigint

instrument_id

FK

text

broker_code

ALPACA|BINANCE

text

broker_symbol

text

native_id

bool

is_tradeable

numeric

min_quantity

BROKER_CONNECTION

text

code

PK

text

broker_code

text

environment

PAPER|LIVE

ACCOUNT

text

code

PK

Tables and foreign keys, instrument at the centre

Runtime — the two paths

Pricing derives its symbols through FeedSymbology; order routing reads broker_instrument. They never cross — a feed can price an instrument no broker trades, and vice versa.

Execution path (orders)

resolve handle

Order

account -> broker_connection
(broker_code, env)

BrokerRegistry adapter

broker_instrument
broker_symbol / native_id

Broker API

Pricing path (market data)

Live feeds
Databento · Binance · Bybit

subscribe held
via FeedSymbology

mark_router
ranked by provider_feed_policy

MarkStore

positions · M2M P/L

The pricing path and the execution path never cross

Reflects migrations 0016–0020 and the sync-broker flow. See also API docs.