DpExchange.Core.Instrument (DpExchangeCore v0.1.4)

Copy Markdown View Source

One listing as the VENUE describes it: canonical symbol, base, quote, instrument type and trading status.

Why this exists

get_symbols/1 returns [String.t()]. A venue adapter fetches the venue's rich listing payload and then throws away everything except the symbol — Coinbase discards base_currency_id, quote_currency_id, product_type and status on the line that maps & &1["product_id"].

The pair catalog needs exactly those discarded fields. Recovering them by parsing the symbol string back apart is what this design explicitly rejects: an earlier draft proposed regex-matching a PERP suffix and got Gemini's quote distribution measurably wrong, missing its GBP, EUR, SOL and FIL quotes entirely. Gemini is the sharp case — its symbols have no separator, so BTCUSDCPERP cannot be split into base and quote at all without the venue's own fields.

So this is a separate callback rather than a change to get_symbols/1, whose callers only want names and shouldn't pay for the extra requests (Gemini's details are per-symbol: 347 calls).

Optional by design

list_instruments/1 is an OPTIONAL callback. Webull and Robinhood publish single-quote -USD catalogs where base and quote are trivially derivable and no non-spot instruments exist, so requiring an implementation there would be ceremony. A consumer building a catalogue falls back to get_symbols/1 plus its own derivation for those, and marks anything it cannot resolve :unknown — surfaced for review, never guessed.

Summary

Functions

Normalise a venue's product-type string.

Build an instrument, normalising the venue's own type and status strings.

Normalise a venue's listing-status string.

Types

instrument_type()

@type instrument_type() :: :spot | :perp | :unknown

status()

@type status() :: :tradable | :delisted | :unknown

t()

@type t() :: %DpExchange.Core.Instrument{
  base: String.t() | nil,
  instrument: instrument_type(),
  quote: String.t() | nil,
  status: status(),
  symbol: String.t()
}

Functions

instrument_from(type)

@spec instrument_from(String.t() | nil) :: instrument_type()

Normalise a venue's product-type string.

Recognises the forms the four venues actually publish. Anything else is :unknown — the whole point is that an unrecognised type is visible.

new(fields)

@spec new(keyword()) :: t()

Build an instrument, normalising the venue's own type and status strings.

Both default to the conservative reading rather than the flattering one: an unrecognised product type is :unknown (excluded from collection and surfaced for review), NOT :spot. A venue that invents a new contract type must not have it silently admitted into a spot-only fleet.

status_from(status)

@spec status_from(String.t() | nil) :: status()

Normalise a venue's listing-status string.

limit_only counts as tradable: the venue still matches orders, and treating it as delisted would silently drop live books. closed / delisted / trading_disabled are the terminal states.