エラー、バグ、質問 - ページ 105

 
Interesting:

テスターはそれ自身の独立したツールのリストを持っており、生成する必要があります(できればエキスパートを初期化するときに行う)。

ありがとうございました。了解です。たくさん見逃した...
 
Renat:

コードはおおよそのもの(2枚からコピーしたもの)でしたが、コメントはその通りです。

以下は、その修正版です。

実際にどのように動作するのかを明確にするために、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と書かれた結果(図の断片の1行目)は、値が大きくなっていることがわかります。

さらに、ロット数をパラメータとして受け取るすべての取引関数では、この値がグリッドにバインドされていることが必要であることを知っています:minvol + N * stepvol、ここでNは0から式(maxvol - minvol) / stepvolの整数部の値までの整数です。従って、このフラグメントで得られたフリーロット値。

double lot_pure=AccountInfoDouble(ACCOUNT_FREEMARGIN)/margin;

は、指定されたグリッドの最も近いノードにバインドされなければならない:minvol + N * stepvol。 これは、まずstepvolで割る前にlotの値からminvolを引き(整数Nを得る)、Nを掛けた後にminvolを追加する必要があることを意味している。しかし、stepvolがminvolの約数であること、つまり整数倍に収まることを暗黙の前提として、すぐにstepvolで割る。この条件が満たされて初めて、この方法で「単純化」でき、副作用が出ないからだ。

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

再び NormalizeDouble() を使って、今度は開始が 0 でステップが 1 のグリッド、つまり整数にバインドします。メッシュバインドパラメータは正しいのですが、スナップツールは少し残念なことに、最も近いメッシュノードにバインドされ、それがたまたま近ければより大きな メッシュノードも含まれます。そして、私たちの場合は、その後の修正コードの強制的な作成につながります。NormalizeDouble()を呼び出す代わりに、整数型へのキャストを用いて「整数のグリッド」にバインドするという素晴らしいツールを使ってみてはいかがでしょうか。

しかし、ここでもう一つ興味深い人工物が発生し、それはJGと記された引用断片の2行目で証明されている。0.1 * NormalizeDouble(7.8 / 0.1)" 式が 7.800000000000001 という結果になり、修正コードが動作することがわかりましたなぜ、補正用のものを追加する必要があるほど、性能の悪いコードが必要なのでしょうか?

明らかに、許容ロット値のグリッドへのバインドコードは、より良いものに置き換えられなければならない。

もちろん、このコードも残しておくことができます。結局のところ、修正する部分、何かあればうまくいくのです。ここで、ログの3行目がそれを証明しています。結果は、最後の最後で右側に戻っています。しかし、このコードは一方で、作り手のプロ意識とクオリティを示す指標でもあります。MT5プラットフォームのコードの品質も含めて。その証拠に、私は研究の結果、2つのバグに行き当たりましたから。

ところで、算出された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
*/

ロット値(私の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".

ここでひとつ、かなり重要な点を強調したいと思います。double型の数値は約1.8e308まで最大値を取ることができますが、ulong型の数値は約1.8e19までしか取れません(これは便宜上の記述で、定数1.8e19そのものは ulong型ではありません)ということです。

(tmp - minvol) / stepvol」式の値が1.8e19を超えたらどうするのですか?この場合、ulong型への変換時に「切り」が発生し、式の値をULONG_MAXで整数分割した余りが値となる。

もし数値がそれほど大きくなければ、MQL5でいえば「X % ULONG_MAX」のようになり、Xは「(tmp - minvol) / stepvol」に等しい整数になります。

これは典型的なケースではないですが、なぜコードにバグを残すのでしょうか?しかも、MQL5のライブラリの関数は信用できない、どんな無意味なものを返すかもしれない(その証拠に、私はそう言っている)。

tmp / stepvol "式の値が1.8e19に収まらない場合のために、あえてチェックを入れています(if条件の最終行)。

tmp < ULONG_MAX * stepvol

もちろん、「tmp / stepvol <」と書いてもOKです。(double)ULONG_MAX "ですが、第一に、明示的な必要性がない場合は除算を避けるようにしていますし、第二に、定数ULONG_MAXはulong型なので、明示的に型変換を行わなければならないからです。オペランドを比較するとき、暗黙のうちに上位の型にキャストされることはありません(少なくともC/C++ではそうです)。そして3つ目は、-言語そのものではなく、ライブラリ関数でさえも、いいバグに遭遇しなかったでしょう-バグはDNAのそれではなく、文字通りMQL5の分子と原子の中にあるのです。

比較演算の左オペランドはtmpでdouble型、右オペランドはULONG_MAX * stepvol式でこれもdouble型です。

この式には2つのオペランドがあり、1つはulong型、もう1つは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」の式にも適用される。

一般に、プロと素人の大きな違いは、プロは少なくとも何かを保証 できるのに対して、素人は...。

私は他の投稿で 見つかった両方のバグを分解し、同時にMetaQuotesが何を行い、何を行わなかったかを明確にしました。

 

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

システムにおいて、最小ロット=0.01


注意事項

  1. 初期条件minvol + N * stepvolは正しいことを保証するものではなく、minlotに違う値を設定するとロジックが破綻します。
  2. 無駄にulongに乗り換えるべきでしたね。自分で困難を作り出しておいて、その感想を1ページまるまる書いてしまうなんて
  3. あなたのコードでtmpを代用するのはあまりに巧妙で、私のバージョンは操作の面でより明確になっています。
 

自分のことだけを話す(でも、自分の姿が映っていれば)

この数ヶ月のバグ取りレースで、私はMetaTraderのバグをそもそもバグと考える癖がつきました。

なぜかというと、よくテストされたパターンで、何かうまくいかないことがあれば、それはバグであり、警鐘を鳴らせばいいのです。

例:バグを発見し、servicedeskにリクエストを 送ったところ、検証コードを書いてくれたが、何もなかった。

もう一度応募して、答えを期待していたら、自分の不器用さがわかった。

その結果、その場で人の気を引いてしまったことが恥ずかしくなってしまったのです。

しかし、メッセージの流れを分析すると、たとえ賢い人であっても、大衆の心理に左右されることは理解できます。

もしバグがあれば、バグを書き、Renatに私のコードを整理させ、私のミスに指をさすようにします。

寛容であれば、「お前はバカだ、コードが曲がっている」と言うことができないことは理解しています。

しかし、あなたはそこまで行くことはできませんし、さらにそれを長引かせるすべてのスタッフMQはすぐにチャンピオンシップが来ている間、悲痛な思索に他の人のコードに座っていることに従事する "しかし、なぜ我々はそれをすべて必要とする"、とそこに行くと本当のアカウントは遠くないです。

最後になりますが、今日の私のモットーは「バグを公表するなら、その問題が自分の手元にあるかどうかをチェックする」です。

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

新築-新しい問題314でコンパイルした後、306で問題なく動作していたExpertが、テスターでエラーが出てしまう(コンパイルがエラーにならない)。

2010.08.21 17:03:36 Core 1 disconnected
2010.08.21 17:03:36 Core 1 tester stopped because OnInit failed
2010.08.21 17:03:36 Core 1 2010.01.04 00:00:00 0x000000000014 へのアクセス違反の読み取り
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.08.21 17:03:36 Core 1 2010.01.04 01.04 00:00:00 PriceChannel_multi_Ch_Timer Expert Advisorは2010年01月04日00:00にEURUSDチャートで H1期間の動作を開始しました。

また、実生活でもアンロードします。エラーの原因は行にあるようでした

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

数行で置き換える

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

はExpert Advisorに現状を戻しました。

何が問題なのか?

ちなみに、前回のビルドでコンパイルしたコードは、今回のビルドでも問題なく動作します。

 
Valmars:

どうしたんだ?

ちなみに、前回のビルドでコンパイルしたコードは、今回のビルドでも問題なく動作します。

私たちのミスです。必ず修正します。
 
Renat:

最小ロット=0.01


注意事項

  1. 初期条件minvol + N * stepvolは正しいことを保証するものではなく、minlotに違う値を設定するとロジックが破綻します。
  2. ulongに切り替えるべきではありませんでした。自分で困難を作り出しておいて、その感想を1ページまるまる書いているのですから。
  3. あなたのコードではtmpの代入がスマートすぎますが、私のバージョンはもっと明確な操作になっています。

今はシステムの最小ロット=0.01ですが、1年後はどうでしょう?2年後?

1) どの条件が正しいか?では、正しい計算式は何でしょうか?例えば、minvol = 0.15 と stepvol = 0.1 の場合、最初の数個の有効なロット値は何でしょうか?? б) 0.15, 0.2, 0.3...? в) ...?私はこれまで、オプションaだと思い込んでいました。

2.私がulongに切り替えた理由は、このような機能は非常に基本的なレンガなので、できるだけ幅広いケースに対応できるように、一番幅の広いタイプを選ぶ権利があるからです。また、バグに遭遇したからといって、その問題を作ったのが私であるとは限りません。:)この文章は、私信ではなく、他の人のために書いたものです。

3.置換は厄介なことではなく、一回しか使えない変数を作らないように、保存するだけです。そして、関数が 最大1回呼び出さ れたときに、その変数が参照渡しされることを確認・検証し、そのために起こりうるエラーを回避するようにしました。もし、それが気になる人がいれば、あなたが行ったように、転送された値(Ask価格などの中間値でも)ごとに変数を作成することができます。この点は重要ではありません。

さらに重要なのは、許容値のグリッドに拘束されるメカニズムで、しかも補正を必要とせず、可能な限りシンプルさを保ちながら、あまり典型的ではないさまざまなケースでの不具合の発生を保証するものである。

その前提は、基本的な構成要素が可能な限り頑丈で汎用性の高いものであること、そうすれば家全体が地震にも耐えられるであろうということです。

 
Urain:

自分のことだけを話す(でも、自分の姿が映っていれば)

この数ヶ月のバグ取りレースで、私はMetaTraderのバグをそもそもバグと考える癖がつきました。

なぜかというと、うまくいかないものはバグであり、警鐘を鳴らすという、よくあるパターンだからです。

MQL5とMQL5のバグがよく似ていることが、ここで重要な役割を担っているのです。MQL5のバグも多いしね。

もしMQL5のバグが大幅に減って、それらがシンプルでなければ、もっと混乱しにくくなるはずです。

ウラン です。

彼らはすでにチャンピオンシップを始める可能性について考え始めており、実際の取引口座で 作業を始める時期が来ている。

今日のモットーは「バグを公表するなら、その問題が自分の手元にあるかどうかをチェックする」です。

MQL5で書かれたExpert Advisorの選手権がギャンブルであることは、この決定が発表された時点で明らかであった。しかし、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()関数をチェックするスクリプトを書きました。 この関数は、いくつかのシンボルに対してゼロを返します。

これは、SymbolNameがSymbolNameの ためと言われているように、MarketWatchにないシンボルのためでしょう。

シンボル名

指定されたシンボルの名前を返します。

文字列SymbolName(
intpos,// リスト内の番号
bool selected// true - MarketWatchのシンボルのみ
);

パラメータ

ポーズ

[in] シンボル番号順。

せんたんてき

[in] クエリモード。trueの 場合、MarketWatchで選択された リストからシンボルが取得されます。この値がfalse の場合、シンボルは 共通リストより 取得される。

戻り値

シンボル名を持つ文字列型の値。

予期せぬ結果を得たシンボル名を印刷し、MarketWatchのリストと比較してください。