FAQ > 金融建模 > 建模及平台使用中遇到的问题 > 回测框架相关问题

Q:如何在策略回测框架(TsBackTesting)中做高频回测时设置成交价为下一个tick价格数据?    

  • A:在实盘操作中,在当前时间点进行选股模型的运算后,并不能立刻以当时的价格达成交易,所以,在高频回测中,有些用户也希望以下一个时点的价格来模拟实盘交易的情况,达到更接近实盘操作的目的。
    那么,在回测框架中,如何实现呢?
    此时,我们可以利用数量类回测中自定义成交价(即FPriceType设置为-1)的方式来达到目的。
    具体步聚为
    第一步:设置成员变量,FPriceType:=-1;将成交价类型设置为自定义。
    第二步:在重写gettradeorder方法中,成交价通过秒线获取下一秒的价格,比如:

    r[i,'成交价']:=specall(ref(close(),-1),
    array(pn_cycle():cy_1s(),pn_date():vEndt,pn_stock():stockid,pn_rate():0));


    注:比例类回测中,无法自定义成交价,所以做不了这个实现。

    实现范例:
    将天软范例回测框架-高频回测:TSFL_TSBackTesting_HighFrequencyTrading变更为开仓与平仓成交价为证券的下一个tick的价格,具体实现如下(更改内容,注意标红的地方):

    Function TSFL_TSBackTesting_HighFrequencyTrading_Nextclose(Stocks,BegT,EndT,Cycle,MaxN,MaxGR,MaxLR,FeeRate);
    Begin
       obj := createobject('HighFrequencyTrading');
       
       //********************回测基本设置***************************//
          //回测开始时间
       obj.FBegT:=BegT;
          //回测截止时间
       obj.FEndT:=EndT;
          //回测周期
       obj.FCycle:=Cycle;
          //组合类型(数量类组合)
       obj.FGroupType:=2;

          //成交价类别(用户自定义)
       obj.FPriceType:=-1;
          //数量类组合开仓数量类别(此处采用固定金额法)
       obj.FOpenVolType:=2;
          //数量类组合平仓数量类别(此处采用可平仓数量占比法)
       obj.FCloseVolType:=2;
       
          //日内止盈止损
       obj.FGLType:=1;
          //止盈线
       obj.FMaxGainRatio:=MaxGR;
          //止损线
       obj.FMaxLossRatio:=MaxLR;
          //费率(%)
       obj.FFeeRate:=FeeRate;

       //********************用户自定义参数***************************//
          //此处的股票池以数组的方式提供,实际可修改为按照选股方法获取
       obj.FStockArr := str2array(Stocks);
          //最大持有数量
       obj.FMaxN := MaxN;

          //回测
       obj.BackTest();
       

          //获取返回结果(返回结果可根据需要选择)
       return array(
                      //---组合基础
                    "交易明细":obj.GetTradeData(BegT,EndT),
                    "资产配置":obj.GetAssetData(BegT,EndT),
                    "持仓明细":obj.GetHoldData(BegT,EndT),

                      //---组合盈亏、交易
                    "组合盈亏":obj.GetGainandLoss(BegT,EndT),
                    "交易汇总":obj.GetTradingAmount(BegT,EndT),
                    "组合盈亏(按证券)":obj.GetGainandLossBySecurity(BegT,EndT),
                    "交易汇总(按证券)":obj.GetTradingAmountBySecurity(BegT,EndT),

                      //---组合收益
                    "区间组合收益率":  obj.GetPortfolioReturn(BegT,EndT),
                    "组合和基准收益率序列":obj.GetPortfolioReturn2(BegT,EndT),
                    "阶段收益":obj.GetTrailingReturn(EndT),
                    "滚动收益":obj.GetRollingReturn(BegT,EndT,cy_month()),

                    //----组合评价
                    "风险回报":obj.GetReturnandRisk(BegT,EndT),
                    "相对回报":obj.GetRelativePerformance(BegT,EndT),
                    );

    End;


    Type HighFrequencyTrading=class(TSBackTesting)

       FStockArr;  //备选股票池
       FMaxN;      //最大持有股票数
       FFeeRate;   //费率(%)

       function GetTradeOrder(vEndT);override;
       begin
          oV := BackupSystemParameters2();
             //当前时间
          d := vEndT;
          
          echo datetimetostr(d);
          cc := GetHoldData();   //获取当前持仓
          cash := GetSurplusFund();  //获取剩余资金
          
          ccstocks := sselect distinct(['代码']) from cc end;  //获取持仓中的股票代码
          tjy := array();
          n:=0;
          if istable(ccstocks) then
             ccnum := length(ccstocks)
          else
             ccnum := 0;
          newccnum := ccnum;
          if newccnum<FMaxN then   //当前持仓股票个数小于最大持仓数
          begin
             setsysparam(pn_cycle(),FCycle);
                //判断开仓
             for nI:=0 to length(FStockArr)-1 do
             begin
                stockid := FStockArr[nI];
                setsysparam(pn_stock(),stockid);
                setsysparam(pn_date(),d);
                if not istradeday(d) then continue;
                if not (stockid in ccstocks) then   //如果持仓中不存在才判断是否满足开仓
                begin
                   setsysparam(pn_rate(),1);
                   if cross(ma(close(),10),ma(close(),20))=1 then
                   begin
                      tjy[n]['截止日'] := d;
                      tjy[n]['代码'] := stockid;
                      tjy[n]['名称'] := stockname(stockid);
                      tjy[n]['方向'] := 1;
                      tjy[n]['动作'] := 0;
                      tjy[n]['成交价']:=specall(ref(close(),-1),
    array(pn_cycle():cy_1s(),pn_date():d,pn_stock():stockid,pn_rate():0));

                          //与FOpenVolType关联,为2时,提供金额即可,此处为剩余资金/剩余个数,由基类计算成交量
                      tjy[n]['资金'] := cash/(FMaxN-ccnum);
                      n++;
                      newccnum++;
                   end
                end
                if newccnum>=FMaxN then   //当持仓的证券个数大于最大持仓个数就终止开仓
                   break;
             end
          end;

             //平仓,对持仓循环判断是否达到平仓条件
          for nJ:=0 to length(cc)-1 do
          begin
             stockid := cc[nJ]['代码'];
             setsysparam(pn_stock(),stockid);
             setsysparam(pn_date(),d);
             setsysparam(pn_rate(),1);
             if cross(ma(close(),20),ma(close(),10))=1 then
             begin
                tjy[n]['截止日'] := d;
                tjy[n]['代码'] := stockid;
                tjy[n]['名称'] := stockname(stockid);
                tjy[n]['方向'] := 1;
                tjy[n]['动作'] := 1;
                tjy[n]['成交价']:=specall(ref(close(),-1),
    array(pn_cycle():cy_1s(),pn_date():d,pn_stock():stockid,pn_rate():0));

                   //FCloseVolType为2时,提供平仓比例即可,此处平仓数量占比(%)即全部平仓。也可将FCloseVolType设置为1,此处直接提供成交量
                tjy[n]['平仓数量占比(%)']:=100;
                n++;
             end
          end;
          
             //添加费率(%)字段
          update tjy set ['费率(%)']=FFeeRate end;
          return tjy;
       end
    End;

    成交价验证:更改后执行范例结果:

    验证其中交易明细:
    买入:

    SZ000009在2020-09-15 11:30:00后的价格为:

    对比结果:与调仓时间的下一个tick价格一致。

    卖出:

    SZ000009在2020-09-23 14:00:00后的价格为:

    对比结果:与调仓时间的下一个tick价格一致。