Type/to search
Getting Started
Welcome to FMZ Quant Trading Platform
Quick Start
Key Security
Platform Basics
Account and Billing
Live Robot Billing and Top-up
Sub-accounts
Exchange
General Protocol
Local Credential Files
Exchange-Specific Notes
Securities and Futures
Crypto
Docker
Strategy Library
Live Trading
Writing Strategies
Development Tools
Backtesting System
Advanced Topics
Data and Research
Integrations

Exchanges limit how often their API may be called. Going over the limit gets requests rejected at best and the account temporarily banned at worst. With exchange.IO("rate", ...) or exchange.IO("quota", ...) you can cap the call frequency of standard functions locally on the docker: a call that exceeds the limit is never sent.

javascript
exchange.IO("rate" | "quota", name, count, window[, "delay"])

Two modes

  • rate (token bucket): the bucket capacity defaults to count. It starts full and refills at a steady "count per window"; each call takes one token. Short bursts are allowed, while the long-run average never exceeds "count per window". Writing count as "10/5" means 10 refills per window with a bucket capacity of 5, which limits bursts.
  • quota (fixed window): at most count calls per window; the counter resets when the next window starts. Windows are aligned to the Unix epoch: "1s" to whole seconds, "1m" to whole minutes, "1h" to whole hours, "1d" to UTC midnight (08:00 Beijing time). For example, counting that starts at 12:00:00.900 is already in a new window at 12:00:01.000.

Use quota with a window matching the exchange's counting period when you must guarantee "no more than N calls in any of the exchange's periods"; use rate when you only need to control the average frequency.

Parameters

ParameterDescription
nameThe function to limit, see the table below. Several names separated by commas (such as "GetTicker,GetDepth") share one rule and their calls are counted together. "*" is a fallback rule that applies only to functions without a rule of their own.
countCalls allowed per window, must be greater than 0; in rate mode it can be written as "count/burst". Passing 0 or a negative number deletes the rule for that name.
windowA duration in the syntax of Go's time.ParseDuration: units ns, us (or µs), ms, s, m, h, decimals allowed ("1.5s"), units can be combined ("1h30m"); "Nd" means N days (decimals allowed, such as "0.5d", not combinable with other units). "@HHMM" or "@HHMMSS" (such as "@0800") counts per day and resets at that time of day (Beijing time); it works with both rate and quota.
actionWhen omitted, a call over the limit fails immediately; with "delay" the call blocks until a call is available and is then sent. Stopping the live trading interrupts the wait.

Function names that can be limited

CategoryNames
Market dataGetTicker, GetTickers, GetDepth, GetTrades, GetRecords, GetMarkets, GetFundings
AccountGetAccount, GetAssets, GetPositions, SetMarginLevel
TradingCreateOrder (Buy and Sell count here too), CancelOrder, ModifyOrder
Order queriesGetOrder, GetOrders, GetHistoryOrders
Conditional ordersCreateConditionOrder, ModifyConditionOrder, CancelConditionOrder, GetConditionOrder, GetConditionOrders, GetHistoryConditionOrders
Custom requestsIO/api: limits only exchange.IO("api", ...), other exchange.IO() commands are unaffected
  • GetAccount and GetAssets are the same underlying request; a rule under either name applies to both functions.
  • Calls made concurrently through exchange.Go() are counted under the function actually called.

Scope of rules

  • Rules are set per exchange object: a rule on exchanges[0] does not affect exchanges[1].
  • Rules last for the current run only. Set them again after the live trading restarts, usually at the beginning of main().
  • Setting the same name again replaces its rule; an empty name (exchange.IO("rate", "")) clears all rules of that exchange object.
  • Each call is counted under one rule only: a function with its own rule is no longer counted under "*", so "*" cannot be used as a "total quota for all calls" stacked on top of specific rules.

When the limit is exceeded

With the default action, a call over the limit sends no request and is treated as a failed call (JavaScript returns null, Python returns None, Rust returns Err). The error message looks like:

rate limit exceeded: GetTicker 10/1s quota limit exceeded: GetTicker 10/1m quota limit exceeded: GetRecords 2000/day (resets at 0800)

With the "delay" action the call blocks until a call is available, so the time recorded in the log is the time after the wait. With a rule that resets daily, "delay" may wait until the next day; use it with care.

Examples

Examples

  • Default action: calls over the limit fail

    javascript
    function main() { // GetTicker at most 5 calls per second on average (token bucket, capacity 5) exchange.IO("rate", "GetTicker", 5, "1s") for (var i = 0; i < 10; i++) { var ticker = exchange.GetTicker("BTC_USDT") if (ticker) { Log("call", i + 1, "succeeded:", ticker.Last) } else { // a call over the limit sends no request and returns null Log("call", i + 1, "rate limited:", GetLastError()) } } }
    python
    def main(): # GetTicker at most 5 calls per second on average (token bucket, capacity 5) exchange.IO("rate", "GetTicker", 5, "1s") for i in range(10): ticker = exchange.GetTicker("BTC_USDT") if ticker: Log("call", i + 1, "succeeded:", ticker["Last"]) else: # a call over the limit sends no request and returns None Log("call", i + 1, "rate limited:", GetLastError())
    rust
    fn main() { // GetTicker at most 5 calls per second on average (token bucket, capacity 5) let _ = exchange.IO(("rate", "GetTicker", 5, "1s")); for i in 0..10 { match exchange.GetTicker("BTC_USDT") { Ok(ticker) => Log!("call", i + 1, "succeeded:", ticker.Last), // a call over the limit sends no request and returns Err Err(e) => Log!("call", i + 1, "rate limited:", e), } } }
  • Rules grouped after the exchange's own limits

    Market data and trading each share one rule; trading calls wait instead of failing when over the limit; every other function without a rule of its own falls back to "*".

    javascript
    function main() { // market data: GetTicker and GetDepth together at most 20 calls per second on average exchange.IO("rate", "GetTicker,GetDepth", 20, "1s") // trading: placing orders (Buy/Sell included) and cancelling together 5 per second, wait when over the limit exchange.IO("rate", "CreateOrder,CancelOrder", 5, "1s", "delay") // fallback: other functions (such as GetAccount, GetPositions) together 60 per minute, window aligned to whole minutes exchange.IO("quota", "*", 60, "1m") while (true) { var ticker = exchange.GetTicker("BTC_USDT") var depth = exchange.GetDepth("BTC_USDT") if (ticker && depth) { Log("last:", ticker.Last, "best bid:", depth.Bids[0].Price) } Sleep(1000) } }
    python
    def main(): # market data: GetTicker and GetDepth together at most 20 calls per second on average exchange.IO("rate", "GetTicker,GetDepth", 20, "1s") # trading: placing orders (Buy/Sell included) and cancelling together 5 per second, wait when over the limit exchange.IO("rate", "CreateOrder,CancelOrder", 5, "1s", "delay") # fallback: other functions (such as GetAccount, GetPositions) together 60 per minute, window aligned to whole minutes exchange.IO("quota", "*", 60, "1m") while True: ticker = exchange.GetTicker("BTC_USDT") depth = exchange.GetDepth("BTC_USDT") if ticker and depth: Log("last:", ticker["Last"], "best bid:", depth["Bids"][0]["Price"]) Sleep(1000)
    rust
    fn main() { // market data: GetTicker and GetDepth together at most 20 calls per second on average let _ = exchange.IO(("rate", "GetTicker,GetDepth", 20, "1s")); // trading: placing orders (Buy/Sell included) and cancelling together 5 per second, wait when over the limit let _ = exchange.IO(("rate", "CreateOrder,CancelOrder", 5, "1s", "delay")); // fallback: other functions (such as GetAccount, GetPositions) together 60 per minute, window aligned to whole minutes let _ = exchange.IO(("quota", "*", 60, "1m")); loop { if let (Ok(ticker), Ok(depth)) = (exchange.GetTicker("BTC_USDT"), exchange.GetDepth("BTC_USDT")) { Log!("last:", ticker.Last, "best bid:", depth.Bids[0].Price); } Sleep(1000); } }
  • Burst capacity, daily quota and deleting rules

    javascript
    function main() { // 10 calls per second on average, but at most 2 in a burst exchange.IO("rate", "GetDepth", "10/2", "1s") // resets every day at 08:00 Beijing time, at most 2000 calls per day exchange.IO("quota", "GetRecords", 2000, "@0800") // units can be combined: at most 100 calls every 1 hour 30 minutes exchange.IO("rate", "GetOrders", 100, "1h30m") // count 0: delete the rule for GetOrders exchange.IO("rate", "GetOrders", 0) // empty name: clear all rules of this exchange object exchange.IO("rate", "") }
    python
    def main(): # 10 calls per second on average, but at most 2 in a burst exchange.IO("rate", "GetDepth", "10/2", "1s") # resets every day at 08:00 Beijing time, at most 2000 calls per day exchange.IO("quota", "GetRecords", 2000, "@0800") # units can be combined: at most 100 calls every 1 hour 30 minutes exchange.IO("rate", "GetOrders", 100, "1h30m") # count 0: delete the rule for GetOrders exchange.IO("rate", "GetOrders", 0) # empty name: clear all rules of this exchange object exchange.IO("rate", "")
    rust
    fn main() { // 10 calls per second on average, but at most 2 in a burst let _ = exchange.IO(("rate", "GetDepth", "10/2", "1s")); // resets every day at 08:00 Beijing time, at most 2000 calls per day let _ = exchange.IO(("quota", "GetRecords", 2000, "@0800")); // units can be combined: at most 100 calls every 1 hour 30 minutes let _ = exchange.IO(("rate", "GetOrders", 100, "1h30m")); // count 0: delete the rule for GetOrders let _ = exchange.IO(("rate", "GetOrders", 0)); // empty name: clear all rules of this exchange object let _ = exchange.IO(("rate", "")); }

See Also