exchange.IO


```exchange.IO()```函数用于调用交易所对象相关的其它接口。调用成功时返回请求的应答数据,调用失败时返回空值。
string / number / bool / object / array / any (系统支持的所有类型)

exchange.IO(k, ...args)

```k```参数用于设置调用类型,可选值为```"api"```、```wait```。
k
true
string
扩展参数,根据具体调用场景传入。```arg```参数可以传递多个。由于```exchange.IO()```函数的多态机制,不同的参数设置对应不同的功能。```exchange.IO()```函数的参数个数和类型都是不确定的。
arg
true
string / number / bool / object / array / any (系统支持的所有类型)

```javascript
function main() {
    while (!exchange.IO("status")) {
        LogStatus("正在等待与交易服务器连接, " + new Date())
    }
}```
```python
def main():
    while not exchange.IO("status"):
        LogStatus("正在等待与交易服务器连接, " + _D())```
```cpp
void main() {
    while(exchange.IO("status") == 0) {
        LogStatus("正在等待与交易服务器连接, " + _D());
    }
}```
使用 status 参数判断与期货公司前置机的连接状态:```exchange.IO("status")```,返回 ```true``` 表示与 CTP 服务器的行情服务器和交易服务器均已正常连接。
```javascript
function on_tick(symbol, ticker) {
    Log("symbol:", symbol, "update")
    Log("ticker:", ticker)
}

function on_order(order) {
    Log("order update", order)
}

function main() {
    while(!exchange.IO("status")) {
        Sleep(10)
    }
    // 如果在程序中不使用诸如exchange.GetTicker()之类的获取行情的函数,可以不设置exchange.IO("mode", 0)
    exchange.IO("mode", 0)
    _C(exchange.SetContractType, "MA005")
    while(true) {
        var e = exchange.IO("wait")
        if(e) {
            if(e.Event == "tick") {
                on_tick(e.Symbol, e.Ticker)
            } else if(e.Event == "order") {
                on_order(e.Order)
            }
        }
    }
}```
```python
def on_tick(symbol, ticker):
    Log("symbol:", symbol, "update")
    # 数据结构:https://www.youquant.com/api#ticker
    Log("ticker:", ticker)

def on_order(order):
    Log("order update", order)

def main():
    # wait connect trade server
    while not exchange.IO("status"):
        Sleep(10)
    # switch push mode
    exchange.IO("mode", 0)
    # subscribe instrument
    _C(exchange.SetContractType, "MA001")
    while True:
        e = exchange.IO("wait")
        if e:
            if e.Event == "tick":
                on_tick(e['Symbol'], e['Ticker'])
            elif e.Event == "order":
                on_order(e['Order'])```
```cpp
void on_tick(const string &symbol, json &ticker) {
    Log("symbol:", symbol, "update");
    Log("ticker:", ticker);
}

void on_order(json &order) {
    Log("order update", order);
}

void main() {
    while(exchange.IO("status") == 0) {
        Sleep(10);
    }
    exchange.IO("mode", 0);
    _C(exchange.SetContractType, "rb2005");
    while(true) {
        auto e = exchange.IO("wait");
        if(e != false) {
            if(e["Event"] == "tick") {
                on_tick(e["Symbol"], e["Ticker"]);
            }
            else if(e["Event"] == "order") {
                on_order(e["Order"]);
            }
        }
    }
}```
使用```wait```参数设置阻塞:

```exchange.IO("wait", Timeout)```,当前交易所有任何品种更新行情信息或订单成交时才返回。可带第二个参数(毫秒数)指定超时,超时返回空值,正常返回```EventTick```/```OrderEvent```结构。结合```exchange.IO("mode", 0)```函数使用,可以使程序在有最新行情时进行响应,执行程序逻辑(使用```exchange.IO("mode", 0)```并不影响```exchange.IO("wait")```,目的是为了在程序中使用```exchange.GetTicker()```等函数调用时不阻塞)。如果```Timeout```参数设置为-1,该函数将立即返回,在没有新事件时返回空值;```Timeout```参数设置为0时阻塞等待最新事件,效果与不设置```Timeout```参数相同。需要注意的是,在使用```exchange.IO("wait")```时,必须至少已经订阅了一个当前处于交易状态的合约(已交割的过期合约不会再有行情数据),否则会阻塞在该函数(由于没有任何行情、订单更新)。仅支持商品期货实盘。

```EventTick```:```{Event:"tick", Index:交易所索引, Nano:事件纳秒级时间, Symbol:合约名称, Ticker:行情数据}```。

```OrderTick```:```{Event:"order", Index:交易所索引, Nano:事件纳秒级时间, Order:订单信息}```。

简单实现回调机制:
```javascript
function on_tick(symbol, ticker) {
    Log("symbol:", symbol, "update", "ticker:", ticker)
}

function main() {
    while(!exchange.IO("status")) {
        Sleep(10)
    }

    _C(exchange.SetContractType, "MA101")
    _C(exchange.SetContractType, "rb2101")
    _C(exchange.SetContractType, "i2101")
    while(true) {
        var e = exchange.IO("wait", -1)
        if(e) {
            if(e.Event == "tick") {
                on_tick(e.Symbol, e.Ticker)
            }
        }
        Sleep(10)
    }
}```
```python
def on_tick(symbol, ticker):
    Log("symbol:", symbol, "update", "ticker:", ticker)

def main():
    while not exchange.IO("status"):
        Sleep(10)
    _C(exchange.SetContractType, "MA101")
    _C(exchange.SetContractType, "rb2101")
    _C(exchange.SetContractType, "i2101")
    while True:
        e = exchange.IO("wait", -1)
        if e:
            if e.Event == "tick":
                on_tick(e['Symbol'], e['Ticker'])
        Sleep(10)```
```cpp
void on_tick(const string &symbol, json &ticker) {
    Log("symbol:", symbol, "update", "ticker:", ticker);
}

void main() {
    while(exchange.IO("status") == 0) {
        Sleep(10);
    }
    _C(exchange.SetContractType, "MA101");
    _C(exchange.SetContractType, "rb2101");
    _C(exchange.SetContractType, "i2101");
    while(true) {
        auto e = exchange.IO("wait", -1);
        if(e != false) {
            if(e["Event"] == "tick") {
                on_tick(e["Symbol"], e["Ticker"]);
            }
        }
    }
}```
多品种回调示例:
```javascript
function main() {
    while (!exchange.IO("status")) {
        LogStatus("正在等待与交易服务器连接, " + new Date())
    }

    Log("开始获取所有合约")
    var instruments = _C(exchange.IO, "instruments")
    Log("合约列表获取成功")
    var len = 0
    for (var instrumentId in instruments) {
        len++
    }
    Log("合约列表长度为:",len)
}```
```python
def main():
    while not exchange.IO("status"):
        LogStatus("正在等待与交易服务器连接, " + _D())

    Log("开始获取所有合约")
    instruments = _C(exchange.IO, "instruments")
    Log("合约列表获取成功")
    length = 0
    for i in range(len(instruments)):
        length += 1
    Log("合约列表长度为:", length)```
```cpp
void main() {
    while(exchange.IO("status") == 0) {
        LogStatus("正在等待与交易服务器连接, " + _D());
    }

    Log("开始获取所有合约");
    auto instruments = _C(exchange.IO, "instruments");
    Log("合约列表获取成功");
    int length = 0;
    for(int i = 0; i < instruments.size(); i++) {
        length++;
    }
    Log("合约列表长度为:", length);
}```
使用```instruments```参数获取所有合约的列表数据:```exchange.IO("instruments")```,返回交易所所有合约的列表,仅支持实盘。
使用```products```参数获取所有产品的列表数据:
```exchange.IO("products")```,返回交易所所有产品的列表,仅支持实盘。

使用```subscribed```参数获取已订阅的合约数据:
```exchange.IO("subscribed")```,返回已订阅行情的合约,仅支持实盘。

使用```settlement```参数获取结算单数据:
```exchange.IO("settlement")```,用于结算单查询。不加第二个参数时默认返回前一个交易日的数据,添加参数如```20170317```则返回日期为```2017-03-17```的结算单,仅支持实盘。

使用api参数调用底层接口:
优宽量化的CTP(商品期货)终端提供了完整的全API实现。当发明者平台的API无法满足您的功能需求时,可以使用```exchange.IO```函数进行更深层的系统调用,完全兼容官方的API名称。CTP的IO直接扩展函数调用请求将在收到第一个```isLast```标记为```true```的响应包后返回。

CTP协议接口:[CTP协议接口相关资料](https://www.youquant.com/bbs-topic/3756)

以下通过几个简单的例子进行说明:
```javascript
function main() {
    while (!exchange.IO("status")) {
        LogStatus("正在等待与交易服务器连接, " + new Date())
    }

    Log(exchange.IO("api", "ReqQryInvestor"))
}```
```python
def main():
    while not exchange.IO("status"):
        LogStatus("正在等待与交易服务器连接, " + _D())

    Log(exchange.IO("api", "ReqQryInvestor"))```
```cpp
void main() {
    while(exchange.IO("status") == 0) {
        LogStatus("正在等待与交易服务器连接, " + _D());
    }

    Log(exchange.IO("api", "ReqQryInvestor"));
}```
查询投资者信息:
```javascript
function main() {
    // CTP协议建立连接需要一定时间
    Sleep(6000)
    exchange.IO("api", "ReqUserPasswordUpdate", {BrokerID: "9999", UserID: "11111", OldPassword: "oldpass", NewPassword: "newpass"})
}```
```python
def main():
    Sleep(6000)
    exchange.IO("api", "ReqUserPasswordUpdate", {"BrokerID": "9999", "UserID": "11111", "OldPassword": "oldpass", "NewPassword": "newpass"})```
```cpp
void main() {
    Sleep(6000);
    exchange.IO("api", "ReqUserPasswordUpdate", R"({"BrokerID": "9999", "UserID": "11111", "OldPassword": "oldpass", "NewPassword": "newpass"})"_json);
}```
修改密码:
```javascript
function main() {
    // CTP协议建立连接需要时间
    Sleep(6000)
    // 如果再加一个参数值为false表示不等待返回值,只发送请求,第三个参数只需要填充需要的字段,也可省略此参数,如果类型为char,传长度为1的字符串即可
    var r = exchange.IO("api", "ReqQryProduct", {ProductID: "MA"})
    // CTP未登录时会失败
    if (!r) {
        return
    }
    _.each(r, function(item) {
        // IO请求可能返回多个数据包,因此以数组形式返回。遍历数据包的所有数据类型,一个数据包可能包含多个具体数据,具体数据类型的名称请参阅CTP官方文档http://www.sfit.com.cn/5_2_DocumentDown.htm
        _.each(item, function(f) {
            // 取出需要的数据,Name为此数据的类型,Value为此数据的值
            if (f.Name == 'CThostFtdcProductField') {
                // 打印查询的甲醇信息
                Log(f.Value)
            }
        })
    });
}```
```python
def main():
    Sleep(6000)
    r = exchange.IO("api", "ReqQryProduct", {"ProductID": "MA"})
    if not r:
        return

    for r_index in range(len(r)):
        for f_index in range(len(r[r_index])):
            if r[r_index][f_index]["Name"] == 'CThostFtdcProductField':
                Log(r[r_index][f_index]["Value"])```
```cpp
void main() {
    Sleep(6000);
    auto r = exchange.IO("api", "ReqQryProduct", R"({"ProductID": "MA"})"_json);
    if(r == false) {
        return;
    }

    for(auto& ele1 : r.items()) {
        for(auto& ele2 : ele1.value().items()) {
            if(ele2.value()["Name"] == "CThostFtdcProductField") {
                Log(ele2.value()["Value"]);
            }
        }
    }
}```
复杂示例:
```javascript
function main() {
    while (!exchange.IO("status")) {
        LogStatus("正在等待与交易服务器连接, " + new Date())
    }
    // 也可不指定日期
    var r = exchange.IO("api", "ReqQrySettlementInfo", {TradingDay: "20190506"})
    var s = ''
    _.each(r, function(item) {
        _.each(item, function(f) {
            if (f.Name == 'CThostFtdcSettlementInfoField') {
                s += f.Value.Content
            }
        })
    })
    Log(s)
}```
```python
def main():
    while not exchange.IO("status"):
        LogStatus("正在等待与交易服务器连接, " + _D())
    r = exchange.IO("api", "ReqQrySettlementInfo", {"TradingDay": "20190506"})
    s = ''
    for i in range(len(r)):
        for ii in range(len(r[i])):
            if r[i][ii]["Name"] == "CThostFtdcSettlementInfoField":
                s += r[i][ii]["Value"]["Content"]

    Log(s)```
```cpp
void main() {
    while(exchange.IO("status") == 0) {
        LogStatus("正在等待与交易服务器连接, " + _D());
    }
    auto r = exchange.IO("api", "ReqQrySettlementInfo", R"({"TradingDay": "20200311"})"_json);
    string s = "";
    for(auto& ele1 : r.items()) {
        for(auto& ele2 : ele1.value().items()) {
            if(ele2.value()["Name"] == "CThostFtdcSettlementInfoField") {
                s += std::string(ele2.value()["Value"]["Content"]);
            }
        }
    }
    Log(s);
}```
复杂示例:
```javascript
function main() {
    // 等待与交易服务器连接
    while (!exchange.IO("status")) {
        LogStatus("正在等待与交易服务器连接, " + new Date())
        Sleep(1000)
    }

    // 场景1: 限制GetTicker每秒最多调用3次,超限返回null
    exchange.IO("rate", "GetTicker", 3, "1s")

    for (var i = 0; i < 10; i++) {
        if (!exchange.IO("status")) {
            Log("Exchange not ready, waiting...", "#FF0000")
            Sleep(5000)
            continue
        }

        var ticker = exchange.GetTicker("rb888")  // 螺纹钢主力合约
        if (ticker) {
            Log("Call", i+1, "Success, Price:", ticker.Last)
        } else {
            Log("Call", i+1, "Failed: Rate limit exceeded", "#FF0000")
        }
        Sleep(100)
    }
}```
```python
def main():
    # 等待与交易服务器连接
    while not exchange.IO("status"):
        LogStatus("正在等待与交易服务器连接, " + _D())
        Sleep(1000)

    # 场景1: 限制GetTicker每秒最多调用3次,超限返回null
    exchange.IO("rate", "GetTicker", 3, "1s")

    for i in range(10):
        if not exchange.IO("status"):
            Log("Exchange not ready, waiting...", "#FF0000")
            Sleep(5000)
            continue

        ticker = exchange.GetTicker("rb888")  # 螺纹钢主力合约
        if ticker:
            Log("Call", i+1, "Success, Price:", ticker["Last"])
        else:
            Log("Call", i+1, "Failed: Rate limit exceeded", "#FF0000")
        Sleep(100)```
```cpp
// C++暂不支持```
**API限流控制功能:**

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

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

**函数签名:**
```javascript
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

账户类:GetAccount, GetPositions

订单类:GetOrder, GetOrders, GetHistoryOrders

行情类:GetTicker, GetDepth, GetRecords

其他:IO/api

注意事项:

  • 优宽量化支持的市场:商品期货(如螺纹钢rb888、豆粕m888等)
  • 使用API限流功能前,必须先检查exchange.IO("status")确保与交易服务器连接正常
  • 建议配合exchange.IO("mode", 0)立即返回模式使用,避免不必要的等待
  • IO/api的限流仅对exchange.IO("api", ...)调用生效
  • quota模式严格对齐时间窗口,例如设置"1s"时,时间窗口为整秒对齐
  • 使用"delay"参数时,调用时间与日志记录时间可能存在差异,这是正常现象

”`javascript function main() { while (!exchange.IO(“status”)) { Sleep(1000) }

// 场景2: 使用delay参数,超限时自动等待
exchange.IO("rate", "GetTicker", 3, "1s", "delay")

Log("Start testing delay mode with m888 (soybean meal)", "#00FF00")
for (var i = 0; i < 10; i++) {
    if (!exchange.IO("status")) {
        Sleep(5000)
        continue
    }

    var startTime = new Date().getTime()
    var ticker = exchange.GetTicker("m888")  // 豆粕主力合约
    var endTime = new Date().getTime()

    if (ticker) {
        Log("Call", i+1, "Price:", ticker.Last, "Time cost:", endTime - startTime, "ms")
    }
}

} python import time def main(): while not exchange.IO(“status”): Sleep(1000)

# 场景2: 使用delay参数,超限时自动等待
exchange.IO("rate", "GetTicker", 3, "1s", "delay")

Log("Start testing delay mode with m888 (soybean meal)", "#00FF00")
for i in range(10):
    if not exchange.IO("status"):
        Sleep(5000)
        continue

    startTime = time.time() * 1000
    ticker = exchange.GetTicker("m888")  # 豆粕主力合约
    endTime = time.time() * 1000

    if ticker:
        Log("Call", i+1, "Price:", ticker["Last"], "Time cost:", endTime - startTime, "ms")```


// C++暂不支持
限制 GetTicker 每秒最多调用 3 次,超限时等待: “`javascript function main() { while (!exchange.IO(“status”)) { Sleep(1000) }

// 场景3: GetTicker和GetDepth共享限制,合计每秒最多5次
exchange.IO("mode", 0)  // 切换为接口立即返回模式
exchange.IO("rate", "GetTicker,GetDepth", 5, "1s")

for (var i = 0; i < 10; i++) {
    if (!exchange.IO("status")) {
        Sleep(5000)
        continue
    }

    if (i % 2 == 0) {
        var ticker = exchange.GetTicker("i888")  // 铁矿石主力合约
        Log("Call", i+1, "GetTicker:", ticker ? "Success" : "Failed")
    } else {
        var depth = exchange.GetDepth("i888")
        Log("Call", i+1, "GetDepth:", depth ? "Success" : "Failed")
    }
    Sleep(100)
}

} python def main(): while not exchange.IO(“status”): Sleep(1000)

# 场景3: GetTicker和GetDepth共享限制,合计每秒最多5次
exchange.IO("mode", 0)  # 切换为接口立即返回模式
exchange.IO("rate", "GetTicker,GetDepth", 5, "1s")

for i in range(10):
    if not exchange.IO("status"):
        Sleep(5000)
        continue

    if i % 2 == 0:
        ticker = exchange.GetTicker("i888")  # 铁矿石主力合约
        Log("Call", i+1, "GetTicker:", "Success" if ticker else "Failed")
    else:
        depth = exchange.GetDepth("i888")
        Log("Call", i+1, "GetDepth:", "Success" if depth else "Failed")
    Sleep(100)```


// C++暂不支持
限制多个函数联合调用频率: “`javascript function main() { while (!exchange.IO(“status”)) { Sleep(1000) }

// 场景4: 限制所有API调用每秒最多5次
exchange.IO("mode", 0)  // 设置为立即返回模式
exchange.IO("rate", "*", 5, "1s")

var symbols = ["rb888", "m888", "i888"]  // 螺纹钢、豆粕、铁矿石
for (var i = 0; i < 10; i++) {
    if (!exchange.IO("status")) {
        Sleep(5000)
        continue
    }

    var symbol = symbols[i % symbols.length]
    var ticker = exchange.GetTicker(symbol)
    var depth = exchange.GetDepth(symbol)
    var account = exchange.GetAccount()

    Log("Round", i+1, symbol, "Ticker:", ticker ? "✓" : "✗",
        "Depth:", depth ? "✓" : "✗",
        "Account:", account ? "✓" : "✗")
    Sleep(100)
}

} python def main(): while not exchange.IO(“status”): Sleep(1000)

# 场景4: 限制所有API调用每秒最多5次
exchange.IO("mode", 0)  # 设置为立即返回模式
exchange.IO("rate", "*", 5, "1s")

symbols = ["rb888", "m888", "i888"]  # 螺纹钢、豆粕、铁矿石
for i in range(10):
    if not exchange.IO("status"):
        Sleep(5000)
        continue

    symbol = symbols[i % len(symbols)]
    ticker = exchange.GetTicker(symbol)
    depth = exchange.GetDepth(symbol)
    account = exchange.GetAccount()

    Log("Round", i+1, symbol, "Ticker:", "✓" if ticker else "✗",
        "Depth:", "✓" if depth else "✗",
        "Account:", "✓" if account else "✗")
    Sleep(100)```


// C++暂不支持
使用通配符限制所有 API 调用: “`javascript function main() { while (!exchange.IO(“status”)) { Sleep(1000) }

// 场景5: quota模式,严格时间窗口对齐
exchange.IO("mode", 0)  // 切换到立即返回模式
exchange.IO("quota", "GetTicker", 3, "1s")

Log("Testing quota mode with c888 (corn)", "#00FF00")
for (var i = 0; i < 8; i++) {
    if (!exchange.IO("status")) {
        Sleep(5000)
        continue
    }

    var ticker = exchange.GetTicker("c888")  // 玉米主力合约
    Log(_D(), "Call", i+1, ticker ? "Success" : "Quota exceeded")
    Sleep(150)  // 每150ms调用一次,一秒内约6-7次
}

} python def main(): while not exchange.IO(“status”): Sleep(1000)

# 场景5: quota模式,严格时间窗口对齐
exchange.IO("mode", 0)  # 切换到立即返回模式
exchange.IO("quota", "GetTicker", 3, "1s")

Log("Testing quota mode with c888 (corn)", "#00FF00")
for i in range(8):
    if not exchange.IO("status"):
        Sleep(5000)
        continue

    ticker = exchange.GetTicker("c888")  # 玉米主力合约
    Log(_D(), "Call", i+1, "Success" if ticker else "Quota exceeded")
    Sleep(150)  # 每150ms调用一次,一秒内约6-7次```


// C++暂不支持
使用 quota 模式进行严格时间窗口对齐限流:

使用mode参数切换行情模式:

  • exchange.IO(“mode”, 0) 立即返回模式。如果当前尚未接收到交易所最新的行情数据推送,则立即返回旧的行情数据;如果有新数据则返回新数据。 商品期货中的应用,可以参考:exchange.IO函数的wait参数的使用例子。

  • exchange.IO(“mode”, 1) 缓存模式(默认模式)。如果当前尚未收到交易所最新的行情数据(与上一次接口获取的数据比较),则等待接收后再返回;如果调用该函数之前已收到最新的行情数据,则立即返回最新数据。

  • exchange.IO(“mode”, 2) 强制更新模式。进入等待状态,直到接收到交易所下一次最新推送数据后返回。

{@var EXCHANGE_OP_IO_CONTROL}