Wednesday, February 24, 2021

关于重复开仓(平仓)问题的较完美解决方案

  接触TB没多久,就遇到了这个比较麻烦的问题,而我在MT4平台写EA(自动交易系统)的时候从来没有遇到过这样的问题。经过反复的测试和思考,我觉得这个问题可能是由于国内交易所对委托单的处理方式以及TB相关函数的返回方式造成的。

我先讲一下MT4平台委托单:在MT4平台中,有两种委托方式:即时成交与挂单交易,这是两种截然不同的方式。

即时方式按市场当时的卖出或买入价下单,如果由于网络延迟等问题造成无法成交,在手动操作的界面会弹出询问是否按新的价格成交的对话框供交易者选择;
挂单方式则严格按交易者的下单价格成交,并且需要提供有效期,一旦无法成交则在有效期内一直挂在市场中。

在即时成交委托方式下,MT4平台OrderSend函数,其返回值为-1或成交定单的编号,这个返回值是由交易服务器返回的,而非客户端自身返回的。因此,如果返回值>0,则在账户持仓中马上就会出现相关定单,而返回值为-1的话,则不会产生持仓。这样就非常容易解决重复开仓的问题。

而国内的期货(包括股票)交易所,只有一种委托方式,那就是挂单交易,一旦不能成交则会产生一个委托挂单。

而且TB平台的A_SendOrder以及Buy、Sell等函数是针对当前公式应用的帐户、商品发送委托单,发送成功返回True,发送失败返回False。这里的返回值是客户端自身返回的,我们无法根据这个返回值来判断是否成交。

因此,我们如果使用以下代码,则非常容易出现重复开平仓的问题。

  1. Begin
  2.         If ( MarketPosition==0  )
  3.         {
  4.                   //开仓条件检查;   
  5.                 //相关开仓指令(A_SendOrder以及Buy、SellShort等);
  6.         }
  7.   
  8.         If ( MarketPosition==1 && 平多仓条件 ) Sell(0,Price);
  9.    
  10.         If ( MarketPosition==-1 && 平空仓条件 ) BuyToCover(0,Price);
  11. End
复制代码
这是因为开仓指令并不从服务器取返回值,只要客户端发送成功就返回true值。而这时如果由于网络延迟等原因造成委托价格与当前价格偏离无法成交,或者已成交但是还未传回客户端,都会导致MarketPosition函数返回0值,从而造成重复发单。

关于交易助手,我仔细观察了一下,似乎交易助手也是根据MarketPosition函数的返回值来判断是否成交的。在网络延迟的情况下,经常会出现类似于“委托单已成交,无法撤单”的提示。因此,交易助手对因价格偏离而导致的挂单可以成功撤单,但是对由于网络延迟导致的成交信息滞后无法处理。还是会出现重复发单的情况。

既然MarketPosition函数无法判断持仓,那么如果使用GolbalVar来解决会怎么样呢?代码如下:
  1. Params
  2.         Numeric SetPos(0);

  3. Begin
  4.         If ( GetGlobalVar(0)==InvalidNumric )        SetGlobalVar(0,SetPos);       
  5.        
  6.         If ( GetGlobalVar(0)==0  )
  7.         {
  8.                   If ( 开多仓条件满足 )
  9.                 {
  10.                         A_SendOrder(...);
  11.                         SetGlobalVar(0,1);
  12.                 }
  13.                   If ( 开空仓条件满足 )
  14.                 {
  15.                         A_SendOrder(...);
  16.                         SetGlobalVar(0,-1);
  17.                 }
  18.         }
  19.   
  20.         If ( GetGlobalVar(0)==1 && 平多仓条件满足 )
  21.         {
  22.                 A_SendOrder(...);
  23.                 SetGlobalVar(0,0);
  24.         }
  25.    
  26.         If ( GetGlobalVar(0)==-1 && 平空仓条件满足 )
  27.         {
  28.                 A_SendOrder(...);
  29.                 SetGlobalVar(0,0);
  30.         }
  31. End
复制代码
以上代码中的SetPos参数是为了手动调整GlobalVar的初始值,否则如果由于一些意外的情况导致客户端重启或者是隔日交易,持仓位置就有可能会与实际情况不同。

由于我们这段代码使用GlobalVar的值来判断持仓情况,一旦交易指令A_SendOrder发出,相关全局变量就会被赋值。如果此时由于各种原因导致委托单没有成交,这就与实际持仓情况不同,给我们代码的执行会带来很大的麻烦。

要解决这个问题,我们必须保证一旦我们的交易指令发出,则必须成交!这一点非常重要!而要做到这一点,我们有两种办法:

办法一:使用交易助手,设置不成交即撤单与撤单成功后续重发委托来配合保证成交。我个人不推荐采用这个办法,因为,我不太清楚其“撤单成功”的真实含义,究竟是成功发出撤单指令还是指取得服务器的返回信息,如果是前者的话,则有可能会产生大量的重复委托,而这一点正是我们极力要避免的。

办法二:其实很简单,就是在发出交易指令的时候指定价格,该价格要偏移几个点以保证成交。也就是说,如果是发买入单则+几个点,如果是发卖出单则-几个点,开仓平仓一样。我自己的系统内使用如下代码:
  1.                         NewPrice=Q_AskPrice+ShiftUnit*MinMove*PriceScale;        //计算开仓价格
  2.                         A_SendOrder(Enum_Buy,Enum_Entry,Lots,NewPrice);
复制代码
其中,ShiftUnit为可调参数,Lots为计算的开仓手数,MinMove*PriceScale为TB函数,用来取得商品价格最小变动值。据LH斑竹解释,Q函数如果与Buy、Sell类函数配合使用可能会出问题,因此我这里使用了A函数。

采用GlobalVar的办法基本上可以解决重复发单的问题,但我个人还是不推荐使用,因为毕竟不是根据实际持仓情况来判断的。而且如果隔日交易的话,必须手动调整持仓位置,万一忘记了,会有麻烦。


经过以上的思考,我们现在知道了重复发单的问题所在,“由于网络延迟造成MarketPosition函数无法即时反映实际持仓情况”。因此,我们只要解决了这个问题,也就解决了重复发单的问题!

嘿嘿,解决的办法其实很简单,一个字:“等”!

因为我们只要“等”到客户端收到服务器返回的成交信息,MarketPosition类函数就会正确反映持仓情况了,请看以下代码:
  1. Params
  2.         Numeric WaitTime(10);        //预设等待时间,单位为秒,可调整
  3.         Numeric ShiftUnit(3);        //下单价格偏移量,可调整
  4. Vars
  5.         Numeric Lots;
  6.         Numeric NewPrice;
  7.         Numeric TimeSeconds;
  8.         Bool    Openning;
  9. Begin
  10.         If ( Q_Last == 0 || ( Date != Date[1] && High == Low ) )         Return;        //如果未开盘,则直接返回
  11.        
  12.         If ( GetGlobalVar(10)==InvalidNumric  )        SetGlobalVar(10,0);        //下单时间初始化
  13.        
  14.         TimeSeconds=Value(Left(TimeToString(CurrentTime),2))*3600                //记录系统当前时间,转化为秒数
  15.                                 +Value(Mid(TimeToString(CurrentTime),3,2))*60
  16.                                 +Value(Right(TimeToString(CurrentTime),2));
  17.        
  18.         If ( TimeSeconds-GetGlobalVar(10)<WaitTime )        Return;        //如果发单后等待时间小于WaitTime,则返回
  19.        
  20.         Openning = Q_Last > Q_LowLimit && Q_Last < Q_UpperLimit;        //停板情况下不允许建仓

  21.         If ( A_TotalPosition==0 && Openning==true )
  22.         {
  23.                 计算开仓手数Lots;
  24.                                   
  25.                 If ( 满足开多仓条件 )
  26.                 {
  27.                         NewPrice=Q_AskPrice+ShiftUnit*MinMove*PriceScale;        //计算开仓价格
  28.                         A_SendOrder(Enum_Buy,Enum_Entry,Lots,NewPrice);
  29.                         SetGlobalVar(10,TimeSeconds);                        //记录下单时间
  30.                         Return;
  31.                 }
  32.                                
  33.                 If ( 满足开空仓条件 )
  34.                 {
  35.                         NewPrice=Q_BidPrice-ShiftUnit*MinMove*PriceScale;
  36.                         A_SendOrder(Enum_Sell,Enum_Entry,Lots,NewPrice);
  37.                         SetGlobalVar(10,TimeSeconds);
  38.                 }
  39.                 Return;
  40.         }
  41.                
  42.         If ( A_BuyPosition>0 && 满足平多仓条件 )
  43.         {
  44.                 NewPrice=Q_BidPrice-ShiftUnit*MinMove*PriceScale;
  45.                 A_SendOrder(Enum_Sell,Enum_Exit,A_BuyPosition,NewPrice);
  46.                 SetGlobalVar(10,TimeSeconds);
  47.         }
  48.                                
  49.         If ( A_SellPosition>0 && 满足平空仓条件 )
  50.         {
  51.                 NewPrice=Q_AskPrice+ShiftUnit*MinMove*PriceScale;
  52.                 A_SendOrder(Enum_Buy,Enum_Exit,A_SellPosition,NewPrice);
  53.                 SetGlobalVar(10,TimeSeconds);
  54.         }
  55. End
复制代码
这是我正在模拟柜台上测试的代码,由于模拟柜台一直不是很正常,所以测试的时间有限,但已经基本解决了重复发单的问题。以下是几个需要说明的问题:
1、这个系统请配合使用交易助手的5秒延时撤单,撤单后续无操作。这个不是必须的,但是为了以防万一,还是设置一下比较好;
2、MinMove*PriceScale为TB函数,用来取得商品价格最小变动值;
3、为了配合使用A_SendOrder和Q函数,这里使用了同为A函数的A_TotalPosition、A_BuyPosition和A_SellPosition代替MarketPosition来判断持仓情况;
4、WaitTime可以根据实际网络情况来进行调整;
5、ShiftUnit可以根据开仓手数大小来调整,开仓手数如果比较大的话,尽量设的大一点;
6、最新加入了停板不允许建仓的限制,因为在停板情况下,建仓的单子不可能成交,会造成连续发单。

以上是一些心得体会,供各位TX参考,欢迎发表不同意见

[ 本帖最后由 troyhou 于 2010-6-18 08:24 编辑 ]
已有 3 人评分威望 金钱 收起 理由
yishengu + 1

z7c9 -4
aa
lh948 + 500 + 500

总评分: 威望 + 497  金钱 + 500   查看全部评分

 http://bbs.tb18.net/thread-8378-1-1.html

No comments: