A macro-first Elixir wrapper around CCXT that runs the JavaScript bundle inside QuickBEAM — no Node.js. Per-exchange/per-method wrappers (REST + WS, public + private) are generated at compile time from CCXT's own type definitions.
Status: Phase 1 complete. The project ships
CcxtOcx.Runtime,CcxtOcx.RuntimePool,CcxtOcx.Error,CcxtOcx.Tiers,CcxtOcx.BundleSurface(+mix ccxt.verify_bundle), andCcxtOcx.Telemetry— the foundation every later phase sits on. See ROADMAP.md for what's next.
{:ok, server} = CcxtOcx.Runtime.start_link(name: :ccxt)
CcxtOcx.Runtime.info(:ccxt)
# => %{ccxt_version: "4.5.52", exchange_count: 122, bundle_path: "..."}
CcxtOcx.Runtime.eval(:ccxt, "self.ccxt.default.exchanges.length")
# => {:ok, 122}The runtime applies browser stubs, loads
node_modules/ccxt/dist/ccxt.browser.min.js once, and exposes eval/3,
call/4, with_runtime/2, and info/1. Caller processes that crash
mid-call do not affect the runtime — see the "Isolation" tests in
test/ccxt_ocx/runtime_test.exs.
Telemetry events are emitted under the [:ccxt_ocx] prefix:
[:ccxt_ocx, :runtime, :memory]— QuickJS memory stats from any runtime or pool worker (seeCcxtOcx.Runtime.memory/1andCcxtOcx.RuntimePool.memory/1).[:ccxt_ocx, :rest, :start | :stop | :exception]— reserved for Phase 2defunified(Task 7); metric definitions exist now so dashboards stay stable when emission lights up.[:ccxt_ocx, :ws, :tick]— reserved for Phase 3defstreaming(Task 11).
Full event contract and handler examples live in CcxtOcx.Telemetry.
ccxt_ocx ships a first-class PromEx
plugin that maps every event above to Prometheus metrics with zero glue.
PromEx is an optional dependency — consumers add it to their own app:
# mix.exs (consumer app)
{:prom_ex, "~> 1.11"}Then reference the plugin in PromEx config:
defmodule MyApp.PromEx do
use PromEx, otp_app: :my_app
@impl true
def plugins do
[
PromEx.Plugins.Application,
PromEx.Plugins.Beam,
# Optional :pool / :poll_rate opts enable periodic pool memory snapshots.
{CcxtOcx.PromEx.Plugin, pool: MyApp.CcxtPool, poll_rate: 10_000}
]
end
endProvided metrics:
ccxt_ocx_runtime_memory_malloc_size_bytes/_used_size_bytes/_obj_count(last_value, tags:[:server, :pool, :phase])ccxt_ocx_rest_duration_milliseconds(distribution, buckets: 10/50/100/250/500/1000/5000 ms)ccxt_ocx_rest_total(counter)ccxt_ocx_rest_exceptions_total(counter)ccxt_ocx_ws_ticks_total(counter)
See CcxtOcx.PromEx.Plugin for tag-stability details and polling config.
While building the macro layer, the fastest way to understand real behavior is to drive the system live:
iex -S mix tidewaveSee docs/tidewave_examples.md for copy-pasteable project_eval patterns, the critical "define global + call" technique, Deribit options exploration, memory/telemetry watching, and more.
These examples are maintained from actual Tidewave sessions and will evolve into the usage patterns for the generated macros.
def deps do
[
{:ccxt_ocx, "~> 0.1.0"}
]
endYou also need the ccxt JavaScript bundle on disk:
npm install
# installs ccxt to node_modules/ccxt/mix deps.get
mix compile --warnings-as-errors
mix test.json
mix dialyzer.json --quiet
mix credo --strict --format json
mix sobelowA Tidewave MCP server is wired in at port 4014:
iex -S mix tidewaveTBD.