MQL5 錯誤處理與除錯技巧:GetLastError() 與日誌實戰

📌 本文重點
掌握 MQL5 錯誤處理的完整方法:GetLastError()、常見錯誤碼解析、ResetLastError(),以及如何用 Print() 建立有效的除錯日誌系統。

為什麼錯誤處理如此重要?

EA 在實盤運行時,下單失敗、網路中斷、伺服器拒絕等情況隨時都可能發生。如果沒有適當的錯誤處理,EA 可能靜默失敗——你以為有下單,其實什麼都沒發生。

GetLastError() 基本用法

bool SafeOrderSend(MqlTradeRequest &req, MqlTradeResult &res)
{
    ResetLastError();  // 清除之前的錯誤碼

    bool ok = OrderSend(req, res);

    if (!ok)
    {
        int err = GetLastError();
        string errMsg = ErrorToString(err);
        Print("下單失敗 | 錯誤碼: ", err, " | 說明: ", errMsg);
        Print("  品種: ", req.symbol, " | 方向: ", EnumToString(req.type));
        Print("  價格: ", req.price, " | 手數: ", req.volume);
        Print("  回傳碼: ", res.retcode, " | 說明: ", res.comment);
    }
    return ok;
}

// 常見錯誤碼對照
string ErrorToString(int code)
{
    switch(code)
    {
        case 0:     return "無錯誤";
        case 4756: return "下單被拒絕";
        case 4753: return "無效的止損";
        case 4752: return "無效的手數";
        case 4051: return "無效的交易品種";
        case 4752: return "交易量過小";
        case 10006: return "下單被拒絕(retcode)";
        case 10007: return "等待回應超時";
        case 10010: return "只允許部分成交";
        case 10013: return "無效請求";
        case 10014: return "無效手數";
        case 10015: return "無效價格";
        case 10016: return "無效止損/止盈";
        case 10018: return "市場已關閉";
        case 10019: return "保證金不足";
        case 10024: return "下單頻率過高";
        case 10026: return "自動交易被伺服器禁止";
        default:    return "未知錯誤 " + IntegerToString(code);
    }
}

完整的重試機制

bool OrderSendWithRetry(MqlTradeRequest &req, MqlTradeResult &res,
                         int maxRetries = 3, int delayMs = 1000)
{
    for (int i = 0; i < maxRetries; i++)
    {
        ResetLastError();
        bool ok = OrderSend(req, res);

        if (ok && res.retcode == TRADE_RETCODE_DONE)
        {
            Print("下單成功!重試次數: ", i, " | 票號: ", res.order);
            return true;
        }

        // 某些錯誤不需要重試
        if (res.retcode == TRADE_RETCODE_INVALID_VOLUME  ||
            res.retcode == TRADE_RETCODE_INVALID_STOPS   ||
            res.retcode == TRADE_RETCODE_MARKET_CLOSED)
        {
            Print("不可恢復錯誤,停止重試: ", res.retcode);
            return false;
        }

        Print("下單失敗(", i+1, "/", maxRetries, ")retcode=", res.retcode, ",等待重試...");
        Sleep(delayMs);

        // 重新整理報價
        req.price = (req.type == ORDER_TYPE_BUY)
                    ? SymbolInfoDouble(req.symbol, SYMBOL_ASK)
                    : SymbolInfoDouble(req.symbol, SYMBOL_BID);
    }

    Print("已達最大重試次數,下單失敗");
    return false;
}

結構化日誌系統

// 日誌等級
enum ENUM_LOG_LEVEL { LOG_DEBUG=0, LOG_INFO=1, LOG_WARN=2, LOG_ERROR=3 };

input ENUM_LOG_LEVEL InpLogLevel = LOG_INFO;  // 最低顯示等級

void Log(ENUM_LOG_LEVEL level, string msg)
{
    if (level < InpLogLevel) return;

    string prefix;
    switch(level)
    {
        case LOG_DEBUG: prefix = "[DEBUG]"; break;
        case LOG_INFO:  prefix = "[INFO] "; break;
        case LOG_WARN:  prefix = "[WARN] "; break;
        case LOG_ERROR: prefix = "[ERROR]"; break;
    }

    string timestamp = TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS);
    Print(prefix, " ", timestamp, " | ", msg);
}

// 使用範例
void OnTick()
{
    Log(LOG_DEBUG, "OnTick 觸發,當前價格: " + DoubleToString(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits));

    if (!IsNewBar()) return;

    Log(LOG_INFO, "新K線: " + TimeToString(iTime(_Symbol, _Period, 0)));

    int signal = GetSignal();
    if (signal != 0)
        Log(LOG_INFO, "交易信號: " + (signal == 1 ? "BUY" : "SELL"));
}

常見除錯技巧

  • 加入大量 Print() 語句:驗證每一步的數值是否符合預期
  • 確認數組方向ArraySetAsSeries 後,[0] 是最新;忘記設定是常見 bug
  • 用 Comment() 在圖表上顯示即時數據Comment("MA Fast=", fast, "\nMA Slow=", slow);
  • 策略測試器 + 視覺模式:可以觀察每個 Tick 的 EA 行為,是最有效的除錯方式

本文由 James Lee 撰寫。內容僅供教育目的,不構成投資建議。

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *