策略框架与API函数
在JavaScript、Python、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)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)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)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)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 limitc++// 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")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 quotac++// 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)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次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")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)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不会为Nonec++// 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)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)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)c++// C++暂不支持 -
场景4:组合限流策略
javascriptfunction main() { // 组合多种限流策略 // 1. 行情类API每秒限制 exchange.IO("rate", "GetTicker,GetDepth", 20, "1s") // 2. 交易类API每秒限制 exchange.IO("rate", "CreateOrder,CancelOrder", 5, "1s") // 3. 账户查询每分钟限制 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. 账户查询每分钟限制 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)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次,成功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次是在新窗口执行的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限制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")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") # 设置其他限流,不受限c++// C++暂不支持 -
最佳实践
1. 根据交易所限制进行设置:参考交易所API文档,将限制值设置为略低于交易所规定的最大值。
2. 预留安全余量:不要将限制设置为交易所的最大值,建议设置为最大值的70%-80%。
3. 实施分层限流:针对不同类型的API设置不同的限制,为重要API预留更多余量。
4. 对关键调用使用delay模式:对于必须成功的API调用,使用
"delay"模式确保调用成功。5. 监控API使用情况:定期检查策略的API调用频率,持续优化调用逻辑。
6. 避免过度调用:合理设计策略逻辑,减少不必要的API调用。
7. 测试限流配置:在实盘交易前,先在模拟环境中测试限流配置的合理性。
参考
策略实盘间通信
功能概述
策略实盘间通信功能允许不同的实盘策略之间进行数据共享和状态同步。通过频道机制,一个实盘可以将自己的状态数据广播给其他实盘,实现跨实盘、跨托管者、跨服务器的数据通信。
核心概念
- 频道(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) # 每分钟更新一次频道状态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秒订阅一次频道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)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)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)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。详细信息请参阅语法手册。