DpExchange.Webull.Feed (DpExchangeWebull v0.1.1)

Copy Markdown View Source

This venue's subscription lifecycle — internal, and the place where its two transports are joined.

Subscribing here is two operations on two protocols

Market data arrives over MQTT; subscriptions are HTTP calls. They are joined by one value: the session_id this feed generates, registers as the MQTT client id, and then names in every HTTP subscribe.

A consumer calls subscribe/3 with symbols. It never learns that a socket was dialled, that an HTTP call followed, or that the two had to agree on an identifier — which is the facade doing precisely what D12 asks of it, on the venue where it costs the most to deliver.

Re-subscribing after a reconnect is this package's job

If the connection is dropped due to network issues, previous subscriptions are not automatically restored. You must re-subscribe after reconnecting.

So a reconnect is followed by a replay of everything wanted. A consumer that had to notice reconnects and replay its own subscriptions would be doing the venue's bookkeeping through an interface designed to hide reconnects entirely.

Coverage is observed, never intended

A symbol enters the coverage map when a payload for it arrives — not when it is subscribed, and not when the HTTP subscribe returns 200. On this venue those are three genuinely different moments, and only the last one means data.

One connection, because five is the ceiling and one is enough

The venue allows five concurrent connections per App Key and pushes at most three messages per second per connection. This feed opens one. Sharding to raise the message ceiling is a change to make when a measurement demands it, and it can never exceed five — a consumer cannot cause a sixth socket, because a consumer cannot ask for sockets at all.

Summary

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

coverage(feed)

@spec coverage(GenServer.server()) :: %{
  required(String.t()) => :stream | :internal_poll | :not_covered
}

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start()

subscribe(feed, symbols, opts)

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

subscribe_notices(feed, opts)

@spec subscribe_notices(
  GenServer.server(),
  keyword()
) :: :ok

unsubscribe(feed, symbols, opts)

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

update_symbols(feed, symbols, opts)

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