TrailingStop と OrderModify error 1 の微妙な関係。

とある人からの問合せで、EA作成初心者がはまりやすいワナがあったので紹介します。
TrailingStop機能の実装で、以下のようなコードを書くと、OrderModify error 1が頻発するのですが、コードを見る限りおかしな所が無いように見えます。

for(i=0;i< OrdersTotal();i++){
   if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == true){
      if (OrderMagicNumber() == MAGIC){
         if(OrderType()==OP_BUY){
            if (
                  (TL>0) &&
                  (Bid-TL*Point>=OrderOpenPrice()) &&
                  (Bid-TL*Point>OrderStopLoss())
               )
OrderModify(OrderTicket(),OrderOpenPrice(),Bid-TL*Point,OrderTakeProfit(),0,Green);
         }
         if(OrderType()==OP_SELL){
            if (
                  (TL>0) &&
                  (Ask+TL*Point<=OrderOpenPrice()) &&
                  (Ask+TL*Point< OrderStopLoss())
               )
OrderModify(OrderTicket(),OrderOpenPrice(),Ask+TL*Point,OrderTakeProfit(),0,Green);
         }
      }
   }
}


OrderModify error 1 は、現在の OrderStopLoss() と同じ価格でもう一度 OrderModify しようとする時などに起きるエラーです。しかし、売りポジションの場合は、

Ask+TL*Point < OrderStopLoss()

という条件式があるので、同じ価格での OrderModify はしないはず...と読めます。


実は、この比較式に問題があって、MQL4内部の浮動小数点数丸め誤差によって

Ask+TL*Point = 1.4855000000000000
OrderStopLoss() = 1.4855000000000002

となっている場合に条件式が成立してしまいます。
(プログラムをデバッグしているときは、どちらの値も 1.4855 だと思い込みやすいので、非常に気づきにくいところです。)
このようなMQL4内部での最小桁の正確な値は、

#include

して、

Print( DoubleToStrMorePrecision(OrderStopLoss(),16) ," ", DoubleToStrMorePrecision(Ask+TL*Point,16) );

すると16桁までの値が分かります。



この問題に対処する為には、

Ask+TL*Point < OrderStopLoss()-0.5*Point

のように、あらかじめ誤差があることを前提に、誤差を無視できるような比較式に置き換えればOKです。
(NormalizeDoubleを活用しても良いのですが、誤差を無視できていることが直感的に分かりやすい式の方が初心者向けかなと...思います。。


同様に買いポジションの比較式も修正して

for(i=0;i< OrdersTotal();i++){
   if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == true){
      if (OrderMagicNumber() == MAGIC){
         if(OrderType()==OP_BUY){
            if (
                  (TL>0) &&
                  (Bid-TL*Point>=OrderOpenPrice()) &&
                  (Bid-TL*Point>OrderStopLoss()+0.5*Point)
               )
OrderModify(OrderTicket(),OrderOpenPrice(),Bid-TL*Point,OrderTakeProfit(),0,Green);
         }
         if(OrderType()==OP_SELL){
            if (
                  (TL>0) &&
                  (Ask+TL*Point<=OrderOpenPrice()) &&
                  ((Ask+TL*Point< OrderStopLoss()-0.5*Point) || OrderStopLoss()==0)
               )
OrderModify(OrderTicket(),OrderOpenPrice(),Ask+TL*Point,OrderTakeProfit(),0,Green);
         }
      }
   }
}

のようにすれば、実用上は問題無いと思います。



# 本題とは関係ありませんが、このままでは
# 売りポジションの初期StopLossが入っていないとトレールできないので、
# OrderStopLoss()==0 を追加しています。
#