请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化策略 帖子:3364712 新帖:33

MQL4程序的常见错误以及如何避免它们

外汇老老法师发表于:4 月 17 日 19:16回复(1)

介绍

一些较旧的程序可能在新版本的MQL4编译器中返回错误。

为了避免关键的程序完成,以前版本的编译器在运行环境中处理了许多错误。例如,除数为零或数组越界都是严重错误,并通常会导致应用程序崩溃。这些错误只在一些状态下针对某些变量值而发生。阅读这篇文章了解如何处理这样的情况。

新的编译器可以检测实际或潜在的错误源并提高代码质量。

在这篇文章中,我们讨论了旧程序编译过程中检测到的可能出现的错误,以及解决这些问题的方法。

  1. 编译错误
    • 1.1. 与关键字一致的标识
    • 1.2. 变量和函数名的特殊字符
    • 1.3. 使用switch操作符的错误
    • 1.4. 函数返回值
    • 1.5. 函数参数数组
  2. 运行时间错误
    • 2.1. 数组越界
    • 2.2. 除数为零
    • 2.3. 当前字符用0替代NULL
    • 2.4. Unicode格式字符串和它们在DLL中的使用
    • 2.5. 文件共享
    • 2.6. 日期时间转换
  3. 编译器警告
    • 3.1. 全局和局部变量名称一致
    • 3.2. 类型不匹配
    • 3.3. 未使用的变量

1编译错误

如果程序代码中包含错误,则它不能被编译。

要完全控制所有的错误,建议使用严谨的编译模式,它通过以下指令来设置:

#property strict

这种模式大大简化了故障排除。


1.1. 与关键字一致的标识

如果变量或函数的名称与其中一个关键字一致

int char[];  // incorrect
int char1[]; // correct
int char()   // incorrect
{
 return(0);
}

编译器会返回一个错误信息:

图1. 错误“unexpected token(非预期标记)”和“name expected(预期名称)”

图1. 错误“unexpected token(非预期标记)”和“name expected(预期名称)”

要解决这个错误,您需要使用变量或函数的正确名称。


1.2. 变量和函数名的特殊字符

如果变量或函数名称中包含特殊字符($,@,点):

int $var1; // incorrect
int @var2; // incorrect 
int var.3; // incorrect
void f@()  // incorrect
{
 return;
}

编译器会返回一个错误信息:

图2. 错误“unknown symbol(未知交易品种)”与“semicolon expected(预期分号)”

图2. 错误“unknown symbol(未知交易品种)”与“semicolon expected(预期分号)”

要解决这个错误,您需要使用正确的函数或变量名。


1.3. 使用switch操作符的错误

在旧版本的编译器中,您可以在switch操作符的表达式和常量中使用任何值:

void start()
  {
   double n=3.14;
   switch(n)
     {
      case 3.14: Print("Pi");break;
      case 2.7: Print("E");break;
     }
  }

在新的编译器中,switch操作符的常量和表达式必须是整数,所以当您尝试使用这样的结构时会发生错误:

图3. 错误“illegal switch expression type(非法switch表达式类型)”和“constant expression is not integral(常量表​​达式不是整数)”

图3. 错误“illegal switch expression type(非法switch表达式类型)”和“constant expression is not integral(常量表​​达式不是整数)”

在这种情况下,您可以使用明确的数值比较,例如:

void start()
  {
   double n=3.14;
   if(n==3.14) Print("Pi");
   else
      if(n==2.7) Print("E");
  }

1.4. 函数返回值

除了空值外的所有函数都应该返回声明的类型值。例如:

int function()
{
}

在严谨的编译模式下发生错误:


图4. 错误“not all control paths return a value(并非所有的控制路径返回一个值)”

图4. 错误“not all control paths return a value(并非所有的控制路径返回一个值)”

在默认的编译模式下,编译器会返回一个警告:

图5. 警告:“not all control paths return a value(并非所有的控制路径返回一个值)”

图5. 警告:“not all control paths return a value(并非所有的控制路径返回一个值)”

如果函数的返回值与声明的不匹配:

int init()                         
  {
   return;                          
  }

在严格的编译中会检测错误:

图6. 错误“function must return a value(函数必须返回一个值)”

图6. 错误“function must return a value(函数必须返回一个值)”

在默认的编译模式下,编译器会返回一个警告:

图7. 警告 'return - function must return a value(回报 - 函数必须返回一个值)”

图7. 警告 'return - function must return a value(回报 - 函数必须返回一个值)”

要解决这样的错误,添加带有相应类型返回值的return操作符到函数代码。



1.5. 函数参数数组

在函数参数,数组现在只引用传递。

double ArrayAverage(double a[])
{
 return(0);
}
在严谨的编译模式下,该代码将导致错误:

图8. 编译器错误“arrays passed by reference only(数组只引用传递)”

图8. 编译器错误“arrays passed by reference only(数组只引用传递)”

在默认的编译模式下,编译器会返回一个警告:

图9. 编译器警告“arrays passed by reference only(数组只引用传递)”

图9. 编译器警告“arrays passed by reference only(数组只引用传递)”

要修复此错误,您必须通过在数组名称之前添加前缀来指定数组是通过引用传递的:

double ArrayAverage(double &a[])
{
 return(0);
}

但应注意的是,现在常量数组 (Time[], Open[], High[], Low[], Close[], Volume[]) 不能引用传递。例如,下面的调用:

ArrayAverage(Open);

无论何种编译模式都会导致错误:

图10. 错误'Open' - constant variable cannot be passed as reference(‘打开’ - 常量变量不能引用传递)

图10. 错误'Open' - constant variable cannot be passed as reference(‘打开’ - 常量变量不能引用传递)

为了避免这些错误,从常量数组​​复制所需的数据:

   //--- an array that stores open price values
   double OpenPrices[];
   //--- copy the values of open prices to the OpenPrices[] array
   ArrayCopy(OpenPrices,Open,0,0,WHOLE_ARRAY);
   //--- function call
   ArrayAverage(OpenPrices);



2. 运行时间错误

程序代码执行过程中出现的错误称为运行时间错误。这种错误通常是依赖于程序的状态,并与变量的不正确的值相关联。

例如,如果变量用作数组元素的索引,其负值将不可避免地导致数组超出范围的错误。


2.1. 数组超出范围

访问指标缓冲区时常常在指标中发生这个错误。该IndicatorCounted()函数返回自上次指标调用的不变的柱数。先前计算的柱的指标值不需要重新计算,所以为了更快的计算,您只需要处理最后的几个柱。

大部分使用这种计算优化的方法的指标看起来如下:

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()
  {
   //--- some calculations require no less than N bars (e.g. 100)      
   if (Bars<100) // if less bars are available on a chart (for example on MN timeframe)    
     return(-1); // stop calculation and exit

   //--- the number of bars that have not changed since the last indicator call
   int counted_bars=IndicatorCounted();
   //--- exit if an error has occurred
   if(counted_bars<0) return(-1);
      
   //--- position of the bar from which calculation in the loop starts
   int limit=Bars-counted_bars;

   //--- if counted_bars=0, reduce the starting position in the loop by 1,   
   if(counted_bars==0) 
     {
      limit--;  // to avoid the array out of range problem when counted_bars==0
      //--- we use a shift of 10 bars back in history, so add this shift during the first calculation
      limit-=10;
     }
   else //--- the indicator has been already calculated, counted_bars>0
     {     
      //--- for repeated calls increase limit by 1 to update the indicator values for the last bar
      limit++;
     } 
   //--- the main calculation loop
   for (int i=limit; i>0; i--)
   {
     Buff1[i]=0.5*(Open[i+5]+Close[i+10]) // values of bars 5 and 10 bars deeper to history are used
   }
}

通常counted_bars==0的情况处理不当(初始限制持仓应该通过等于相对循环变量的1 +最大指数的值来降低)。

另外,请记住,在执行start()函数的时候,我们可以从0到Bars ()-1的访问指标缓冲区的数组元素。如果您需要使用无指标缓冲区的数组,那么按照指标缓冲区的当前大小使用ArrayResize()函数来增加其大小。也可以通过调用用作参数的指标缓冲区的 ArraySize()来获得元素地址的最大指数。


2.2. 除数为零

当除法运算中除数为零时则会发生零除的错误:

void OnStart()
  {
//---
   int a=0, b=0,c;
   c=a/b;
   Print("c=",c);
  }

当您运行这个脚本时,专家选项卡会出现一条错误的消息,并且程序关闭:

图11. 错误消息“zero divide(除数为零)”

图11. 错误消息“zero divide(除数为零)”

当除数的值由任何外部数据值来决定时,通常会出现此错误。例如,如果交易参数进行分析,如果没有新建订单,那么已用预付款的值等于0。另一个例子:如果要从一个文件读取分析数据,如果该文件不可用,那么我们也不能保证正确的操作。所以您应该考虑到这样的情况并正确地处理它们。

最简单的方法是除法运算前检查除数并报告不正确的参数值:

void OnStart()
  {
//---
   int a=0, b=0,c;
   if(b!=0) {c=a/b; Print(c);}
   else {Print("Error: b=0"); return; };
  }

这不会导致严重的错误,但是不正确参数值的消息一出现则程序即关闭:

图12. 不正确的除数消息

图12. 不正确的除数消息


2.3. 当前字符用0替代NULL

在旧版本的编译器中0(零)可用作满足金融工具规范的函数参数。

例如,当前交易品种的移动平均线技术指标的值可能被要求如下:

AlligatorJawsBuffer[i]=iMA(0,0,13,8,MODE_SMMA,PRICE_MEDIAN,i);    // incorrect

在新的编译器中您应该明确地指定NULL来规定当前的交易品种:

AlligatorJawsBuffer[i]=iMA(NULL,0,13,8,MODE_SMMA,PRICE_MEDIAN,i); // correct

此外,当前交易品种和图表周期可使用Symbol()和Period()函数来指定。

AlligatorJawsBuffer[i]=iMA(Symbol(),Period(),13,8,MODE_SMMA,PRICE_MEDIAN,i); // correct


2.4. Unicode字符串和它们在DLL中的使用

字符串现在表示为Unicode字符序列。

记住这一点,并使用适当的Windows函数。例如,使用wininet.dll库来替代 InternetOpenA() 和InternetOpenUrlA(),您应该调用InternetOpenW() 和InternetOpenUrlW()。

该字符串的内部结构在MQL4中(现在只需要12个字节)发生了变化,当传递字符串到DLL时应使用MqlString结构:

#pragma pack(push,1)
struct MqlString
  {
   int      size;       // 32 bit integer, contains the size of the buffer allocated for the string
   LPWSTR   buffer;     // 32 bit address of the buffer that contains the string
   int      reserved;   // 32 bit integer, reserved, do not use
  };
#pragma pack(pop,1)


2.5. 文件共享

在新MQL4中,FILE_SHARE_WRITE和FILE_SHARE_READ标志应明确地指定以便打开文件时共享使用。

如果标志不存在,那么该文件以单独模式打开,直到文件由打开它的用户关闭才可以被其他人打开。

例如,使用离线图表时共享标志应明确指定:

   // 1-st change - add share flags
   ExtHandle=FileOpenHistory(c_symbol+i_period+".hst",FILE_BIN|FILE_WRITE|FILE_SHARE_WRITE|FILE_SHARE_READ);

欲了解更多详情,请阅读新MQL4的离线图表


2.6. 日期时间转换

转换日期时间为一个字符串现在取决于编译模式:

  datetime date=D'2014.03.05 15:46:58';
  string str="mydate="+date;
//--- str="mydate=1394034418" - old compiler, no directive #property strict in the new compiler
//--- str="mydate=2014.03.05 15:46:58" - new compiler with the directive #property strict

例如,尝试使用文件名中包含冒号的文件会导致错误。


3. 编译器警告

编译器警告是信息性而非错误的讯息,但它们指出了可能的错误来源。

一个清晰的代码不应该包含警告。


3.1. 全局和局部变量名称一致

如果全局和局部各级变量具有相似的名称:

int i; // a global variable
void OnStart()
  {
//---
   int i=0,j=0; // local variables
   for (i=0; i<5; i++) {j+=i;}
   PrintFormat("i=%d, j=%d",i,j);
  }

编译器会显示指出全局变量的声明行号的警告:

图13. 警告“declaration of '%' hides global declaration at line %(声明'%'隐藏在行%的全局声明)”

图13. 警告“declaration of '%' hides global declaration at line %(声明'%'隐藏在行%的全局声明)”

要解决这样的警告需要修正全局变量的名称。


3.2. 类型不匹配

新的编译器有一个新的类型转换操作。

#property strict
void OnStart()
  {
   double a=7;
   float b=a;
   int c=b;
   string str=c;
   Print(c);
  }

在严谨的编译模式下如果类型不匹配则编译器会显示警告:

Figure 14. Warnings "possible loss of data due to type conversion" and "implicit conversion from 'number' to 'string'

图14. 警告“possible loss of data due to type conversion(由于类型转换可能丢失数据)”和“implicit conversion from 'number' to 'string'(从'数字'到'字符串'的隐式转换)”

在这个示例中,编译器警告关于分配的不同数据类型的可能的精确度损失和从整数到字符串的隐式转换。

要解决此警告需要使用明确的类型转换:

#property strict
void OnStart()
  {
   double a=7;
   float b=(float)a;
   int c=(int)b;
   string str=(string)c;
   Print(c);
  }

3.3. 未使用的变量

程序代码中存在不使用的变量(多余的实体)不是一个好习惯。

void OnStart()
  {
   int i,j=10,k,l,m,n2=1;
   for(i=0; i<5; i++) {j+=i;}
  }

无论何种编译模式都会显示这些变量的报告:

图15. 警告“variable '%' not used('%'变量未使用)”

图15. 警告“variable '%' not used('%'变量未使用)”

要修复它,需要从代码中移除未使用的变量。


结论

本文描述了包含错误的旧程序的编译过程中可能出现的常见问题。

在所有情况下,建议使用严谨的编译模式来调试程序。


p align="center"// stop calculation and exit br/pnbsp;span class="keyword"

全部回复

0/140

达人推荐

量化课程

    移动端课程