exchange.IO

exchange.IO()函数用于调用交易所对象的其它相关接口。

string / number / bool / object / array / any (平台支持的任意类型)

exchange.IO(k, ...args)

`k`参数用于设置调用类型,可选值包括`"api"`、`"currency"`、`"base"`、`"trade_margin"`、`"trade_normal"`、`"public_base"`、`"mbase"`、`selfTradePreventionMode`、`simulate`、`cross`、`dual`、`unified`等。
k
true
string
扩展参数,根据具体调用场景传入。`arg`参数支持传入多个值。由于`exchange.IO()`函数采用多态机制,不同的参数设置对应不同的功能。`exchange.IO()`函数的参数个数和类型均不固定。
arg
true
string / number / bool / object / array / any (平台支持的任意类型)

```javascript
function main() {
    var arrOrders = [
        {"instId":"BTC-USDT-SWAP","tdMode":"cross","side":"buy","ordType":"limit","px":"16000","sz":"1","posSide":"long"},
        {"instId":"BTC-USDT-SWAP","tdMode":"cross","side":"buy","ordType":"limit","px":"16000","sz":"2","posSide":"long"}
    ]

    // 调用 exchange.IO 直接访问交易所批量下单接口
    var ret = exchange.IO("api", "POST", "/api/v5/trade/batch-orders", "", JSON.stringify(arrOrders))
    Log(ret)
}```
```python
import json
def main():
    arrOrders = [
        {"instId":"BTC-USDT-SWAP","tdMode":"cross","side":"buy","ordType":"limit","px":"16000","sz":"1","posSide":"long"},
        {"instId":"BTC-USDT-SWAP","tdMode":"cross","side":"buy","ordType":"limit","px":"16000","sz":"2","posSide":"long"}
    ]
    ret = exchange.IO("api", "POST", "/api/v5/trade/batch-orders", "", json.dumps(arrOrders))
    Log(ret)```
```cpp
void main() {
    json arrOrders = R"([
        {"instId":"BTC-USDT-SWAP","tdMode":"cross","side":"buy","ordType":"limit","px":"16000","sz":"1","posSide":"long"},
        {"instId":"BTC-USDT-SWAP","tdMode":"cross","side":"buy","ordType":"limit","px":"16000","sz":"2","posSide":"long"}
    ])"_json;
    auto ret = exchange.IO("api", "POST", "/api/v5/trade/batch-orders", "", arrOrders.dump());
    Log(ret);
}```
使用```exchange.IO()```函数的```exchange.IO("api", httpMethod, resource, params, raw)```调用形式,需要首先了解该交易所的API接口并查阅相关文档。通过此方式可以扩展FMZ平台尚未添加的功能。提交```POST```请求时无需担心参数加密、签名、验证等过程,FMZ已在底层完成处理,只需填入相应参数即可。

可以参考**OKX交易所**期货合约的批量下单示例,使用参数```raw```传递订单参数:
```javascript
var amount = 1
var price = 10
var basecurrency = "ltc"
function main () {
    // 注意 amount.toString() 和 price.toString() 左边右边都有一个 ' 字符
    var message = "symbol=" + basecurrency + "&amount='" + amount.toString() + "'&price='" + price.toString() + "'&side=buy" + "&type=limit"
    var id = exchange.IO("api", "POST", "/v1/order/new", message)
}```
```python
amount = 1
price = 10
basecurrency = "ltc"
def main():
    message = "symbol=" + basecurrency + "&amount='" + str(amount) + "'&price='" + str(price) + "'&side=buy" + "&type=limit"
    id = exchange.IO("api", "POST", "/v1/order/new", message)```
```cpp
void main() {
    auto amount = 1.0;
    auto price = 10.0;
    auto basecurrency = "ltc";
    string message = format("symbol=%s&amount=\"%.1f\"&price=\"%.1f\"&side=buy&type=limit", basecurrency, amount, price);
    auto id = exchange.IO("api", "POST", "/v1/order/new", message);
}```
如果```params```参数(即HTTP请求参数)中的键值为字符串类型,需要在参数值两侧使用单引号(即符号')将参数值包裹起来。
```javascript
function main() {
    var ret = exchange.IO("api", "GET", "https://www.okx.com/api/v5/account/max-withdrawal", "ccy=BTC")
    Log(ret)
}```
```python
def main():
    ret = exchange.IO("api", "GET", "https://www.okx.com/api/v5/account/max-withdrawal", "ccy=BTC")
    Log(ret)```
```cpp
void main() {
    auto ret = exchange.IO("api", "GET", "https://www.okx.com/api/v5/account/max-withdrawal", "ccy=BTC");
    Log(ret);
}```
支持传入完整的 URL 参数,这样可以省略切换基地址的操作(调用 ```exchange.SetBase()``` 函数)。
```javascript
function main(){
    var ret = exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT")
    Log(ret)
}```
```python
def main():
    ret = exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT")
    Log(ret)```
```cpp
void main() {
    auto ret = exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT");
    Log(ret);
}```
不使用参数 ```raw``` 的调用示例:
```javascript
function main() {
    // 例如开始实盘上设置交易所对象目前交易对为BTC_USDT,打印目前交易对行情
    Log(exchange.GetTicker())
    // 切换交易对为LTC_BTC
    exchange.IO("currency", "LTC_BTC")
    Log(exchange.GetTicker())
}```
```python
def main():
    Log(exchange.GetTicker())
    exchange.IO("currency", "LTC_BTC")
    Log(exchange.GetTicker())```
```cpp
void main() {
    Log(exchange.GetTicker());
    exchange.IO("currency", "LTC_BTC");
    Log(exchange.GetTicker());
}```
切换当前交易所的交易对,通过代码切换**实盘创建时**或**回测时**配置的交易对。
```javascript
function main () {
    // exchanges[0]就是实盘创建时,第一个添加的交易所对象
    exchanges[0].IO("base", "https://api.huobi.pro")
}```
```python
def main():
    exchanges[0].IO("base", "https://api.huobi.pro")```
```cpp
void main() {
    exchanges[0].IO("base", "https://api.huobi.pro");
}```
例如交易所对象封装时默认的基地址为 ```https://api.huobipro.com```,需要切换至 ```https://api.huobi.pro``` 时,使用以下代码进行切换:
```javascript
function main() {
    exchange.SetBase("https://api.bitfinex.com")
    exchange.IO("mbase", "https://api-pub.bitfinex.com")
}```
```python
def main():
    exchange.SetBase("https://api.bitfinex.com")
    exchange.IO("mbase", "https://api-pub.bitfinex.com")```
```cpp
void main() {
    exchange.SetBase("https://api.bitfinex.com");
    exchange.IO("mbase", "https://api-pub.bitfinex.com");
}```
对于行情接口和交易接口基地址不同的交易所,例如 Bitfinex 期货有两个地址,一个是行情接口地址,一个是交易接口地址。

- Bitfinex 期货切换私有接口基地址使用 ```exchange.SetBase("xxx")```。

- Bitfinex 期货切换公共接口基地址使用 ```exchange.IO("mbase", "xxx")```。
```javascript
function main() {
    // 场景1: 限制GetTicker每秒最多调用10次,超限返回null
    exchange.IO("rate", "GetTicker", 10, "1s")

    for (var i = 0; i < 20; i++) {
        var ticker = exchange.GetTicker("BTC_USDT")
        if (ticker) {
            Log("Ticker:", ticker.Last)
        } else {
            Log("Rate limit exceeded")
        }
    }
}```
```python
def main():
    # 场景1: 限制GetTicker每秒最多调用10次,超限返回null
    exchange.IO("rate", "GetTicker", 10, "1s")

    for i in range(20):
        ticker = exchange.GetTicker("BTC_USDT")
        if ticker:
            Log("Ticker:", ticker["Last"])
        else:
            Log("Rate limit exceeded")```
```cpp
// C++暂不支持```
限制 GetTicker 每秒最多调用 10 次,超限时报错并返回空值:
```javascript
function main() {
    // 场景2: 使用delay参数,超限时自动等待
    exchange.IO("rate", "GetTicker", 10, "1s", "delay")

    for (var i = 0; i < 20; i++) {
        var ticker = exchange.GetTicker("BTC_USDT")
        Log("Call", i+1, "Ticker:", ticker.Last)
    }
}```
```python
def main():
    # 场景2: 使用delay参数,超限时自动等待
    exchange.IO("rate", "GetTicker", 10, "1s", "delay")

    for i in range(20):
        ticker = exchange.GetTicker("BTC_USDT")
        Log("Call", i+1, "Ticker:", ticker["Last"])```
```cpp
// C++暂不支持```
限制 GetTicker 每秒最多调用 10 次,超限时自动等待:
```javascript
function main() {
    // 场景3: GetTicker和GetDepth共享限制,合计每秒最多10次
    exchange.IO("rate", "GetTicker,GetDepth", 10, "1s")

    for (var i = 0; i < 20; i++) {
        if (i % 2 == 0) {
            Log("Ticker:", exchange.GetTicker("BTC_USDT"))
        } else {
            Log("Depth:", exchange.GetDepth("BTC_USDT"))
        }
    }
}```
```python
def main():
    # 场景3: GetTicker和GetDepth共享限制,合计每秒最多10次
    exchange.IO("rate", "GetTicker,GetDepth", 10, "1s")

    for i in range(20):
        if i % 2 == 0:
            Log("Ticker:", exchange.GetTicker("BTC_USDT"))
        else:
            Log("Depth:", exchange.GetDepth("BTC_USDT"))```
```cpp
// C++暂不支持```
限制多个函数的联合调用频率:
```javascript
function main() {
    // 场景4: 限制所有API调用每分钟最多100次
    exchange.IO("rate", "*", 100, "1m")

    for (var i = 0; i < 10; i++) {
        exchange.GetTicker("BTC_USDT")
        exchange.GetDepth("BTC_USDT")
        exchange.GetAccount()
        Log("Round", i+1, "completed")
        Sleep(1000)
    }
}```
```python
def main():
    # 场景4: 限制所有API调用每分钟最多100次
    exchange.IO("rate", "*", 100, "1m")

    for i in range(10):
        exchange.GetTicker("BTC_USDT")
        exchange.GetDepth("BTC_USDT")
        exchange.GetAccount()
        Log("Round", i+1, "completed")
        Sleep(1000)```
```cpp
// C++暂不支持```
使用通配符限制所有API调用:
```javascript
function main() {
    // 场景5: 严格限制,时间窗口对齐到整秒
    exchange.IO("quota", "GetTicker", 3, "1s")

    for (var i = 0; i < 10; i++) {
        var ticker = exchange.GetTicker("BTC_USDT")
        if (ticker) {
            Log(_D(), "Ticker:", ticker.Last)
        } else {
            Log(_D(), "Quota exceeded, waiting for next window")
        }
        Sleep(100)
    }
}```
```python
def main():
    # 场景5: 严格限制,时间窗口对齐到整秒
    exchange.IO("quota", "GetTicker", 3, "1s")

    for i in range(10):
        ticker = exchange.GetTicker("BTC_USDT")
        if ticker:
            Log(_D(), "Ticker:", ticker["Last"])
        else:
            Log(_D(), "Quota exceeded, waiting for next window")
        Sleep(100)```
```cpp
// C++暂不支持```
使用quota模式进行严格时间窗口对齐限流:
```javascript
function main() {
    // 场景6: 每天最多调用1000次GetTicker,每天08:15重置配额
    exchange.IO("quota", "GetTicker", 1000, "@0815")

    var count = 0
    while (true) {
        var ticker = exchange.GetTicker("BTC_USDT")
        if (ticker) {
            count++
            Log("Call count:", count, "Ticker:", ticker.Last)
        } else {
            Log("Daily quota exceeded, waiting for reset at 08:15")
            Sleep(60000)  // Wait 1 minute
        }
        Sleep(1000)
    }
}```
```python
def main():
    # 场景6: 每天最多调用1000次GetTicker,每天08:15重置配额
    exchange.IO("quota", "GetTicker", 1000, "@0815")

    count = 0
    while True:
        ticker = exchange.GetTicker("BTC_USDT")
        if ticker:
            count += 1
            Log("Call count:", count, "Ticker:", ticker["Last"])
        else:
            Log("Daily quota exceeded, waiting for reset at 08:15")
            Sleep(60000)  # Wait 1 minute
        Sleep(1000)```
```cpp
// C++暂不支持```
使用日内配额,每天在指定时间重置:
```javascript
function main() {
    // 场景7: 组合多个限流规则
    exchange.IO("rate", "GetTicker", 10, "1s")      // GetTicker每秒10次
    exchange.IO("rate", "GetDepth", 5, "1s")        // GetDepth每秒5次
    exchange.IO("rate", "CreateOrder", 2, "1s")     // CreateOrder每秒2次
    exchange.IO("quota", "*", 1000, "@0000")        // 所有API每天00:00重置,限1000次

    Log("Rate limits configured successfully")

    // 测试限流
    for (var i = 0; i < 5; i++) {
        exchange.GetTicker("BTC_USDT")
        exchange.GetDepth("BTC_USDT")
        Sleep(200)
    }
}```
```python
def main():
    # 场景7: 组合多个限流规则
    exchange.IO("rate", "GetTicker", 10, "1s")      # GetTicker每秒10次
    exchange.IO("rate", "GetDepth", 5, "1s")        # GetDepth每秒5次
    exchange.IO("rate", "CreateOrder", 2, "1s")     # CreateOrder每秒2次
    exchange.IO("quota", "*", 1000, "@0000")        # 所有API每天00:00重置,限1000次

    Log("Rate limits configured successfully")

    # 测试限流
    for i in range(5):
        exchange.GetTicker("BTC_USDT")
        exchange.GetDepth("BTC_USDT")
        Sleep(200)```
```cpp
// C++暂不支持```
组合使用多个限流规则:

**一、用于加密货币中心化交易所其它未统一封装的API接口调用,参数```k```设置为```"api"```:**

```javascript
exchange.IO("api", httpMethod, resource, params, raw)
  • httpMethod:该参数为字符串类型,填入请求类型POSTGET等。
  • resource:该参数为字符串类型,填入请求路径;支持使用完整的请求路径,详见参考示例。
  • params:该参数为字符串类型,填入请求参数,使用URL编码。
  • raw:该参数为原始字符串参数,可省略不传。
**二、用于切换交易对,参数```k```设置为```"currency"```:**

```javascript
exchange.IO("currency", currency)
  • currency:该参数为字符串类型,格式统一为大写,使用下划线分隔baseCurrencyquoteCurrency,例如:BTC_USDT。 1、回测系统现已支持切换交易对(仅限数字货币现货交易所对象),回测时需要注意只能切换为相同计价币的交易对,例如当前交易对为ETH_BTC只能切换为LTC_BTC,不能切换为LTC_USDT。 2、对于加密货币期货合约交易所对象,切换交易对后需要再次设置合约代码以确定要交易的具体合约。 3、使用{@fun/Account/exchange.SetCurrency exchange.SetCurrency}函数切换交易对的功能与使用exchange.IO("currency", currency)切换交易对的功能完全相同。 三、用于切换加密货币现货交易所对象杠杆账户模式:

  • 参数k设置为"trade_margin",切换为现货杠杆账户模式。下单、获取账户资产将访问交易所现货杠杆接口。 如果交易所现货杠杆区分全仓、逐仓,则使用:exchange.IO("trade_super_margin")切换为杠杆账户全仓,exchange.IO("trade_margin")切换为杠杆账户逐仓。

  • 参数k设置为"trade_normal",切换回普通现货账户模式。

支持切换杠杆账户模式的现货交易所:

交易所 特殊备注
OKX 杠杆账户模式的交易对与普通模式有所不同,部分交易对可能不支持。使用exchange.IO("trade_super_margin")切换为杠杆账户全仓,exchange.IO("trade_margin")切换为逐仓。使用trade_normal切换为普通币币模式。使用exchange.IO("tdMode", "cross")直接指定杠杆模式。如果OKX账户设置为「组合保证金」模式,必须切换为全仓模式,OKX的组合保证金模式仅支持全仓杠杆。
火币 杠杆账户模式的交易对与普通模式有所不同,部分交易对可能不支持。火币杠杆账户分为全仓和逐仓,使用trade_margin切换为杠杆账户逐仓,使用trade_super_margin切换为杠杆账户全仓。使用trade_normal切换为普通币币模式。
币安(Binance) 杠杆账户模式分为逐仓、全仓,使用trade_margin切换为逐仓,使用trade_super_margin切换为全仓,使用trade_normal切换为普通币币模式。
GateIO 杠杆账户模式分为逐仓、全仓,使用trade_margin切换为逐仓,使用trade_super_margin切换为全仓,使用trade_normal切换为普通币币模式。
AscendEx 使用exchange.IO("trade_margin")切换为杠杆账户模式,使用exchange.IO("trade_normal")切换回普通账户模式。
WOO 使用exchange.IO("trade_margin")切换为杠杆账户模式,使用exchange.IO("trade_normal")切换回普通账户模式。
CoinEx 使用exchange.IO("trade_margin")切换为杠杆账户模式,使用exchange.IO("trade_normal")切换回普通账户模式。

四、其它切换功能: 在用户指南中可以查看exchange.IO()函数的其它切换功能五、API限流控制功能:

FMZ平台支持对交易所API调用进行频率限制,防止触发交易所的频率限制。支持两种限流模式:

  • rate模式(平滑限流):不严格对齐时间窗口,适用于一般限流需求。
  • quota模式(额度限流):严格对齐时间窗口,例如1m对齐到整分钟,1s对齐到整秒。

函数签名:

exchange.IO("rate", functionNames, maxCalls, period, [behavior])
exchange.IO("quota", functionNames, maxCalls, period, [behavior])

参数说明:

参数 类型 说明
mode string 限流模式:”rate”(平滑限流)或 “quota”(额度限流)
functionNames string 要限制的函数名,支持单个函数、多个函数(逗号分隔)、通配符(*表示所有函数)
maxCalls number 时间周期内允许的最大调用次数
period string 时间周期或重置时间点。支持时间单位:ns, us, µs, ms, s, m, h, d(例如:”1s”, “1m”, “1h”)。支持重置时间点:@HHMM 或 @HHMMSS(例如:”@0815”表示每天08:15重置)
behavior string 可选参数,超限行为。默认为空(超限返回null并报错);设置为”delay”时超限会等待

支持的函数列表:

交易类:CreateOrder, CancelOrder, Buy, Sell, CreateConditionOrder, CancelConditionOrder

账户类:GetAccount, GetAssets, GetPositions

订单类:GetOrder, GetOrders, GetHistoryOrders, GetConditionOrder, GetConditionOrders, GetHistoryConditionOrders

行情类:GetTicker, GetTickers, GetDepth, GetRecords, GetTrades

其他:GetMarkets, GetFundings, SetMarginLevel, Go, IO/api

注意事项:

  • Buy/Sell函数的限流遵循CreateOrder的限流设置。
  • Go函数的限流遵循实际并发调用函数的限流设置。
  • IO/api的限流仅对exchange.IO("api", ...)调用生效。
  • quota模式严格对齐时间窗口,例如设置"1s"时,时间窗口按整秒对齐。
  • 使用"delay"参数时,调用时间与日志记录时间可能存在差异,此为正常现象。

{@fun/NetSettings/exchange.SetBase exchange.SetBase}, {@fun/Account/exchange.SetCurrency exchange.SetCurrency}, {@var EXCHANGE_OP_IO_CONTROL}