Packages
dp_exchange_webull
0.2.21
0.4.59
0.4.58
0.4.57
0.4.56
0.4.55
0.4.54
0.4.53
0.4.52
0.4.51
0.4.50
0.4.49
0.4.48
0.4.47
0.4.46
0.4.45
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.31
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.3.3
0.3.2
0.3.1
0.2.30
0.2.29
0.2.28
0.2.27
0.2.26
0.2.25
0.2.24
0.2.23
0.2.22
0.2.21
0.2.20
0.2.19
0.2.18
0.2.17
0.2.16
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.1.25
0.1.24
0.1.23
0.1.22
0.1.21
0.1.20
0.1.19
0.1.18
0.1.17
0.1.16
0.1.15
0.1.14
0.1.13
0.1.12
0.1.11
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
EXPERIMENTAL — Webull venue package for the DpExchange family. Market data, trading and streaming behind the shared DpExchange.Core.Venue facade.
Current section
Files
Jump to
Current section
Files
dp_exchange_webull
mix.exs
mix.exs
defmodule DpExchangeWebull.MixProject do
use Mix.Project
# SEED, not a release. CI increments the last segment of whatever it finds here, so
# `0.1.0` first publishes as `0.1.1`. Hand-editing this to `0.2.0` is how a breaking
# change is signalled. The bump script matches the attribute assignment below by its
# exact literal form — do not reformat it, and do not repeat that form anywhere else
# in this file, comments included, or the script will rewrite the wrong line.
@version "0.2.21"
@source_url "https://github.com/DistortionPoint/dp-exchange-webull"
def project do
[
app: :dp_exchange_webull,
version: @version,
elixir: "~> 1.18",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
deps: deps(),
aliases: aliases(),
dialyzer: dialyzer(),
preferred_cli_env: preferred_cli_env(),
test_coverage: test_coverage(),
# Hex.pm
name: "DpExchangeWebull",
description:
"EXPERIMENTAL — Webull venue package for the DpExchange family. Market data, " <>
"trading and streaming behind the shared DpExchange.Core.Venue facade.",
package: package(),
source_url: @source_url,
docs: docs(),
usage_rules: usage_rules()
]
end
# No `mod:` — a library does not start itself. A consumer supervises this venue
# through `child_spec/1` and decides restart strategy, shutdown order and naming. A
# consumer that has not asked for Webull must not find a socket open.
def application do
[extra_applications: [:logger]]
end
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_env), do: ["lib"]
defp deps do
[
# The contract. Three-part pin: while Core is 0.x a minor bump may break us, and
# that is the signal it is meant to send.
#
# `0.1.68` is the floor because `capabilities/0` declares
# `no_venue_contact: [{:get_fees, 2}]`, and `Capabilities.new/1` builds the struct
# with `struct!/2` — a key the struct does not define raises `KeyError` rather than
# being silently dropped. `no_venue_contact` was added to `Capabilities` in Core
# 0.1.68; under any lower floor (this pin previously allowed down to 0.1.48) a
# consumer resolving an old-enough Core gets this package's `capabilities/0`
# crashing on every call. `~> 0.1.48` compiled and passed here only because CI always
# resolves the newest allowed version. 0.1.68 also covers `Timeframe.nameable/0`
# admitting `1y` (needed since 0.1.57 — see `webull_test.exs`'s "1y is declared"
# test), so it is the binding constraint, not an additional one.
{:dp_exchange_core, "~> 0.1.68"},
# This venue's own transport. Core ships no transport library at any strength —
# a venue that speaks WebSocket ships what it needs to speak it.
#
# `~> 0.5`, not `~> 0.4`: `Socket.disconnect/2` calls `WebSockex.send_frame/3`, and
# the third argument — the send timeout — only exists from 0.5. Under `~> 0.4` this
# package resolved 0.5.1 locally and so compiled and passed, while a consumer who
# resolved 0.4.x got `:undef` at the call. DpCryptoManagement hit exactly that on
# 2026-09-08. Arity is only checked when the function runs, so it shipped as a
# compile warning rather than an error, and only on a path nothing exercised.
#
# `send_frame/2` works on both and is the WRONG fix: it takes WebSockex's own
# 5_000ms default, where `@disconnect_timeout_ms` is deliberately 500ms because
# `disconnect/2` runs inside `Feed.terminate/2`, under a supervisor's shutdown
# budget. Ten times the wait during shutdown is not a free compatibility win, so the
# honest fix is to require the version whose API this package actually uses.
{:websockex, "~> 0.5"},
{:jason, "~> 1.4"},
{:decimal, "~> 2.0"},
# Dev/Test
{:usage_rules, "~> 1.2", only: :dev},
{:ex_doc, "~> 0.34", only: :dev, runtime: false},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
{:sobelow, "~> 0.13", only: [:dev, :test], runtime: false},
# Exercises the REST pipeline through Req's test seam, so tier-1 tests reach no
# network. Never ships.
{:plug, "~> 1.16", only: :test}
]
end
defp test_coverage, do: [threshold: 90, ignore_modules: []]
defp aliases do
[quality: ["format --check-formatted", "credo --strict", "dialyzer", "sobelow --config"]]
end
defp dialyzer do
[plt_add_apps: [:mix, :ex_unit], plt_file: {:no_warn, "priv/plts/dialyzer.plt"}]
end
defp preferred_cli_env, do: [quality: :test]
defp package do
[
licenses: ["MIT"],
links: %{"GitHub" => @source_url},
maintainers: ["bcatherall"],
# `files:` belongs HERE, not at the project level. Hex reads `package[:files]`; a
# `files:` in `project/0` is silently ignored and Hex ships its own defaults —
# which puts `priv/plts/dialyzer.plt` in the tarball and leaves out anything you
# meant to add. Nothing warns. Inspect `mix hex.build` before every publish.
#
# `priv/` is absent because the only thing in it is the dialyzer PLT.
# `config/` is absent: it governs this package's own dev and test, never a
# consumer's.
files: [
"lib",
"mix.exs",
".formatter.exs",
"README.md",
"LICENSE",
"CHANGELOG.md",
"AGENTS.md",
"usage-rules.md",
"docs/reference"
]
]
end
defp docs do
[
main: "DpExchange.Webull",
extras: ["README.md", "CHANGELOG.md", "usage-rules.md"],
source_ref: "v#{@version}"
]
end
defp usage_rules, do: [file: "AGENTS.md", usage_rules: [:usage_rules]]
end