Skip to main content

doc/ccxt-pro-elixir-target.md

# CCXT Pro Elixir Target

This document records the current Binance-first CCXT Pro Elixir generation path.

## Goal

Build an extensible Elixir target for CCXT Pro from `ts/src/pro/binance.ts`.
The target should move toward TypeScript AST to IR to Elixir code generation,
while websocket lifecycle, subscriptions, caches, and resolve/reject semantics
live in a reusable Elixir Pro runtime.

This document is Binance-first. The current goal is to make Binance Pro
production-mature before generalizing to another exchange. Binance-specific
logic should stay in metadata, URL selection, signing, and routing; method
coverage, websocket lifecycle, caches, parser output, and request/waiter
semantics should remain reusable runtime or IR concepts.

## Maturity Checklist

The Elixir Pro target is considered mature only when each item below has
current evidence from generated files, manifest assertions, tests, or live
smoke runs:

- Instance model: `Ccxt.Pro.binance/1` and `Ccxt.Pro.Binance.new/1` accept
  CCXT-style `apiKey`, `secret`, `options`, and `binanceEnv`/`binance_env`;
  exchange-first wrappers merge instance credentials/options with call options
  so users do not need to pass full opts to every method.
- Source coverage: `pro_manifest.json` records all `107` methods from
  `ts/src/pro/binance.ts`, with `unsupportedMethods: []`, source spans,
  generated/runtime-owned status, `renderPlan`, and runtime contracts.
- IR coverage: public watch, private watch, unwatch, authenticate,
  listenKey/listenToken, websocket API request, signing, parser, cache,
  handler, and reconnect behavior must be represented by structured
  `irSummary.nodes` or explicit runtime contracts. Complex branches must not be
  patched into output with regex/string fallbacks.
- Runtime coverage: `runtimeSemanticCoverage` must include the required
  features for OTP Registry/DynamicSupervisor ownership, websocket reconnect,
  message-hash routing, subscription/unwatch lifecycle, request-id routing,
  websocket API response routing, listenKey auth/keepalive, ArrayCache,
  OrderBook, balance, positions, order/trade caches, public/private event
  routing, parser output, and waiter error rejection. `Ccxt.ProLifecycleCoverage`
  exposes this evidence as a runtime coverage matrix, and
  `doc/ccxt-pro-cache-parity.md` tracks specialized cache-class parity, and
  `doc/ccxt-pro-lifecycle-coverage.md` records the current human-readable view.
- Live-test policy: public streams run as ordinary live smoke; private
  read-only websocket API calls use `BINANCE_PROD_API_KEY` /
  `BINANCE_PROD_API_SECRET` or demo credentials where supported; prod-only
  order mutation tests require `CCXT_PRO_ENABLE_PROD_ORDER_LIVE=true`, place a
  non-marketable order, and cancel it immediately.
- Risk boundary: Binance Pro currently exposes order mutations only
  (`createOrderWs`, `editOrderWs`, `cancelOrderWs`, `cancelAllOrdersWs`) as
  state-changing methods. Withdraw, transfer, borrow, repay, convert, and gift
  code APIs are REST-surface risks, not Pro manifest methods; they remain
  dry-run/signature/manual-confirmation only.
- Stable gates: `npm run assertElixirPro`, `npm run checkElixir`, and
  `mix compile --warnings-as-errors` must pass against the current worktree.
  Live smoke remains env-gated and must report whether a branch executed,
  skipped for missing credentials, or recorded an expected Binance permission
  limitation.

## Completion Audit Snapshot

Current evidence for the Binance-only Pro target:

- Instance model: `Ccxt.Pro.binance/1`, `Ccxt.Pro.Binance.new/1`, and
  exchange-first wrappers are generated. `pro_manifest.json` records `52`
  required exchange-callable public methods and `52` wrapped methods, with
  `getPrivateWsUrl` explicitly excluded as a websocket URL helper. ExUnit
  asserts wrapper coverage and no-opts instance calls for signed helpers.
- Source coverage: `pro_manifest.json` records `107` Binance Pro TypeScript
  methods, split into `66` generated methods and `41` runtime-owned methods,
  with `unsupportedMethods: []`.
- Traceability: every manifest method records `source.path/startLine/endLine`,
  `irSummary.nodes`, and generated/runtime-owned lowering status. Generated
  methods have `renderPlan` entries; runtime-owned methods have runtime
  contracts.
- IR/codegen discipline: `npm run assertElixirPro` fails on unsupported source
  methods, missing render plans, missing runtime contracts, generated drift,
  incomplete exchange wrappers, or missing runtime semantic features. The
  broader `npm run checkElixir` gate also asserts the non-Pro selected Elixir
  target has no fallback markers or regex/string-style source rewrites.
- Runtime semantics: `runtimeSemanticCoverage` records `18` required features
  and current evidence entries for OTP registry/dynamic supervisor ownership,
  websocket reconnect, message-hash routing, subscription/unwatch lifecycle,
  request-id routing, websocket API response routing, listenKey auth/keepalive,
  ArrayCache, OrderBook, balance, positions, order/trade caches,
  public/private event routing, parser output, and waiter rejection.
  `Ccxt.ProLifecycleCoverage.summary/0` reports `18/18` required features
  covered with `125` evidence entries.
- Live/demo/prod policy: public streams and signed read-only websocket API calls
  run through env-gated live smoke. Demo read-only websocket API calls run under
  `demo_private_live`. Production order mutation is isolated behind
  `CCXT_PRO_ENABLE_PROD_ORDER_LIVE=true`, uses a non-marketable spot limit
  order, and immediately cancels it. Event-driven private watchers are covered
  deterministically through fake-connection/cache/snapshot tests instead of
  unstable live event waits.
- Method-level evidence: `methodCoverageMatrix` records one row for every
  Binance Pro source method, including generated/runtime owner, unit evidence,
  live evidence, risk class, and skip/manual reason. The matrix is part of the
  generated manifest and is asserted by ExUnit.
- Risk inventory: Binance Pro mutation surface is limited to `createOrderWs`,
  `editOrderWs`, `cancelOrderWs`, and `cancelAllOrdersWs`; manifest tests assert
  that withdraw, transfer, borrow, repay, convert, and gift-code methods are not
  Pro methods. `createOrderWs`, `editOrderWs`, `cancelOrderWs`, `fetchOrderWs`,
  and `watchOrders` are covered by gated non-marketable production live tests.
  `cancelAllOrdersWs` remains manual-confirmed only because cancel-all can
  cancel unrelated user orders on the same symbol.
- Manual-only boundary: `watchMyTrades` requires a real fill event and therefore
  remains `manual-fill-only`; the non-marketable production order policy
  intentionally cannot prove fills. Private liquidation watchers are
  `event-driven-private-unit-only` because waiting for a real liquidation is not
  deterministic or safe as an always-on live gate.
- Binance-only scope: the original broader target mentioned a second Pro
  exchange as a generalization proof, but the active scope is explicitly
  Binance-only. No second-exchange work is required for the current goal.

Completion boundary:

- The current Binance-first goal is complete when the generated manifest records
  `107/107` source methods, `unsupportedMethods: []`, a synchronized
  `methodCoverageMatrix`, passing unit/generation gates, ordinary live smoke,
  and gated non-marketable production order smoke.
- Manual-only rows are acceptable only when the matrix records their risk and
  skip reason. At this snapshot those rows are exactly `watchMyTrades`
  (`manual-fill-only`) and `cancelAllOrdersWs`
  (`manual-confirmed-cancel-all-only`).
- Re-run the live gates whenever credentials, Binance permissions, network
  restrictions, signing runtime, or websocket runtime behavior changes.

## Current Architecture

The runtime is OTP-based:

- `Ccxt.Application` starts the application supervision tree.
- `Ccxt.Pro.Supervisor` starts `Ccxt.Pro.Registry` and `Ccxt.Pro.ConnectionSupervisor`.
- `Ccxt.Pro.connection/1` returns one dynamically supervised connection process per websocket URL.
- `Ccxt.Pro.connections/0`, `Ccxt.Pro.connection_info/1`, and
  `Ccxt.Pro.close_connection/1` provide Elixir runtime introspection and
  explicit connection lifecycle control for IEx/manual testing. These helpers
  do not change CCXT Pro `unWatch*` semantics: `unWatch*` still only sends
  `UNSUBSCRIBE` and waits for the exchange acknowledgement.
- CCXT-style `:verbose` can be passed through generated watch/request options;
  it toggles connection-level websocket send/message/close logging on the
  reused URL connection. The Elixir runtime also emits `:telemetry` events
  under `[:ccxt, :pro, ...]`, and `Ccxt.Pro.attach_debug_logger/0` /
  `Ccxt.Pro.detach_debug_logger/0` provide an IEx-friendly event logger.
- `Ccxt.Pro.Binance.stream_ticker/2`, `stream_trades/4`,
  `stream_order_book/3`, and `stream_ohlcv/5` are Elixir-native convenience
  wrappers over generated `watch_*` methods; they keep CCXT's one-message watch
  semantics intact while letting callers consume repeated public updates with
  `Enum.take/2`, `Stream.each/2`, or other `Enumerable` tooling.
- Private watcher convenience wrappers are available as `stream_balance/1`,
  `stream_orders/4`, `stream_positions/4`, and `stream_my_trades/4`. These
  repeat the generated private `watch_*` methods and intentionally do not imply
  a private `unwatch_*` API; use `Ccxt.Pro.close_connection/1` for explicit
  connection shutdown.
- `Ccxt.Pro.Connection` owns websocket subscriptions, message hash routing, waiter resolution, and disconnect rejection.
- `Ccxt.Pro.Connection.handle_disconnect/2` returns the WebSockex reconnect
  directive and rejects pending waiters with `{:disconnected, reason}`; the
  lifecycle coverage matrix exposes this as `websocket-process-reconnect` with
  `reject-pending-waiters`.
- `Ccxt.Pro.ArrayCache` provides a reusable bounded newest-first cache used by
  public ticker/trade/OHLCV streams, private order/trade/liquidation stream
  retention, and order book pre-snapshot delta buffers. Subscriptions can pass
  `:cache_limit` to configure retention per message hash.
- Private stream subscriptions can also carry `:snapshot_payloads`; the
  connection runtime preloads those payloads into the private cache and resolves
  matching waiters immediately. Generated private watch helpers also support
  `:fetch_balance_snapshot`/`:fetchBalanceSnapshot` and
  `:fetch_positions_snapshot`/`:fetchPositionsSnapshot`; those option branches
  can use an injectable `:snapshot_fetcher`, an injectable REST `:rest_fetcher`,
  or the generated high-level REST methods.

Manual examples live under `elixir/examples/`:

- `pro_watch_ticker.exs`
- `pro_unwatch_ticker.exs`
- `pro_connection_info.exs`
- `pro_close_connection.exs`
- `pro_debug_logger.exs`
- `pro_stream_ticker.exs`
- `pro_ticker_worker.exs`
- `pro_public_market_streams.exs`
- `pro_private_readonly.exs`
- `pro_safe_order_lifecycle.exs`
- `pro_order_event_stream.exs`
- `pro_order_event_worker.exs`
- `pro_soak_smoke.exs`
- `pro_structure_persistence.exs`

Run examples from `ccxt/elixir` with `mix run examples/<name>.exs`. Public
examples do not require API keys. Private examples load `../../.env` when it is
present and require production credentials. Order examples require
`CCXT_PRO_ENABLE_PROD_ORDER_LIVE=true` and use non-marketable spot limit orders
with immediate cleanup.

Production public-stream soak is deliberately opt-in and does not require API
keys:

```sh
cd ..
npm run testElixirProSoak
npm run testElixirProLongSoak
```

The soak gate runs `test/ccxt_pro_binance_soak_test.exs` with the `:soak` tag.
It validates a public order-book snapshot, consumes `stream_ticker/2` for
`CCXT_PRO_SOAK_SECONDS` seconds, checks maximum ticker update gaps through
`CCXT_PRO_SOAK_MAX_GAP_MS`, verifies connection introspection, validates
explicit close/reopen behavior for a public ticker watch, verifies supervised
restart after an abnormal connection process exit, and then closes all Pro
websocket connections. The default symbol is `BTC/USDT`; override it with
`BINANCE_PRO_SOAK_SYMBOL`. Because soak is an operational stability gate,
ordinary `mix test` and `npm run checkElixir` exclude it by default.

The long soak gate runs `test/ccxt_pro_binance_long_soak_test.exs` with the
`:soak_long` tag. It defaults to `CCXT_PRO_LONG_SOAK_SECONDS=900` and records
public ticker update count, maximum update gap, telemetry message count,
connection start count, disconnect count, terminated count, and cleanup status.
For a quick sanity run, override `CCXT_PRO_LONG_SOAK_SECONDS=5` and
`CCXT_PRO_LONG_SOAK_MIN_UPDATES=1`.

The generator is currently an AST-driven Pro slice:

- `build/elixirProTranspiler.ts` reads `ts/src/pro/binance.ts` with the TypeScript compiler API.
- The generator builds a `ProMethodModel` for every TypeScript class method.
- Each method model records structured source span
  (`source.path/startLine/endLine`), async/static/visibility, parameters, return
  type, body AST summary, method category, AST-derived `irSummary`, and lowering
  status.
- Every source method now has non-empty structured `irSummary.nodes`; ExUnit
  asserts this for all 107 Binance Pro TypeScript methods.
- Lowering coverage is inferred from `ProMethodModel` category plus AST/body
  signals such as calls, awaits, returns, and statement shape; it no longer
  depends on a hand-maintained required-method/lowering list.
- Generated `elixir/lib/ccxt/pro/binance.ex` includes source trace lines for each method.
- Generated `elixir/lib/ccxt/pro_manifest.json` records coverage, generated
  methods, runtime-owned methods, unsupported methods, unsupported reasons,
  structured source trace, `renderPlan` records for generated methods, and
  `runtimeContract` records for runtime-owned methods.
- Generated method blocks in `renderBinancePro` are now selected through the
  manifest-backed `renderPlan`; inline metadata helpers remain in the module
  scaffold, while non-inline generated methods must resolve through a renderer
  entry before they can be emitted. Non-inline renderer names are dispatched
  through `METHOD_RENDERER_REGISTRY`, so the manifest renderer key is the actual
  generation entrypoint. The generated module insertion points read
  `renderSections.main/parser`, which are derived from non-inline `renderPlan`
  entries and TypeScript source order rather than a hand-maintained method
  order list.
- Runtime-owned method contracts record the owning Elixir module/function, the
  contract kind, and the dispatch/cache/signing signals that connect the
  TypeScript method to reusable Pro runtime behavior.
- Websocket API request methods expose AST-derived sub-IR signals and nodes in the
  manifest, including signed request routing, request waiter routing,
  order-payload create/edit/cancel variants, option routing, market-symbol
  routing, and response filter nodes.
- Public/private stream, unsubscribe, helper, cache, and orderbook methods also
  expose structured `irSummary.nodes`, including watch single/multiple nodes,
  channel families, private user-data auth nodes, cache nodes, unsubscribe
  families, snapshot preload nodes, and orderbook snapshot/delta cache nodes.
- Auth/listenKey, parser, websocket API response handler, public/private event
  handler, and top-level dispatch methods also expose structured nodes for
  signing, listen-token routing, parser branches, response handlers, event
  dispatch, waiter rejection, and cache updates.
- `npm run assertElixirPro` verifies every TypeScript source method is lowered,
  every manifest method has structured source trace matching its AST span, every
  generated method has a render plan, every render entrypoint method is backed by
  a non-inline render plan entry, every non-inline generated method is consumed
  by the module render entrypoint, every non-inline render plan entry resolves
  through `METHOD_RENDERER_REGISTRY`, the registry has no unused renderer keys,
  every runtime-owned method has a complete runtime contract, and compares
  generated output plus manifest byte-for-byte to prevent manual drift.

The current generator uses AST-derived IR summaries for coverage and source
trace. Generated method bodies are emitted through manifest-backed renderer
entries, and stabilized payload/cache/auth/parser/router behavior is also
recorded as smaller reusable Pro IR nodes. Further work can keep splitting
large renderer internals into finer IR nodes, but that is now an incremental
refinement rather than a source-method coverage gap. The public watch renderer slice
emits `watch_ticker/2`, `watch_tickers/2`,
`watch_trades/4`, `watch_trades_for_symbols/4`, `watch_order_book/3`,
`watch_order_book_for_symbols/3`, `watch_ohlcv/5`,
`watch_ohlcv_for_symbols/4`, `watch_liquidations/4`, and
`watch_liquidations_for_symbols/4` from shared Pro IR specs rather than inline
method templates. The ticker-family public stream methods, including mark
prices and bids/asks, are covered through generated
`watch_multi_ticker_helper/5` plus a shared ticker-family wrapper renderer for
`watch_tickers/2`, `watch_mark_prices/2`, and `watch_bids_asks/2`; the helper
delegates public watch-any subscription, cache registration, and parser
market-type selection to a typed ticker-family subscription node. The public
`unwatch_*` methods are covered by shared unsubscribe wrapper, channel-family,
and ticker-stream renderers. The first websocket API request renderer slice
emits market-data request methods including `fetch_order_book_ws/3`,
`fetch_ohlcv_ws/5`, `fetch_ticker_ws/2`, and `fetch_trades_ws/4`, plus signed
helper query methods including `fetch_balance_ws/1`, `fetch_positions_ws/2`,
`fetch_order_ws/3`, and `fetch_open_orders_ws/4`. The shared websocket API
request renderer now delegates request setup, payload, request mode, and
response rendering to `WsApiRequestNodeIrSpec`; its dispatcher now delegates to
dedicated signed-waiter, unsigned request, and signed-helper request renderers,
covering signed waiter, unsigned connection, unsigned helper, and signed helper
request modes. The
websocket API wrapper renderer covers `fetch_position_ws/2` and
`fetch_closed_orders_ws/4`.
Validation-aware websocket API request rendering covers `fetch_orders_ws/4`
and `fetch_my_trades_ws/4`, plus cancel mutations `cancel_order_ws/3` and
`cancel_all_orders_ws/2`; these methods render guarded validation branches plus
typed signed-request payload/response nodes through the validated request IR.
Signed order mutation rendering now covers
`create_order_ws/6` and `edit_order_ws/7`, including test/SOR/algo-order method
selection, spot cancel-replace, and contract modify-order payload selection;
their signed request/response path is centralized through a generated order
mutation request helper, and the generated order mutation methods render their
success path through typed payload-wrapper, pre-request, and signed-request IR
nodes rather than an opaque method-body line block. Edit-order spot/swap
payload selection is centralized through generated order payload helpers. Order payload helper
generation is driven by a composed payload-helper IR spec with typed child
specs for order-id payloads, create-order payload delegation, order request
market stubs, spot cancel-replace payloads, contract modify payloads, base
order payloads, option/camel-case params, and conditional-order detection. The
manifest also records AST-derived websocket API sub-IR signals and structured
nodes such as
`order-payload:create`, `order-payload:spot-cancel-replace`,
`order-payload:contract-modify`, `order-payload:cancel`,
`request:signed-waiter`, `request:unsigned-connection`,
`request:unsigned-helper`, `request:signed-helper`,
`validation:guarded-request`, `request:signed-validated`,
`order-mutation:success`, `request:signed-order-mutation`,
`payload-shape:book-depth`, `payload-shape:ohlcv-query`,
`payload-shape:my-trades-query`, `payload-field:symbol`,
`payload-field:order-id`, `payload-field:return-rate-limits`,
`response-filter:positions`, and `response-filter:symbol-since-limit`. Stream,
unsubscribe, helper, cache, and orderbook methods also record structured nodes
such as `watch:single`, `watch:multiple`, `channel-family:trades`,
`subscription:public-single`, `request:public-watch`,
`subscription:public-multiple`, `request:public-watch-any`,
`auth:user-data-stream`, `cache:snapshot-preload`, `unsubscribe:orderbook`,
`snapshot:balance`, `snapshot:positions`, `request:snapshot-fetch`,
`payload:unsubscribe`, `request:unsubscribe-waiter`, and
`orderbook:snapshot-delta-merge`. Auth, parser, and handler methods record
nodes such as `auth:signature`, `auth:listen-token`, `parser:trade`,
`auth-signing:hmac-sha256`, `auth-signing:rsa-sha256`,
`auth-signing:eddsa-ed25519`, `credential:api-key`,
`payload:sorted-rawencode`,
`metadata-field:capabilities`, `metadata-field:urls`,
`metadata-field:timeframes`, `request-counter:per-url`,
`runtime-state:options-request-id`, `request-id:increment`,
`auth-subscription:request`, `response:subscription-id`,
`auth-route:spot-signature`, `auth-route:listen-key`,
`auth-route:listen-token`, `auth-route:unsupported`,
`listen-token-lifecycle:subscribe-ack`,
`listen-token-lifecycle:renewal`,
`listen-key-keepalive:supported`, `listen-key-keepalive:unsupported`,
`request:rest-keepalive`,
`routing:public-websocket-url`, `routing:private-websocket-url`,
`routing:stream-path`, `routing-dimension:market-type`,
`routing-dimension:listen-key`,
`parser-branch:public-private`, `parser-branch:ticker-family`,
`parser-wrapper:safe_trade`, `parser-wrapper:safe_ticker`,
`parser-wrapper:safe_order`, `parser-wrapper:safe_liquidation`,
`parser-field:liquidation-order`, `parser-field:contracts`,
`parser-field:side`, `parser-field:hedged`, `handler:ws-api-response`,
`response:ws-api-success`, `error:status-error`, `request:request-id`,
`private-event:order`, `cache-engine:order-cache`,
`cache-engine:array-cache`, `cache-engine:balance-position-cache`,
`cache-class:array-cache`, `cache-class:order-cache`,
`cache-class:position-cache`, `cache-class:orderbook-cache`,
`cache-semantic:bounded-newest-first`, `cache-semantic:symbol-id-index`,
`cache-semantic:order-trade-merge`, `cache-semantic:balance-delta`, and
`dispatch:request-channel-private-error`,
`dispatch-branch:request-id`, `dispatch-branch:channel`,
`dispatch-branch:private-event`.
Auth/listenKey IR rendering covers spot websocket API signature subscription,
margin listenToken subscription, `authenticate/1` routing, renewal, and futures
listenKey keepalive; the signature and listenToken subscription methods render
their websocket API auth request and subscription-id response handling through a
typed auth subscription request node. `authenticate/1` routes through typed
auth router branch specs for spot signature, futures listenKey, margin
listenToken, and unsupported branches. `keep_alive_listen_key/3` renders its
supported futures endpoint fetch and unsupported branch through a typed
listenKey keepalive request node. Shared private watch IR rendering covers private balance,
orders, positions, my trades, and my liquidation stream methods, with private
payload fetches delegated to a typed private watch payload/request node; the single
symbol private liquidation wrapper uses the same single-from-multiple wrapper
IR as the public mark-price wrapper. Structured
parser IR rendering now covers the field-mapping parser shape used by
`parse_ws_liquidation/2`, `parse_ws_order/2`, and `parse_ws_position/1`;
conditional parser IR rendering covers the branch shapes used by
`parse_ws_ticker/3` and `parse_ws_trade/2`.

## Covered Public Websocket Methods

Generated Binance Pro methods currently covered:

- `watch_ticker/2`
- `watch_tickers/2`
- `watch_trades/4`
- `watch_trades_for_symbols/4`
- `watch_order_book/3`
- `watch_order_book_for_symbols/3`
- `watch_ohlcv/5`
- `watch_ohlcv_for_symbols/4`
- `watch_liquidations/4`
- `watch_liquidations_for_symbols/4`
- `watch_mark_price/2`
- `watch_mark_prices/2`
- `watch_bids_asks/2`
- `watch_multi_ticker_helper/5`
- `unwatch_ticker/2`
- `unwatch_tickers/2`
- `unwatch_mark_price/2`
- `unwatch_mark_prices/2`
- `unwatch_order_book/2`
- `unwatch_order_book_for_symbols/2`
- `unwatch_trades/2`
- `unwatch_trades_for_symbols/2`
- `unwatch_ohlcv/3`
- `unwatch_ohlcv_for_symbols/2`
- `fetch_ohlcv_ws/5`
- `fetch_ticker_ws/2`
- `fetch_order_book_ws/3`
- `fetch_balance_ws/1`
- `fetch_position_ws/2`
- `fetch_positions_ws/2`
- `cancel_order_ws/3`
- `cancel_all_orders_ws/2`
- `fetch_order_ws/3`
- `fetch_orders_ws/4`
- `fetch_closed_orders_ws/4`
- `fetch_open_orders_ws/4`
- `fetch_my_trades_ws/4`
- `fetch_trades_ws/4`
- `create_order_ws/6`
- `edit_order_ws/7`
- `sign_params/2`
- `ensure_user_data_stream_ws_subscribe_signature/2`
- `ensure_user_data_stream_ws_subscribe_listen_token/2`
- `renew_listen_token/1`
- `authenticate/1`
- `get_private_ws_url/3`
- `keep_alive_listen_key/3`
- `watch_my_liquidations/4`
- `watch_my_liquidations_for_symbols/4`
- `watch_balance/1`
- `watch_orders/4`
- `watch_positions/4`
- `watch_my_trades/4`
- `parse_ws_order/2`
- `parse_ws_position/1`

Current TypeScript source-method trace coverage:

- `107/107` Binance Pro methods are currently bound to generated Elixir output
  or explicit runtime-owned Pro behavior.
- Current manifest split is `66` generated methods and `41` runtime-owned
  methods.
- `publicStream` coverage is `14/14`.
- `unsubscribe` coverage is `10/10`.
- `auth` coverage is `8/8`; covered methods include the HMAC branch of
  `signParams` and the spot `userDataStream.subscribe.signature`
  authentication path, futures listenKey creation/keepalive helpers, and margin
  listenToken subscription/renewal. The subscription, routing, renewal, and
  keepalive methods are emitted through shared auth/listenKey IR renderers.
- `privateStream` coverage is `6/6`; covered methods include private balance,
  orders, positions, my trades, and my liquidations streams. These methods are
  emitted through shared private watch IR renderers.
- `parser` coverage is `5/5`; `parse_ws_liquidation/2` and
  `parse_ws_position/1` are emitted through structured parser IR, while
  `parse_ws_ticker/3` and `parse_ws_trade/2` are emitted through conditional
  parser IR. `parse_ws_order/2` is also emitted through structured parser IR.
- `handler` coverage is `26/26`; AST call signals classify handler contracts
  into public stream handlers, private stream handlers, unsubscribe
  acknowledgements, order book cache handlers, websocket API response handlers,
  and a small top-level event dispatch set. These runtime-owned contracts are
  recorded in the manifest with owner functions such as `dispatch/3`,
  `dispatch_private/2`, `handle_orderbook_delta/3`,
  `resolve_or_reject_request_message/3`, and `handle_frame/2`.
- `wsApi` coverage is `24/24`; covered methods now include public market-data
  requests, signed balance/position/order/trade query requests, signed
  cancel-order requests, and signed create/edit order requests.
- `helper` coverage is `14/14`; metadata/url/category/account-type/market-type
  helpers are generated, while snapshot/cache helpers are explicit
  runtime-owned behavior with manifest runtime contracts.
- Coverage can be inspected with `npm run inspectElixirPro`.
- `npm run inspectElixirPro` reads TypeScript AST-derived `ProMethodModel`
  records and groups coverage by method category.
- `elixir/lib/ccxt/pro_manifest.json` records `irSummary` for every source
  method; tests assert that all 107 methods have non-empty AST-derived IR
  signals, non-empty structured IR nodes, structured source trace
  (`source.path/startLine/endLine`), MethodModel metadata, and no unsupported
  entries.
- The same manifest records `methodCoverageMatrix` for every source method,
  including source span, category, generated/runtime owner, renderer or runtime
  owner function, unit evidence, live evidence, risk class, and skip reason.
  Tests assert the matrix covers `107/107` methods and stays synchronized with
  the source-method manifest.
- The same manifest records a `renderPlan` for every generated method; tests
  assert that the render plan and generated method set are identical. The
  manifest also records source-derived `renderSections.main/parser` for module
  emission; tests assert those sections match the non-inline render plan.
- The generator assert also checks that the method blocks emitted by
  `renderBinancePro` are resolved from `renderPlan`, rather than from a separate
  hand-maintained required-method or render-order source; non-inline render
  plan entries that are not consumed by a module render section fail the assert
  path, and renderer registry drift is rejected in both directions.
- Runtime-owned source methods also record `runtimeContract`; generator assert
  and ExUnit both require complete contract metadata for those methods.
- `Ccxt.ProLifecycleCoverage` groups `runtimeSemanticCoverage` into required
  lifecycle feature rows, owner modules/functions, source methods, contract
  kinds, and signals. `doc/ccxt-pro-lifecycle-coverage.md` is the rendered
  matrix for review.
- This number is method-trace coverage, not runtime behavior quality; several
  traced handler/helper methods are represented by shared runtime behavior.
- `requestId` remains runtime-owned because the TypeScript method mutates
  instance-local `options.requestId[url]` counters. The current Elixir Pro
  module is stateless at the exchange-function boundary, so request id ownership
  stays with websocket request/waiter runtime behavior until exchange instance
  state is modeled explicitly. The Pro IR still records the counter shape as
  `request-counter:per-url`, `runtime-state:options-request-id`,
  `request-id:increment`, and `waiter:message-hash`.

The generator tracks these TypeScript source methods:

- `watchTicker`
- `watchLiquidations`
- `watchLiquidationsForSymbols`
- `handleLiquidation`
- `parseWsLiquidation`
- `watchTickers`
- `watchMultiTickerHelper`
- `parseWsTicker`
- `watchOrderBook`
- `watchOrderBookForSymbols`
- `fetchOrderBookSnapshot`
- `handleDelta`
- `handleDeltas`
- `handleOrderBookMessage`
- `handleOrderBook`
- `handleOrderBookSubscription`
- `unWatchOrderBookForSymbols`
- `unWatchOrderBook`
- `unWatchTradesForSymbols`
- `unWatchTrades`
- `watchTrades`
- `watchTradesForSymbols`
- `parseWsTrade`
- `handleTrade`
- `watchOHLCV`
- `watchOHLCVForSymbols`
- `unWatchOHLCVForSymbols`
- `unWatchOHLCV`
- `handleOHLCV`
- `fetchOHLCVWs`
- `handleFetchOHLCV`
- `fetchTickerWs`
- `handleTickerWs`
- `fetchOrderBookWs`
- `handleFetchOrderBook`
- `fetchBalanceWs`
- `handleBalanceWs`
- `handleAccountStatusWs`
- `fetchPositionWs`
- `fetchPositionsWs`
- `handlePositionsWs`
- `cancelOrderWs`
- `cancelAllOrdersWs`
- `fetchOrderWs`
- `fetchOrdersWs`
- `fetchClosedOrdersWs`
- `fetchOpenOrdersWs`
- `handleOrderWs`
- `handleOrdersWs`
- `fetchMyTradesWs`
- `fetchTradesWs`
- `handleTradesWs`
- `createOrderWs`
- `editOrderWs`
- `handleEditOrderWs`
- `signParams`
- `getPrivateWsUrl`
- `ensureUserDataStreamWsSubscribeSignature`
- `handleUserDataStreamSubscribe`
- `authenticate`
- `keepAliveListenKey`
- `watchMarkPrice`
- `watchMarkPrices`
- `unWatchTickers`
- `unWatchMarkPrices`
- `unWatchMarkPrice`
- `unWatchTicker`
- `watchBidsAsks`
- `handleBidsAsks`
- `handleTickers`
- `handleMarkPrices`
- `handleTickersAndBidsAsks`
- `getWsUrl`
- `stream`
- `watchMyLiquidations`
- `watchMyLiquidationsForSymbols`
- `watchBalance`
- `watchOrders`
- `watchPositions`
- `watchMyTrades`
- `parseWsOrder`
- `parseWsPosition`
- `handleMyLiquidation`
- `handleBalance`
- `handleOrderUpdate`
- `handlePositions`
- `handleMyTrade`
- `handleOrder`
- `handleAcountUpdate`

## Runtime Semantics Implemented

Current websocket dispatch supports:

- `24hrMiniTicker`
- `24hrTicker`
- `trade`
- `aggTrade`
- `kline`
- `markPrice_kline`
- `indexPrice_kline`
- `depthUpdate`
- `markPriceUpdate`
- `forceOrder`
- book ticker payloads without an `e` event field
- Binance `UNSUBSCRIBE` acknowledgement frames
- Binance private user-data events: `executionReport`, `outboundAccountPosition`,
  `balanceUpdate`, `ACCOUNT_UPDATE`, and `ORDER_TRADE_UPDATE`
- Binance websocket API request frames with `status != 200` or an `error` body
  are rejected by request id.
- Invalid websocket JSON rejects all pending stream/request waiters, so callers
  do not remain blocked after a malformed frame. Request and unsubscribe waiters
  unwrap their internal waiter tags and report errors with the original caller
  ref, matching `request/3` and `unwatch_any/3` receive contracts.
- Binance `UNSUBSCRIBE` error acknowledgements reject the unwatch caller instead
  of reporting a successful unsubscribe.
- Binance `eventStreamTerminated` user-data events reject matching private
  stream waiters instead of leaving callers blocked.

Current parser coverage:

- ticker: mini ticker, full 24h ticker, book ticker, mark price update
- liquidation: futures `forceOrder` payloads
- tickers: individual ticker update maps and all-market ticker arrays
- trade: public trade, aggregate trade, private execution trade payload
- order book: REST snapshot plus websocket depth delta merge
- OHLCV: kline payload to `[timestamp, open, high, low, close, volume]`
- private order: spot `executionReport` and futures `ORDER_TRADE_UPDATE`
- private position: futures account update position entries

Current cache behavior is intentionally minimal:

- `watch_ticker/2` returns the latest parsed ticker update.
- `watch_tickers/2` returns a one-update `%{symbol => ticker}` map for symbol subscriptions and parses all-market ticker arrays.
- `watch_trades/4` and `watch_trades_for_symbols/4` subscribe all requested
  trade channels through cached `watch_any`; raw trade payloads are retained in
  `Ccxt.Pro.ArrayCache` and parsed/filtered by `since` and `limit`.
- `watch_ohlcv/5` and `watch_ohlcv_for_symbols/4` subscribe requested kline
  channels through cached `watch_any`; raw kline payloads are retained in
  `Ccxt.Pro.ArrayCache` and parsed/filtered by `since` and `limit`.
- `watch_tickers/2`, `watch_mark_price/2`, `watch_mark_prices/2`, and
  `watch_bids_asks/2` use cached ticker-family `watch_any`; raw ticker payloads
  are retained in `Ccxt.Pro.ArrayCache` and parsed into symbol-keyed ticker
  maps.
- Public ticker, trade, and OHLCV cache retention is covered by offline
  connection tests that assert per-message-hash `ArrayCache` storage and
  newest-first bounded semantics.
- `watch_liquidations/4` and `watch_liquidations_for_symbols/4` return one
  liquidation update when Binance emits a matching `forceOrder` event; the
  `ForSymbols` path subscribes all requested futures `forceOrder` channels
  through `watch_any`.
- `watch_order_book_for_symbols/3` registers all requested order book channels,
  loads snapshots per symbol, and returns the first normalized order book that
  reaches a valid snapshot/delta state. Snapshot-time websocket delta buffers
  use `Ccxt.Pro.ArrayCache` with optional per-subscription `:cache_limit`.
- `watch_balance/1`, `watch_orders/4`, `watch_positions/4`,
  `watch_my_trades/4`, and `watch_my_liquidations/4` authenticate the user-data
  stream, reuse the supervised websocket connection, and wait on CCXT message
  hashes. Balance, positions, orders, my trades, and my liquidations are merged
  into runtime private cache structures before watcher resolution. Private
  order/trade/liquidation list retention now uses `Ccxt.Pro.ArrayCache` with
  bounded newest-first append semantics and optional per-subscription
  `:cache_limit`.
- Private event-driven watchers are not part of the always-on live smoke
  because they depend on future account events that may not occur during a
  deterministic timeout. `watch_balance/1`, `watch_orders/4`,
  `watch_positions/4`, `watch_my_trades/4`, `watch_my_liquidations/4`, and
  `watch_my_liquidations_for_symbols/4` are covered by deterministic
  fake-connection tests, cache payload tests, snapshot preload tests, and
  message-hash routing tests instead. The Pro unit suite asserts these watcher
  calls stay out of `test/ccxt_pro_binance_live_test.exs`.
- `fetch_ohlcv_ws/5` sends a Binance websocket API `klines` request and parses
  the response `result` into OHLCV arrays.
- `fetch_ticker_ws/2` and `fetch_order_book_ws/3` send signed Binance futures
  websocket API requests using generated `sign_params/2`.
- `fetch_balance_ws/1`, `fetch_position_ws/2`, `fetch_positions_ws/2`,
  `fetch_order_ws/3`, `fetch_orders_ws/4`, `fetch_open_orders_ws/4`,
  `fetch_closed_orders_ws/4`, and `fetch_my_trades_ws/4` send signed websocket
  API read requests and parse the response through shared ws-api helpers.
- `cancel_order_ws/3` and `cancel_all_orders_ws/2` are generated and covered by
  offline signed request/response tests. They are excluded from the always-on
  live smoke because they mutate order state and need a user-confirmed target
  order or a freshly created test order.
- `fetch_trades_ws/4` sends the unsigned websocket API public historical trades
  request and is included in the live smoke.
- `create_order_ws/6` and `edit_order_ws/7` are generated and covered by offline
  signed request/response tests. Real production order mutation is gated by
  `CCXT_PRO_ENABLE_PROD_ORDER_LIVE=true`; when enabled, the live test builds a
  deliberately non-marketable spot limit buy from the current production order
  book, submits it, and immediately cancels it. Without that explicit flag the
  test reports that no production order was sent.
- Prod-only funding or account-state mutations that are not order placement
  remain out of live execution. Withdraw, transfer, borrow, repay, convert, and
  gift-code flows should be covered only by inventory lists, dry-run/signature
  rendering, or request-shape tests unless they receive a separate manual
  confirmation for the exact operation.
- The Binance Pro manifest does not include withdraw, transfer, borrow, repay,
  convert, or gift-code methods. Its current mutation surface is limited to
  `create_order_ws/6`, `edit_order_ws/7`, `cancel_order_ws/3`, and
  `cancel_all_orders_ws/2`; an offline manifest test asserts this risk
  inventory.
- Non-Pro Binance production-only mutation classes are covered by
  `test/ccxt_binance_prod_only_dry_run_test.exs` with fake credentials and no
  HTTP execution. That dry-run suite renders signed production request shapes
  and asserts demo rejection for withdraw, transfer, futures transfer, gift-code
  create/redeem, convert accept/transfer, margin borrow/repay, portfolio margin
  borrow/repay, simple earn redeem, option order, portfolio margin order, and
  portfolio margin leverage.
- `sign_params/2` implements the TypeScript `signParams` signing branches:
  HMAC SHA256 for ordinary API secrets, RSA SHA256 for long PEM private keys,
  and Ed25519 for short Binance EdDSA PEM private keys. The manifest records
  credential requirements, timestamp/recvWindow payload extension, sorted
  rawencode canonicalization, and all three signing algorithms.
- `ensure_user_data_stream_ws_subscribe_signature/2` sends the signed spot
  websocket API `userDataStream.subscribe.signature` request and returns the
  subscription id.
- `ensure_user_data_stream_ws_subscribe_listen_token/2` creates a margin
  listenToken through `sapiPostUserListenToken`, subscribes it through websocket
  API `userDataStream.subscribe.listenToken`, and returns subscription metadata.
- `renew_listen_token/1` reuses the margin listenToken subscription path with
  explicit parameters, matching the TypeScript renewal wrapper at the current
  stateless Elixir boundary.
- `authenticate/1` currently covers the TypeScript spot signature branch,
  margin listenToken branch, and listenKey branches for future, delivery, and
  portfolio-margin streams.
- `get_private_ws_url/3` generates Binance private websocket URLs for
  listenKey-backed future, delivery, and portfolio-margin streams. Public and
  private websocket URL helpers now record typed routing nodes with market type,
  category, environment, stream hash, and listenKey dimensions in the manifest.
- `keep_alive_listen_key/3` implements listenKey keepalive requests for future,
  delivery, and portfolio-margin streams.
- All generated `unwatch_*` public methods send Binance `UNSUBSCRIBE` and wait
  for the request acknowledgement.
- `watch_order_book/3` keeps local order book state in the websocket connection process.

The cache-class parity milestone now has runtime owners for the Binance Pro
cache semantics used so far. `Ccxt.Pro.IndexedArrayCache` owns the reusable
`symbol -> key` cache behavior behind `ArrayCacheBySymbolById` and
`ArrayCacheBySymbolBySide` style caches. `Ccxt.Pro.TimestampArrayCache` owns the
`ArrayCacheByTimestamp` style timestamp replacement cache used by OHLCV streams.
`Ccxt.Pro.PositionCache` wires symbol+side indexing into Binance position
streams, and `Ccxt.Pro.CacheUpdates` owns CCXT Pro `getLimit`-style
`newUpdates` counters. `Ccxt.Pro.Connection` wires those counters into public
cached streams and private list/order streams when callers pass
`newUpdates: true` or `new_updates: true`.

Order book state currently follows the Binance/CCXT Pro sequence rules:

- Deltas received before the REST snapshot are buffered.
- The REST snapshot initializes bids, asks, and nonce from `lastUpdateId`.
- Spot deltas with `u <= nonce` are dropped.
- The first valid spot delta must satisfy `U - 1 <= nonce` and `u - 1 >= nonce`.
- Later spot deltas must satisfy `U - 1 == previous u`.
- Futures deltas use `U/u/pu` continuity; cached futures deltas can bridge the
  REST snapshot and later deltas must continue from the previous update id.
- Sequence gaps reject the waiter with a checksum-style error, matching the TypeScript `watchOrderBook` behavior where checksum mode turns sequence inconsistency into `ChecksumError`.
- When checksum mode is disabled, the same sequence inconsistency is surfaced
  as `:sequence_gap` for testable non-checksum order book subscriptions.

## Verification

Standard gate:

```sh
npm run checkElixir
```

Pro generation stability:

```sh
npm run assertElixirPro
```

This gate fails if any TypeScript method is unsupported, if any runtime-owned
method lacks a complete runtime contract, or if generated files drift from the
AST-derived output.

Public Binance Pro live smoke:

```sh
cd elixir
mix test --include live --exclude prod_order_live \
  test/ccxt_pro_binance_live_test.exs --timeout 180000
```

The public stream live tests use Binance websocket streams and do not require
API keys. Event-driven liquidation streams are covered by a stable live
`forceOrder` subscribe/unsubscribe acknowledgement test, while actual forced
order payload parsing stays in deterministic parser and fake-connection tests
because Binance only emits payloads when a real forced-order event occurs.
Signed websocket API live tests run when `BINANCE_PROD_API_KEY` /
`BINANCE_PROD_API_SECRET` are available. The Pro live test credential check
intentionally does not fall back to generic `BINANCE_API_KEY` /
`BINANCE_API_SECRET`, because those may point at demo credentials and mask
production permission failures.
The signed read-only websocket API smoke covers `fetch_balance_ws/1`,
`fetch_position_ws/2`, `fetch_positions_ws/2`, `fetch_open_orders_ws/4`,
`fetch_orders_ws/4`, `fetch_closed_orders_ws/4`, and `fetch_my_trades_ws/4`.
These calls inspect account/order/trade state and do not create, edit, cancel,
transfer, borrow, repay, convert, or withdraw.
The always-on live smoke does not wait for a real liquidation payload; it
verifies the public `forceOrder` subscription lifecycle against Binance and
keeps payload shape assertions deterministic.

Demo private read-only smoke can be run on its own:

```sh
cd elixir
mix test --exclude live --include demo_private_live \
  test/ccxt_pro_binance_live_test.exs --timeout 180000
```

This verifies the demo read-only websocket API matrix for
`fetch_balance_ws/1`, `fetch_positions_ws/2`, `fetch_open_orders_ws/4`,
`fetch_orders_ws/4`, `fetch_closed_orders_ws/4`, and `fetch_my_trades_ws/4`.
The test accepts successful demo responses or explicit Binance demo
unsupported/credential/permission responses. It is read-only and does not place
orders or move funds.

Production order mutation smoke is deliberately opt-in:

```sh
cd elixir
CCXT_PRO_ENABLE_PROD_ORDER_LIVE=true \
BINANCE_PROD_ORDER_AMOUNT=0.0002 \
mix test --exclude live --include prod_order_live \
  test/ccxt_pro_binance_live_test.exs --timeout 120000
```

That test uses production credentials, places a non-marketable spot limit buy,
and cancels the created order immediately. It is meant only for the prod-only
order-placement class; non-order funding mutations stay dry-run/request-shape
only.

Latest verified run, 2026-06-11:

- `npm run inspectElixirPro`: `107/107` Binance Pro source methods covered,
  split as `66` generated and `41` runtime-owned.
- `npm run assertElixirPro`: generated output stable.
- `mix test test/ccxt_pro_binance_test.exs test/ccxt_pro_binance_soak_test.exs`:
  `114 tests, 0 failures (4 excluded)`; this includes HMAC SHA256, RSA SHA256,
  Ed25519 `signParams` branch coverage, Pro runtime connection lifecycle helper
  coverage, and proof that the `:soak` gate stays opt-in.
- `mix compile --warnings-as-errors`: passed.
- `npm run checkElixir`: `509 tests, 0 failures (60 excluded)`.
- `CCXT_PRO_SOAK_SECONDS=5 CCXT_PRO_SOAK_MIN_UPDATES=1 mix test --include soak test/ccxt_pro_binance_soak_test.exs --timeout 120000`:
  `3 tests, 0 failures`. This short public-stream run validated order-book
  snapshots before/after a ticker stream, repeated ticker updates, connection
  introspection, ticker unwatch-on-halt, explicit close/reopen, supervised
  restart after abnormal process exit, and websocket cleanup.
- `CCXT_PRO_LONG_SOAK_SECONDS=5 CCXT_PRO_LONG_SOAK_MIN_UPDATES=1 npm run testElixirProLongSoak`:
  `1 test, 0 failures`. This short long-soak sanity run records ticker update
  count, maximum update gap, connection starts, disconnects, terminations,
  telemetry message count, and explicit websocket cleanup.
- `CCXT_PRO_WORKER_SECONDS=5 mix run examples/pro_ticker_worker.exs`: received
  `3` public ticker updates for `BTC/USDT`, returned a GenServer snapshot with
  `last_error: nil`, and stopped the worker with explicit websocket cleanup.
- `BINANCE_PRO_ENV=prod CCXT_PRO_ORDER_EVENT_WORKER_SECONDS=90 mix run
  examples/pro_order_event_worker.exs` while
  `CCXT_PRO_ENABLE_PROD_ORDER_LIVE=true BINANCE_PROD_ORDER_SYMBOL=BTC/USDT
  BINANCE_PROD_ORDER_AMOUNT=0.0002 mix run examples/pro_safe_order_lifecycle.exs`
  was running: the private worker received `3` order-event updates for a
  non-marketable production spot order lifecycle covering create/open, cancel
  of the original order, and cancel of the edited order. The final worker
  snapshot retained recent orders and the retry path surfaced Binance `-2035
  User Data Stream subscription already active`, which is an exchange
  lifecycle condition after an active user-data stream subscription.
- `npm run testElixirConsumer`: compiled `smoke/consumer_app` as an external
  path dependency consumer using `{:ccxt, path: "../.."}` and ran `1` live
  public Binance Pro test with `0` failures. This verifies
  `Ccxt.Pro.binance/1`, `watch_ticker/2`, `stream_ticker/2`, and
  `Ccxt.Pro.close_connection/1` outside the package project.
- `npm run buildElixirPackage`: built `ccxt-0.1.0-binance-pro-preview.tar`
  with package metadata and the intended runtime/docs payload. A follow-up
  `mix hex.build --unpack` succeeded and confirmed the package includes
  `lib`, `priv`, `doc`, `mix.exs`, and `README.md`, including
  `doc/real-project-integration.md` and `doc/release-checklist.md`, while
  excluding `test`, `examples`, and `smoke`.
- `npm run testElixirPackageConsumer`: built and unpacked the local Hex package
  into a temporary directory, copied `smoke/consumer_app` into a clean temporary
  consumer project, set `CCXT_CONSUMER_CCXT_PATH` to the unpacked package, and
  ran the same public Binance Pro watch/stream/close smoke successfully from the
  packaged payload.
- `npm run releaseElixirPreviewCheck`: release-preview aggregation gate that
  runs `checkElixir`, `docsElixir`, and `testElixirPackageConsumer` in order.
  Latest local run completed all three phases, including docs generation and
  packaged consumer smoke with `1` test and `0` failures.
- `npm run testElixirProLongSoak`: `1 test, 0 failures` over `901.0` seconds.
  This default production public ticker soak used `BTC/USDT` on `prod` for
  `900` seconds and recorded `883` ticker updates, `1767` websocket messages,
  `2992` ms maximum update gap, `2353` ms first update latency, `1` connection
  start, `0` disconnects, `0` terminations, and explicit websocket cleanup.
- `mix test --include live --exclude prod_order_live test/ccxt_pro_binance_live_test.exs --timeout 180000`:
  `34 tests, 0 failures`. This run covered the public single-symbol and
  multi-symbol ticker, trade, OHLCV, order book, mark-price, bids/asks, and
  unsubscribe smoke paths, plus live `forceOrder` subscribe/unsubscribe ACK
  coverage for the liquidation stream. It also covered signed read-only
  balance, position, open-orders, all-orders, closed-orders, and my-trades
  websocket API calls when matching credentials were available, or recorded the
  expected Binance credential / permission reason.
- `CCXT_PRO_ENABLE_PROD_ORDER_LIVE=true BINANCE_PROD_ORDER_SYMBOL=BTC/USDT BINANCE_PROD_ORDER_AMOUNT=0.0002 mix test --only prod_order_live test/ccxt_pro_binance_live_test.exs --timeout 180000`:
  `3 tests, 0 failures (31 excluded)`. The gated production order smoke placed
  non-marketable spot limit orders and verified create, fetch, edit, cancel,
  and `stream_orders/4` execution-report event handling before cleanup.

## Remaining Public Scope

All TypeScript methods categorized as public websocket streams are now covered
in the current manifest.

## Remaining Private Scope

All TypeScript methods categorized as private websocket streams are now covered
in the current manifest. Balance, positions, orders, my trades, and my
liquidations now maintain lightweight runtime private cache state across
user-data events. Spot/margin `balanceUpdate` events add the websocket delta to
the existing cached free balance, matching the TypeScript
`Precise.stringAdd(previousValue, delta)` branch. Futures/delivery
`ACCOUNT_UPDATE` position entries inherit the websocket event timestamp so
`watch_positions/4` can return parsed positions with `timestamp` and
`datetime`, matching the TypeScript `handlePositions` timestamp assignment.
`Ccxt.Pro.PositionCache` owns the symbol+side-indexed position cache,
per-symbol payload view, and flattened values view used by
`watch_positions/4`.
Private order caches now merge trade execution metadata by order id: TRADE
events are routed through `Ccxt.Pro.OrderCache`, attach cached raw trades and
accumulated fee metadata to the cached order payload, and generated
`parse_ws_order/2` emits unified orders with `trades` and `fee`. The same
runtime component exposes a `hashmap/1` view keyed by `symbol -> order id`,
matching the lookup shape used by CCXT Pro order caches while preserving list
payload compatibility for current watchers.
Order/trade/liquidation list retention is now routed through
`Ccxt.Pro.ArrayCache`. Private stream subscriptions support per-message-hash
`:cache_limit`. Private stream subscriptions also support injectable
`:snapshot_payloads`, which preload balance/position caches and immediately
resolve matching private watch waiters. The generated private watch helpers now
wire the TypeScript `fetchBalanceSnapshot` and `fetchPositionsSnapshot` option
branches through `:snapshot_fetcher`, `:rest_fetcher`, or generated high-level
REST methods. Remaining cache work is deeper specialized cache variants beyond
the cache classes already represented in the manifest.

## Binance Pro Coverage Status

`ts/src/pro/binance.ts` source-method coverage is complete for Binance. The
remaining work listed here is incremental parity refinement and should continue
to be implemented as generic capabilities rather than one-off method templates:

### Stream Helper Completion Matrix

The Elixir-only `stream_*` helpers are generated convenience wrappers around
the generated `watch_*` methods. They repeatedly await the matching CCXT Pro
watch method and call the matching public `unwatch_*` method when the enumerable
halts where Binance exposes an unsubscribe channel. Private account streams use
the Binance user data stream lifecycle and runtime connection cleanup instead
of method-specific unsubscribe calls.

| Stream helper | Watch source | Unit coverage | Live coverage | Notes |
| --- | --- | --- | --- | --- |
| `stream_ticker/2,3` | `watch_ticker/2` | fake repeated updates | prod public live | Public stream; `Enum.take/2` receives repeated ticker updates. |
| `stream_trades/4,5` | `watch_trades/4` | fake repeated updates | prod public live | Public stream; no funds risk. |
| `stream_order_book/3,4` | `watch_order_book/3` | fake repeated snapshots | prod public live | Public stream; uses generated order-book watch path. |
| `stream_ohlcv/5,6` | `watch_ohlcv/5` | fake repeated candles | prod public live | Public stream; no funds risk. |
| `stream_balance/1,2` | `watch_balance/1` | fake private cache updates | prod private read-only smoke | Private stream; live can report credential, permission, or active subscription state. |
| `stream_positions/4,5` | `watch_positions/4` | fake private cache updates | prod private read-only smoke | Private futures stream; live depends on futures permission and account state. |
| `stream_orders/4,5` | `watch_orders/4` | fake repeated order events | gated prod live order event | Live proof uses `CCXT_PRO_ENABLE_PROD_ORDER_LIVE=true`, a non-marketable order, and immediate cancel. |
| `stream_my_trades/4,5` | `watch_my_trades/4` | fake repeated trade events | manual-fill only | Non-marketable orders intentionally do not emit trade fills; live proof requires an explicitly accepted real fill or remains fake/unit evidence. |

Current stream completion status is therefore complete for generated API
surface, repeated enumerable behavior, public live behavior, read-only private
smoke behavior, and gated order-event behavior. The only intentionally
ungated live gap is `stream_my_trades/4,5`, because proving it on Binance
requires a real executed trade rather than the non-marketable production order
policy.

- `unsubscribe`: the now-covered `un_watch_*` channel families are generated
  through shared unsubscribe wrapper, channel-family, and ticker-stream
  renderers. Direct channel-family unsubscribe methods delegate the
  `unwatch_channels/5` waiter request to a typed unsubscribe request node;
  ticker and mark-price unsubscribe wrappers delegate their
  `unwatch_ticker_stream/6` call through a typed ticker-stream unsubscribe
  node. The shared `unwatch_channels/5` helper is rendered through a typed
  payload/waiter node that owns the Binance `UNSUBSCRIBE` message, generated
  request id, public websocket URL, and `Ccxt.Pro.Connection.unwatch_any/3`
  call.
- `publicStream`: source-method coverage is complete. `watch_ticker/2`,
  `watch_tickers/2`, `watch_trades/4`, `watch_trades_for_symbols/4`,
  `watch_ohlcv/5`, `watch_ohlcv_for_symbols/4`, `watch_liquidations/4`,
  `watch_liquidations_for_symbols/4`, `watch_mark_price/2`,
  `watch_mark_prices/2`, `watch_bids_asks/2`, and
  `watch_multi_ticker_helper/5` are covered by shared public
  watch/watchMultiple/ticker-family renderers. The single and multiple public
  watch renderers now delegate channel/message-hash/subscription/watch-call
  rendering to typed public subscription nodes for `watch_ticker/2`,
  `watch_trades_for_symbols/4`, `watch_ohlcv_for_symbols/4`, and
  `watch_liquidations_for_symbols/4`; `watch_multi_ticker_helper/5` delegates
  ticker-family public watch-any subscription, cache registration, and parser
  market-type selection to a typed subscription node while preserving its
  unsubscribe branch. `watch_order_book/3` and
  `watch_order_book_for_symbols/3` are covered by orderbook watch renderers,
  including multi-symbol channel registration and per-symbol snapshot loading.
- `wsApi`: source-method coverage is complete. `fetch_order_book_ws/3`,
  `fetch_ohlcv_ws/5`, `fetch_ticker_ws/2`, and `fetch_trades_ws/4` are covered
  by the shared websocket API request renderer, whose typed request node covers
  signed waiter, unsigned connection, and unsigned helper request modes through
  dedicated request-mode renderers.
  `fetch_balance_ws/1`,
  `fetch_positions_ws/2`, `fetch_order_ws/3`, and `fetch_open_orders_ws/4` use
  the same renderer through signed helper request mode. `fetch_position_ws/2`
  and `fetch_closed_orders_ws/4` are covered by the websocket API wrapper
  renderer; wrapper nodes now record their delegated ws-api method, list result
  shape, and response filter semantics. `fetch_orders_ws/4` and
  `fetch_my_trades_ws/4` are covered by
  validation-aware websocket API request rendering, as are `cancel_order_ws/3`
  and `cancel_all_orders_ws/2`; their success path now uses typed payload,
  signed-request, and response IR nodes after guarded validation branches.
  `create_order_ws/6` and `edit_order_ws/7` are
  covered by signed order mutation IR rendering, with shared signed
  request/response handling for order mutation results, typed success/request
  IR nodes for the generated mutation method body, and shared edit-order payload
  routing for spot cancel-replace versus contract modify-order. The
  runtime-owned websocket API response handlers now record typed
  success-response, status-error, and request-id waiter nodes in the manifest.
  The
  payload-helper renderer now owns ignored option keys, Binance camel-case key
  mappings, and conditional-order option key detection. The manifest records
  AST-derived sub-IR signals and structured nodes for signed requests, request
  waiters, ordinary websocket API request modes, guarded validation requests,
  websocket API payload shapes/fields, create-order payload delegation, spot
  cancel-replace, contract modify-order, signed order mutation requests, cancel
  payloads, and response filters. Stream, unsubscribe, and
  cache helpers also expose structured nodes for watch mode, channel family,
  private auth, cache, snapshot preload, and orderbook snapshot/delta behavior.
  Auth/listenKey, parser, websocket API response, event handler, and top-level
  dispatch methods also expose structured nodes in the same manifest format.
  The payload helper renderer now consumes a
  composed typed IR spec and delegates to smaller reusable render nodes for
  order-id payloads, create-order request delegation, edit payload branch
  selection, base order payloads, option mapping, and conditional-order
  detection. Further improvements should keep moving renderer-specific internals
  into explicit IR specs where a reusable pattern is clear.
- `auth`: websocket signature subscription, listenToken subscription, renewal,
  authenticate routing, and listenKey keepalive are covered by shared
  auth/listenKey IR renderers. The signature and listenToken subscription
  methods now use typed request/result nodes for websocket API auth
  subscription and subscription-id response handling. `authenticate/1` now uses
  typed router branch specs rather than an opaque branch-line block, and
  `keep_alive_listen_key/3` uses a typed keepalive request spec for supported
  endpoint fetch versus unsupported types. The signer runtime now covers the
  HMAC SHA256, RSA SHA256, and Ed25519 branches used by Binance Pro
  `signParams`.
- `privateStream`: the now-covered private stream methods are generated through
  shared private watch IR renderers. Their `watch_private_payload/3` calls are
  now rendered through a typed private watch payload/request node. Balance,
  positions, orders, my trades, and
  my liquidations consume runtime private cache payloads. The single-symbol
  private liquidation method is also covered by the shared single-from-multiple
  wrapper IR. Spot/margin balance delta updates now accumulate against existing
  cached free balances, and `Ccxt.Pro.PositionCache` stores symbol-indexed
  position entries with event timestamps for parsed `watch_positions/4`
  results. Private order cache entries use
  `Ccxt.Pro.OrderCache` to merge TRADE execution metadata into the generated
  order parser output, including cached trades and accumulated fee metadata,
  and expose a symbol/order-id index view; private order handlers now record
  `cache-engine:order-cache`, `cache-class:order-cache`,
  `cache-semantic:symbol-id-index`, and
  `cache-semantic:order-trade-merge` in the manifest. Private trade/liquidation list retention uses the reusable
  `Ccxt.Pro.ArrayCache` runtime component with per-subscription `:cache_limit`.
  Their handlers record `cache-engine:array-cache`,
  `cache-class:array-cache`, and `cache-semantic:bounded-newest-first`, while
  account handlers record `cache-engine:balance-position-cache`,
  `cache-class:position-cache`, `cache-semantic:symbol-index`,
  `cache-semantic:side-index`, and `cache-semantic:balance-delta`.
  Private watch subscriptions can preload snapshot payloads into the runtime
  cache and resolve the matching waiter immediately, matching the cache warm-up
  role of `setBalanceCache`/`setPositionsCache`. Generated watch helpers now
  support the TypeScript `fetchBalanceSnapshot` and `fetchPositionsSnapshot`
  option branches with injectable snapshot fetchers for offline tests, optional
  REST fetchers, and generated high-level REST fallback; the snapshot preload
  helpers are now rendered through typed preload/fetch nodes that record
  balance and positions snapshot fetch semantics in the manifest. Runtime-owned
  cache helper methods also record snapshot targets, REST snapshot requests,
  cache-engine nodes, cache-class nodes, and cache semantic nodes for balance,
  positions, and orderbook snapshot caches.
  The next private-stream
  improvement is broader watcher wiring for the new indexed cache primitives
  and broader live coverage where credentials permit it.
- `parser`: simple field-mapping parsers are covered by structured parser IR,
  and ticker/trade are covered by conditional parser IR. Parser manifest nodes
  now record conditional branch names, safe wrapper usage, and order/trade cache
  merge semantics. The parser category has no remaining inline method template;
  future parser work should improve field
  parity with the TypeScript implementation where current Elixir behavior is
  intentionally narrower.
- `cache`: public ticker-family streams, public trades/OHLCV, and private
  order/trade/liquidation list retention now use `Ccxt.Pro.ArrayCache`. Order
  book pre-snapshot delta buffers use the same bounded append semantics.
  Balance cache updates handle both full account-position payloads and
  incremental `balanceUpdate` deltas, while position cache entries preserve
  event timestamps for parser output through `Ccxt.Pro.PositionCache`.
  `Ccxt.Pro.CacheUpdates` owns CCXT Pro `allNewUpdates`,
  `newUpdatesBySymbol`, nested id/side, and timestamp counter semantics without
  changing default list-shaped watcher payloads. Public cached watch streams and
  private list/order watchers can opt into that incremental window with
  `newUpdates: true` or `new_updates: true`.
  `Ccxt.Pro.OrderCache` merges private
  trade metadata by order id before generated parsing. Private snapshot preload
  is available through subscription-level `:snapshot_payloads`, and generated
  private watch helpers can fetch balance/position snapshots before
  subscription registration. Remaining cache work is any exchange-specific
  specialized cache variant beyond Binance, plus future private payload shapes
  that can safely expose incremental windows without changing non-list parser
  inputs.
  Current cache-owned TypeScript methods are explicitly represented by runtime
  contracts in `pro_manifest.json`.
- `Pro IR`: continue splitting large renderer internals into smaller Pro IR
  nodes as patterns stabilize, especially for deeper payload helper internals
  and specialized cache-class variants.