DpExchange.Gemini.Rest (DpExchangeGemini v0.1.1)

Copy Markdown View Source

Gemini's REST surface — internal. The facade's market-data callbacks are served here.

Every endpoint below was measured against the live venue on 2026-08-28. Where the measurement disagreed with Gemini's documentation, the measurement won and the divergence is recorded in docs/reference/gemini/.

The candle window is fixed and every parameter is ignored

/v2/candles/{symbol}/{time_frame} takes no bounds. limit, start and end are accepted and discarded — three requests differing only in those returned byte-identical responses. Each width serves a fixed window:

Width sentBars≈ span
1m14401 day
5m20157 days
15m134314 days
30m143930 days
1hr146361 days
6hr36792 days
1day3641 year

So a range is honoured by filtering here, and a range the window cannot cover is an error rather than a short answer. Handing back 364 daily bars to a caller who asked for five years is the family's named failure mode in its quietest form: every value real, only the meaning wrong.

Neither ticker carries a quote timestamp, so the venue's own clock is used

/v1/pubticker returns a timestamp, but it is inside the volume object — it stamps the 24-hour volume window, updates about once a minute, and is not when the bid and ask were true. /v2/ticker carries no timestamp at all. Using either as the quote time would be a substitution of exactly the kind this family exists to stop, and the host adapter does something worse: parse_timestamp(nil) returns DateTime.utc_now(), so a quote with no venue time gets the client's clock and looks perfectly fresh.

This package uses the HTTP Date response header — the venue's own statement of when it served the answer, which bounds the quote's age and is not our clock. When that header is absent the request fails with {:error, :missing_venue_timestamp} rather than returning a quote whose freshness cannot be stated.

Summary

Functions

Base URL, overridable per process for tests through Core.Config.

Candles for a symbol and canonical timeframe, filtered to range.

Every pair with its last price and 24-hour change, in one call.

A price-level snapshot for one symbol.

Best bid, best ask and last trade for one symbol.

Every spot symbol the venue lists, canonical.

The increments and minimum the venue will actually accept for a symbol.

Canonical timeframes this venue serves, shortest first.

Functions

base_url(opts \\ [])

@spec base_url(keyword()) :: String.t()

Base URL, overridable per process for tests through Core.Config.

get_historical_prices(symbol, timeframe, range, opts)

@spec get_historical_prices(String.t(), String.t(), keyword(), keyword()) ::
  {:ok, [map()]} | {:error, term()} | {:refused, term()}

Candles for a symbol and canonical timeframe, filtered to range.

range accepts :start and :end as DateTimes. Both are optional; with neither, the venue's whole fixed window is returned.

Refuses rather than truncating:

  • an unknown width → {:error, {:unsupported_timeframe, width}}
  • a :start older than the window can reach → {:error, {:range_unavailable, …}}

get_market_overview(opts)

@spec get_market_overview(keyword()) :: {:ok, map()} | {:error, term()}

Every pair with its last price and 24-hour change, in one call.

/v1/pricefeed is the only endpoint here that describes the whole catalogue at once, which is what makes an overview affordable — the alternative is one request per symbol across 346 symbols, which is not an overview, it is a rate-limit incident.

get_order_book(symbol, opts)

@spec get_order_book(
  String.t(),
  keyword()
) ::
  {:ok, DpExchange.Core.Types.OrderBook.t()}
  | {:error, term()}
  | {:refused, term()}

A price-level snapshot for one symbol.

Each level carries the venue's own timestamp, so unlike a quote there is nothing to derive: the book's time is the newest level's time.

get_price(symbol, opts)

@spec get_price(
  String.t(),
  keyword()
) ::
  {:ok, DpExchange.Core.Types.Quote.t()} | {:error, term()} | {:refused, term()}

Best bid, best ask and last trade for one symbol.

Timestamped from the venue's Date response header — see the module doc for why not from the payload.

get_symbols(opts)

@spec get_symbols(keyword()) :: {:ok, [String.t()]} | {:error, term()}

Every spot symbol the venue lists, canonical.

Perpetuals are excluded. They are real instruments and the venue lists them alongside spot pairs, but this package declares supported_instrument_types: [:spot], and a perpetual has no canonical BASE-QUOTE form — emitting one would invent a spot pair that does not exist.

quantization(symbol, opts)

@spec quantization(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, term()} | {:refused, term()}

The increments and minimum the venue will actually accept for a symbol.

From /v1/symbols/details/{symbol}, which is also the source behind the venue's own published minimums table — that page states it fetches this endpoint live.

tick_size is the base-asset increment and quote_increment the price increment. They are not interchangeable and the names do not say so: for btcusd, tick_size is 1.0e-8 BTC while quote_increment is 0.01 USD.

timeframes()

@spec timeframes() :: [String.t()]

Canonical timeframes this venue serves, shortest first.