外汇中高频 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 与布林带两类技术指标进行趋势与突破判断:

  1. RSI 因子:用于衡量价格上涨和下跌动能的相对强弱,判断市场是否处于强势上行或弱势下行状态。本案例中,当 RSI 大于 70 时,表示多头动能较强;当 RSI 小于 30 时,表示空头动能较强。

  2. 布林带因子:由中轨、上轨和下轨构成,用于刻画价格相对移动均线的波动区间。当价格突破上轨且 K 线收盘价高于开盘价时,说明价格可能向上突破;当价格跌破下轨且 K 线收盘价低于开盘价时,说明价格可能向下突破。

  3. 点差过滤与风控参数:策略在生成开仓信号前检查最优卖价与最优买价之间的点差,若点差超过阈值则不下单;下单时同时设置滑点、止盈价、止损价和委托有效期,避免在流动性不足或报价异常时执行交易。

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 所示:

表 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 回调。

表 2. 表 2-2 回测配置参数说明
参数名 示例值 说明
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 行情如何触发回调:

表 3. 表 2-3 callbackForSnapshot 参数说明
配置项 说明
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. 回测结果分析

下表汇总了本次回测的核心统计指标,便于统一查看样本区间、资金表现、交易成本、成交与持仓情况。

表 4. 表 3-1 回测核心指标统计
维度 实际指标 业务说明
回测时间区间 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 引擎在多频率行情聚合、响应式流批一体指标计算、事件驱动交易回调以及复杂算法订单撮合方面的完整能力,可为外汇及衍生品量化策略开发与高保真回测提供参考范式。