コンテンツにスキップ

【検証#10】OrderModifyエラー1の正体

この記事の3行まとめ

  • 🚨 OrderModify error 1「同じ値で変更しようとした」エラー
  • 💻 原因は浮動小数点誤差で「同じ」と判定されてしまうこと
  • ✅ 対策:0.5*_Pointのシフトまたは事前の一致チェック

はじめに

この記事は、「とあるMetaTraderの備忘秘録」様のブログ記事を検証・紹介するシリーズの第10弾です。

トレーリングストップを実装したEAで頻発する「error 1」の正体を解明します。

元ネタ

TrailingStop と OrderModify error 1 の微妙な関係。
https://fai-fx.hatenadiary.org/entry/20091122/1258893252
(2009年11月22日 公開)

時代背景に関する注意

この記事の元となるブログ記事は2009年頃に公開されたものです。
MQL5では OrderModify の代わりに PositionModify などの関数を使用しますが、浮動小数点誤差による判定の問題は共通です。


問題提起

よくあるエラーログ

Text Only
2024.01.15 10:30:45   OrderModify error 1
2024.01.15 10:30:47   OrderModify error 1
2024.01.15 10:30:49   OrderModify error 1

error 1 は「不明なエラー」という意味ですが、実際には明確な原因があります。


エラー1の正体

公式ドキュメント

ERR_NO_RESULT (1): No error returned, but the result is unknown

実際の原因

「変更前と変更後の値が同じ」 場合にこのエラーが発生します。

MQL
// 現在のSLが 150.000
// 新しいSLも 150.000(計算結果)
OrderModify(ticket, price, 150.000, tp, 0);  // → error 1

なぜ「同じ値」になるのか

トレーリングストップのロジック

MQL
// 典型的なトレーリングストップ
double newSL = Bid - TrailingStop * Point;
if(newSL > OrderStopLoss())
{
    OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0);
}

問題が発生するケース

Text Only
Bid = 150.500
TrailingStop = 50 pips
新しいSL = 150.500 - 0.500 = 150.000

しかし現在のSLも 150.000...

計算上は newSL > OrderStopLoss() が true になるべきだが、
浮動小数点誤差で微妙に異なる値になり、
比較は true でも OrderModify は「同じ値」と判定する

浮動小数点の罠(再び)

150.000000000001149.999999999999 のような
極めて近い値が「異なる」と判定され、変更を試みます。
しかしサーバー側では「同じ」と判定されるため、error 1。


MQL4での対策

対策1: 0.5*Point シフト

MQL
// 変更前にわずかなマージンを追加
double newSL = NormalizeDouble(Bid - TrailingStop * Point, Digits);
double currentSL = NormalizeDouble(OrderStopLoss(), Digits);

// 0.5*Pointより大きい差がある場合のみ変更
if(newSL - currentSL > 0.5 * Point)
{
    OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0);
}

対策2: 事前に一致チェック

MQL
double newSL = NormalizeDouble(Bid - TrailingStop * Point, Digits);
double currentSL = NormalizeDouble(OrderStopLoss(), Digits);

// 完全に同じ場合はスキップ
if(MathAbs(newSL - currentSL) < Point)
{
    return;  // 変更不要
}

OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0);

MQL5での対応

MQL5では PositionModify を使用しますが、同様の問題が発生する可能性があります。

MQL5検証コード

MQL
//+------------------------------------------------------------------+
//|                                    OrderModify_Error.mq5          |
//| 「とあるMetaTraderの備忘秘録」様の記事を検証                        |
//| https://fai-fx.hatenadiary.org/entry/20091122/1258893252          |
//+------------------------------------------------------------------+
#property copyright "FXおもしろラボ"
#property link      "https://fx-omoshiro-lab.com/"
#property version   "1.00"

#include <Trade/Trade.mqh>

void OnStart()
{
    Print("=== OrderModify error 1 の解説 ===");
    Print("");
    Print("このスクリプトは概念説明用です。");
    Print("実際のポジションは開きません。");
    Print("");

    // 問題のシミュレーション
    Print("--- 問題のシミュレーション ---");
    double currentSL = 150.000000000000;
    double newSL_raw = 150.000000000001;  // 計算結果(微妙に異なる)

    PrintFormat("現在のSL: %.15f", currentSL);
    PrintFormat("新しいSL: %.15f", newSL_raw);
    PrintFormat("差分: %.15f", newSL_raw - currentSL);
    PrintFormat("newSL > currentSL: %s", (newSL_raw > currentSL) ? "true" : "false");
    Print("");
    Print("→ 比較はtrueだが、サーバーでは「同じ値」と判定されerror 1");
    Print("");

    // 対策1の解説
    Print("--- 対策1: 0.5*_Point シフト ---");
    double point = 0.001;  // 3桁通貨の場合
    double margin = 0.5 * point;
    PrintFormat("マージン: %.4f", margin);
    PrintFormat("差分(%.15f) > マージン(%.4f): %s", 
                newSL_raw - currentSL, margin,
                (newSL_raw - currentSL > margin) ? "true → 変更実行" : "false → スキップ");
    Print("");

    // 対策2の解説
    Print("--- 対策2: 事前一致チェック ---");
    double newSL_normalized = NormalizeDouble(newSL_raw, 3);
    double currentSL_normalized = NormalizeDouble(currentSL, 3);
    PrintFormat("正規化後 新SL: %.3f", newSL_normalized);
    PrintFormat("正規化後 現SL: %.3f", currentSL_normalized);
    PrintFormat("一致: %s", (newSL_normalized == currentSL_normalized) ? "true → スキップ" : "false → 変更実行");
    Print("");

    // 安全なトレーリングストップ関数
    Print("--- 安全なトレーリングストップの実装例 ---");
    Print("void SafeTrailingStop(ulong ticket, double trailPips)");
    Print("{");
    Print("    double newSL = NormalizeDouble(bid - trailPips * _Point, _Digits);");
    Print("    double currentSL = PositionGetDouble(POSITION_SL);");
    Print("    ");
    Print("    // 0.5*_Point以上の差がある場合のみ変更");
    Print("    if(newSL - currentSL > 0.5 * _Point)");
    Print("    {");
    Print("        trade.PositionModify(ticket, newSL, tp);");
    Print("    }");
    Print("}");

    Print("");
    Print("=== 解説完了 ===");
}

安全なトレーリングストップ関数

MQL
#include <Trade/Trade.mqh>
CTrade trade;

//+------------------------------------------------------------------+
//| 安全なトレーリングストップ                                         |
//+------------------------------------------------------------------+
void SafeTrailingStop(ulong ticket, double trailPoints)
{
    if(!PositionSelectByTicket(ticket))
        return;

    double currentSL = PositionGetDouble(POSITION_SL);
    double currentTP = PositionGetDouble(POSITION_TP);
    double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
    long posType = PositionGetInteger(POSITION_TYPE);

    double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
    double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

    double newSL = 0;

    if(posType == POSITION_TYPE_BUY)
    {
        newSL = NormalizeDouble(bid - trailPoints * _Point, _Digits);

        // 利益が出ている & 新SLが現SLより高い & 差が十分ある
        if(bid > openPrice + trailPoints * _Point &&
           newSL > currentSL &&
           newSL - currentSL > 0.5 * _Point)  // ← ここが重要
        {
            trade.PositionModify(ticket, newSL, currentTP);
        }
    }
    else if(posType == POSITION_TYPE_SELL)
    {
        newSL = NormalizeDouble(ask + trailPoints * _Point, _Digits);

        // 利益が出ている & 新SLが現SLより低い(または0) & 差が十分ある
        if(ask < openPrice - trailPoints * _Point &&
           (currentSL == 0 || newSL < currentSL) &&
           (currentSL == 0 || currentSL - newSL > 0.5 * _Point))
        {
            trade.PositionModify(ticket, newSL, currentTP);
        }
    }
}

まとめ

ポイント 内容
エラー OrderModify error 1
意味 「変更前後の値が同じ」
原因 浮動小数点誤差
対策 0.5*_Point マージン or 事前チェック

オリジナル記事への謝辞

この記事は「とあるMetaTraderの備忘秘録」様の貴重な知見をもとに、
MQL5での検証と解説を加えたものです。
オリジナル記事に心より感謝いたします。


ソースコードのダウンロード

この記事で紹介したコードをダウンロードできます。

ファイルの種類:スクリプト

保存先: MQL5/Scripts/ フォルダ
使い方: MT5のナビゲーターから「スクリプト」を展開し、チャートにドラッグ&ドロップで実行

10_OrderModify_Error.mq5 をダウンロード


関連記事・用語