FAQ > 金融建模 > 建模问题 > 回测框架相关问题

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

  • 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变更为开仓与平仓成交价为证券的下一条交易明细的价格,具体实现如下(更改内容,注意标红的地方):

    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后的价格为:

    对比结果:与调仓时间的下一条交易明细价格一致。

    卖出:

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

    对比结果:与调仓时间的下一条交易明细价格一致。