Type/to search
Built-in Functions
Global
Log
Market
Trade
Account
Futures
Exchange
IO
Network
Storage
Threads
exchange.Go
EventLoop
ctx.subscribe
ctx.poll
Subscription
threading
Thread
ThreadLock
ThreadEvent
ThreadCondition
ThreadDict
Server
TA
Talib
OS
Structures
Built-in Variables and Constants
Conventions
Exchanges

Subscribes to WebSocket push streams (market data for a symbol, market-wide tickers, or an account's private streams) and returns a Subscription object. You can subscribe to a single symbol's ticker, best bid/ask, order book, trades and K-lines (candlesticks), to the tickers of an entire market, or to an entire account's order updates, positions and balances. When symbol is an array, one call subscribes to several symbols and returns an array of subscription objects. Retrieve events with ctx.poll, then handle each event according to its channel. A subscription belongs to the thread that created it. Both the main thread and threads created with threading.Thread() can subscribe.

ctx.subscribe(ex, options)

Examples

  • Subscribes to the ticker and the 20-level order book of the current trading pair through the exchange object. It also subscribes to the account's private stream and handles each event type separately according to channel.

    javascript
    function main() { // Pass the exchange object as the first argument; when a market data channel does not specify a symbol, the exchange object's current trading pair is used var tk = ctx.subscribe(exchange, {channel: "ticker"}) var book = ctx.subscribe(exchange, {channel: "depth", depth: 20}) // The private stream takes no symbol and covers the whole account: order updates, positions and balances are all pushed through this stream var acct = ctx.subscribe(exchange, {channel: "private"}) Log("subscribed:", JSON.stringify(tk), JSON.stringify(book), JSON.stringify(acct)) while (true) { var ev = ctx.poll([], 1000) // [] means all event streams subscribed by this thread; wait at most 1 second if (!ev) { continue } if (ev.type === "status") { // Check gateway status and initialization status first; their channel may be the same as that of data events if (ev.kind === 35 && ev.channel === "private") { Log("account snapshot ready:", ev.prefillStatus === 1, ev.message) } continue } if (ev.channel === "ticker") { Log("ticker", ev.symbol, "bid:", ev.bid, "ask:", ev.ask, "last:", ev.last) } else if (ev.channel === "depth") { var b = book.book(1) // Reads the same order book as ev.book(1) if (b.bids.length > 0 && b.asks.length > 0) { Log("bid1:", b.bids[0][0], "ask1:", b.asks[0][0]) } } else if (ev.channel === "orders") { Log("order", ev.id, ev.state, "filled:", ev.filledQty, "/", ev.qty) } } }
    python
    def main(): # Pass the exchange object as the first argument; when a market data channel does not specify a symbol, the exchange object's current trading pair is used tk = ctx.subscribe(exchange, {"channel": "ticker"}) book = ctx.subscribe(exchange, {"channel": "depth", "depth": 20}) # The private stream takes no symbol and covers the whole account: order updates, positions and balances are all pushed through this stream acct = ctx.subscribe(exchange, {"channel": "private"}) Log("subscribed:", tk.info(), book.info(), acct.info()) while True: ev = ctx.poll([], 1000) # [] means all event streams subscribed by this thread; wait at most 1 second if ev is None: continue if ev.type == "status": # Check gateway status and initialization status first if ev.kind == 35 and ev.channel == "private": Log("account snapshot ready:", ev.prefillStatus == 1, ev.message) continue if ev.channel == "ticker": Log("ticker", ev.symbol, "bid:", ev.bid, "ask:", ev.ask, "last:", ev.last) elif ev.channel == "depth": b = book.book(1) # Reads the same order book as ev.book(1) if b["bids"] and b["asks"]: Log("bid1:", b["bids"][0][0], "ask1:", b["asks"][0][0]) elif ev.channel == "orders": Log("order", ev.id, ev.state, "filled:", ev.filledQty, "/", ev.qty)
    rust
    use sdk::ctx::EventExt; fn main() { // The exchange object is the first argument: when a market data channel does not specify a symbol, its current trading pair is used let book = ctx::subscribe(exchange, ctx::SubOpts::depth(20)).unwrap(); let acct = ctx::subscribe(exchange, ctx::SubOpts::private()).unwrap(); loop { match ctx::poll(&[], Some(1000)) { ctx::Polled::Event(ev) => match ev.channel() { "depth" => { if let Ok(b) = book.book(1) { if !b.bids.is_empty() && !b.asks.is_empty() { Log!("bid1:", b.bids[0].0, "ask1:", b.asks[0].0); } } } "positions" | "balance" => { Log!("positions:", format!("{:?}", acct.positions())); } channel => Log!("event:", channel, "kind:", ev.kind), }, ctx::Polled::Stopped => break, _ => {} } } }
  • Subscribe to tick-by-tick trades for three symbols in one batch. The call returns an array of subscription objects, which can be passed straight to ctx.poll(). The example counts each symbol's trades over 1 minute and then unsubscribes from all of them at once.

    javascript
    function main() { // When symbol is an array, the subscription objects come back in the same order; if any symbol is invalid, none of the batch is subscribed var symbols = ["BTC_USDT", "ETH_USDT", "SOL_USDT"] var subs = ctx.subscribe(exchange, {channel: "trade", symbol: symbols}) subs.forEach(function(s) { Log(s.symbol, "stream:", s.id) }) var count = {} var t0 = Date.now() while (Date.now() - t0 < 60000) { var ev = ctx.poll(subs, 1000) // The subscription array can be passed straight to ctx.poll if (!ev) { continue } if (ev.kind === 35 && ev.prefillStatus === 2) { Log("subscription failed:", ev.symbol, ev.message) // Only this symbol's subscription failed } else if (ev.kind === 4) { count[ev.symbol] = (count[ev.symbol] || 0) + 1 } } Log("trades in 60 seconds:", count) subs.unsubscribe() // Unsubscribe from all subscriptions at once }
    python
    import time def main(): # When symbol is a list, a SubscriptionList comes back in the same order; if any symbol is invalid, none of the batch is subscribed symbols = ["BTC_USDT", "ETH_USDT", "SOL_USDT"] subs = ctx.subscribe(exchange, {"channel": "trade", "symbol": symbols}) for s in subs: Log(s.symbol, "stream:", s.id) count = {} t0 = time.time() while time.time() - t0 < 60: ev = ctx.poll(subs, 1000) # A SubscriptionList can be passed straight to ctx.poll if ev is None: continue if ev.kind == 35 and ev.prefillStatus == 2: Log("subscription failed:", ev.symbol, ev.message) elif ev.kind == 4: count[ev.symbol] = count.get(ev.symbol, 0) + 1 Log("trades in 60 seconds:", count) subs.unsubscribe() # Unsubscribe from all subscriptions at once
    rust
    use sdk::ctx::EventExt; use std::collections::HashMap; fn main() { let symbols = ["BTC_USDT", "ETH_USDT", "SOL_USDT"]; // Returns handles in the same order as symbols; if any symbol is invalid, the whole batch returns Err let subs = ctx::subscribe_many(exchange, ctx::SubOpts::trade(), &symbols).unwrap(); let ids: Vec<u32> = subs.iter().map(|s| s.id()).collect(); let mut count: HashMap<u32, u64> = HashMap::new(); let t0 = Unix(); while Unix() - t0 < 60 { match ctx::poll(&ids, Some(1000)) { ctx::Polled::Event(ev) if ev.kind == EV_MD_TRADE => { *count.entry(ev.stream()).or_insert(0) += 1; } ctx::Polled::Stopped => return, _ => {} } } for (s, symbol) in subs.iter().zip(symbols) { Log!(symbol, "trades in 60 seconds:", count.get(&s.id()).copied().unwrap_or(0)); s.unsubscribe().unwrap(); } }
  • Subscribing in a thread: the child thread subscribes to the order books of two symbols on its own, and these events are delivered only to that thread. The main thread subscribes to its own ticker, and the two do not interfere with each other.

    javascript
    function main() { var t = threading.Thread(function(symbols, ms) { // Subscribe in the thread; events from these two subscriptions are delivered only to this thread var subs = ctx.subscribe(0, {channel: "depth", symbol: symbols, depth: 5}) var n = 0 var t0 = Date.now() while (Date.now() - t0 < ms) { var ev = ctx.poll([], 1000) // Only receives events subscribed by this thread if (ev && ev.kind === 3) { var b = ev.book(1) if (b.bids.length > 0 && b.asks.length > 0) { Log("thread", ev.symbol, "bid1:", b.bids[0][0], "ask1:", b.asks[0][0]) } n++ } } subs.unsubscribe() // Can only unsubscribe in the thread that created the subscription return n }, ["ETH_USDT", "SOL_USDT"], 30000) var tk = ctx.subscribe(0, {channel: "ticker", symbol: "BTC_USDT"}) var t0 = Date.now() while (Date.now() - t0 < 30000) { var ev = ctx.poll([], 1000) // Cannot receive the depth events subscribed by the child thread if (ev && ev.kind === 1) { Log("main", ev.symbol, "last:", ev.last) } } tk.unsubscribe() Log("depth updates in the thread:", t.join().ret) }
    python
    import threading import time def depth_worker(symbols, seconds): # Subscribe in the thread; events from these two subscriptions are delivered only to this thread subs = ctx.subscribe(0, {"channel": "depth", "symbol": symbols, "depth": 5}) t0 = time.time() while time.time() - t0 < seconds: ev = ctx.poll([], 1000) # Only receives events subscribed by this thread if ev is not None and ev.kind == 3: b = ev.book(1) if b["bids"] and b["asks"]: Log("thread", ev.symbol, "bid1:", b["bids"][0][0], "ask1:", b["asks"][0][0]) subs.unsubscribe() # Also unsubscribed automatically when the thread exits def main(): t = threading.Thread(target=depth_worker, args=(["ETH_USDT", "SOL_USDT"], 30)) t.start() tk = ctx.subscribe(0, {"channel": "ticker", "symbol": "BTC_USDT"}) t0 = time.time() while time.time() - t0 < 30: ev = ctx.poll([], 1000) # Cannot receive the depth events subscribed by the child thread if ev is not None and ev.kind == 1: Log("main", ev.symbol, "last:", ev.last) tk.unsubscribe() t.join()
    rust
    fn main() { let worker = std::thread::spawn(|| { // Subscribe in the thread; events from these two subscriptions are delivered only to this thread let symbols = ["ETH_USDT", "SOL_USDT"]; let subs = ctx::subscribe_many(0u16, ctx::SubOpts::depth(5), &symbols).unwrap(); let mut n = 0u64; let t0 = Unix(); while Unix() - t0 < 30 { match ctx::poll(&[], Some(1000)) { // Only receives events subscribed by this thread ctx::Polled::Event(ev) if ev.kind == EV_MD_DEPTH5 => n += 1, ctx::Polled::Stopped => break, _ => {} } } for s in subs { let _ = s.unsubscribe(); // Can only unsubscribe in the thread that created the subscription } n }); let tk = ctx::subscribe(0u16, ctx::SubOpts::ticker().symbol("BTC_USDT")).unwrap(); let t0 = Unix(); while Unix() - t0 < 30 { match ctx::poll(&[tk.id()], Some(1000)) { ctx::Polled::Event(ev) => Log!("main ticker event, seq:", ev.seq), ctx::Polled::Stopped => return, _ => {} } } let _ = tk.unsubscribe(); Log!("depth updates in the thread:", worker.join().unwrap_or(0)); }
  • Subscribe to all-market tickers: receive tickers for every symbol on the exchange through a single subscription, then count how many symbols have been received after 10 seconds; if the exchange does not support this, the subscription receives a failed status event.

    javascript
    function main() { // No symbol specified: a single subscription receives tickers for all symbols on the exchange var all = ctx.subscribe(exchange, {channel: "tickers"}) var last = {} var t0 = Date.now() while (Date.now() - t0 < 10000) { var ev = ctx.poll(all, 1000) if (!ev) { continue } if (ev.kind === 35 && ev.prefillStatus === 2) { Log("tickers not supported:", ev.message) // The exchange does not support all-market push return } if (ev.kind === 1) { last[ev.symbol] = ev.last // symbol is the instrument this ticker belongs to } } Log("symbols:" , Object.keys(last).length, "BTC_USDT last:", last["BTC_USDT"]) all.unsubscribe() }
    python
    import time def main(): # No symbol specified: a single subscription receives tickers for all symbols on the exchange all_tickers = ctx.subscribe(exchange, {"channel": "tickers"}) last = {} t0 = time.time() while time.time() - t0 < 10: ev = ctx.poll(all_tickers, 1000) if ev is None: continue if ev.kind == 35 and ev.prefillStatus == 2: Log("tickers not supported:", ev.message) # The exchange does not support all-market push return if ev.kind == 1: last[ev.symbol] = ev.last # symbol is the instrument this ticker belongs to Log("symbols:", len(last), "BTC_USDT last:" , last.get("BTC_USDT")) all_tickers.unsubscribe()
    rust
    use sdk::ctx::EventExt; use std::collections::HashMap; fn main() { // No symbol specified: a single subscription receives tickers for all symbols on the exchange let all = ctx::subscribe(exchange, ctx::SubOpts::tickers()).unwrap(); let mut last: HashMap<String, f64> = HashMap::new(); let t0 = Unix(); while Unix() - t0 < 10 { match ctx::poll(&[all.id()], Some(1000)) { ctx::Polled::Event(ev) => { if let Some(st) = ev.feed_status() { if st.failed() { Log!("tickers not supported:" , st.message); // The exchange does not support all-market push return; } } else if let Some(t) = ev.ticker() { last.insert(t.symbol, t.last); // symbol is the instrument this ticker belongs to } } ctx::Polled::Stopped => return, _ => {} } } Log!("symbols:", last.len(), "BTC_USDT last:", format!("{:?}", last.get("BTC_USDT"))); let _ = all.unsubscribe(); }
  • Identical subscriptions share a single event stream: the two subscription objects have the same id, and after one of them unsubscribes, the other still keeps receiving events.

    javascript
    function main() { var a = ctx.subscribe(0, {channel: "trade", symbol: "BTC_USDT"}) var b = ctx.subscribe(0, {channel: "trade", symbol: "BTC_USDT"}) var c = ctx.subscribe(0, {channel: "ticker", symbol: "BTC_USDT"}) Log("a.id:", a.id, "b.id:", b.id, "c.id:", c.id) // a.id and b.id are the same, c.id is different a.unsubscribe() // b still references this subscription, so the trade feed is not interrupted var n = 0 while (n < 10) { var ev = ctx.poll(b, 5000) if (!ev) { Log("no trade in 5 seconds") continue } if (ev.kind === 4) { Log("trade", ev.tradeId, ev.side, ev.px, ev.qty) n++ } } b.unsubscribe() // Releases the last reference; only now is the exchange actually unsubscribed c.unsubscribe() }
    python
    def main(): a = ctx.subscribe(0, {"channel": "trade", "symbol": "BTC_USDT"}) b = ctx.subscribe(0, {"channel": "trade", "symbol": "BTC_USDT"}) c = ctx.subscribe(0, {"channel": "ticker", "symbol": "BTC_USDT"}) Log("a.id:", a.id, "b.id:", b.id, "c.id:", c.id) # a.id and b.id are the same, c.id is different a.unsubscribe() # b still references this subscription, so the trade feed is not interrupted n = 0 while n < 10: ev = ctx.poll(b, 5000) if ev is None: Log("no trade in 5 seconds") continue if ev.kind == 4: Log("trade", ev.tradeId, ev.side, ev.px, ev.qty) n += 1 b.unsubscribe() # Releases the last reference; only now is the exchange actually unsubscribed c.unsubscribe()
  • Handling subscriptions rejected by the exchange: subscribing to an unsupported K-line interval does not throw an exception. Instead, the reason for the failure is reported in that subscription's own event stream.

    javascript
    function main() { // Most exchanges do not support 7-second K-lines var k = ctx.subscribe(exchange, {channel: "kline", interval: 7}) while (true) { var ev = ctx.poll(k, 3000) if (!ev) { continue } if (ev.kind === 35) { if (ev.prefillStatus === 2) { Log("kline subscription failed:", ev.message) break } Log("kline history ready") } else if (ev.kind === 5 && ev.closed) { Log("closed bar", _D(ev.openTimeMs), ev.close) } } }
    python
    def main(): # Most exchanges do not support 7-second K-lines k = ctx.subscribe(exchange, {"channel": "kline", "interval": 7}) while True: ev = ctx.poll(k, 3000) if ev is None: continue if ev.kind == 35: if ev.prefillStatus == 2: Log("kline subscription failed:", ev.message) break Log("kline history ready") elif ev.kind == 5 and ev.closed: Log("closed bar", _D(ev.openTimeMs), ev.close)

Returns

TypeDescription

Subscription object / Subscription array

The subscription object. Its properties are id (the event stream ID, which is the ev.stream of the events this subscription produces), ex, channel, symbol, depth and interval. Its methods are book(), positions(), balances() and unsubscribe(). Pass it directly to ctx.poll() to get only this subscription's events. When options.symbol is an array, the call returns an array of subscription objects in the same order.

Arguments

NameTypeRequiredDescription

ex

object / number

Yes

The account to subscribe to. Pass an exchange object (such as exchange or exchanges[1]), or the account's index in the exchanges array (0 means exchanges[0]; the numeric string "0" also works). Account labels are not supported.

options

object

Yes

Subscription options {channel, symbol, depth, interval, mode, buffer}. This is the only supported form, and the symbol is also set here. When symbol is an array, the call is a batch subscription. Each field is described below.

See Also

Remarks

Fields of options:

FieldDescription
channelChannel: ticker (default), bbo (best bid/offer), depth (order book), trade (tick-by-tick trades), kline (candlesticks), tickers (market-wide tickers), private (private account stream)
symbolSymbol code, e.g. BTC_USDT, BTC_USDT.swap; an array of symbol codes can also be passed (batch subscription). Required for market data channels; it can be omitted when ex is an exchange object, in which case the current trading pair of that exchange object is used. Do not pass this field for the tickers and private channels
depthNumber of order book levels for the depth channel; when omitted or set to 0, the exchange's default depth is used
intervalCandlestick period for the kline channel, in seconds, e.g. 60
modeWhen set to latest, only the most recent event is kept; older events not consumed in time are overwritten
bufferCapacity of the bounded buffer (positive integer, in number of events); on overflow the oldest events are discarded

For symbol notation, see Symbol Codes. When ex is an exchange object and symbol is not passed for a market data channel, the trading pair of that exchange object at the moment ctx.subscribe() is called is used; for futures, the contract type is included as well (e.g. BTC_USDT.swap). Subsequent calls to exchange.SetCurrency() or exchange.SetContractType() do not change existing subscriptions. When ex is an index, symbol is not filled in automatically; the tickers (entire market) and private (entire account) channels never carry a symbol.

Passing an array as symbol makes a batch subscription, subscribing to multiple symbols in a single call:

  • Returns an array of subscription objects in the same order as the input array, even if the array has only one element; when symbol is a string, a single subscription object is still returned.
  • The whole batch is validated up front: if any symbol or option is invalid, an exception is thrown for the whole batch and none of the symbols is subscribed.
  • Once validation passes, each element is an independent subscription with its own id, order book and reference count; if the exchange rejects one of the symbols, only that subscription receives a failed status event, and the other subscriptions keep pushing as normal.
  • The returned array has an unsubscribe() method (non-enumerable) that unsubscribes all elements in one call; the array can be passed directly to ctx.poll().

The identity of a subscription is determined jointly by the account, symbol, channel, depth and interval:

  • Within the same thread, subscriptions with exactly the same identity share one event stream, and the returned subscription objects have the same id. The docker manages them by reference count: each call to ctx.subscribe() adds a reference, and each call to unsubscribe() on a subscription object removes one; the docker actually unsubscribes from the exchange only when the last reference is released, so unsubscribing in one place does not interrupt the same subscription still in use elsewhere.
  • If any of these differs, it is a separate subscription with a separate event stream: ticker and bbo for the same symbol are two subscriptions; depth of 5 and 20 are also two subscriptions, each maintaining its own order book (exchanges may push different depths at different frequencies, and the docker does not merge them into the largest depth).
  • Event stream IDs increase monotonically within a run and are never reused; subscribing again after fully unsubscribing yields a new ID.

A subscription belongs to the thread that called ctx.subscribe(). Both the main thread and threads created by threading.Thread() (the standard library threading.Thread in Python, any thread in Rust) can subscribe and receive events:

  • Events of a subscription are delivered only to the thread that subscribed to it, and ctx.poll() receives only the events of subscriptions made by the current thread. To have multiple threads each receive their own events, subscribe to the required streams separately in each thread.
  • When different threads subscribe to the same identity, the exchange is subscribed only once, and only one copy of the order book, candlestick cache and account state is kept; however, each thread gets its own subscription object (with a different id) and its own independent copy of the events, with independent delivery modes, so a slow consumer in one thread does not affect other threads. The docker actually unsubscribes from the exchange only after all threads have unsubscribed.
  • When a thread subscribes later to a candlestick stream that is already pushing, it first receives the cached candlesticks (prefill is true) and the most recent initialization status event; the same applies to the private stream: if the account snapshot is ready, the thread immediately receives the full snapshot and the ready event, and positions() and balances() can be read right away. Other channels start receiving from the next push.
  • Only the thread that owns a subscription can unsubscribe it: calling unsubscribe() on that subscription object from another thread throws an exception, and the subscription remains active. When a thread ends, all its subscriptions are automatically unsubscribed; the same subscriptions shared by other threads are not affected.
  • Gateway status events are sent to every thread that has subscriptions on that exchange; timer events are sent only to the main thread.

The tickers channel subscribes to tickers for the entire market: without passing symbol, a single subscription receives tickers for all symbols on that exchange. The events are the same as ticker events (kind is 1), with channel set to tickers and symbol set to each respective symbol.

  • Supported only when the exchange provides a market-wide push; for each exchange's support, see the "Push" column of the market data group in Capability Matrix and the individual exchange pages. On unsupported exchanges, the subscription receives a failed status event (message starts with unsupported channel: tickers), and it does not automatically fall back to per-symbol subscriptions.
  • On some exchanges the market-wide push does not include the best bid and best ask prices, in which case bid and ask are 0; if you need bid/ask prices, subscribe to ticker or bbo per symbol.
  • By default, the latest event is kept for each symbol: even if consumption is slow, you still get the latest ticker for every symbol.

The private channel is the account's private stream. It takes no symbol, covers the entire account, and there is only one per account. Order updates, positions and balances are all pushed on this single event stream, with the channel of the corresponding events being orders, positions and balance respectively:

  • Once the subscription takes effect (and after each reconnection), an account snapshot (positions, balances, with snapshot set to true) is pushed first, followed by a status event with kind of 35 and channel of private: prefillStatus of 1 means the snapshot is ready, and 2 means the snapshot failed (message gives the reason for failure, and the docker automatically retries with backoff). Only after that does real-time pushing begin (snapshot is false). Order updates have no snapshot.
  • Every push of a position or balance is the complete current state of that position or asset, not an incremental update: a position quantity of 0 means the position has been closed, and both available and frozen being 0 means the asset has been cleared.
  • The positions and balances methods of the subscription object return the latest positions and balances maintained by the docker from the pushed data.
  • What the real-time pushes cover varies by exchange (for example, some exchanges do not push positions and only provide a snapshot). See the notes on each exchange's page for details.

Subscriptions are lazy: ctx.subscribe() returns immediately and registers the subscription, but the connection to the exchange is not established and the subscription request is not sent until ctx.poll() is called for the first time. Changes in connection status are notified through gateway status events (kind is 33, channel is gateway).

Subscription failures fall into two cases:

  • An exception is thrown when the parameters are invalid, for example: the number of parameters is not 2, the second parameter is not an object, options contains an unknown field (such as a misspelled intervel), an unknown channel, a market data channel without symbol, the tickers or private channel with symbol, both mode and buffer passed at the same time, or the symbol array contains an invalid element (in which case none of the batch is subscribed).
  • When the exchange or connector rejects the subscription (for example, an unsupported channel, K-line period or depth level), ctx.subscribe() does not throw an exception. Instead, a status event with kind of 35 and prefillStatus of 2 is received on that subscription's own event stream, with message giving the reason for failure. After that, the subscription is void, and calling unsubscribe() does nothing.

Handling when events are not retrieved in time (delivery mode): ticker, bbo and depth keep only the latest event by default, and tickers keeps only the latest event per symbol by default; trade and kline buffer the most recent 1024 events by default, discarding the oldest events on overflow. This can be changed via mode or buffer (only one of the two may be used). Order update, position, balance, gateway status and initialization status events are never discarded, so ctx.poll() must be called continuously after subscribing. The order book is continuously updated inside the docker and is not affected by how fast events are retrieved; reading it via book() at any time always returns the latest complete order book. To check whether the docker's buffer for receiving pushes is backed up, see the ctx.connectors() counters in ctx.poll.

Pushes are completely independent of the REST interfaces: subscriptions only affect the events received by ctx.poll(). Functions such as exchange.GetTicker, exchange.GetDepth, exchange.GetTrades, exchange.GetRecords and exchange.GetAccount request the exchange's REST interface directly every time and do not read pushed data; whether you subscribe, and what you subscribe to, does not change their return values.

In JavaScript and Rust strategies, ctx.poll() and EventLoop within the same thread retrieve events from the same event queue, so a thread should use only one of the two methods to retrieve events. In Python, EventLoop() has its own independent queue (containing only the market data and order updates automatically subscribed for broker-counter exchanges, plus completion notifications of concurrent tasks such as exchange.Go()), and does not compete with ctx.poll(). For whether each exchange supports pushes and which channels it supports, see the capability matrix and the notes on each exchange's page.

Python strategies are written the same way as JavaScript: ctx.subscribe(ex, options) accepts only these two positional arguments (passing more, fewer, or keyword arguments raises TypeError), options is a dict with the same fields and validation rules, and the dict passed in is not modified. When options["symbol"] is a list (or tuple), a SubscriptionList is returned (a subclass of list, with elements in the same order as passed in), and calling its unsubscribe() unsubscribes all elements at once. The attributes of a subscription object are read-only, and info() returns a dict containing the same fields. Threads created via threading.Thread automatically unsubscribe their subscriptions when they end; threads created by bypassing threading.Thread, such as via _thread.start_new_thread(), can also subscribe, but do not automatically unsubscribe when they end, so you need to call unsubscribe() yourself.

Rust strategies use the function of the same name in the ctx module: ctx::subscribe(ex, opts: ctx::SubOpts) -> Result<ctx::Sub, String>, where ex can be an exchange object (exchange, exchanges[i]) or an index of type u16; on failure it returns Err and does not panic. Subscription options are built with ctx::SubOpts::ticker(), bbo(), trade(), depth(n), kline(seconds), tickers() or private(), followed by chained calls to .symbol("BTC_USDT"), .latest() and .buffer(n). Batch subscriptions use ctx::subscribe_many(ex, opts, &["BTC_USDT", "ETH_USDT"]) -> Result<Vec<ctx::Sub>, String>, which also validates the entire batch first. The returned ctx::Sub can be copied and provides the methods id() (passed to ctx::poll(&[sub.id()], ...)), book(n), positions(), balances() and unsubscribe(). The reference count is based on the number of ctx::subscribe() calls, so every call must be matched by one call to unsubscribe(). Any thread in Rust can create a subscription, and the subscription likewise belongs to the thread that created it.