策略框架与API函数
在使用JavaScript、Python、Rust、C++语言编写的策略中,需要在策略主循环中调用Sleep()函数。回测时用于控制回测速度,实盘时用于控制策略的轮询间隔,从而控制对交易所API接口的请求频率。
示例
-
加密货币策略基本框架范例:
javascriptfunction onTick(){ //在这里写策略逻辑,将会不断调用,例如打印行情信息 Log(exchange.GetTicker()) } function main(){ while(true){ onTick() // Sleep函数主要用于数字货币策略的轮询频率控制,防止访问交易所API接口过于频繁 Sleep(60000) } }pythondef onTick(): Log(exchange.GetTicker()) def main(): while True: onTick() Sleep(60000)rustfn onTick() { // 在这里写策略逻辑,将会不断调用,例如打印行情信息 Log!(exchange.GetTicker(None)); } fn main() { loop { onTick(); // Sleep函数主要用于数字货币策略的轮询频率控制,防止访问交易所API接口过于频繁 Sleep(60000); } }c++void onTick() { Log(exchange.GetTicker()); } void main() { while(true) { onTick(); Sleep(60000); } } -
举个最简单的例子,如果我想每隔1秒种就在交易所挂一个价格为100,数量为1的买单可以这样写:
javascriptfunction onTick(){ // 这个仅仅是例子,回测或者实盘会很快把资金全部用于下单,实盘请勿使用 exchange.Buy(100, 1) } function main(){ while(true){ onTick() // 暂停多久可自定义,单位为毫秒,1秒等于1000毫秒 Sleep(1000) } }pythondef onTick(): exchange.Buy(100, 1) def main(): while True: onTick() Sleep(1000)rustfn onTick() { // 这个仅仅是例子,回测或者实盘会很快把资金全部用于下单,实盘请勿使用 let _ = exchange.Buy(100, 1); } fn main() { loop { onTick(); // 暂停多久可自定义,单位为毫秒,1秒等于1000毫秒 Sleep(1000); } }c++void onTick() { exchange.Buy(100, 1); } void main() { while(true) { onTick(); Sleep(1000); } } -
设计一个On Bar架构的策略
javascriptfunction onTick() { Log("K-line updated, new BAR generated") } function main() { var exName = exchange.GetName() if (exName.includes("Futures_")) { exchange.SetContractType("swap") } var lastTs = 0 while (true) { var r = _C(exchange.GetRecords) if (r.length > 0 && r[r.length - 1].Time != lastTs) { onTick() lastTs = r[r.length - 1].Time } Sleep(1000) } }pythondef onTick(): Log("K-line updated, new BAR generated") def main(): exName = exchange.GetName() if "Futures_" in exName: exchange.SetContractType("swap") lastTs = 0 while True: r = _C(exchange.GetRecords) if len(r) > 0 and r[-1]["Time"] != lastTs: onTick() lastTs = r[-1].Time Sleep(1000)rustfn onTick() { Log!("K-line updated, new BAR generated"); } fn main() { let exName = exchange.GetName(); if exName.contains("Futures_") { let _ = exchange.SetContractType("swap"); } let mut lastTs = 0; loop { let r = _C!(exchange.GetRecords(None, None, None)); if r.len() > 0 && r[r.len() - 1].Time != lastTs { onTick(); lastTs = r[r.len() - 1].Time; } Sleep(1000); } }c++void onTick() { Log("K-line updated, new BAR generated"); } void main() { auto exName = exchange.GetName(); if (exName.find("Futures_") != std::string::npos) { exchange.SetContractType("swap"); } Record lastBar; lastBar.Time = 0; while (true) { auto r = _C(exchange.GetRecords); if (r.size() > 0 && r[r.size() - 1].Time != lastBar.Time) { onTick(); lastBar.Time = r[r.size() - 1].Time; } Sleep(1000); } } -
以下展示所有API接口的速查表,详细的API描述请参考:发明者量化交易平台API手册。
全局函数
| 函数名称 | 简介 |
|---|---|
| Version | 返回系统当前版本号 |
| Sleep | 休眠函数,参数为暂停的毫秒数 |
| IsVirtual | 判断执行环境,返回真值表示回测环境 |
| 发送邮件 | |
| Mail_Go | Mail函数的异步版本 |
| SetErrorFilter | 过滤错误日志,参数为正则表达式字符串,匹配该正则表达式的错误日志将不会上传到日志系统 |
| GetPid | 获取实盘进程ID |
| GetLastError | 获取最近一次的错误信息 |
| GetCommand | 获取策略交互命令,策略交互控件设置请参考:交互控件 |
| GetMeta | 获取生成策略注册码时写入的Meta值 |
| Dial | 用于原始Socket访问 |
| HttpQuery | 发送HTTP请求 |
| HttpQuery_Go | HttpQuery函数的异步版本 |
| Encode | 数据编码函数 |
| UnixNano | 获取纳秒级时间戳 |
| Unix | 获取秒级时间戳 |
| GetOS | 获取系统信息 |
| MD5 | 计算MD5哈希值 |
| DBExec | 数据库函数,用于执行SQL语句并进行数据库操作 |
| UUID | 生成UUID |
| EventLoop | 监听事件,在任意WebSocket可读或exchange.Go、HttpQuery_Go等并发任务完成后返回,该函数仅适用于实盘 |
| _G | 持久化保存数据,该函数实现了一个可保存的全局字典功能。数据结构为键值对表,永久保存在托管者本地数据库文件中 |
| _D | 时间戳处理函数,将毫秒时间戳或Date对象转换为时间字符串 |
| _N | 格式化浮点数,例如_N(3.1415, 2)将删除3.1415小数点后两位以后的数值,函数返回3.14 |
| _C | 重试函数,用于接口容错。注意,例如对exchange.GetTicker函数进行容错,应使用_C(exchange.GetTicker)而非_C(exchange.GetTicker()) |
| _Cross | 交叉判断函数,_Cross()函数返回正数表示上穿周期数,负数表示下穿周期数,0表示当前价格相同 |
| JSONParse | 解析JSON,能够正确解析包含大数值的JSON字符串,将大数值解析为字符串类型。回测系统不支持JSONParse()函数 |
| SetChannelData | 在频道上发布最新状态数据,用于实盘间通信 |
| GetChannelData | 订阅指定实盘的频道数据,用于实盘间通信 |
日志函数
| 函数名称 | 简介 |
|---|---|
| Log | 输出日志,支持设置日志文本颜色、推送功能,以及打印base64编码的图片 |
| LogProfit | 输出盈亏数据,打印盈亏数值并根据数值绘制收益曲线 |
| LogProfitReset | 清空LogProfit函数输出的所有收益日志和收益图表 |
| LogStatus | 在状态栏输出信息,支持在状态栏中设置按钮控件和输出表格 |
| EnableLog | 开启或关闭订单信息的日志记录功能 |
| Chart | 图表绘制函数,基于Highcharts/Highstocks图表库 |
| KLineChart | Pine语言风格的图表绘制函数,用于在策略运行时以类似Pine语言的方式进行自定义绘图 |
| LogReset | 清除日志,支持通过参数设置保留最近指定数量的日志记录 |
| LogVacuum | 回收SQLite资源,在调用LogReset()函数清除日志后,回收SQLite删除数据时占用的存储空间 |
| console.log | 在实盘页面的「调试信息」栏中输出调试信息 |
| console.error | 在实盘页面的「调试信息」栏中输出错误信息 |
行情函数
| 函数名称 | 简介 |
|---|---|
| exchange.GetTicker | 获取Tick行情数据 |
| exchange.GetDepth | 获取订单簿深度数据 |
| exchange.GetTrades | 获取市场成交记录 |
| exchange.GetRecords | 获取K线数据 |
| exchange.GetPeriod | 获取当前K线周期 |
| exchange.SetMaxBarLen | 设置K线最大长度 |
| exchange.GetRawJSON | 获取最近一次REST请求返回的原始内容 |
| exchange.GetRate | 获取当前设置的汇率值 |
| exchange.SetData | 设置策略运行时加载的数据 |
| exchange.GetData | 获取已加载的数据或外部链接提供的数据 |
| exchange.GetMarkets | 获取交易所市场信息 |
| exchange.GetTickers | 获取交易所聚合行情数据 |
交易函数
| 函数名称 | 简介 |
|---|---|
| exchange.Buy | 提交买单,期货合约下单时必须注意交易方向是否设置正确,如果交易方向与交易函数不匹配将报错 |
| exchange.Sell | 提交卖单,期货合约下单时必须注意交易方向是否设置正确,如果交易方向与交易函数不匹配将报错 |
| exchange.CreateOrder | 提交订单,通过参数指定交易品种、交易方向、价格、数量 |
| exchange.ModifyOrder | 修改普通订单的价格和数量,支持通过附加参数修改订单的其他属性 |
| exchange.ModifyConditionOrder | 修改条件单的数量和触发条件,支持通过附加参数修改条件单的其他属性 |
| exchange.CancelOrder | 取消订单 |
| exchange.GetOrder | 获取订单信息,数据结构为Order结构 |
| exchange.GetOrders | 获取未完成的订单,数据结构为Order结构数组(列表) |
| exchange.GetHistoryOrders | 获取当前交易对、合约的历史订单,支持指定具体交易品种 |
| exchange.SetPrecision | 设置exchange交易所对象的价格与下单量精度,设置后系统将自动忽略数据的多余部分 |
| exchange.SetRate | 设置汇率 |
| exchange.IO | 用于交易所对象相关的其他接口调用 |
| exchange.Log | 输出并记录交易日志,不实际下单 |
| exchange.Encode | 签名加密计算 |
| exchange.Go | 多线程异步支持函数 |
| exchange.GetAccount | 获取账户信息 |
| exchange.GetAssets | 请求交易所账户资产信息 |
| exchange.GetName | 获取交易所对象的名称 |
| exchange.GetLabel | 获取交易所对象的标签 |
| exchange.GetCurrency | 获取当前交易对 |
| exchange.SetCurrency | 切换交易对 |
| exchange.GetQuoteCurrency | 获取当前交易对的计价币名称 |
期货函数
| 函数名称 | 简介 |
|---|---|
| exchange.GetPositions | 获取期货持仓信息,返回Position结构数组(列表) |
| exchange.SetMarginLevel | 设置杠杆倍数 |
| exchange.SetDirection | 设置exchange.Buy函数、exchange.Sell函数在期货合约下单时的订单方向 |
| exchange.SetContractType | 设置合约代码,例如:exchange.SetContractType("swap")设置合约代码为swap,将当前操作的合约设置为永续合约 |
| exchange.GetContractType | 获取当前设置的合约代码 |
| exchange.GetFundings | 获取当前期货交易所永续合约的资金费率数据 |
网络函数
| 函数名称 | 简介 |
|---|---|
| exchange.SetBase | 设置交易所API接口的基础地址 |
| exchange.GetBase | 获取当前交易所API接口的基础地址 |
| exchange.SetProxy | 设置网络代理 |
| exchange.SetTimeout | 设置REST协议的超时时间 |
API限流控制
功能概述
API限流控制功能用于限制策略对交易所API的调用频率,防止因触发交易所的频率限制而导致账号被封禁或临时受限。FMZ平台提供了灵活的限流配置方式,支持两种限流模式和多种配置策略。
为什么需要API限流
-
避免触发交易所限制:大多数交易所对API调用频率有严格限制,一旦超限,可能导致账号被临时或永久封禁。
-
合理分配API配额:在多策略、多交易对场景下,需要合理分配API调用资源。
-
提高策略稳定性:通过主动限流,避免因频繁调用导致的连接失败和数据获取异常。
-
符合交易所规范:遵守交易所的API使用规范,维护良好的API使用关系。
两种限流模式
rate模式(平滑限流)
-
适用于一般的限流需求
-
不严格对齐时间窗口
-
调用分布相对平滑
-
推荐用于日常的API调用限制
quota模式(额度限流)
-
严格对齐时间窗口
-
例如:设置
"1s"时,窗口对齐到整秒;设置"1m"时,窗口对齐到整分钟 -
适用于需要严格控制时间窗口的场景
-
推荐用于日内配额管理
基本用法
rate模式基本示例
示例
- undefinedjavascriptfunction main() { // Limit GetTicker to maximum 10 times per second exchange.IO("rate", "GetTicker", 10, "1s") // Normal API calls for (var i = 0; i < 20; i++) { var ticker = exchange.GetTicker("BTC_USDT") if (ticker) { Log("Success:", ticker.Last) } else { Log("Rate limit exceeded") // Returns null when exceeding 10 times/second } Sleep(50) } }pythondef main(): # Limit GetTicker to maximum 10 times per second exchange.IO("rate", "GetTicker", 10, "1s") # Normal API calls for i in range(20): ticker = exchange.GetTicker("BTC_USDT") if ticker: Log("Success:", ticker["Last"]) else: Log("Rate limit exceeded") # Returns None when exceeding 10 times/second Sleep(50)rustfn main() { // Limit GetTicker to maximum 10 times per second let _ = exchange.IO(("rate", "GetTicker", 10, "1s")); // Normal API calls for _i in 0..20 { match exchange.GetTicker("BTC_USDT") { Ok(ticker) => Log!("Success:", ticker.Last), Err(_) => Log!("Rate limit exceeded"), // Returns Err when exceeding 10 times/second } Sleep(50); } }c++// C++暂不支持
-
quota模式基本示例
javascriptfunction main() { // Strict limit, time window aligned to whole seconds exchange.IO("quota", "GetTicker", 5, "1s") for (var i = 0; i < 10; i++) { var ticker = exchange.GetTicker("BTC_USDT") Log(_D(), "Call", i+1, ticker ? "Success" : "Quota exceeded") Sleep(150) // About 6-7 calls per second, will trigger limit } }pythondef main(): # Strict limit, time window aligned to whole seconds exchange.IO("quota", "GetTicker", 5, "1s") for i in range(10): ticker = exchange.GetTicker("BTC_USDT") Log(_D(), "Call", i+1, "Success" if ticker else "Quota exceeded") Sleep(150) # About 6-7 calls per second, will trigger limitrustfn main() { // Strict limit, time window aligned to whole seconds let _ = exchange.IO(("quota", "GetTicker", 5, "1s")); for i in 0..10 { match exchange.GetTicker("BTC_USDT") { Ok(_) => Log!(_D(None), "Call", i + 1, "Success"), Err(_) => Log!(_D(None), "Call", i + 1, "Quota exceeded"), } Sleep(150); // About 6-7 calls per second, will trigger limit } }c++// C++暂不支持 -
函数名配置
单个函数限流
javascriptfunction main() { // Only limit GetTicker function exchange.IO("rate", "GetTicker", 10, "1s") // GetTicker is limited, GetDepth is not limited exchange.GetTicker("BTC_USDT") exchange.GetDepth("BTC_USDT") }pythondef main(): # Only limit GetTicker function exchange.IO("rate", "GetTicker", 10, "1s") # GetTicker is limited, GetDepth is not limited exchange.GetTicker("BTC_USDT") exchange.GetDepth("BTC_USDT")rustfn main() { // Only limit GetTicker function let _ = exchange.IO(("rate", "GetTicker", 10, "1s")); // GetTicker is limited, GetDepth is not limited let _ = exchange.GetTicker("BTC_USDT"); let _ = exchange.GetDepth("BTC_USDT"); }c++// C++暂不支持 -
多个函数联合限流
javascriptfunction main() { // GetTicker and GetDepth share quota, total 10 times per second exchange.IO("rate", "GetTicker,GetDepth", 10, "1s") for (var i = 0; i < 15; i++) { if (i % 2 == 0) { exchange.GetTicker("BTC_USDT") // Counted in shared quota } else { exchange.GetDepth("BTC_USDT") // Counted in shared quota } } }pythondef main(): # GetTicker and GetDepth share quota, total 10 times per second exchange.IO("rate", "GetTicker,GetDepth", 10, "1s") for i in range(15): if i % 2 == 0: exchange.GetTicker("BTC_USDT") # Counted in shared quota else: exchange.GetDepth("BTC_USDT") # Counted in shared quotarustfn main() { // GetTicker and GetDepth share quota, total 10 times per second let _ = exchange.IO(("rate", "GetTicker,GetDepth", 10, "1s")); for i in 0..15 { if i % 2 == 0 { let _ = exchange.GetTicker("BTC_USDT"); // Counted in shared quota } else { let _ = exchange.GetDepth("BTC_USDT"); // Counted in shared quota } } }c++// C++暂不支持 -
使用通配符限制所有函数
javascriptfunction main() { // Limit all API calls to total 100 times per minute exchange.IO("rate", "*", 100, "1m") // All calls are counted in total quota exchange.GetTicker("BTC_USDT") exchange.GetDepth("BTC_USDT") exchange.GetAccount() exchange.CreateOrder("BTC_USDT", "buy", 50000, 0.001) }pythondef main(): # Limit all API calls to total 100 times per minute exchange.IO("rate", "*", 100, "1m") # All calls are counted in total quota exchange.GetTicker("BTC_USDT") exchange.GetDepth("BTC_USDT") exchange.GetAccount() exchange.CreateOrder("BTC_USDT", "buy", 50000, 0.001)rustfn main() { // Limit all API calls to total 100 times per minute let _ = exchange.IO(("rate", "*", 100, "1m")); // All calls are counted in total quota let _ = exchange.GetTicker("BTC_USDT"); let _ = exchange.GetDepth("BTC_USDT"); let _ = exchange.GetAccount(); let _ = exchange.CreateOrder("BTC_USDT", "buy", 50000, 0.001); }c++// C++暂不支持 -
时间周期配置
支持的时间单位
-
ns:纳秒 -
us或µs:微秒 -
ms:毫秒 -
s:秒 -
m:分钟 -
h:小时 -
d:天
示例:
"100ms","1s","5m","1h","1d"javascriptfunction main() { // Different time period configurations exchange.IO("rate", "GetTicker", 10, "1s") // 10 times per second exchange.IO("rate", "GetDepth", 30, "1m") // 30 times per minute exchange.IO("rate", "GetAccount", 100, "1h") // 100 times per hour exchange.IO("rate", "CreateOrder", 500, "1d") // 500 times per day }pythondef main(): # 不同时间周期的配置 exchange.IO("rate", "GetTicker", 10, "1s") # 每秒10次 exchange.IO("rate", "GetDepth", 30, "1m") # 每分钟30次 exchange.IO("rate", "GetAccount", 100, "1h") # 每小时100次 exchange.IO("rate", "CreateOrder", 500, "1d") # 每天500次rustfn main() { // 不同时间周期的配置 let _ = exchange.IO(("rate", "GetTicker", 10, "1s")); // 每秒10次 let _ = exchange.IO(("rate", "GetDepth", 30, "1m")); // 每分钟30次 let _ = exchange.IO(("rate", "GetAccount", 100, "1h")); // 每小时100次 let _ = exchange.IO(("rate", "CreateOrder", 500, "1d")); // 每天500次 }c++// C++暂不支持 -
-
重置时间点配置
使用
@HHMM或@HHMMSS格式指定每日的重置时间点,仅在 quota 模式下有效。javascriptfunction main() { // Reset quota daily at 08:15 exchange.IO("quota", "GetTicker", 1000, "@0815") // Reset quota daily at 00:00 exchange.IO("quota", "CreateOrder", 500, "@0000") // Reset quota daily at 23:59:59 exchange.IO("quota", "*", 5000, "@235959") }pythondef main(): # Reset quota daily at 08:15 exchange.IO("quota", "GetTicker", 1000, "@0815") # Reset quota daily at 00:00 exchange.IO("quota", "CreateOrder", 500, "@0000") # Reset quota daily at 23:59:59 exchange.IO("quota", "*", 5000, "@235959")rustfn main() { // Reset quota daily at 08:15 let _ = exchange.IO(("quota", "GetTicker", 1000, "@0815")); // Reset quota daily at 00:00 let _ = exchange.IO(("quota", "CreateOrder", 500, "@0000")); // Reset quota daily at 23:59:59 let _ = exchange.IO(("quota", "*", 5000, "@235959")); }c++// C++暂不支持 -
行为模式
默认模式(超限返回null)
javascriptfunction main() { exchange.IO("rate", "GetTicker", 5, "1s") // 不指定 behavior 参数 for (var i = 0; i < 10; i++) { var ticker = exchange.GetTicker("BTC_USDT") if (ticker) { Log("Call", i+1, "Success:", ticker.Last) } else { Log("Call", i+1, "Failed: rate limit exceeded") // 可选择 Sleep 等待,或跳过本次调用 Sleep(200) } } }pythondef main(): exchange.IO("rate", "GetTicker", 5, "1s") # 不指定 behavior 参数 for i in range(10): ticker = exchange.GetTicker("BTC_USDT") if ticker: Log("Call", i+1, "Success:", ticker["Last"]) else: Log("Call", i+1, "Failed: rate limit exceeded") # 可选择 Sleep 等待,或跳过本次调用 Sleep(200)rustfn main() { let _ = exchange.IO(("rate", "GetTicker", 5, "1s")); // 不指定 behavior 参数 for i in 0..10 { match exchange.GetTicker("BTC_USDT") { Ok(ticker) => Log!("Call", i + 1, "Success:", ticker.Last), Err(_) => { Log!("Call", i + 1, "Failed: rate limit exceeded"); // 可选择 Sleep 等待,或跳过本次调用 Sleep(200); } } } }c++// C++暂不支持 -
delay模式(超限自动等待)
javascriptfunction main() { exchange.IO("rate", "GetTicker", 5, "1s", "delay") // 指定delay参数 // 调用超限时会自动等待,确保每次调用都成功 for (var i = 0; i < 10; i++) { var ticker = exchange.GetTicker("BTC_USDT") Log("Call", i+1, "Success:", ticker.Last) // ticker不会为null } }pythondef main(): exchange.IO("rate", "GetTicker", 5, "1s", "delay") # 指定delay参数 # 调用超限时会自动等待,确保每次调用都成功 for i in range(10): ticker = exchange.GetTicker("BTC_USDT") Log("Call", i+1, "Success:", ticker["Last"]) # ticker不会为Nonerustfn main() { let _ = exchange.IO(("rate", "GetTicker", 5, "1s", "delay")); // 指定delay参数 // 调用超限时会自动等待,确保每次调用都成功 for i in 0..10 { let ticker = exchange.GetTicker("BTC_USDT").unwrap(); Log!("Call", i + 1, "Success:", ticker.Last); // ticker不会返回Err } }c++// C++暂不支持 -
支持的函数列表
交易类函数
CreateOrder:创建订单CancelOrder:取消订单Buy:买入(受CreateOrder限制)Sell:卖出(受CreateOrder限制)CreateConditionOrder:创建条件单CancelConditionOrder:取消条件单
账户类函数
GetAccount:获取账户信息GetAssets:获取资产信息GetPositions:获取持仓信息
订单类函数
GetOrder:获取单个订单GetOrders:获取所有订单GetHistoryOrders:获取历史订单GetConditionOrder:获取单个条件单GetConditionOrders:获取所有条件单GetHistoryConditionOrders:获取历史条件单
行情类函数
GetTicker:获取单个行情(ticker)GetTickers:获取多个行情(ticker)GetDepth:获取市场深度GetRecords:获取K线数据GetTrades:获取最新成交记录
其它函数
GetMarkets:获取市场列表GetFundings:获取资金费率SetMarginLevel:设置杠杆倍数Go:并发调用(受实际调用函数限制)IO/api:自定义API调用(仅限exchange.IO("api", ...))
-
实际应用场景
场景1:防止触发交易所的频率限制
javascriptfunction main() { // 假设交易所限制:GetTicker 每秒 20 次,CreateOrder 每秒 5 次 // 将频率设置为略低于交易所限制的值,以预留安全余量 exchange.IO("rate", "GetTicker", 15, "1s") exchange.IO("rate", "CreateOrder", 4, "1s") while (true) { var ticker = exchange.GetTicker("BTC_USDT") if (ticker && ticker.Last < 50000) { exchange.CreateOrder("BTC_USDT", "buy", ticker.Last, 0.001) } Sleep(100) } }pythondef main(): # 假设交易所限制:GetTicker 每秒 20 次,CreateOrder 每秒 5 次 # 将频率设置为略低于交易所限制的值,以预留安全余量 exchange.IO("rate", "GetTicker", 15, "1s") exchange.IO("rate", "CreateOrder", 4, "1s") while True: ticker = exchange.GetTicker("BTC_USDT") if ticker and ticker["Last"] < 50000: exchange.CreateOrder("BTC_USDT", "buy", ticker["Last"], 0.001) Sleep(100)rustfn main() { // 假设交易所限制:GetTicker 每秒 20 次,CreateOrder 每秒 5 次 // 将频率设置为略低于交易所限制的值,以预留安全余量 let _ = exchange.IO(("rate", "GetTicker", 15, "1s")); let _ = exchange.IO(("rate", "CreateOrder", 4, "1s")); loop { if let Ok(ticker) = exchange.GetTicker("BTC_USDT") { if ticker.Last < 50000.0 { let _ = exchange.CreateOrder("BTC_USDT", "buy", ticker.Last, 0.001); } } Sleep(100); } }c++// C++ 暂不支持 -
场景2:多交易所对象统一限流
javascriptfunction main() { // 为每个交易所对象设置限流 for (var i = 0; i < exchanges.length; i++) { exchanges[i].IO("rate", "GetTicker", 10, "1s") exchanges[i].IO("rate", "CreateOrder", 2, "1s") } // 并发获取多个交易所的行情 while (true) { for (var i = 0; i < exchanges.length; i++) { var ticker = exchanges[i].GetTicker("BTC_USDT") if (ticker) { Log(exchanges[i].GetName(), "Price:", ticker.Last) } } Sleep(1000) } }pythondef main(): # 为每个交易所对象设置限流 for i in range(len(exchanges)): exchanges[i].IO("rate", "GetTicker", 10, "1s") exchanges[i].IO("rate", "CreateOrder", 2, "1s") # 并发获取多个交易所的行情 while True: for i in range(len(exchanges)): ticker = exchanges[i].GetTicker("BTC_USDT") if ticker: Log(exchanges[i].GetName(), "Price:", ticker["Last"]) Sleep(1000)rustfn main() { // 为每个交易所对象设置限流 for e in exchanges.iter() { let _ = e.IO(("rate", "GetTicker", 10, "1s")); let _ = e.IO(("rate", "CreateOrder", 2, "1s")); } // 并发获取多个交易所的行情 loop { for e in exchanges.iter() { if let Ok(ticker) = e.GetTicker("BTC_USDT") { Log!(e.GetName(), "Price:", ticker.Last); } } Sleep(1000); } }c++// C++ 暂不支持 -
场景3:日内配额管理
javascriptfunction main() { // 每天最多 1000 次 API 调用,每天早上 8 点重置 exchange.IO("quota", "*", 1000, "@0800") var callCount = 0 while (true) { var ticker = exchange.GetTicker("BTC_USDT") if (ticker) { callCount++ Log("Call count:", callCount, "Price:", ticker.Last) } else { Log("Daily quota exceeded, waiting for tomorrow 08:00") Sleep(60000) // 等待 1 分钟后重试 } Sleep(10000) } }pythondef main(): # 每天最多 1000 次 API 调用,每天早上 8 点重置 exchange.IO("quota", "*", 1000, "@0800") callCount = 0 while True: ticker = exchange.GetTicker("BTC_USDT") if ticker: callCount += 1 Log("Call count:", callCount, "Price:", ticker["Last"]) else: Log("Daily quota exceeded, waiting for tomorrow 08:00") Sleep(60000) # 等待 1 分钟后重试 Sleep(10000)rustfn main() { // 每天最多 1000 次 API 调用,每天早上 8 点重置 let _ = exchange.IO(("quota", "*", 1000, "@0800")); let mut callCount = 0; loop { match exchange.GetTicker("BTC_USDT") { Ok(ticker) => { callCount += 1; Log!("Call count:", callCount, "Price:", ticker.Last); } Err(_) => { Log!("Daily quota exceeded, waiting for tomorrow 08:00"); Sleep(60000); // 等待 1 分钟后重试 } } Sleep(10000); } }c++// C++ 暂不支持 -
场景4:组合限流策略
javascriptfunction main() { // 组合使用多种限流策略 // 1. 行情类API每秒限流 exchange.IO("rate", "GetTicker,GetDepth", 20, "1s") // 2. 交易类API每秒限流 exchange.IO("rate", "CreateOrder,CancelOrder", 5, "1s") // 3. 账户查询类API每分钟限流 exchange.IO("rate", "GetAccount,GetPositions", 30, "1m") // 4. 所有API每日总配额 exchange.IO("quota", "*", 10000, "@0000") Log("Multi-level rate limiting configured") // 策略主循环 while (true) { // 获取行情数据 var ticker = exchange.GetTicker("BTC_USDT") var depth = exchange.GetDepth("BTC_USDT") // 查询账户信息 if (Date.now() % 60000 < 1000) { // 每分钟查询一次 var account = exchange.GetAccount() Log("Account:", account) } // 交易逻辑 if (ticker && ticker.Last < 50000) { exchange.CreateOrder("BTC_USDT", "buy", ticker.Last, 0.001) } Sleep(500) } }pythonimport time def main(): # 组合使用多种限流策略 # 1. 行情类API每秒限流 exchange.IO("rate", "GetTicker,GetDepth", 20, "1s") # 2. 交易类API每秒限流 exchange.IO("rate", "CreateOrder,CancelOrder", 5, "1s") # 3. 账户查询类API每分钟限流 exchange.IO("rate", "GetAccount,GetPositions", 30, "1m") # 4. 所有API每日总配额 exchange.IO("quota", "*", 10000, "@0000") Log("Multi-level rate limiting configured") # 策略主循环 while True: # 获取行情数据 ticker = exchange.GetTicker("BTC_USDT") depth = exchange.GetDepth("BTC_USDT") # 查询账户信息 if int(time.time() * 1000) % 60000 < 1000: # 每分钟查询一次 account = exchange.GetAccount() Log("Account:", account) # 交易逻辑 if ticker and ticker["Last"] < 50000: exchange.CreateOrder("BTC_USDT", "buy", ticker["Last"], 0.001) Sleep(500)rustfn main() { // 组合使用多种限流策略 // 1. 行情类API每秒限流 let _ = exchange.IO(("rate", "GetTicker,GetDepth", 20, "1s")); // 2. 交易类API每秒限流 let _ = exchange.IO(("rate", "CreateOrder,CancelOrder", 5, "1s")); // 3. 账户查询类API每分钟限流 let _ = exchange.IO(("rate", "GetAccount,GetPositions", 30, "1m")); // 4. 所有API每日总配额 let _ = exchange.IO(("quota", "*", 10000, "@0000")); Log!("Multi-level rate limiting configured"); // 策略主循环 loop { // 获取行情数据 let ticker = exchange.GetTicker("BTC_USDT"); let depth = exchange.GetDepth("BTC_USDT"); // 查询账户信息 if UnixNano() / 1000000 % 60000 < 1000 { // 每分钟查询一次 let account = exchange.GetAccount(); Log!("Account:", account); } // 交易逻辑 if let Ok(t) = ticker { if t.Last < 50000.0 { let _ = exchange.CreateOrder("BTC_USDT", "buy", t.Last, 0.001); } } Sleep(500); } }c++// C++ 暂不支持 -
注意事项
1. quota模式时间窗口对齐
quota模式严格对齐时间窗口:
"1s":对齐到整秒(例如:12:00:00、12:00:01、12:00:02……)"1m":对齐到整分钟(例如:12:00:00、12:01:00、12:02:00……)"1h":对齐到整小时(例如:12:00:00、13:00:00、14:00:00……)
这意味着即使从12:00:00.500开始计数,到12:00:01.000时,当前时间窗口也会重置。
javascriptfunction main() { // quota模式:严格对齐到整秒 exchange.IO("quota", "GetTicker", 3, "1s") // 假设当前时间为 12:00:00.500 exchange.GetTicker("BTC_USDT") // 第1次,成功 exchange.GetTicker("BTC_USDT") // 第2次,成功 exchange.GetTicker("BTC_USDT") // 第3次,成功 exchange.GetTicker("BTC_USDT") // 第4次,失败(超限) Sleep(500) // 等待500ms,此时时间为 12:00:01.000 // 窗口已重置 exchange.GetTicker("BTC_USDT") // 新窗口第1次,成功 }pythondef main(): # quota模式:严格对齐到整秒 exchange.IO("quota", "GetTicker", 3, "1s") # 假设当前时间为 12:00:00.500 exchange.GetTicker("BTC_USDT") # 第1次,成功 exchange.GetTicker("BTC_USDT") # 第2次,成功 exchange.GetTicker("BTC_USDT") # 第3次,成功 exchange.GetTicker("BTC_USDT") # 第4次,失败(超限) Sleep(500) # 等待500ms,此时时间为 12:00:01.000 # 窗口已重置 exchange.GetTicker("BTC_USDT") # 新窗口第1次,成功rustfn main() { // quota模式:严格对齐到整秒 let _ = exchange.IO(("quota", "GetTicker", 3, "1s")); // 假设当前时间为 12:00:00.500 let _ = exchange.GetTicker("BTC_USDT"); // 第1次,成功 let _ = exchange.GetTicker("BTC_USDT"); // 第2次,成功 let _ = exchange.GetTicker("BTC_USDT"); // 第3次,成功 let _ = exchange.GetTicker("BTC_USDT"); // 第4次,失败(超限) Sleep(500); // 等待500ms,此时时间为 12:00:01.000 // 窗口已重置 let _ = exchange.GetTicker("BTC_USDT"); // 新窗口第1次,成功 }c++// C++暂不支持 -
2. delay模式下的时间差异
使用
"delay"参数时,实际的API调用时间与日志记录的时间可能并不一致。这是因为触发限流时程序会进入等待,而日志记录的是等待结束后的时间。javascriptfunction main() { exchange.IO("rate", "GetTicker", 2, "1s", "delay") Log(_D(), "Call 1") // 12:00:00.000 exchange.GetTicker("BTC_USDT") Log(_D(), "Call 2") // 12:00:00.100 exchange.GetTicker("BTC_USDT") Log(_D(), "Call 3") // 12:00:00.200,但实际会等待到12:00:01.000 exchange.GetTicker("BTC_USDT") // 触发限流,自动等待 Log(_D(), "Call 3 completed") // 日志显示12:00:01.000+ // 看起来一秒内调用了3次,但实际第3次是在新窗口执行的 }pythondef main(): exchange.IO("rate", "GetTicker", 2, "1s", "delay") Log(_D(), "Call 1") # 12:00:00.000 exchange.GetTicker("BTC_USDT") Log(_D(), "Call 2") # 12:00:00.100 exchange.GetTicker("BTC_USDT") Log(_D(), "Call 3") # 12:00:00.200,但实际会等待到12:00:01.000 exchange.GetTicker("BTC_USDT") # 触发限流,自动等待 Log(_D(), "Call 3 completed") # 日志显示12:00:01.000+ # 看起来一秒内调用了3次,但实际第3次是在新窗口执行的rustfn main() { let _ = exchange.IO(("rate", "GetTicker", 2, "1s", "delay")); Log!(_D(None), "Call 1"); // 12:00:00.000 let _ = exchange.GetTicker("BTC_USDT"); Log!(_D(None), "Call 2"); // 12:00:00.100 let _ = exchange.GetTicker("BTC_USDT"); Log!(_D(None), "Call 3"); // 12:00:00.200,但实际会等待到12:00:01.000 let _ = exchange.GetTicker("BTC_USDT"); // 触发限流,自动等待 Log!(_D(None), "Call 3 completed"); // 日志显示12:00:01.000+ // 看起来一秒内调用了3次,但实际第3次是在新窗口执行的 }c++// C++暂不支持 -
3. Buy/Sell函数的限流
Buy和Sell函数在底层均调用CreateOrder,因此其限流规则遵循CreateOrder的设置。javascriptfunction main() { // 设置CreateOrder限流 exchange.IO("rate", "CreateOrder", 5, "1s") // Buy和Sell也会受到此限制 for (var i = 0; i < 10; i++) { if (i % 2 == 0) { exchange.Buy(50000, 0.001) // 受CreateOrder限制 } else { exchange.Sell(51000, 0.001) // 受CreateOrder限制 } } }pythondef main(): # 设置CreateOrder限流 exchange.IO("rate", "CreateOrder", 5, "1s") # Buy和Sell也会受到此限制 for i in range(10): if i % 2 == 0: exchange.Buy(50000, 0.001) # 受CreateOrder限制 else: exchange.Sell(51000, 0.001) # 受CreateOrder限制rustfn main() { // 设置CreateOrder限流 let _ = exchange.IO(("rate", "CreateOrder", 5, "1s")); // Buy和Sell也会受到此限制 for i in 0..10 { if i % 2 == 0 { let _ = exchange.Buy(50000, 0.001); // 受CreateOrder限制 } else { let _ = exchange.Sell(51000, 0.001); // 受CreateOrder限制 } } }c++// C++暂不支持 -
4. Go函数的限流
Go函数的限流取决于实际被并发调用的函数。javascriptfunction main() { // 限制GetTicker exchange.IO("rate", "GetTicker", 5, "1s") // 并发调用GetTicker时受限 var tasks = [] for (var i = 0; i < 10; i++) { tasks.push(exchange.Go("GetTicker", "BTC_USDT")) } for (var i = 0; i < tasks.length; i++) { var ticker = tasks[i].wait() Log("Task", i, ticker ? "Success" : "Rate limited") } }pythondef main(): # 限制GetTicker exchange.IO("rate", "GetTicker", 5, "1s") # 并发调用GetTicker时受限 tasks = [] for i in range(10): tasks.append(exchange.Go("GetTicker", "BTC_USDT")) for i in range(len(tasks)): ticker = tasks[i].wait() Log("Task", i, "Success" if ticker else "Rate limited")rustfn main() { // 限制GetTicker let _ = exchange.IO(("rate", "GetTicker", 5, "1s")); // 并发调用GetTicker时受限 // Rust中exchange.Go为类型化写法,使用Go::GetTicker token let mut tasks = Vec::new(); for _i in 0..10 { tasks.push(exchange.Go(Go::GetTicker, ("BTC_USDT",))); } for (i, task) in tasks.iter().enumerate() { match task.wait(0) { Ok(_) => Log!("Task", i, "Success"), Err(_) => Log!("Task", i, "Rate limited"), } } }c++// C++暂不支持 -
5. IO/api的限流
IO/api限流仅对exchange.IO("api", ...)调用生效,不会影响其他exchange.IO功能。javascriptfunction main() { // 限制exchange.IO("api", ...)调用 exchange.IO("rate", "IO/api", 10, "1s") // 受限制 for (var i = 0; i < 15; i++) { var ret = exchange.IO("api", "GET", "/api/v5/account/balance", "") Log("API call", i, ret ? "Success" : "Rate limited") } // 不受限制 exchange.IO("currency", "LTC_USDT") // 切换交易对,不受限 exchange.IO("rate", "GetDepth", 5, "1s") // 设置其它限流,不受限 }pythondef main(): # 限制exchange.IO("api", ...)调用 exchange.IO("rate", "IO/api", 10, "1s") # 受限制 for i in range(15): ret = exchange.IO("api", "GET", "/api/v5/account/balance", "") Log("API call", i, "Success" if ret else "Rate limited") # 不受限制 exchange.IO("currency", "LTC_USDT") # 切换交易对,不受限 exchange.IO("rate", "GetDepth", 5, "1s") # 设置其它限流,不受限rustfn main() { // 限制exchange.IO("api", ...)调用 let _ = exchange.IO(("rate", "IO/api", 10, "1s")); // 受限制 for i in 0..15 { match exchange.IO(("api", "GET", "/api/v5/account/balance", "")) { Ok(_) => Log!("API call", i, "Success"), Err(_) => Log!("API call", i, "Rate limited"), } } // 不受限制 let _ = exchange.IO(("currency", "LTC_USDT")); // 切换交易对,不受限 let _ = exchange.IO(("rate", "GetDepth", 5, "1s")); // 设置其它限流,不受限 }c++// C++暂不支持 -
最佳实践
-
根据交易所限制设置:请参考交易所的 API 文档,将限流值设置为略低于交易所限制的水平。
-
留出安全余量:请勿将限流值设置为交易所允许的最大值,建议设置为最大值的 70%-80%。
-
分层限流:针对不同类型的 API 设置不同的限流值,并为重要 API 保留更多余量。
-
使用 delay 模式处理关键调用:对于必须成功的 API 调用,请使用
"delay"模式以确保调用成功。 -
监控 API 使用情况:定期检查策略的 API 调用频率,并持续优化调用逻辑。
-
避免过度调用:合理设计策略逻辑,避免不必要的 API 调用。
-
测试限流配置:在实盘运行前,先在模拟环境中测试限流配置是否合理。
-
参考
策略实盘间通信
功能概述
策略实盘间通信功能允许不同的实盘策略之间共享数据并同步状态。通过频道机制,一个实盘可以将自身的状态数据广播给其他实盘,从而实现跨实盘、跨托管者、跨服务器的数据通信。
核心概念
-
频道(Channel):每个实盘都拥有一个独立的频道,频道ID即为实盘ID
-
广播端:使用
SetChannelData()函数在频道上发布数据的实盘 -
订阅端:使用
GetChannelData()函数订阅其他实盘频道数据的实盘 -
状态覆盖:频道上仅保存最新状态,新数据会覆盖旧数据,而非采用消息队列机制
主要特性
-
非阻塞通信:所有函数调用均为非阻塞,不会影响策略主流程
-
跨平台支持:支持跨实盘、跨托管者、跨服务器进行数据传输
-
多频道订阅:单个实盘可同时订阅多个不同实盘的频道
-
灵活的数据格式:支持任何可JSON序列化的数据结构
应用场景
-
主从策略协同:主策略分析市场并广播信号,从策略接收信号并执行交易
-
多账户同步:在多个交易账户之间同步交易信号和仓位信息
-
策略监控:广播策略运行状态,由监控实盘订阅并进行展示或告警
-
数据共享:共享行情分析、指标计算等结果,避免重复计算
基本用法
示例
-
广播端示例 - 发布市场数据
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)) 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"])) + "Last Price: ${:.2f}".format(channelState["lastPrice"])) 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}", robotId, updateId, _D(timestamp), ticker.Last )); Sleep(60000); // 每分钟更新一次频道状态 } }c++ -
订阅端示例 - 订阅多个频道
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" } 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.lastPrice.toFixed(2) + "\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 += "最新价: ${:.2f}\n".format(state1["lastPrice"]) 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 += "最新价: ${:.2f}\n".format(state2["lastPrice"]) 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_f64().unwrap_or(0.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)); } else { statusMsg += "状态: 等待中... (首次调用返回 null)\n"; } LogStatus!(statusMsg); Sleep(5000); // 每 5 秒读取一次频道数据 } }c++ -
实际应用场景
场景1:主从策略协同交易
主策略(信号广播端)
javascriptfunction main() { var robotId = _G() Log("Main strategy started, Bot ID:", robotId) while(true) { // 分析市场行情,生成交易信号 var records = exchange.GetRecords("BTC_USDT") if (!records || records.length < 20) { Sleep(5000) continue } // 简单的均线交叉策略 var ma5 = TA.MA(records, 5) var ma20 = TA.MA(records, 20) var signal = "HOLD" if (ma5[ma5.length-1] > ma20[ma20.length-1] && ma5[ma5.length-2] <= ma20[ma20.length-2]) { signal = "BUY" } else if (ma5[ma5.length-1] < ma20[ma20.length-1] && ma5[ma5.length-2] >= ma20[ma20.length-2]) { signal = "SELL" } // 广播交易信号 var signalData = { timestamp: Date.now(), symbol: "BTC_USDT", signal: signal, price: records[records.length-1].Close, ma5: ma5[ma5.length-1], ma20: ma20[ma20.length-1] } SetChannelData(signalData) LogStatus("Main Strategy - Signal Broadcast\n" + "Signal: " + signal + "\n" + "Price: $" + signalData.price.toFixed(2) + "\n" + "MA5: " + signalData.ma5.toFixed(2) + "\n" + "MA20: " + signalData.ma20.toFixed(2)) Sleep(60000) } }pythondef main(): robotId = _G() Log("Main strategy started, Bot ID:", robotId) while True: # 分析市场行情,生成交易信号 records = exchange.GetRecords("BTC_USDT") if not records or len(records) < 20: Sleep(5000) continue # 简单的均线交叉策略 ma5 = TA.MA(records, 5) ma20 = TA.MA(records, 20) signal = "HOLD" if ma5[-1] > ma20[-1] and ma5[-2] <= ma20[-2]: signal = "BUY" elif ma5[-1] < ma20[-1] and ma5[-2] >= ma20[-2]: signal = "SELL" # 广播交易信号 signalData = { "timestamp": time.time() * 1000, "symbol": "BTC_USDT", "signal": signal, "price": records[-1]["Close"], "ma5": ma5[-1], "ma20": ma20[-1] } SetChannelData(signalData) LogStatus("Main Strategy - Signal Broadcast\n" + "Signal: {}\n".format(signal) + "Price: ${:.2f}\n".format(signalData["price"]) + "MA5: {:.2f}\n".format(signalData["ma5"]) + "MA20: {:.2f}".format(signalData["ma20"])) Sleep(60000)rustfn main() { let robotId = _G!(); Log!("Main strategy started, Bot ID:", robotId); loop { // 分析市场行情,生成交易信号 let records = match exchange.GetRecords("BTC_USDT", None, None) { Ok(r) if r.len() >= 20 => r, _ => { Sleep(5000); continue; } }; // 简单的均线交叉策略 let ma5 = TA.MA(&records, 5); let ma20 = TA.MA(&records, 20); let n = ma5.len(); let mut signal = "HOLD"; if ma5[n - 1] > ma20[n - 1] && ma5[n - 2] <= ma20[n - 2] { signal = "BUY"; } else if ma5[n - 1] < ma20[n - 1] && ma5[n - 2] >= ma20[n - 2] { signal = "SELL"; } // 广播交易信号 // Rust 的 SetChannelData 仅接受字符串参数,因此使用 format! 构造 JSON 文本 let price = records[records.len() - 1].Close; let signalData = format!( r#"{{"timestamp": {}, "symbol": "BTC_USDT", "signal": "{}", "price": {}, "ma5": {}, "ma20": {}}}"#, Unix() * 1000, signal, price, ma5[n - 1], ma20[n - 1] ); SetChannelData(&signalData); LogStatus!(format!( "Main Strategy - Signal Broadcast\nSignal: {}\nPrice: ${:.2}\nMA5: {:.2}\nMA20: {:.2}", signal, price, ma5[n - 1], ma20[n - 1] )); Sleep(60000); } }c++ -
实际应用场景
场景1:主从策略协同交易
从策略(信号接收执行端)
javascriptfunction main() { var masterRobotId = "632799" // 主策略的实盘ID var lastSignal = null Log("Follower strategy started, subscribing to main strategy:", masterRobotId) while(true) { // 获取主策略的信号 var signalData = GetChannelData(masterRobotId) if (signalData === null) { LogStatus("Waiting for main strategy signal...") Sleep(5000) continue } // 检查是否有新信号 if (lastSignal !== signalData.signal) { Log("Received new signal:", signalData.signal, "Price:", signalData.price) // 执行交易 if (signalData.signal === "BUY") { var ticker = exchange.GetTicker(signalData.symbol) if (ticker) { exchange.Buy(ticker.Last, 0.01) Log("Executing buy, Price:", ticker.Last) } } else if (signalData.signal === "SELL") { var ticker = exchange.GetTicker(signalData.symbol) if (ticker) { exchange.Sell(ticker.Last, 0.01) Log("Executing sell, Price:", ticker.Last) } } lastSignal = signalData.signal } LogStatus("Follower Strategy - Following Main Strategy\n" + "Current Signal: " + signalData.signal + "\n" + "Signal Price: $" + signalData.price.toFixed(2) + "\n" + "Signal Time: " + _D(signalData.timestamp)) Sleep(5000) } }pythondef main(): masterRobotId = "632799" # 主策略的实盘ID lastSignal = None Log("Follower strategy started, subscribing to main strategy:", masterRobotId) while True: # 获取主策略的信号 signalData = GetChannelData(masterRobotId) if signalData is None: LogStatus("Waiting for main strategy signal...") Sleep(5000) continue # 检查是否有新信号 if lastSignal != signalData["signal"]: Log("Received new signal:", signalData["signal"], "Price:", signalData["price"]) # 执行交易 if signalData["signal"] == "BUY": ticker = exchange.GetTicker(signalData["symbol"]) if ticker: exchange.Buy(ticker["Last"], 0.01) Log("Executing buy, Price:", ticker["Last"]) elif signalData["signal"] == "SELL": ticker = exchange.GetTicker(signalData["symbol"]) if ticker: exchange.Sell(ticker["Last"], 0.01) Log("Executing sell, Price:", ticker["Last"]) lastSignal = signalData["signal"] LogStatus("Follower Strategy - Following Main Strategy\n" + "Current Signal: {}\n".format(signalData["signal"]) + "Signal Price: ${:.2f}\n".format(signalData["price"]) + "Signal Time: {}".format(_D(signalData["timestamp"]))) Sleep(5000)rustfn main() { // Rust 的 GetChannelData() 函数不接受频道 ID 参数,无法订阅主策略实盘的频道, // 只能读取当前实盘自身频道的最新数据(此处演示等价的信号处理逻辑) let mut lastSignal = String::new(); Log!("Follower strategy started"); loop { // 获取频道中的信号 let signalData = GetChannelData(); if signalData.is_null() { LogStatus!("Waiting for signal..."); Sleep(5000); continue; } let signal = signalData["signal"].as_str().unwrap_or("").to_string(); let price = signalData["price"].as_f64().unwrap_or(0.0); let symbol = signalData["symbol"].as_str().unwrap_or("BTC_USDT").to_string(); // 检查是否有新信号 if lastSignal != signal { Log!("Received new signal:", &signal, "Price:", price); // 执行交易 if signal == "BUY" { if let Ok(ticker) = exchange.GetTicker(symbol.as_str()) { let _ = exchange.Buy(ticker.Last, 0.01); Log!("Executing buy, Price:", ticker.Last); } } else if signal == "SELL" { if let Ok(ticker) = exchange.GetTicker(symbol.as_str()) { let _ = exchange.Sell(ticker.Last, 0.01); Log!("Executing sell, Price:", ticker.Last); } } lastSignal = signal.clone(); } LogStatus!(format!( "Follower Strategy\nCurrent Signal: {}\nSignal Price: ${:.2}\nSignal Time: {}", signal, price, _D(signalData["timestamp"].as_i64().unwrap_or(0)) )); Sleep(5000); } }c++ -
场景2:多策略状态监控
监控策略
javascriptfunction main() { // 需要监控的策略实盘ID列表 var monitorList = ["632799", "632800", "632801"] while(true) { var table = { type: "table", title: "策略运行状态监控", cols: ["实盘ID", "状态", "最后更新", "交易对", "当前价格", "盈亏"], rows: [] } for (var i = 0; i < monitorList.length; i++) { var robotId = monitorList[i] var data = GetChannelData(robotId) if (data !== null) { var updateTime = _D(data.timestamp) var timeDiff = Date.now() - data.timestamp var status = timeDiff < 120000 ? "运行中" : "异常" table.rows.push([ robotId, status, updateTime, data.symbol || "-", data.lastPrice ? "$" + data.lastPrice.toFixed(2) : "-", data.profit ? data.profit.toFixed(2) + "%" : "-" ]) } else { table.rows.push([ robotId, "等待数据", "-", "-", "-", "-" ]) } } LogStatus("`" + JSON.stringify(table) + "`") Sleep(10000) } }pythondef main(): # 需要监控的策略实盘ID列表 monitorList = ["632799", "632800", "632801"] while True: table = { "type": "table", "title": "策略运行状态监控", "cols": ["实盘ID", "状态", "最后更新", "交易对", "当前价格", "盈亏"], "rows": [] } for robotId in monitorList: data = GetChannelData(robotId) if data is not None: updateTime = _D(data["timestamp"]) timeDiff = time.time() * 1000 - data["timestamp"] status = "运行中" if timeDiff < 120000 else "异常" table["rows"].append([ robotId, status, updateTime, data.get("symbol", "-"), "${:.2f}".format(data["lastPrice"]) if "lastPrice" in data else "-", "{:.2f}%".format(data["profit"]) if "profit" in data else "-" ]) else: table["rows"].append([ robotId, "等待数据", "-", "-", "-", "-" ]) LogStatus("`" + json.dumps(table) + "`") Sleep(10000)rustfn main() { // Rust 的 GetChannelData() 函数不接受频道 ID 参数,无法订阅其它实盘的频道进行监控, // 只能读取当前实盘自身频道的最新数据(此处演示等价的状态表格展示逻辑) loop { let data = GetChannelData(); let row = if !data.is_null() { let timestamp = data["timestamp"].as_i64().unwrap_or(0); let updateTime = _D(timestamp); let timeDiff = Unix() * 1000 - timestamp; let status = if timeDiff < 120000 { "运行中" } else { "异常" }; format!( r#"["{}", "{}", "{}", "{}"]"#, _G!(), status, updateTime, data["symbol"].as_str().unwrap_or("-") ) } else { format!(r#"["{}", "等待数据", "-", "-"]"#, _G!()) }; // 构造表格JSON文本(Rust无JSON序列化,使用format!拼接) let table = format!( r#"{{"type": "table", "title": "策略运行状态监控", "cols": ["实盘ID", "状态", "最后更新", "交易对"], "rows": [{}]}}"#, row ); LogStatus!(format!("`{}`", table)); Sleep(10000); } }c++ -
API函数说明
SetChannelData(data)
功能:在频道上发布最新的状态数据
参数:
- data:待发布的数据,可以是任意可进行JSON序列化的数据结构
返回值:无
特性:
-
非阻塞调用
-
覆盖此前的数据,不累积历史记录
-
自动使用当前实盘ID作为频道ID
数据长度限制:
-
JSON序列化后不得超过1024字节
-
建议仅传输必要的状态信息
详细文档:SetChannelData
GetChannelData(robotId)
功能:订阅指定实盘的频道数据
参数:
- robotId:待订阅的实盘ID(字符串或数字)
返回值:
-
首次调用返回null,需要重试
-
成功后返回该频道的最新数据
特性:
-
非阻塞调用
-
支持订阅多个频道
-
支持订阅自身的频道
详细文档:GetChannelData
注意事项
-
首次调用返回null:
GetChannelData()函数在首次调用时会返回null,这是正常现象,需要等待数据同步完成。建议在代码中进行null判断。 -
数据覆盖机制:频道上仅保存最新状态,调用
SetChannelData()会覆盖此前的数据。如需保存历史数据,应在订阅端自行记录。 -
非阻塞特性:所有频道通信函数均为非阻塞调用,不会影响策略主流程的执行。但这也意味着无法保证数据的实时性。
-
数据大小限制:传入SetChannelData的数据经JSON序列化后不得超过1024字节。应仅传输必要的状态信息,如交易信号、价格、持仓等关键数据,避免传输完整的K线数组或大量历史数据。
-
实盘环境限制:频道通信功能主要适用于实盘环境,在回测系统中可能受限或不可用。
-
实盘ID获取:可通过
_G()函数获取当前实盘ID,也可在平台界面中查看实盘ID。 -
安全性考虑:频道数据可能被其他具有相应权限的实盘订阅,请勿在频道中传输敏感信息(如API密钥等)。
最佳实践
-
合理的更新频率:根据实际需求设置数据更新频率,避免因更新过于频繁而造成资源浪费。
-
数据结构设计:设计清晰的数据结构,并包含必要的元数据(如时间戳、版本号等),以便于订阅端处理。
-
错误处理:订阅端应处理null返回值,广播端应确保数据格式正确。
-
状态版本控制:在数据中包含版本号或更新ID,帮助订阅端判断是否存在新数据。
-
监控与告警:对于关键的通信链路,建议实现超时监控与告警机制。
-
测试验证:在正式使用前,应先在测试环境中验证频道通信的稳定性与延迟。
-
文档记录:记录频道数据格式与通信协议,以便于后续维护与多人协作。
参考
JavaScript多线程
发明者量化交易平台从系统底层真正支持JavaScript语言策略的多线程功能,实现了以下对象:
| 对象 | 说明 | 备注 |
|---|---|---|
| threading | 多线程全局对象 | 成员函数:Thread、getThread、mainThread等。 |
| Thread | 线程对象 | 成员函数:peekMessage、postMessage、join等。 |
| ThreadLock | 线程锁对象 | 成员函数:acquire、release。可作为线程执行函数的参数传入线程环境。 |
| ThreadEvent | 事件对象 | 成员函数:set、clear、wait、isSet。可作为线程执行函数的参数传入线程环境。 |
| ThreadCondition | 条件对象 | 成员函数:notify、notifyAll、wait、acquire、release。可作为线程执行函数的参数传入线程环境。 |
| ThreadDict | 字典对象 | 成员函数:get、set。可作为线程执行函数的参数传入线程环境。 |
发明者量化交易平台语法手册:JavaScript多线程
Web3
| 函数名称 | 简介 |
|---|---|
| exchange.IO("abi", ...) | 注册ABI接口 |
| exchange.IO("api", "eth", ...) | 调用以太坊RPC方法 |
| exchange.IO("encode", ...) | 对函数调用进行编码 |
| exchange.IO("encodePacked", ...) | 执行encodePacked编码 |
| exchange.IO("decode", ...) | 对数据进行解码 |
| exchange.IO("key", ...) | 切换私钥 |
| exchange.IO("api", ...) | 调用智能合约方法 |
| exchange.IO("address") | 获取当前配置的钱包地址 |
| exchange.IO("base", ...) | 设置RPC节点地址 |
TA指标库
| 函数名称 | 简介 |
|---|---|
| TA.MACD | 计算指数平滑异同移动平均线指标 |
| TA.KDJ | 计算随机指标 |
| TA.RSI | 计算相对强弱指标 |
| TA.ATR | 计算真实波动幅度均值指标 |
| TA.OBV | 计算能量潮指标 |
| TA.MA | 计算移动平均线指标 |
| TA.EMA | 计算指数移动平均线指标 |
| TA.BOLL | 计算布林带指标 |
| TA.Alligator | 计算鳄鱼线指标 |
| TA.CMF | 计算蔡金资金流量指标 |
| TA.Highest | 计算指定周期内的最高价 |
| TA.Lowest | 计算指定周期内的最低价 |
| TA.SMA | 计算简单移动平均线指标 |
talib指标库
talib指标库包含众多技术分析指标,例如:talib.CDL2CROWS。详细信息请参阅语法手册。