Codebase MQL4 — เขียน Expert Advisor และ Automated Trading บน MetaTrader 4
MQL4 คืออะไรและใช้ทำอะไร
MQL4 (MetaQuotes Language 4) เป็นภาษาโปรแกรมที่พัฒนาโดย MetaQuotes สำหรับเขียน automated trading programs บน MetaTrader 4 (MT4) platform ซึ่งเป็น trading platform ที่นิยมที่สุดสำหรับ Forex trading ทั่วโลก
MQL4 ใช้สำหรับเขียน Expert Advisors (EAs) โปรแกรม automated trading ที่ซื้อขายอัตโนมัติ, Custom Indicators สร้าง technical indicators เฉพาะทาง, Scripts โปรแกรมที่รันครั้งเดียวสำหรับ specific tasks, Libraries ฟังก์ชันที่ reuse ได้ข้าม programs
MQL4 syntax คล้าย C/C++ มี built-in functions สำหรับ market data access, order management, technical analysis, file operations และ network communication เหมาะสำหรับ traders ที่ต้องการ automate trading strategies, สร้าง custom indicators ที่ไม่มีใน standard MT4 และ backtest strategies ก่อนใช้จริง
เริ่มต้นเขียน MQL4
ขั้นตอนเริ่มต้นเขียนโปรแกรม MQL4
+------------------------------------------------------------------+
| MQL4 Basics — Getting Started |
+------------------------------------------------------------------+
=== 1. ติดตั้ง MetaTrader 4 ===
ดาวน์โหลดจาก broker ที่ใช้ (เช่น XM, FXCM, IC Markets)
เปิด MetaEditor: Tools > MetaQuotes Language Editor (F4)
=== 2. โครงสร้างไฟล์ MQL4 ===
Expert Advisors: MQL4/Experts/
Indicators: MQL4/Indicators/
Scripts: MQL4/Scripts/
Libraries: MQL4/Libraries/
Include: MQL4/Include/
=== 3. MQL4 Data Types ===
int — จำนวนเต็ม
double — ทศนิยม (ราคา, lots)
string — ข้อความ
bool — true/false
datetime — วันเวลา
color — สี
=== 4. Built-in Variables ===
Ask — ราคา Ask ปัจจุบัน
Bid — ราคา Bid ปัจจุบัน
Point — ค่า point เล็กที่สุด (0.0001 สำหรับ 4 digits)
Digits — จำนวนทศนิยมของราคา
Bars — จำนวน bars บน chart
Volume — volume ของ bar ปัจจุบัน
=== 5. Hello World EA ===
+------------------------------------------------------------------+
| Expert initialization function |
+------------------------------------------------------------------+
int OnInit()
{
Print("EA initialized on ", Symbol(), " ", Period(), " timeframe");
Print("Current Ask: ", Ask);
Print("Current Bid: ", Bid);
Print("Spread: ", MarketInfo(Symbol(), MODE_SPREAD), " points");
return(INIT_SUCCEEDED);
}
+------------------------------------------------------------------+
| Expert deinitialization function |
+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Print("EA removed. Reason: ", reason);
}
+------------------------------------------------------------------+
| Expert tick function — called on every price tick |
+------------------------------------------------------------------+
void OnTick()
{
แสดงราคาทุก tick
static datetime lastBar = 0;
datetime currentBar = iTime(Symbol(), Period(), 0);
ทำงานเฉพาะเมื่อมี bar ใหม่
if(currentBar != lastBar)
{
lastBar = currentBar;
double close1 = iClose(Symbol(), Period(), 1);
double open1 = iOpen(Symbol(), Period(), 1);
if(close1 > open1)
Print("Previous bar: BULLISH | Close=", close1);
else
Print("Previous bar: BEARISH | Close=", close1);
}
}
=== 6. Compile และ Test ===
กด F7 ใน MetaEditor เพื่อ compile
ลาก EA ไปวางบน chart ใน MT4
เปิด Strategy Tester (Ctrl+R) เพื่อ backtest
สร้าง Expert Advisor (EA)
ตัวอย่าง EA สำหรับ automated trading
//+------------------------------------------------------------------+
//| Moving Average Crossover EA |
//| Strategy: Buy when fast MA crosses above slow MA |
//| Sell when fast MA crosses below slow MA |
//+------------------------------------------------------------------+
#property copyright "SiamCafe"
#property version "1.00"
#property strict
// === Input Parameters ===
input int FastMA_Period = 10; // Fast MA Period
input int SlowMA_Period = 30; // Slow MA Period
input int MA_Method = MODE_EMA; // MA Method (0=SMA, 1=EMA)
input double LotSize = 0.01; // Lot Size
input int StopLoss = 50; // Stop Loss (points)
input int TakeProfit = 100; // Take Profit (points)
input int MagicNumber = 12345; // Magic Number
input int MaxSpread = 30; // Maximum Spread (points)
input int Slippage = 3; // Maximum Slippage
// === Global Variables ===
double fastMA_current, fastMA_prev;
double slowMA_current, slowMA_prev;
//+------------------------------------------------------------------+
int OnInit()
{
if(FastMA_Period >= SlowMA_Period)
{
Alert("Fast MA must be less than Slow MA!");
return(INIT_PARAMETERS_INCORRECT);
}
Print("MA Crossover EA started: Fast=", FastMA_Period, " Slow=", SlowMA_Period);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
void OnTick()
{
// Check new bar
static datetime lastBar = 0;
if(iTime(Symbol(), Period(), 0) == lastBar) return;
lastBar = iTime(Symbol(), Period(), 0);
// Check spread
int spread = (int)MarketInfo(Symbol(), MODE_SPREAD);
if(spread > MaxSpread)
{
Print("Spread too high: ", spread);
return;
}
// Calculate MAs
fastMA_current = iMA(Symbol(), Period(), FastMA_Period, 0, MA_Method, PRICE_CLOSE, 1);
fastMA_prev = iMA(Symbol(), Period(), FastMA_Period, 0, MA_Method, PRICE_CLOSE, 2);
slowMA_current = iMA(Symbol(), Period(), SlowMA_Period, 0, MA_Method, PRICE_CLOSE, 1);
slowMA_prev = iMA(Symbol(), Period(), SlowMA_Period, 0, MA_Method, PRICE_CLOSE, 2);
// Check for crossover signals
bool buySignal = (fastMA_prev <= slowMA_prev) && (fastMA_current > slowMA_current);
bool sellSignal = (fastMA_prev >= slowMA_prev) && (fastMA_current < slowMA_current);
// Count open orders
int buyOrders = 0, sellOrders = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
if(OrderType() == OP_BUY) buyOrders++;
if(OrderType() == OP_SELL) sellOrders++;
}
}
}
// Execute trades
if(buySignal && buyOrders == 0)
{
// Close sell orders first
CloseOrders(OP_SELL);
double sl = Ask - StopLoss * Point;
double tp = Ask + TakeProfit * Point;
int ticket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, Slippage,
sl, tp, "MA Cross Buy", MagicNumber, 0, clrGreen);
if(ticket > 0)
Print("BUY order opened: #", ticket, " at ", Ask);
else
Print("BUY error: ", GetLastError());
}
if(sellSignal && sellOrders == 0)
{
CloseOrders(OP_BUY);
double sl = Bid + StopLoss * Point;
double tp = Bid - TakeProfit * Point;
int ticket = OrderSend(Symbol(), OP_SELL, LotSize, Bid, Slippage,
sl, tp, "MA Cross Sell", MagicNumber, 0, clrRed);
if(ticket > 0)
Print("SELL order opened: #", ticket, " at ", Bid);
else
Print("SELL error: ", GetLastError());
}
}
//+------------------------------------------------------------------+
void CloseOrders(int orderType)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber
&& OrderType() == orderType)
{
double price = (orderType == OP_BUY) ? Bid : Ask;
bool closed = OrderClose(OrderTicket(), OrderLots(), price, Slippage, clrYellow);
if(!closed)
Print("Close error: ", GetLastError());
}
}
}
}
void OnDeinit(const int reason)
{
Print("EA removed. Open P/L: ", AccountProfit());
}
Custom Indicators และ Scripts
สร้าง custom indicator
+------------------------------------------------------------------+
| Custom RSI Divergence Indicator |
+------------------------------------------------------------------+
property copyright "SiamCafe"
property indicator_separate_window
property indicator_minimum 0
property indicator_maximum 100
property indicator_buffers 3
property indicator_color1 DodgerBlue
property indicator_color2 Red
property indicator_color3 Lime
property indicator_width1 2
property strict
input int RSI_Period = 14;
input int Overbought = 70;
input int Oversold = 30;
double RSIBuffer[];
double OverboughtBuffer[];
double OversoldBuffer[];
int OnInit()
{
SetIndexBuffer(0, RSIBuffer);
SetIndexBuffer(1, OverboughtBuffer);
SetIndexBuffer(2, OversoldBuffer);
SetIndexStyle(0, DRAW_LINE);
SetIndexStyle(1, DRAW_LINE, STYLE_DOT);
SetIndexStyle(2, DRAW_LINE, STYLE_DOT);
SetIndexLabel(0, "RSI(" + IntegerToString(RSI_Period) + ")");
SetIndexLabel(1, "Overbought");
SetIndexLabel(2, "Oversold");
IndicatorShortName("Custom RSI(" + IntegerToString(RSI_Period) + ")");
return(INIT_SUCCEEDED);
}
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
int start = MathMax(RSI_Period + 1, prev_calculated - 1);
for(int i = start; i < rates_total; i++)
{
RSIBuffer[i] = iRSI(Symbol(), Period(), RSI_Period, PRICE_CLOSE,
rates_total - 1 - i);
OverboughtBuffer[i] = Overbought;
OversoldBuffer[i] = Oversold;
}
return(rates_total);
}
+------------------------------------------------------------------+
| Utility Script: Account Summary |
+------------------------------------------------------------------+
Save as Scripts/AccountSummary.mq4
void OnStart()
{
double balance = AccountBalance();
double equity = AccountEquity();
double margin = AccountMargin();
double freeMargin = AccountFreeMargin();
double profit = AccountProfit();
int leverage = AccountLeverage();
string report = StringFormat(
"=== Account Summary ===\n"
"Balance: %.2f %s\n"
"Equity: %.2f\n"
"Margin: %.2f\n"
"Free Margin: %.2f\n"
"Profit: %.2f\n"
"Leverage: 1:%d\n"
"Open Orders: %d\n"
"Server: %s\n",
balance, AccountCurrency(),
equity, margin, freeMargin, profit,
leverage, OrdersTotal(),
AccountServer()
);
// Count orders by type
int buys = 0, sells = 0;
double buyProfit = 0, sellProfit = 0;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS))
{
if(OrderType() == OP_BUY)
{
buys++;
buyProfit += OrderProfit() + OrderSwap() + OrderCommission();
}
else if(OrderType() == OP_SELL)
{
sells++;
sellProfit += OrderProfit() + OrderSwap() + OrderCommission();
}
}
}
report += StringFormat(
"\nBuy Orders: %d (P/L: %.2f)\n"
"Sell Orders: %d (P/L: %.2f)\n",
buys, buyProfit, sells, sellProfit
);
Alert(report);
// Save to file
int handle = FileOpen("AccountReport.txt", FILE_WRITE|FILE_TXT);
if(handle != INVALID_HANDLE)
{
FileWriteString(handle, report);
FileClose(handle);
Print("Report saved to AccountReport.txt");
}
}
Backtesting และ Optimization
ทดสอบและ optimize EA
+------------------------------------------------------------------+
| Backtesting Guide for MQL4 |
+------------------------------------------------------------------+
=== 1. Strategy Tester Setup ===
เปิด Strategy Tester: Ctrl+R ใน MT4
Settings:
- Expert Advisor: เลือก EA ที่ต้องการ test
- Symbol: เลือกคู่เงิน (เช่น EURUSD)
- Period: เลือก timeframe (เช่น H1)
- Model: "Every tick" (แม่นที่สุด)
- Date: เลือกช่วงเวลา test (เช่น 1 ปี)
- Spread: Current หรือ fixed
=== 2. EA with Reporting ===
+------------------------------------------------------------------+
#property strict
// Performance tracking
int totalTrades = 0;
int winTrades = 0;
int loseTrades = 0;
double totalProfit = 0;
double totalLoss = 0;
double maxDrawdown = 0;
double peakEquity = 0;
void TrackPerformance()
{
double equity = AccountEquity();
if(equity > peakEquity)
peakEquity = equity;
double drawdown = (peakEquity - equity) / peakEquity * 100;
if(drawdown > maxDrawdown)
maxDrawdown = drawdown;
}
void OnTrade()
{
// Called when order status changes
for(int i = OrdersHistoryTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
double profit = OrderProfit() + OrderSwap() + OrderCommission();
if(profit > 0)
{
winTrades++;
totalProfit += profit;
}
else
{
loseTrades++;
totalLoss += MathAbs(profit);
}
totalTrades++;
}
}
}
}
void PrintReport()
{
double winRate = (totalTrades > 0) ?
(double)winTrades / totalTrades * 100 : 0;
double profitFactor = (totalLoss > 0) ?
totalProfit / totalLoss : 0;
double avgWin = (winTrades > 0) ?
totalProfit / winTrades : 0;
double avgLoss = (loseTrades > 0) ?
totalLoss / loseTrades : 0;
Print("=== Performance Report ===");
Print("Total Trades: ", totalTrades);
Print("Win Rate: ", DoubleToStr(winRate, 1), "%");
Print("Profit Factor: ", DoubleToStr(profitFactor, 2));
Print("Avg Win: ", DoubleToStr(avgWin, 2));
Print("Avg Loss: ", DoubleToStr(avgLoss, 2));
Print("Max Drawdown: ", DoubleToStr(maxDrawdown, 1), "%");
Print("Net Profit: ", DoubleToStr(totalProfit - totalLoss, 2));
}
=== 3. Optimization Tips ===
Strategy Tester > Expert properties > Inputs tab
เลือก parameters ที่ต้องการ optimize:
- Start: ค่าเริ่มต้น
- Step: ค่าที่เพิ่มแต่ละรอบ
- Stop: ค่าสุดท้าย
ตัวอย่าง optimize MA periods:
FastMA: Start=5, Step=5, Stop=30
SlowMA: Start=20, Step=10, Stop=100
StopLoss: Start=30, Step=10, Stop=100
Optimization criteria:
- Balance (สูงสุด)
- Profit Factor (> 1.5)
- Expected Payoff (สูงสุด)
- Maximal Drawdown (ต่ำสุด)
- Custom (ใช้ OnTester() function)
=== 4. Walk-Forward Analysis ===
แบ่งข้อมูลเป็น:
- In-sample (70%): สำหรับ optimization
- Out-of-sample (30%): สำหรับ validation
ทำซ้ำหลาย periods เพื่อตรวจสอบ robustness
Risk Management และ Best Practices
การจัดการความเสี่ยงและแนวปฏิบัติที่ดี
+------------------------------------------------------------------+
| Risk Management Functions for MQL4 |
+------------------------------------------------------------------+
#property strict
input double RiskPercent = 2.0; // Risk per trade (%)
input double MaxDailyLoss = 5.0; // Max daily loss (%)
input int MaxOpenOrders = 3; // Max concurrent orders
//--- Calculate lot size based on risk percentage
double CalculateLotSize(double stopLossPoints)
{
double balance = AccountBalance();
double riskAmount = balance * RiskPercent / 100;
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double tickSize = MarketInfo(Symbol(), MODE_TICKSIZE);
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
if(tickValue == 0 || stopLossPoints == 0) return minLot;
double lotSize = riskAmount / (stopLossPoints * tickValue / tickSize);
// Round to lot step
lotSize = MathFloor(lotSize / lotStep) * lotStep;
// Clamp to min/max
lotSize = MathMax(minLot, MathMin(maxLot, lotSize));
return NormalizeDouble(lotSize, 2);
}
//--- Check if daily loss limit reached
bool IsDailyLossExceeded()
{
double dailyLoss = 0;
datetime today = StringToTime(TimeToString(TimeCurrent(), TIME_DATE));
for(int i = OrdersHistoryTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
{
if(OrderCloseTime() >= today && OrderSymbol() == Symbol())
{
dailyLoss += OrderProfit() + OrderSwap() + OrderCommission();
}
}
}
// Add floating P/L
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol())
dailyLoss += OrderProfit() + OrderSwap() + OrderCommission();
}
}
double maxLoss = AccountBalance() * MaxDailyLoss / 100;
if(dailyLoss < -maxLoss)
{
Print("Daily loss limit reached: ", dailyLoss);
return true;
}
return false;
}
//--- Count open orders for this EA
int CountOpenOrders()
{
int count = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
count++;
}
}
return count;
}
//--- Pre-trade checks
bool CanTrade()
{
if(!IsTradeAllowed())
{
Print("Trading not allowed");
return false;
}
if(IsDailyLossExceeded())
return false;
if(CountOpenOrders() >= MaxOpenOrders)
{
Print("Max orders reached: ", MaxOpenOrders);
return false;
}
if(AccountFreeMargin() < 100)
{
Print("Insufficient free margin: ", AccountFreeMargin());
return false;
}
return true;
}
=== Best Practices Summary ===
1. ใช้ risk-based lot sizing เสมอ (ไม่เกิน 2% per trade)
2. ตั้ง Stop Loss ทุก order (ห้าม trade โดยไม่มี SL)
3. ใช้ Magic Number แยก orders ของแต่ละ EA
4. Check IsTradeAllowed() ก่อน trade
5. Handle errors จาก OrderSend() ทุกครั้ง
6. ใช้ NormalizeDouble() สำหรับราคาและ lots
7. Backtest อย่างน้อย 2 ปีก่อนใช้จริง
8. เริ่ม live ด้วย demo account ก่อน
9. ตั้ง daily loss limit
10. Log ทุก trade สำหรับ review
FAQ คำถามที่พบบ่อย
Q: MQL4 กับ MQL5 ต่างกันอย่างไร?
A: MQL5 เป็น version ใหม่กว่า syntax คล้าย C++ มากขึ้น รองรับ OOP เต็มรูปแบบ multi-threading, netting position system (MQL4 ใช้ hedging), Strategy Tester ที่เร็วกว่าและรองรับ multi-currency testing MQL4 ยังคง popular กว่าเพราะ MT4 มี brokers รองรับมากกว่า, EAs และ indicators มีให้เลือกมากกว่า, community ใหญ่กว่า สำหรับ Forex trading แนะนำเริ่มจาก MQL4 ถ้าต้องการ stocks/futures ใช้ MQL5
Q: EA ทำกำไรจริงไหม?
A: EA ที่ดีสามารถทำกำไรได้ แต่ไม่มี EA ที่ทำกำไรตลอดเวลา ปัจจัยสำคัญ strategy ต้อง robust (ทดสอบหลาย market conditions), risk management ต้องเข้มงวด (ไม่เสี่ยงเกิน 2% per trade), ต้อง adapt ตาม market changes (re-optimize periodically), execution quality ขึ้นกับ broker (spread, slippage, uptime) EA ที่ขายตาม internet ส่วนใหญ่ overfitted กับ historical data ไม่ทำกำไรจริง ควรเขียนเองและ backtest อย่างถูกวิธี
Q: ต้องรู้ programming มาก่อนไหม?
A: รู้พื้นฐาน programming จะช่วยมาก เพราะ MQL4 syntax คล้าย C ถ้าไม่เคยเขียน code เริ่มเรียน basic concepts (variables, loops, conditions, functions) ก่อน 2-4 สัปดาห์ จากนั้นเรียน MQL4 specific functions (order management, indicator functions) อีก 2-4 สัปดาห์ resources ที่ดี MQL4 Reference ใน MetaEditor (F1), MQL4 Community Forums, YouTube tutorials และ books เช่น Expert Advisor Programming for MetaTrader 4
Q: Backtesting ไว้ใจได้แค่ไหน?
A: Backtesting เป็น necessary step แต่ไม่ guarantee ผลในอนาคต ข้อจำกัด tick data quality ใน MT4 ไม่สมบูรณ์ (ใช้ Tick Data Suite สำหรับ accurate ticks), spread ใน backtest อาจไม่ตรงกับ reality (ใช้ variable spread), slippage ไม่จำลองได้สมบูรณ์, market conditions เปลี่ยน (regime change) ทำ walk-forward analysis, test หลาย time periods, หลายคู่เงิน และเริ่ม live ด้วย demo account อย่างน้อย 3 เดือนก่อนใช้เงินจริง