第十五节 模块编写

上节课我们介绍了子函数,通过子函数我们可以将一些功能封装起来,直接调用,能实现特定功能的子函数我们称其为模块。

一款EA往往都是由许多个功能模块组合起来完成交易功能的,这些模块中最基本的模块有K线信号控制、开仓、平仓、挂单模块,这些模块的基础之上,有移动止损、离场、移动挂单、仓位计算模块等等,基础的模块每个EA用的都差不多,基础之上的模块根据策略的不同,模块的功能也不尽相同。这节课我们就先把K线信号控制、开仓、平仓、挂单模块实现封装,以便后面的EA编程直接调用。

1 K线信号控制

这个模块控制程序只在K线开盘的时候运行一次,这使得策略的回测相对来说更加准确和可靠,要实现则个功能很简单,只要判断K线的根数有没有增加就行了,如果K线增加了一根那说明新的K线开盘了。模块代码如下:

int bar=0;
int Barjudge()
{
if(Bars!=bar)
{
    	bar=Bars;
    	return(1);
 	}
 	else return(0);
}

如果这个函数返回了1,那么说明有新的K线开盘,采用这个模块的EA就可以通过模块的返回值来判断当下是否运行。注意以上的bar这个变量是个全局变量,需要在主函数前声明。

2开仓模块

要实现开仓当然非常简单,只要一个开仓函数就可以了,但是实际交易的时候可没有这么容易,我们会碰到两个问题。

第一个问题就是手数的问题,我们的EA计算出来的手数有可能会小于最小手数,也有可能会大于最大手数,当小于最小手数时,我们不做单,当大于最大手数时我们把单子拆开来分成几单来做,这一部分内容可以参考第五节课。

第二个问题就是开仓遇到的问题,比如说由于卡顿导致订单没有成交等等,此时我们需要立即再发送开单命令,直到成交为止。

根据以上问题我们把程序写出来,代码如下:

 int orderopen(string sym,string direction,double lot,double sl,double tp,int mag,string comment)
{
   int check; 
   double lot_min=MarketInfo(sym,MODE_MINLOT);
   double lot_max=MarketInfo(sym,MODE_MAXLOT);
   double lot_last=0;
   int err=0;
   int huadian=10;
   double amount;
   int i;
   if(lot<lot_min)
   {
      Print('lot is too small');
      return(1);
   }
   else if(lot>lot_max)
   {
      amount=MathCeil(lot/lot_max);
      lot_last=lot-(amount-1)*lot_max;
      for(i=(int)amount;i>0;i--)
		{
      	if (i!=1)
      	{
      	   if(direction=='BUY')
      	   {
      	      do
      	      {
check=OrderSend(sym,OP_BUY,lot_max,MarketInfo(sym,
MODE_ASK),huadian,sl,tp,comment,mag,0,clrBlue);
                  if(check==-1)
                  {
                     err=GetLastError();
                  }
                  else
                  {
                     break;
                  }
               }while(err==146 || err==135);
            }
            else if(direction=='SELL')
            {
      	      do
      	      {
check=OrderSend(sym,OP_SELL,lot_max,MarketInfo(sym,
MODE_BID),huadian,sl,tp,comment,mag,0,clrRed);
                  if(check==-1)
                  {
                     err=GetLastError();
                  }
                  else
                  {
                     break;
                  }
               }while(err==146 || err==135);
            }
      	}
      	else
      	{
      	   if(direction=='BUY')
      	   {
      	      do
      	      {
check=OrderSend(sym,OP_BUY,lot_last,MarketInfo(sym,
MODE_ASK),huadian,sl,tp,comment,mag,0,clrBlue);
                  if(check==-1)
                  {
                     err=GetLastError();
                  }
                  else
                  {
                     break;
                  }
               }while(err==146 || err==135);
            }
            else if(direction=='SELL')
            {
      	      do
      	      {
check=OrderSend(sym,OP_SELL,lot_last,MarketInfo(sym,
MODE_BID),huadian,sl,tp,comment,mag,0,clrRed);
                  if(check==-1)
                  {
                     err=GetLastError();
                  }
                  else
                  {
                     break;
                  }
               }while(err==146 || err==135);
            }
      	}
      }
   }
   else
   {
      if(direction=='BUY')
      {
      	do
      	{
check=OrderSend(sym,OP_BUY,lot,MarketInfo(sym,
MODE_BID),huadian,sl,tp,comment,mag,0,clrBlue);
            if(check==-1)
            {
               err=GetLastError();
            }
            else
            {
               break;
            }
         }while(err==146 || err==135);
      }
      if(direction=='SELL')
      {
      	do
      	{
check=OrderSend(sym,OP_SELL,lot,MarketInfo(sym,
MODE_BID),huadian,sl,tp,comment,mag,0,clrRed);
            if(check==-1)
            {
               err=GetLastError();
            }
            else
            {
               break;
            }
         }while(err==146 || err==135);
      }
   }
   return(0);
}	

以上程序中对于手数小于最小手数以及手数大于最大手数的情况的处理方式参照第五课,对于卡顿导致订单没有成交的问题,我们采用do while循环语句来解决,在程序中如果订单没有成交,那么OrderSend()函数会返回-1,我们将这个返回值赋给check这个变量,如果check等于-1那么检查错误的原因,然后将这个原因对应的编号赋值给err,如果err=146或者135那么程序会再循环一次直到check不等于-1跳出循环为止。

3平仓模块

平仓模块主要解决的是平台因为卡顿无法平仓的问题,思路和开仓模块一样,代码如下:

void ordercl(string sym,int ticket)
   {
      int i;
      bool result=false;
      double price;
      double lot;
      int err=0;
      int huadian=10;
      for(i=OrdersTotal()-1;i>=0;i--)
      {
         if(OrderSelect(i,SELECT_BY_POS))
         {
            if(OrderSymbol()==sym && OrderTicket()==ticket)
            {
               if(OrderType()==OP_SELL)
               {
                  lot=OrderLots();
                  price=MarketInfo(sym,MODE_ASK);
                  do
                  {
                     result=OrderClose(ticket,lot,price,huadian);
                     if(result==false)
                     {
                        err=GetLastError();
                     }
                  }while(err==146 || err==135);
               }
               else if(OrderType()==OP_BUY)
               {
                  lot=OrderLots();
                  price=MarketInfo(sym,MODE_BID);
                  do
                  {
                     result=OrderClose(ticket,lot,price,huadian);
                     if(result==false)
                     {
                        err=GetLastError();
                     }
                  }while(err==146 || err==135);
               }
               if(result==false)
               {
                  err=GetLastError();
                  Print('order close failed,err='+(string)err);
                  Print('Ticket Number='+(string)OrderTicket());
               }
            }
         }
      }
   }

以上平仓模块中我们需要输入的参数有需要平仓的订单对应的品种和订单号,实际上如果只根据订单号来平仓也是可以的,这里增加品种的识别是为了防止

4挂单模块

挂单模块一般来说不会存在延迟的问题,所以它只需要解决拆单的问题,但是需要解决价格错误的问题,比如说挂突破买单,当挂单的价格小于当前买价时,挂单是挂不了的,所以要排除这种价格错误的情况。代码如下:

int pendingorder(string sym,string direction,double price,double lot,double sl,
double tp,int mag,string comment)
{
   int check;
   double lot_min=MarketInfo(sym,MODE_MINLOT);
   double lot_max=MarketInfo(sym,MODE_MAXLOT);
   double lot_last=0;
   int huadian=10;
   double amount;
   int i;
   if(lot<lot_min)
   {
      Print('lot is too small');
      return(1);
   }
   if(direction=='BUYSTOP' && price<MarketInfo(sym,MODE_ASK)+
MarketInfo(sym,MODE_SPREAD)*MarketInfo(sym,MODE_POINT))
   {
      Print('Price is too low for BUYSTOP order');
      return(1);
   }
   else if(direction=='BUYLIMIT' && price>MarketInfo(sym,MODE_ASK)-
MarketInfo(sym,MODE_SPREAD)*MarketInfo(sym,MODE_POINT))
   {
      Print('Price is too high for BUYLIMIT order');
      return(1);
   }
   else if(direction=='SELLSTOP' && price>MarketInfo(sym,MODE_BID)-
MarketInfo(sym,MODE_SPREAD)*MarketInfo(sym,MODE_POINT))
   {
      Print('Price is too high for SELLSTOP order');
      return(1);
   }
   else if(direction=='SELLLIMIT' && price<MarketInfo(sym,MODE_BID)+
MarketInfo(sym,MODE_SPREAD)*MarketInfo(sym,MODE_POINT))
   {
      Print('Price is too low for SELLLIMIT order');
      return(1);
   }
   
   if(lot>lot_max)
   {
      amount=MathCeil(lot/lot_max);
      lot_last=lot-(amount-1)*lot_max;
      for(i=(int)amount;i>0;i--)
		{
      	if (i!=1)
      	{
      	    if(direction=='BUYSTOP')
      	     {
              check=OrderSend(sym,OP_BUYSTOP,lot_max,price,huadian,sl,tp,
comment,mag,0,clrBlue);
            }
            else if(direction=='BUYLIMIT')
            {
              check=OrderSend(sym,OP_BUYLIMIT,lot_max,price,huadian,sl,tp,
comment,mag,0,clrBlue);
            }
            else if(direction=='SELLSTOP')
            {
              check=OrderSend(sym,OP_SELLSTOP,lot_max,price,huadian,sl,tp,
comment,mag,0,clrRed);
            }
            else if(direction=='SELLLIMIT')
            {
              check=OrderSend(sym,OP_SELLLIMIT,lot_max,price,huadian,sl,tp,
comment,mag,0,clrRed);
            }
      	}
      	else
      	{
            if(direction=='BUYSTOP')
      	  	 {
              check=OrderSend(sym,OP_BUYSTOP,lot_last,price,huadian,sl,tp,
comment,mag,0,clrBlue);
            }
            else if(direction=='BUYLIMIT')
            {
              check=OrderSend(sym,OP_BUYLIMIT,lot_last,price,huadian,sl,tp,
comment,mag,0,clrBlue);
            }
            else if(direction=='SELLSTOP')
            {
              check=OrderSend(sym,OP_SELLSTOP,lot_last,price,huadian,sl,tp,
comment,mag,0,clrRed);
            }
            else if(direction=='SELLLIMIT')
            {
              check=OrderSend(sym,OP_SELLLIMIT,lot_last,price,huadian,sl,tp,
comment,mag,0,clrRed);
            }
      	  }
      }
   }
   else
   {
      if(direction=='BUYSTOP')
      {
           check=OrderSend(sym,OP_BUYSTOP,lot,price,huadian,sl,tp,
comment,mag,0,clrBlue);
      }
      else if(direction=='BUYLIMIT')
      {
           check=OrderSend(sym,OP_BUYLIMIT,lot,price,huadian,sl,tp,
comment,mag,0,clrBlue);
      }
      else if(direction=='SELLSTOP')
      {
           check=OrderSend(sym,OP_SELLSTOP,lot,price,huadian,sl,tp,
comment,mag,0,clrRed);
      }
      else if(direction=='SELLLIMIT')
      {
           check=OrderSend(sym,OP_SELLLIMIT,lot,price,huadian,sl,tp,
comment,mag,0,clrRed);
      }
   }
   return(0);
}

以上代码中,我们对输入的一些错误进行了处理,输出了问题以备交易者参考,接下来我们将大订单拆开来,分解成了小单子再根据挂单的种类挂上去,虽然程序看起来有点多,实际上都是对挂单类型的识别,原理比较简单。

那么以上四个模块就是我们以后可以使用的通用模块了,接下来的课会讲解几个EA的编程,在这个过程中我们会反复用到这几个模块,以上内容希望大家好好理解和掌握。