MQL5 程式設計常見錯誤與最佳實踐:從踩坑到精通
本文揭露 MQL5 程式交易開發中最常踩的坑,包括:指標句柄管理錯誤、浮點數比較陷阱、OnTick 效能問題、Magic Number 管理、錯誤碼處理等,並提供具體的最佳實踐解決方案與可直接使用的程式碼模板。
MQL5 程式設計常見錯誤與最佳實踐:從踩坑到精通
> 「每個優秀的程式設計師都曾經寫過糟糕的程式碼,差別在於他們從錯誤中學習的速度。」 — 程式設計師的成長之路
前言:為什麼錯誤是學習的最佳機會?
我還記得剛開始學習 MQL5 的時候,寫的第一個 EA 在模擬帳戶運行了一週後,MT5 突然變得超級慢,最後直接當機。檢查日誌才發現,我忘記釋放指標句柄,導致記憶體洩漏。
這種「踩坑」經驗雖然痛苦,但卻是成長最快的方式。今天,我想分享這些年來我遇到(或看到別人遇到)的常見錯誤,以及如何避免它們。
錯誤 1:指標句柄的記憶體洩漏(最常見的殺手)
問題描述
每次呼叫 iMA()、iRSI()、iMACD() 等指標函數時,MT5 都會在後台建立一個指標句柄。如果你在 OnTick() 中不斷建立新的句柄而不釋放,記憶體就會像漏水的水桶一樣慢慢耗盡。
錯誤示範
// ❌ 錯誤寫法:每次 OnTick 都建立新句柄
void OnTick()
{
// 每次有新報價就建立新的 MA 指標
int maHandle = iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE);
double maValue[];
CopyBuffer(maHandle, 0, 0, 1, maValue);
// 問題:maHandle 沒有被釋放!
// 運行一天後,可能建立了數千個句柄...
}
解決方案:正確的指標生命週期管理
// ✅ 正確寫法:全域句柄 + 生命週期管理
int g_maHandle = INVALID_HANDLE;
int g_rsiHandle = INVALID_HANDLE;
//+------------------------------------------------------------------+
//| EA 初始化時建立指標句柄 |
//+------------------------------------------------------------------+
int OnInit()
{
Print("=== 初始化指標句柄 ===");
// 建立移動平均線指標
g_maHandle = iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE);
if(g_maHandle == INVALID_HANDLE)
{
Print("錯誤: 無法建立 MA 指標");
return INIT_FAILED;
}
// 建立 RSI 指標
g_rsiHandle = iRSI(_Symbol, _Period, 14, PRICE_CLOSE);
if(g_rsiHandle == INVALID_HANDLE)
{
Print("錯誤: 無法建立 RSI 指標");
IndicatorRelease(g_maHandle); // 釋放已建立的指標
return INIT_FAILED;
}
Print("指標句柄建立成功:");
Print("MA Handle: ", g_maHandle);
Print("RSI Handle: ", g_rsiHandle);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| EA 結束時釋放指標句柄 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Print("=== 釋放指標資源 ===");
// 釋放 MA 指標
if(g_maHandle != INVALID_HANDLE)
{
if(IndicatorRelease(g_maHandle))
Print("MA 指標已釋放");
else
Print("警告: MA 指標釋放失敗");
g_maHandle = INVALID_HANDLE;
}
// 釋放 RSI 指標
if(g_rsiHandle != INVALID_HANDLE)
{
if(IndicatorRelease(g_rsiHandle))
Print("RSI 指標已釋放");
else
Print("警告: RSI 指標釋放失敗");
g_rsiHandle = INVALID_HANDLE;
}
}
//+------------------------------------------------------------------+
//| 使用指標的範例 |
//+------------------------------------------------------------------+
double GetMAValue(int shift = 0)
{
if(g_maHandle == INVALID_HANDLE)
{
Print("錯誤: MA 指標句柄無效");
return EMPTY_VALUE;
}
double values[1];
if(CopyBuffer(g_maHandle, 0, shift, 1, values) <= 0)
{
Print("錯誤: 無法取得 MA 數值");
return EMPTY_VALUE;
}
return values[0];
}
進階技巧:指標管理器類別
對於複雜的 EA,建議建立一個指標管理器:
//+------------------------------------------------------------------+
//| 指標管理器類別 |
//+------------------------------------------------------------------+
class IndicatorManager
{
private:
struct IndicatorHandle
{
int handle;
string name;
datetime created;
};
IndicatorHandle handles[];
int count;
public:
IndicatorManager() : count(0) {}
~IndicatorManager()
{
ReleaseAll();
}
// 建立指標
int CreateMA(string symbol, ENUM_TIMEFRAMES timeframe,
int period, int shift, ENUM_MA_METHOD method,
ENUM_APPLIED_PRICE price)
{
int handle = iMA(symbol, timeframe, period, shift, method, price);
if(handle != INVALID_HANDLE)
{
AddHandle(handle, "MA_" + IntegerToString(period));
}
return handle;
}
// 釋放所有指標
void ReleaseAll()
{
for(int i = 0; i < count; i++)
{
if(handles[i].handle != INVALID_HANDLE)
{
IndicatorRelease(handles[i].handle);
Print("釋放指標: ", handles[i].name);
}
}
ArrayResize(handles, 0);
count = 0;
}
private:
void AddHandle(int handle, string name)
{
ArrayResize(handles, count + 1);
handles[count].handle = handle;
handles[count].name = name;
handles[count].created = TimeCurrent();
count++;
}
};
// 使用範例
IndicatorManager g_indicatorManager;
int OnInit()
{
// 透過管理器建立指標
int maHandle = g_indicatorManager.CreateMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE);
// ... 不需要手動釋放,管理器會在解構時自動處理
return INIT_SUCCEEDED;
}
錯誤 2:陣列操作的地雷區
問題 2.1:陣列索引越界
這是最容易導致程式崩潰的錯誤之一。
// ❌ 危險的陣列操作
double prices[10];
for(int i = 0; i <= 10; i++) // 錯誤:i=10 時會越界!
{
prices[i] = iClose(_Symbol, _Period, i);
}
// ✅ 安全的陣列操作
double safePrices[];
ArrayResize(safePrices, 10); // 先設定大小
// 方法 1:使用 ArraySize()
for(int i = 0; i < ArraySize(safePrices); i++)
{
safePrices[i] = iClose(_Symbol, _Period, i);
}
// 方法 2:使用 CopyClose() 自動處理
if(CopyClose(_Symbol, _Period, 0, 10, safePrices) > 0)
{
// 複製成功,safePrices 已經自動調整大小
ArraySetAsSeries(safePrices, true); // 設定為時間序列
}
問題 2.2:未初始化陣列
// ❌ 未初始化就使用
double data[];
Print(data[0]); // 錯誤:陣列大小為0!
// ✅ 正確的初始化
double properData[];
ArrayResize(properData, 10);
ArrayInitialize(properData, 0.0); // 初始化為0
// 或者使用 Copy 函數自動初始化
double autoData[];
if(CopyRates(_Symbol, _Period, 0, 10, autoData) > 0)
{
// autoData 已經被正確初始化並填充數據
}
錯誤 3:浮點數比較的陷阱
問題描述
在金融計算中,浮點數的精度問題可能導致嚴重的交易錯誤。
// ❌ 錯誤的浮點數比較
double price1 = 1.23456;
double price2 = 1.23457;
if(price1 == price2) // 幾乎永遠不會成立!
{
Print("價格相等"); // 這行永遠不會執行
}
// ✅ 正確的浮點數比較
bool CompareDoubles(double value1, double value2, int digits = 5)
{
return NormalizeDouble(value1 - value2, digits) == 0.0;
}
// 或者使用內建的 CompareDoubles
#include
錯誤 4:交易函數的錯誤處理不完整
常見問題
很多開發者只檢查 OrderSend() 的返回值,卻忽略了詳細的錯誤資訊。
// ❌ 不完整的錯誤處理
bool OpenOrder()
{
MqlTradeRequest request = {0};
MqlTradeResult result = {0};
// 設定請求參數...
if(!OrderSend(request, result))
{
Print("下單失敗"); // 太籠統了!
return false;
}
return true;
}
// ✅ 完整的錯誤處理
bool OpenOrderWithDetailedError()
{
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
// 設定交易請求
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = 0.1;
request.type = ORDER_TYPE_BUY;
request.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
request.deviation = 5;
request.magic = 123456;
// 發送訂單
bool success = OrderSend(request, result);
if(!success)
{
int errorCode = GetLastError();
Print("=== 下單失敗詳細資訊 ===");
Print("錯誤碼: ", errorCode);
Print("錯誤描述: ", GetTradeErrorDescription(errorCode));
Print("返回碼: ", result.retcode);
Print("返回描述: ", GetRetcodeDescription(result.retcode));
// 根據錯誤類型採取不同行動
HandleOrderError(errorCode, result.retcode);
return false;
}
Print("=== 下單成功 ===");
Print("訂單編號: ", result.order);
Print("成交價格: ", DoubleToString(result.price, Digits()));
Print("成交量: ", DoubleToString(result.volume, 2));
Print("手續費: ", DoubleToString(result.commission, 2));
return true;
}
//+------------------------------------------------------------------+
//| 詳細的錯誤描述函數 |
//+------------------------------------------------------------------+
string GetTradeErrorDescription(int errorCode)
{
switch(errorCode)
{
case 0: return "成功";
case 1: return "沒有錯誤,但結果未知";
case 2: return "通用錯誤";
case 3: return "無效參數";
case 4: return "伺服器忙線";
case 130: return "無效止損或止盈";
case 131: return "無效手數";
case 132: return "交易量不足";
case 133: return "市場關閉";
case 134: return "保證金不足";
case 135: return "市場改變";
case 136: return "無報價";
case 137: return "經紀商忙線";
case 138: return "重新報價";
case 139: return "訂單被鎖定";
case 140: return "只允許買單或賣單";
case 145: return "價格改變";
case 146: return "交易環境忙線";
case 10004: return "請求處理中";
case 10006: return "請求失敗";
case 10007: return "請求取消";
default: return "未知錯誤: " + IntegerToString(errorCode);
}
}
string GetRetcodeDescription(long retcode)
{
switch(retcode)
{
case 10004: return "TRADE_RETCODE_REQUOTE";
case 10006: return "TRADE_RETCODE_REJECT";
case 10007: return "TRADE_RETCODE_CANCEL";
case 10008: return "TRADE_RETCODE_PLACED";
case 10009: return "TRADE_RETCODE_DONE";
case 10010: return "TRADE_RETCODE_DONE_PARTIAL";
case 10011: return "TRADE_RETCODE_ERROR";
case 10012: return "TRADE_RETCODE_TIMEOUT";
case 10013: return "TRADE_RETCODE_INVALID";
case 10014: return "TRADE_RETCODE_INVALID_VOLUME";
case 10015: return "TRADE_RETCODE_INVALID_PRICE";
case 10016: return "TRADE_RETCODE_INVALID_STOPS";
case 10017: return "TRADE_RETCODE_TRADE_DISABLED";
case 10018: return "TRADE_RETCODE_MARKET_CLOSED";
case 10019: return "TRADE_RETCODE_NO_MONEY";
case 10020: return "TRADE_RETCODE_PRICE_CHANGED";
case 10021: return "TRADE_RETCODE_PRICE_OFF";
case 10022: return "TRADE_RETCODE_INVALID_EXPIRATION";
case 10023: return "TRADE_RETCODE_ORDER_CHANGED";
case 10024: return "TRADE_RETCODE_TOO_MANY_REQUESTS";
case 10025: return "TRADE_RETCODE_NO_CHANGES";
case 10026: return "TRADE_RETCODE_SERVER_DISABLES_AT";
case 10027: return "TRADE_RETCODE_CLIENT_DISABLES_AT";
case 10028: return "TRADE_RETCODE_LOCKED";
case 10029: return "TRADE_RETCODE_FROZEN";
case 10030: return "TRADE_RETCODE_INVALID_FILL";
case 10031: return "TRADE_RETCODE_CONNECTION";
case 10032: return "TRADE_RETCODE_ONLY_REAL";
case 10033: return "TRADE_RETCODE_LIMIT_ORDERS";
case 10034: return "TRADE_RETCODE_LIMIT_VOLUME";
case 10035: return "TRADE_RETCODE_INVALID_ORDER";
case 10036: return "TRADE_RETCODE_POSITION_CLOSED";
case 10038: return "TRADE_RETCODE_INVALID_CLOSE_VOLUME";
case 10039: return "TRADE_RETCODE_CLOSE_ORDER_EXIST";
case 10040: return "TRADE_RETCODE_LIMIT_POSITIONS";
case 10041: return "TRADE_RETCODE_REJECT_CANCEL";
case 10042: return "TRADE_RETCODE_LONG_ONLY";
case 10043: return "TRADE_RETCODE_SHORT_ONLY";
case 10044: return "TRADE_RETCODE_CLOSE_ONLY";
case 10045: return "TRADE_RETCODE_FIFO_CLOSE";
default: return "未知返回碼: " + IntegerToString(retcode);
}
}
//+------------------------------------------------------------------+
//| 錯誤處理策略 |
//+------------------------------------------------------------------+
void HandleOrderError(int errorCode, long retcode)
{
// 重新報價 - 可以重試
if(retcode == 10004 || errorCode == 138)
{
Print("收到重新報價,等待後重試...");
Sleep(100); // 等待100毫秒
// 可以在此處加入重試邏輯
}
// 保證金不足 - 需要調整手數
else if(errorCode == 134 || retcode == 10019)
{
Print("保證金不足,建議:");
Print("1. 減少交易手數");
Print("2. 檢查是否有其他持倉");
Print("3. 等待帳戶入金");
}
// 無效手數 - 檢查手數規則
else if(errorCode == 131 || retcode == 10014)
{
Print("無效手數,請檢查:");
Print("1. 最小手數: ", SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN));
Print("2. 最大手數: ", SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX));
Print("3. 手數步進: ", SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP));
}
// 無效止損止盈 - 檢查距離
else if(errorCode == 130 || retcode == 10016)
{
Print("無效止損止盈,請檢查:");
Print("最小止損距離: ",
SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL), "點");
}
}
錯誤 5:時間處理的常見陷阱
問題 5.1:時區混淆
// ❌ 混淆伺服器時間和本地時間
datetime serverTime = TimeCurrent(); // 伺服器時間
datetime localTime = TimeLocal(); // 本地電腦時間
// 重要:交易相關的時間應該使用伺服器時間!
if(TimeHour(serverTime) >= 9 && TimeHour(serverTime) < 17)
{
// 交易時間判斷
}
// ✅ 正確的時間處理
bool IsTradingHours()
{
datetime current = TimeCurrent();
int hour = TimeHour(current);
int dayOfWeek = TimeDayOfWeek(current);
// 週一至週五,9:00-17:00(伺服器時間)
if(dayOfWeek >= 1 && dayOfWeek <= 5)
{
return (hour >= 9 && hour < 17);
}
return false;
}
問題 5.2:新K線檢測不準確
// ❌ 不可靠的新K線檢測
datetime lastBar = 0;
void OnTick()
{
datetime currentBar = iTime(_Symbol, _Period, 0);
if(currentBar > lastBar) // 可能漏掉某些情況
{
lastBar = currentBar;
// 執行新K線邏輯
}
}
// ✅ 可靠的新K線檢測
datetime g_lastBarTime = 0;
int g_lastBarIndex = -1;
bool IsNewBar()
{
datetime currentTime;
int currentIndex;
// 方法1:使用時間和索引雙重檢查
if(CopyTime(_Symbol, _Period, 0, 1, currentTime) > 0)
{
if(currentTime != g_lastBarTime)
{
g_lastBarTime = currentTime;
return true;
}
}
// 方法2:使用成交量確認(更可靠)
long currentVolume[];
if(CopyTickVolume(_Symbol, _Period, 0, 1, currentVolume) > 0)
{
if(currentVolume[0] == 1) // 新K線的第一個tick
{
return true;
}
}
return false;
}
錯誤 6:忽略交易規則檢查
問題描述
每個經紀商、每個交易品種都有不同的交易規則,忽略這些規則會導致下單失敗。
// ✅ 完整的交易規則檢查函數
bool CheckTradingRules(double volume, double price,
double stopLoss, double takeProfit)
{
// 1. 檢查交易時間
if(!IsTradeAllowed())
{
Print("錯誤: 當前不允許交易");
return false;
}
// 2. 檢查市場狀態
if(!SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_FULL)
{
Print("錯誤: 交易品種不可用");
return false;
}
// 3. 檢查手數
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(volume < minLot || volume > maxLot)
{
Print("錯誤: 手數超出範圍");
return false;
}
// 檢查手數步進
double remainder = MathMod(volume, lotStep);
if(MathAbs(remainder) > 0.00001)
{
Print("錯誤: 手數不符合步進規則");
return false;
}
// 4. 檢查止損止盈距離
if(stopLoss > 0 || takeProfit > 0)
{
int stopLevel = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double minDistance = stopLevel * point;
if(stopLoss > 0)
{
double slDistance = MathAbs(price - stopLoss);
if(slDistance < minDistance)
{
Print("錯誤: 止損距離太小");
return false;
}
}
if(takeProfit > 0)
{
double tpDistance = MathAbs(price - takeProfit);
if(tpDistance < minDistance)
{
Print("錯誤: 止盈距離太小");
return false;
}
}
}
// 5. 檢查保證金
double marginRequired;
if(OrderCalcMargin(ORDER_TYPE_BUY, _Symbol, volume, price, marginRequired))
{
double freeMargin = AccountInfoDouble(ACCOUNT_FREEMARGIN);
if(marginRequired > freeMargin)
{
Print("錯誤: 保證金不足");
Print("需要: $", DoubleToString(marginRequired, 2));
Print("可用: $", DoubleToString(freeMargin, 2));
return false;
}
}
return true;
}
錯誤 7:日誌記錄不完整
問題描述
當 EA 出現問題時,如果沒有完整的日誌,很難找出問題所在。
// ✅ 完整的日誌記錄系統
enum LOG_LEVEL
{
LOG_LEVEL_DEBUG, // 除錯資訊
LOG_LEVEL_INFO, // 一般資訊
LOG_LEVEL_WARNING, // 警告
LOG_LEVEL_ERROR, // 錯誤
LOG_LEVEL_CRITICAL // 嚴重錯誤
};
input LOG_LEVEL LogLevel = LOG_LEVEL_INFO; // 預設記錄等級
void Log(LOG_LEVEL level, string message)
{
// 根據設定決定是否記錄
if(level < LogLevel)
return;
string prefix;
switch(level)
{
case LOG_LEVEL_DEBUG: prefix = "[DEBUG] "; break;
case LOG_LEVEL_INFO: prefix = "[INFO] "; break;
case LOG_LEVEL_WARNING: prefix = "[WARNING] "; break;
case LOG_LEVEL_ERROR: prefix = "[ERROR] "; break;
case LOG_LEVEL_CRITICAL: prefix = "[CRITICAL] "; break;
}
string timestamp = TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS);
string logMessage = timestamp + " " + prefix + message;
Print(logMessage);
// 嚴重錯誤時發送通知
if(level >= LOG_LEVEL_ERROR)
{
SendNotification(logMessage);
// 可以寫入檔案備份
WriteToLogFile(logMessage);
}
}
// 使用範例
void OnTick()
{
Log(LOG_LEVEL_DEBUG, "OnTick 開始執行");
try
{
// 交易邏輯...
Log(LOG_LEVEL_INFO, "檢查交易信號");
if(someErrorCondition)
{
Log(LOG_LEVEL_ERROR, "發現交易條件錯誤");
}
}
catch(string errorMsg)
{
Log(LOG_LEVEL_CRITICAL, "發生異常: " + errorMsg);
}
Log(LOG_LEVEL_DEBUG, "OnTick 執行完成");
}
錯誤 8:忽略效能優化
問題 8.1:過度頻繁的指標計算
// ❌ 效能不佳的寫法
void OnTick()
{
// 每次tick都重新計算所有指標
double ma1 = iMA(_Symbol, _Period, 10, 0, MODE_SMA, PRICE_CLOSE, 0);
double ma2 = iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE, 0);
double ma3 = iMA(_Symbol, _Period, 30, 0, MODE_SMA, PRICE_CLOSE, 0);
double rsi = iRSI(_Symbol, _Period, 14, PRICE_CLOSE, 0);
double macd = iMACD(_Symbol, _Period, 12, 26, 9, PRICE_CLOSE, 0);
// ... 每次tick都這樣做會很慢
}
// ✅ 效能優化的寫法
class EfficientEA
{
private:
int ma10Handle, ma20Handle, ma30Handle, rsiHandle, macdHandle;
datetime lastCalcTime;
public:
bool Initialize()
{
// 初始化時建立一次指標句柄
ma10Handle = iMA(_Symbol, _Period, 10, 0, MODE_SMA, PRICE_CLOSE);
ma20Handle = iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE);
ma30Handle = iMA(_Symbol, _Period, 30, 0, MODE_SMA, PRICE_CLOSE);
rsiHandle = iRSI(_Symbol, _Period, 14, PRICE_CLOSE);
macdHandle = iMACD(_Symbol, _Period, 12, 26, 9, PRICE_CLOSE);
lastCalcTime = 0;
return true;
}
void OnTickEfficient()
{
// 只在需要時計算(例如每5秒或新K線)
datetime currentTime = TimeCurrent();
if(currentTime - lastCalcTime < 5) // 5秒內不重複計算
return;
lastCalcTime = currentTime;
// 批次取得指標數值
double ma10 = GetIndicatorValue(ma10Handle, 0);
double ma20 = GetIndicatorValue(ma20Handle, 0);
double ma30 = GetIndicatorValue(ma30Handle, 0);
double rsi = GetIndicatorValue(rsiHandle, 0);
double macd = GetIndicatorValue(macdHandle, 0);
// 使用快取的數值進行交易決策
}
private:
double GetIndicatorValue(int handle, int shift)
{
double value[1];
if(CopyBuffer(handle, 0, shift, 1, value) > 0)
return value[0];
return EMPTY_VALUE;
}
};
最佳實踐總結
1. 記憶體管理黃金法則
- 指標句柄:在 OnInit 建立,在 OnDeinit 釋放
- 陣列操作:總是檢查大小,使用安全的複製函數
- 物件生命週期:使用類別封裝資源管理
2. 錯誤處理最佳實踐
- 詳細日誌:記錄足夠的除錯資訊
- 分級處理:根據錯誤嚴重程度採取不同行動
- 重試機制:對於可恢復錯誤(如重新報價)實作重試
3. 交易安全準則
- 規則檢查:下單前檢查所有交易規則
- 風險控制:實作動態手數計算和風險限制
- 時間管理:正確處理時區和交易時間
4. 效能優化技巧
- 避免重複計算:快取指標數值
- 批次操作:一次取得多個數據點
- 適當頻率:根據策略需求調整計算頻率
實用除錯工具
1. 內建除錯功能
// 開啟詳細日誌
#property show_inputs
#property show_confirm
// 使用 DebugBreak() 暫停執行
if(someCondition)
{
DebugBreak(); // 在此處暫停,檢查變數值
}
// 檢查記憶體使用
Print("記憶體使用: ", MQL5InfoInteger(MQL5_MEMORY_USED), " bytes");
2. 自訂除錯面板
// 在圖表上顯示除錯資訊
void DrawDebugInfo()
{
string debugText = "";
debugText += "時間: " + TimeToString(TimeCurrent()) + "\n";
debugText += "價格: " + DoubleToString(SymbolInfoDouble(_Symbol, SYMBOL_BID), 5) + "\n";
debugText += "持倉: " + IntegerToString(PositionsTotal()) + "\n";
debugText += "錯誤: " + IntegerToString(GetLastError()) + "\n";
ObjectCreate(0, "DebugPanel", OBJ_LABEL, 0, 0, 0);
ObjectSetString(0, "DebugPanel", OBJPROP_TEXT, debugText);
ObjectSetInteger(0, "DebugPanel", OBJPROP_CORNER, CORNER_RIGHT_UPPER);
ObjectSetInteger(0, "DebugPanel", OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, "DebugPanel", OBJPROP_YDISTANCE, 10);
ObjectSetInteger(0, "DebugPanel", OBJPROP_FONTSIZE, 8);
}
結語:從錯誤中成長
學習程式設計就像學習任何技能一樣,犯錯是不可避免的過程。關鍵在於:
1. 擁抱錯誤:每個錯誤都是一次學習機會
2. 系統化除錯:建立標準的除錯流程
3. 持續改進:從每次錯誤中提取經驗教訓
4. 分享經驗:幫助其他人避免同樣的錯誤
記住,即使是經驗豐富的開發者也會犯錯。差別在於他們有更好的工具和習慣來快速發現和解決問題。
希望這篇文章能幫助你避開一些常見的陷阱,寫出更穩定、更可靠的 MQL5 程式!
---
下一步學習建議:
1. ✅ MQL5 程式設計常見錯誤與最佳實踐(本篇)
2. 🔜 深入理解 MQL5 記憶體管理與效能優化
3. 🔜 高級錯誤處理與異常恢復機制
4. 🔜 實戰:建立生產級別的 EA 框架
---
*本文分享的經驗來自多年的實戰開發,每個錯誤背後都有一個故事。如果你有特別的「踩坑」經驗想分享,歡迎在下方留言交流!*