DpExchange.Coinbase.Rest (DpExchangeCoinbase v0.1.1)

Copy Markdown View Source

Coinbase Advanced Trade REST — internal. Nothing here is public API; the facade is DpExchange.Coinbase.

Credentials choose the endpoint, they do not gate it

Coinbase serves the same market data two ways: /products/{id}/ticker is account-scoped and needs a Bearer JWT, /market/products/{id}/ticker is public. This module picks the authenticated path when it is given credentials and the public one when it is not.

That is the facade's rule made concrete — a caller passes credentials or does not, and reads the consequence from capabilities/0. It is also a fix for a real incident: the price-collection task did not pass credentials, hit the authenticated path anyway, and produced 315 Unauthorized warnings overnight on 2026-04-30.

Historical candles are public

/market/products/{id}/candles, no auth. The authenticated variant 401s on every backfill call, which is what it did until 2026-07-02.

This module cannot fabricate

There is no fallback path, no test-mode branch and no hardcoded price table. A request that fails returns an error. The adapter this was ported from had a generate_fallback_candles/4 that invented OHLC from a table of base prices; its error path was fixed in May 2026 after fabricated candles were traced to phantom profits in backtests, but the generator survived behind a node-wide test flag. It is not here in any form — see docs/reference/coinbase/reconciliation.md.

A caller wanting deterministic candles uses this package's fake, selected per process, which is a real implementation of the facade rather than a branch inside the live one.

Summary

Functions

Historical candles for symbol at timeframe.

The current price for symbol.

Every product Coinbase lists, as canonical symbols.

Every timeframe Coinbase serves, shortest first.

The most candles Coinbase will return for one request. A hard boundary, not a hint.

Functions

get_historical_prices(symbol, timeframe, range, opts)

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

Historical candles for symbol at timeframe.

A timeframe Coinbase does not serve is an error, never the nearest width.

get_price(symbol, opts)

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

The current price for symbol.

Returns {:refused, reason} when the venue does not carry the symbol — a permanent answer, distinct from a transient {:error, _}.

get_symbols(opts)

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

Every product Coinbase lists, as canonical symbols.

granularities()

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

Every timeframe Coinbase serves, shortest first.

max_candles()

@spec max_candles() :: pos_integer()

The most candles Coinbase will return for one request. A hard boundary, not a hint.