输入/搜索内容
欢迎使用发明者量化交易平台
编程语言
JavaScript
TypeScript
Python
C++
My语言(麦语言)
PINE语言
Blockly可视化
Workflow工作流
密钥安全性
实盘
策略库
托管者
部署托管者
一键租用托管者
手动部署托管者
托管者操作注意事项
全局指定IP地址
命令行版本托管者程序的参数
实盘数据迁移
托管者监控
交易所
策略编辑器
回测系统
策略入口函数
策略框架与API函数
模板类库
策略参数
交互控件
期权交易
C++策略编写说明
JavaScript策略编写说明
Web3
内置库
扩展API接口
MCP 服务
交易终端
数据探索
Alpha因子分析工具
通用协议
调试工具
远程编辑
完整策略的导入与导出
多语言支持
实盘、策略分组
实盘展示
策略分享与出租
实盘消息推送
实盘报错、异常退出的常见原因
交易所特殊说明

JavaScriptPythonC++语言编写的策略中,需要在策略主循环中调用Sleep()函数。回测时用于控制回测的速度,实盘时用于控制策略轮询的时间间隔,从而控制访问交易所API接口的请求频率。

示例

  • 加密货币策略基本框架范例:

    javascript
    function onTick(){ //在这里写策略逻辑,将会不断调用,例如打印行情信息 Log(exchange.GetTicker()) } function main(){ while(true){ onTick() // Sleep函数主要用于数字货币策略的轮询频率控制,防止访问交易所API接口过于频繁 Sleep(60000) } }
    python
    def 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的买单可以这样写:

    javascript
    function onTick(){ // 这个仅仅是例子,回测或者实盘会很快把资金全部用于下单,实盘请勿使用 exchange.Buy(100, 1) } function main(){ while(true){ onTick() // 暂停多久可自定义,单位为毫秒,1秒等于1000毫秒 Sleep(1000) } }
    python
    def 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架构的策略

    javascript
    function 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) } }
    python
    def 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发送邮件
Mail_GoMail函数的异步版本
SetErrorFilter过滤错误日志,参数为正则表达式字符串,匹配该正则表达式的错误日志将不会上传到日志系统
GetPid获取实盘进程ID
GetLastError获取最近一次的错误信息
GetCommand获取策略交互命令,策略交互控件设置请参考:交互控件
GetMeta获取生成策略注册码时写入的Meta值
Dial用于原始Socket访问
HttpQuery发送HTTP请求
HttpQuery_GoHttpQuery函数的异步版本
Encode数据编码函数
UnixNano获取纳秒级时间戳
Unix获取秒级时间戳
GetOS获取系统信息
MD5计算MD5哈希值
DBExec数据库函数,用于执行SQL语句并进行数据库操作
UUID生成UUID
EventLoop监听事件,在任意WebSocket可读或exchange.GoHttpQuery_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图表库
KLineChartPine语言风格的图表绘制函数,用于在策略运行时以类似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的调用频率,防止触发交易所的频率限制导致账户被封禁或临时限制。FMZ平台提供了灵活的限流配置方式,支持两种限流模式和多种配置策略。

为什么需要API限流

  • 避免触发交易所限制:大多数交易所对API调用频率有严格限制,超限会导致账户被暂时或永久封禁。
  • 合理分配API配额:在多策略、多交易对场景下,需要合理分配API调用资源。
  • 提高策略稳定性:通过主动限流,避免因频繁调用导致的连接失败和数据获取异常。
  • 符合交易所规范:遵守交易所的API使用规范,维护良好的API使用关系。

两种限流模式

rate模式(平滑限流)

  • 适用于一般限流需求
  • 不严格对齐时间窗口
  • 调用分布相对平滑
  • 推荐用于日常API调用限制

quota模式(额度限流)

  • 严格对齐时间窗口
  • 例如设置"1s"时,窗口对齐到整秒;设置"1m"时,窗口对齐到整分钟
  • 适用于需要严格控制时间窗口的场景
  • 推荐用于日内配额管理

基本用法

rate模式基本示例

示例

  • undefined
    javascript
    function 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) } }
    python
    def 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模式基本示例

    javascript
    function 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 } }
    python
    def 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 limit
    c++
    // C++暂不支持
  • 函数名配置

    单个函数限流

    javascript
    function 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") }
    python
    def 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++暂不支持
  • 多个函数联合限流

    javascript
    function 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 } } }
    python
    def 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 quota
    c++
    // C++暂不支持
  • 使用通配符限制所有函数

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

    javascript
    function 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 }
    python
    def 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模式下生效。

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

    javascript
    function 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) } } }
    python
    def 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模式(超限自动等待)

    javascript
    function 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 } }
    python
    def 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不会为None
    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:防止触发交易所频率限制

    javascript
    function 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) } }
    python
    def 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:多交易所对象统一限流

    javascript
    function 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) } }
    python
    def 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:日内配额管理

    javascript
    function 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) } }
    python
    def 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:组合限流策略

    javascript
    function 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) } }
    python
    import 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时窗口就会重置。

    javascript
    function 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次,成功 }
    python
    def 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调用时间与日志记录时间可能不一致。这是因为当触发限流时,程序会等待,但日志记录的是等待结束后的时间。

    javascript
    function 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次是在新窗口执行的 }
    python
    def 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函数的限流

    BuySell函数底层调用CreateOrder,因此它们的限流遵循CreateOrder的设置。

    javascript
    function 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限制 } } }
    python
    def 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函数的限流取决于实际并发调用的函数。

    javascript
    function 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") } }
    python
    def 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功能。

    javascript
    function 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") // 设置其他限流,不受限 }
    python
    def 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序列化的数据结构

应用场景

  • 主从策略协同:主策略分析市场并广播信号,从策略接收信号执行交易
  • 多账户同步:在多个交易账户之间同步交易信号和仓位信息
  • 策略监控:监控策略广播运行状态,监控实盘订阅并进行展示或告警
  • 数据共享:共享行情分析、指标计算等结果,避免重复计算

基本用法

示例

  • 广播端示例 - 发布市场数据

    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)) 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"])) + "Last Price: ${:.2f}".format(channelState["lastPrice"])) Sleep(60000) # 每分钟更新一次频道状态
    c++
  • 订阅端示例 - 订阅多个频道

    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" } 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秒订阅一次频道 } }
    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 += "最新价: ${:.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:主从策略协同交易

    主策略(信号广播端)

    javascript
    function 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) } }
    python
    def 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:主从策略协同交易

    从策略(信号接收执行端)

    javascript
    function 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) } }
    python
    def 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:多策略状态监控

    监控策略

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

    注意事项

    • 首次调用返回nullGetChannelData()函数首次调用时会返回null,这是正常现象,需要等待数据同步完成。建议在代码中进行null判断。

    • 数据覆盖机制:频道上仅保存最新状态,调用SetChannelData()会覆盖之前的数据。如需保存历史数据,应在订阅端自行记录。

    • 非阻塞特性:所有频道通信函数均为非阻塞的,不会影响策略的主流程执行。但这也意味着无法保证数据的即时性。

    • 数据大小限制:传入SetChannelData的数据在JSON序列化后不得超过1024字节。应仅传输必要的状态信息,如交易信号、价格、持仓等关键数据,避免传输完整的K线数组或大量历史数据。

    • 实盘环境限制:频道通信功能主要适用于实盘环境,在回测系统中可能受限或不可用。

    • 实盘ID获取:可通过_G()函数获取当前实盘ID,也可在平台界面查看实盘ID。

    • 安全性考虑:频道数据可能被其他有权限的实盘订阅,请勿在频道中传输敏感信息(如API密钥等)。

    最佳实践

    • 合理的更新频率:根据实际需求设置数据更新频率,避免过于频繁的更新造成资源浪费。

    • 数据结构设计:设计清晰的数据结构,包含必要的元数据(如时间戳、版本号等),便于订阅端处理。

    • 错误处理:订阅端应处理null返回值,广播端应确保数据格式正确。

    • 状态版本控制:在数据中包含版本号或更新ID,帮助订阅端判断是否有新数据。

    • 监控与告警:对于关键的通信链路,建议实现超时监控和告警机制。

    • 测试验证:在正式使用前,先在测试环境验证频道通信的稳定性和延迟。

    • 文档记录:记录频道数据格式和通信协议,便于后续维护和多人协作。

参考

发明者量化交易平台从系统底层真正支持JavaScript语言策略的多线程功能,实现了以下对象:

对象说明备注
threading多线程全局对象成员函数:ThreadgetThreadmainThread等。
Thread线程对象成员函数:peekMessagepostMessagejoin等。
ThreadLock线程锁对象成员函数:acquirerelease。可作为线程执行函数的参数传入线程环境。
ThreadEvent事件对象成员函数:setclearwaitisSet。可作为线程执行函数的参数传入线程环境。
ThreadCondition条件对象成员函数:notifynotifyAllwaitacquirerelease。可作为线程执行函数的参数传入线程环境。
ThreadDict字典对象成员函数:getset。可作为线程执行函数的参数传入线程环境。

发明者量化交易平台语法手册:JavaScript多线程

函数名称简介
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.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.CDL2CROWS。详细信息请参阅语法手册。