An in-process Webull, for a consumer's tier-1 tests and for the conformance suite.
It is not a mock. Nothing is stubbed, no expectation is recorded, no call is
verified. It is a real implementation of DpExchange.Core.Venue answering from memory,
and it runs the same conformance suite as the real adapter.
Two rules
Less capable is allowed. Differently capable is not. Where this cannot answer it returns an error, never an empty success.
It never rewrites a value the caller supplied, and never stamps the current clock —
@at is fixed, because a fake that stamps utc_now/0 cannot be used to test anything
about freshness and is itself the substitution this family refuses. The one exception is
get_balances/2's timestamp, which is DateTime.utc_now/0 because the real package
stamps it with when it asked (Rest.get_balances/2's asked_at: a balance has no venue
event time), so a fixed past instant there would be the fake being differently capable.
It refuses an option it does not read, as the facade does. Each function checks opts
through the same DpExchange.Webull.Options table the facade uses - positional arguments
first, then the options, then the credentials - and answers {:error, {:unknown_option, key}} or {:error, {:invalid_options, opts}} before it consults an injected outcome
(DpExchange.Core.FakeInjection), so a misspelt key is never masked by an injection. The
reads with no error channel (coverage/1, wanted/1, coverage_by_kind/1, streaming?/1,
live?/1) raise ArgumentError.
It models the three things that make this venue different
Credentials are required everywhere a real call reaches the venue — not for market data alone, and not for a callback that never builds a request.
get_price/2without them is{:error, {:missing_credentials, :webull}}, because the real venue signs every call and has no anonymous endpoint — and so is every credentialed account and order call (get_balances/2,get_accounts/2,place_order/3, and the rest).{:error, {:missing_credentials, :webull}}, not{:refused, _}: a missing local credential never reaches the venue at all, andDpExchange.Core.Venue's own moduledoc reserves:refusedfor the venue's own permanent word about a request it received. It isAuth.headers/2's own return value for a credential that does not match its shape, echoed here rather than invented — this used to answer{:refused, :missing_credentials}here and{:ok, _}on several of the account and order callbacks, both wrong for the reasons above; found bydp_exchange_core0.1.57's assertion 17. A fake that answered anyway would let a consumer's test pass while the real call returns 401.get_fees/2is the one deliberate exception. It answers a captured published rate and never builds a request, so there is nothing for a credential to gate — see its own moduledoc. A 2026-09-06 sweep gated it anyway, on the reasoning that the real path "had never run throughAuth.headers/2"; that broke a real consumer who resolves fees before any account is attached, and was reverted 2026-09-07.The gate covers the widened surface too, and that part assertion 17 cannot check. Options, watchlists, financials, corporate events, filings, news and the screener all reach the venue through the same signed
Restrequest path as everything else, so they refuse without credentials exactly asget_price/2does. They were missed originally because assertion 17 gates onCore.AdapterContract's hardcoded@credentialedlist —get_balances,get_accounts,get_fees,get_transfers,place_order,cancel_order,get_order,get_orders,get_trade_history— which names none of them, so twelve callbacks answered{:ok, _}with no credentials while the real venue returned 401. A cross-package audit found the identical gap indp_exchange_schwab's fake on the same widened surface; it is a property of the assertion's fixed list, not of either venue.No volume.
volumeisnilon every quote and every bar, matching a venue that reports none. Returning0would look like a real measurement of no trading.UAT has no stream.
subscribe/2underenvironment: :uatrefuses, exactly as the real feed does, rather than delivering fake production data.
Remembered per process, and what is unmeasured
Orders from place_order/3, place_orders/3 and replace_order/4 are remembered, so
every id they return is accepted by this fake's get_order/3 and cancel_order/3
(close_position/3 is unsupported here, so it issues none). Watchlists from
create_watchlist/3 read back their members; only the fixture's own wl-1 returns canned
ones. get_watchlist/2 for any other id is {:refused, :not_found} - the real venue's
answer for an unknown watchlist id is unmeasured (get-watchlist-instruments.md states
only the generic error bodies), so the fake fails closed rather than guess one.
Failure injection and anonymous mode
Every function below that has a real success path (not an unconditional
Venue.not_supported()) checks DpExchange.Core.FakeInjection.next_outcome/1 or /2
first — a queued or always-set outcome from FakeInjection.queue_failures/2,3 or
fail_always/2,3 short-circuits the fake's normal logic and is returned as-is.
authenticated/1 also checks FakeInjection.credentials_bypassed?/1 before its normal
{:error, {:missing_credentials, :webull}} path. Neither changes anything for a test
that never calls FakeInjection — see that module for the full contract.
subscribe/2, unsubscribe/2 and update_symbols/2 are NOT wired: each takes a list
of symbols in one call, and "this one symbol in the batch fails, the rest succeed" is a
case whole-call injection cannot express — see FakeInjection's own moduledoc.
place_orders/3 is the same shape for the same reason: one call, many orders.
Summary
Functions
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.add_payment_method/2.
Adds instruments to a watchlist this fake holds (wl-1 or one created in this process).
Another id is {:refused, :not_found}. Requires opts[:credentials].
See DpExchange.Webull.add_watchlist_instruments/3.
Whether bars of timeframe are forward-adjusted. See DpExchange.Webull.adjusted?/1.
The real adapter's asset classes, DpExchange.Webull.asset_classes/0.
See DpExchange.Core.Venue.asset_classes/0.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.cancel_all_orders/2.
Marks a remembered order cancelled and answers {:ok, :cancelled}; an unknown id is
{:refused, :not_found}.
The real adapter's declaration, DpExchange.Webull.capabilities/0, unchanged.
See DpExchange.Core.Venue.capabilities/0.
The status of a token: the one create_token/1 mints is PENDING, any other is
INVALID. A token that is not text is {:error, {:invalid_field, :token}}. Requires
opts[:credentials]. See DpExchange.Webull.check_token/2.
A child spec that starts nothing: start_link/1 answers :ignore.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.close_position/3.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.commit_conversion/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.convert/4.
Every symbol this process's fake has delivered to, as %{symbol => :stream}.
See DpExchange.Core.Venue.coverage/1.
This process's delivered symbols split into :quotes and :top_of_book; an empty kind
is omitted. See DpExchange.Core.Venue.coverage_by_kind/1.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.create_account/1.
Mints a PENDING fixture token, as the real endpoint does. Requires opts[:credentials].
See DpExchange.Webull.create_token/1.
Remembers a watchlist for this process and returns it with its members.
Requires opts[:credentials]. See DpExchange.Webull.create_watchlist/3.
Deletes a watchlist this process knows; an unknown id is {:refused, :not_found}.
Requires opts[:credentials]. See DpExchange.Webull.delete_watchlist/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.estimate_withdrawal_fee/4.
The fundamentals kinds get_fundamental/3 reaches.
Two fixture accounts, one CRYPTO and one INDIVIDUAL_MARGIN. Requires the credentials.
See DpExchange.Webull.get_accounts/2.
One fixed auction imbalance row.
One fixture USD balance, available_balance nil, stamped with the current time.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_contract_stats/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_conversion/2.
Fixture corporate events for opts[:symbol], which is required
({:error, :symbol_required} without it). Requires opts[:credentials].
See DpExchange.Webull.get_corporate_events/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_deposit_address/3.
The fixture event-contract book: four sides (yes_bids, yes_asks, no_bids,
no_asks), the venue's own level maps, and quote_time in milliseconds. Requires
opts[:credentials]. See DpExchange.Webull.get_event_order_book/2.
Three fixture ticks (fewer when opts[:limit] is smaller) for one event-contract market,
in the venue's own shape (a yes price, a no price, a yes/no side). Requires
opts[:credentials]. See DpExchange.Webull.get_event_trades/2.
The captured published crypto spread, as the real adapter answers it.
One fixture filing for the symbol. Requires opts[:credentials].
See DpExchange.Webull.get_filings/2.
One fixture financial statement of the requested kind. Requires opts[:credentials].
See DpExchange.Webull.get_financials/3.
One fixture row of the named fundamentals kind. An unknown kind is
{:error, {:unknown_fundamental, kind}}. Requires opts[:credentials].
See DpExchange.Webull.get_fundamental/3.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_funding/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_fx_rate/3.
One fixture candle for a listed symbol, kept only if it falls inside range.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_market_overview/1.
One fixture news item. Requires opts[:credentials]. See DpExchange.Webull.get_news/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_notional_balances/3.
A small fixture option chain for the underlying. Requires opts[:credentials].
See DpExchange.Webull.get_option_chain/2.
The sorted expiry dates of the fixture chain. Requires opts[:credentials].
See DpExchange.Webull.get_option_expirations/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_option_greeks/2.
A remembered order by client order id; an unknown id is {:refused, :not_found}.
A fixed two-level book for an equity-style symbol; a crypto pair is refused.
The one fixed order plus every order this process placed.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_payment_method/3.
Fixture open positions for one account. Requires opts[:account_id] and the credentials.
See DpExchange.Webull.get_positions/1.
A fixed fixture price for BTC-USD, ETH-USD, SOL-USD or XRP-USD.
DpExchange.Webull.get_prices/2's shape: a result for every symbol asked for, each
exactly what get_price/2 gives that symbol here.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_rate_limit_status/2.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_roles/1.
One fixture screener row for the named screener. Requires opts[:credentials].
See DpExchange.Webull.get_screener/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_staking_balances/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_staking_history/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_staking_rates/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_staking_rewards/1.
The four fixture crypto symbols. Requires opts[:credentials].
See DpExchange.Core.Venue.get_symbols/1.
A fixture bid and ask around the symbol's fixture price, with nil sizes.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_trade_history/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_trade_volume/2.
Two fixed prints for an equity-style symbol; a crypto pair is refused.
Fixture cash activity of every type, filtered to opts[:activity_types] when given.
Fixture cash activity, filtered to opts[:activity_types] (default deposits, withdrawals
and transfers).
One fixed volume-profile row for the symbol. Requires opts[:credentials].
See DpExchange.Core.Venue.get_volume_profile/3.
One watchlist with its members: the fixture wl-1 or one this process created.
Any other id is {:refused, :not_found}. See DpExchange.Webull.get_watchlist/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.list_approved_addresses/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.list_custody_fees/2.
The one fixture event-contract category. Requires opts[:credentials].
See DpExchange.Webull.list_event_categories/1.
The fixture events of series KXGDP; any other opts[:series_symbol] lists none.
:series_symbol is required ({:error, :series_symbol_required}). Requires
opts[:credentials]. See DpExchange.Webull.list_event_events/1.
The fixture event-contract market as ONE page (pagination_key: nil), narrowed by
opts[:series_symbol], opts[:event_symbol] and opts[:symbols]. status and
tradable_status are two fields, as on the real row. Requires opts[:credentials].
See DpExchange.Webull.list_event_markets/1.
The fixture event series as ONE page (pagination_key: nil), narrowed by opts[:category]
and opts[:symbols]. Requires opts[:credentials].
See DpExchange.Webull.list_event_series/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.list_fee_promos/1.
The two fixture futures contracts, narrowed by opts[:symbols], opts[:code] and
opts[:status]. One of :symbols / :code is required
({:error, :symbols_or_code_required}). Requires opts[:credentials].
See DpExchange.Webull.list_futures_contracts/1.
The one fixture futures product class. Requires opts[:credentials].
See DpExchange.Webull.list_futures_product_classes/1.
The four fixture crypto instruments; XRP-USD has status :unknown.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.list_networks/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.list_payment_methods/2.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.list_portfolios/1.
The fixture watchlist wl-1 plus any this process created, with symbols nil.
Requires opts[:credentials]. See DpExchange.Webull.list_watchlists/1.
Whether the environment moves real money. See DpExchange.Webull.live?/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.market_status/1.
A fixture OAuth exchange (opts[:code]) or refresh (opts[:refresh_token]), exactly one
of them required. Needs no venue credentials: the client id and secret are the arguments.
See DpExchange.Webull.oauth_token/3.
Remembers an order for this process and returns it, so get_order/3 and cancel_order/3
accept its id. A repeated client order id is refused.
One fixture row per request in the batch. An accepted order is remembered under its
client_order_id, so get_order/3 and cancel_order/3 find it; the second row (index 1)
is always refused with INSUFFICIENT_BUYING_POWER and is not remembered.
A fixed estimated cost and fee for a valid request; no order is remembered.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.preview_replace/4.
The real adapter's name, DpExchange.Webull.provider_name/0.
See DpExchange.Core.Venue.provider_name/0.
Fixture increments and limits: crypto pairs get cent and 1e-8 steps, other symbols a whole-share increment. An unlisted crypto pair is refused.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.quote_conversion/4.
The quote currencies the symbol mapping recognises. See DpExchange.Webull.quotes/0.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.remove_approved_address/3.
Removes instruments from a watchlist this fake holds. Another id is
{:refused, :not_found}. Requires opts[:credentials].
See DpExchange.Webull.remove_watchlist_instruments/3.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.rename_account/3.
Amends an order this process remembers; an unknown id is {:refused, :not_found}.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.request_approved_address/4.
The real adapter's id, DpExchange.Webull.runtime_id/0.
See DpExchange.Core.Venue.runtime_id/0.
The screener identifiers get_screener/2 takes.
Reorders instruments of a watchlist this fake holds. opts[:sorts] is required
({:error, :sorts_required}). Another id is {:refused, :not_found}; order is not
modelled. Requires opts[:credentials].
See DpExchange.Webull.sort_watchlist_instruments/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.stake/3.
Answers :ignore: the fake has no processes. See DpExchange.Core.Venue.start_link/1.
Whether the environment carries a live stream. See DpExchange.Webull.streaming?/1.
Delivers a fixture quote and top of book for each listed symbol to opts[:to] (default
self()), as {:dp_exchange, :webull, payload}.
Validates opts[:to] and answers :ok; this fake emits no notices.
See DpExchange.Core.Venue.subscribe_notices/1.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.test_connection/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.transfer_internal/4.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.unstake/3.
Drops the named symbols from this process's fake subscription and answers :ok.
See DpExchange.Core.Venue.unsubscribe/2.
Narrows this process's fake subscription to the listed symbols and delivers fixtures for
newly added ones. See DpExchange.Core.Venue.update_symbols/2.
Renames a watchlist; opts[:symbols] is refused because membership is not changed here.
Requires opts[:credentials]. See DpExchange.Webull.update_watchlist/2.
Calls the venue does not serve. See DpExchange.Webull.venue_does_not_serve/0.
The symbols this process's fake was asked to stream, sorted. The fake delivers at once, so
this is the union of what it has delivered to; see DpExchange.Webull.wanted/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.withdraw/5.
Functions
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.add_payment_method/2.
@spec add_watchlist_instruments(String.t(), [String.t()], keyword()) :: {:ok, map()} | {:error, term()} | {:refused, term()}
Adds instruments to a watchlist this fake holds (wl-1 or one created in this process).
Another id is {:refused, :not_found}. Requires opts[:credentials].
See DpExchange.Webull.add_watchlist_instruments/3.
Whether bars of timeframe are forward-adjusted. See DpExchange.Webull.adjusted?/1.
The real adapter's asset classes, DpExchange.Webull.asset_classes/0.
See DpExchange.Core.Venue.asset_classes/0.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.cancel_all_orders/2.
Marks a remembered order cancelled and answers {:ok, :cancelled}; an unknown id is
{:refused, :not_found}.
Requires opts[:account_id] and the credentials. See DpExchange.Core.Venue.cancel_order/3.
The real adapter's declaration, DpExchange.Webull.capabilities/0, unchanged.
See DpExchange.Core.Venue.capabilities/0.
The status of a token: the one create_token/1 mints is PENDING, any other is
INVALID. A token that is not text is {:error, {:invalid_field, :token}}. Requires
opts[:credentials]. See DpExchange.Webull.check_token/2.
A child spec that starts nothing: start_link/1 answers :ignore.
Unlike DpExchange.Webull.child_spec/1 it does not wrap credentials or declare a
supervisor. See DpExchange.Core.Venue.child_spec/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.close_position/3.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.commit_conversion/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.convert/4.
Every symbol this process's fake has delivered to, as %{symbol => :stream}.
See DpExchange.Core.Venue.coverage/1.
This process's delivered symbols split into :quotes and :top_of_book; an empty kind
is omitted. See DpExchange.Core.Venue.coverage_by_kind/1.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.create_account/1.
Mints a PENDING fixture token, as the real endpoint does. Requires opts[:credentials].
See DpExchange.Webull.create_token/1.
Remembers a watchlist for this process and returns it with its members.
Requires opts[:credentials]. See DpExchange.Webull.create_watchlist/3.
Deletes a watchlist this process knows; an unknown id is {:refused, :not_found}.
Requires opts[:credentials]. See DpExchange.Webull.delete_watchlist/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.estimate_withdrawal_fee/4.
@spec fundamental_kinds() :: [atom()]
The fundamentals kinds get_fundamental/3 reaches.
Two fixture accounts, one CRYPTO and one INDIVIDUAL_MARGIN. Requires the credentials.
See DpExchange.Webull.get_accounts/2.
One fixed auction imbalance row.
opts[:auction] is required (:opening or :closing): {:error, :auction_required}
without it. Requires opts[:credentials]. See
DpExchange.Core.Venue.get_auction_imbalance/2.
One fixture USD balance, available_balance nil, stamped with the current time.
Requires opts[:account_id] and the credentials. See
DpExchange.Core.Venue.get_balances/2.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_contract_stats/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_conversion/2.
Fixture corporate events for opts[:symbol], which is required
({:error, :symbol_required} without it). Requires opts[:credentials].
See DpExchange.Webull.get_corporate_events/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_deposit_address/3.
@spec get_event_order_book(String.t(), keyword()) :: {:ok, map()} | {:error, term()} | {:refused, term()}
The fixture event-contract book: four sides (yes_bids, yes_asks, no_bids,
no_asks), the venue's own level maps, and quote_time in milliseconds. Requires
opts[:credentials]. See DpExchange.Webull.get_event_order_book/2.
@spec get_event_trades(String.t(), keyword()) :: {:ok, [map()]} | {:error, term()} | {:refused, term()}
Three fixture ticks (fewer when opts[:limit] is smaller) for one event-contract market,
in the venue's own shape (a yes price, a no price, a yes/no side). Requires
opts[:credentials]. See DpExchange.Webull.get_event_trades/2.
The captured published crypto spread, as the real adapter answers it.
Needs no credentials: it builds no request. See DpExchange.Core.Venue.get_fees/2.
One fixture filing for the symbol. Requires opts[:credentials].
See DpExchange.Webull.get_filings/2.
One fixture financial statement of the requested kind. Requires opts[:credentials].
See DpExchange.Webull.get_financials/3.
@spec get_fundamental(atom(), String.t(), keyword()) :: {:ok, [map()]} | {:error, term()} | {:refused, term()}
One fixture row of the named fundamentals kind. An unknown kind is
{:error, {:unknown_fundamental, kind}}. Requires opts[:credentials].
See DpExchange.Webull.get_fundamental/3.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_funding/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_fx_rate/3.
One fixture candle for a listed symbol, kept only if it falls inside range.
Requires opts[:credentials]; volume is nil. See
DpExchange.Core.Venue.get_historical_prices/4.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_market_overview/1.
One fixture news item. Requires opts[:credentials]. See DpExchange.Webull.get_news/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_notional_balances/3.
A small fixture option chain for the underlying. Requires opts[:credentials].
See DpExchange.Webull.get_option_chain/2.
The sorted expiry dates of the fixture chain. Requires opts[:credentials].
See DpExchange.Webull.get_option_expirations/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_option_greeks/2.
A remembered order by client order id; an unknown id is {:refused, :not_found}.
Requires opts[:account_id] and the credentials. See DpExchange.Core.Venue.get_order/3.
A fixed two-level book for an equity-style symbol; a crypto pair is refused.
Requires opts[:credentials] and honours opts[:depth]. See
DpExchange.Core.Venue.get_order_book/2.
The one fixed order plus every order this process placed.
Requires opts[:account_id] and the credentials. See DpExchange.Core.Venue.get_orders/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_payment_method/3.
Fixture open positions for one account. Requires opts[:account_id] and the credentials.
See DpExchange.Webull.get_positions/1.
A fixed fixture price for BTC-USD, ETH-USD, SOL-USD or XRP-USD.
Requires opts[:credentials] ({:error, {:missing_credentials, :webull}} without them);
volume is nil; any other symbol or category is refused. See
DpExchange.Core.Venue.get_price/2.
DpExchange.Webull.get_prices/2's shape: a result for every symbol asked for, each
exactly what get_price/2 gives that symbol here.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_rate_limit_status/2.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_roles/1.
One fixture screener row for the named screener. Requires opts[:credentials].
See DpExchange.Webull.get_screener/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_staking_balances/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_staking_history/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_staking_rates/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_staking_rewards/1.
The four fixture crypto symbols. Requires opts[:credentials].
See DpExchange.Core.Venue.get_symbols/1.
A fixture bid and ask around the symbol's fixture price, with nil sizes.
Requires opts[:credentials]; an unlisted symbol is refused. See
DpExchange.Core.Venue.get_top_of_book/2.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_trade_history/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.get_trade_volume/2.
Two fixed prints for an equity-style symbol; a crypto pair is refused.
Requires opts[:credentials]. See DpExchange.Core.Venue.get_trades/2.
Fixture cash activity of every type, filtered to opts[:activity_types] when given.
Requires opts[:account_id] and the credentials. See DpExchange.Webull.get_transactions/2.
Fixture cash activity, filtered to opts[:activity_types] (default deposits, withdrawals
and transfers).
Requires opts[:account_id] and the credentials. See DpExchange.Webull.get_transfers/2.
One fixed volume-profile row for the symbol. Requires opts[:credentials].
See DpExchange.Core.Venue.get_volume_profile/3.
One watchlist with its members: the fixture wl-1 or one this process created.
Any other id is {:refused, :not_found}. See DpExchange.Webull.get_watchlist/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.list_approved_addresses/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.list_custody_fees/2.
The one fixture event-contract category. Requires opts[:credentials].
See DpExchange.Webull.list_event_categories/1.
The fixture events of series KXGDP; any other opts[:series_symbol] lists none.
:series_symbol is required ({:error, :series_symbol_required}). Requires
opts[:credentials]. See DpExchange.Webull.list_event_events/1.
@spec list_event_markets(keyword()) :: {:ok, %{rows: [map()], pagination_key: String.t() | nil}} | {:error, term()} | {:refused, term()}
The fixture event-contract market as ONE page (pagination_key: nil), narrowed by
opts[:series_symbol], opts[:event_symbol] and opts[:symbols]. status and
tradable_status are two fields, as on the real row. Requires opts[:credentials].
See DpExchange.Webull.list_event_markets/1.
@spec list_event_series(keyword()) :: {:ok, %{rows: [map()], pagination_key: String.t() | nil}} | {:error, term()} | {:refused, term()}
The fixture event series as ONE page (pagination_key: nil), narrowed by opts[:category]
and opts[:symbols]. Requires opts[:credentials].
See DpExchange.Webull.list_event_series/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.list_fee_promos/1.
The two fixture futures contracts, narrowed by opts[:symbols], opts[:code] and
opts[:status]. One of :symbols / :code is required
({:error, :symbols_or_code_required}). Requires opts[:credentials].
See DpExchange.Webull.list_futures_contracts/1.
@spec list_futures_product_classes(keyword()) :: {:ok, [map()]} | {:error, term()} | {:refused, term()}
The one fixture futures product class. Requires opts[:credentials].
See DpExchange.Webull.list_futures_product_classes/1.
The four fixture crypto instruments; XRP-USD has status :unknown.
Requires opts[:credentials]; only the US_CRYPTO category is served. See
DpExchange.Webull.list_instruments/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.list_networks/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.list_payment_methods/2.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.list_portfolios/1.
The fixture watchlist wl-1 plus any this process created, with symbols nil.
Requires opts[:credentials]. See DpExchange.Webull.list_watchlists/1.
Whether the environment moves real money. See DpExchange.Webull.live?/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.market_status/1.
@spec oauth_token(String.t(), String.t(), keyword()) :: {:ok, map()} | {:error, term()} | {:refused, term()}
A fixture OAuth exchange (opts[:code]) or refresh (opts[:refresh_token]), exactly one
of them required. Needs no venue credentials: the client id and secret are the arguments.
See DpExchange.Webull.oauth_token/3.
Remembers an order for this process and returns it, so get_order/3 and cancel_order/3
accept its id. A repeated client order id is refused.
Requires opts[:account_id] and the credentials. See DpExchange.Core.Venue.place_order/3.
One fixture row per request in the batch. An accepted order is remembered under its
client_order_id, so get_order/3 and cancel_order/3 find it; the second row (index 1)
is always refused with INSUFFICIENT_BUYING_POWER and is not remembered.
Requires opts[:account_id] and the credentials. See DpExchange.Webull.place_orders/3.
A fixed estimated cost and fee for a valid request; no order is remembered.
Requires opts[:account_id] and the credentials. See DpExchange.Webull.preview_order/3.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.preview_replace/4.
The real adapter's name, DpExchange.Webull.provider_name/0.
See DpExchange.Core.Venue.provider_name/0.
Fixture increments and limits: crypto pairs get cent and 1e-8 steps, other symbols a whole-share increment. An unlisted crypto pair is refused.
Requires opts[:credentials]. See DpExchange.Core.Venue.quantization/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.quote_conversion/4.
@spec quotes() :: [String.t()]
The quote currencies the symbol mapping recognises. See DpExchange.Webull.quotes/0.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.remove_approved_address/3.
@spec remove_watchlist_instruments(String.t(), [String.t()], keyword()) :: {:ok, map()} | {:error, term()} | {:refused, term()}
Removes instruments from a watchlist this fake holds. Another id is
{:refused, :not_found}. Requires opts[:credentials].
See DpExchange.Webull.remove_watchlist_instruments/3.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.rename_account/3.
Amends an order this process remembers; an unknown id is {:refused, :not_found}.
Requires opts[:account_id] and the credentials. See DpExchange.Webull.replace_order/4.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.request_approved_address/4.
The real adapter's id, DpExchange.Webull.runtime_id/0.
See DpExchange.Core.Venue.runtime_id/0.
@spec screeners() :: [String.t()]
The screener identifiers get_screener/2 takes.
@spec sort_watchlist_instruments(String.t(), keyword()) :: {:ok, map()} | {:error, term()} | {:refused, term()}
Reorders instruments of a watchlist this fake holds. opts[:sorts] is required
({:error, :sorts_required}). Another id is {:refused, :not_found}; order is not
modelled. Requires opts[:credentials].
See DpExchange.Webull.sort_watchlist_instruments/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.stake/3.
Answers :ignore: the fake has no processes. See DpExchange.Core.Venue.start_link/1.
Whether the environment carries a live stream. See DpExchange.Webull.streaming?/1.
Delivers a fixture quote and top of book for each listed symbol to opts[:to] (default
self()), as {:dp_exchange, :webull, payload}.
Under environment: :uat it answers {:error, {:streaming_unavailable, :uat}}, as the
real feed does. A call that changes what is wanted needs opts[:credentials] (the App Key
while no shard is open, {:error, {:missing_required_field, :app_key}} otherwise missing
credentials, {:error, {:missing_credentials, :webull}}); see stream_gate/2. State is per
process. See DpExchange.Core.Venue.subscribe/2.
Validates opts[:to] and answers :ok; this fake emits no notices.
See DpExchange.Core.Venue.subscribe_notices/1.
Not ported: answers {:error, :not_supported}.
See DpExchange.Core.Venue.test_connection/2.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.transfer_internal/4.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.unstake/3.
Drops the named symbols from this process's fake subscription and answers :ok.
See DpExchange.Core.Venue.unsubscribe/2.
Narrows this process's fake subscription to the listed symbols and delivers fixtures for
newly added ones. See DpExchange.Core.Venue.update_symbols/2.
Renames a watchlist; opts[:symbols] is refused because membership is not changed here.
Requires opts[:credentials]. See DpExchange.Webull.update_watchlist/2.
Calls the venue does not serve. See DpExchange.Webull.venue_does_not_serve/0.
@spec wanted(keyword()) :: [DpExchange.Core.Venue.symbol()]
The symbols this process's fake was asked to stream, sorted. The fake delivers at once, so
this is the union of what it has delivered to; see DpExchange.Webull.wanted/1.
Not served by this venue: answers {:error, :not_supported}.
See DpExchange.Core.Venue.withdraw/5.