DpExchange.Webull (DpExchangeWebull v0.1.1)

Copy Markdown View Source

Webull, behind the DpExchange facade.

⚠️ EXPERIMENTAL

This package has not run in production. While it is 0.x the API may change without a major version — pin all three segments. Maturity is declared per endpoint through capabilities/0; do not read this banner as your check.

This module is the entire public API of this package. Transport, signing, session handling and supervision are internal, and nothing here returns them.

Credentials are required for market data, which is unusual

Every Webull OpenAPI call is signed — including the ones that look public. There is no anonymous quote endpoint. capabilities/0 declares credential_benefit: :required, the first venue in this family to do so, and get_price/2 takes credentials that the same call on other venues does not need.

The host still owns authentication: it holds the App Key and secret and passes them in. This package signs one request with them and keeps nothing.

Two transports, and you see neither

Market data arrives over MQTT 3.1.1 carried on a WebSocket; subscriptions are HTTP calls; the two are joined by a session identifier this package generates. You call subscribe/2 with symbols.

That matters beyond tidiness. The venue does not restore subscriptions after a reconnect — this package replays them. A consumer doing that itself would have to notice reconnects, which is exactly what the facade exists to hide.

The UAT environment has REST but no stream

environment: :uat gives authenticated REST against test data. It has no brokermqtt-uat.webullbroker.com does not resolve — so subscribe/2 there returns {:error, {:streaming_unavailable, :uat}} rather than quietly falling back to production. A consumer testing against UAT that received production prices would be reading real market data believing it was fake.

No trade volume, anywhere

Webull's crypto OpenAPI reports no volume: not on bars, not on the snapshot, not on the stream. volume is nil, never 0, and capabilities/0 says reports_trade_volume: false so volume-dependent work can be routed elsewhere rather than reading a column of zeroes.

Supervision

children = [{DpExchange.Webull, []}]

{:ok, quote} = DpExchange.Webull.get_price("BTC-USD", credentials: my_credentials())

Summary

Functions

The quote currencies this venue settles in.

Whether this environment carries a live stream.

Functions

quotes()

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

The quote currencies this venue settles in.

streaming?(opts \\ [])

@spec streaming?(keyword()) :: boolean()

Whether this environment carries a live stream.

Exposed because the answer differs by environment on this venue and a consumer should be able to ask before it commits, rather than learning from a subscription that never delivers.