期货分钟频 CTA 策略

Commodity Trading Advisor (CTA)策略在现代金融市场中扮演着重要角色,通过技术分析和趋势跟踪捕捉市场动向,实现风险对冲和利润最大化;在中高频交易中,CTA 策略对交易效率、盈利能力的助益尤为明显。本案例以国内商品期货为交易对象,使用 1 分钟频行情数据,把平均真实范围(Average True Range,ATR)与 相对强弱指数(Relative Strength Index,RSI)两个技术指标组合起来,在分钟级别上捕捉价格的超买超卖与波动放大,并用回落止损及时离场。

在投入实盘交易之前,利用历史数据对量化中高频策略进行测试和评估,是确保交易策略有效性和可行性的重要步骤。在本文中,我们将通过一个期货分钟频率 CTA 策略的回测案例,完整演示如何基于 DolphinDB 回测引擎完成数据准备、策略编写、参数配置、引擎创建、回测执行与结果展示。

1. 策略介绍

CTA 投资策略主要通过技术分析和系统化交易方法,在期货和期权市场上进行交易,可分为主观 CTA 和量化 CTA 两大主流策略。主观 CTA 策略由管理人结合基本面分析,对市场走势作出主观判断并据此交易,以获取收益;量化 CTA 策略则通过建立量化模型,根据模型产生的买卖信号进行投资决策。量化 CTA 策略依赖技术分析工具和指标,如移动平均线(MA)、相对强弱指数(RSI)和平均真实范围(ATR)等,来识别市场趋势和交易机会。同时,CTA 策略高度重视风险管理,通常设定止损和止盈点,以保护投资组合免受重大损失。期货资产买卖的双向交易和 T+0 交易制度优势,使得量化 CTA 策略能够快速适应市场环境,更直观地捕捉市场变化带来的机遇,因此当期货市场出现剧烈上涨或下跌波动时,量化 CTA 策略往往能够有效地从中获利。

量化 CTA 策略可分为趋势跟踪、趋势反转和套利对冲三类:

  • 趋势跟踪:主要通过追随已形成的价格趋势获利,在上涨趋势时持有多仓,下跌时持有空仓,当趋势结束时平仓。

  • 趋势反转:通过捕捉价格拐点,在价格回归过程中获利。当信号表明价格反转向上时持有多仓,表明价格反转向下时持有空仓;价格回归时平仓。

  • 套利对冲:对相关品种或合约进行交易。可以做多低估值品种,做空高估值品种;也可以从价格回归中获利,包括期现套利、跨期套利、跨品种套利、跨市场套利等。

本案例实现的是其中的趋势反转策略。策略用 ATR 衡量市场波动幅度,用 RSI 衡量市场的超买或超卖状态,只有两者同时满足条件时才开仓,在建仓后则用回撤止损控制单笔风险。

1.1 核心因子

本案例的核心因子为 ATR 与 RSI 两个经典技术指标,二者共同决定开仓时机:

  • ATR(平均真实范围): 计算 1 分钟内标的的最高价、最低价与前收盘价的绝对差值(TR 指标),即 TR = max(high − low, |high − prevClose|, |low − prevClose|);对 TR 取 14 期指数移动平均(EMA)得到 ATR,再对 ATR 取 10 期简单移动平均,得到 ATR 的 10 分钟均值(记为 mATR)。

  • RSI(相对强弱指数): 用收盘价的逐期变化计算 14 期 RSI:把每期收盘价的变化拆分为上涨部分与下跌部分并分别做指数移动平均(Exponential Moving Average,EMA),RSI = 100 × upAvg ÷ (upAvg + downAvg),取值越大表示近期上涨动能越强。

ATR 与 RSI 都是经典技术指标,本案例采用了它们的基本思想,并以 EMA 对真实波幅和涨跌幅度做平滑。两个因子的定义与实现见 2.2 小节。

1.2 交易标的

本案例适用于国内商品期货主力合约的分钟频行情,交易标的为大商所(XDCE)黄大豆一号主力合约 A2305,数据频率为 1 分钟,回测区间为 2023.02.06 至 2023.02.08,共 3 个交易日、1044 条 K 线。附件 futureTestData.csv 提供了该合约的分钟频行情,覆盖 2023.02.02 至 2023.02.24,加载方式见 2.1 小节。

1.3 交易规则

策略成立的前提是期货资产支持双向交易和 T+0,因此同一标的在任一时刻只持有一个方向的 2 手仓位,并且可以当日开仓、当日平仓。交易规则分为开仓与止损两部分:

  • 开仓:

    • 当 RSI 值大于 70 且 ATR 大于其 10 分钟均值时,买入开仓。

    • 当 RSI 值小于 30 且 ATR 大于其 10 分钟均值时,卖出开仓。

  • 止损:

    • 持有多仓时,若价格自持仓期间的最高收盘价回落 0.004(即 0.4%),卖平。

    • 持有空仓时,若价格自持仓期间的最低收盘价反弹 0.004(即 0.4%),买平。

价格判断与止损基准均取自分钟 K 线的收盘价。需要说明的是,本案例为便于读者上手,简化实现:

  • 委托数量固定为 2 手

  • 委托价格取最新收盘价加减 0.02

  • 所有合约统一使用同一套演示参数

均未按实盘的资金规模、盘口深度与逐合约参数做精细化处理。

2. 策略回测

本案例整体流程为:

  1. 加载本案例提供的分钟频行情表;

  2. 定义 ATR、RSI 两个状态指标,在策略回调函数中编写开仓与止损逻辑;

  3. 配置回测参数与期货合约基本信息表;

  4. 创建回测引擎并把行情按时间顺序回放到引擎中执行;

  5. 读取并展示回测结果。

运行前需要加载 Backtest 插件和 MatchingEngineSimulator 插件;DolphinDB 的安装部署、插件安装等通用环境准备请参考 DolphinDB 官方文档与回测插件接口说明文档。

2.1 准备数据

本案例使用的数据来自案例附件 futureTestData.csv。表中数据为大商所(XDCE)黄大豆一号主力合约 A2305 的行情,共 5916 条记录,覆盖交易日 2023.02.02 至 2023.02.24。回测区间为 2023.02.06 至 2023.02.08,共 3 个交易日、1044 条 1 分钟 K 线。

直接加载 futureTestData.csv 即可得到行情表 tb:

tb = loadText("./futureTestData.csv")
replaceColumn!(tb, `volume, long(tb.volume))
tb[`signal] = fixedLengthArrayVector(tb.backwardFactorHighPrice, tb.backwardFactorLowPrice, tb.backwardFactorClosePrice)

行情表 tb 的字段如下:

表 1. 表 2-1 行情表字段
字段 类型 含义
symbol SYMBOL 合约代码,本案例为 A2305
symbolSource SYMBOL 交易所代码,本案例为 XDCE(大商所)
tradeTime TIMESTAMP 行情时间戳
tradingDay DATE 交易日
open DOUBLE 开盘价
low DOUBLE 最低价
high DOUBLE 最高价
close DOUBLE 收盘价
volume LONG 成交量,单位为手
amount DOUBLE 成交额,单位为元
upLimitPrice DOUBLE 涨停价
downLimitPrice DOUBLE 跌停价
prevClosePrice DOUBLE 前收盘价
settlementPrice DOUBLE 结算价
prevSettlementPrice DOUBLE 昨结算价
backwardFactorHighPrice DOUBLE 主连复权后的最高价
backwardFactorLowPrice DOUBLE 主连复权后的最低价
backwardFactorClosePrice DOUBLE 主连复权后的收盘价
signal(脚本中构造) DOUBLE[] 数组向量,依次存放主连复权后的最高价、最低价与收盘价,供计算 ATR、RSI 使用

前 15 个字段是回测引擎回放分钟频行情所必需的字段,后 3 个复权价列与服务端构造出的 signal 列供计算指标使用。加载之后只需以下两处微调,其余字段名与数据类型都已与引擎要求一致:

  • volume 类型修正:文本文件中的整数列会被 loadText 识别为 INT,而回测引擎要求 volume 为 LONG,因此用 replaceColumn! 把该列修改为 LONG 类型。

  • signal 构造:用于计算指标的信号价格取主连复权(平滑)之后的最高价、最低价与收盘价,而数组向量(DOUBLE[])无法用文本文件表达,因此把三列复权价保留在附件中,由脚本用 fixedLengthArrayVector 合成 signal 列;撮合使用的开高低收价格、涨跌停价则取自未复权的原始字段。

这样得到的 tb 已经是回测引擎可以直接回放的分钟频行情表,将在 2.5 小节中通过 appendQuotationMsg 回放到引擎。需要注意,脚本使用相对路径读取数据文件,运行前请把 futureTestData.csv 放在脚本的同一目录下。

2.2 编写策略逻辑

中高频回测通常采用事件驱动方式,策略需要响应行情更新、订单成交等事件。DolphinDB 回测引擎采用事件驱动机制,提供了策略初始化函数、盘前回调函数、行情回调函数、每日盘后回调函数等一系列事件函数,用户可以在相应的回调函数中编写策略逻辑。

(1)定义指标

为使用中高频行情数据计算量价等因子,DolphinDB 回测引擎采用状态响应式引擎,实现流批统一的指标计算,并处理带状态的高频因子。具体可参考状态响应式引擎用户手册。指标函数以 @state 修饰,表示函数内部会保留跨批次的中间状态。本案例基于分钟频行情数据定义 ATR 与 RSI 两个技术分析指标:

@state
def atr(high, low, close, m=14, n=10) {
    prevClosePrice = prev(close)
    tr = rowMax([high-low, abs(high-prevClosePrice), abs(low-prevClosePrice)])
    atr = ema(tr, m)
    mAtr = mavg(atr, n)
    return atr, mAtr
}

@state
def rsi(close, timePeriod=14) {
    deltaClose = deltas(close)
    up = iif(nullCompare( > , deltaClose, 0), deltaClose, 0)
    down = iif(nullCompare( > , deltaClose, 0), 0, - deltaClose)
    upAvg = ema(up, timePeriod)
    downAvg = ema(down, timePeriod)
    return 100.0 * upAvg / (upAvg + downAvg)
}

atr 函数一次返回两个值——ATR 与其 10 分钟均值 mATR,因此后续用下标 [0]、[1] 分别取值。rsi 函数先用 deltas 求收盘价的逐期变化,再用 iif 把它拆分为上涨部分 up 与下跌部分 down,分别做 EMA 平滑后按 100 × upAvg ÷ (upAvg + downAvg) 计算 RSI。

(2)订阅指标

在策略初始化函数 initialize 中,订阅基于主连复权(平滑)处理之后的最高价、最低价和收盘价行情数据计算 ATR、mATR 和 RSI 指标。这里的 signal 字段为数组向量,存储的数据分别为主连行情复权之后的最高价、最低价和最新收盘价。调用 subscribeIndicator 时,需传入回测引擎名、需要计算的数据类型和指标字典。字典的 key 为指标名,用于后续访问;value 为指标计算的元代码。指标计算结果将随行情一起传入 onBar 等策略回调函数:

def initialize(mutable contextDict){
    d=dict(STRING,ANY)
    d["ATR"]=<atr(signal[0], signal[1], signal[2], 14,10)[0]>
    d["mATR"]=<atr(signal[0], signal[1], signal[2], 14,10)[1]>
    d["RSI"]=<rsi(signal[2], 14)>
    Backtest::subscribeIndicator(contextDict["engine"], "kline", d)
}

(3)生成信号并下单

行情回调函数 onBar 在每根分钟 K 线到来时触发。onBar 的 msg 参数为回测引擎传来的最新分钟频行情,indicator 为 initialize 中定义的指标计算结果;二者都是字典,key 为期货合约名,value 为该合约对应的行情信息与指标计算结果。函数按合约逐个判断,先读取持仓、价格与三个指标,再依次处理多头开仓、空头开仓、多仓止损、空仓止损四种情形:

def onBar(mutable contextDict, msg, indicator){
    lowDict = contextDict["lowPrice"]
    highDict = contextDict["highPrice"]
    symbols = msg.keys()
    for (i in symbols){
        istock = msg[i].symbol
        symbolSource = msg[i].symbolSource
        pos = Backtest::getPosition(contextDict["engine"], istock)
        longPos = pos.longPosition
        shortPos = pos.shortPosition
        price = msg[i].close
        atr = indicator[i].ATR
        matr = indicator[i].mATR
        rsi = indicator[i].RSI

        // 没有多头持仓,并且多头趋势时,买入
        if (longPos < 1 and shortPos < 1 and atr > matr and rsi > contextDict["buySignalRSI"] and matr > 0){
            Backtest::submitOrder(contextDict["engine"],
                (istock, symbolSource, contextDict["tradeTime"], 5, price+0.02, 0., 2, 1, 0), "buyOpen")
            highDict[istock] = price
            continue
        }

        // 没有空头持仓,并且空头趋势时,卖出
        if (longPos < 1 and shortPos < 1 and atr > matr and rsi < contextDict["sellSignalRSI"] and matr > 0){
            Backtest::submitOrder(contextDict["engine"],
                (istock, symbolSource, contextDict["tradeTime"], 5, price-0.02, 0., 2, 2, 0), "sellOpen")
            lowDict[istock] = price
            continue
        }

        if (longPos > 0 and price > highDict[istock]){
            // 更新最高价
            highDict[istock] = max(price, highDict[istock])
        } else if (longPos > 0 and price <= highDict[istock]*(1-contextDict["closeLine"])){
            // 平仓
            Backtest::submitOrder(contextDict["engine"],
                (istock, symbolSource, contextDict["tradeTime"], 5, price-0.02, 0., 2, 3, 0), "sellClose")
        }

        if (shortPos > 0 and price < lowDict[istock]){
            // 更新最低价
            lowDict[istock] = min(price, lowDict[istock])
        } else if (shortPos > 0 and price >= lowDict[istock]*(1+contextDict["closeLine"])){
            // 平仓
            Backtest::submitOrder(contextDict["engine"],
                (istock, symbolSource, contextDict["tradeTime"], 5, price+0.02, 0., 2, 4, 0), "buyClose")
        }
    }
    contextDict["lowPrice"] = lowDict
    contextDict["highPrice"] = highDict
}

代码中的关键判断与状态更新如下:

  • 开仓条件:longPos < 1 and shortPos < 1 保证同一标的在任一时刻只持有一个方向的仓位;atr > matr 要求当前波动幅度高于其 10 分钟均值;rsi > contextDict["buySignalRSI"](默认 70)为买入开仓条件,rsi < contextDict["sellSignalRSI"](默认 30)为卖出开仓条件;matr > 0 用于过滤指标预热阶段(ATR 均值尚未有效计算的 K 线)。

  • 信号时点与执行时点:信号在每分钟 K 线收盘时产生,取该分钟的最新增权收盘价判断条件,并以同一分钟的时间戳 contextDict["tradeTime"] 提交订单;由于本案例的撮合模式为“以委托价格成交”,委托会立即按委托价格成交。

  • 状态更新:开仓时把 highDict(多仓)或 lowDict(空仓)置为当前价格,作为回撤基准的起点;持仓过程中,多仓持续记录收盘价的最高值,空仓持续记录收盘价的最低值。当收盘价自最高值回落 0.4%(price <= highDict[istock] * (1 - closeLine))时卖平,当收盘价自最低值反弹 0.4%(price >= lowDict[istock] * (1 + closeLine))时买平。两个字典通过 contextDict 在多次回调之间持久化,并在 onBar 结束时写回。

其中 Backtest::submitOrder 是回测引擎提供的下单接口。与股票不同,期货的订单信息元组包含 9 个元素:本案例的下单数量固定为 2 手,未根据可用资金或持仓规模动态计算

Backtest::submitOrder(engine, msg, label="")
//engine 引擎实例
//msg: 订单信息元组,(标的代码, 交易所代码, 时间, 订单类型, 委托订单价格, 止损价/止盈价, 委托订单数量, 买卖方向, 委托订单有效性)
//label: 可选参数,方便用于对订单进行分类

以本案例的买入开仓为例:订单类型 5 表示限价单;委托价格取最新收盘价加 0.02(卖出方向为减 0.02),相当于在盘口方向上让出 0.02 个价格单位;止损价/止盈价暂不支持,填写 0;委托数量固定为 2 手;买卖方向 1/2/3/4 分别表示买开、卖开、卖平、买平;委托订单有效性 0 表示当日有效。下单时传入的 label 分别为 buyOpen、sellOpen、sellClose、buyClose,便于在成交明细中按开平类型筛选统计。

(4)约束说明

  • 本案例的下单数量固定为 2 手,未根据可用资金或持仓规模动态计算

  • 期货合约的合约乘数、保证金率、合约单位、报价单位、最小变动单位与费率在合约基本信息表中配置(见 2.3 小节),引擎据此计算保证金占用与可用资金

  • 行情表按引擎要求提供了涨停价与跌停价字段,用于撮合与风控。

  • 由于委托为限价单且撮合模式设置为“以委托价格成交”,本案例的委托始终按委托价格成交,脚本中因此没有针对未成交订单的撤单或重试逻辑。

2.3 设置回测参数

回测的开始与结束日期、初始资金、手续费、行情类型、订单延时、动态分红除权等均可以通过参数进行配置。通过调整这些参数,可以模拟不同的市场环境,并评估交易策略的表现。本案例为期货策略的回测,因此策略类型参数应设置为 "futures",以确保回测引擎能够正确识别和处理期货交易的特定规则和逻辑;此外,策略逻辑上下文 context 也可以通过参数设置策略中的全局变量。具体的初始参数配置代码示例如下:

userConfig = dict(STRING, ANY)
userConfig["startDate"] = 2023.02.06
userConfig["endDate"] = 2023.02.08
userConfig["strategyGroup"] = "futures"
userConfig["cash"] = 10000000
userConfig["commission"] = 1.5
userConfig["tax"] = 0.0 // useless in future goods backtest engine
userConfig["dataType"] = 3
userConfig["msgAsTable"] = false
userConfig["frequency"] = 0
userConfig["outputOrderInfo"] = true
userConfig["depth"] = 5
userConfig["matchingMode"] = 3
Context = dict(STRING, ANY)
Context["buySignalRSI"] = 70.
Context["sellSignalRSI"] = 30.
Context["closeLine"] = 0.004
Context["lowPrice"] = dict(STRING, ANY)
Context["highPrice"] = dict(STRING, ANY)
userConfig["context"] = Context

userConfig 中与本案例直接相关的配置项及取值含义如下:

表 2. 表 2-2 userConfig 相关配置项
配置项 取值 说明
startDate / endDate 2023.02.06 / 2023.02.08 回测区间,DATE 类型。本案例覆盖 3 个交易日
strategyGroup "futures" 策略类型为期货,引擎按期货的交易规则处理订单
cash 10,000,000 初始资金,单位为元
commission 1.5 手续费。期货回测的手续费也可在合约基本信息表的 commission 字段中配置
tax 0.0 印花税,仅对股票、融资融券生效,期货回测中该项无效
dataType 3 行情类型为分钟频率
msgAsTable false 行情与指标以字典形式传入回调函数
frequency 0 本案例已使用分钟频行情,无需再合成
outputOrderInfo true 在订单交易明细中输出风控日志
depth 5 订单簿深度
matchingMode 3 订单撮合模式为以委托价格成交

策略逻辑中的全局变量通过 context 传入,本案例的取值如下:

表 3. 表 2-3 context 全局变量
变量 取值 说明
buySignalRSI 70 买入开仓的 RSI 阈值
sellSignalRSI 30 卖出开仓的 RSI 阈值
closeLine 0.004 止损的回落/反弹比例,即 0.4%
lowPrice 空字典 记录空仓期间收盘价的最低值,供止损判断使用
highPrice 空字典 记录多仓期间收盘价的最高值,供止损判断使用

创建期货回测时,还需要用户配置期货合约的基本信息表,包括期货合约的合约乘数、保证金率、交易单位、报价单位、最小变动价位、手续费和手续费的计费方式(按手数计费或者按成交金额计费)等:

symbol = distinct(tb.symbol)
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)
表 4. 表 2-4 basicInfo 基本信息表字段
字段 取值 说明
symbol A2305 期货合约代码
multiplier 100 合约乘数
marginRatio 0.2 保证金率
tradeUnit 0.01 合约单位
priceUnit 0.02 报价单位
priceTick 0.03 价格最小变动单位
commission 1.5 费用
deliveryCommissionMode 1 计费方式,1 表示 费用 × 手数,2 表示 费用 × 金额

本案例为演示脚本,上述合约参数按固定值统一填写;若用于实际回测,请按交易所公布的合约参数逐合约填写,基本信息表的完整字段说明可见期货回测接口文档。

  • 策略阈值(buySignalRSI、sellSignalRSI、closeLine)在 Context 中修改;

  • 回测区间、初始资金、手续费与撮合方式等在 userConfig 中修改;

  • 合约参数在 basicInfo 中修改。

其中 closeLine 与 Context 中的 lowPrice、highPrice 联动:closeLine 即止损比例,修改后触发止损的价格随之变化。调整 startDate、endDate 时,需要保证所选日期落在行情数据的覆盖范围内(附件中 A2305 的行情覆盖 2023.02.02 至 2023.02.24)。

2.4 创建回测引擎

设置引擎名称、引擎配置项、策略回调函数字典以及期货合约基本信息表之后,通过接口 createBacktester 创建回测引擎实例。本案例创建一个回测引擎 engine,用于回放 2.1 小节中准备好的分钟频行情并演示策略的完整执行过程。

strategyName = "Backtest_test1"
callbacks = dict(STRING, ANY)
callbacks["initialize"] = initialize
callbacks["beforeTrading"] = beforeTrading
callbacks["onBar"] = onBar
callbacks["onOrder"] = onOrder
callbacks["onTrade"] = onTrade
callbacks["afterTrading"] = afterTrading
callbacks["finalize"] = finalized
try{Backtest::dropBacktestEngine(strategyName)}catch(ex){print ex}
engine = Backtest::createBacktester(strategyName, userConfig, callbacks, , basicInfo)

其中 callbacks 为回调函数字典,key 为事件名,value 为用户自定义的策略回调函数;本案例使用分钟频行情,因此行情回调注册到 onBar。各回调函数与引擎的连接方式为:initialize 中订阅指标并可通过 contextDict["engine"] 访问引擎实例,onBar 中读取持仓与指标并下单,afterTrading、finalize 等可在每日盘后与策略结束前处理业务逻辑。

2.5 执行回测

创建回测引擎后,通过 appendQuotationMsg 将 2.1 小节准备好的分钟频行情表 tb 传入引擎,执行回测。tb 已按 tradeTime 升序排列(附件本身即按该顺序排列),引擎内部会把行情按时间先后分发给模拟撮合引擎和相应的行情回调函数,每根 K 线触发一次 onBar。运行完整脚本即可依次完成数据准备和回测执行,关键代码如下:

Backtest::appendQuotationMsg(engine, tb)

运行前需要确认两点:

  • 行情数据文件 futureTestData.csv 与脚本位于同一目录(脚本使用相对路径 ./futureTestData.csv 读取);

  • 脚本开头的 loadPlugin("MatchingEngineSimulator") 与 loadPlugin("Backtest") 已成功加载所需插件。

2.6 获取回测结果

回测运行结束之后,可以通过相应的接口获取每日持仓、每日权益、收益概述、成交明细和策略中用户自定义的逻辑上下文。本案例读取成交明细、可用资金、每日持仓与每日权益:

resnew = select * from Backtest::getTradeDetails(engine) where orderStatus in [0, 1]

cash = Backtest::getAvailableCash(engine)

pos = Backtest::getDailyPosition(engine)

dailyTotalPortfolios = Backtest::getDailyTotalPortfolios(engine)
  • getTradeDetails 返回订单交易明细,其中 orderStatus 用于表示订单状态,脚本筛选出 orderStatus 为 0 或 1 的订单记录;明细中的 label 字段即下单时传入的分类标签(buyOpen、sellOpen、sellClose、buyClose),可用于按开平类型分别统计。

  • getAvailableCash 返回账户可用资金。

  • getDailyPosition 返回每日持仓数据,包括每个交易日盘后的持仓数量和持仓均价,以及当日买卖数量和金额。第 3 章展示了该接口的返回结果。

  • getDailyTotalPortfolios 返回每日权益,即每个交易日盘后的账户资产情况。

回测插件提供的完整回测结果接口可以参阅回测插件的接口说明文档。

3. 回测结果展示

本案例的回测区间为 2023.02.06 至 2023.02.08,共 3 个交易日。下表为通过 Backtest::getDailyPosition 获取的每日持仓数据:

表 5. 表 3-1 每日持仓数据结果表
symbol tradeDate lastDayLongPosition lastDayShortPosition longPosition longPositionAvgPrice shortPosition shortPositionAvgPrice todayBuyVolume todayBuyValue todaySellVolume todaySellValue closePrice
A2305 2023.02.06 0 0 2 5552.02 0 5561.98 2 11104.04 2 11123.96 5,573
A2305 2023.02.07 2 0 0 5563.02 2 5556.65 2 11148.04 4 22215.92 5,562
A2305 2023.02.08 0 2 2 5552.52 0 5549.23 4 22168.08 2 11053.96 5,548

从表中可以看到,回测期内每个交易日都发生了开仓与平仓:日终持仓依次为多 2 手、空 2 手、多 2 手,说明多空两个方向的信号都被触发;由于期货支持 T+0,当日建立的仓位可在当日平仓,lastDayLongPosition 与 longPosition 的变化也印证了这一点。3 个交易日的买入与卖出成交量均为 8 手,每笔委托固定 2 手;买成交均价在 5,549 至 5,563 之间,与同期行情价格区间一致,说明下单价格与撮合结果符合预期。

需要说明的是,上述结果主要用于验证脚本流程,不能直接用于评价策略的收益能力。如需评估策略的真实表现,请使用真实数据,按实盘情况配置合约参数与撮合参数,并同时输出收益概述、每日权益等指标做综合分析。

4. 总结

本文以一个期货分钟频 CTA 趋势反转策略为例,完整演示了如何通过 DolphinDB 回测引擎完成策略回测。策略的核心思想是用 ATR 与 RSI 两个技术指标共同判断入场时机:当 RSI 突破 70(或跌破 30)且 ATR 高于其 10 分钟均值时开仓,建仓后以持仓期间收盘价的最高(最低)值为基准,回落(反弹)0.4% 即止损离场,属于典型的高频趋势反转加回撤止损的组合。