策略框架与API函数
在JavaScript、Python、C++语言编写的策略中,需要在策略主循环中调用Sleep()函数。回测时用于控制回测的速度,实盘时用于控制策略轮询的时间间隔,从而控制访问交易所API接口的请求频率。
示例
-
经典的商品期货策略架构:
javascriptfunction main(){ while(true){ // 需要在判断exchange.IO("status")函数返回true,即为真值时才可调用行情、交易等函数 if(exchange.IO("status")){ exchange.SetContractType("MA000") var ticker = exchange.GetTicker() Log("MA000 ticker:", ticker) LogStatus(_D(), "已经连接CTP !") } else { LogStatus(_D(), "未连接CTP !") } } }pythondef main(): while True: if exchange.IO("status"): exchange.SetContractType("MA000") ticker = exchange.GetTicker() Log("MA000 ticker:", ticker) LogStatus(_D(), "已经连接CTP !") else: LogStatus(_D(), "未连接CTP !")c++void main() { while(true) { if(exchange.IO("status") == 1) { exchange.SetContractType("MA000"); auto ticker = exchange.GetTicker(); Log("MA000 ticker:", ticker); LogStatus(_D(), "已经连接CTP !"); } else { LogStatus(_D(), "未连接CTP !"); } } } -
设计一个On Bar架构的策略
javascriptfunction onTick() { Log("K线更新,新BAR产生") } function main() { var lastTs = 0 while (true) { if (exchange.IO("status")) { exchange.SetContractType("rb888") 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线更新,新BAR产生") def main(): lastTs = 0 while True: if exchange.IO("status"): exchange.SetContractType("rb888") 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线更新,新BAR产生"); } void main() { Record lastBar; lastBar.Time = 0; while (true) { if (exchange.IO("status") == 1) { exchange.SetContractType("rb888"); 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 | 监听事件 |
| _G | 持久化保存数据,该函数实现可保存的全局字典功能。数据结构为KV表,永久保存在托管者本地数据库文件中 |
| _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 | 获取市场成交记录(基于Tick数据计算) |
| exchange.GetRecords | 获取K线数据 |
| exchange.GetPeriod | 获取当前K线周期 |
| exchange.SetMaxBarLen | 设置K线最大长度 |
| exchange.GetRate | 获取当前设置的汇率 |
| exchange.SetData | 设置策略运行时加载的数据 |
| exchange.GetData | 获取已加载的数据或外部链接提供的数据 |
| exchange.GetMarkets | 获取已订阅的交易品种信息 |
| exchange.GetTickers | 获取已订阅交易品种的聚合行情数据 |
交易函数
| 函数名称 | 简介 |
|---|---|
| exchange.Buy | 提交买单,期货合约下单时必须注意交易方向是否设置正确,如果交易方向与交易函数不匹配将报错 |
| exchange.Sell | 提交卖单,期货合约下单时必须注意交易方向是否设置正确,如果交易方向与交易函数不匹配将报错 |
| exchange.CreateOrder | 提交订单,通过参数指定交易品种、交易方向、价格和数量 |
| exchange.CancelOrder | 取消订单 |
| exchange.GetOrder | 获取订单信息,数据结构为Order结构 |
| exchange.GetOrders | 获取未完成的订单,数据结构为Order结构数组(列表) |
| exchange.GetHistoryOrders | 获取当前交易日所有合约的历史订单;支持获取指定合约的当前交易日历史订单 |
| exchange.SetPrecision | 设置exchange交易所对象的价格与下单量精度,设置后系统将自动忽略超出精度的部分 |
| exchange.SetRate | 设置汇率 |
| exchange.IO | 用于交易所对象相关的其他接口调用 |
| exchange.Log | 输出、记录交易日志,不执行实际下单 |
| exchange.GetName | 获取交易所对象的名称 |
| exchange.GetLabel | 获取交易所对象的标签 |
| exchange.GetCurrency | 获取当前交易对 |
| exchange.GetAccount | 获取账户信息 |
| exchange.GetQuoteCurrency | 获取当前交易对的计价币名称 |
期货函数
| 函数名称 | 简介 |
|---|---|
| exchange.GetPositions | 获取期货持仓信息,返回Position结构数组(列表) |
| exchange.SetMarginLevel | 设置杠杆倍数 |
| exchange.SetDirection | 设置exchange.Buy函数、exchange.Sell函数在期货合约下单时的交易方向 |
| exchange.SetContractType | 设置合约代码,例如切换当前操作的合约为螺纹钢主力连续合约:exchange.SetContractType("rb888") |
| exchange.GetContractType | 获取当前设置的合约代码 |
| StrDecode | GBK编码解码 |
API限流控制
功能概述
优宽量化平台支持对商品期货交易所API调用进行频率限制,防止触发交易所的频率限制。通过合理配置API限流,可以有效避免因频繁调用导致的接口限制问题,保障策略稳定运行。
API限流功能支持两种限流模式:
- rate模式(平滑限流):不严格对齐时间窗口,适用于一般限流需求。例如设置每秒10次,系统会平滑地控制调用频率。
- quota模式(额度限流):严格对齐时间窗口,例如1m对齐到整分钟,1s对齐到整秒。适用于需要严格控制时间窗口的场景。
基础用法
函数签名
javascript
exchange.IO("rate", functionNames, maxCalls, period, [behavior])
exchange.IO("quota", functionNames, maxCalls, period, [behavior])
参数说明
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| mode | string | 是 | 限流模式:"rate"(平滑限流)或 "quota"(额度限流) |
| functionNames | string | 是 | 要限制的函数名,支持单个函数、多个函数(逗号分隔)、通配符(*表示所有函数) |
| maxCalls | number | 是 | 时间周期内允许的最大调用次数 |
| period | string | 是 | 时间周期或重置时间点。支持时间单位:ns、us、µs、ms、s、m、h、d(例如:"1s"、"1m"、"1h")。支持重置时间点:@HHMM 或 @HHMMSS(例如:"@0815"表示每天08:15重置) |
| behavior | string | 否 | 超限行为。默认为空(超限返回null并报错);设置为"delay"时超限会等待 |
支持的函数列表
优宽量化平台的API限流功能支持以下函数:
交易类(2个):
CreateOrder- 创建订单CancelOrder- 撤销订单
账户类(2个):
GetAccount- 获取账户信息GetPositions- 获取持仓信息
订单类(3个):
GetOrder- 获取订单信息GetOrders- 获取订单列表GetHistoryOrders- 获取历史订单
行情类(3个):
GetTicker- 获取行情Tick数据GetDepth- 获取深度数据GetRecords- 获取K线数据
其它(1个):
IO/api- 仅对exchange.IO("api", ...)调用生效
函数名称配置
单个函数限流
限制单个函数的调用频率:
示例
- undefinedjavascriptfunction main() { while (!exchange.IO("status")) { Sleep(1000) } // 限制GetTicker每秒最多调用3次 exchange.IO("rate", "GetTicker", 3, "1s") for (var i = 0; i < 10; i++) { if (!exchange.IO("status")) { Sleep(5000) continue } var ticker = exchange.GetTicker("rb888") if (ticker) { Log("Success:", ticker.Last) } else { Log("Rate limit exceeded", "#FF0000") } Sleep(100) } }pythondef main(): while not exchange.IO("status"): Sleep(1000) # 限制GetTicker每秒最多调用3次 exchange.IO("rate", "GetTicker", 3, "1s") for i in range(10): if not exchange.IO("status"): Sleep(5000) continue ticker = exchange.GetTicker("rb888") if ticker: Log("Success:", ticker["Last"]) else: Log("Rate limit exceeded", "#FF0000") Sleep(100)c++// C++暂不支持
-
多个函数联合限流
多个函数共享同一个限流配额:
javascriptfunction main() { while (!exchange.IO("status")) { Sleep(1000) } // GetTicker和GetDepth共享限制,合计每秒最多5次 exchange.IO("rate", "GetTicker,GetDepth", 5, "1s") for (var i = 0; i < 10; i++) { if (!exchange.IO("status")) { Sleep(5000) continue } if (i % 2 == 0) { var ticker = exchange.GetTicker("m888") Log("GetTicker:", ticker ? "Success" : "Failed") } else { var depth = exchange.GetDepth("m888") Log("GetDepth:", depth ? "Success" : "Failed") } Sleep(100) } }pythondef main(): while not exchange.IO("status"): Sleep(1000) # GetTicker和GetDepth共享限制,合计每秒最多5次 exchange.IO("rate", "GetTicker,GetDepth", 5, "1s") for i in range(10): if not exchange.IO("status"): Sleep(5000) continue if i % 2 == 0: ticker = exchange.GetTicker("m888") Log("GetTicker:", "Success" if ticker else "Failed") else: depth = exchange.GetDepth("m888") Log("GetDepth:", "Success" if depth else "Failed") Sleep(100)c++// C++暂不支持 -
通配符限流
使用通配符
*限制所有API调用:javascriptfunction main() { while (!exchange.IO("status")) { Sleep(1000) } // 限制所有API调用每秒最多10次 exchange.IO("mode", 0) // 建议配合立即返回模式 exchange.IO("rate", "*", 10, "1s") var symbols = ["rb888", "m888", "i888"] for (var i = 0; i < 5; i++) { if (!exchange.IO("status")) { Sleep(5000) continue } var symbol = symbols[i % symbols.length] var ticker = exchange.GetTicker(symbol) var account = exchange.GetAccount() Log("Round", i+1, "Ticker:", ticker ? "✓" : "✗", "Account:", account ? "✓" : "✗") Sleep(500) } }pythondef main(): while not exchange.IO("status"): Sleep(1000) # 限制所有API调用每秒最多10次 exchange.IO("mode", 0) # 建议配合立即返回模式 exchange.IO("rate", "*", 10, "1s") symbols = ["rb888", "m888", "i888"] for i in range(5): if not exchange.IO("status"): Sleep(5000) continue symbol = symbols[i % len(symbols)] ticker = exchange.GetTicker(symbol) account = exchange.GetAccount() Log("Round", i+1, "Ticker:", "✓" if ticker else "✗", "Account:", "✓" if account else "✗") Sleep(500)c++// C++暂不支持 -
时间周期配置
固定时间周期
支持多种时间单位:
ns(纳秒)、us/µs(微秒)、ms(毫秒)、s(秒)、m(分钟)、h(小时)、d(天)。javascriptfunction main() { while (!exchange.IO("status")) { Sleep(1000) } // 不同时间周期的限流配置 exchange.IO("rate", "GetTicker", 10, "1s") // 每秒10次 exchange.IO("rate", "GetDepth", 60, "1m") // 每分钟60次 exchange.IO("rate", "GetAccount", 100, "1h") // 每小时100次 Log("Rate limits configured") }pythondef main(): while not exchange.IO("status"): Sleep(1000) # 不同时间周期的限流配置 exchange.IO("rate", "GetTicker", 10, "1s") # 每秒10次 exchange.IO("rate", "GetDepth", 60, "1m") # 每分钟60次 exchange.IO("rate", "GetAccount", 100, "1h") # 每小时100次 Log("Rate limits configured")c++// C++暂不支持 -
日内重置时间点
使用
@HHMM或@HHMMSS格式指定每日的重置时间:javascriptfunction main() { while (!exchange.IO("status")) { Sleep(1000) } // 每天08:15重置配额,限制1000次 exchange.IO("quota", "GetTicker", 1000, "@0815") Log("Daily quota configured: 1000 calls, reset at 08:15") }pythondef main(): while not exchange.IO("status"): Sleep(1000) # 每天08:15重置配额,限制1000次 exchange.IO("quota", "GetTicker", 1000, "@0815") Log("Daily quota configured: 1000 calls, reset at 08:15")c++// C++暂不支持 -
行为模式配置
默认模式(返回 null)
超限时 API 调用返回 null,不等待:
javascriptfunction main() { while (!exchange.IO("status")) { Sleep(1000) } // 默认模式:超限返回null exchange.IO("rate", "GetTicker", 3, "1s") for (var i = 0; i < 10; i++) { if (!exchange.IO("status")) { Sleep(5000) continue } var ticker = exchange.GetTicker("rb888") if (ticker) { Log("Call", i+1, "Success") } else { Log("Call", i+1, "Rate limit exceeded, returned null", "#FF0000") } Sleep(100) } }pythondef main(): while not exchange.IO("status"): Sleep(1000) # 默认模式:超限返回null exchange.IO("rate", "GetTicker", 3, "1s") for i in range(10): if not exchange.IO("status"): Sleep(5000) continue ticker = exchange.GetTicker("rb888") if ticker: Log("Call", i+1, "Success") else: Log("Call", i+1, "Rate limit exceeded, returned null", "#FF0000") Sleep(100)c++// C++暂不支持 -
delay模式(自动等待)
超限时自动等待,直到可以调用:
javascriptfunction main() { while (!exchange.IO("status")) { Sleep(1000) } // delay模式:超限自动等待 exchange.IO("rate", "GetTicker", 3, "1s", "delay") Log("Testing delay mode", "#00FF00") for (var i = 0; i < 10; i++) { if (!exchange.IO("status")) { Sleep(5000) continue } var startTime = new Date().getTime() var ticker = exchange.GetTicker("m888") var endTime = new Date().getTime() Log("Call", i+1, "Time cost:", endTime - startTime, "ms") } }pythonimport time def main(): while not exchange.IO("status"): Sleep(1000) # delay模式:超限自动等待 exchange.IO("rate", "GetTicker", 3, "1s", "delay") Log("Testing delay mode", "#00FF00") for i in range(10): if not exchange.IO("status"): Sleep(5000) continue startTime = time.time() * 1000 ticker = exchange.GetTicker("m888") endTime = time.time() * 1000 Log("Call", i+1, "Time cost:", endTime - startTime, "ms")c++// C++暂不支持 -
限流模式对比
rate模式 vs quota模式
javascriptfunction main() { while (!exchange.IO("status")) { Sleep(1000) } exchange.IO("mode", 0) // rate模式:平滑限流,不严格对齐时间窗口 // exchange.IO("rate", "GetTicker", 3, "1s") // quota模式:严格对齐时间窗口,例如1s对齐到整秒 exchange.IO("quota", "GetTicker", 3, "1s") for (var i = 0; i < 8; i++) { if (!exchange.IO("status")) { Sleep(5000) continue } var ticker = exchange.GetTicker("c888") Log(_D(), "Call", i+1, ticker ? "Success" : "Quota exceeded") Sleep(150) // 每150ms调用一次 } }pythondef main(): while not exchange.IO("status"): Sleep(1000) exchange.IO("mode", 0) # rate模式:平滑限流,不严格对齐时间窗口 # exchange.IO("rate", "GetTicker", 3, "1s") # quota模式:严格对齐时间窗口,例如1s对齐到整秒 exchange.IO("quota", "GetTicker", 3, "1s") for i in range(8): if not exchange.IO("status"): Sleep(5000) continue ticker = exchange.GetTicker("c888") Log(_D(), "Call", i+1, "Success" if ticker else "Quota exceeded") Sleep(150) # 每150ms调用一次c++// C++暂不支持 -
实际应用场景
场景1:单品种行情监控
监控单个合约的行情,避免频繁调用:
javascriptfunction main() { while (!exchange.IO("status")) { LogStatus("Waiting for connection...") Sleep(1000) } // 设置行情获取频率限制 exchange.IO("rate", "GetTicker", 5, "1s", "delay") Log("Monitoring rb888 (rebar) price") while (true) { if (!exchange.IO("status")) { Sleep(5000) continue } var ticker = exchange.GetTicker("rb888") LogStatus(_D(), "rb888 Price:", ticker.Last, "Buy:", ticker.Buy, "Sell:", ticker.Sell) Sleep(200) } }pythondef main(): while not exchange.IO("status"): LogStatus("Waiting for connection...") Sleep(1000) # 设置行情获取频率限制 exchange.IO("rate", "GetTicker", 5, "1s", "delay") Log("Monitoring rb888 (rebar) price") while True: if not exchange.IO("status"): Sleep(5000) continue ticker = exchange.GetTicker("rb888") LogStatus(_D(), "rb888 Price:", ticker["Last"], "Buy:", ticker["Buy"], "Sell:", ticker["Sell"]) Sleep(200)c++// C++暂不支持 -
场景2:多品种轮询策略
轮询多个合约行情,控制总体调用频率:
javascriptfunction main() { while (!exchange.IO("status")) { Sleep(1000) } exchange.IO("mode", 0) // 所有行情函数共享配额 exchange.IO("rate", "GetTicker,GetDepth", 10, "1s") var symbols = ["rb888", "m888", "i888", "c888"] Log("Monitoring multiple symbols:", symbols.join(", ")) while (true) { if (!exchange.IO("status")) { Sleep(5000) continue } for (var i = 0; i < symbols.length; i++) { var ticker = exchange.GetTicker(symbols[i]) if (ticker) { Log(symbols[i], "Price:", ticker.Last) } } Sleep(1000) } }pythondef main(): while not exchange.IO("status"): Sleep(1000) exchange.IO("mode", 0) # 所有行情函数共享配额 exchange.IO("rate", "GetTicker,GetDepth", 10, "1s") symbols = ["rb888", "m888", "i888", "c888"] Log("Monitoring multiple symbols:", ", ".join(symbols)) while True: if not exchange.IO("status"): Sleep(5000) continue for symbol in symbols: ticker = exchange.GetTicker(symbol) if ticker: Log(symbol, "Price:", ticker["Last"]) Sleep(1000)c++// C++暂不支持 -
场景3:高频交易策略
对交易相关接口进行严格限流:
javascriptfunction main() { while (!exchange.IO("status")) { Sleep(1000) } // 分别设置不同函数的限流 exchange.IO("rate", "GetTicker", 10, "1s", "delay") exchange.IO("rate", "CreateOrder", 2, "1s", "delay") exchange.IO("rate", "CancelOrder", 5, "1s", "delay") exchange.IO("rate", "GetAccount", 1, "1s", "delay") Log("Rate limits configured for high-frequency trading") // 策略逻辑 while (true) { if (!exchange.IO("status")) { Sleep(5000) continue } var ticker = exchange.GetTicker("rb888") var account = exchange.GetAccount() // 交易逻辑(示例) LogStatus(_D(), "Price:", ticker.Last, "Balance:", account.Balance) Sleep(500) } }pythondef main(): while not exchange.IO("status"): Sleep(1000) # 分别设置不同函数的限流 exchange.IO("rate", "GetTicker", 10, "1s", "delay") exchange.IO("rate", "CreateOrder", 2, "1s", "delay") exchange.IO("rate", "CancelOrder", 5, "1s", "delay") exchange.IO("rate", "GetAccount", 1, "1s", "delay") Log("Rate limits configured for high-frequency trading") # 策略逻辑 while True: if not exchange.IO("status"): Sleep(5000) continue ticker = exchange.GetTicker("rb888") account = exchange.GetAccount() # 交易逻辑(示例) LogStatus(_D(), "Price:", ticker["Last"], "Balance:", account["Balance"]) Sleep(500)c++// C++暂不支持 -
场景4:日内交易次数控制
使用 quota 模式控制每日总交易次数:
javascriptfunction main() { while (!exchange.IO("status")) { Sleep(1000) } // 每天09:00重置,限制每日开仓100次 exchange.IO("quota", "CreateOrder", 100, "@0900") Log("Daily order limit: 100 orders, reset at 09:00") var orderCount = 0 while (true) { if (!exchange.IO("status")) { Sleep(5000) continue } var ticker = exchange.GetTicker("m888") // 交易逻辑(示例) // var orderId = exchange.CreateOrder(...) // if (orderId) { // orderCount++ // Log("Order created, daily count:", orderCount) // } else { // Log("Daily order quota exceeded", "#FF0000") // } Sleep(1000) } }pythondef main(): while not exchange.IO("status"): Sleep(1000) # 每天09:00重置,限制每日开仓100次 exchange.IO("quota", "CreateOrder", 100, "@0900") Log("Daily order limit: 100 orders, reset at 09:00") orderCount = 0 while True: if not exchange.IO("status"): Sleep(5000) continue ticker = exchange.GetTicker("m888") # 交易逻辑(示例) # orderId = exchange.CreateOrder(...) # if orderId: # orderCount += 1 # Log("Order created, daily count:", orderCount) # else: # Log("Daily order quota exceeded", "#FF0000") Sleep(1000)c++// C++暂不支持 -
注意事项
-
必须检查连接状态
- 使用API限流功能前,必须先通过
exchange.IO("status")检查与交易服务器的连接状态 - 在循环中持续检查连接状态,确保策略稳定运行
- 使用API限流功能前,必须先通过
-
配合立即返回模式
- 建议配合
exchange.IO("mode", 0)立即返回模式使用 - 避免行情获取函数的等待影响限流效果
- 建议配合
-
商品期货市场
- 优宽量化支持的市场为商品期货
- 合约代码格式示例:rb888(螺纹钢)、m888(豆粕)、i888(铁矿石)、c888(玉米)
-
限流配置时机
- 建议在策略初始化阶段配置限流规则
- 限流配置立即生效,无需重启策略
-
quota模式时间对齐
- quota模式严格对齐时间窗口
- 例如设置"1s"时,时间窗口对齐到整秒;设置"1m"时,时间窗口对齐到整分钟
-
delay模式的时间差异
- 使用"delay"参数时,API调用时间与日志记录时间可能存在差异
- 这是因为函数在等待限流窗口,属于正常现象
-
合理设置限流参数
- 根据交易所的实际限制设置参数
- 预留一定余量,避免触及交易所硬限制
最佳实践
-
分级限流策略
- 对不同类型的API设置不同的限流参数
- 重要的交易接口(CreateOrder、CancelOrder)设置更严格的限制
- 行情接口可以相对宽松
-
结合实际业务
- 根据策略的实际调用频率设置限流
- 监控实际调用情况,动态调整限流参数
-
异常处理
- 对返回null的情况进行妥善处理
- 记录超限日志,便于后续分析优化
-
测试验证
- 在模拟环境中充分测试限流配置
- 验证各种极端情况下的表现
-
持续监控
- 监控API调用频率和限流触发情况
- 定期检查和优化限流配置
-
策略实盘间通信
功能概述
策略实盘间通信功能允许不同的实盘策略之间进行数据共享和状态同步。通过频道机制,一个实盘可以将自己的状态数据广播给其他实盘,实现跨实盘、跨托管者、跨服务器的数据通信。
核心概念
- 频道(Channel):每个实盘都有一个独立的频道,频道ID即为实盘ID
- 广播端:使用
SetChannelData()函数在频道上发布数据的实盘 - 订阅端:使用
GetChannelData()函数订阅其他实盘频道数据的实盘 - 状态覆盖:频道上仅保存最新状态,新数据会覆盖旧数据,而非消息队列
主要特性
- 非阻塞通信:所有函数调用均为非阻塞式,不会影响策略主流程
- 跨平台支持:支持跨实盘、跨托管者、跨服务器进行数据传输
- 多频道订阅:单个实盘可同时订阅多个不同实盘的频道
- 灵活的数据格式:支持任意可JSON序列化的数据结构
应用场景
- 主从策略协同:主策略分析市场并广播信号,从策略接收信号执行交易
- 多账户同步:在多个交易账户之间同步交易信号和仓位信息
- 策略监控:监控策略广播运行状态,监控实盘订阅并进行展示或告警
- 数据共享:共享行情分析、指标计算等结果,避免重复计算
基本用法
示例
-
广播端示例 - 发布市场数据
javascriptfunction main() { var updateId = 0 var robotId = _G() // 获取当前实盘ID while(true) { if (!exchange.IO("status")) { Sleep(1000) continue } // 获取市场数据 var ticker = exchange.GetTicker("rb888") if (!ticker) { Sleep(5000) continue } // 准备频道状态数据 var channelState = { robotId: robotId, updateId: ++updateId, timestamp: Date.now(), symbol: "rb888", lastPrice: ticker.Last, volume: ticker.Volume, high: ticker.High, low: ticker.Low } // 在频道上发布最新状态(覆盖旧状态) SetChannelData(channelState) // 显示当前频道状态 LogStatus("频道广播端 [实盘ID: " + robotId + "]\n" + "更新ID: #" + channelState.updateId + "\n" + "时间: " + _D(channelState.timestamp) + "\n" + "交易对: " + channelState.symbol + "\n" + "最新价: $" + channelState.lastPrice.toFixed(2)) Sleep(60000) // 每分钟更新一次频道状态 } }pythondef main(): updateId = 0 robotId = _G() # 获取当前实盘ID while True: if not exchange.IO("status"): Sleep(1000) continue # 获取市场数据 ticker = exchange.GetTicker("rb888") if not ticker: Sleep(5000) continue # 准备频道状态数据 channelState = { "robotId": robotId, "updateId": updateId + 1, "timestamp": time.time() * 1000, "symbol": "rb888", "lastPrice": ticker["Last"], "volume": ticker["Volume"], "high": ticker["High"], "low": ticker["Low"] } updateId += 1 # 在频道上发布最新状态(覆盖旧状态) SetChannelData(channelState) # 显示当前频道状态 LogStatus("频道广播端 [实盘ID: {}]\n".format(robotId) + "更新ID: #{}\n".format(channelState["updateId"]) + "时间: {}\n".format(_D(channelState["timestamp"])) + "最新价: ${:.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("主策略启动,实盘ID:", robotId) while(true) { if (!exchange.IO("status")) { Sleep(1000) continue } // 分析市场,生成交易信号 var records = exchange.GetRecords("rb888") 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: "rb888", signal: signal, price: records[records.length-1].Close, ma5: ma5[ma5.length-1], ma20: ma20[ma20.length-1] } SetChannelData(signalData) LogStatus("主策略 - 信号广播\n" + "信号: " + signal + "\n" + "价格: $" + signalData.price.toFixed(2) + "\n" + "MA5: " + signalData.ma5.toFixed(2) + "\n" + "MA20: " + signalData.ma20.toFixed(2)) Sleep(60000) } }pythondef main(): robotId = _G() Log("主策略启动,实盘ID:", robotId) while True: if not exchange.IO("status"): Sleep(1000) continue # 分析市场,生成交易信号 records = exchange.GetRecords("rb888") 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": "rb888", "signal": signal, "price": records[-1]["Close"], "ma5": ma5[-1], "ma20": ma20[-1] } SetChannelData(signalData) LogStatus("主策略 - 信号广播\n" + "信号: {}\n".format(signal) + "价格: ${:.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("从策略启动,订阅主策略:", masterRobotId) while(true) { // 获取主策略的信号 var signalData = GetChannelData(masterRobotId) if (signalData === null) { LogStatus("等待主策略信号...") Sleep(5000) continue } // 检查是否有新信号 if (lastSignal !== signalData.signal) { Log("收到新信号:", signalData.signal, "价格:", signalData.price) // 执行交易 if (signalData.signal === "BUY") { var ticker = exchange.GetTicker(signalData.symbol) if (ticker) { exchange.Buy(ticker.Last, 0.01) Log("执行买入,价格:", ticker.Last) } } else if (signalData.signal === "SELL") { var ticker = exchange.GetTicker(signalData.symbol) if (ticker) { exchange.Sell(ticker.Last, 0.01) Log("执行卖出,价格:", ticker.Last) } } lastSignal = signalData.signal } LogStatus("从策略 - 跟随主策略\n" + "当前信号: " + signalData.signal + "\n" + "信号价格: $" + signalData.price.toFixed(2) + "\n" + "信号时间: " + _D(signalData.timestamp)) Sleep(5000) } }pythondef main(): masterRobotId = "632799" # 主策略的实盘ID lastSignal = None Log("从策略启动,订阅主策略:", masterRobotId) while True: # 获取主策略的信号 signalData = GetChannelData(masterRobotId) if signalData is None: LogStatus("等待主策略信号...") Sleep(5000) continue # 检查是否有新信号 if lastSignal != signalData["signal"]: Log("收到新信号:", signalData["signal"], "价格:", signalData["price"]) # 执行交易 if signalData["signal"] == "BUY": ticker = exchange.GetTicker(signalData["symbol"]) if ticker: exchange.Buy(ticker["Last"], 0.01) Log("执行买入,价格:", ticker["Last"]) elif signalData["signal"] == "SELL": ticker = exchange.GetTicker(signalData["symbol"]) if ticker: exchange.Sell(ticker["Last"], 0.01) Log("执行卖出,价格:", ticker["Last"]) lastSignal = signalData["signal"] LogStatus("从策略 - 跟随主策略\n" + "当前信号: {}\n".format(signalData["signal"]) + "信号价格: ${:.2f}\n".format(signalData["price"]) + "信号时间: {}".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多线程
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。详细信息请参阅语法手册。