BinanceOrderBook
Maintains a live, local Binance SPOT order book for a configurable set
of currency pairs, per Binance's documented local-order-book-
maintenance algorithm (not linked here -- its status page returns
inconsistent HTTP status codes to automated checks; search Binance's
spot API docs for "How to manage a local order book correctly"):
bootstrap from a REST depth snapshot, then apply WS update-deltas
whose U (first update id) brackets the book's current
lastUpdateId, resyncing on any gap.
| Property | Value |
|---|---|
| Project | Virtufin.Worker.BinanceOrderBook |
| Base class | Virtufin.Worker.DevKit.WorkerBase<JsonNode, JsonObject> |
| Engine | application/x-dotnet-dll (Managed only) |
One instance, many pairs
One worker instance tracks a configurable set of pairs (default: every
pair it sees) rather than one instance per pair -- one-worker-per-pair
was judged operationally heavy for Binance spot's 50+ actively-traded
pairs. Internally, a ConcurrentDictionary<string, L2OrderBook<DecimalAmount,
DecimalAmount>> (from Virtufin.Base.OrderBooks) holds one book per pair,
with Level2OrderBookUpdater applying snapshots and diffs.
The worker subscribes to three flat, un-suffixed topics --
act.exchange.binance.orderbook.update, .snapshot, and the consolidated
act.exchange.binance.instrument.info -- rather than the pubsub-topics
spec's per-pair pattern for these input topics.
The pair for an update-delta event is parsed from Binance's own
combined-stream envelope (payload.stream); the pair for a snapshot
event is read from the CloudEvent's currencypair extension
attribute (Binance's raw REST response has no symbol field of its
own). Prices and sizes are normalized to the instrument's tick/lot
precision from instrument.info; a pair's book events are skipped until
that metadata arrives. The worker's output -- state key,
changed/gap topics -- stays genuinely per-pair, matching the spec.
Snapshot delivery
This worker doesn't call Binance's REST snapshot endpoint itself.
HttpGetWorker (workers/HttpGetWorker) is deployed once
per tracked pair (Binance's depth endpoint is single-symbol), and a
platform Trigger (Dapr Jobs, the same primitive the TUI's Triggers
page exposes) fires periodically to invoke it. Post-gap recovery
latency is therefore bounded by the polling interval (default @every
30s), not instant -- a deliberate tradeoff paired with dropping (not
buffering) updates received while the book isn't Live.
State
| Key | act\|hyp.<name>.exchange.<venue>.orderbook.<pair> |
| Value | { bids, asks, last_update_id, updated_at } |
Events published
| Action | Topic | Payload |
|---|---|---|
| Book updated | ...orderbook.changed.<pair> |
{ pair, last_update_id } |
| Gap detected | ...orderbook.changed.<pair>.error |
{ pair, expected, got } |
Configuration
| Key | Default | Purpose |
|---|---|---|
currencypairs |
(all pairs seen) | Comma-separated allowlist. |
venue |
binance |
Multi-venue future-proofing. |
world |
act |
act or hyp.<name>. |
stateservice |
workmanager |
SaveStateRequest.service. |
apihost |
localhost:5002 |
Gateway host:port. |
See the repo README for the full deploy sequence and known-gaps list.