Global
Version
返回当前系统版本号。
Version()示例
javascript
function main() {
Log("version:", Version())
}
python
def main():
Log("version:", Version())
rust
fn main() {
Log!("version:", Version());
}
c++
void main() {
Log("version:", Version());
}返回值
| 类型 | 描述 |
string | 当前系统版本号,例如: |
备注
系统版本号即托管者程序的版本号。
Sleep
休眠函数,使程序暂停运行一段指定的时间。
Sleep(millisecond)示例
javascript
function main() {
Sleep(1000 * 10) // 等待10秒钟
Log("Waited for 10 seconds")
}
python
def main():
Sleep(1000 * 10)
Log("Waited for 10 seconds")
rust
fn main() {
Sleep(1000 * 10); // 等待10秒钟
Log!("Waited for 10 seconds");
}
c++
void main() {
Sleep(1000 * 10);
Log("Waited for 10 seconds");
}参数
| 名称 | 类型 | 必填 | 描述 |
millisecond | number | 是 |
|
备注
例如,执行Sleep(1000)函数时,程序将休眠1秒。该函数支持小于1毫秒的休眠操作,例如Sleep(0.1)。支持的最小参数为0.000001,即纳秒级休眠,1纳秒等于1e-6毫秒。
在使用Python语言编写策略时,对于轮询间隔、时间等待等操作,应当使用Sleep(millisecond)函数,而不建议使用Python中time库的time.sleep(second)函数。因为策略在回测时若使用time.sleep(second)函数,会使策略程序实际等待一段时间(而非在回测系统的时间序列上跳过),从而导致回测速度非常缓慢。
IsVirtual
用于判断策略的运行环境是否为回测系统。
IsVirtual()示例
javascript
function main() {
if (IsVirtual()) {
Log("Currently in backtest environment.")
} else {
Log("Currently in live trading environment.")
}
}
python
def main():
if IsVirtual():
Log("Currently in backtest environment.")
else:
Log("Currently in live trading environment.")
rust
fn main() {
if IsVirtual() {
Log!("Currently in backtest environment.");
} else {
Log!("Currently in live trading environment.");
}
}
c++
void main() {
if (IsVirtual()) {
Log("Currently in backtest environment.");
} else {
Log("Currently in live trading environment.");
}
}返回值
| 类型 | 描述 |
bool | 当策略运行在回测系统环境中时,返回真值,例如: |
备注
用于判断当前运行环境是否为回测系统,以便兼容回测与实盘环境之间的差异。
发送邮件。
Mail(smtpServer, smtpUsername, smtpPassword, mailTo, title, body)示例
javascript
function main(){
Mail("smtp.163.com", "[email protected]", "password", "[email protected]", "title", "body")
}
python
def main():
Mail("smtp.163.com", "[email protected]", "password", "[email protected]", "title", "body")
rust
fn main() {
Mail("smtp.163.com", "[email protected]", "password", "[email protected]", "title", "body");
}
c++
void main() {
Mail("smtp.163.com", "[email protected]", "password", "[email protected]", "title", "body");
}返回值
| 类型 | 描述 |
bool | 邮件发送成功时返回真值,例如 |
参数
| 名称 | 类型 | 必填 | 描述 |
smtpServer | string | 是 | 用于指定邮件发送方的 |
smtpUsername | string | 是 | 用于指定邮件发送方的邮箱地址。 |
smtpPassword | string | 是 | 用于指定邮件发送方邮箱的 |
mailTo | string | 是 | 用于指定邮件接收方的邮箱地址。 |
title | string | 是 | 邮件标题。 |
body | string | 是 | 邮件正文。 |
参考
备注
smtpPassword参数设置的是SMTP服务的密码,而非邮箱登录密码。
设置smtpServer参数时,如需更改端口,可直接在smtpServer参数中附加端口号。例如:QQ 邮箱的smtp.qq.com:587端口经测试可用。
如果出现报错unencryped connection,则需要修改Mail函数的smtpServer参数,其格式为ssl://xxx.com:xxx。例如,QQ 邮箱SMTP的ssl方式为ssl://smtp.qq.com:465,或使用smtp://xxx.com:xxx。
该函数在回测系统中不起作用。
Mail_Go
Mail函数的异步版本。
Mail_Go(smtpServer, smtpUsername, smtpPassword, mailTo, title, body)示例
javascript
function main() {
var r1 = Mail_Go("smtp.163.com", "[email protected]", "password", "[email protected]", "title", "body")
var r2 = Mail_Go("smtp.163.com", "[email protected]", "password", "[email protected]", "title", "body")
var ret1 = r1.wait()
var ret2 = r2.wait()
Log("ret1:", ret1)
Log("ret2:", ret2)
}
python
# 不支持
c++
// 不支持返回值
| 类型 | 描述 |
object |
|
参数
| 名称 | 类型 | 必填 | 描述 |
smtpServer | string | 是 | 用于指定邮件发送方的 |
smtpUsername | string | 是 | 用于指定邮件发送方的邮箱地址。 |
smtpPassword | string | 是 | 邮件发送方邮箱的 |
mailTo | string | 是 | 用于指定邮件接收方的邮箱地址。 |
title | string | 是 | 邮件标题。 |
body | string | 是 | 邮件正文内容。 |
参考
备注
在回测系统中不起作用。
SetErrorFilter
过滤错误日志。
SetErrorFilter(filters)示例
-
过滤常见错误。
javascriptfunction main() { SetErrorFilter("502:|503:|tcp|character|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF|reused") }pythondef main(): SetErrorFilter("502:|503:|tcp|character|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF|reused")rustfn main() { SetErrorFilter("502:|503:|tcp|character|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF|reused"); }c++void main() { SetErrorFilter("502:|503:|tcp|character|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF|reused"); } -
过滤指定接口的错误信息。
javascriptfunction main() { // 查询一个不存在的订单(id 为 123),故意触发接口报错 var order = exchange.GetOrder("123") Log(order) // 过滤 http 502 错误和 GetOrder 接口错误;设置错误过滤后,第二次调用 GetOrder 将不再报错 SetErrorFilter("502:|GetOrder") order = exchange.GetOrder("123") Log(order) }pythondef main(): order = exchange.GetOrder("123") Log(order) SetErrorFilter("502:|GetOrder") order = exchange.GetOrder("123") Log(order)rustfn main() { // 查询一个不存在的订单(id 为 123),故意触发接口报错 let orderId = OrderId { S: "123".to_string(), ..Default::default() }; let order = exchange.GetOrder(&orderId); Log!(order); // 过滤 http 502 错误和 GetOrder 接口错误;设置错误过滤后,第二次调用 GetOrder 将不再报错 SetErrorFilter("502:|GetOrder"); let order = exchange.GetOrder(&orderId); Log!(order); }c++void main() { TId orderId; Order order = exchange.GetOrder(orderId); Log(order); SetErrorFilter("502:|GetOrder"); order = exchange.GetOrder(orderId); Log(order); }
参数
| 名称 | 类型 | 必填 | 描述 |
filters | string | 是 | 正则表达式字符串。 |
备注
与此正则表达式匹配的错误日志将不再上传至日志系统。该函数可多次调用(无次数限制)以设置多个过滤条件,多次设置的正则表达式会累积并同时生效。可传入空字符串以重置用于过滤错误日志的正则表达式:SetErrorFilter("")。被过滤的日志将不再写入托管者目录下对应实盘 Id 的数据库文件中,从而防止因频繁报错导致数据库文件膨胀。
GetPid
获取实盘进程的 ID。
GetPid()示例
javascript
function main(){
var id = GetPid()
Log(id)
}
python
def main():
id = GetPid()
Log(id)
rust
fn main() {
let id = GetPid();
Log!(id);
}
c++
void main() {
auto id = GetPid();
Log(id);
}返回值
| 类型 | 描述 |
string | 返回实盘进程的 ID。 |
GetLastError
获取最近一次的错误信息。
GetLastError()示例
javascript
function main(){
// 由于不存在编号为 123 的订单,因此会触发错误
exchange.GetOrder("123")
var error = GetLastError()
Log(error)
}
python
def main():
exchange.GetOrder("123")
error = GetLastError()
Log(error)
rust
fn main() {
// 由于不存在编号为 123 的订单,因此会触发错误
// Rust 的 GetOrder 接受 &OrderId 参数,字符串 id 需放在 S 字段中
let id = OrderId { S: "123".to_string(), ..Default::default() };
let _ = exchange.GetOrder(&id);
let error = GetLastError();
Log!(error);
}
c++
void main() {
// 订单 ID 的类型为 TId,因此不能传入字符串;此处下单一个不符合交易所规范的订单以触发错误
exchange.GetOrder(exchange.Buy(1, 1));
auto error = GetLastError();
Log(error);
}返回值
| 类型 | 描述 |
string | 最近一次的错误信息。 |
备注
该函数在回测系统中不起作用。
GetCommand
获取策略的交互命令。
GetCommand()示例
-
检测交互命令,并在检测到交互命令时使用
Log函数将其输出。javascriptfunction main(){ while(true) { var cmd = GetCommand() if (cmd) { Log(cmd) } Sleep(1000) } }pythondef main(): while True: cmd = GetCommand() if cmd: Log(cmd) Sleep(1000)rustfn main() { loop { // Rust 的 GetCommand() 需要传入超时参数(毫秒),返回 Option<String>,无命令时为 None if let Some(cmd) = GetCommand(0) { Log!(cmd); } Sleep(1000); } }c++void main() { while(true) { auto cmd = GetCommand(); if(cmd != "") { Log(cmd); } Sleep(1000); } } -
例如,在策略交互控件中添加一个不带输入框的控件,将其命名为
buy,控件描述信息为买入,这是一个按钮控件;再添加一个带输入框的控件,将其命名为sell,控件描述信息为卖出,这是一个由按钮和输入框组合而成的交互控件。在策略中编写交互代码,以响应不同的交互控件:javascriptfunction main() { while (true) { LogStatus(_D()) var cmd = GetCommand() if (cmd) { Log("cmd:", cmd) var arr = cmd.split(":") if (arr[0] == "buy") { Log("Buy, this control has no quantity") } else if (arr[0] == "sell") { Log("Sell, this control has quantity:", arr[1]) } else { Log("Other control triggered:", arr) } } Sleep(1000) } }pythondef main(): while True: LogStatus(_D()) cmd = GetCommand() if cmd: Log("cmd:", cmd) arr = cmd.split(":") if arr[0] == "buy": Log("Buy, this control has no quantity") elif arr[0] == "sell": Log("Sell, this control has quantity:", arr[1]) else: Log("Other control triggered:", arr) Sleep(1000)rustfn main() { loop { LogStatus!(_D(None)); if let Some(cmd) = GetCommand(0) { Log!("cmd:", cmd); let arr: Vec<&str> = cmd.split(':').collect(); if arr[0] == "buy" { Log!("Buy, this control has no quantity"); } else if arr[0] == "sell" { Log!("Sell, this control has quantity:", arr[1]); } else { Log!("Other control triggered:", arr); } } Sleep(1000); } }c++#include <iostream> #include <sstream> #include <string> #include <vector> using namespace std; void split(const string& s,vector<string>& sv,const char flag = ' ') { sv.clear(); istringstream iss(s); string temp; while (getline(iss, temp, flag)) { sv.push_back(temp); } return; } void main() { while(true) { LogStatus(_D()); auto cmd = GetCommand(); if (cmd != "") { vector<string> arr; split(cmd, arr, ':'); if(arr[0] == "buy") { Log("Buy, this control has no quantity"); } else if (arr[0] == "sell") { Log("Sell, this control has quantity:", arr[1]); } else { Log("Other control triggered:", arr); } } Sleep(1000); } }
返回值
| 类型 | 描述 |
string | 返回的命令格式为 |
备注
该函数在回测系统中无效。
GetMeta
获取在生成策略注册码时写入的Meta值。
GetMeta()示例
应用场景范例:使用Meta限制策略可操作的资产数量。
javascript
function main() {
// 策略允许的计价币最大资产数值
var maxBaseCurrency = null
// 获取创建注册码时的元数据
var level = GetMeta()
// 检测Meta对应的条件
if (level == "level1") {
// -1为不限制
maxBaseCurrency = -1
} else if (level == "level2") {
maxBaseCurrency = 10
} else if (level == "level3") {
maxBaseCurrency = 1
} else {
maxBaseCurrency = 0.5
}
while(1) {
Sleep(1000)
var ticker = exchange.GetTicker()
// 检测资产数值
var acc = exchange.GetAccount()
if (maxBaseCurrency != -1 && maxBaseCurrency < acc.Stocks + acc.FrozenStocks) {
// 停止执行策略交易逻辑
LogStatus(_D(), "level:", level, "Position exceeds registration code limit, strategy trading logic will not execute!")
continue
}
// 其它交易逻辑
// 正常输出状态栏信息
LogStatus(_D(), "level:", level, "Strategy running normally! ticker data:\n", ticker)
}
}
python
def main():
maxBaseCurrency = null
level = GetMeta()
if level == "level1":
maxBaseCurrency = -1
elif level == "level2":
maxBaseCurrency = 10
elif level == "level3":
maxBaseCurrency = 1
else:
maxBaseCurrency = 0.5
while True:
Sleep(1000)
ticker = exchange.GetTicker()
acc = exchange.GetAccount()
if maxBaseCurrency != -1 and maxBaseCurrency < acc["Stocks"] + acc["FrozenStocks"]:
LogStatus(_D(), "level:", level, "Position exceeds registration code limit, strategy trading logic will not execute!")
continue
# 其它交易逻辑
# 正常输出状态栏信息
LogStatus(_D(), "level:", level, "Strategy running normally! ticker data:\n", ticker)
rust
fn main() {
// 策略允许的计价币最大资产数值
let maxBaseCurrency;
// 获取创建注册码时的元数据,Rust 的 GetMeta() 返回 JsonValue 类型
let meta = GetMeta();
let level = meta.as_str().unwrap_or("");
// 检测Meta对应的条件
if level == "level1" {
// -1为不限制
maxBaseCurrency = -1.0;
} else if level == "level2" {
maxBaseCurrency = 10.0;
} else if level == "level3" {
maxBaseCurrency = 1.0;
} else {
maxBaseCurrency = 0.5;
}
loop {
Sleep(1000);
let ticker = exchange.GetTicker(None).unwrap();
// 检测资产数值
let acc = exchange.GetAccount().unwrap();
if maxBaseCurrency != -1.0 && maxBaseCurrency < acc.Stocks + acc.FrozenStocks {
// 停止执行策略交易逻辑
LogStatus!(_D(None), "level:", level, "Position exceeds registration code limit, strategy trading logic will not execute!");
continue;
}
// 其它交易逻辑
// 正常输出状态栏信息
LogStatus!(_D(None), "level:", level, "Strategy running normally! ticker data:\n", ticker);
}
}
c++
void main() {
auto maxBaseCurrency = 0.0;
auto level = GetMeta();
if (level == "level1") {
maxBaseCurrency = -1;
} else if (level == "level2") {
maxBaseCurrency = 10;
} else if (level == "level3") {
maxBaseCurrency = 1;
} else {
maxBaseCurrency = 0.5;
}
while(1) {
Sleep(1000);
auto ticker = exchange.GetTicker();
auto acc = exchange.GetAccount();
if (maxBaseCurrency != -1 && maxBaseCurrency < acc.Stocks + acc.FrozenStocks) {
// 停止执行策略交易逻辑
LogStatus(_D(), "level:", level, "Position exceeds registration code limit, strategy trading logic will not execute!");
continue;
}
// 其它交易逻辑
// 正常输出状态栏信息
LogStatus(_D(), "level:", level, "Strategy running normally! ticker data:\n", ticker);
}
}返回值
| 类型 | 描述 |
string |
|
备注
应用场景:需要对不同的策略租用者进行资金限制。生成注册码时设置的Meta值长度不能超过190个字符。GetMeta()函数仅支持实盘,在回测系统中不起作用。如果生成策略注册码时未设置元数据(Meta),GetMeta()函数将返回空值。
Dial
用于原始 Socket 访问,支持 tcp、udp、tls、unix 协议。支持 4 种主流通信协议:mqtt、nats、amqp、kafka。同时支持连接数据库,可用的数据库包括:sqlite3、mysql、postgres、clickhouse。
Dial(address)
Dial(address, timeout)
Dial(address, options)示例
-
Dial 函数调用示例:
javascriptfunction main(){ // Dial 支持 tcp://、udp://、tls://、unix:// 协议,可传入一个参数指定超时秒数 var client = Dial("tls://www.baidu.com:443") if (client) { // write 可额外传入一个数字参数指定超时,返回成功发送的字节数 client.write("GET / HTTP/1.1\nConnection: Closed\n\n") while (true) { // read 可额外传入一个数字参数指定超时,单位:毫秒;返回 null 表示出错、超时或 socket 已关闭 var buf = client.read() if (!buf) { break } Log(buf) } client.close() } }pythondef main(): client = Dial("tls://www.baidu.com:443") if client: client.write("GET / HTTP/1.1\nConnection: Closed\n\n") while True: buf = client.read() if not buf: break Log(buf) client.close()rustfn main() { // Dial 支持 tcp://、udp://、tls://、unix:// 协议,可使用 Dial::new(addr, timeout) 指定超时秒数 let mut client = Dial("tls://www.baidu.com:443"); if client.Valid() { // write 的第二个数字参数用于指定超时,返回成功发送的字节数 client.write("GET / HTTP/1.1\nConnection: Closed\n\n", 0); loop { // read 的数字参数用于指定超时,单位:毫秒;返回空字符串表示出错、超时或 socket 已关闭 let buf = client.read(0); if buf == "" { break; } Log!(buf); } client.close(); } }c++void main() { auto client = Dial("tls://www.baidu.com:443"); if(client.Valid) { client.write("GET / HTTP/1.1\nConnection: Closed\n\n"); while(true) { auto buf = client.read(); if(buf == "") { break; } Log(buf); } client.close(); } } -
访问币安(Binance)的 WebSocket 行情接口:
javascriptfunction main() { LogStatus("Connecting...") // 访问币安的 WebSocket 接口 var client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr") if (!client) { Log("Connection failed, exiting") return } while (true) { // read 仅返回调用 read 之后接收到的数据 var buf = client.read() if (!buf) { break } var table = { type: 'table', title: '行情图表', cols: ['币种', '最高', '最低', '买一', '卖一', '最后成交价', '成交量', '更新时间'], rows: [] } var obj = JSON.parse(buf) _.each(obj, function(ticker) { table.rows.push([ticker.s, ticker.h, ticker.l, ticker.b, ticker.a, ticker.c, ticker.q, _D(ticker.E)]) }) LogStatus('`' + JSON.stringify(table) + '`') } client.close() }pythonimport json def main(): LogStatus("Connecting...") client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr") if not client: Log("Connection failed, exiting") return while True: buf = client.read() if not buf: break table = { "type" : "table", "title" : "行情图表", "cols" : ["币种", "最高", "最低", "买一", "卖一", "最后成交价", "成交量", "更新时间"], "rows" : [] } obj = json.loads(buf) for i in range(len(obj)): table["rows"].append([obj[i]["s"], obj[i]["h"], obj[i]["l"], obj[i]["b"], obj[i]["a"], obj[i]["c"], obj[i]["q"], _D(int(obj[i]["E"]))]) LogStatus('`' + json.dumps(table) + '`') client.close()rustfn main() { LogStatus!("Connecting..."); // 访问币安的 WebSocket 接口 let mut client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr"); if !client.Valid() { Log!("Connection failed, exiting"); return; } loop { // read 仅返回调用 read 之后接收到的数据 let buf = client.read(0); if buf == "" { break; } let obj = JSONParse(&buf).unwrap(); // Rust SDK 没有 JSON 序列化功能,此处使用字符串拼接来构造状态栏表格的 JSON 文本 let mut rows = String::new(); if let Some(arr) = obj.as_array() { for ticker in arr { if !rows.is_empty() { rows += ","; } rows += &format!(r#"["{}","{}","{}","{}","{}","{}","{}","{}"]"#, ticker["s"].as_str().unwrap_or(""), ticker["h"].as_str().unwrap_or(""), ticker["l"].as_str().unwrap_or(""), ticker["b"].as_str().unwrap_or(""), ticker["a"].as_str().unwrap_or(""), ticker["c"].as_str().unwrap_or(""), ticker["q"].as_str().unwrap_or(""), _D(ticker["E"].as_i64().unwrap_or(0))); } } let table = format!(r#"{{"type":"table","title":"行情图表","cols":["币种","最高","最低","买一","卖一","最后成交价","成交量","更新时间"],"rows":[{}]}}"#, rows); LogStatus!(format!("`{}`", table)); } client.close(); }c++void main() { LogStatus("Connecting..."); auto client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr"); if(!client.Valid) { Log("Connection failed, exiting"); return; } while(true) { auto buf = client.read(); if(buf == "") { break; } json table = R"({ "type" : "table", "title" : "行情图表", "cols" : ["币种", "最高", "最低", "买一", "卖一", "最后成交价", "成交量", "更新时间"], "rows" : [] })"_json; json obj = json::parse(buf); for(auto& ele : obj.items()) { table["rows"].push_back({ele.value()["s"], ele.value()["h"], ele.value()["l"], ele.value()["b"], ele.value()["a"], ele.value()["c"], ele.value()["q"], _D(ele.value()["E"])}); } LogStatus("`" + table.dump() + "`"); } client.close(); } -
访问币安(Binance)的 WebSocket 接口,并设置 wss 请求头。
javascriptfunction main() { let options = {"headers": {"X-MBX-APIKEY": "your access key"}} let random = `fmz${UnixNano()}` let ts = new Date().getTime() let secretKey = "your secret key" let topic = "com_announcement_en" let payload = `random=${random}&topic=${topic}&recvWindow=30000×tamp=${ts}` let signature = Encode("sha256", "string", "hex", payload, "string", secretKey) let query = `?${payload}&signature=${signature}` Log("query:", query) let conn = Dial(`wss://api.binance.com/sapi/wss${query}`, options) for (var i = 0 ; i < 10 ; i++) { let ret = conn.read() Log(ret) } }pythonimport time def main(): options = {"headers": {"X-MBX-APIKEY": "your access key"}} random = "fmz" + str(UnixNano()) ts = int(time.time() * 1000) secretKey = "your secret key" topic = "com_announcement_en" payload = f"random={random}&topic={topic}&recvWindow=30000×tamp={ts}" signature = Encode("sha256", "string", "hex", payload, "string", secretKey) query = f"?{payload}&signature={signature}" Log("query:", query) conn = Dial(f"wss://api.binance.com/sapi/wss{query}", options) for i in range(10): ret = conn.read() Log(ret)rustfn main() { // Rust 中使用 Dial::with_options(),以 JSON 字符串形式传入 options 设置请求头 let options = r#"{"headers": {"X-MBX-APIKEY": "your access key"}}"#; let random = format!("fmz{}", UnixNano()); let ts = Unix() * 1000; let secretKey = "your secret key"; let topic = "com_announcement_en"; let payload = format!("random={}&topic={}&recvWindow=30000×tamp={}", random, topic, ts); let signature = Encode("sha256", "string", "hex", &payload, "string", secretKey); let query = format!("?{}&signature={}", payload, signature); Log!("query:", query); let mut conn = Dial::with_options(&format!("wss://api.binance.com/sapi/wss{}", query), options); for _i in 0..10 { let ret = conn.read(0); Log!(ret); } }c++// 暂不支持 -
访问 OKX 的 WebSocket 行情接口:
javascriptvar ws = null function main(){ var param = { "op": "subscribe", "args": [{ "channel": "tickers", "instId": "BTC-USDT" }] } // 调用 Dial 函数时,指定 reconnect=true 即可启用重连模式,指定 payload 即为重连时发送的消息。当 WebSocket 连接断开后,将自动重连并自动发送该消息 ws = Dial("wss://ws.okx.com:8443/ws/v5/public|compress=gzip_raw&mode=recv&reconnect=true&payload="+ JSON.stringify(param)) if(ws){ var pingCyc = 1000 * 20 var lastPingTime = new Date().getTime() while(true){ var nowTime = new Date().getTime() var ret = ws.read() Log("ret:", ret) if(nowTime - lastPingTime > pingCyc){ var retPing = ws.write("ping") lastPingTime = nowTime Log("Sending: ping", "#FF0000") } LogStatus("Current time:", _D()) Sleep(1000) } } } function onexit() { ws.close() Log("Exiting") }pythonimport json import time ws = None def main(): global ws param = { "op": "subscribe", "args": [{ "channel": "tickers", "instId": "BTC-USDT" }] } ws = Dial("wss://ws.okx.com:8443/ws/v5/public|compress=gzip_raw&mode=recv&reconnect=true&payload=" + json.dumps(param)) if ws: pingCyc = 1000 * 20 lastPingTime = time.time() * 1000 while True: nowTime = time.time() * 1000 ret = ws.read() Log("ret:", ret) if nowTime - lastPingTime > pingCyc: retPing = ws.write("ping") lastPingTime = nowTime Log("Sending: ping", "#FF0000") LogStatus("Current time:", _D()) Sleep(1000) def onexit(): ws.close() Log("Exiting")rustfn main() { let param = r#"{"op":"subscribe","args":[{"channel":"tickers","instId":"BTC-USDT"}]}"#; // 调用 Dial 函数时,指定 reconnect=true 即可启用重连模式,指定 payload 即为重连时发送的消息。当 WebSocket 连接断开后,将自动重连并自动发送该消息 let mut ws = Dial(&format!("wss://ws.okx.com:8443/ws/v5/public|compress=gzip_raw&mode=recv&reconnect=true&payload={}", param)); if ws.Valid() { let pingCyc = 1000 * 20; let mut lastPingTime = Unix() * 1000; loop { let nowTime = Unix() * 1000; let ret = ws.read(0); Log!("ret:", ret); if nowTime - lastPingTime > pingCyc { let retPing = ws.write("ping", 0); lastPingTime = nowTime; Log!("Sending: ping", "#FF0000"); } LogStatus!("Current time:", _D(None)); Sleep(1000); } } // 在 Rust 中,连接对象在离开作用域时会自动关闭,也可以显式调用 ws.close() }c++auto objWS = Dial("wss://ws.okx.com:8443/ws/v5/public|compress=gzip_raw&mode=recv&reconnect=true"); void main() { json param = R"({ "op": "subscribe", "args": [{ "channel": "tickers", "instId": "BTC-USDT" }] })"_json; objWS.write(param.dump()); if(objWS.Valid) { uint64_t pingCyc = 1000 * 20; uint64_t lastPingTime = Unix() * 1000; while(true) { uint64_t nowTime = Unix() * 1000; auto ret = objWS.read(); Log("ret:", ret); if(nowTime - lastPingTime > pingCyc) { auto retPing = objWS.write("ping"); lastPingTime = nowTime; Log("Sending: ping", "#FF0000"); } LogStatus("Current time:", _D()); Sleep(1000); } } } void onexit() { objWS.close(); Log("Exiting"); } -
访问火币交易所的 WebSocket 行情接口:
javascriptvar ws = null function main(){ var param = {"sub": "market.btcusdt.detail", "id": "id1"} ws = Dial("wss://api.huobi.pro/ws|compress=gzip&mode=recv&reconnect=true&payload="+ JSON.stringify(param)) if(ws){ while(1){ var ret = ws.read() Log("ret:", ret) // 响应心跳包操作 try { var jsonRet = JSON.parse(ret) if(typeof(jsonRet.ping) == "number") { var strPong = JSON.stringify({"pong" : jsonRet.ping}) ws.write(strPong) Log("Responding to ping, sending pong:", strPong, "#FF0000") } } catch(e) { Log("e.name:", e.name, "e.stack:", e.stack, "e.message:", e.message) } LogStatus("Current time:", _D()) Sleep(1000) } } } function onexit() { ws.close() Log("Executing ws.close()") }pythonimport json ws = None def main(): global ws param = {"sub" : "market.btcusdt.detail", "id" : "id1"} ws = Dial("wss://api.huobi.pro/ws|compress=gzip&mode=recv&reconnect=true&payload=" + json.dumps(param)) if ws: while True: ret = ws.read() Log("ret:", ret) # 响应心跳包操作 try: jsonRet = json.loads(ret) if "ping" in jsonRet and type(jsonRet["ping"]) == int: strPong = json.dumps({"pong" : jsonRet["ping"]}) ws.write(strPong) Log("Responding to ping, sending pong:", strPong, "#FF0000") except Exception as e: Log("e:", e) LogStatus("Current time:", _D()) Sleep(1000) def onexit(): ws.close() Log("Executing ws.close()")rustfn main() { let param = r#"{"sub":"market.btcusdt.detail","id":"id1"}"#; let mut ws = Dial(&format!("wss://api.huobi.pro/ws|compress=gzip&mode=recv&reconnect=true&payload={}", param)); if ws.Valid() { loop { let ret = ws.read(0); Log!("ret:", ret); // 响应心跳包操作,Rust 中使用 JSONParse() 解析,解析失败返回 None if let Some(jsonRet) = JSONParse(&ret) { if jsonRet["ping"].is_number() { let strPong = format!(r#"{{"pong":{}}}"#, jsonRet["ping"].as_i64().unwrap_or(0)); ws.write(&strPong, 0); Log!("Responding to ping, sending pong:", strPong, "#FF0000"); } } LogStatus!("Current time:", _D(None)); Sleep(1000); } } // Rust 中连接对象在离开作用域时自动关闭,也可以显式调用 ws.close() }c++using namespace std; void main() { json param = R"({"sub" : "market.btcusdt.detail", "id" : "id1"})"_json; auto ws = Dial("wss://api.huobi.pro/ws|compress=gzip&mode=recv&reconnect=true&payload=" + param.dump()); if(ws.Valid) { while(true) { auto ret = ws.read(); Log("ret:", ret); // 响应心跳包操作 try { auto jsonRet = json::parse(ret); if(jsonRet["ping"].is_number()) { json pong = R"({"pong" : 0})"_json; pong["pong"] = jsonRet["ping"]; auto strPong = pong.dump(); ws.write(strPong); Log("Responding to ping, sending pong:", strPong, "#FF0000"); } } catch(exception &e) { Log("e:", e.what()); } LogStatus("Current time:", _D()); Sleep(1000); } } } void onexit() { // ws.close(); Log("Executing ws.close()"); } -
访问 OKX 的 WebSocket 验证接口:
javascriptfunction getLogin(pAccessKey, pSecretKey, pPassphrase) { // 签名函数,用于生成登录请求 var ts = (new Date().getTime() / 1000).toString() var login = { "op": "login", "args":[{ "apiKey" : pAccessKey, "passphrase" : pPassphrase, "timestamp" : ts, "sign" : exchange.Encode("sha256", "string", "base64", ts + "GET" + "/users/self/verify", "string", pSecretKey) }] } return login } var client_private = null function main() { // 由于 read 函数设置了超时,需过滤超时报错,否则会产生冗余的错误输出 SetErrorFilter("timeout") // 持仓频道的订阅信息 var posSubscribe = { "op": "subscribe", "args": [{ "channel": "positions", "instType": "ANY" }] } var accessKey = "xxx" var secretKey = "xxx" var passphrase = "xxx" client_private = Dial("wss://ws.okx.com:8443/ws/v5/private") client_private.write(JSON.stringify(getLogin(accessKey, secretKey, passphrase))) Sleep(3000) // 登录后不能立即订阅私有频道,需等待服务器响应 client_private.write(JSON.stringify(posSubscribe)) if (client_private) { var lastPingTS = new Date().getTime() while (true) { var buf = client_private.read(-1) if (buf) { Log(buf) } // 检测到连接断开后重连 if (buf == "" && client_private.write(JSON.stringify(posSubscribe)) == 0) { Log("Detected disconnection, closing connection, reconnecting") client_private.close() client_private = Dial("wss://ws.okx.com:8443/ws/v5/private") client_private.write(JSON.stringify(getLogin(accessKey, secretKey, passphrase))) Sleep(3000) client_private.write(JSON.stringify(posSubscribe)) } // 发送心跳包 var nowPingTS = new Date().getTime() if (nowPingTS - lastPingTS > 10 * 1000) { client_private.write("ping") lastPingTS = nowPingTS } } } } function onexit() { var ret = client_private.close() Log("Connection closed!", ret) }pythonimport json import time def getLogin(pAccessKey, pSecretKey, pPassphrase): ts = str(time.time()) login = { "op": "login", "args":[{ "apiKey" : pAccessKey, "passphrase" : pPassphrase, "timestamp" : ts, "sign" : exchange.Encode("sha256", "string", "base64", ts + "GET" + "/users/self/verify", "string", pSecretKey) }] } return login client_private = None def main(): global client_private SetErrorFilter("timeout") posSubscribe = { "op": "subscribe", "args": [{ "channel": "positions", "instType": "ANY" }] } accessKey = "xxx" secretKey = "xxx" passphrase = "xxx" client_private = Dial("wss://ws.okx.com:8443/ws/v5/private") client_private.write(json.dumps(getLogin(accessKey, secretKey, passphrase))) Sleep(3000) client_private.write(json.dumps(posSubscribe)) if client_private: lastPingTS = time.time() * 1000 while True: buf = client_private.read(-1) if buf: Log(buf) if buf == "" and client_private.write(json.dumps(posSubscribe)) == 0: Log("Detected disconnection, closing connection, reconnecting") ret = client_private.close() client_private = Dial("wss://ws.okx.com:8443/ws/v5/private") client_private.write(json.dumps(getLogin(accessKey, secretKey, passphrase))) Sleep(3000) client_private.write(json.dumps(posSubscribe)) nowPingTS = time.time() * 1000 if nowPingTS - lastPingTS > 10 * 1000: client_private.write("ping") lastPingTS = nowPingTS def onexit(): ret = client_private.close() Log("Connection closed!", ret)rustfn getLogin(pAccessKey: &str, pSecretKey: &str, pPassphrase: &str) -> String { // 签名函数,用于生成登录请求。Rust 中没有 exchange.Encode 成员函数,因此使用全局 Encode 函数计算签名 let ts = format!("{}", Unix()); let sign = Encode("sha256", "string", "base64", &format!("{}GET/users/self/verify", ts), "string", pSecretKey); format!(r#"{{"op":"login","args":[{{"apiKey":"{}","passphrase":"{}","timestamp":"{}","sign":"{}"}}]}}"#, pAccessKey, pPassphrase, ts, sign) } fn main() { // 由于 read 函数设置了超时,需过滤超时报错,否则会产生冗余的错误输出 SetErrorFilter("timeout"); // 持仓频道的订阅信息 let posSubscribe = r#"{"op":"subscribe","args":[{"channel":"positions","instType":"ANY"}]}"#; let accessKey = "xxx"; let secretKey = "xxx"; let passphrase = "xxx"; let mut client_private = Dial("wss://ws.okx.com:8443/ws/v5/private"); client_private.write(&getLogin(accessKey, secretKey, passphrase), 0); Sleep(3000); // 登录后不能立即订阅私有频道,需等待服务器响应 client_private.write(posSubscribe, 0); if client_private.Valid() { let mut lastPingTS = Unix() * 1000; loop { let buf = client_private.read(-1); if buf != "" { Log!(buf); } // 检测到连接断开后重连 if buf == "" && client_private.write(posSubscribe, 0) == 0 { Log!("Detected disconnection, closing connection, reconnecting"); client_private.close(); client_private = Dial("wss://ws.okx.com:8443/ws/v5/private"); client_private.write(&getLogin(accessKey, secretKey, passphrase), 0); Sleep(3000); client_private.write(posSubscribe, 0); } // 发送心跳包 let nowPingTS = Unix() * 1000; if nowPingTS - lastPingTS > 10 * 1000 { client_private.write("ping", 0); lastPingTS = nowPingTS; } } } }c++auto client_private = Dial("wss://ws.okx.com:8443/ws/v5/private"); json getLogin(string pAccessKey, string pSecretKey, string pPassphrase) { auto ts = std::to_string(Unix()); json login = R"({ "op": "login", "args": [{ "apiKey": "", "passphrase": "", "timestamp": "", "sign": "" }] })"_json; login["args"][0]["apiKey"] = pAccessKey; login["args"][0]["passphrase"] = pPassphrase; login["args"][0]["timestamp"] = ts; login["args"][0]["sign"] = exchange.Encode("sha256", "string", "base64", ts + "GET" + "/users/self/verify", "string", pSecretKey); return login; } void main() { SetErrorFilter("timeout"); json posSubscribe = R"({ "op": "subscribe", "args": [{ "channel": "positions", "instType": "ANY" }] })"_json; auto accessKey = "xxx"; auto secretKey = "xxx"; auto passphrase = "xxx"; client_private.write(getLogin(accessKey, secretKey, passphrase).dump()); Sleep(3000); client_private.write(posSubscribe.dump()); if (client_private.Valid) { uint64_t lastPingTS = Unix() * 1000; while (true) { auto buf = client_private.read(-1); if (buf != "") { Log(buf); } if (buf == "") { if (client_private.write(posSubscribe.dump()) == 0) { Log("Detected disconnection, closing connection, reconnecting"); client_private.close(); client_private = Dial("wss://ws.okx.com:8443/ws/v5/private"); client_private.write(getLogin(accessKey, secretKey, passphrase).dump()); Sleep(3000); client_private.write(posSubscribe.dump()); } } uint64_t nowPingTS = Unix() * 1000; if (nowPingTS - lastPingTS > 10 * 1000) { client_private.write("ping"); lastPingTS = nowPingTS; } } } } void onexit() { client_private.close(); Log("Exiting"); } -
访问 CoinEx 的 WebSocket 验证接口:
javascriptvar conn = null function main() { var accessKey = "your accessKey" var ts = new Date().getTime() var signature = exchange.Encode("sha256", "string", "hex", String(ts), "string", "{{secretkey}}") Log("signature:", signature) var payload = { "id": 1, "method": "server.sign", "params": { "access_id": accessKey, "signed_str": signature, "timestamp": ts, } } Log(`JSON.stringify(payload):`, JSON.stringify(payload)) conn = Dial("wss://socket.coinex.com/v2/futures|compress=gzip&mode=recv&payload=" + JSON.stringify(payload)) if (!conn) { throw "stop" } Log("Dial ... ", conn.read()) // 订阅持仓推送 conn.write(JSON.stringify({ "method": "position.subscribe", "params": {"market_list": ["BTCUSDT"]}, "id": 1 })) while (true) { var msg = conn.read() if (msg) { Log("msg:", msg) } } } function onexit() { conn.close() }python// 略rustfn main() { let accessKey = "your accessKey"; let ts = Unix() * 1000; // Rust 没有 exchange.Encode 成员函数,无法使用 {{secretkey}} 模板替换,使用全局 Encode 函数直接传入秘钥计算签名 let signature = Encode("sha256", "string", "hex", &format!("{}", ts), "string", "your secretKey"); Log!("signature:", signature); // Rust SDK 没有 JSON 序列化功能,使用字符串拼接构造 payload 的 JSON 文本 let payload = format!(r#"{{"id":1,"method":"server.sign","params":{{"access_id":"{}","signed_str":"{}","timestamp":{}}}}}"#, accessKey, signature, ts); Log!("payload:", payload); let mut conn = Dial(&format!("wss://socket.coinex.com/v2/futures|compress=gzip&mode=recv&payload={}", payload)); if !conn.Valid() { Panic!("stop"); } Log!("Dial ... ", conn.read(0)); // 订阅持仓推送 conn.write(r#"{"method":"position.subscribe","params":{"market_list":["BTCUSDT"]},"id":1}"#, 0); loop { let msg = conn.read(0); if msg != "" { Log!("msg:", msg); } } }c++// 略 -
以下示例演示如何访问 MEXC 交易所的
Websocket接口,订阅public.aggre.deals.v3.api.pb频道,并使用protobuf.js解码二进制数据:javascriptlet strPushDataV3ApiWrapper = `syntax = "proto3"; option java_package = "com.mxc.push.common.protobuf"; option optimize_for = SPEED; option java_multiple_files = true; option java_outer_classname = "PushDataV3ApiWrapperProto"; message PublicAggreDealsV3Api { repeated PublicAggreDealsV3ApiItem deals = 1; string eventType = 2; } message PublicAggreDealsV3ApiItem { string price = 1; string quantity = 2; int32 tradeType = 3; int64 time = 4; } message PushDataV3ApiWrapper { string channel = 1; oneof body { PublicAggreDealsV3Api publicAggreDeals = 314; } optional string symbol = 3; optional string symbolId = 4; optional int64 createTime = 5; optional int64 sendTime = 6; }` let code = HttpQuery("https://cdnjs.cloudflare.com/ajax/libs/protobufjs/7.5.3/protobuf.js") let exports = {} let module = { exports } new Function("module", "exports", code)(module, exports) let protobuf = module.exports function main() { const PushDataV3ApiWrapper = protobuf.parse(strPushDataV3ApiWrapper).root.lookupType("PushDataV3ApiWrapper") var payload = { "method": "SUBSCRIPTION", "params": [ "[email protected]@100ms@BTCUSDT" ] } // proxy=socks5://x.x.x.x:xxxx var conn = Dial("wss://wbs-api.mexc.com/ws|payload=" + JSON.stringify(payload)) var data = null while (true) { var ret = conn.read() if (ret) { const uint8arrayData = new Uint8Array(ret) const message = PushDataV3ApiWrapper.decode(uint8arrayData) data = PushDataV3ApiWrapper.toObject(message, { longs: String, enums: String, bytes: String, defaults: true, arrays: true, objects: true }) Log("data:", data) } LogStatus(_D(), data) } }python# 可以使用 Python 中相应的库实现编码与解码。c++// 略 -
Dial函数连接数据库时返回的连接对象具有2个独有的方法函数:
-
exec(sqlString):用于执行SQL语句,用法与DBExec()函数类似。 -
fd():该函数返回一个句柄(例如句柄变量为handle),用于在其它线程中重连。即使由Dial创建的连接对象已通过close()函数关闭,也可将该句柄传入Dial()函数(例如Dial(handle))以重用连接。
以下是使用Dial函数连接
sqlite3数据库的示例。javascriptvar client = null function main() { // client = Dial("sqlite3://:memory:") // 使用内存数据库 client = Dial("sqlite3://test1.db") // 打开/连接托管者所在目录的数据库文件 // 记录句柄 var sqlite3Handle = client.fd() Log("sqlite3Handle:", sqlite3Handle) // 查询数据库中的表 var ret = client.exec("SELECT name FROM sqlite_master WHERE type='table'") Log(ret) } function onexit() { Log("Executing client.close()") client.close() }python// 不支持rustfn main() { // let mut client = Dial("sqlite3://:memory:"); // 使用内存数据库 let mut client = Dial("sqlite3://test1.db"); // 打开/连接托管者所在目录的数据库文件 // Rust 的连接对象不支持 fd() 方法 // 查询数据库中的表 let ret = client.exec("SELECT name FROM sqlite_master WHERE type='table'"); Log!(format!("{:?}", ret)); Log!("Executing client.close()"); client.close(); }c++// 不支持 -
返回值
| 类型 | 描述 | ||||||||||||||||||||
object | 如果超时,
WebSocket 协议推送的数据,如果策略中
|
参数
| 名称 | 类型 | 必填 | 描述 |
address | string | 是 | 请求地址。 |
timeout | number | 否 | 超时时间(单位:秒)。 |
options | object | 否 | 配置选项。 |
备注
address参数的详细说明:在标准地址wss://ws.okx.com:8443/ws/v5/public之后,使用|符号进行分隔。如果参数字符串中包含|字符,则使用||作为分隔符。分隔符之后的部分为功能参数设置,各参数之间使用&字符连接。
例如,同时设置ss5代理和压缩参数时,可以写作:
Dial("wss://ws.okx.com:8443/ws/v5/public|proxy=socks5://xxx:9999&compress=gzip_raw&mode=recv")
| Dial函数的address参数支持的功能 | 参数说明 |
|---|---|
| WebSocket协议数据压缩相关的参数:compress=参数值 | compress用于指定压缩方式,可选值包括gzip_raw、gzip等。如果所用的gzip并非标准gzip,可以使用扩展方式:gzip_raw |
| WebSocket协议数据压缩相关的参数:mode=参数值 | mode用于指定压缩模式,可选dual、send、recv三种。dual表示双向压缩,即同时发送和接收压缩数据;send表示仅发送压缩数据;recv表示仅接收压缩数据并在本地解压缩。 |
| WebSocket协议启用compression设置:enableCompression=true | 使用enableCompression=false可关闭该设置,默认不启用。 |
| WebSocket协议设置底层自动重连相关的参数:reconnect=参数值 | reconnect用于设置是否自动重连,reconnect=true表示启用重连。未设置该参数时默认不重连。 |
| WebSocket协议设置底层自动重连相关的参数:interval=参数值 | interval为重试的时间间隔,单位为毫秒。例如interval=10000表示重试间隔为10秒;未设置时默认为1秒,即interval=1000。 |
| WebSocket协议设置底层自动重连相关的参数:payload=参数值 | payload为WebSocket重连时需要发送的订阅消息,例如:payload=okok。 |
| socks5代理的相关参数:proxy=参数值 | proxy用于设置ss5代理,参数值格式为:socks5://name:[email protected]:1080。其中name为ss5服务端的用户名,pwd为ss5服务端的登录密码,1080为ss5服务的端口。 |
Dial()函数仅支持实盘。
使用Dial函数连接数据库时,连接字符串的编写方式可参考各数据库对应的Go语言驱动项目。
| 支持的数据库 | 驱动项目 | 连接字符串(Connection String) | 备注 |
|---|---|---|---|
| sqlite3 | github.com/mattn/go-sqlite3 | sqlite3://file:test.db?cache=shared&mode=memory | sqlite3://前缀表示使用的是sqlite3数据库,调用示例:Dial("sqlite3://test1.db") |
| mysql | github.com/go-sql-driver/mysql | mysql://username:yourpassword@tcp(localhost:3306)/yourdatabase?charset=utf8mb4 | -- |
| postgres | github.com/lib/pq | postgres://user=postgres dbname=yourdatabase sslmode=disable password=yourpassword host=localhost port=5432 | -- |
| clickhouse | github.com/ClickHouse/clickhouse-go | clickhouse://tcp://host:9000?username=username&password=yourpassword&database=youdatabase | -- |
需要注意,当address参数中设置的payload内容包含字符=或其它特殊字符时,可能会影响Dial函数对address参数的解析,示例如下。
backPack交易所websocket私有接口调用示例:
javascript
var client = null
function main() {
// base64编码的秘钥对公钥,即在FMZ上配置的access key
var base64ApiKey = "xxx"
var ts = String(new Date().getTime())
var data = "instruction=subscribe×tamp=" + ts + "&window=5000"
// 由于signEd25519最终返回的是base64编码,其中会有字符"="
var signature = signEd25519(data)
// payload 被JSON编码后可能包含字符"="
payload = {
"method": "SUBSCRIBE",
"params": ["account.orderUpdate"],
"signature": [base64ApiKey, signature, ts, "5000"]
}
client = Dial("wss://ws.backpack.exchange")
client.write(JSON.stringify(payload))
if (!client) {
Log("Connection failed, exiting")
return
}
while (true) {
var buf = client.read()
Log(buf)
}
}
function onexit() {
client.close()
}
function signEd25519(data) {
return exchange.Encode("ed25519.seed", "raw", "base64", data, "base64", "{{secretkey}}")
}
代码中采用以下调用方式可以正常工作:
javascript
client = Dial("wss://ws.backpack.exchange")
client.write(JSON.stringify(payload))
如果直接写在payload中则无法正常工作,例如:
javascript
client = Dial("wss://ws.backpack.exchange|payload=" +
JSON.stringify(payload))
目前仅JavaScript语言支持在Dial函数中使用mqtt、nats、amqp、kafka通信协议,下面以JavaScript语言策略代码为例,演示mqtt、nats、amqp、kafka四种协议的使用方法:
javascript
// 需要先配置并部署完成各个协议的代理服务器
// 为了便于演示,主题test_topic的订阅(read操作)与发布(write操作)都在当前这个策略中进行
var arrConn = []
var arrName = []
function main() {
LogReset(1)
conn_nats = Dial("nats://[email protected]:4222?topic=test_topic")
conn_mqtt = Dial("mqtt://127.0.0.1:1883?topic=test_topic")
conn_amqp = Dial("amqp://q:[email protected]:5672/?queue=test_Queue")
conn_kafka = Dial("kafka://localhost:9092/test_topic")
arrConn = [conn_nats, conn_amqp, conn_mqtt, conn_kafka]
arrName = ["nats", "amqp", "mqtt", "kafka"]
while (true) {
for (var i in arrConn) {
var conn = arrConn[i]
var name = arrName[i]
// 写数据
conn.write(name + ", time: " + _D() + ", test msg.")
// 读数据
var readMsg = conn.read(1000)
Log(name + " readMsg: ", readMsg, "#FF0000")
}
Sleep(1000)
}
}
function onexit() {
for (var i in arrConn) {
arrConn[i].close()
Log("Closing", arrName[i], "connection")
}
}
详细介绍请参考文档:探索FMZ:交易策略实盘间通信协议实践
HttpQuery
发送 HTTP 请求。
HttpQuery(url)
HttpQuery(url, options)示例
-
访问 OKX 公共行情 API 接口的示例。
javascriptfunction main(){ // 一个不带参数的 GET 访问示例 var info = JSON.parse(HttpQuery("https://www.okx.com/api/v5/public/time")) Log(info) // 一个带参数的 GET 访问示例 var ticker = JSON.parse(HttpQuery("https://www.okx.com/api/v5/market/books?instId=BTC-USDT")) Log(ticker) }pythonimport json import urllib.request def main(): # HttpQuery 不支持 Python,可以使用 urllib/urllib2 库代替 info = json.loads(urllib.request.urlopen("https://www.okx.com/api/v5/public/time").read().decode('utf-8')) Log(info) ticker = json.loads(urllib.request.urlopen("https://www.okx.com/api/v5/market/books?instId=BTC-USDT").read().decode('utf-8')) Log(ticker)rustfn main() { // 一个不带参数的 GET 访问示例,在 Rust 中由返回值类型注解决定返回 Body 字符串(String)或完整应答(HttpRet) let body: String = HttpQuery("https://www.okx.com/api/v5/public/time", None); let info = JSONParse(&body).unwrap(); Log!(info); // 一个带参数的 GET 访问示例 let body2: String = HttpQuery("https://www.okx.com/api/v5/market/books?instId=BTC-USDT", None); let ticker = JSONParse(&body2).unwrap(); Log!(ticker); }c++void main() { auto info = json::parse(HttpQuery("https://www.okx.com/api/v5/public/time")); Log(info); auto ticker = json::parse(HttpQuery("https://www.okx.com/api/v5/market/books?instId=BTC-USDT")); Log(ticker); } -
HttpQuery 函数使用代理设置的示例。
javascriptfunction main() { // 本次设置代理并发送 HTTP 请求,无用户名、无密码,此次 HTTP 请求会通过代理发送 HttpQuery("socks5://127.0.0.1:8889/http://www.baidu.com/") // 本次设置代理并发送 HTTP 请求,输入用户名和密码,仅对 HttpQuery 的当前调用生效;之后再次调用 HttpQuery("http://www.baidu.com") 时不会使用代理 HttpQuery("socks5://username:[email protected]:8889/http://www.baidu.com/") }python# HttpQuery 不支持 Python,可以使用 Python 的 urllib2 库rustfn main() { // 本次设置代理并发送 HTTP 请求,无用户名、无密码,此次 HTTP 请求会通过代理发送 let ret1: String = HttpQuery("socks5://127.0.0.1:8889/http://www.baidu.com/", None); // 本次设置代理并发送 HTTP 请求,输入用户名和密码,仅对 HttpQuery 的当前调用生效;之后再次调用 HttpQuery("http://www.baidu.com") 时不会使用代理 let ret2: String = HttpQuery("socks5://username:[email protected]:8889/http://www.baidu.com/", None); }c++void main() { HttpQuery("socks5://127.0.0.1:8889/http://www.baidu.com/"); HttpQuery("socks5://username:[email protected]:8889/http://www.baidu.com/"); }
返回值
| 类型 | 描述 |
string / object | 返回请求的应答数据。如果返回值为 |
参数
| 名称 | 类型 | 必填 | 描述 |
url | string | 是 | HTTP 请求的 URL。 |
options | object | 否 | HTTP 请求的相关设置,例如可以是以下结构:
此结构中的所有字段均为可选,例如可以不设置 |
参考
备注
HttpQuery()函数仅支持JavaScript、C++语言;Python语言可以使用urllib库直接发送 HTTP 请求。HttpQuery()主要用于访问交易所无需签名的接口,例如行情信息等公共接口。
回测系统中可以使用HttpQuery()发送请求(仅支持GET请求)以获取数据。回测时限制访问不同URL的次数为 20 次,并且HttpQuery()的访问会缓存数据;相同的URL第二次访问时,HttpQuery()函数将返回缓存数据,不再发起实际的网络请求。
HttpQuery_Go
发送HTTP请求,HttpQuery函数的异步版本。
HttpQuery_Go(url)
HttpQuery_Go(url, options)示例
异步访问交易所公共接口获取聚合行情数据。
javascript
function main() {
// 创建第一个异步线程
var r1 = HttpQuery_Go("https://www.okx.com/api/v5/market/tickers?instType=SPOT")
// 创建第二个异步线程
var r2 = HttpQuery_Go("https://api.huobi.pro/market/tickers")
// 获取第一个异步线程调用的返回值
var tickers1 = r1.wait()
// 获取第二个异步线程调用的返回值
var tickers2 = r2.wait()
// 打印结果
Log("tickers1:", tickers1)
Log("tickers2:", tickers2)
}
python
# 不支持
c++
// 不支持返回值
| 类型 | 描述 |
object |
|
参数
| 名称 | 类型 | 必填 | 描述 |
url | string | 是 | HTTP请求的URL地址。 |
options | object | 否 | HTTP请求相关配置参数,可以使用以下结构:
此结构中的所有字段均为可选字段,例如可以不设置 |
参考
备注
HttpQuery_Go()函数仅支持JavaScript语言,Python语言可以使用urllib库直接发送HTTP请求。HttpQuery_Go()主要用于访问交易所无需签名的接口,例如行情信息等公共接口。回测系统不支持HttpQuery_Go函数。
Encode
该函数根据传入的参数对数据进行编码。
Encode(algo, inputFormat, outputFormat, data)
Encode(algo, inputFormat, outputFormat, data, keyFormat, key)示例
-
Encode 函数调用示例。
javascriptfunction main() { Log(Encode("raw", "raw", "hex", "example", "raw", "123")) // 6578616d706c65 Log(Encode("raw", "raw", "hex", "example")) // 6578616d706c65 Log(Encode("sha256", "raw", "hex", "example", "raw", "123")) // 698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba Log(Encode("sha256", "raw", "hex", "example", "", "123")) // 50d858e0985ecc7f60418aaf0cc5ab587f42c2570a884095a9e8ccacd0f6545c Log(Encode("sha256", "raw", "hex", "example", null, "123")) // 50d858e0985ecc7f60418aaf0cc5ab587f42c2570a884095a9e8ccacd0f6545c Log(Encode("sha256", "raw", "hex", "example", "string", "123")) // 698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba Log(Encode("raw", "raw", "hex", "123")) // 313233 Log(Encode("raw", "raw", "base64", "123")) // MTIz Log(Encode("sha256", "raw", "hex", "example", "hex", "313233")) // 698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba Log(Encode("sha256", "raw", "hex", "example", "base64", "MTIz")) // 698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba }pythondef main(): Log(Encode("raw", "raw", "hex", "example", "raw", "123")) # 6578616d706c65 Log(Encode("raw", "raw", "hex", "example", "", "")) # 6578616d706c65 Log(Encode("sha256", "raw", "hex", "example", "raw", "123")) # 698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba Log(Encode("sha256", "raw", "hex", "example", "", "123")) # 50d858e0985ecc7f60418aaf0cc5ab587f42c2570a884095a9e8ccacd0f6545c Log(Encode("sha256", "raw", "hex", "example", "string", "123")) # 698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba Log(Encode("raw", "raw", "hex", "123", "", "")) # 313233 Log(Encode("raw", "raw", "base64", "123", "", "")) # MTIz Log(Encode("sha256", "raw", "hex", "example", "hex", "313233")) # 698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba Log(Encode("sha256", "raw", "hex", "example", "base64", "MTIz")) # 698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84barustfn main() { // Rust 的 Encode() 函数 6 个参数均为必填;不加密时,keyFormat 和 key 传入空字符串即可 Log!(Encode("raw", "raw", "hex", "example", "raw", "123")); // 6578616d706c65 Log!(Encode("raw", "raw", "hex", "example", "", "")); // 6578616d706c65 Log!(Encode("sha256", "raw", "hex", "example", "raw", "123")); // 698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba Log!(Encode("sha256", "raw", "hex", "example", "", "123")); // 50d858e0985ecc7f60418aaf0cc5ab587f42c2570a884095a9e8ccacd0f6545c Log!(Encode("sha256", "raw", "hex", "example", "string", "123")); // 698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba Log!(Encode("raw", "raw", "hex", "123", "", "")); // 313233 Log!(Encode("raw", "raw", "base64", "123", "", "")); // MTIz Log!(Encode("sha256", "raw", "hex", "example", "hex", "313233")); // 698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba Log!(Encode("sha256", "raw", "hex", "example", "base64", "MTIz")); // 698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba }c++void main() { Log(Encode("raw", "raw", "hex", "example", "raw", "123")); // 6578616d706c65 Log(Encode("raw", "raw", "hex", "example")); // 6578616d706c65 Log(Encode("sha256", "raw", "hex", "example", "raw", "123")); // 698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba Log(Encode("sha256", "raw", "hex", "example", "", "123")); // 50d858e0985ecc7f60418aaf0cc5ab587f42c2570a884095a9e8ccacd0f6545c Log(Encode("sha256", "raw", "hex", "example", "string", "123")); // 698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba Log(Encode("raw", "raw", "hex", "123")); // 313233 Log(Encode("raw", "raw", "base64", "123")); // MTIz Log(Encode("sha256", "raw", "hex", "example", "hex", "313233")); // 698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba Log(Encode("sha256", "raw", "hex", "example", "base64", "MTIz")); // 698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba } -
参数
algo还支持以下取值:"text.encoder.utf8"、"text.decoder.utf8"、"text.encoder.gbk"、"text.decoder.gbk",用于对字符串进行编码和解码。javascriptfunction main(){ var ret1 = Encode("text.encoder.utf8", "raw", "hex", "你好") // e4bda0e5a5bd Log(ret1) var ret2 = Encode("text.decoder.utf8", "hex", "string", ret1) Log(ret2) var ret3 = Encode("text.encoder.gbk", "raw", "hex", "你好") // c4e3bac3 Log(ret3) var ret4 = Encode("text.decoder.gbk", "hex", "string", ret3) Log(ret4) }pythondef main(): ret1 = Encode("text.encoder.utf8", "raw", "hex", "你好", "", "") # e4bda0e5a5bd Log(ret1) ret2 = Encode("text.decoder.utf8", "hex", "string", ret1, "", "") Log(ret2) ret3 = Encode("text.encoder.gbk", "raw", "hex", "你好", "", "") # c4e3bac3 Log(ret3) ret4 = Encode("text.decoder.gbk", "hex", "string", ret3, "", "") Log(ret4)rustfn main() { // Rust 的 Encode() 函数6个参数均为必填,不加密时 keyFormat、key 传入空字符串 let ret1 = Encode("text.encoder.utf8", "raw", "hex", "你好", "", ""); // e4bda0e5a5bd Log!(ret1); let ret2 = Encode("text.decoder.utf8", "hex", "string", &ret1, "", ""); Log!(ret2); let ret3 = Encode("text.encoder.gbk", "raw", "hex", "你好", "", ""); // c4e3bac3 Log!(ret3); let ret4 = Encode("text.decoder.gbk", "hex", "string", &ret3, "", ""); Log!(ret4); }c++void main(){ auto ret1 = Encode("text.encoder.utf8", "raw", "hex", "你好"); // e4bda0e5a5bd Log(ret1); auto ret2 = Encode("text.decoder.utf8", "hex", "string", ret1); Log(ret2); auto ret3 = Encode("text.encoder.gbk", "raw", "hex", "你好"); // c4e3bac3 Log(ret3); auto ret4 = Encode("text.decoder.gbk", "hex", "string", ret3); Log(ret4); }
返回值
| 类型 | 描述 |
string |
|
参数
| 名称 | 类型 | 必填 | 描述 |
algo | string | 是 | 参数 参数 参数 |
inputFormat | string | 是 | 用于指定 |
outputFormat | string | 是 | 用于指定输出的数据格式。 |
data | string | 是 | 参数 |
keyFormat | string | 否 | 用于指定 |
key | string | 否 | 参数 当参数 当参数 |
备注
Encode()函数仅支持实盘。若不传入key、keyFormat参数,则不进行key加密。
UnixNano
获取当前时刻的纳秒级时间戳。
UnixNano()示例
如果需要获取毫秒级时间戳,可以使用以下代码:
javascript
function main() {
var time = UnixNano() / 1000000
Log(_N(time, 0))
}
python
def main():
time = UnixNano()
Log(time)
rust
fn main() {
let time = UnixNano() / 1000000;
Log!(_N(time, 0));
}
c++
void main() {
auto time = UnixNano();
Log(time);
}返回值
| 类型 | 描述 |
number |
|
参考
Unix
获取当前时刻的秒级时间戳。
Unix()示例
javascript
function main() {
var t = Unix()
Log(t)
}
python
def main():
t = Unix()
Log(t)
rust
fn main() {
let t = Unix();
Log!(t);
}
c++
void main() {
auto t = Unix();
Log(t);
}返回值
| 类型 | 描述 |
number | 返回秒级时间戳。 |
参考
GetOS
获取托管者所在设备的操作系统信息。
GetOS()示例
javascript
function main() {
Log("GetOS:", GetOS())
}
python
def main():
Log("GetOS:", GetOS())
rust
fn main() {
Log!("GetOS:", GetOS());
}
c++
void main() {
Log("GetOS:", GetOS());
}返回值
| 类型 | 描述 |
string | 操作系统信息。 |
备注
例如,在Mac OS操作系统下运行的托管者,调用GetOS()函数可能返回:darwin/amd64。由于苹果电脑采用多种硬件架构,返回值中会附带具体的架构信息。其中,darwin即Mac OS系统的内核名称。
MD5
计算参数data的 MD5 哈希值。
MD5(data)示例
javascript
function main() {
Log("MD5", MD5("hello world"))
}
python
def main():
Log("MD5", MD5("hello world"))
rust
fn main() {
Log!("MD5", MD5("hello world"));
}
c++
void main() {
Log("MD5", MD5("hello world"));
}返回值
| 类型 | 描述 |
string | MD5 哈希值。 |
参数
| 名称 | 类型 | 必填 | 描述 |
data | string | 是 | 需要进行 MD5 计算的数据。 |
参考
备注
调用MD5("hello world")函数后,返回值为:5eb63bbbe01eeed093cb22bb8f5acdc3。
DBExec
数据库接口函数。
DBExec(sql)示例
-
支持内存数据库。对于
DBExec函数的参数,如果sql语句以:开头,则在内存数据库中执行操作;由于无需写入文件,速度更快。此方式适用于无需持久化保存的数据库操作,例如:javascriptfunction main() { var strSql = [ ":CREATE TABLE TEST_TABLE(", "TS INT PRIMARY KEY NOT NULL,", "HIGH REAL NOT NULL,", "OPEN REAL NOT NULL,", "LOW REAL NOT NULL,", "CLOSE REAL NOT NULL,", "VOLUME REAL NOT NULL)" ].join("") var ret = DBExec(strSql) Log(ret) // 增加一条数据 Log(DBExec(":INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);")) // 查询数据 Log(DBExec(":SELECT * FROM TEST_TABLE;")) }pythondef main(): arr = [ ":CREATE TABLE TEST_TABLE(", "TS INT PRIMARY KEY NOT NULL,", "HIGH REAL NOT NULL,", "OPEN REAL NOT NULL,", "LOW REAL NOT NULL,", "CLOSE REAL NOT NULL,", "VOLUME REAL NOT NULL)" ] strSql = "" for i in range(len(arr)): strSql += arr[i] ret = DBExec(strSql) Log(ret) # 增加一条数据 Log(DBExec(":INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);")) # 查询数据 Log(DBExec(":SELECT * FROM TEST_TABLE;"))rustfn main() { let arr = [ ":CREATE TABLE TEST_TABLE(", "TS INT PRIMARY KEY NOT NULL,", "HIGH REAL NOT NULL,", "OPEN REAL NOT NULL,", "LOW REAL NOT NULL,", "CLOSE REAL NOT NULL,", "VOLUME REAL NOT NULL)", ]; let strSql = arr.join(""); let ret = DBExec(&strSql); Log!(format!("{:?}", ret)); // 增加一条数据 Log!(format!("{:?}", DBExec(":INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);"))); // 查询数据 Log!(format!("{:?}", DBExec(":SELECT * FROM TEST_TABLE;"))); }c++void main() { string strSql = ":CREATE TABLE TEST_TABLE(\ TS INT PRIMARY KEY NOT NULL,\ HIGH REAL NOT NULL,\ OPEN REAL NOT NULL,\ LOW REAL NOT NULL,\ CLOSE REAL NOT NULL,\ VOLUME REAL NOT NULL)"; auto ret = DBExec(strSql); Log(ret); // 增加一条数据 Log(DBExec(":INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);")); // 查询数据 Log(DBExec(":SELECT * FROM TEST_TABLE;")); } -
使用
DBExec()函数创建数据表。javascriptfunction main() { var strSql = [ "CREATE TABLE TEST_TABLE(", "TS INT PRIMARY KEY NOT NULL,", "HIGH REAL NOT NULL,", "OPEN REAL NOT NULL,", "LOW REAL NOT NULL,", "CLOSE REAL NOT NULL,", "VOLUME REAL NOT NULL)" ].join("") var ret = DBExec(strSql) Log(ret) }pythondef main(): arr = [ "CREATE TABLE TEST_TABLE(", "TS INT PRIMARY KEY NOT NULL,", "HIGH REAL NOT NULL,", "OPEN REAL NOT NULL,", "LOW REAL NOT NULL,", "CLOSE REAL NOT NULL,", "VOLUME REAL NOT NULL)" ] strSql = "" for i in range(len(arr)): strSql += arr[i] ret = DBExec(strSql) Log(ret)rustfn main() { let arr = [ "CREATE TABLE TEST_TABLE(", "TS INT PRIMARY KEY NOT NULL,", "HIGH REAL NOT NULL,", "OPEN REAL NOT NULL,", "LOW REAL NOT NULL,", "CLOSE REAL NOT NULL,", "VOLUME REAL NOT NULL)", ]; let strSql = arr.join(""); let ret = DBExec(&strSql); Log!(format!("{:?}", ret)); }c++void main() { string strSql = "CREATE TABLE TEST_TABLE(\ TS INT PRIMARY KEY NOT NULL,\ HIGH REAL NOT NULL,\ OPEN REAL NOT NULL,\ LOW REAL NOT NULL,\ CLOSE REAL NOT NULL,\ VOLUME REAL NOT NULL)"; auto ret = DBExec(strSql); Log(ret); } -
对数据表中的记录执行增、删、查、改操作。
javascriptfunction main() { var strSql = [ "CREATE TABLE TEST_TABLE(", "TS INT PRIMARY KEY NOT NULL,", "HIGH REAL NOT NULL,", "OPEN REAL NOT NULL,", "LOW REAL NOT NULL,", "CLOSE REAL NOT NULL,", "VOLUME REAL NOT NULL)" ].join("") Log(DBExec(strSql)) // 增加一条数据 Log(DBExec("INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);")) // 查询数据 Log(DBExec("SELECT * FROM TEST_TABLE;")) // 修改数据 Log(DBExec("UPDATE TEST_TABLE SET HIGH=? WHERE TS=?", 110, 1518970320000)) // 删除数据 Log(DBExec("DELETE FROM TEST_TABLE WHERE HIGH=?", 110)) }pythondef main(): arr = [ "CREATE TABLE TEST_TABLE(", "TS INT PRIMARY KEY NOT NULL,", "HIGH REAL NOT NULL,", "OPEN REAL NOT NULL,", "LOW REAL NOT NULL,", "CLOSE REAL NOT NULL,", "VOLUME REAL NOT NULL)" ] strSql = "" for i in range(len(arr)): strSql += arr[i] Log(DBExec(strSql)) # 增加一条数据 Log(DBExec("INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);")) # 查询数据 Log(DBExec("SELECT * FROM TEST_TABLE;")) # 修改数据 Log(DBExec("UPDATE TEST_TABLE SET HIGH=? WHERE TS=?", 110, 1518970320000)) # 删除数据 Log(DBExec("DELETE FROM TEST_TABLE WHERE HIGH=?", 110))rustfn main() { let arr = [ "CREATE TABLE TEST_TABLE(", "TS INT PRIMARY KEY NOT NULL,", "HIGH REAL NOT NULL,", "OPEN REAL NOT NULL,", "LOW REAL NOT NULL,", "CLOSE REAL NOT NULL,", "VOLUME REAL NOT NULL)", ]; let strSql = arr.join(""); Log!(format!("{:?}", DBExec(&strSql))); // 增加一条数据 Log!(format!("{:?}", DBExec("INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);"))); // 查询数据 Log!(format!("{:?}", DBExec("SELECT * FROM TEST_TABLE;"))); // 修改数据,Rust 的 DBExec() 函数只接受单个SQL语句字符串参数,不支持 ? 占位符传参,参数值直接写在语句中 Log!(format!("{:?}", DBExec("UPDATE TEST_TABLE SET HIGH=110 WHERE TS=1518970320000;"))); // 删除数据 Log!(format!("{:?}", DBExec("DELETE FROM TEST_TABLE WHERE HIGH=110;"))); }c++void main() { string strSql = "CREATE TABLE TEST_TABLE(\ TS INT PRIMARY KEY NOT NULL,\ HIGH REAL NOT NULL,\ OPEN REAL NOT NULL,\ LOW REAL NOT NULL,\ CLOSE REAL NOT NULL,\ VOLUME REAL NOT NULL)"; Log(DBExec(strSql)); // 增加一条数据 Log(DBExec("INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);")); // 查询数据 Log(DBExec("SELECT * FROM TEST_TABLE;")); // 修改数据 Log(DBExec("UPDATE TEST_TABLE SET HIGH=? WHERE TS=?", 110, 1518970320000)); // 删除数据 Log(DBExec("DELETE FROM TEST_TABLE WHERE HIGH=?", 110)); }
返回值
| 类型 | 描述 |
object | 包含sql语句执行结果的对象,例如: ```json {"columns":["TS","HIGH","OPEN","LOW","CLOSE","VOLUME"],"values":[[1518970320000,100,99.1,90,100,12345.6]]} ``` |
参数
| 名称 | 类型 | 必填 | 描述 |
sql | string | 是 | sql语句字符串。 |
参考
备注
-
通过向函数
DBExec()传入参数,可对实盘数据库(SQLite 数据库)进行操作。-
可实现对实盘数据库中数据的增、删、查、改等操作,并支持SQLite语法。
-
实盘数据库中的系统保留表包括:
kvdb、cfg、log、profit、chart,请勿对这些表进行操作。 -
目前不支持事务,不建议执行此类操作,否则会引发系统冲突。
-
DBExec()函数仅支持实盘。
-
UUID
创建一个 UUID。
UUID()示例
javascript
function main() {
var uuid1 = UUID()
var uuid2 = UUID()
Log(uuid1, uuid2)
}
python
def main():
uuid1 = UUID()
uuid2 = UUID()
Log(uuid1, uuid2)
rust
fn main() {
let uuid1 = UUID();
let uuid2 = UUID();
Log!(uuid1, uuid2);
}
c++
void main() {
auto uuid1 = UUID();
auto uuid2 = UUID();
Log(uuid1, uuid2);
}返回值
| 类型 | 描述 |
string | 32 位的 UUID。 |
备注
UUID() 函数仅支持实盘。
EventLoop
监听事件,当任意WebSocket有可读数据,或exchange.Go()、HttpQuery_Go()等并发任务完成后返回。
EventLoop()
EventLoop(timeout)示例
javascript
function main() {
var routine_getTicker = exchange.Go("GetTicker")
var routine_getDepth = exchange.Go("GetDepth")
var routine_getTrades = exchange.Go("GetTrades")
// Sleep(2000),如果这里使用Sleep语句,会导致之后的EventLoop函数错过之前的事件。因为等待了2秒,并发的函数已经收到了数据,之后才开始EventLoop监听机制,就会错过这些事件
// 除非在第一行代码就开始调用EventLoop(-1),先初始化EventLoop的监听机制,才不会错过这些事件
// Log("GetDepth:", routine_getDepth.wait()) 如果这里提前调用wait函数取出GetDepth函数并发调用的结果,本次GetDepth函数收到请求结果的事件便不会在EventLoop函数中返回
var ts1 = new Date().getTime()
var ret1 = EventLoop(0)
var ts2 = new Date().getTime()
var ret2 = EventLoop(0)
var ts3 = new Date().getTime()
var ret3 = EventLoop(0)
Log("First concurrent task completed:", _D(ts1), ret1)
Log("Second concurrent task completed:", _D(ts2), ret2)
Log("Third concurrent task completed:", _D(ts3), ret3)
Log("GetTicker:", routine_getTicker.wait())
Log("GetDepth:", routine_getDepth.wait())
Log("GetTrades:", routine_getTrades.wait())
}
python
import time
def main():
routine_getTicker = exchange.Go("GetTicker")
routine_getDepth = exchange.Go("GetDepth")
routine_getTrades = exchange.Go("GetTrades")
ts1 = time.time()
ret1 = EventLoop(0)
ts2 = time.time()
ret2 = EventLoop(0)
ts3 = time.time()
ret3 = EventLoop(0)
Log("First concurrent task completed:", _D(ts1), ret1)
Log("Second concurrent task completed:", _D(ts2), ret2)
Log("Third concurrent task completed:", _D(ts3), ret3)
Log("GetTicker:", routine_getTicker.wait())
Log("GetDepth:", routine_getDepth.wait())
Log("GetTrades:", routine_getTrades.wait())
rust
fn main() {
// Rust 中 exchange.Go 使用类型化 token(如 Go::GetTicker)而非方法名字符串,无参时传 ()
let routine_getTicker = exchange.Go(Go::GetTicker, ());
let routine_getDepth = exchange.Go(Go::GetDepth, ());
let routine_getTrades = exchange.Go(Go::GetTrades, ());
// Sleep(2000),如果这里使用Sleep语句,会导致之后的EventLoop函数错过之前的事件。因为等待了2秒,并发的函数已经收到了数据,之后才开始EventLoop监听机制,就会错过这些事件
// 除非在第一行代码就开始调用EventLoop(-1),先初始化EventLoop的监听机制,才不会错过这些事件
// Log!("GetDepth:", routine_getDepth.wait(0)) 如果这里提前调用wait函数取出GetDepth函数并发调用的结果,本次GetDepth函数收到请求结果的事件便不会在EventLoop函数中返回
let ts1 = Unix() * 1000;
let ret1 = EventLoop(0);
let ts2 = Unix() * 1000;
let ret2 = EventLoop(0);
let ts3 = Unix() * 1000;
let ret3 = EventLoop(0);
Log!("First concurrent task completed:", _D(ts1), ret1);
Log!("Second concurrent task completed:", _D(ts2), ret2);
Log!("Third concurrent task completed:", _D(ts3), ret3);
Log!("GetTicker:", routine_getTicker.wait(0).unwrap());
Log!("GetDepth:", routine_getDepth.wait(0).unwrap());
Log!("GetTrades:", routine_getTrades.wait(0).unwrap());
}
c++
void main() {
auto routine_getTicker = exchange.Go("GetTicker");
auto routine_getDepth = exchange.Go("GetDepth");
auto routine_getTrades = exchange.Go("GetTrades");
auto ts1 = Unix() * 1000;
auto ret1 = EventLoop(0);
auto ts2 = Unix() * 1000;
auto ret2 = EventLoop(0);
auto ts3 = Unix() * 1000;
auto ret3 = EventLoop(0);
Log("First concurrent task completed:", _D(ts1), ret1);
Log("Second concurrent task completed:", _D(ts2), ret2);
Log("Third concurrent task completed:", _D(ts3), ret3);
Ticker ticker;
Depth depth;
Trades trades;
routine_getTicker.wait(ticker);
routine_getDepth.wait(depth);
routine_getTrades.wait(trades);
Log("GetTicker:", ticker);
Log("GetDepth:", depth);
Log("GetTrades:", trades);
}返回值
| 类型 | 描述 |
object | 如果返回的对象不为空值,则返回内容中的
|
参数
| 名称 | 类型 | 必填 | 描述 |
timeout | number | 否 | 参数 |
参考
备注
代码中首次调用EventLoop()函数时,才会初始化该事件监听机制。如果在事件回调发生之后才首次调用EventLoop(),则会错过此前的事件。系统底层封装的队列结构最多可缓存500个事件回调,如果程序运行过程中没有及时调用EventLoop()函数取出,超出500个缓存上限的较晚事件回调将会丢失。
EventLoop()函数的调用不会影响系统底层WebSocket的缓存队列,也不会影响exchange.Go()等并发函数的缓存,这些缓存中的数据仍需使用各自的方法取出。对于在EventLoop()函数返回之前已经取出的数据,不会在EventLoop()函数中再次产生返回事件。
EventLoop()函数的主要用途是通知策略层:系统底层已接收到新的网络数据,从而以事件驱动整个策略。当EventLoop()函数返回事件时,只需遍历所有数据来源(例如WebSocket连接、exchange.Go()创建的对象)尝试获取数据即可。
EventLoop()函数仅支持实盘。
在主函数main()中调用时,监听主线程的事件。在使用JavaScript语言编写的策略中,也可以在threading.Thread()函数创建的线程的执行函数中调用,用于监听当前线程的事件。
__Serve
__Serve函数用于创建HTTP服务、TCP服务、WebSocket服务(基于HTTP协议)。
__Serve(serveURI, handler)
__Serve(serveURI, handler, ...args)示例
javascript
function main() {
let httpServer = __Serve("http://:8088?gzip=true", function (ctx) {
Log("http connect from: ", ctx.remoteAddr(), "->", ctx.localAddr())
let path = ctx.path()
if (path == "/") {
ctx.write(JSON.stringify({
path: ctx.path(),
method: ctx.method(),
headers: ctx.headers(),
cookie: ctx.header("Cookie"),
remote: ctx.remoteAddr(),
query: ctx.rawQuery()
}))
} else if (path == "/tickers") {
let ret = exchange.GetTickers()
if (!ret) {
ctx.setStatus(500)
ctx.write(GetLastError())
} else {
ctx.write(JSON.stringify(ret))
}
} else if (path == "/wss") {
if (ctx.upgrade("websocket")) { // upgrade to websocket
while (true) {
let r = ctx.read(10)
if (r == "") {
break
} else if (r) {
if (r == "ticker") {
ctx.write(JSON.stringify(exchange.GetTicker()))
} else {
ctx.write("not support")
}
}
}
Log("websocket closed", ctx.remoteAddr())
}
} else {
ctx.setStatus(404)
}
})
let echoServer = __Serve("tcp://:8089", function (ctx) {
Log("tcp connect from: ", ctx.remoteAddr(), "->", ctx.localAddr())
while (true) {
let d = ctx.read()
if (!d) {
break
}
ctx.write(d)
}
Log("connect closed")
})
Log("http serve on", httpServer, "tcp serve on", echoServer)
for (var i = 0; i < 5; i++) {
if (i == 2) {
// test Http
var retHttp = HttpQuery("http://127.0.0.1:8088?num=123&limit=100", {"debug": true})
Log("retHttp:", retHttp)
} else if (i == 3) {
// test TCP
var tcpConn = Dial("tcp://127.0.0.1:8089")
tcpConn.write("Hello TCP Server")
var retTCP = tcpConn.read()
Log("retTCP:", retTCP)
} else if (i == 4) {
// test Websocket
var wsConn = Dial("ws://127.0.0.1:8088/wss|compress=gzip")
wsConn.write("ticker")
var retWS = wsConn.read(1000)
Log("retWS:", retWS)
// no depth
wsConn.write("depth")
retWS = wsConn.read(1000)
Log("retWS:", retWS)
}
Sleep(1000)
}
}
python
# 不支持
c++
// 不支持返回值
| 类型 | 描述 |
string | 返回一个字符串,记录创建的服务的IP地址、端口。例如: |
参数
| 名称 | 类型 | 必填 | 描述 |
serveURI | string | 是 |
|
handler | function | 是 |
参数 |
arg | string / number / bool / object / array / function / any (平台支持的任意类型) | 否 | 作为参数
调用 |
参考
备注
- 该函数仅支持JavaScript语言策略。
- 服务线程与全局作用域隔离,因此不支持闭包或引用外部变量、自定义函数等;但是可以调用平台所有的API函数。
WebSocket服务基于HTTP协议实现,可以在path中设置一个路由分支,设计WebSocket消息订阅/推送的实现代码,可以参考本节示例代码。
参数handler传入的回调处理函数接收一个ctx参数。ctx参数为一个上下文对象,用于获取数据和写入数据,具有以下方法:
- ctx.proto()
应用于HTTP/TCP协议,调用时返回协议名称。例如:HTTP/1.1、tcp。 - ctx.host()
应用于HTTP协议,调用时返回主机信息:IP地址、端口。 - ctx.path()
应用于HTTP协议,调用时返回请求路径。 - ctx.query(key)
应用于HTTP协议,调用时返回请求中query查询中key对应的值。例如发送的请求为:http://127.0.0.1:8088?num=123,参数handler传入的回调处理函数中ctx.query("num")调用时返回"123"。 - ctx.rawQuery()
应用于HTTP协议,调用时返回请求中的原始查询(HTTP请求的query)。 - ctx.headers()
应用于HTTP协议,调用时返回请求中的请求头信息。 - ctx.header(key)
应用于HTTP协议,调用时返回指定的请求头中的某个key对应的值。例如获取当前请求的headers中的User-Agent:ctx.header("User-Agent")。 - ctx.method()
应用于HTTP协议,调用时返回请求方法,例如GET、POST等。 - ctx.body()
应用于HTTP协议的POST请求,调用时返回请求的正文。 - ctx.setHeader(key, value)
应用于HTTP协议,设置响应报文的请求头信息。 - ctx.setStatus(code)
应用于HTTP协议,设置HTTP报文状态码,通常在路由分支最后设置HTTP状态码,默认为200。 - ctx.remoteAddr()
应用于HTTP/TCP协议,调用时返回请求中的远程客户端地址、端口。 - ctx.localAddr()
应用于HTTP/TCP协议,调用时返回服务本地地址、端口。 - ctx.upgrade("websocket")
应用于基于HTTP协议的WebSocket协议实现,切换ctx上下文对象为WebSocket协议;切换成功返回布尔值(真),失败返回布尔值(假)。 - ctx.read(timeout_ms)
应用于基于HTTP协议的WebSocket协议实现/TCP协议,读取WebSocket连接的数据,TCP连接的数据,普通HTTP协议中不支持使用该read方法;可以指定超时时间参数timeout_ms,单位毫秒。 - ctx.write(s)
应用于HTTP/TCP协议,用于写入字符串数据,可以使用JSON.stringify()编码JSON对象为字符串之后写入。对于WebSocket协议,可以使用该方法将编码后的字符串传递给客户端。
_G
持久化保存数据。该函数实现了一个可持久化保存的全局字典功能,数据以键值对(KV)表的结构永久保存在托管者的本地数据库文件中。
_G()
_G(k)
_G(k, v)示例
javascript
function main(){
// 设置一个全局变量num,值为1
_G("num", 1)
// 更改一个全局变量num,值为字符串ok
_G("num", "ok")
// 删除全局变量num
_G("num", null)
// 返回全局变量num的值
Log(_G("num"))
// 删除所有全局变量
_G(null)
// 返回实盘ID
var robotId = _G()
}
python
def main():
_G("num", 1)
_G("num", "ok")
_G("num", None)
Log(_G("num"))
_G(None)
robotId = _G()
rust
fn main() {
// 设置一个全局变量num,值为1
_G!("num", 1);
// 更改一个全局变量num,值为字符串ok
_G!("num", "ok");
// 删除全局变量num
_G!("num", null);
// 返回全局变量num的值
Log!(_G!("num"));
// Rust 不支持 _G!(null) 删除所有全局变量的形式
// 返回实盘ID
let robotId = _G!();
}
c++
void main() {
_G("num", 1);
_G("num", "ok");
_G("num", NULL);
Log(_G("num"));
_G(NULL);
// 不支持 auto robotId = _G();
}返回值
| 类型 | 描述 |
string / number / bool / object / array / 空值 | 持久化保存的 |
参数
| 名称 | 类型 | 必填 | 描述 |
k | string / 空值 | 否 | 参数 |
v | string / number / bool / object / array / 空值 | 否 | 参数 |
参考
备注
每个实盘单独对应一个数据库。策略重启或托管者停止运行后,_G()函数保存的数据依然会持续存在。但回测结束后,_G()函数在回测系统中保存的数据将被清除。使用_G()函数持久化保存数据时,应根据硬件设备的内存与硬盘空间合理使用,切勿滥用。
在实盘运行中,当调用_G()函数且不传入任何参数时,_G()函数返回当前实盘的Id。
调用_G()函数时,参数v传入空值表示删除对应的k-v键值对。
调用_G()函数时,若仅参数k传入字符串,则_G()函数返回参数k对应的已保存键值。
调用_G()函数时,若仅参数k传入空值,则表示删除所有已记录的k-v键值对。
当k-v键值对已持久化保存后,再次调用_G()函数,并传入已持久化保存的键名作为参数k、新的键值作为参数v,即可更新该k-v键值对。
以实盘Id为123456为例,使用_G()函数持久化保存的K-V键值数据存储在该实盘(即策略实例程序)所属托管者目录下的/logs/storage/123456/123456.db3数据库文件中,数据记录在kvdb表内。
_D
将毫秒级时间戳或Date对象转换为时间字符串。
_D()
_D(timestamp)
_D(timestamp, fmt)示例
-
获取并打印当前时间字符串:
javascriptfunction main(){ var time = _D() Log(time) }pythondef main(): strTime = _D() Log(strTime)rustfn main() { let time = _D(None); Log!(time); }c++void main() { auto strTime = _D(); Log(strTime); } -
时间戳为1574993606000,使用代码进行转换:
javascriptfunction main() { Log(_D(1574993606000)) }pythondef main(): # 在北京时间的服务器上运行结果为:2019-11-29 10:13:26;而在其他地区服务器上的托管者运行此代码,结果则为:2019-11-29 02:13:26 Log(_D(1574993606))rustfn main() { Log!(_D(1574993606000)); }c++void main() { Log(_D(1574993606000)); } -
使用参数
fmt进行格式化,JavaScript、Python、C++语言的格式化字符串有所不同,具体请参看以下示例:javascriptfunction main() { Log(_D(1574993606000, "yyyy--MM--dd hh--mm--ss")) // 2019--11--29 10--13--26 }pythondef main(): # 1574993606 为秒级时间戳 Log(_D(1574993606, "%Y--%m--%d %H--%M--%S")) # 2019--11--29 10--13--26rustfn main() { // Rust 的 _D() 函数不支持 fmt 参数,仅支持默认格式:yyyy-MM-dd hh:mm:ss Log!(_D(1574993606000)); // 2019-11-29 10:13:26 }c++void main() { Log(_D(1574993606000, "%Y--%m--%d %H--%M--%S")); // 2019--11--29 10--13--26 }
返回值
| 类型 | 描述 |
string | 时间字符串。 |
参数
| 名称 | 类型 | 必填 | 描述 |
timestamp | number / object | 否 | 毫秒级时间戳或 |
fmt | string | 否 | 格式化字符串, |
参考
备注
若不传入任何参数,则返回当前时间字符串。在Python策略中使用_D()函数时,需要注意传入的参数为秒级时间戳(JavaScript、C++策略中为毫秒级时间戳,1秒等于1000毫秒)。在实盘中使用_D()函数将时间戳解析为可读时间字符串时,需要注意托管者程序所在操作系统的时区与时间设置,因为_D()函数的解析结果取决于托管者系统的时间。
_N
格式化浮点数。
_N()
_N(num)
_N(num, precision)示例
-
例如
_N(3.1415, 2)会保留3.1415小数点后两位,删除其余数位,函数返回3.14。javascriptfunction main(){ var i = 3.1415 Log(i) var ii = _N(i, 2) Log(ii) }pythondef main(): i = 3.1415 Log(i) ii = _N(i, 2) Log(ii)rustfn main() { let i = 3.1415; Log!(i); let ii = _N(i, 2); Log!(ii); }c++void main() { auto i = 3.1415; Log(i); auto ii = _N(i, 2); Log(ii); } -
如果需要将小数点左边的N位数字都置为0,可以这样编写:
javascriptfunction main(){ var i = 1300 Log(i) var ii = _N(i, -3) // 查看日志得知为1000 Log(ii) }pythondef main(): i = 1300 Log(i) ii = _N(i, -3) Log(ii)rustfn main() { let i = 1300; Log!(i); let ii = _N(i, -3); // 查看日志得知为1000 Log!(ii); }c++void main() { auto i = 1300; Log(i); auto ii = _N(i, -3); Log(ii); }
返回值
| 类型 | 描述 |
number | 根据精度设置格式化后的浮点数。 |
参数
| 名称 | 类型 | 必填 | 描述 |
num | number | 是 | 待格式化的浮点数。 |
precision | number | 否 | 用于设置格式化精度,参数 |
参考
备注
参数precision可以为正整数或负整数。
_C
重试函数,用于对接口调用进行容错处理。
_C(pfn)
_C(pfn, ...args)示例
-
对无参数的函数进行容错处理:
javascriptfunction main(){ var ticker = _C(exchange.GetTicker) // 调整_C()函数重试时间间隔为2秒 _CDelay(2000) var depth = _C(exchange.GetDepth) Log(ticker) Log(depth) }pythondef main(): ticker = _C(exchange.GetTicker) _CDelay(2000) depth = _C(exchange.GetDepth) Log(ticker) Log(depth)rustfn main() { let ticker = _C!(exchange.GetTicker(None)); // 调整_C!()宏重试时间间隔为2秒 _CDelay(2000); let depth = _C!(exchange.GetDepth(None)); Log!(ticker); Log!(depth); }c++void main() { auto ticker = _C(exchange.GetTicker); _CDelay(2000); auto depth = _C(exchange.GetDepth); Log(ticker); Log(depth); } -
对带参数的函数进行容错处理:
javascriptfunction main(){ var records = _C(exchange.GetRecords, PERIOD_D1) Log(records) }pythondef main(): records = _C(exchange.GetRecords, PERIOD_D1) Log(records)rustfn main() { let records = _C!(exchange.GetRecords(None, PERIOD_D1, None)); Log!(records); }c++void main() { auto records = _C(exchange.GetRecords, PERIOD_D1); Log(records); } -
也可用于对自定义函数进行容错处理:
javascriptvar test = function(a, b){ var time = new Date().getTime() / 1000 if(time % b == 3){ Log("Condition met!", "#FF0000") return true } Log("Retrying!", "#FF0000") return false } function main(){ var ret = _C(test, 1, 5) Log(ret) }pythonimport time def test(a, b): ts = time.time() if ts % b == 3: Log("Condition met!", "#FF0000") return True Log("Retrying!", "#FF0000") return False def main(): ret = _C(test, 1, 5) Log(ret)rustfn test(a: i64, b: i64) -> Result<bool> { let time = Unix(); if time % b == 3 { Log!("Condition met!", "#FF0000"); return Ok(true); } Log!("Retrying!", "#FF0000"); Err(Error::Api("retry".to_string())) } fn main() { // Rust 中自定义函数返回 Result 类型即可使用 _C! 宏容错,返回 Err 时会重试 let ret = _C!(test(1, 5)); Log!(ret); }c++// C++ 不支持这种方式对于自定义函数容错
返回值
| 类型 | 描述 |
除假值和空值以外的所有平台支持的类型(any)。 | 回调函数执行后的返回值。 |
参数
| 名称 | 类型 | 必填 | 描述 |
pfn | function | 是 | 参数 |
arg | string / number / bool / object / array / function / any (平台支持的任意类型) | 否 | 回调函数的参数,参数 |
备注
_C()函数会反复调用指定的函数,直到其成功返回为止(当参数pfn所引用的函数被调用时返回空值或假值,则会重试调用pfn)。
例如_C(exchange.GetTicker)。默认重试间隔为3秒,可调用_CDelay()函数来设置重试间隔。
例如_CDelay(1000),表示将_C()函数的重试间隔改为1秒。
可以对以下函数进行容错处理(但不限于此):
exchange.GetTicker()exchange.GetDepth()exchange.GetTrades()exchange.GetRecords()exchange.GetAccount()exchange.GetOrders()exchange.GetOrder()exchange.GetPositions()
以上函数均可通过_C()函数调用以实现容错。_C()函数的容错并不局限于上述列出的函数。请注意,参数pfn为函数引用而非函数调用,即应写作_C(exchange.GetTicker),而非_C(exchange.GetTicker())。
_Cross
返回数组arr1与数组arr2的交叉周期数。
_Cross(arr1, arr2)示例
可以模拟一组数据来测试_Cross(Arr1, Arr2)函数:
javascript
// 快线指标
var arr1 = [1,2,3,4,5,6,8,8,9]
// 慢线指标
var arr2 = [2,3,4,5,6,7,7,7,7]
function main(){
Log("_Cross(arr1, arr2) : ", _Cross(arr1, arr2))
Log("_Cross(arr2, arr1) : ", _Cross(arr2, arr1))
}
python
arr1 = [1,2,3,4,5,6,8,8,9]
arr2 = [2,3,4,5,6,7,7,7,7]
def main():
Log("_Cross(arr1, arr2) : ", _Cross(arr1, arr2))
Log("_Cross(arr2, arr1) : ", _Cross(arr2, arr1))
rust
fn main() {
// 快线指标
let arr1 = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 8.0, 8.0, 9.0];
// 慢线指标
let arr2 = [2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 7.0, 7.0, 7.0];
Log!("_Cross(arr1, arr2) : ", _Cross(&arr1, &arr2));
Log!("_Cross(arr2, arr1) : ", _Cross(&arr2, &arr1));
}
c++
void main() {
vector<double> arr1 = {1,2,3,4,5,6,8,8,9};
vector<double> arr2 = {2,3,4,5,6,7,7,7,7};
Log("_Cross(arr1, arr2) : ", _Cross(arr1, arr2));
Log("_Cross(arr2, arr1) : ", _Cross(arr2, arr1));
}返回值
| 类型 | 描述 |
number | 数组 |
参数
| 名称 | 类型 | 必填 | 描述 |
arr1 | array | 是 | 元素为 |
arr2 | array | 是 | 元素为 |
备注
_Cross()函数的返回值为正数时表示上穿的周期数,为负数时表示下穿的周期数,为0时表示当前价格相等。详细使用说明请参阅:内置函数_Cross分析及使用说明。
JSON.parse
JSON.parse函数是ECMAScript标准内建对象JSON的方法,用于解码(解析)JSON字符串。发明者量化交易平台在此基础上为其扩展了一个参数safeStr。
JSON.parse(s)
JSON.parse(s, safeStr)示例
解码(解析)一个包含大数值的JSON字符串。
javascript
function main() {
let s1 = '{"num": 8754613216564987646512354656874651651358}'
Log("JSON.parse:", JSON.parse(s1)) // JSON.parse: {"num":8.754613216564987e+39}
Log("JSON.parse:", JSON.parse(s1, true)) // JSON.parse: {"num":"8754613216564987646512354656874651651358"}
let s2 = '{"num": 123}'
Log("JSON.parse:", JSON.parse(s2)) // JSON.parse: {"num":123}
Log("JSON.parse:", JSON.parse(s2, true)) // JSON.parse: {"num":123}
}
python
# 可以使用Python的第三方库处理大数值数据。
rust
fn main() {
// Rust 使用 JSONParse() 函数解析JSON字符串,没有 safeStr 参数
// 超出精度范围的大数值会被解析为 f64 ,可能丢失精度
let s1 = r#"{"num": 8754613216564987646512354656874651651358}"#;
Log!("JSONParse:", JSONParse(s1).unwrap()["num"].as_f64().unwrap_or(0.0)); // JSONParse: 8.754613216564987e39
let s2 = r#"{"num": 123}"#;
Log!("JSONParse:", JSONParse(s2).unwrap()["num"].as_f64().unwrap_or(0.0)); // JSONParse: 123
}
c++
// 可以使用其它方案处理。返回值
| 类型 | 描述 |
object | 返回值为 |
参数
| 名称 | 类型 | 必填 | 描述 |
s | string | 是 | 该参数为需要解码(解析)的 |
safeStr | bool | 否 | 当该参数设置为 |
备注
JSON.parse()函数能够正确解析包含较大数值的JSON字符串;当safeStr参数设置为真值时,会将较大的数值解析为字符串类型。
safeStr参数位同样支持传入reviver参数,即一个用于转换结果的函数,该函数会针对对象的每个成员调用一次;具体用法可查阅相关资料,此处不再赘述。
仅支持JavaScript语言。
回测系统中不支持JSON.parse()函数的safeStr参数功能。
JSON.stringify
JSON.stringify函数是ECMAScript标准内置对象JSON的方法,用于将JavaScript值转换为JSON字符串。
JSON.stringify(obj)示例
将对象序列化为JSON字符串并输出。
javascript
function main() {
let s1 = {"num": "8754613216564987646512354656874651651358"}
Log("JSON.stringify:", JSON.stringify(s1))
// JSON.stringify: {"num":"8754613216564987646512354656874651651358"}
// JSON.stringify(s1) 返回的变量为一个字符串类型
}
python
// 略
c++
// 略返回值
| 类型 | 描述 |
string | 返回序列化后的 |
参数
| 名称 | 类型 | 必填 | 描述 |
obj | string / number / bool / object / array / function / any (平台支持的任意类型) | 是 | 需要序列化为JSON字符串的值。 |
备注
仅支持JavaScript语言。
SetChannelData
在频道上发布最新的状态数据。该函数用于实盘之间的通信,可将当前实盘的状态数据广播到频道上,供其他实盘订阅获取。
SetChannelData(data)示例
-
频道广播端示例 - 发布 BTC 行情价格数据
javascriptfunction main() { var updateId = 0 var robotId = _G() // 获取当前实盘 ID while(true) { // 获取实时市场价格 var ticker = exchange.GetTicker("BTC_USDT") if (!ticker) { Sleep(5000) continue } // 构造当前频道状态数据 var channelState = { robotId: robotId, updateId: ++updateId, timestamp: Date.now(), symbol: "BTC_USDT", lastPrice: ticker.Last, volume: ticker.Volume, high: ticker.High, low: ticker.Low } // 在频道上发布最新状态(覆盖旧状态) SetChannelData(channelState) // 显示当前频道状态 LogStatus("Channel Broadcaster [Bot ID: " + robotId + "]\n" + "Update ID: #" + channelState.updateId + "\n" + "Time: " + _D(channelState.timestamp) + "\n" + "Symbol: " + channelState.symbol + "\n" + "Last Price: $" + channelState.lastPrice.toFixed(2) + "\n" + "Volume: " + channelState.volume.toFixed(4) + "\n" + "High: $" + channelState.high.toFixed(2) + "\n" + "Low: $" + channelState.low.toFixed(2)) Sleep(60000) // 每分钟更新一次频道状态 } }pythondef main(): updateId = 0 robotId = _G() # 获取当前实盘 ID while True: # 获取实时市场价格 ticker = exchange.GetTicker("BTC_USDT") if not ticker: Sleep(5000) continue # 构造当前频道状态数据 channelState = { "robotId": robotId, "updateId": updateId + 1, "timestamp": time.time() * 1000, "symbol": "BTC_USDT", "lastPrice": ticker["Last"], "volume": ticker["Volume"], "high": ticker["High"], "low": ticker["Low"] } updateId += 1 # 在频道上发布最新状态(覆盖旧状态) SetChannelData(channelState) # 显示当前频道状态 LogStatus("Channel Broadcaster [Bot ID: {}]\n".format(robotId) + "Update ID: #{}\n".format(channelState["updateId"]) + "Time: {}\n".format(_D(channelState["timestamp"])) + "Symbol: {}\n".format(channelState["symbol"]) + "Last Price: ${:.2f}\n".format(channelState["lastPrice"]) + "Volume: {:.4f}\n".format(channelState["volume"]) + "High: ${:.2f}\n".format(channelState["high"]) + "Low: ${:.2f}".format(channelState["low"])) Sleep(60000) # 每分钟更新一次频道状态rustfn main() { let mut updateId = 0; let robotId = _G!(); // 获取当前实盘 ID loop { // 获取实时市场价格 let ticker = match exchange.GetTicker("BTC_USDT") { Ok(t) => t, Err(_) => { Sleep(5000); continue; } }; // 构造当前频道状态数据 // Rust 的 SetChannelData 仅接受字符串参数,因此使用 format! 构造 JSON 文本 updateId += 1; let timestamp = Unix() * 1000; let channelState = format!( r#"{{"robotId": {}, "updateId": {}, "timestamp": {}, "symbol": "BTC_USDT", "lastPrice": {}, "volume": {}, "high": {}, "low": {}}}"#, robotId, updateId, timestamp, ticker.Last, ticker.Volume, ticker.High, ticker.Low ); // 在频道上发布最新状态(覆盖旧状态) SetChannelData(&channelState); // 显示当前频道状态 LogStatus!(format!( "Channel Broadcaster [Bot ID: {}]\nUpdate ID: #{}\nTime: {}\nSymbol: BTC_USDT\nLast Price: ${:.2}\nVolume: {:.4}\nHigh: ${:.2}\nLow: ${:.2}", robotId, updateId, _D(timestamp), ticker.Last, ticker.Volume, ticker.High, ticker.Low )); Sleep(60000); // 每分钟更新一次频道状态 } }c++ -
跨平台发送示例 - 模拟外部平台(如 TradingView)向 FMZ 实盘发送数据
javascript// 此示例演示如何使用 HttpQuery 发送 HTTP POST 请求,模拟外部平台向 FMZ 实盘发送数据 // 在实际场景中,外部平台(如 TradingView 的 Webhook 告警 URL、第三方交易系统等)会直接调用 FMZ API 端点 function main() { let uuid = "6BC42A119B5DBFA2188A8279DA3B5C30" let robotId = 123456 // 目标实盘 ID(用于接收数据的实盘) let baseUrl = "https://www.fmz.com" while (true) { // 准备待发送的数据(可以是 JSON、文本或其他格式) let sendData = { "action": "buy", "symbol": "BTC_USDT", "price": 50000, "timestamp": Date.now() } // 构造 HTTP POST 请求 let options = { method: "POST", body: JSON.stringify(sendData) // body 可以是 JSON 字符串、普通文本等 } let url = `${baseUrl}/api/v1?method=pub&robot=${robotId}&channel=${uuid}` // 发送数据 let ret = HttpQuery(url, options) Log("Simulated external platform sending data, result:", ret) Sleep(10000) // 每 10 秒发送一次 } }python# 此示例演示如何使用 HttpQuery 发送 HTTP POST 请求,模拟外部平台向 FMZ 实盘发送数据 # 在实际场景中,外部平台(如 TradingView 的 Webhook 告警 URL、第三方交易系统等)会直接调用 FMZ API 端点 import json def main(): uuid = "6BC42A119B5DBFA2188A8279DA3B5C30" robotId = 123456 # 目标实盘 ID(用于接收数据的实盘) baseUrl = "https://www.fmz.com" while True: # 准备待发送的数据(可以是 JSON、文本或其他格式) sendData = { "action": "buy", "symbol": "BTC_USDT", "price": 50000, "timestamp": time.time() * 1000 } # 构造 HTTP POST 请求 options = { "method": "POST", "body": json.dumps(sendData) # body 可以是 JSON 字符串、普通文本等 } url = "{}/api/v1?method=pub&robot={}&channel={}".format(baseUrl, robotId, uuid) # 发送数据 ret = HttpQuery(url, options) Log("Simulated external platform sending data, result:", ret) Sleep(10000) # 每 10 秒发送一次rust// 此示例演示如何使用 HttpQuery 发送 HTTP POST 请求,模拟外部平台向 FMZ 实盘发送数据 // 在实际场景中,外部平台(如 TradingView 的 Webhook 告警 URL、第三方交易系统等)会直接调用 FMZ API 端点 fn main() { let uuid = "6BC42A119B5DBFA2188A8279DA3B5C30"; let robotId = 123456; // 目标实盘 ID(用于接收数据的实盘) let baseUrl = "https://www.fmz.com"; loop { // 准备待发送的数据(可以是 JSON、文本或其他格式) let sendData = format!( r#"{{"action": "buy", "symbol": "BTC_USDT", "price": 50000, "timestamp": {}}}"#, Unix() * 1000 ); // 构造 HTTP POST 请求,{:?} 会将 body 转义为合法的 JSON 字符串值 let options = format!(r#"{{"method": "POST", "body": {:?}}}"#, sendData); let url = format!("{}/api/v1?method=pub&robot={}&channel={}", baseUrl, robotId, uuid); // 发送数据 let ret: String = HttpQuery(&url, options.as_str()); Log!("Simulated external platform sending data, result:", ret); Sleep(10000); // 每 10 秒发送一次 } }c++
返回值
| 类型 | 描述 |
空值 | 该函数无返回值。 |
参数
| 名称 | 类型 | 必填 | 描述 |
data | object / array / string / number / bool / 空值 | 是 | 需要发布到频道的数据,可以是任何支持 |
参考
备注
SetChannelData()函数为非阻塞调用,调用后立即返回,不会等待数据传输完成。
每个实盘都拥有一个独立的频道,频道ID即为实盘ID(可通过_G()函数获取)。
频道上仅保存最新的状态数据,每次调用SetChannelData()都会覆盖之前发布的数据,而非追加历史消息。
频道数据支持跨实盘、跨托管者、跨服务器进行广播,多个实盘可以订阅同一个频道。
订阅端使用GetChannelData()函数订阅频道数据。
频道通信适用于实盘环境,在回测系统中该功能可能受到限制。
传入的数据参数data在JSON序列化后的字节长度不得超过1024字节,超出限制可能导致数据发布失败。建议仅传输必要的状态信息,避免传输过大的数据对象。
发布的数据应根据硬件设备的内存和网络带宽合理使用,避免发布过大的数据对象。
SetChannelData()函数发布的数据不仅可以被FMZ平台内的其他实盘订阅,还支持跨平台数据发送功能。外部平台(如TradingView的Webhook告警、第三方交易系统、监控软件等)可以通过HTTP POST请求向指定的FMZ实盘发送数据。
跨平台发送数据的方式: 外部系统通过HTTP POST请求将数据发送到FMZ平台API端点:https://www.fmz.com/api/v1?method=pub&robot={robotId}&channel={uuid},其中robotId为目标实盘ID,uuid为32位字符的频道标识符。发送的数据在请求body中传递,可以是JSON格式、文本或其他格式。注意:必须先有实盘订阅该UUID频道,外部系统才能成功发送数据;广播的数据会发送到robotId实盘所在托管者下的所有实盘,同一托管者下订阅了该UUID频道的实盘均可接收数据。
GetChannelData
订阅指定实盘的频道数据。该函数用于实盘间通信,可获取其他实盘通过SetChannelData()函数发布的最新状态数据。
GetChannelData(channelId)示例
-
频道订阅端示例 - 订阅两个实盘的频道数据
javascriptfunction main() { // 需要订阅的两个频道 ID(请根据实际情况修改) var channelId1 = "632799" // 频道 1 的实盘 ID var channelId2 = "632800" // 频道 2 的实盘 ID while(true) { // 订阅频道 1 的当前状态 var state1 = GetChannelData(channelId1) // 订阅频道 2 的当前状态 var state2 = GetChannelData(channelId2) // 构建状态显示 var statusMsg = "频道订阅端 - 当前订阅状态\n\n" // 显示频道 1 状态 statusMsg += "═══ 频道1 [" + channelId1 + "] ═══\n" if (state1 !== null) { statusMsg += "更新ID: #" + state1.updateId + "\n" statusMsg += "时间: " + _D(state1.timestamp) + "\n" statusMsg += "交易对: " + state1.symbol + "\n" statusMsg += "最新价: $" + state1.lastPrice.toFixed(2) + "\n" statusMsg += "成交量: " + state1.volume.toFixed(4) + "\n" } else { statusMsg += "状态: 等待中... (首次调用返回 null)\n" } statusMsg += "\n" // 显示频道 2 状态 statusMsg += "═══ 频道2 [" + channelId2 + "] ═══\n" if (state2 !== null) { statusMsg += "更新ID: #" + state2.updateId + "\n" statusMsg += "时间: " + _D(state2.timestamp) + "\n" statusMsg += "交易对: " + state2.symbol + "\n" statusMsg += "最新价: $" + state2.lastPrice.toFixed(2) + "\n" statusMsg += "成交量: " + state2.volume.toFixed(4) + "\n" } else { statusMsg += "状态: 等待中... (首次调用返回 null)\n" } LogStatus(statusMsg) Sleep(5000) // 每 5 秒订阅一次频道 } }pythondef main(): # 需要订阅的两个频道 ID(请根据实际情况修改) channelId1 = "632799" # 频道 1 的实盘 ID channelId2 = "632800" # 频道 2 的实盘 ID while True: # 订阅频道 1 的当前状态 state1 = GetChannelData(channelId1) # 订阅频道 2 的当前状态 state2 = GetChannelData(channelId2) # 构建状态显示 statusMsg = "频道订阅端 - 当前订阅状态\n\n" # 显示频道 1 状态 statusMsg += "═══ 频道1 [{}] ═══\n".format(channelId1) if state1 is not None: statusMsg += "更新ID: #{}\n".format(state1["updateId"]) statusMsg += "时间: {}\n".format(_D(state1["timestamp"])) statusMsg += "交易对: {}\n".format(state1["symbol"]) statusMsg += "最新价: ${:.2f}\n".format(state1["lastPrice"]) statusMsg += "成交量: {:.4f}\n".format(state1["volume"]) else: statusMsg += "状态: 等待中... (首次调用返回 None)\n" statusMsg += "\n" # 显示频道 2 状态 statusMsg += "═══ 频道2 [{}] ═══\n".format(channelId2) if state2 is not None: statusMsg += "更新ID: #{}\n".format(state2["updateId"]) statusMsg += "时间: {}\n".format(_D(state2["timestamp"])) statusMsg += "交易对: {}\n".format(state2["symbol"]) statusMsg += "最新价: ${:.2f}\n".format(state2["lastPrice"]) statusMsg += "成交量: {:.4f}\n".format(state2["volume"]) else: statusMsg += "状态: 等待中... (首次调用返回 None)\n" LogStatus(statusMsg) Sleep(5000) # 每 5 秒订阅一次频道rustfn main() { // Rust 的 GetChannelData() 函数不接受频道 ID 参数,只能读取当前实盘自身频道 // (即本实盘通过 SetChannelData() 发布)的最新数据,无法订阅其它实盘的频道 loop { // 订阅频道的当前状态 let state = GetChannelData(); // 构建状态显示 let mut statusMsg = String::from("频道订阅端 - 当前订阅状态\n\n"); if !state.is_null() { statusMsg += &format!("更新ID: #{}\n", state["updateId"].as_i64().unwrap_or(0)); statusMsg += &format!("时间: {}\n", _D(state["timestamp"].as_i64().unwrap_or(0))); statusMsg += &format!("交易对: {}\n", state["symbol"].as_str().unwrap_or("")); statusMsg += &format!("最新价: ${:.2}\n", state["lastPrice"].as_f64().unwrap_or(0.0)); statusMsg += &format!("成交量: {:.4}\n", state["volume"].as_f64().unwrap_or(0.0)); } else { statusMsg += "状态: 等待中... (首次调用返回 null)\n"; } LogStatus!(statusMsg); Sleep(5000); // 每 5 秒订阅一次频道 } }c++ -
跨平台订阅示例 - 使用 UUID 订阅外部系统发送的数据
javascriptfunction main() { // 使用 32 位 UUID 作为频道标识符 let uuid = "6BC42A119B5DBFA2188A8279DA3B5C30" while (true) { // 订阅 UUID 频道的数据 let data = GetChannelData(uuid) if (data !== null) { Log("Received cross-platform data:", data) } else { Log("Waiting for data... (first call returns null)") } Sleep(10000) // 每 10 秒检查一次 } }pythondef main(): # 使用 32 位 UUID 作为频道标识符 uuid = "6BC42A119B5DBFA2188A8279DA3B5C30" while True: # 订阅 UUID 频道的数据 data = GetChannelData(uuid) if data is not None: Log("Received cross-platform data:", data) else: Log("Waiting for data... (first call returns None)") Sleep(10000) # 每 10 秒检查一次rustfn main() { // Rust 的 GetChannelData() 函数不接受频道 ID 参数,无法使用 32 位 UUID 订阅跨平台数据, // 只能读取当前实盘自身频道(即本实盘通过 SetChannelData() 发布)的最新数据 loop { // 订阅频道的数据 let data = GetChannelData(); if !data.is_null() { Log!("Received cross-platform data:", data); } else { Log!("Waiting for data... (first call returns null)"); } Sleep(10000); // 每 10 秒检查一次 } }c++
返回值
| 类型 | 描述 |
object / array / string / number / bool / 空值 | 返回所订阅频道的最新状态数据。首次调用时返回 |
参数
| 名称 | 类型 | 必填 | 描述 |
channelId | string / number | 是 | 频道标识符,支持以下两种类型:
|
参考
备注
GetChannelData()函数为非阻塞调用,调用后立即返回,不会等待数据接收完成。
首次调用GetChannelData()函数时会返回null,需要重试并等待频道数据同步完成。
每次调用获取的均为频道上的最新状态数据,而非历史消息队列。
一个实盘可同时订阅多个不同实盘的频道,只需多次调用GetChannelData()并分别传入不同的实盘ID即可。
当前实盘也可以订阅自身的频道,即robotId参数可以是当前实盘的ID。
频道数据可跨实盘、跨托管者、跨服务器进行传输。
广播端使用SetChannelData()函数发布频道数据。
频道通信适用于实盘环境,在回测系统中该功能可能受限。
GetChannelData()函数支持跨平台订阅功能。当使用32位UUID作为频道标识符时,可接收来自FMZ平台外部系统通过HTTP API发送的数据。外部系统需同时指定实盘ID和UUID才能发送数据;同一托管者下的所有实盘均可订阅该UUID频道的数据,而不同托管者的实盘则无法订阅。