外汇中高频 CTA 与风控策略
外汇中高频 CTA(Commodity Trading Advisor)策略旨在通过系统化交易规则捕捉汇率趋势变化,并结合实时风险控制机制管理外汇远期交易中的止盈、止损与成交风险。本文基于 tick 级外汇远期行情数据,利用 DolphinDB Backtest 回测引擎实现一套融合小时级 K 线决策与 tick 级算法订单风控的外汇 CTA 策略,并介绍其策略逻辑、回测流程与回测结果分析。
1. 策略介绍
本策略面向外汇远期合约(Forward FX)交易场景。中国外汇市场以银行间外汇市场为核心,由中国外汇交易中心(CFETS)组织交易,主要交易品种包括即期外汇、远期外汇、外汇掉期和期权等;由于中国尚未开放外汇期货交易,外汇远期合约成为企业管理汇率风险的重要工具之一,也可用于利差套利、汇率择时和资金流动管理等策略化操作。本策略以相对强弱指数(Relative Strength Index,RSI)和布林带(Bollinger Bands)为核心技术指标,通过小时级 K 线判断趋势突破方向,并在订单执行阶段借助 DolphinDB 回测引擎内置的算法订单机制,根据 tick 行情实时触发止盈止损。策略目标是在汇率趋势形成时参与方向性交易,同时通过点差过滤、滑点设置、止盈止损和委托有效期控制降低异常行情与执行偏差带来的风险。
1.1 核心因子
策略使用 RSI 与布林带两类技术指标进行趋势与突破判断:
-
RSI 因子:用于衡量价格上涨和下跌动能的相对强弱,判断市场是否处于强势上行或弱势下行状态。本案例中,当 RSI 大于 70 时,表示多头动能较强;当 RSI 小于 30 时,表示空头动能较强。
-
布林带因子:由中轨、上轨和下轨构成,用于刻画价格相对移动均线的波动区间。当价格突破上轨且 K 线收盘价高于开盘价时,说明价格可能向上突破;当价格跌破下轨且 K 线收盘价低于开盘价时,说明价格可能向下突破。
-
点差过滤与风控参数:策略在生成开仓信号前检查最优卖价与最优买价之间的点差,若点差超过阈值则不下单;下单时同时设置滑点、止盈价、止损价和委托有效期,避免在流动性不足或报价异常时执行交易。
1.2 交易标的
本案例以外汇远期合约为交易标的,选取单只合约在 2018.12.31 至 2019.01.10 期间的 tick 级高频历史行情切片开展策略回测。
外汇远期合约是指买卖双方约定在未来某一特定日期,按照预先约定的汇率交割一定金额货币的金融衍生品,常用于锁定换汇成本和规避汇率波动风险。与场内标准化合约相比,外汇远期多属于场外交易(OTC),具有定制化特征,其价格走势主要受即期汇率、两币种利差、期限结构及市场预期等多重因素影响。
1.3 交易规则
回测以 tick 行情为输入实时合成 1 小时 K 线。策略在 K 线回调中生成交易信号,并通过算法订单根据 tick 行情判断是否触发止盈止损。
-
做多开仓条件:
-
最优卖价与最优买价之间的点差不超过最大点差阈值;
-
RSI 大于 70,表示多头动能较强;
-
当前最优卖价突破布林带上轨;
-
K 线收盘价高于开盘价,确认价格向上运行;
-
当前多头持仓小于 1,避免重复开仓。
-
-
做空开仓条件:
-
最优卖价与最优买价之间的点差不超过最大点差阈值;
-
RSI 小于 30,表示空头动能较强;
-
当前最优买价跌破布林带下轨;
-
K 线收盘价低于开盘价,确认价格向下运行;
-
当前空头持仓小于 1,避免重复开仓。
-
-
订单风控规则:
-
通过算法订单提交限价止盈止损委托;
-
做多时根据开仓价格设置止损价和止盈价;做空时反向设置止损价和止盈价;
-
结合滑点参数修正委托价格和风控触发价;
-
设置订单到期时间(本案例设置为 10 小时),若委托在有效期内未成交或不再满足风控条件,可由算法订单机制处理撤单、止盈与止损。
-
2. 策略回测
下文依次介绍数据准备、技术指标定义、策略逻辑编写、回测参数设置、回测引擎创建、回测执行和结果获取。本案例基于 DolphinDB Backtest 回测引擎实现,核心能力包括响应式指标订阅、tick 行情按指定频率合成 K 线、事件驱动策略回调、算法订单止盈止损以及回测结果查询。
本案例已在 DolphinDB Server 3.00.6.1、Backtest 3.00.6.6 与 MatchingEngineSimulator 3.00.6.6 环境下验证通过。运行脚本前,请确保服务器已安装版本匹配的插件,并按顺序先加载 MatchingEngineSimulator 再加载 Backtest。插件安装与配置详情请参见 Backtest。
2.1 准备数据
本示例使用外汇远期合约 tick 级历史行情数据作为回测输入。行情数据需包含回测引擎所需的基础字段(如合约代码、行情时间、最新价、买卖盘口数组等),且需与 Backtest 插件快照行情格式(dataType = 1)保持一致。
样例数据包含的具体字段及类型定义如表 2-1 所示:
| 字段名称 | DolphinDB 数据类型 | 字段说明 |
|---|---|---|
| symbol | SYMBOL | 合约代码(如 EURUSD6) |
| symbolSource | STRING | 行情源 / 数据源标识 |
| timestamp | TIMESTAMP | 行情时间戳(毫秒级精度) |
| tradingDay | DATE | 交易日期 |
| lastPrice | DOUBLE | 最新成交价 |
| upLimitPrice | DOUBLE | 涨停价 |
| downLimitPrice | DOUBLE | 跌停价 |
| totalBidQty | LONG | 委买总量 |
| totalOfferQty | LONG | 委卖总量 |
| bidPrice | DOUBLE[] | 买档价格数组(买一至买 N 档) |
| bidQty | LONG[] | 买档数量数组(买一至买 N 档) |
| offerPrice | DOUBLE[] | 卖档价格数组(卖一至卖 N 档) |
| offerQty | LONG[] | 卖档数量数组(卖一至卖 N 档) |
| highPrice | DOUBLE | 当日最高价 |
| lowPrice | DOUBLE | 当日最低价 |
| signal | DOUBLE[] | 辅助信号 / 自定义特征向量 |
| prevClosePrice | DOUBLE | 昨收盘价 |
| settlementPrice | DOUBLE | 结算价 |
| prevSettlementPrice | DOUBLE | 昨结算价 |
| open | DOUBLE | 当日开盘价 |
| close | DOUBLE | 最新价 / 当前周期收盘价 |
| low | DOUBLE | 当日最低价 |
| high | DOUBLE | 当日最高价 |
| volume | LONG | 当日累计成交量 |
下载并解压附录中的 fxCTAData.zip,获取回测所需的 CSV 文件。使用时根据上述 Schema 定义字段名与类型,通过 loadText 将 CSV 文件加载为内存表 fxQuoteData,供后续回测引擎读取:
colName = [
"symbol", "symbolSource", "timestamp", "tradingDay",
"lastPrice", "upLimitPrice", "downLimitPrice", "totalBidQty",
"totalOfferQty", "bidPrice", "bidQty", "offerPrice",
"offerQty", "highPrice", "lowPrice",
"signal", "prevClosePrice", "settlementPrice", "prevSettlementPrice",
"open", "close", "low", "high", "volume"
]
colType = [
"SYMBOL", "STRING", "TIMESTAMP", "DATE",
"DOUBLE", "DOUBLE", "DOUBLE", "LONG",
"LONG", "DOUBLE[]", "LONG[]", "DOUBLE[]",
"LONG[]", "DOUBLE", "DOUBLE",
"DOUBLE[]", "DOUBLE", "DOUBLE", "DOUBLE",
"DOUBLE", "DOUBLE", "DOUBLE", "DOUBLE", "LONG"
]
fxQuoteData = loadText(
filename="/path/to/fxCTAData.csv",
schema=table(colName as name, colType as type)
)
2.2 定义技术指标
本案例直接使用 DolphinDB ta 模块提供的 RSI 与布林带函数计算技术指标。ta 模块中的指标函数可直接作为响应式指标表达式传入 Backtest::subscribeIndicator,由回测引擎在合成 K 线时自动更新。
use ta
ta::rsi(lastPrice, 11)
ta::bBands(lastPrice, 20, 2, 2, 0)
RSI 指标:通过 ta::rsi(lastPrice, 11) 计算,用于衡量一段时间内价格上涨与下跌动能的相对强弱。当 RSI 高于 70 时,说明多头动能较强;当 RSI 低于 30 时,说明空头动能较强。
布林带指标:通过 ta::bBands(lastPrice, 20, 2, 2, 0) 计算。该函数返回上轨、中轨和下轨,本案例分别订阅为 bhigh、bmid 和 blow,用于判断价格是否突破常规波动区间。
2.3 编写策略逻辑
本策略主要由初始化函数和 K 线回调函数组成。初始化函数负责订阅指标,K 线回调函数负责读取最新 K 线与指标结果,并根据交易规则提交算法订单。
初始化函数:initialize(mutable context) 在回测引擎启动前执行一次。策略在该函数中为合成 K 线数据订阅 RSI 和布林带指标。回测引擎会将指标计算结果传入后续的 onBar 回调。
def initialize(mutable context){
print("initialize")
d=dict(STRING,ANY)
d["rsi"] = <ta::rsi(lastPrice, 11)>
d["bhigh"] = <ta::bBands(lastPrice, 20, 2, 2, 0)[0]>
d["bmid"] = <ta::bBands(lastPrice, 20, 2, 2, 0)[1]>
d["blow"] = <ta::bBands(lastPrice, 20, 2, 2, 0)[2]>
Backtest::subscribeIndicator(context["engine"], "snapshot_kline", d)
}
K 线回调函数:onBar(mutable context, msg, indicator) 在引擎按指定频率合成 K 线后触发。函数从行情中读取买一价、卖一价、开盘价、收盘价等信息,并结合 RSI、布林带和持仓状态判断是否提交开仓算法订单。
def onBar(mutable context, msg, indicator){
istock = msg.keys()[0]
msg_ = msg[istock]
askPrice0 = msg_.offerPrice[0]
bidPrice0 = msg_.bidPrice[0]
spread = askPrice0 - bidPrice0
indicator_ = indicator[istock]
blow = indicator_.blow
bhigh = indicator_.bhigh
if(spread > context["maxSpread"]){
return
}
rsi_ = indicator_.rsi
if(isNull(rsi_)){return}
close = msg_.lastPrice
bmid = indicator_.bmid
open = msg_.open
istock = msg_.symbol
position = Backtest::getPosition(context["engine"],istock)
longpos = position.longPosition
shortpos = position.shortPosition
if(rsi_ > 70. and askPrice0 > bhigh and close > open and longpos < 1){
Backtest::submitOrder(context["engine"], (istock,msg_.symbolSource,
context["barTime"],5, round(askPrice0,5),
askPrice0 -context["sl"] + context['Slippage'], askPrice0 + context["tp"] + context['Slippage'],
2, 1,context['Slippage'], 0, context["barTime"]+36000000),"openBuy", 5)
return
}
if(rsi_ < 30. and bidPrice0 < blow and close < open and shortpos < 1){
Backtest::submitOrder(context["engine"], (istock,msg_.symbolSource,
context["barTime"],5, round(bidPrice0,5),
bidPrice0 + context["sl"] - context['Slippage'],
bidPrice0 - context["tp"] - context['Slippage'], 2, 2, context['Slippage'], 0,
context["barTime"]+36000000),"openSell", 5)
return
}
}
这里使用的 Backtest::submitOrder 是回测引擎提供的下单接口。配置项 enableAlgoOrder=true 时,可开启算法订单。算法订单支持限价止盈订单、市价止盈订单、限价止损订单、市价止损订单、限价止盈止损订单和市价止盈止损订单等类型。本案例使用 orderType=5 提交限价止盈止损订单。
其他生命周期回调函数:除 initialize 和 onBar 外,Backtest 引擎还支持盘前、订单、成交、盘后和结束等生命周期回调。本案例的交易逻辑集中在小时级 K 线回调中,其他回调主要用于保持回调字典完整,并为后续扩展预留入口。
def beforeTrading(mutable context){
context["opens"] = false
}
def onSnapshot(mutable context, msg){
// 本案例设置 callbackForSnapshot = 2,不直接触发 onSnapshot
}
def onOrder(mutable context, msg){
// 可在此处理订单状态回报,本案例无需额外逻辑
}
def onTrade(mutable context, msg){
// 可在此处理成交回报,本案例无需额外逻辑
}
def afterTrading(mutable context){
// 可在此处理盘后统计或状态重置,本案例无需额外逻辑
}
def finalize(mutable context){
// 可在此处理回测结束后的清理工作,本案例无需额外逻辑
}
在创建回测引擎时,这些函数会与 initialize、onBar 一起写入 callbacks 字典。虽然本案例未在订单与成交回调中增加额外处理,但补充空实现可以保证代码按章节顺序执行时变量定义完整。
2.4 设置回测参数
在创建回测引擎前,需要配置回测基础信息字典 userConfig 和策略上下文字典 Context。本案例通过 callbackForSnapshot 和 frequency 将 tick 行情按 1 小时频率合成 K 线,并触发 onBar 回调。
| 参数名 | 示例值 | 说明 |
|---|---|---|
| startDate | 2018.12.31 | 回测起始日期,对应样例数据实际起始交易日。 |
| endDate | 2019.01.10 | 回测结束日期,对应样例数据实际结束交易日。 |
| strategyGroup | "universal" | 策略分组,本案例使用通用类型。 |
| frequency | 3600000 | K 线合成频率,单位为毫秒;3600000 表示 1 小时。 |
| cash | 10000000 | 初始资金。 |
| dataType | 1 | 行情数据类型配置。 |
| depth | 1 | 委托深度配置,1 表示使用盘口买一/卖一档深度。 |
| enableAlgoOrder | true | 是否开启算法订单止盈止损功能。 |
| latency | 1 | 订单延时,用于模拟实盘报单延迟。 |
| callbackForSnapshot | 2 | tick 行情按指定 frequency 频率合成 K 线,并触发 onBar 回调。 |
| matchingMode | 3 | 撮合模式配置。 |
startDate = 2018.12.31
endDate = 2019.01.10
userConfig = dict(STRING,ANY)
userConfig["startDate"] = startDate
userConfig["endDate"] = endDate
userConfig["strategyGroup"] = "universal"
userConfig["frequency"] = 3600000
userConfig["cash"] = 10000000
userConfig["dataType"] = 1
userConfig["depth"] = 1
userConfig["enableAlgoOrder"] = true
userConfig["latency"] = 1
userConfig["callbackForSnapshot"] = 2
userConfig["matchingMode"] = 3
Context=dict(STRING,ANY)
Context["tp"] = 0.01
Context["sl"] = 0.001
userConfig["context"] = Context
symbol = ["EURUSD6"]
basicInfo = table(symbol as symbol, take(100.,size(symbol)) as multiplier, take(0.2,size(symbol)) as marginRatio,
take(0.01,size(symbol)) as tradeUnit, take(0.02,size(symbol)) as priceUnit,
take(0.03,size(symbol)) as priceTick, take(1.5,size(symbol)) as commission, take(1,size(symbol)) as deliveryCommissionMode)
callbackForSnapshot 与 frequency 的组合决定 tick 行情如何触发回调:
| 配置项 | 说明 |
|---|---|
| callbackForSnapshot = 0 | tick 行情触发 onSnapshot 行情回调。 |
| callbackForSnapshot = 1,frequency > 0 | tick 行情触发 onSnapshot 回调,同时按指定 frequency 合成 K 线并触发 onBar 回调。 |
| callbackForSnapshot = 2,frequency > 0 | tick 行情仅按指定 frequency 合成 K 线并触发 onBar 回调。 |
2.5 创建回测引擎
设置引擎名称、回测配置、回调函数和合约基本信息表后,可通过 Backtest::createBacktester 创建回测引擎。其中,接口 createBacktester 的第四个参数表示是否开启 JIT 优化。默认值为 false,表示不开启,若需要开启 JIT 优化,只需要设置此参数为 true 即可。
callbacks = dict(STRING,ANY)
callbacks["initialize"] = initialize
callbacks["beforeTrading"] = beforeTrading
callbacks["onBar"] = onBar
callbacks["onOrder"] = onOrder
callbacks["onTrade"] = onTrade
callbacks["afterTrading"] = afterTrading
callbacks["finalize"] = finalize
strategyName = "fxCtaStrategy"
try{Backtest::dropBacktestEngine(strategyName)}catch(ex){print ex}
engine = Backtest::createBacktester(strategyName, userConfig, callbacks, false, basicInfo)
2.6 执行回测
创建引擎后,调用 Backtest::appendQuotationMsg 将外汇行情数据写入回测引擎。引擎会按照行情时间顺序推进回测时钟,按配置合成 K 线、计算订阅指标、触发 onBar 回调,并在订单提交后由算法订单机制根据 tick 行情执行止盈止损逻辑。行情数据推送完成后,调用 Backtest::appendEndMarker(engine) 向引擎发送结束标记,通知回测引擎完成后续结算与收尾处理。
Backtest::appendQuotationMsg(engine, fxQuoteData)
Backtest::appendEndMarker(engine)
2.7 获取回测结果
回测完成后,可通过 Backtest 插件接口获取每日持仓、每日权益、收益概览、成交明细、未完成订单和策略上下文等结果,用于分析策略收益、风险暴露、成交质量与风控执行情况。
// 获取每日持仓
DailyPosition = Backtest::getDailyPosition(long(engine))
// 获取每日组合权益
DailyTotalPortfolios = Backtest::getDailyTotalPortfolios(long(engine))
// 获取策略上下文
contextTable = Backtest::getContextDict(long(engine))
// 获取成交明细,仅保留已提交或已成交订单
TradeDetails = select * from Backtest::getTradeDetails(long(engine)) where orderStatus in [0,1]
在实际分析中,可结合交易明细和每日权益曲线进一步评估策略的胜率、盈亏比、回撤、持仓周期、止盈止损触发频率以及不同市场阶段下的表现稳定性。
3. 回测结果分析
下表汇总了本次回测的核心统计指标,便于统一查看样本区间、资金表现、交易成本、成交与持仓情况。
| 维度 | 实际指标 | 业务说明 |
|---|---|---|
| 回测时间区间 | 2018.12.31 00:00:22 ~ 2019.01.10 23:59:59 | 约 11 个自然日(跨年行情片段) |
| 输入行情行数 | 1,346,100 条 | tick 级高频外汇远期行情 |
| 期初 / 期末权益 | 10,000,000 / 9,999,963.988 | 初始资金 1,000 万元 |
| 累计盈亏 / 收益率 | -36.012 元 / -0.00036% | 单位净值最低 0.999996,最高 1.000000 |
| 累计手续费 | 36.00 元 | 交易成本几乎完全解释了期末小幅亏损 |
| 总成交记录数 | 12 笔 | 4 笔 openBuy、2 笔 openSell、6 笔算法订单平仓记录 |
| 最大持仓 | 多头 2 手,空头 2 手 | 严格符合 qty = 2 与 longpos < 1 的仓位限制 |
| 开仓日 / 有持仓的交易日数 | 4天 / 7 天 | 交易频率较低,风险暴露周期较短 |
样本内共生成 12 条成交记录,包括 4 笔 openBuy、2 笔 openSell 和 6 笔算法订单平仓记录。最大多头和空头持仓均为 2 手。在 4 个交易日内触发了 6 次开仓,持仓周期共覆盖 7 个交易日。这表明,在多重指标共振与点差过滤约束下,策略的交易频率较低。
收益与损益归因方面,期末权益为 9,999,963.988,累计亏损与手续费均约 36 元,说明在 11 天短样本内,交易损益基本被固定手续费和滑点抵消。
本次回测仅覆盖约 11 个自然日,样本较少,结果主要用于验证策略逻辑与算法订单的执行流程是否顺畅,不能直接代表策略的长期盈利能力。实际投研时,可使用更长周期、覆盖更多合约的数据进行评估。
4. 总结
本文介绍了一套融合小时级 K 线决策与 tick 级算法订单风控的外汇中高频 CTA 策略实现方案。策略利用小时级 K 线上的 RSI 与布林带突破识别趋势方向,以减少日内微观噪声对信号的干扰;同时借助 DolphinDB Backtest 内置算法订单,在 tick 级行情中监控止盈止损与订单有效期,更贴近实盘成交与风控触发过程。
该案例展示了 DolphinDB Backtest 引擎在多频率行情聚合、响应式流批一体指标计算、事件驱动交易回调以及复杂算法订单撮合方面的完整能力,可为外汇及衍生品量化策略开发与高保真回测提供参考范式。
