# 后台机制与策略实现

后台程序化是一种直接操作实际持仓的交易方法,相比较图表程序化,后台对于实际账户的管理更加精细(成交状态、追撤单操作)、灵活、高效。因此通过后台可以非常轻松的实现全市场预警交易。但是后台的整个过程属于静默执行,并不能将自身的运行过程显示于K线图中。

后台程序使用指南

# 图表策略如何转成后台策略

我们以图表策略MACD突破零轴金死叉交易系统为示例,将其转化为后台策略。

图表思路分析:

  1. 关于MACD实现过程不再赘述,金叉表示从下上穿0轴时的状态(并非一个持续的过程),因此我们使用CROSS函数。
  2. HOLDING函数是图表的理论持仓,它有三种状态:
      HOLDING=0代表没有持仓;
      HOLDING>0代表持有多头理论持仓;
      HOLDING<0代表持有空头理论持仓。
    因此可以根据HOLDING的状态判断,实现一开一平的信号过滤机制。
//技术指标部分
DIFF :=EMA(CLOSE,12) - EMA(CLOSE,26);
DEA  :=EMA(DIFF,9);
MACD :=2*(DIFF-DEA);

//程序化交易下单部分
//平空开多条件
if CROSS(MACD,0)=1 then begin 
    平空:SELLSHORT(HOLDING<0,1,MARKET);
    开多:BUY(HOLDING=0,1,MARKET);
end

//平多开空条件
if CROSS(0,MACD)=1 then begin 
    平多:SELL(HOLDING>0,1,MARKET);
    开空:BUYSHORT(HOLDING=0,1,MARKET);
end

当前持仓:HOLDING,COLORGRAY,LINETHICK0;
当前资产:ASSET,NOAXIS,COLORGRAY;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

后台思路分析:

  1. 关于MACD实现过程不再赘述,金叉表示从下上穿0轴时的状态(并非一个持续的过程),因此我们使用CROSS函数。

  2. THOLDING实际账户持仓,它有三种状态:
      THOLDING=0代表净持仓为0;
      HOLDING>0代表持有多头净持仓;
      HOLDING<0代表持有空头净持仓。
    因此可以根据THOLDING的状态判断,实现一开一平的信号过滤机制。

  3. 后台函数都是T****开头的,因此后台的下单函数相比图表下单函数均在最前面增加了字母T。例如:
      BUY==>TBUY,
      SELL==>TSELL;
    空头下单函数同理。

  4. 下单指令图表和后台有明确区分,例如:
      市价指令MARKET==>MKT,
      限价指令LIMIT==>LMT;

后台交易函数的详细说明

那么我们保证整体交易逻辑不变的情况下,只需要图表专用函数替换成后台专用函数即可。

//技术指标部分
DIFF :=EMA(CLOSE,12) - EMA(CLOSE,26);
DEA  :=EMA(DIFF,9);
MACD :=2*(DIFF-DEA);

//程序化交易下单部分
//平空开多条件
if CROSS(MACD,0)=1 then begin 
    平空:TSELLSHORT(THOLDING<0,1,MKT);
    开多:TBUY(THOLDING=0,1,MKT);
end

//平多开空条件
if CROSS(0,MACD)=1 then begin 
    平多:TSELL(THOLDING>0,1,MKT);
    开空:TBUYSHORT(THOLDING=0,1,MKT);
end

当前持仓:THOLDING,COLORGRAY,LINETHICK0;
当前资产:TASSET,NOAXIS,COLORGRAY;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

使用上述代码经过一段时间的测试发现,当同一合约存在锁仓时,本该产生平仓的下单语句没有进行。那么当我多策略同品种时,这种干扰因素如何排除呢?

经过分析,我们得知THOLDING是多空净持仓,而后台策略直接操作实际持仓,并不像图表有独立的理论持仓作为依据,从而间接实现下单交易。既然如此,有人提出解决方案: 我们使用全局变量进行标记。用标记代替THOLDING是否可行
这种方法是可行的,但是当策略复杂度过高时,也提高了错误的可能性。因此这种方式不是最佳解决方法。

有部分用户通过翻看后台函数发现仓位函数不仅仅只有THOLDING;列举下:

THOLDING
THOLDING2

TBUYHOLDING
TSELLHOLDING

TBUYHOLDINGEX
TSELLHOLDINGEX
1
2
3
4
5
6
7
8

如何能够直观的条件上述3组函数的特性呢?这里我们分享一个小技巧

后台函数调试小技巧

  • 后台资金类函数和账户持仓类函数是直接读取账户相关信息,可以直接将其显示在K线图中进行快速查看。例如: AA:TBUYHOLDING(1);
    注意:这类函数不能用于图表交易,因为他们没有历史值,不符合图表的计算机制,用于图表会造成信号闪烁甚至紊乱。
  1. 通过调试分析,我们发现使用TBUYHOLDING(1)、TSELLHOLDING(1) 或者 TBUYHOLDINGEX('' ,'' ,1 )、TSELLHOLDINGEX('' ,'' ,1 )均能达到多空独立控制的需求。因此上述代码可以再次进行调整。 注意:这两组函数的范围值均大于等于0。
//技术指标部分
DIFF :=EMA(CLOSE,12) - EMA(CLOSE,26);
DEA  :=EMA(DIFF,9);
MACD :=2*(DIFF-DEA);

//程序化交易下单部分
//平空开多条件
if CROSS(MACD,0)=1 then begin 
    //TSELLHOLDING(1)空头可用持仓数量,当大于0时,说明持仓空头仓位。
    平空:TSELLSHORT(TSELLHOLDING(1)>0,1,MKT);
    //TBUYHOLDING(1)多头可用持仓数量
    开多:TBUY(TBUYHOLDING(1)=0,1,MKT);
end

//平多开空条件
if CROSS(0,MACD)=1 then begin 
    平多:TSELL(TBUYHOLDING(1)>0,1,MKT);
    开空:TBUYSHORT(TSELLHOLDING(1)=0,1,MKT);
end

多头可用持仓:TBUYHOLDING(1),COLORGRAY,LINETHICK0;
空头可用持仓:TSELLHOLDING(1),COLORGRAY,LINETHICK0;
当前资产:TASSET,NOAXIS,COLORGRAY;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

  随着我们策略需求模块的填充,使用TSELLHOLDING函数,可能也无法满足使用,因此我们也可以根据实际情况,在策略开发初期时直接使用TBUYHOLDINGEX、TSELLHOLDINGEX仓位函数。由于策略是逐个模块调试开发实现的,当在后期需要对整个策略拓展时,非常容易因为遗漏或参数使用不当,造成逻辑错误。增加调试排查问题的难度。

# 后台如何调试

  策略在开发过程中不可避免的需要跟踪调试,当系统提供的变量跟踪功能无法满足逻辑调试需求时,我们可以结合debugfile函数跟踪逻辑执行过程,以达到快速定位解决问题。

debugfile(文件保存路径,写入的文本内容,变量)

示例1:单个变量打印输出

//将在程式化交易的监控部分输出到D:\TEST.TXT文件, "当前资产为1234.00",
//"%.2f"为一个打印的控制符号,系统会将他替换为指定的一个数字输出,%.2f为显示两位小数,%.0f则表示不显示小数。
DEBUGFILE('D:\TEST.TXT','当前资产为%.2f',1234);
1
2
3

示例2:多个变量同时打印输出

//通过NUMTOSTR函数将多个数值分别转换成字符串后,使用'&' 将字符串拼接在一起即可。参数3位置可以填任意数值。
DEBUGFILE('D:\TEST.TXT','当前资产='&NUMTOSTR(TASSET,2) & '   当前持仓='&NUMTOSTR(THOLDING,0),1);
1
2

在调试过程中,一般都是采用示例2中的方式实现,dubugfile一般是就近添加原则,将其加在需要调试的逻辑条件附近。完整的调试示例如下:

//技术指标部分
DIFF :=EMA(CLOSE,12) - EMA(CLOSE,26);
DEA  :=EMA(DIFF,9);
MACD :=2*(DIFF-DEA);

 
//条件
con1:=CROSS(MACD,0)=1 ;   //MACD上穿0轴
con2:=cross(0,MACD)=1 ;   //MACD下穿0轴

debugfile('D:\T_' & stklabel & '.txt',stklabel &'   [开仓条件='& numtostr(con1 and tholding=0,0)  &']'
        & '   各项条件:[开多条件1=' & numtostr(con1,0)&'   持仓手数='& numtostr(tholding,0)&']',0);
if con1 and tholding=0 then
    begin
         tbuy(1, 1, mkt);
   end

debugfile('D:\T_' & stklabel & '.txt',stklabel &'   [平仓条件='& numtostr(con1 and tholding=0,0)  &']'
        & '   各项条件:[平仓条件1='& numtostr(con2,0)&'   持仓手数=' & numtostr(tholding,0) & ']',0);
if con2 and tholding=0 then
    begin
         tsell(1, 1, mkt);
    end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

第11行解析

  • 参数1:'D:\T_' & 'stklabel' & '.txt'
    • 日志写入的路径,此处的结构:'D:\T_' + 品种代码 + 文件名后缀。此处的品种代码是字符串变量,多品种时可以实现分别记录,避免混淆。
  • 参数2:
    'stklabel' & ' [开仓条件=' & numtostr(con1 and tholding=0,0) & ']' & ' 各项条件:[开多条件1=' & numtostr(con1,0) & ' 持仓手数=' & numtostr(tholding,0) & ']'
    • 使用numtostr将开平仓条件、持仓转换成字符串后。与其他字符串拼接。以增加整个日志文件的可读性。
  • 参数3:0 。
    • 任意数字,在此处没有实质性作用,仅是占位使用。

debugfile日志输出如下:

日志文件名:T_P00.txt

       1                    2         3                       4
----------------------------------------------------------------------------------
2023-01-08 21:45:48.232    AG00   [开仓条件=0]   各项条件:[开多条件1=0   持仓手数=0]
2023-01-08 21:45:48.233    AG00   [平仓条件=0]   各项条件:[平仓条件1=0   持仓手数=0]

1
2
3
4
5

# 图表和后台信号不一致

刚接触后台时,一些使用过图表策略的用户会直接将图表和后台的运行结果进行对比,并且潜意识以图表信号为标准。这种方法是否可行?

这种方法本身就不正确,采用这种方法等同于刻舟求剑。其实图表和后台的差异原因产生的基本相同,只是各个因素造成差异的占比存在不同。在之前技术指标的运行机制分析中,我们知道影响一致性的首要因素是数据量,除此之外,还有函数、同步计算间隔这类不可人为控制因素,图表和后台直接亦是如此。

例如:分析图表策略和后台策略的函数造成的差异性。

  1. 图表策略:
//技术指标部分
DIFF :=EMA(CLOSE,12) - EMA(CLOSE,26);
DEA  :=EMA(DIFF,9);
MACD :=2*(DIFF-DEA);

//程序化交易下单部分
//平空开多条件
if CROSS(MACD,0)=1 then begin 
    平空:SELLSHORT(HOLDING<0,1,MARKET);
    开多:BUY(HOLDING=0,1,MARKET);
end

//平多开空条件
if CROSS(0,MACD)=1 then begin 
    平多:SELL(HOLDING>0,1,MARKET);
    开空:BUYSHORT(HOLDING=0,1,MARKET);
end

//止盈止损部分************************
//多头方向的盈损判断:最新行情大于持仓均价为盈利,反之亏损
if HOLDING>0 then BEGIN
	//多头止盈
	IF C-AVGENTERPRICE>50*MINDIFF THEN BEGIN
		多头止盈:SELL(1,HOLDING,MARKET);
	END
	
	//多头止损
	IF AVGENTERPRICE-C>20*MINDIFF THEN BEGIN
		多头止损:SELL(1,HOLDING,MARKET);
	END
END

//空头方向的盈损判断:最新行情小于持仓均价为盈利,反之亏损
if HOLDING<0 then BEGIN
	//空头止盈,
	IF AVGENTERPRICE-C>50*MINDIFF THEN BEGIN
		空头止盈:SELLSHORT(1,HOLDING,MARKET);
	END
	
	//空头止损
	IF C-AVGENTERPRICE>20*MINDIFF THEN BEGIN
		空头止损:SELLSHORT(1,HOLDING,MARKET);
	END
END
//************************************

持仓均价:AVGENTERPRICE;
当前持仓:HOLDING,COLORGRAY,LINETHICK0;
当前资产:ASSET,NOAXIS,COLORGRAY;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
  1. 后台策略
//技术指标部分
DIFF :=EMA(CLOSE,12) - EMA(CLOSE,26);
DEA  :=EMA(DIFF,9);
MACD :=2*(DIFF-DEA);

//程序化交易下单部分
//平空开多条件
if CROSS(MACD,0)=1 then begin 
    平空:TSELLSHORT(THOLDING<0,1,MKT);
    开多:TBUY(THOLDING=0,1,MKT);
end

//平多开空条件
if CROSS(0,MACD)=1 then begin 
    平多:TSELL(THOLDING>0,1,MKT);
    开空:TBUYSHORT(THOLDING=0,1,MKT);
end

//止盈止损部分************************
//多头方向的盈损判断:最新行情大于持仓均价为盈利,反之亏损
if THOLDING>0 then BEGIN
	//多头止盈
	IF C-TAVGENTERPRICE>50*MINDIFF THEN BEGIN
		多头止盈:TSELL(1,THOLDING,MKT);
	END
	
	//多头止损
	IF TAVGENTERPRICE-C>20*MINDIFF THEN BEGIN
		多头止损:TSELL(1,THOLDING,MKT);
	END
END

//空头方向的盈损判断:最新行情小于持仓均价为盈利,反之亏损
if THOLDING<0 then BEGIN
	//空头止盈,
	IF TAVGENTERPRICE-C>50*MINDIFF THEN BEGIN
		空头止盈:TSELLSHORT(1,THOLDING,MKT);
	END
	
	//空头止损
	IF C-TAVGENTERPRICE>20*MINDIFF THEN BEGIN
		空头止损:TSELLSHORT(1,THOLDING,MKT);
	END
END
//************************************

DEBUGFILE('D:\TEST.TXT','持仓均价='&NUMTOSTR(TAVGENTERPRICE,2) ,1);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

上述两个示例中,在第35行中使用了AVGENTERPRICE和TAVGENTERPRICE函数,作为持仓均价,AVGENTERPRICE是基于K线开盘价、收盘价得到的;而TAVGENTERPRICE是直接读取账户的实际持仓均价。因此将他们分别用于图表和后台止盈止损的因子项中,虽然策略整理逻辑一致,但是因为函数差异延伸成为止盈止损条件结果出现差异。函数差异的影响一般和策略复杂度成正比。
注:对于后台策略,在实际使用持仓均线函数建议使用TAVGENTERPRICEEX2

当程序化选择固定时间隔时,计算间隔的差异也会被放大。图表和后台是按照启动时进行计时轮询,因此在两者启动时存在时间差,其次因为执行效率的差异,也无法保证两者在运行时能够使用同一笔最新价参与计算,再加上操作系统对CPU资源的调度差异。多重因素结合后,差异性也可能会被放大。

本质上图表和后台的计算结果没有对错之分,后台策略的结果或者逻辑分析,只能通过调试函数进行跟踪处理。