动态网格交易、科创板做市、股票 CTA……DolphinDB 中高频策略回测实例之股票篇

股票中高频交易是量化交易的重要分支,其核心在于在极短时间内处理大量市场数据,执行多笔交易,从而捕捉细微的价格波动并获取利润。中高频交易策略的成功依赖于精准的算法设计、低延迟的交易系统以及交易程序强大的数据分析能力。作为实盘操作前的关键步骤,策略回测能够帮助交易者模拟策略在不同市场环境下的表现,评估策略的胜率、盈亏比和最大回撤等关键指标,识别潜在风险与不足,从而通过优化策略参数,确保策略在实战中具备持续的盈利能力。

在此背景下,我们将通过三个常见的股票中高频交易策略的回测案例,为大家详细介绍 DolphinDB 中高频回测引擎的使用方法,并通过性能测试,展示 DolphinDB 一站式中高频策略回测解决方案的优越性能。此前我们发布过期货 CTA 策略的回测案例:DolphinDB:DolphinDB 中高频回测解决方案:期货分钟频 CTA 策略回测实例,大家同样可以参考了解。

1. 背景介绍

本文将详细展示如何在 DolphinDB 中高频回测引擎中实现股票动态网格交易策略科创板做市策略以及股票 CTA 策略。在展示如何使用 DolphinDB 脚本来编写案例策略之前,为使读者更好地理解,本章将简要介绍三种股票中高频策略,以及 DolphinDB 提供的中高频回测解决方案。

1.1 动态网格交易策略背景介绍

传统网格交易策略以固定的基准价为基础,进行买卖操作。当价格下跌至设定的触发点时执行买入;当价格上升至设定的触发点时执行卖出。这种策略适用于价格在小范围内波动的情形,但面对突发的大幅波动时表现不足,无法有效捕捉上涨趋势或规避暴跌行情。

动态网格交易策略在传统网格策略的基础上进行了改进,引入了根据市场条件动态调整网格参数的机制,以增强策略的灵活性和适应性。它通过实时分析市场的波动性或其他技术指标,自动调整网格的间距、位置或大小,从而能够更好地应对市场的剧烈波动和趋势变化。这种方法使得交易策略在面对不断变化的市场环境时,能够更加高效地捕捉机会并降低风险。

1.2 科创板做市策略背景介绍

做市策略通过赚取买卖价差获取利润,是高频交易策略中最为核心和广泛应用的策略之一。该策略的基本原理是通过持续提供买卖报价,捕捉市场波动中的价差。然而,由于做市策略要求在各种市场条件下不断报单,在标的资产出现单边行情或剧烈波动时,做市商可能面临库存风险,导致持仓规模急剧增加,从而放大策略的整体风险。为有效应对这些挑战,做市策略通常由三个关键模块组成:报价管理、持仓对冲管理和风险控制管理。

报价管理模块专注于设置买卖双边报价,包括在市场发生单边成交或价格大幅波动时,如何快速撤单并重新调整报价以降低冲击。持仓对冲管理则关注在持仓规模超出预设阈值时,及时执行平仓或对冲操作,以防止仓位失控和潜在损失。此外,风险控制管理模块通过实时监控市场动态与持仓情况,设定预警机制和风控措施,确保策略在各类市场环境中保持稳健表现。这三个模块的协同运作,使得做市策略在复杂多变的市场环境中能够有效平衡收益与风险。

1.3 股票 CTA 策略背景介绍

在中高频交易中,CTA 策略通过预测价格走势来捕捉市场机会,特别擅长追踪大单动向和市场趋势。其核心思想是通过分析订单流信息和市场事件,预判短期价格波动方向,并利用速度优势提前建仓,待价格达到预期目标后及时平仓,实现收益最大化。

CTA 策略虽然起源于商品期货市场,但其理念已被广泛应用于股票市场。股票 CTA 策略通过技术指标、价格动量和订单流等数据预测短期或中期的价格走势,当模型识别出趋势或市场异动时,策略迅速建仓,并在价格向有利方向发展时平仓锁定利润。股票 CTA 策略强调系统化、数据驱动和速度优势,特别适合中高频交易场景。此外,这类策略通常结合分散投资和严格的风险管理措施,以应对市场波动和不确定性。

总体而言,股票 CTA 策略为投资者提供了系统化、可重复的方式来捕捉市场趋势,尤其在动量驱动的市场环境中表现优异。

1.4 DolphinDB 中高频回测解决方案概述

中高频量化交易策略回测平台的实现主要包括三个重要环节:行情数据按顺序回放,委托订单撮合,以及策略开发与策略回测绩效评估。而在实现中高频策略回测时往往面临以下几个挑战:

首先,海量中高频交易数据对回测引擎的查询与计算性能提出了极高的要求。

其次,为确保策略评估和优化的有效性,回测过程中应尽可能模拟实际的交易过程,例如考虑订单能否成交、成交价格、成交量以及市场冲击等因素。

此外,回测引擎还应具有灵活的架构,能够支持多种交易策略和技术指标的实现,并且易于扩展,以适应不同的市场和交易需求。

针对上述挑战,DolphinDB 基于其高性能的分布式存储和计算架构,为用户提供了一个易扩展、性能优的中高频量化交易策略回测解决方案。该方案实现了库内行情回放、模拟撮合引擎和事件型中高频回测引擎三大核心组件,支持通过 DolphinScript、Python 或 C++语言完成中高频策略的研发和测试。具体来说,该方案涵盖以下三个模块:

  • 回放功能:支持将一个或多个不同结构的分布式表中的数据严格按照时间或者按指定多列排序顺序回放到流表中,因此可以方便地解决因子计算和量化策略研发中研发和生产一体化的问题。
  • 模拟撮合引擎插件:支持沪深交易所 Level-2 逐笔行情和快照行情,实现与交易所一致的 “价格优先,时间优先” 的高精度的撮合、基于多种行情数据的撮合模式、丰富的可用于模拟实盘环境的撮合配置。
  • 回测插件:用户可以在其中自定义指标,支持基于逐笔、快照、分钟和日频行情进行策略回测,获取回测的收益、持仓、交易明细等信息。其中基于逐笔和快照行情进行高精度策略回测,用户可以实现仿真和回测一体化的策略验证。

值得一提的是,这三个模块化解决方案与外部解决方案兼容性良好。即使用户已经实现了某个环节的解决方案,DolphinDB 提供的解决方案也可以与其融合成一个完整的回测方案。

2. 基于 DolphinDB 的股票中高频交易策略实现

在本章节中,我们将详细讲解如何利用 DolphinDB 内置脚本编写事件驱动函数,以实现前述的三种策略,内容包括策略逻辑的实现过程、初始参数的配置、事件函数的定义,以及如何根据实际情况进行并行回测、策略参数寻优等。

2.1 股票动态网格交易策略回测的实现

动态网格策略通过设置两个关键参数:网格间距 α 和反弹间距 β,来灵活应对市场波动。当标的资产价格首次触及预设的网格线时,策略记录该点位但不立即执行操作;只有当价格再次触及设定的反弹间距 β 时,才会执行买入或卖出操作。这种策略逻辑有效过滤掉市场中的微小波动,减少不必要的交易,并确保在更有利的价格区间进行操作。具体策略逻辑如下:

  • 构建网格策略参数:初始价格为策略开盘时的第一个成交价,网格间距 α 设置为2%,反弹间距 beta设置为1%,每格的交易金额 M 设置为10万
  • 开仓逻辑:标的价格触发基准价之下的第 n 个网格线,等待最新价格从最低价反弹 β 买入,数量为 n*M/最新价
  • 平仓逻辑:标的价格触发基准价之上的第 n 个网格线,等待最新价格从最高价回落 β 卖出,数量为 n*M/最新价
  • 根据开仓或者平仓信号,更新基准价为最新买或卖价格

2.1.1 编写自定义策略

中高频回测中,策略通常是事件驱动的,而一个策略逻辑通常需要涉及多种事件,比如新的行情到来、新的订单成交等等。DolphinDB 回测引擎采用事件驱动机制,提供了全面的事件函数如策略初始化函数、盘前回调函数、行情回调函数、每日盘后回调函数等,用户可以在相应的回调函数中编写策略逻辑实现相应的策略。后续本案例将会展示不同的事件函数是如何实现的。

首先,可以在初始化函数 initialize 中通过参数逻辑上下文 contextDict 设置策略参数。策略初始化函数 initialize 只在创建引擎之后触发一次。在本案例中,需在初始化函数中设置网格策略的参数,包括网格间距、反弹间距以及每格的交易金额,以及每只标的相应的初始价格基准价等策略回测全局参数。

def initialize(mutable contextDict){
    //通过Backtest::setUniverse可以更换当日股票池,
    //如Backtest::setUniverse(contextDict["engine"],["688088.XSHG","688157.XSHG","688208.XSHG"])
    print("initialize")
    // 网格策略参数   
    // 初始价格
    contextDict["initPrice"] = dict(SYMBOL,ANY)
    // 网格间距(百分数)
    contextDict["alpha"]=0.01
    // 回落间距(百分数)
    contextDict["beta"] =0.005
    // 每格交易金额
    contextDict["M"] = 100000
    contextDict["baseBuyPrice"] = dict(SYMBOL,ANY)
    contextDict["baseSellPrice"] = dict(SYMBOL,ANY)
    contextDict["lowPrice"]=dict(SYMBOL,ANY)
    contextDict["highPrice"]=dict(SYMBOL,ANY)
    contextDict["N"]=dict(SYMBOL,ANY)
    // 手续费费率
    contextDict["feeRatio"] = 0.00015
    //记录每日统计量
    contextDict['dailyReport' ]= table(1000:0,[`SecurityID,`tradeDate,`closePrice,`buyVolume,
    `buyAmount,`sellVolume,`sellAmount,`PosExposureVolume,`PosExposureValue,`dailyEarning],
    [SYMBOL,DATE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE])
    //日志
    contextDict["log"]=table(10000:0,[`tradeDate,`time,`info],[DATE,TIMESTAMP,STRING])
}

其次,要实现策略的主要逻辑需要先定义一个自定义函数 updateBaseBuyPrice ,当最新价格突破新的网格上线或网格下线时,根据最新的网格线更新基准买卖价格,以及更新当前的最高或最低价格。

def updateBaseBuyPrice(istock,lastPrice,basePrice,mutable baseBuyPrice,mutable baseSellPrice,mutable N,mutable highPrice,mutable lowPrice,alpha,n,mode=0){
    //根据最新价和最新的基准价更新网格线和最高或者最低价
    baseBuyPrice[istock]=basePrice*(1-alpha)
    baseSellPrice[istock]=basePrice*(1+alpha)
    N[istock]=n
    if(mode==0){
        //买入、卖出等初始化
        lowPrice[istock]=0.
        highPrice[istock]=10000.
    }
    else if(mode==1){
        //下跌,更新下网格线 
        lowPrice[istock]=lastPrice
        highPrice[istock]=10000.        
    }
    else if(mode==2){
        //上涨,更新上网格线
        lowPrice[istock]=0.
        highPrice[istock]=lastPrice         
    }
}

然后,在 onSnapshot 回调函数中,根据上涨或下跌行情实时更新网格数量和最新的网格线,并记录下跌过程中的最低价和上涨过程中的最高价。

def onSnapshot(mutable contextDict, msg){
    if(second(contextDict["tradeTime"])<09:30:00 or second(contextDict["tradeTime"])>14:57:00){
        return 
    }
    alpha=contextDict["alpha"]
    // 回落间距(百分数)
    beta=contextDict["beta"]
    M=contextDict["M"] 
    for (istock in msg.keys()){
        lastPrice=msg[istock]["lastPrice"]
        设置初始价
        if(not istock in contextDict["initPrice"].keys()){
            contextDict["initPrice"][istock]=lastPrice
            updateBaseBuyPrice(istock,lastPrice,lastPrice, contextDict["baseBuyPrice"],
            contextDict["baseSellPrice"] , contextDict["N"], contextDict["highPrice"], contextDict["lowPrice"],alpha,1,0)
        }
        init_price=contextDict["initPrice"][istock]
        if(lastPrice<=contextDict["baseBuyPrice"][istock]){
            //下跌,更新下网格线
            n=floor(log(lastPrice\init_price)\log(1-alpha))+1   
            if(n>contextDict["N"][istock]){
                newBasePrice=init_price*pow((1-alpha),n)
                updateBaseBuyPrice(istock,lastPrice,newBasePrice, contextDict["baseBuyPrice"], contextDict["baseSellPrice"], 
                contextDict["N"], contextDict["highPrice"], contextDict["lowPrice"],alpha,n,1)
            }
        }
        else if(lastPrice>contextDict["baseSellPrice"][istock]){
            //上涨,更新上网格线
            n=floor(log(lastPrice\init_price)\log(1+alpha))+1
            if(n>contextDict["N"][istock]){
                newBasePrice=init_price*pow((1+alpha),n)
                updateBaseBuyPrice(istock,lastPrice,newBasePrice, contextDict["baseBuyPrice"], 
                contextDict["baseSellPrice"] , contextDict["N"], contextDict["highPrice"], contextDict["lowPrice"],alpha,n,2)
            }
        }

当最新价格触发反弹价或者回落价格时,执行买入或卖出相应数量的标的操作,然后动态更新最新价的基准价格。在买入或者卖出时,调用 Backtest::getPosition 接口获取当日的买入或者卖出数量。Backtest::getPosition 接口是引擎提供的实时获取账户买\卖持仓量,买\卖成交金额等接口函数,具体返回字段见 回测引擎插件接口说明文档

        if(contextDict["lowPrice"][istock]>0. and lastPrice>contextDict["lowPrice"][istock]*(1+beta)){
            //买入
            qty=int(contextDict["N"][istock]*M\lastPrice)/100*100
            Backtest::submitOrder(contextDict["engine"], (istock,contextDict["tradeTime"] , 5, lastPrice, qty, 1),"buy")        
            contextDict["initPrice"][istock]=lastPrice
            updateBaseBuyPrice(istock,lastPrice,lastPrice, contextDict["baseBuyPrice"], 
            contextDict["baseSellPrice"] , contextDict["N"], contextDict["highPrice"], contextDict["lowPrice"],alpha,1,0)
        }
        else if(contextDict["highPrice"][istock]<10000. and lastPrice<contextDict["highPrice"][istock]*(1-beta)){
            //卖出
            qty=Backtest::getPosition(contextDict["engine"],istock).todayBuyVolume[0]
            if(qty<=0){
                continue
            }
            qty=min([int(contextDict["N"][istock]*M\lastPrice)/100*100,qty])
            Backtest::submitOrder(contextDict["engine"], (istock,contextDict["tradeTime"] , 5, lastPrice, qty, 3),"sell")
            contextDict["initPrice"][istock]=lastPrice
            updateBaseBuyPrice(istock,lastPrice,lastPrice, contextDict["baseBuyPrice"], contextDict["baseSellPrice"] , 
            contextDict["N"], contextDict["highPrice"], contextDict["lowPrice"],alpha,1,0)
        }
        //实时更新最低或者最高价
		if(contextDict["lowPrice"][istock]>0){
			contextDict["lowPrice"][istock]=min([contextDict["lowPrice"][istock],lastPrice])
		}
		if(contextDict["highPrice"][istock]<10000.){
			contextDict["highPrice"][istock]=max([contextDict["highPrice"][istock],lastPrice])
		}

2.1.2 根据策略设置相应的配置参数

回测的开始与结束日期、初始资金、手续费和印花税、行情类型、订单延时、动态分红除权等均可以通过该参数进行配置。这些参数允许用户灵活地调整回测条件,以模拟不同的市场环境和交易策略的效果。具体的初始参数配置代码示例如下:

startDate=2021.01.01
endDate=2021.01.04
userConfig=dict(STRING,ANY)
userConfig["startDate"]=startDate
userConfig["endDate"]=endDate
userConfig["strategyGroup"]= "stock"
userConfig["frequency"]= 0
userConfig["cash"]= 100000000       
userConfig["commission"]= 0.0
userConfig["tax"]= 0.0
userConfig["dataType"]= 2
userConfig["msgAsTable"]= false
strategyName="gridStrategy" 

2.1.3 调用 createBacktestEngine 创建回测引擎

engine = Backtest::createBacktestEngine(strategyName, userConfig,,initialize,
 beforeTrading,,onSnapshot,onOrder,onTrade,afterTrading,finalized)

2.1.4 调用 appendQuotationMsg 执行回测

通过 Backtest::createBacktestEngine 创建回测引擎之后,可以通过以下方式将行情数据传入回测引擎,执行回测。参数 gridStrategyData 为相应的行情数据,行情数据字段和类型说明参考 回测插件的接口文档

Backtest::appendQuotationMsg(engine,gridStrategyData)

2.1.5 获取回测结果

回测运行结束之后,可以通过相应的接口获取每日持仓、每日权益、收益概述、成交明细和策略中用户自定义的逻辑上下文。回测插件提供的完整回测结果接口可以参阅 回测插件的接口说明文档

//成交明细
tradeDetails=Backtest::getTradeDetails(engine)
//查询当前的未成交(未完成)订单列表
openOrders=Backtest::getOpenOrders(long(engine))
//每日持仓
dailyPosition=Backtest::getDailyPosition(long(engine))
//可用资金
enableCash=Backtest::getAvailableCash(long(engine))
//未成交订单明细
openOrders=tradeDetails[tradeDetails.orderStatus==-3]
//日组合指标展示
totalPortfolios=Backtest::getTotalPortfolios(long(engine))
回测结果综合展示
returnSummary=Backtest::getReturnSummary(long(engine))

下图即为获取每日持仓信息展示:

2.1.6 并行回测

通常情况下,技术指标更关注策略的胜率指标,因为较高的胜率意味着策略的潜在价值更大。在中高频回测中,由于行情数据量庞大,采用并行回测可以显著提升策略回测的效率。

首先,定义函数 runBacktest_snapshot,用于通过快照+成交数据执行策略回测。回测流程包括获取从回测开始到结束的所有交易日,并对每个交易日逐一执行策略回测。最后,在回测结束时,添加一条 symbol 为 "END" 的消息,标识行情回放结束。回测引擎在接收到 “END” 标志后将结束回测,并统计策略的收益、胜率等关键指标。

 def runBacktest_snapshot(userConfig,initialize, beforeTrading,onTick,onSnapshot,
    onOrder,onTrade,afterTrading,finalize,strategyName,startDate,endDate,codes){
        //快照+成交行情进行策略回测
        engine =  Backtest::createBacktestEngine(strategyName, userConfig,,
        initialize, beforeTrading,,onSnapshot,onOrder,onTrade,afterTrading,finalize)
        go
        tradeDates=getMarketCalendar("CFFEX",startDate, endDate)
        for( idate in tradeDates){  
            messageTable=select iif(substr(SecurityID,6,2)=="SH",
            strReplace(SecurityID,"SH",".XSHG"),
            strReplace(SecurityID,"SZ",".XSHE")) as symbol, 
            iif(substr(SecurityID,6,2)=="SH","XSHG","XSHE") as symbolSource,
            dateTime as timestamp,lastPrice, upperLimitPrice as upLimitPrice,
            lowerLimitPrice as downLimitPrice,long(totalBidQty) as totalBidQty,
            long(totalOfferQty) as totalOfferQty,bidPrice,bidOrderQty as bidQty,
            OfferPrice as offerPrice,offerOrderQty as offeQty,
            fixedLengthArrayVector([lastPrice]) as signal,tradePrice2 as tradePrice,
            tradeQty2 as tradeQty,0.0 as prevClosePrice from  
            loadTable("dfs://Level2_tickTrade",`tickTradeTable)
            where SecurityID in codes and  date(dateTime) between startDate:endDate and
            second(dateTime) between startTime:endTime order by timestamp
            Backtest::appendQuotationMsg(engine,messageTable)
        }
        //增加回测结束标志
        temp=select * from messageTable where timestamp=max(timestamp) limit 1
        update temp set symbol="END"
        Backtest::appendQuotationMsg(engine,temp)
        return engine
    }

其次,通过定义 runBacktestParallelMode 函数将股票池分为 n 份,进行策略并行回测。

def runBacktestParallelMode(userConfig,initialize, beforeTrading,onTick,
    onSnapshot,onOrder,onTrade,afterTrading,finalize,startDate,endDate,codes,
    parallelMode=4,nSize=1){
        dates=getMarketCalendar("CFFEX",startDate, endDate)
        destroyAllBacktestEngine()
        jobs= array(STRING, 0, 10)
        //把股票分成 n 份,并行
        cuts=cut(codes,nSize)
        for( icodes in cuts){
            strategyName=strReplace("backtest"+concat(icodes,""),".","")        
            jobId=submitJob(strategyName,strategyName+"job",runBacktest_snapshot,
            userConfig,initialize, beforeTrading,onTick,onSnapshot,onOrder,onTrade,
            afterTrading,finalize,strategyName,min(dates),max(dates),icodes)
            jobs=jobs.append!(jobId)    
        }
        //获取每个引擎的回测结果,并返回,具体代码请见附件
    }

本案例选取了2020年沪深交易所的最活跃800支股票标的进行并行回测,获取成交明细,分析策略最终的胜率等指标,代码示例如下:

dailyReport,tradeOutputTable,engines,removejobs=runBacktestParallelMode(userConfig,
        initialize, beforeTrading,,onSnapshot,onOrder,onTrade,afterTrading,finalize,
        startDate,endDate,codes,4,n)

2.2 科创版做市策略回测的实现

以下是一个简单的做市策略逻辑:

  • 策略参数说明
    • 最小报价金额
    • 双边报价最大的买卖价差(百分数)
    • 单边最大的持仓量
    • 对冲价格偏移量(单位为元)
    • 当日亏损金额上限
    • 最新一分钟的价格波动上限

  • 双边报价逻辑:当前没有报价时,首先获取最新 tick 盘口的买卖中间价 midPrice =(ask1+bid1)/2,以 min(midPrice-最大的买卖价差/2,bid1)和 max(midPrice+最大的买卖价差/2,ask1)的价格进行双边报价,报价数量为最小报价金额除以相应的价格
  • 对冲模块逻辑:报价订单发生成交时,以成交价加减对冲价格偏移量进行对冲
  • 风控模块逻辑
    • 当当日亏损金额超过限制时,停止当日报价
    • 当单边的持仓量超过规定上限时,停止接受新的报价,并进行平仓操作。直到持仓数量减少至单边最大持仓量的四分之一时,重新开始接受报价
    • 当最新一分钟的价格波动超过设定的上限时,暂定本次报价

在本案例中,我们采用逐笔行情数据进行回测,策略信号的触发基于快照行情。策略的委托订单则通过逐笔行情数据进行高精度撮合,撮合过程中严格遵循价格优先、时间优先的原则,以确保回测结果的准确性和一致性。此外,本案例的回测流程与之前介绍的动态网格案例相似,对于重复的步骤将不再详细说明,重点将放在自定义策略的编写及参数优化部分。

2.2.1 编写自定义策略

在策略初始化函数 initialize 中设置双边报价最大的买卖价差、报价金额等全局参数,同时订阅行情的最新一分钟的价格波动指标。该函数还有一个 userParam 参数,可通过该外部参数为策略参数赋值。

def initialize(mutable contextDict,userParam){
	print("initialize")
	// 报价模块参数
	contextDict["maxBidAskSpread"]=userParam["maxBidAskSpread"]//双边报价最大的买卖价差
	...
}

在 onSnapshot 策略回调函数中,根据风控规则进行买卖双边报价,并在持仓超限时进行平仓逻辑处理。在 onTrade 成交回报回调函数中对成交订单进行对冲。

def onTrade(mutable contextDict,trades){
	 // trades 为字典列表
	///成交主推回调
	///在这里处理对冲模块
	hedgeOffsetAmount=contextDict["hedgeAmount"] // 对冲偏移金额
	for (itrade in trades){
		...
		///下达对冲单
		Backtest::submitOrder(contextDict["engine"], (stock, itrade.tradeTime, 5, price, vol, bsFlag),"hedgeOrder")
	}
}

对科创版做市策略进行回测时,使用逐笔行情进行买卖双边报价,利用逐笔数据进行订单撮合,同时可以设置订单延时等。使用 replay 函数把逐笔成交、逐笔委托和快照数据按顺序异构回放到 tickHqTable 表中,然后可以通过接口 Backtest::appendQuotationMsg(engine, msg) 执行逐笔行情的策略回测。

userConfig=dict(STRING,ANY)
//逐笔行情
userConfig["dataType"]=0
//策略参数设置
userParam=dict(STRING,FLOAT)
userParam["maxBidAskSpread"]=0.03//双边报价最大的买卖价差
userParam["quotAmount"]=100000  ///报价金额
...
engine = Backtest::createBacktestEngine(strategyName, userConfig,,
initialize{,userParam}, beforeTrading,,onSnapshot,onOrder,onTrade,afterTrading,finalize)
///开始执行回测
Backtest::appendQuotationMsg(engine,select * from tickHqTable)

2.2.2 策略参数寻优

我们可以通过调整策略参数来优化策略表现,这些参数包括最大买卖价差、报价金额、对冲偏移量、风控模块的最大单边持仓上限、当日亏损上限和价格波动等指标。以下是对对冲偏移量和价格波动率参数进行优化的示例:

// step 3:并行策略回测
jobs=array(STRING, 0, 10)
codes=(exec distinct(SecurityID) from loadTable("dfs://Level 2_tl","snapshot") where SecurityID like "68%")[:10]
for(hedgeAmount in [0.01,0.02,0.03,0.04]){
	for(maxVolatility_1m in [0.03,0.04,0.05]){
	//策略参数设置
		userParam=dict(STRING,FLOAT)
		userParam["maxBidAskSpread"]=0.03//双边报价最大的买卖价差
		userParam["quotAmount"]=100000  ///报价金额
		// 对冲模块参数
		userParam["hedgeAmount"]=hedgeAmount//对冲偏移金额
		//风控参数
		userParam["maxPos"]=2000//最大单边持仓上限
		userParam["maxLossAmount"]=200000 //当日亏损上限金额
		userParam["maxVolatility_1m"]=maxVolatility_1m //最新一分钟
		strategyName="marketMakingStrategy"
		strategyName=strategyName+strReplace(concat(each(def(a,b):a+"_"+string(b),userParam.keys(),userParam.values())),".","")
		jobId=submitJob(strategyName,strategyName+"job",runBacktest_tick,userConfig,initialize{,userParam}, beforeTrading,,
		onSnapshot,onOrder,onTrade,afterTrading,finalize,strategyName,startDate,endDate,codes)
		jobs=jobs.append!(jobId)
	}
}

2.3 股票中高频 CTA 策略回测的实现

本案例将基于 Level 2 快照数据和逐笔成交数据,实现以下的 CTA 策略逻辑:

  • 快照数据计算 MACD 指标,当 MACD 指标出现金叉之后,且满足下面两个条件之一时,执行买入:
    • 基于逐笔成交,成交价的过去30秒内的 CCI 指标从下向上突破+100线进入超买区间,并且过去30秒的成交量大于50000股时,买入500股。
    • 当成交价的过去30秒内 CCI 指标从下向上突破-100线时,买入500股。

  • MACD 指标死叉时,卖出。

2.3.1 编写自定义策略

首先基于 Level2 快照定义 MACD 指标、基于 Level2 逐笔成交行情定义30秒内的 CCI 和成交量指标。回测引擎内部创建的是状态响应式引擎,因子指标定义的方式,可以具体参考状态响应式引擎用户手册。在策略初始化函数中,首先订阅基于 Level2 快照行情 MACD 指标、订阅基于成交行情的30秒的 CCI 和成交量指标。

def initialize(mutable contextDict){
	print("initialize")
	//订阅快照行情的指标
	d=dict(STRING,ANY)
	d["macd"]=<macd(lastPrice,240,520,180)[0]>
	d["prevMacd"]=<macd(lastPrice,240,520,180)[1]>
	Backtest::subscribeIndicator(contextDict["engine"], "snapshot", d)
	d=dict(STRING,ANY)
	d["cci"]=<myCCI(price,timestamp,orderType)[0]>
	d["prevcci"]=<myCCI(price,timestamp,orderType)[1]>
	d["tradeVol30s"]=<tradeVol30s(qty,timestamp,orderType)>
	Backtest::subscribeIndicator(contextDict["engine"], "trade", d)
}

在快照行情回调函数 onSnapshot 中,通过获取订阅的 MACD 指标记录买入卖出信号。在逐笔成交行情 onTick 中执行买入操作。

def onSnapshot(mutable contextDict, msg){
	msg可以为字典或表,最新时刻的tick数据
	if(second(contextDict["tradeTime"])<09:40:00 or second(contextDict["tradeTime"])>14:57:00){
		return 
	}
	buyList=contextDict["buyList"]
	///记录买入卖出信号处理
	for (istock in msg.keys()){
		macd=msg[istock]["macd"]
		prevMacd=msg[istock]["prevMacd"]
		if(prevMacd<0 and macd>0){
			pos=Backtest::getPosition(contextDict["engine"],istock).longPosition[0]
			if((pos<0) and (not istock in buyList)){	
				buyList=buyList.append!(istock)
			}
		}
		else if(prevMacd>0 and macd<0){
			pos=Backtest::getPosition(contextDict["engine"],istock).longPosition[0]
			openQty=sum(nullFill(Backtest::getOpenOrders(contextDict["engine"],istock,,"close").openQty,0))
			if(pos-openQty>0){
				//卖出
				pos=pos-openQty
				price=round(msg[istock]["lastPrice"]-0.02,3)
				Backtest::submitOrder(contextDict["engine"], (istock,contextDict["tradeTime"], 5, price, pos, 3),"close")
			}
		}
	}
	contextDict["buyList"]=buyList		
}

3. 性能测试

为了更清晰地展示 DolphinDB 中高频回测引擎的性能,我们对于上述三个案例,选取相应的数据进行了性能测试。

本文使用的测试环境配置为:

  • DolphinDB v3.00.1 单节点
  • CPU:Intel(R) Xeon(R) Silver 4216 CPU @ 2.10GHz 64核
  • 内存:400G
  • 硬盘:SSD 12 Gbps

3.1 动态网格交易策略性能测试

针对动态网格交易策略,我们选取了三组数据进行测试:第一组为2021年部分月份上交所交易最活跃的10只股票;第二组为2021 年上海证券交易所最活跃80只标的一个交易日内的所有数据;第三组为2021 年部分月份上海证券交易所最活跃80只标的数据。三组数据均为快照+区间成交行情数据,测试结果见表3-1。

表 3-1:股票动态网格交易策略回测性能测试结果

标的数据量核数数据回放耗时(s)执行回测耗时(s)
2021年上交所交易最活跃的10只股票8,447,769行118s282.6s
一个交易日80只股票306,003行10.5s6s
2021年上交所交易最活跃的80只股票70,528,043行816.1s247.7s
2021年上交所交易最活跃的80只股票70,528,043行169.2s153.6s

3.2 科创板做市策略性能测试

在科创板做市策略的案例中,我们通过调整策略中的指标参数对策略做出了调整优化。针对该案例的参数寻优,我们进行了性能测试。股票池为2023年02月份20个交易日的科创版股票,对策略参数对冲偏移金额分别为0.01、0.02、0.03、0.04和最新一分钟的价格波动上限分别为0.03、0.04、0.05的总共12种场景,并分别选取一只标的以及100只标的分组进行回测,测试结果见表3-2,回测结果为12种场景的平均耗时。

表 3-2:科创板做市策略参数寻优性能测试结果

标的个数数据回放平均耗时(秒)回测执行平均耗时(秒)
13.511.4
100184.8909.6

3.3 股票中高频 CTA 策略性能测试

对于上述实现的股票中高频 CTA 策略,我们选取了2023年2月的20个交易日内部分活跃标的进行测试。测试结果如表3-3所示,具体展示了一只股票标的以及50只股票标的并行回测的回测耗时。

表 3-3:股票高频 CTA 策略回测性能测试结果

标的个数测试模式总耗时(秒)每个引擎数据回放平均耗时(秒)每个引擎回测执行平均耗时(秒)
1单线程11.10.610.5
50按股票分成10份、10个并行163.421.2122.2

上述性能测试结果显示,DolphinDB 在处理海量中高频数据时,通过并行回测显著提升了回测效率。不过需要注意的是,回测耗时与线程数并非完全成比例增加。由于回测引擎是计算密集型任务,当线程数过多时,资源竞争和调度开销也会随之增加,从而对整体运行时间产生一定的负面影响。

4. 总结与展望

本文展示了如何通过 DolphinDB 回测引擎实现股票中高频策略的回测,展现了 DolphinDB 回测引擎优异的性能、丰富的策略触发机制以及全面的回测结果信息。接下来我们还将陆续发布融资融券、期权等回测案例,以进一步展示 DolphinDB 在不同市场和策略中的应用。

5. 附录

股票动态网格交易策略回测 demo(提供代码demo分别对应用户配置项参数中 msgAsTable 为 true 和 false 的情况)

科创板做市策略 demo (提供代码demo分别对应用户配置项参数中 msgAsTable 为 true 和 false 的情况)

股票中高频 CTA 策略 demo

  • 26
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值