CCXT.WS.Subscription.Behaviour behaviour (ccxt_client v0.6.1)

Copy Markdown View Source

Behaviour for WebSocket subscription pattern implementations.

Each pattern module converts a list of pre-formatted channel strings (or per-pattern objects) into the exchange-native subscribe frame. Frame encoding (Jason) happens at the WS boundary in CCXT.WS.subscribe/3, not inside pattern modules — modules return plain maps.

Return shape

subscribe/2 and unsubscribe/2 return map() | [map()] | {:error, term()}.

  • Most exchanges accept an array of channels in a single frame → return a map().
  • HTX (:sub_subscribe) and BingX (:reqtype_sub) require one frame per channel → return a [map()] with one frame per channel.
  • Upbit-style custom (config[:custom_type] == "array_format") also returns a [map()].
  • Implementations that enforce an input-shape contract may return {:error, term()} (e.g. :multiple_maps_not_supported or :mixed_channel_types from EventSubscribe / MethodParams). CCXT.WS.Subscription.build_subscribe/3 passes these through to the caller verbatim rather than wrapping them in {:ok, _}.

CCXT.WS.subscribe/3 handles both frame shapes by iterating the list when present and sending one ZenWebsocket.Client.send_message/2 per frame.

Channel formatting is the caller's responsibility

Pattern modules do not format channel strings from unified symbols. Callers pass pre-formatted channels like "tickers.BTCUSDT" or "market.btcusdt.ticker". A future task may introduce spec-driven channel templates; this behaviour is deliberately narrow.

Implementing a Pattern

defmodule CCXT.WS.Subscription.OpSubscribe do
  @behaviour CCXT.WS.Subscription.Behaviour

  @impl true
  def subscribe(channels, config) do
    %{
      (config[:op_field] || "op") => "subscribe",
      (config[:args_field] || "args") => channels
    }
  end

  @impl true
  def unsubscribe(channels, config) do
    %{
      (config[:op_field] || "op") => "unsubscribe",
      (config[:args_field] || "args") => channels
    }
  end
end

Summary

Types

channel()

@type channel() :: String.t() | map()

config()

@type config() :: map()

frame()

@type frame() :: map()

Callbacks

subscribe(channels, config)

@callback subscribe(channels :: [channel()], config :: config()) ::
  frame() | [frame()] | {:error, term()}

unsubscribe(channels, config)

(optional)
@callback unsubscribe(channels :: [channel()], config :: config()) ::
  frame() | [frame()] | {:error, term()}