错误、漏洞、问题 - 页 105

 
Interesting:

测试员有自己单独的工具清单,需要生成(最好是在初始化EA时完成)。

非常感谢您!明白了。错过了很多...
 
Renat:

代码是近似的(从两块复制的),但你的评论是正确的。

以下是更正后的版本。

我给它加了几个Print()的调用,以清楚它的实际工作情况。

double CalculateMaxVolume(string symbol)
  {
   double price=0.0;
   double margin=0.0;
//--- select lot size
   if(!SymbolInfoDouble(symbol,SYMBOL_ASK,price))                return(0.0);
   if(!OrderCalcMargin(ORDER_TYPE_BUY,symbol,1.0,price,margin)) return(0.0);
   if(margin<=0.0)                                            return(0.0);

   double lot_pure=AccountInfoDouble(ACCOUNT_FREEMARGIN)/margin;
   double lot=NormalizeDouble(lot_pure,2);
   Print("lot_pure = ", lot_pure, ", lot = ", lot);
//--- normalize and check limits
   double stepvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP);
   if(stepvol>0.0)
     {
      double newlot=stepvol*NormalizeDouble(lot/stepvol,0);
      if(newlot>lot) { Print("Чёрт побери: lot = ", lot, ", newlot = ", newlot);
                       lot=NormalizeDouble(newlot-stepvol,2);
                     }
      else           lot=newlot;
     }

   double minvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN);
   if(lot<minvol) lot=0.0;   // 

   double maxvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX);
   if(lot>maxvol) lot=maxvol;
//--- return trading volume
   return(lot);
  }

void OnStart()
{
  Print("CalculateMaxVolume(Symbol()) = ", CalculateMaxVolume(Symbol()));
}

/* Вывод в лог (хронология - сверху вниз)
CO      0       1 (EURUSD,M15)  01:40:33        lot_pure = 7.799703611262773, lot = 7.8
JG      0       1 (EURUSD,M15)  01:40:33        Чёрт побери: lot = 7.8, newlot = 7.800000000000001
MQ      0       1 (EURUSD,M15)  01:40:33        CalculateMaxVolume(Symbol()) = 7.7
*/

现在工作正常了。但要注意的是--在现在实际遇到的条件下。如果它们在未来被扩展,这个代码在某些情况下会开始犯错。我将在下面解释。

事实上,我不得不做一些研究,结果得到了非常有趣的结果。但是,让我们一个一个地谈一谈。

首先跳出来的是:我为什么要和NormalizeDouble()跳那么多的舞?那么,NormalizeDoubel()是做什么的?将一个自由值绑定到网格上。例如,在这个代码片段中。

double lot=NormalizeDouble(lot_pure,2);

NormalizeDouble()以lot_pure(自由值,即由自由保证金和1手的必要保证金计算出来的值,没有任何四舍五入和其他约束)为参数,给出一个值,与网格中最近的值绑定,起点为0,步长为0.01。
这里需要注意的是:到最近 的网格节点,包括较大的 节点!这一点很重要。

在代码的这个地方,这个约束是为了什么?为什么是0.01格,而不是0.001格?

顺便说一下,我们可以看到,在日志中标有CO的结果(所示片段的第一行)已经导致了数值的增加。

此外,我们知道,所有接受手数作为参数之一的交易函数,都要求该值与网格绑定:minvol + N * stepvol,其中N是一个从0到表达式(maxvol - minvol)/ stepvol的整数部分的值。因此,在这个片段中得到的自由手数值。

double lot_pure=AccountInfoDouble(ACCOUNT_FREEMARGIN)/margin;

这意味着你必须首先从lot的值中减去minvol,然后再除以stepvol(得到那个整数N),在乘以N后,再加上minvol。但你立即除以stepvol,隐含地假设stepvol是minvol的除数,即它适合整数倍,因为只有满足这个条件,你才能以这种方式 "简化 "而不产生副作用。

double newlot=stepvol*NormalizeDouble(lot/stepvol,0);

你再次使用NormalizeDouble(),这次是绑定到一个起始点为0、步长为1的网格,也就是绑定到整数。网格的绑定参数是正确的,但抓取工具有点不幸:它绑定了网格中最近的节点,包括一个更大的 节点,如果它刚好更近的话。而在我们的案例中,它将导致后续的强制性修正代码的工作。为什么不在这里使用一个奇妙的工具,即绑定到 "整数网格",而不是调用NormalizeDouble(),将其转换为整数类型,不增加 被转换的值,而只是在必要时将减少 到最接近的整数,即--我们需要的。

但这里出现了一个更有趣的文物,这在所引用的标有JG的片段的第二行中得到了证明。事实证明,"0.1 * NormalizeDouble(7.8 / 0.1) "表达式给出的结果是7.800000000000001,这导致纠正代码的工作!"。为什么你需要这样一个表现如此糟糕的代码,以至于你需要给它加上一个修正的代码?

显然,对可接受的地段价值的网格进行约束的准则必须被更好的准则所取代。

当然,我们也可以留下这段代码--毕竟,如果有的话,代码中的纠正部分会起作用。这里是日志的第三行,证明了这一点:结果在最后直接返回。但是,这个代码,在另一方面,是其创造者的专业性和质量的一个指标。包括MT5平台的代码质量。而这一点将由我来证明,因为我在研究中偶然发现了两个错误。

顺便说一下,让我们再看一下计算出的lot_pure值的初始绑定代码和修正代码。

double lot=NormalizeDouble(lot_pure,2);
...
lot=NormalizeDouble(newlot-stepvol,2);
在这两种情况下,都有一个步长为0.01的网格,为什么是这个网格?因为你想绑定到有stepvol的minvol + N * stepvol网格。如果将来我们既有最低手数,又有0.001的阶梯量,会发生什么?

这很简单--如果在与网格结合的过程中,步长为0.01的自由值变化超过0.001,代码会给出错误的结果。这就是我在开始时提到的注意事项。

如果 "四舍五入 "超过0.001,将返回自由保证金不足的手数,而在 "四舍五入 "的情况下,如果自由值在0.001-0.004999的范围内,则返回低值或0。

也就是说,这段代码包含了未来的潜在错误。这是一个关于开发人员专业性和他们的代码质量的问题。

现在,考虑到我所发现的情况,我将提出我自己的功能变体。

double CalculateMaxVolume_New(string symbol)
{
  double stepvol = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
  double minvol  = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
  double maxvol  = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);

  // Не доверяем значениям, которые вернули функции
  if(stepvol > 0 && minvol > 0 && maxvol > minvol)
  {
    double tmp = 0;

    // Вычисляем цену Ask, Margin на 1 лот, лотов на FreeMargin
    if(SymbolInfoDouble(symbol, SYMBOL_ASK, tmp)            && tmp > 0       &&
       OrderCalcMargin(ORDER_TYPE_BUY, symbol, 1, tmp, tmp) && tmp > 0       &&
       (tmp = AccountInfoDouble(ACCOUNT_FREEMARGIN) / tmp)         >= minvol &&
       tmp < ULONG_MAX * stepvol)
    {
      Print("pure_lot = ", tmp); // Эту строку нужно удалить из рабочего кода

      if(tmp > maxvol) // Здесь в tmp содержится недискретизированное число лотов
        return maxvol;

      // Привязываемся к сетке дискретизации
      return minvol + stepvol * (ulong)((tmp - minvol) / stepvol);
    }
  }

  return 0;
}

void OnStart()
{
  Print("CalculateMaxVolume_New(Symbol()) = ", CalculateMaxVolume_New(Symbol()));
}

/* Вывод в лог (хронология - сверху вниз)
LQ      0       1 (EURUSD,M15)  01:39:07        pure_lot = 7.799095304944626
KD      0       1 (EURUSD,M15)  01:39:07        CalculateMaxVolume_New(Symbol()) = 7.7
*/

有几种情况,与lot值(存储在我的tmp中)有关,经过计算但还没有绑定到有效值的网格中。我们把网格边界的价值称为离散化。

1.当tmp<minvol时的情况。在这种情况下,不等式在tmp离散化后也会保留,因为离散化过程只涉及计算值的减少(否则就没有足够的自由余量,因为计算值是给定自由余量的最大可能值)。

因此,这种情况可以在早期阶段,即在取样之前就被消除。

2.当tmp>maxvol时的情况。在这种情况下,限制不是自由保证金,而是交易功能所允许的最大手数。在这种情况下,只需返回maxvol的值。

为了简单地返回maxvol的值,不需要对tmp进行取样,所以这种情况也在取样代码之前被切断。

3.当minvol <= tmp <= maxvol时的情况。在这种情况下,有必要进行离散化,但离散化的数值仍然在这种情况下的不等式之内,也就是说,离散化之后不需要再进行任何修正。

采样代码简单而高效。

return minvol + stepvol * (ulong)((tmp - minvol) / stepvol);

这里,表达式"(tmp - minvol) / stepvol "计算的是同一个数字N(锚定网格参数),但有一个分数部分。由于这里满足不等式minvol<=tmp(情况3),因此保证了 计算值为非负值。然后,我们明确地将计算出的值转为一个ulong类型的值。它是ulong,因为它保证了 计算值是非负的。

在转换为整数类型的过程中,实数类型的小数部分被丢弃了。这不是四舍五入,而是舍弃小数部分,这保证了自由保证金所允许的最大手数值不会增加。这正是我们需要的。

既然得到了N的整数值,那么允许自由保证金的最大手数离散值就以标准方式得到(即手数离散值网格中的下一个整数将不允许自由保证金,而得到的N仍将允许自由保证金)。"minvol + stepvol * N"。

我想强调一个相当重要的问题。问题是,双数类型的数字的最大值可以达到约1.8e308,而长数类型的数字--只有约1.8e19(这只是一个方便的条目,常数1.8e19不是 长数类型的)。

如果"(tmp - minvol) / stepvol "表达式的值超过1.8e19怎么办?在这种情况下,在转换为ulong类型的过程中,将发生 "切割"--数值将是表达式数值的整数除以ULONG_MAX的剩余部分。

如果数字不是那么大,就MQL5而言,它看起来像 "X % ULONG_MAX",其中X是一个整数,等于"(tmp - minvol)/ stepvol"。

这不是一个典型的案例,但为什么要在代码中留下bug?此外,MQL5库函数是不可信的,它们可能会返回任何胡言乱语(我会给出证明)。

对于 "tmp / stepvol "表达式的值不符合1.8e19的情况,我们特意引入一个检查(if条件的最后一行)。

tmp < ULONG_MAX * stepvol

当然,你可以写 "tmp / stepvol <(double)ULONG_MAX",但首先,我尽量避免在没有明确需要的情况下进行除法运算;其次,我必须进行明确的类型转换,因为ULONG_MAX常数是ulong类型的。当比较操作数时,它们不会被隐含地投向更高的类型(至少在C/C++中是这样的),在第三种情况下--我不会在语言本身遇到一个好的错误,甚至在库函数中也不会遇到--这个错误不是DNA中的那个,而是字面上的MQL5的分子和原子。

比较操作的左边操作数是tmp,是双倍类型,右边操作数是 "ULONG_MAX * stepvol "表达式,也是双倍类型。

这个表达式有两个操作数,一个是 ulong 类型,另一个是 double 类型。根据隐式类型转换的规则,首先将低级类型的操作数转换为高级类型的操作数,执行操作,将结果转换为高级类型的操作数。double类型比ulong "老",这就是为什么ulong的ULONG_MAX值被隐含地投给了double,操作被执行,结果是double类型。

然而,这里有一个错误,顺便说一下,这个错误并不总是出现,而只是在某些情况下,包括这个情况,这个错误在于 "ULONG_MAX * stepvol "表达式的结果只是 stepvol 的值。

因此,我所显示的功能不工作,而且在MetaQuotes开发人员修复这个错误之前也不会工作。

为了现在开始使用这个函数,你应该利用这个错误的特殊性:如果你进行显式类型转换,它就会消失。

tmp < (double)ULONG_MAX * stepvol

回到描述的检查:它保证"tmp / stepvol "表达式的值不超过ULONG_MAX。但采样代码使用的表达式是"(tmp - minvol) / stepvol"。

这个表达式的值也不会超过ULONG_MAX,因为前面的检查确保 minvol>0和tmp>= minvol,即tmp - minvol < tmp。

因此,不超过ULOMG_MAX的保证 也适用于"(tmp - minvol)/ stepvol "的表达。

一般来说,专业人员和非专业人员之间的一个主要区别是,专业人员至少可以保证 一些东西,而非专业人员则...

我在另一篇文章 中对发现的两个bug进行了分解,同时澄清了MetaQuotes做了什么和没有做什么。

 

Для чего в этом месте кода выполняется эта привязка? И почему именно к сетке 0.01, а не к, скажем, 0.001?

在系统中,最小手数=0.01


注意事项。

  1. 你的初始条件minvol + N * stepvol并不能保证正确,你可以把minlot设置成不同的值,你的逻辑就会被破坏。
  2. 你不应该改用乌龙--你给自己制造了困难,然后写了一整页的想法。
  3. 你的代码中对tmp的替代太聪明了,我的版本在操作方面要清晰得多
 

我只为自己说话(但如果你看到自己的影子,你不是唯一的一个)。

在过去几个月的错误追逐中,我已经养成了习惯,首先将一个不工作的程序视为MetaTrader的一个错误。

为什么这么说,这只是一个经过良好测试的模式,如果有些东西不工作,那么它就是一个错误,让警钟长鸣。

例如:我发现了一个错误,向servicedesk 发送了请求,他们写了一个验证码,但没有任何结果。

我再次申请,在期待答案的过程中发现了自己的笨拙。

其结果是,我为当场分散人们的注意力而感到羞愧。

但分析信息的流向,我明白,大众的人,即使是聪明人也会受到从众心理的影响。

如果有bug,我会写一个bug,让Renat来整理我的代码,把矛头指向我的错误。

我明白,宽容不允许说:是的,你是个白痴,你的代码是歪的。

但你不能走那么远,再延长下去所有的工作人员MQ很快就会从事那种坐在别人的代码上哀伤地沉思 "但我们为什么需要这一切",而冠军即将到来,在那里和去真正的帐户都不远了。

我总结一下,我今天的座右铭是 "如果你要发布一个错误,请检查问题是否在你手中"。

Общайтесь с разработчиками через Сервисдеск!
Общайтесь с разработчиками через Сервисдеск!
  • www.mql5.com
Ваше сообщение сразу станет доступно нашим отделам тестирования, технической поддержки и разработчикам торговой платформы.
 

新建筑--新问题。专家,在314中编译后在306中工作正常(编译没有错误),但在测试器中却出现了问题。

2010.08.21 17:03:36 核心1断开连接
2010.08.21 17:03:36 核心1测试器因OnInit失败而停止运行
2010.08.21 17:03:36 核心 1 2010.01.04 00:00:00 违反访问权限读到0x0000000000000014
2010.08.21 17:03:36 Core 1 2010.01.04 00:00:00 Balance=10000.00 Equite=10000.00 Profit=0.00
2010.08.21 17:03:36 Core 1 2010。01.04 01.04 00:00:00 PriceChannel_multi_Ch_Timer专家顾问在2010.01.04 00:00开始在欧元兑美元上半年的图表上 工作。

在现实生活中,它也会卸货。看来,错误的来源是一条线

m_symbol[j].Name(TradeSymbols[i]);

用几行字取代它

string curSymbol=TradeSymbols[i];
m_symbol[j].Name(curSymbol);

返回到专家顾问的现状。

问题是什么?

顺便说一下,在上一个版本中编译的代码,在这个版本中也能正常工作。

 
Valmars:

怎么了?

顺便说一下,在上一个版本中编译的代码在这个版本中也能正常工作。

我们的错误--我们一定会纠正。
 
Renat:

最小手数=0.01


注意事项。

  1. 你的初始条件minvol + N * stepvol不能保证正确,你可以把minlot设置成不同的值,你的逻辑就会被破坏。
  2. 你不应该改用乌龙--你给自己制造了困难,然后写了一整页的想法。
  3. 你的代码中对tmp的替代太聪明了,而我的版本在操作上要清晰得多。

现在,系统的最小手数=0.01,但一年后呢?在两年内?

1)哪个条件是正确的?那么正确的公式是什么呢?例如,对于minvol=0.15和stepvol=0.1--前几个有效手数是多少? a) 0.15,0.25,0.35...? б) 0.15, 0.2, 0.3...? в) ...?我一直以为是选项A。

2.我改用ulong是有原因的--我有权选择范围最广的类型,这样就能满足最广泛的情况,因为这种功能是非常基本的砖。而且,我遇到了一个错误,并不意味着是我制造了问题。:)推理为他人写了更多的东西,以使其在尽可能广泛的范围内明确--我们这里不是个人通信。

3.替换并不棘手--只是保存,以免产生一次性使用的变量。并检查和验证了当一个函数 最多调用 一次时,变量是通过引用传递的,这样就可以避免因此而可能出现的错误。如果这让一些人感到困扰,他们可以像你所做的那样,为每个转让的价值(甚至是中间的价值,如Ask价格)创建一个变量。这一点并不重要。

更重要的是与可接受值的网格结合的机制,此外,它不需要修正,并保证在不同的非非常典型的情况下不会发生故障,同时保持最大可能的简单性。

其前提是,基本的建筑构件应该是尽可能的坚固和多功能--那么整个房子就有可能在地震中幸存下来。

 
Urain:

我只为自己说话(但如果你看到自己的影子,你不是唯一的一个)。

在过去几个月的bug竞赛中,我已经养成了一个习惯,即首先将MetaTrader的bug视为一个bug。

为什么这么说,只是一个既定的模式,如果某样东西不工作,那么它就是一个错误,让警钟长鸣。

MQL5和MQL5的错误看起来很像他们的事实在这里起了重要作用。而且有很多MQL5的错误。

如果MQL5的bug明显减少,而且它们不是那么简单,那就更难将它们混淆。

Urain

他们已经开始考虑启动冠军赛的可能性,现在已经到了他们开始研究真实交易账户 的时候了。

我总结一下,今天的座右铭是 "如果你要发布一个错误,请检查问题是否在你手中"。

只用MQL5编写的专家顾问锦标赛是一场赌博,这一事实在宣布决定时已经很清楚。但EA管理层有自己的愿景。他们自己已经决定了。没有人干涉他们的决定。所以,如果冠军就在眼前,他们为自己创造了一种生活,那又如何?

这里很简单:你需要做一些错误的本地化工作:开始从代码中删除所有不影响错误的东西。最后你会得到一种测试的例子,它足够小,但主要演示了这个错误。这将不再是 "别人的代码",而是 "演示错误MQL5的代码"。

 
写了一个脚本来测试OrderCalcMargin()函数
void OnStart()
  {
//---
   int total=SymbolsTotal(false);
   double marginbay;
   double marginsell;
   MqlTick last_tick;
   for(int i=0;i<total;i++)
     {

      string symbol=SymbolName(i,false);
      Print("************************************************");
      Print("Инструмент - ",symbol);
      Print("Валюта депозита = ",AccountInfoString(ACCOUNT_CURRENCY));
      Print("Базовая валюта = ",SymbolInfoString(symbol,SYMBOL_CURRENCY_BASE));
      Print("Валюта маржи = ",SymbolInfoString(symbol,SYMBOL_CURRENCY_MARGIN));
      if(SymbolInfoTick(symbol,last_tick))
        {
         OrderCalcMargin(ORDER_TYPE_BUY,symbol,1.0,last_tick.ask,marginbay);
         OrderCalcMargin(ORDER_TYPE_SELL,symbol,1.0,last_tick.bid,marginsell);
         Print("Маржа для покупки = ",marginbay);
         Print("Маржа для продажи = ",marginsell);
        }
      else Print("SymbolInfoTick() failed, error = ",GetLastError());
     }
  }
//+------------------------------------------------------------------+
该函数对某些仪器的返回值为零,这是一个错误还是设计成这样的?
 
sergey1294:
我写了一个脚本来检查OrderCalcMargin()函数。 该函数对某些符号返回0。

这可能是针对那些不在MarketWatch中的符号,因为SymbolName 被说成是针对SymbolName

符号名称

返回指定符号的名称。

stringSymbolName(
intpos,// 列表中的编号
bool selected// true - 只有MarketWatch中的符号
);

参数

姿势

[在]符号编号的顺序。

选定的

[in] 查询模式。如果为真,那么该符号将从MarketWatch的选定 列表中取出。如果该值为false,那么该符号将 从公共列表中 取出。

返回的值

带有符号名称的字符串类型的值。

打印得到意外结果的符号名称,并与MarketWatch中的列表进行比较。