输入/搜索内容
内置函数
Global
Version
Sleep
IsVirtual
Mail
Mail_Go
SetErrorFilter
GetPid
GetLastError
GetCommand
GetMeta
Dial
HttpQuery
HttpQuery_Go
Encode
UnixNano
Unix
GetOS
MD5
DBExec
UUID
EventLoop
__Serve
_G
_D
_N
_C
_Cross
JSON.parse
JSON.stringify
SetChannelData
GetChannelData
Log
Market
Trade
Account
Futures
NetSettings
Threads
threading
Thread
getThread
mainThread
currentThread
Lock
Condition
Event
Dict
pending
Thread
ThreadLock
ThreadEvent
ThreadCondition
ThreadDict
Web3
TA
Talib
talib.CDL2CROWS
talib.CDL3BLACKCROWS
talib.CDL3INSIDE
talib.CDL3LINESTRIKE
talib.CDL3OUTSIDE
talib.CDL3STARSINSOUTH
talib.CDL3WHITESOLDIERS
talib.CDLABANDONEDBABY
talib.CDLADVANCEBLOCK
talib.CDLBELTHOLD
talib.CDLBREAKAWAY
talib.CDLCLOSINGMARUBOZU
talib.CDLCONCEALBABYSWALL
talib.CDLCOUNTERATTACK
talib.CDLDARKCLOUDCOVER
talib.CDLDOJI
talib.CDLDOJISTAR
talib.CDLDRAGONFLYDOJI
talib.CDLENGULFING
talib.CDLEVENINGDOJISTAR
talib.CDLEVENINGSTAR
talib.CDLGAPSIDESIDEWHITE
talib.CDLGRAVESTONEDOJI
talib.CDLHAMMER
talib.CDLHANGINGMAN
talib.CDLHARAMI
talib.CDLHARAMICROSS
talib.CDLHIGHWAVE
talib.CDLHIKKAKE
talib.CDLHIKKAKEMOD
talib.CDLHOMINGPIGEON
talib.CDLIDENTICAL3CROWS
talib.CDLINNECK
talib.CDLINVERTEDHAMMER
talib.CDLKICKING
talib.CDLKICKINGBYLENGTH
talib.CDLLADDERBOTTOM
talib.CDLLONGLEGGEDDOJI
talib.CDLLONGLINE
talib.CDLMARUBOZU
talib.CDLMATCHINGLOW
talib.CDLMATHOLD
talib.CDLMORNINGDOJISTAR
talib.CDLMORNINGSTAR
talib.CDLONNECK
talib.CDLPIERCING
talib.CDLRICKSHAWMAN
talib.CDLRISEFALL3METHODS
talib.CDLSEPARATINGLINES
talib.CDLSHOOTINGSTAR
talib.CDLSHORTLINE
talib.CDLSPINNINGTOP
talib.CDLSTALLEDPATTERN
talib.CDLSTICKSANDWICH
talib.CDLTAKURI
talib.CDLTASUKIGAP
talib.CDLTHRUSTING
talib.CDLTRISTAR
talib.CDLUNIQUE3RIVER
talib.CDLUPSIDEGAP2CROWS
talib.CDLXSIDEGAP3METHODS
talib.AD
talib.ADOSC
talib.OBV
talib.ACOS
talib.ASIN
talib.ATAN
talib.CEIL
talib.COS
talib.COSH
talib.EXP
talib.FLOOR
talib.LN
talib.LOG10
talib.SIN
talib.SINH
talib.SQRT
talib.TAN
talib.TANH
talib.MAX
talib.MAXINDEX
talib.MIN
talib.MININDEX
talib.MINMAX
talib.MINMAXINDEX
talib.SUM
talib.HT_DCPERIOD
talib.HT_DCPHASE
talib.HT_PHASOR
talib.HT_SINE
talib.HT_TRENDMODE
talib.ATR
talib.NATR
talib.TRANGE
talib.BBANDS
talib.DEMA
talib.EMA
talib.HT_TRENDLINE
talib.KAMA
talib.MA
talib.MAMA
talib.MIDPOINT
talib.MIDPRICE
talib.SAR
talib.SAREXT
talib.SMA
talib.T3
talib.TEMA
talib.TRIMA
talib.WMA
talib.LINEARREG
talib.LINEARREG_ANGLE
talib.LINEARREG_INTERCEPT
talib.LINEARREG_SLOPE
talib.STDDEV
talib.TSF
talib.VAR
talib.ADX
talib.ADXR
talib.APO
talib.AROON
talib.AROONOSC
talib.BOP
talib.CCI
talib.CMO
talib.DX
talib.MACD
talib.MACDEXT
talib.MACDFIX
talib.MFI
talib.MINUS_DI
talib.MINUS_DM
talib.MOM
talib.PLUS_DI
talib.PLUS_DM
talib.PPO
talib.ROC
talib.ROCP
talib.ROCR
talib.ROCR100
talib.RSI
talib.STOCH
talib.STOCHF
talib.STOCHRSI
talib.TRIX
talib.ULTOSC
talib.WILLR
talib.AVGPRICE
talib.MEDPRICE
talib.TYPPRICE
talib.WCLPRICE
OS
结构体
内置变量

返回当前系统版本号。

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

当前系统版本号,例如:3.6

备注

系统版本号即托管者程序的版本号。

休眠函数,使程序暂停运行一段指定的时间。

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

millisecond参数用于设置休眠时长,单位为毫秒。

备注

例如,执行Sleep(1000)函数时,程序将休眠1秒。该函数支持小于1毫秒的休眠操作,例如Sleep(0.1)。支持的最小参数为0.000001,即纳秒级休眠,1纳秒等于1e-6毫秒。

在使用Python语言编写策略时,对于轮询间隔、时间等待等操作,应当使用Sleep(millisecond)函数,而不建议使用Pythontime库的time.sleep(second)函数。因为策略在回测时若使用time.sleep(second)函数,会使策略程序实际等待一段时间(而非在回测系统的时间序列上跳过),从而导致回测速度非常缓慢。

用于判断策略的运行环境是否为回测系统。

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

当策略运行在回测系统环境中时,返回真值,例如:true;当策略运行在实盘环境中时,返回假值,例如:false

备注

用于判断当前运行环境是否为回测系统,以便兼容回测与实盘环境之间的差异。

发送邮件。

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

邮件发送成功时返回真值,例如true;发送失败时返回假值,例如false

参数

名称类型必填描述

smtpServer

string

用于指定邮件发送方的SMTP服务器地址。

smtpUsername

string

用于指定邮件发送方的邮箱地址。

smtpPassword

string

用于指定邮件发送方邮箱的SMTP服务密码。

mailTo

string

用于指定邮件接收方的邮箱地址。

title

string

邮件标题。

body

string

邮件正文。

参考

备注

smtpPassword参数设置的是SMTP服务的密码,而非邮箱登录密码。

设置smtpServer参数时,如需更改端口,可直接在smtpServer参数中附加端口号。例如:QQ 邮箱的smtp.qq.com:587端口经测试可用。

如果出现报错unencryped connection,则需要修改Mail函数的smtpServer参数,其格式为ssl://xxx.com:xxx。例如,QQ 邮箱SMTPssl方式为ssl://smtp.qq.com:465,或使用smtp://xxx.com:xxx

该函数在回测系统中不起作用。

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

Mail_Go函数立即返回一个并发对象,可以使用该并发对象的wait方法获取邮件发送结果。邮件发送成功返回真值(例如:true),发送失败返回假值(例如:false)。

参数

名称类型必填描述

smtpServer

string

用于指定邮件发送方的SMTP服务器地址。

smtpUsername

string

用于指定邮件发送方的邮箱地址。

smtpPassword

string

邮件发送方邮箱的SMTP授权密码。

mailTo

string

用于指定邮件接收方的邮箱地址。

title

string

邮件标题。

body

string

邮件正文内容。

参考

备注

在回测系统中不起作用。

过滤错误日志。

SetErrorFilter(filters)

示例

  • 过滤常见错误。

    javascript
    function main() { SetErrorFilter("502:|503:|tcp|character|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF|reused") }
    python
    def main(): SetErrorFilter("502:|503:|tcp|character|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF|reused")
    rust
    fn 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"); }
  • 过滤指定接口的错误信息。

    javascript
    function main() { // 查询一个不存在的订单(id 为 123),故意触发接口报错 var order = exchange.GetOrder("123") Log(order) // 过滤 http 502 错误和 GetOrder 接口错误;设置错误过滤后,第二次调用 GetOrder 将不再报错 SetErrorFilter("502:|GetOrder") order = exchange.GetOrder("123") Log(order) }
    python
    def main(): order = exchange.GetOrder("123") Log(order) SetErrorFilter("502:|GetOrder") order = exchange.GetOrder("123") Log(order)
    rust
    fn 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 的数据库文件中,从而防止因频繁报错导致数据库文件膨胀。

获取实盘进程的 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()

示例

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()

示例

  • 检测交互命令,并在检测到交互命令时使用Log函数将其输出。

    javascript
    function main(){ while(true) { var cmd = GetCommand() if (cmd) { Log(cmd) } Sleep(1000) } }
    python
    def main(): while True: cmd = GetCommand() if cmd: Log(cmd) Sleep(1000)
    rust
    fn 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,控件描述信息为卖出,这是一个由按钮和输入框组合而成的交互控件。在策略中编写交互代码,以响应不同的交互控件:

    javascript
    function 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) } }
    python
    def 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)
    rust
    fn 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

返回的命令格式为ControlName:Data,其中ControlName为控件名称,Data为控件中输入的数据。如果交互控件不包含输入框、下拉框等输入组件(例如:不带输入框的按钮控件),则返回的命令格式为ControlName,即仅返回控件名称。

备注

该函数在回测系统中无效。

获取在生成策略注册码时写入的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数据。

备注

应用场景:需要对不同的策略租用者进行资金限制。生成注册码时设置的Meta值长度不能超过190个字符。GetMeta()函数仅支持实盘,在回测系统中不起作用。如果生成策略注册码时未设置元数据(Meta),GetMeta()函数将返回空值。

用于原始 Socket 访问,支持 tcpudptlsunix 协议。支持 4 种主流通信协议:mqttnatsamqpkafka。同时支持连接数据库,可用的数据库包括:sqlite3mysqlpostgresclickhouse

Dial(address)
Dial(address, timeout)
Dial(address, options)

示例

  • Dial 函数调用示例:

    javascript
    function 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() } }
    python
    def 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()
    rust
    fn 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 行情接口:

    javascript
    function 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() }
    python
    import 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()
    rust
    fn 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 请求头。

    javascript
    function 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&timestamp=${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) } }
    python
    import 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&timestamp={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)
    rust
    fn 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&timestamp={}", 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 行情接口:

    javascript
    var 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") }
    python
    import 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")
    rust
    fn 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 行情接口:

    javascript
    var 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()") }
    python
    import 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()")
    rust
    fn 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 验证接口:

    javascript
    function 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) }
    python
    import 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)
    rust
    fn 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 验证接口:

    javascript
    var 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
    // 略
    rust
    fn 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解码二进制数据:

    javascript
    let 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数据库的示例。

    javascript
    var 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
    // 不支持
    rust
    fn 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

如果超时,Dial() 函数返回空值;正常调用时返回一个连接对象。该连接对象包含三个方法:readwriteclose。其中,read 方法用于读取数据,write 方法用于发送数据,close 方法用于关闭连接。read 方法支持以下参数:

  • 不传参数时,函数会阻塞,直到有消息到达时才返回。例如:ws.read()
  • 传入参数时,单位为毫秒,用于指定消息等待的超时时间。例如:ws.read(2000) 表示超时时间为两秒(2000 毫秒)。
  • 以下两个参数仅对 WebSocket 有效:
    传入参数 -1 表示无论是否有消息,函数都立即返回。例如:ws.read(-1)
    传入参数 -2 表示无论是否有消息,函数都立即返回,但只返回最新的消息,缓冲区中的其余消息将被丢弃。例如:ws.read(-2)read() 函数缓冲区说明:

WebSocket 协议推送的数据,如果策略中 read() 函数两次调用之间的时间间隔过长,就可能造成数据累积。这些数据存储在缓冲区中,缓冲区的数据结构为队列,上限为 2000 个。当数据量超过 2000 个后,最新的数据进入缓冲区,最旧的数据将被清除。

场景无参数参数:-1参数:-2参数:2000,单位是毫秒
缓冲区已有数据立即返回最旧数据立即返回最旧数据立即返回最新数据立即返回最旧数据
缓冲区无数据阻塞至有数据时返回立即返回空值立即返回空值等待 2000 毫秒,无数据则返回空值,有数据则返回
WebSocket 连接断开或底层重连时read() 函数返回空字符串(即 ""),write() 函数返回 0,可据此检测该情况。此时可使用 close() 函数关闭连接;如果已设置自动重连,则无需手动关闭,系统底层会自动重连。
------

参数

名称类型必填描述

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)备注
sqlite3github.com/mattn/go-sqlite3sqlite3://file:test.db?cache=shared&mode=memorysqlite3://前缀表示使用的是sqlite3数据库,调用示例:Dial("sqlite3://test1.db")
mysqlgithub.com/go-sql-driver/mysqlmysql://username:yourpassword@tcp(localhost:3306)/yourdatabase?charset=utf8mb4--
postgresgithub.com/lib/pqpostgres://user=postgres dbname=yourdatabase sslmode=disable password=yourpassword host=localhost port=5432--
clickhousegithub.com/ClickHouse/clickhouse-goclickhouse://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&timestamp=" + 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函数中使用mqttnatsamqpkafka通信协议,下面以JavaScript语言策略代码为例,演示mqttnatsamqpkafka四种协议的使用方法:

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:交易策略实盘间通信协议实践

发送 HTTP 请求。

HttpQuery(url)
HttpQuery(url, options)

示例

  • 访问 OKX 公共行情 API 接口的示例。

    javascript
    function 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) }
    python
    import 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)
    rust
    fn 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 函数使用代理设置的示例。

    javascript
    function 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 库
    rust
    fn 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

返回请求的应答数据。如果返回值为JSON字符串,在JavaScript语言的策略中可以使用JSON.parse()函数解析,在C++语言的策略中可以使用json::parse()函数解析。在参数options结构中,如果debug设置为 true,返回值为对象(JSON);如果debug设置为 false,返回值为字符串。

参数

名称类型必填描述

url

string

HTTP 请求的 URL。

options

object

HTTP 请求的相关设置,例如可以是以下结构:

json
{ method: "POST", body: "a=10&b=20&c=30", charset: "UTF-8", cookie: "session_id=12345; lang=en", debug: false, headers: {"TEST-HTTP-QUERY": "123"}, timeout: 1000 }
  • method: 用于设置请求方法。
  • body: 用于设置请求体内容,通常用于 POST、PUT 等请求。
  • cookie: 用于设置请求中的 Cookie,通常用于携带身份验证信息或会话标识。
  • headers: 用于设置请求头信息,可用于指定内容类型、身份验证信息等。
  • debug: 设置为true时,本次HttpQuery函数调用返回完整的应答报文;设置为false时,仅返回应答报文Body中的数据。
  • timeout: 超时设置,设置为 1000 表示超时时间为 1 秒。
  • charset: 支持对请求的应答数据进行转码,例如:GB18030,支持常用编码。

此结构中的所有字段均为可选,例如可以不设置headers字段。

参考

备注

HttpQuery()函数仅支持JavaScriptC++语言;Python语言可以使用urllib库直接发送 HTTP 请求。HttpQuery()主要用于访问交易所无需签名的接口,例如行情信息等公共接口。

回测系统中可以使用HttpQuery()发送请求(仅支持GET请求)以获取数据。回测时限制访问不同URL的次数为 20 次,并且HttpQuery()的访问会缓存数据;相同的URL第二次访问时,HttpQuery()函数将返回缓存数据,不再发起实际的网络请求。

发送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

HttpQuery_Go()函数立即返回一个并发对象,可以使用该并发对象的wait方法获取HTTP请求的结果。JavaScript语言的策略中可以使用JSON.parse()函数解析返回的数据。

参数

名称类型必填描述

url

string

HTTP请求的URL地址。

options

object

HTTP请求相关配置参数,可以使用以下结构:

json
{ method: "POST", body: "a=10&b=20&c=30", charset: "UTF-8", cookie: "session_id=12345; lang=en", debug: false, headers: {"TEST-HTTP-QUERY": "123"}, timeout: 1000 }
  • debug:设置为true时,此次HttpQuery_Go函数调用返回完整的响应报文。设置为false时仅返回响应报文Body中的数据。
  • timeout:超时设置,设置1000表示1秒超时。

此结构中的所有字段均为可选字段,例如可以不设置headers字段。
HttpQuery_Go函数的options参数与HttpQuery函数的options参数一致,不再赘述。

参考

备注

HttpQuery_Go()函数仅支持JavaScript语言,Python语言可以使用urllib库直接发送HTTP请求。HttpQuery_Go()主要用于访问交易所无需签名的接口,例如行情信息等公共接口。回测系统不支持HttpQuery_Go函数。

该函数根据传入的参数对数据进行编码。

Encode(algo, inputFormat, outputFormat, data)
Encode(algo, inputFormat, outputFormat, data, keyFormat, key)

示例

  • Encode 函数调用示例。

    javascript
    function 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 }
    python
    def 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
    rust
    fn 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",用于对字符串进行编码和解码。

    javascript
    function 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) }
    python
    def 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)
    rust
    fn 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

Encode函数返回编码、加密之后的数据。

参数

名称类型必填描述

algo

string

参数algo用于指定编码计算时使用的算法,支持设置为以下值之一:"raw"(不使用算法)、"sign"、"signTx"、"md4"、"md5"、"sha256"、"sha512"、"sha1"、"keccak256"、"sha3.224"、"sha3.256"、"sha3.384"、"sha3.512"、"sha3.keccak256"、"sha3.keccak512"、"sha512.384"、"sha512.256"、"sha512.224"、"ripemd160"、"blake2b.256"、"blake2b.512"、"blake2s.128"、"blake2s.256"。

参数algo还支持"text.encoder.utf8"、"text.decoder.utf8"、"text.encoder.gbk"、"text.decoder.gbk",用于对字符串进行编码或解码。

参数algo同时支持"ed25519"算法,并可搭配不同的哈希算法使用,例如参数algo可写为"ed25519.md5"、"ed25519.sha512"等,也支持ed25519.seed计算。

inputFormat

string

用于指定data参数的数据格式。inputFormat参数支持设置为"raw"、"hex"、"base64"、"string"其中之一。"raw"表示原始数据,"hex"表示hex编码数据,"base64"表示base64编码数据,"string"表示字符串数据。

outputFormat

string

用于指定输出的数据格式。outputFormat参数支持设置为"raw"、"hex"、"base64"、"string"其中之一。"raw"表示原始数据,"hex"表示hex编码数据,"base64"表示base64编码数据,"string"表示字符串数据。

data

string

参数data为所要处理的数据。

keyFormat

string

用于指定key参数的数据格式。keyFormat参数支持设置为"raw"、"hex"、"base64"、"string"其中之一。"raw"表示原始数据,"hex"表示hex编码数据,"base64"表示base64编码数据,"string"表示字符串数据。

key

string

参数keyHMAC加密时使用的密钥。

当参数algo设置为"sign"或"signTx"时,需要传入参数key

当参数algo设置为"raw"时,不会使用key参数进行HMAC加密(因为HMAC加密必须指定算法)。

备注

Encode()函数仅支持实盘。若不传入keykeyFormat参数,则不进行key加密。

获取当前时刻的纳秒级时间戳。

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

UnixNano()函数返回纳秒级时间戳。

参考

获取当前时刻的秒级时间戳。

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()

示例

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。由于苹果电脑采用多种硬件架构,返回值中会附带具体的架构信息。其中,darwinMac OS系统的内核名称。

计算参数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(sql)

示例

  • 支持内存数据库。对于DBExec函数的参数,如果sql语句以:开头,则在内存数据库中执行操作;由于无需写入文件,速度更快。此方式适用于无需持久化保存的数据库操作,例如:

    javascript
    function 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;")) }
    python
    def 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;"))
    rust
    fn 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() 函数创建数据表。

    javascript
    function 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) }
    python
    def 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)
    rust
    fn 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); }
  • 对数据表中的记录执行增、删、查、改操作。

    javascript
    function 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)) }
    python
    def 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))
    rust
    fn 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语法。

    • 实盘数据库中的系统保留表包括:kvdbcfglogprofitchart,请勿对这些表进行操作。

    • 目前不支持事务,不建议执行此类操作,否则会引发系统冲突。

    • DBExec()函数仅支持实盘。

创建一个 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() 函数仅支持实盘。

监听事件,当任意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

如果返回的对象不为空值,则返回内容中的Event字段表示事件的触发类型。例如以下返回值结构:

json
{"Seq":1,"Event":"Exchange_GetTrades","ThreadId":0,"Index":3,"Nano":1682068771309583400}

参数

名称类型必填描述

timeout

number

参数timeout用于设置超时时间,单位为毫秒。
timeout设置为0时,函数会一直等待,直到有事件发生才返回;当timeout大于0时,表示设置事件等待的超时时间;当timeout小于0时,则立即返回最近的事件。

参考

备注

代码中首次调用EventLoop()函数时,才会初始化该事件监听机制。如果在事件回调发生之后才首次调用EventLoop(),则会错过此前的事件。系统底层封装的队列结构最多可缓存500个事件回调,如果程序运行过程中没有及时调用EventLoop()函数取出,超出500个缓存上限的较晚事件回调将会丢失。

EventLoop()函数的调用不会影响系统底层WebSocket的缓存队列,也不会影响exchange.Go()等并发函数的缓存,这些缓存中的数据仍需使用各自的方法取出。对于在EventLoop()函数返回之前已经取出的数据,不会在EventLoop()函数中再次产生返回事件。

EventLoop()函数的主要用途是通知策略层:系统底层已接收到新的网络数据,从而以事件驱动整个策略。当EventLoop()函数返回事件时,只需遍历所有数据来源(例如WebSocket连接、exchange.Go()创建的对象)尝试获取数据即可。

EventLoop()函数仅支持实盘。

在主函数main()中调用时,监听主线程的事件。在使用JavaScript语言编写的策略中,也可以在threading.Thread()函数创建的线程的执行函数中调用,用于监听当前线程的事件。

__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地址、端口。例如:127.0.0.1:8088[::]:8089

参数

名称类型必填描述

serveURI

string

serveURI参数用于配置服务绑定的协议、IP地址、端口等设置,例如:http://0.0.0.0:8088?gzip=true,即:http://:8088?gzip=true

  • TCP协议
    serveURI参数设置例如:tcp://127.0.0.1:6666?tls=true;可以添加证书、私钥例如:tls=true&cert_pem=xxxx&cert_key_pem=xxxx
  • HTTP协议
    serveURI参数设置例如:http://127.0.0.1:6666?gzip=true;可以设置压缩设置:gzip=true
    serveURI参数用于HTTPS例如:https://127.0.0.1:6666?tls=true&gzip=true;可以加入cert_pemcert_key_pem参数来加载证书。

handler

function

handler参数用于传入路由处理函数(HTTP协议)、消息处理函数(TCP协议)、Stream处理函数(WebSocket)。

参数handler传入的回调函数可以定义多个参数,第一个参数为ctx对象(上下文对象)。

arg

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

作为参数handler传入的回调函数的参数的实参,参数arg可能有多个,例如:

javascript
__Serve("http://:8088", function(ctx, a, b, c) { Log(`ctx.host():`, ctx.host(), ", a=", a, ", b=", b, ", c=", c) }, 1, 2, 3)

调用__Serve()函数时传入的参数123对应传入回调函数的参数abc

参考

备注

  • 该函数仅支持JavaScript语言策略。
  • 服务线程与全局作用域隔离,因此不支持闭包或引用外部变量、自定义函数等;但是可以调用平台所有的API函数。
  • WebSocket服务基于HTTP协议实现,可以在path中设置一个路由分支,设计WebSocket消息订阅/推送的实现代码,可以参考本节示例代码。

参数handler传入的回调处理函数接收一个ctx参数。ctx参数为一个上下文对象,用于获取数据和写入数据,具有以下方法:

  • ctx.proto()
    应用于HTTP/TCP协议,调用时返回协议名称。例如:HTTP/1.1tcp
  • 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-Agentctx.header("User-Agent")
  • ctx.method()
    应用于HTTP协议,调用时返回请求方法,例如GETPOST等。
  • 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协议,可以使用该方法将编码后的字符串传递给客户端。

持久化保存数据。该函数实现了一个可持久化保存的全局字典功能,数据以键值对(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-v键值对中的键值数据。

参数

名称类型必填描述

k

string / 空值

参数k为所保存键值对中的键名,不区分大小写。

v

string / number / bool / object / array / 空值

参数v为所保存键值对中的键值,可以是任何能够进行JSON序列化的数据。

参考

备注

每个实盘单独对应一个数据库。策略重启或托管者停止运行后,_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表内。

将毫秒级时间戳或Date对象转换为时间字符串。

_D()
_D(timestamp)
_D(timestamp, fmt)

示例

  • 获取并打印当前时间字符串:

    javascript
    function main(){ var time = _D() Log(time) }
    python
    def main(): strTime = _D() Log(strTime)
    rust
    fn main() { let time = _D(None); Log!(time); }
    c++
    void main() { auto strTime = _D(); Log(strTime); }
  • 时间戳为1574993606000,使用代码进行转换:

    javascript
    function main() { Log(_D(1574993606000)) }
    python
    def main(): # 在北京时间的服务器上运行结果为:2019-11-29 10:13:26;而在其他地区服务器上的托管者运行此代码,结果则为:2019-11-29 02:13:26 Log(_D(1574993606))
    rust
    fn main() { Log!(_D(1574993606000)); }
    c++
    void main() { Log(_D(1574993606000)); }
  • 使用参数fmt进行格式化,JavaScriptPythonC++语言的格式化字符串有所不同,具体请参看以下示例:

    javascript
    function main() { Log(_D(1574993606000, "yyyy--MM--dd hh--mm--ss")) // 2019--11--29 10--13--26 }
    python
    def main(): # 1574993606 为秒级时间戳 Log(_D(1574993606, "%Y--%m--%d %H--%M--%S")) # 2019--11--29 10--13--26
    rust
    fn 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

毫秒级时间戳或Date对象。

fmt

string

格式化字符串,JavaScript语言默认格式:yyyy-MM-dd hh:mm:ssPython语言默认格式:%Y-%m-%d %H:%M:%SC++语言默认格式:%Y-%m-%d %H:%M:%S

参考

备注

若不传入任何参数,则返回当前时间字符串。在Python策略中使用_D()函数时,需要注意传入的参数为秒级时间戳(JavaScript、C++策略中为毫秒级时间戳,1秒等于1000毫秒)。在实盘中使用_D()函数将时间戳解析为可读时间字符串时,需要注意托管者程序所在操作系统的时区与时间设置,因为_D()函数的解析结果取决于托管者系统的时间。

格式化浮点数。

_N()
_N(num)
_N(num, precision)

示例

  • 例如_N(3.1415, 2)会保留3.1415小数点后两位,删除其余数位,函数返回3.14

    javascript
    function main(){ var i = 3.1415 Log(i) var ii = _N(i, 2) Log(ii) }
    python
    def main(): i = 3.1415 Log(i) ii = _N(i, 2) Log(ii)
    rust
    fn 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,可以这样编写:

    javascript
    function main(){ var i = 1300 Log(i) var ii = _N(i, -3) // 查看日志得知为1000 Log(ii) }
    python
    def main(): i = 1300 Log(i) ii = _N(i, -3) Log(ii)
    rust
    fn 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为整数,默认值为4。

参考

备注

参数precision可以为正整数或负整数。

重试函数,用于对接口调用进行容错处理。

_C(pfn)
_C(pfn, ...args)

示例

  • 对无参数的函数进行容错处理:

    javascript
    function main(){ var ticker = _C(exchange.GetTicker) // 调整_C()函数重试时间间隔为2秒 _CDelay(2000) var depth = _C(exchange.GetDepth) Log(ticker) Log(depth) }
    python
    def main(): ticker = _C(exchange.GetTicker) _CDelay(2000) depth = _C(exchange.GetDepth) Log(ticker) Log(depth)
    rust
    fn 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); }
  • 对带参数的函数进行容错处理:

    javascript
    function main(){ var records = _C(exchange.GetRecords, PERIOD_D1) Log(records) }
    python
    def main(): records = _C(exchange.GetRecords, PERIOD_D1) Log(records)
    rust
    fn main() { let records = _C!(exchange.GetRecords(None, PERIOD_D1, None)); Log!(records); }
    c++
    void main() { auto records = _C(exchange.GetRecords, PERIOD_D1); Log(records); }
  • 也可用于对自定义函数进行容错处理:

    javascript
    var 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) }
    python
    import 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)
    rust
    fn 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

参数pfn为函数引用,即一个回调函数

arg

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

回调函数的参数,参数arg可以有多个。参数arg的类型与个数由回调函数的参数决定。

备注

_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())

返回数组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与数组arr2的交叉周期数。

参数

名称类型必填描述

arr1

array

元素为number类型的数组。

arr2

array

元素为number类型的数组。

备注

_Cross()函数的返回值为正数时表示上穿的周期数,为负数时表示下穿的周期数,为0时表示当前价格相等。详细使用说明请参阅:内置函数_Cross分析及使用说明

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

返回值为JSON对象。

参数

名称类型必填描述

s

string

该参数为需要解码(解析)的JSON字符串。

safeStr

bool

当该参数设置为true时,若解析过程中遇到可能超出精度范围的数值,会将其以字符串形式返回,以避免精度丢失或数值溢出问题。

备注

JSON.parse()函数能够正确解析包含较大数值的JSON字符串;当safeStr参数设置为真值时,会将较大的数值解析为字符串类型。

safeStr参数位同样支持传入reviver参数,即一个用于转换结果的函数,该函数会针对对象的每个成员调用一次;具体用法可查阅相关资料,此处不再赘述。

仅支持JavaScript语言。

回测系统中不支持JSON.parse()函数的safeStr参数功能。

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

返回序列化后的JSON字符串。

参数

名称类型必填描述

obj

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

需要序列化为JSON字符串的值。

备注

仅支持JavaScript语言。

在频道上发布最新的状态数据。该函数用于实盘之间的通信,可将当前实盘的状态数据广播到频道上,供其他实盘订阅获取。

SetChannelData(data)

示例

  • 频道广播端示例 - 发布 BTC 行情价格数据

    javascript
    function 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) // 每分钟更新一次频道状态 } }
    python
    def 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) # 每分钟更新一次频道状态
    rust
    fn 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 / 空值

需要发布到频道的数据,可以是任何支持JSON序列化的数据结构,通常为包含实盘状态信息的对象。

参考

备注

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频道的实盘均可接收数据。

订阅指定实盘的频道数据。该函数用于实盘间通信,可获取其他实盘通过SetChannelData()函数发布的最新状态数据。

GetChannelData(channelId)

示例

  • 频道订阅端示例 - 订阅两个实盘的频道数据

    javascript
    function 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 秒订阅一次频道 } }
    python
    def 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 秒订阅一次频道
    rust
    fn 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 订阅外部系统发送的数据

    javascript
    function 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 秒检查一次 } }
    python
    def 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 秒检查一次
    rust
    fn 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 / 空值

返回所订阅频道的最新状态数据。首次调用时返回null,需要重试。数据结构由广播端发布的数据决定。

参数

名称类型必填描述

channelId

string / number

频道标识符,支持以下两种类型:

  1. 实盘ID:用于订阅其他实盘的频道数据(即实盘间通信),可通过_G()函数获取实盘ID。

  2. 32位UUID:用于订阅跨平台发送的数据(即外部系统通过HTTP API向FMZ平台发送的数据)。

参考

备注

GetChannelData()函数为非阻塞调用,调用后立即返回,不会等待数据接收完成。

首次调用GetChannelData()函数时会返回null,需要重试并等待频道数据同步完成。

每次调用获取的均为频道上的最新状态数据,而非历史消息队列。

一个实盘可同时订阅多个不同实盘的频道,只需多次调用GetChannelData()并分别传入不同的实盘ID即可。

当前实盘也可以订阅自身的频道,即robotId参数可以是当前实盘的ID。

频道数据可跨实盘、跨托管者、跨服务器进行传输。

广播端使用SetChannelData()函数发布频道数据。

频道通信适用于实盘环境,在回测系统中该功能可能受限。

GetChannelData()函数支持跨平台订阅功能。当使用32位UUID作为频道标识符时,可接收来自FMZ平台外部系统通过HTTP API发送的数据。外部系统需同时指定实盘ID和UUID才能发送数据;同一托管者下的所有实盘均可订阅该UUID频道的数据,而不同托管者的实盘则无法订阅。