Giới thiệu
MQL5 (MetaQuotes Language 5) là ngôn ngữ lập trình chuyên biệt được thiết kế riêng cho nền tảng MetaTrader 5. Đây là công cụ mạnh mẽ nhất để xây dựng Expert Advisor (EA) — robot giao dịch tự động chạy trực tiếp trong MT5 mà không cần phần mềm bên ngoài.
Bài viết này hướng dẫn bạn lập trình EA từ đầu, bao gồm:
- Cấu trúc một Expert Advisor hoàn chỉnh
- Xử lý tín hiệu giao dịch với các hàm OnInit, OnTick, OnDeinit
- Đặt lệnh, quản lý vị thế và rủi ro
- Backtest và tối ưu hóa trên Strategy Tester
- Những lỗi thường gặp và cách khắc phục
1. MQL5 vs Python – Khi Nào Nên Dùng?

| Tiêu chí | MQL5 | Python |
|---|---|---|
| Tốc độ thực thi | ⭐⭐⭐⭐⭐ Cực nhanh | ⭐⭐⭐ Trung bình |
| Tích hợp MT5 | Native, không cần cài thêm | Cần thư viện MetaTrader5 |
| Backtest | Strategy Tester tích hợp | Tự xây dựng |
| Machine Learning | Hạn chế | Rất mạnh |
| Đa sàn | Chỉ MT5 | Binance, SSI, MT5… |
| Cộng đồng | MQL5.com marketplace | PyPI + GitHub |
| IDE | MetaEditor (tích hợp) | VSCode, PyCharm… |
Khi nào nên dùng MQL5?
- Cần tốc độ thực thi tối đa (scalping, HFT)
- Chiến lược chỉ chạy trên MT5
- Muốn bán EA trên MQL5 Market
- Cần backtest nhanh với Strategy Tester tích hợp
2. Cấu Trúc Expert Advisor (EA)
2.1. Ba hàm cốt lõi của mọi EA
Mọi Expert Advisor trong MQL5 đều có 3 hàm chính:
//+------------------------------------------------------------------+
//| Expert Advisor: EMA Crossover Bot |
//| Tác giả: DNT Academy |
//+------------------------------------------------------------------+
// === INPUT PARAMETERS (người dùng tùy chỉnh) ===
input int EMA_Fast = 20; // EMA nhanh
input int EMA_Slow = 50; // EMA chậm
input int RSI_Period = 14; // Chu kỳ RSI
input double Lots = 0.01; // Khối lượng giao dịch
input int StopLoss = 1000; // SL (points)
input int TakeProfit = 2000; // TP (points)
input int MagicNumber = 123456; // Magic Number
// === GLOBAL VARIABLES ===
int handleEMA_Fast, handleEMA_Slow, handleRSI;
double emaFast[], emaSlow[], rsiValue[];
//+------------------------------------------------------------------+
//| OnInit - Chạy 1 lần khi EA được gắn vào chart |
//+------------------------------------------------------------------+
int OnInit()
{
// Tạo handle cho các indicator
handleEMA_Fast = iMA(_Symbol, PERIOD_CURRENT, EMA_Fast, 0, MODE_EMA, PRICE_CLOSE);
handleEMA_Slow = iMA(_Symbol, PERIOD_CURRENT, EMA_Slow, 0, MODE_EMA, PRICE_CLOSE);
handleRSI = iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE);
// Kiểm tra handle hợp lệ
if(handleEMA_Fast == INVALID_HANDLE ||
handleEMA_Slow == INVALID_HANDLE ||
handleRSI == INVALID_HANDLE)
{
Print("❌ Lỗi tạo indicator handle!");
return(INIT_FAILED);
}
// Thiết lập mảng
ArraySetAsSeries(emaFast, true);
ArraySetAsSeries(emaSlow, true);
ArraySetAsSeries(rsiValue, true);
Print("✅ EA EMA Crossover Bot khởi động thành công!");
Print(" Symbol: ", _Symbol, " | Timeframe: ", EnumToString(Period()));
Print(" Lots: ", Lots, " | SL: ", StopLoss, " | TP: ", TakeProfit);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| OnTick - Chạy mỗi khi có tick mới (giá thay đổi) |
//+------------------------------------------------------------------+
void OnTick()
{
// Logic giao dịch chính (xem phần dưới)
}
//+------------------------------------------------------------------+
//| OnDeinit - Chạy khi EA bị gỡ hoặc đổi chart |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Giải phóng handle indicator
IndicatorRelease(handleEMA_Fast);
IndicatorRelease(handleEMA_Slow);
IndicatorRelease(handleRSI);
Print("🛑 EA đã dừng. Lý do: ", reason);
}
3. Logic Giao Dịch Trong OnTick
3.1. Kiểm tra tín hiệu và đặt lệnh
void OnTick()
{
// === 1. CHỈ KIỂM TRA KHI CÓ NẾN MỚI ===
static datetime lastBarTime = 0;
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
if(currentBarTime == lastBarTime)
return; // Chưa có nến mới -> bỏ qua
lastBarTime = currentBarTime;
// === 2. LẤY GIÁ TRỊ INDICATOR ===
if(CopyBuffer(handleEMA_Fast, 0, 0, 3, emaFast) < 3) return;
if(CopyBuffer(handleEMA_Slow, 0, 0, 3, emaSlow) < 3) return;
if(CopyBuffer(handleRSI, 0, 0, 3, rsiValue) < 3) return;
// === 3. PHÁT HIỆN TÍN HIỆU ===
bool buySignal = false;
bool sellSignal = false;
// Golden Cross: EMA nhanh cắt lên EMA chậm
if(emaFast[1] > emaSlow[1] && emaFast[2] <= emaSlow[2])
{
if(rsiValue[1] < 70) // RSI chưa quá mua
buySignal = true;
}
// Death Cross: EMA nhanh cắt xuống EMA chậm
if(emaFast[1] < emaSlow[1] && emaFast[2] >= emaSlow[2])
{
if(rsiValue[1] > 30) // RSI chưa quá bán
sellSignal = true;
}
// === 4. KIỂM TRA VỊ THẾ HIỆN TẠI ===
int totalPositions = CountPositions();
// === 5. THỰC THI GIAO DỊCH ===
if(buySignal && totalPositions == 0)
{
OpenOrder(ORDER_TYPE_BUY);
Print("📈 BUY Signal! EMA20=", emaFast[1], " > EMA50=", emaSlow[1],
" | RSI=", rsiValue[1]);
}
else if(sellSignal && totalPositions == 0)
{
OpenOrder(ORDER_TYPE_SELL);
Print("📉 SELL Signal! EMA20=", emaFast[1], " < EMA50=", emaSlow[1],
" | RSI=", rsiValue[1]);
}
}
3.2. Hàm đặt lệnh
bool OpenOrder(ENUM_ORDER_TYPE orderType)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
double price, sl, tp;
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
if(orderType == ORDER_TYPE_BUY)
{
price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
sl = price - StopLoss * point;
tp = price + TakeProfit * point;
}
else
{
price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
sl = price + StopLoss * point;
tp = price - TakeProfit * point;
}
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = Lots;
request.type = orderType;
request.price = price;
request.sl = sl;
request.tp = tp;
request.deviation = 20;
request.magic = MagicNumber;
request.comment = "EMA_Crossover_Bot";
if(!OrderSend(request, result))
{
Print("❌ Lỗi đặt lệnh: ", result.retcode, " - ", result.comment);
return false;
}
if(result.retcode == TRADE_RETCODE_DONE)
{
Print("✅ Lệnh thành công! Ticket: ", result.order,
" | Price: ", price, " | SL: ", sl, " | TP: ", tp);
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| Đếm số vị thế đang mở của EA này |
//+------------------------------------------------------------------+
int CountPositions()
{
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(PositionGetTicket(i) > 0)
{
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == MagicNumber)
{
count++;
}
}
}
return count;
}
4. Quản Lý Rủi Ro Nâng Cao
4.1. Trailing Stop tự động
void TrailingStop(int trailPoints)
{
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(PositionGetTicket(i) <= 0) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentSL = PositionGetDouble(POSITION_SL);
double currentTP = PositionGetDouble(POSITION_TP);
ulong ticket = PositionGetInteger(POSITION_TICKET);
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double newSL = bid - trailPoints * point;
if(newSL > currentSL && newSL > openPrice)
{
ModifyPosition(ticket, newSL, currentTP);
}
}
else // SELL
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double newSL = ask + trailPoints * point;
if(newSL < currentSL && newSL < openPrice)
{
ModifyPosition(ticket, newSL, currentTP);
}
}
}
}
bool ModifyPosition(ulong ticket, double sl, double tp)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_SLTP;
request.position = ticket;
request.sl = sl;
request.tp = tp;
return OrderSend(request, result);
}
4.2. Position Sizing theo % rủi ro
double CalculateLotSize(double riskPercent, int slPoints)
{
double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
double riskAmount = accountBalance * riskPercent / 100.0;
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double lotSize = riskAmount / (slPoints * point / tickSize * tickValue);
// Làm tròn theo lot step
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
lotSize = MathFloor(lotSize / lotStep) * lotStep;
// Giới hạn min/max
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
lotSize = MathMax(minLot, MathMin(lotSize, maxLot));
return NormalizeDouble(lotSize, 2);
}
5. Backtest và Tối Ưu Hóa
5.1. Quy trình backtest trên Strategy Tester
Để backtest EA trên MT5:
1. Mở Strategy Tester: Ctrl + R hoặc menu View → Strategy Tester
2. Cấu hình:
– Expert Advisor: chọn EA vừa viết
– Symbol: XAUUSD (hoặc symbol mong muốn)
– Period: H1
– Modeling: Every tick (chính xác nhất)
– Date: chọn khoảng thời gian backtest (khuyến nghị 1-3 năm)
– Deposit: $10,000
3. Chạy test: nhấn Start
4. Phân tích kết quả: xem tab Results, Graph, Report
5.2. Các chỉ số đánh giá EA quan trọng
| Chỉ số | Giá trị tốt | Ý nghĩa |
|---|---|---|
| Profit Factor | > 1.5 | Tổng lãi / Tổng lỗ |
| Max Drawdown | < 20% | Mức sụt giảm vốn tối đa |
| Win Rate | > 50% | Tỷ lệ lệnh thắng |
| Sharpe Ratio | > 1.0 | Lợi nhuận / Rủi ro |
| Recovery Factor | > 3.0 | Net Profit / Max Drawdown |
| Expected Payoff | > 0 | Lợi nhuận kỳ vọng mỗi lệnh |
5.3. Optimization (Tối ưu hóa tham số)
// Khi khai báo input, MT5 cho phép tối ưu hóa:
// input <type> <name> = <default>;
// Trong Strategy Tester: Start, Step, Stop
// Ví dụ tối ưu:
// EMA_Fast: Start=10, Step=5, Stop=30
// EMA_Slow: Start=30, Step=10, Stop=100
// StopLoss: Start=500, Step=100, Stop=2000
// TakeProfit: Start=1000, Step=200, Stop=4000
Lưu ý quan trọng: Tránh Over-optimization (overfitting) — khi EA hoạt động tốt trên dữ liệu quá khứ nhưng thất bại trên dữ liệu thực. Giải pháp:
- Sử dụng Forward Test (chia dữ liệu: 70% train, 30% test)
- Chạy trên nhiều symbol khác nhau
- Kiểm tra trên nhiều khung thời gian
6. Những Lỗi Thường Gặp và Cách Khắc Phục
6.1. Lỗi phổ biến khi lập trình EA
| # | Lỗi | Nguyên nhân | Giải pháp |
|---|---|---|---|
| 1 | OrderSend error 10013 | Invalid request | Kiểm tra price, sl, tp hợp lệ |
| 2 | OrderSend error 10016 | Invalid stops | SL/TP quá gần giá hiện tại (< STOPS_LEVEL) |
| 3 | OrderSend error 10014 | Invalid volume | Lots không đúng bội số volume_step |
| 4 | OrderSend error 10006 | Requote | Tăng deviation hoặc dùng ORDER_FILLING_IOC |
| 5 | EA không giao dịch | Không check nến mới | Thêm logic kiểm tra lastBarTime |
| 6 | Indicator trả về 0 | Buffer chưa sẵn sàng | Kiểm tra CopyBuffer() >= cần_thiết |
6.2. Checklist trước khi chạy EA thật
- ✅ Backtest ít nhất 1 năm dữ liệu
- ✅ Forward test trên tài khoản demo ít nhất 1 tháng
- ✅ Profit Factor > 1.5
- ✅ Max Drawdown < 20%
- ✅ Mọi lệnh đều có Stop Loss
- ✅ Position sizing theo % rủi ro (không fixed lot)
- ✅ Kiểm tra trên nhiều điều kiện thị trường (trending + ranging)
- ✅ Log đầy đủ để debug khi cần
7. Kết Luận
MQL5 là ngôn ngữ mạnh mẽ và nhanh nhất để xây dựng Expert Advisor trực tiếp trên MetaTrader 5. Với tốc độ thực thi native, Strategy Tester tích hợp, và cộng đồng MQL5 Market rộng lớn, MQL5 là lựa chọn hàng đầu cho:
- ✅ Trader muốn tự động hóa chiến lược trực tiếp trên MT5
- ✅ Scalping và chiến lược cần tốc độ cao
- ✅ Bán EA thương mại trên MQL5 Market
- ✅ Backtest và tối ưu hóa nhanh chóng
⚠️ Lưu ý: Không có EA nào hoạt động tốt mãi mãi. Thị trường thay đổi liên tục, do đó EA cần được theo dõi, đánh giá và điều chỉnh định kỳ. Luôn sử dụng quản lý rủi ro nghiêm ngặt và không bao giờ đầu tư số tiền bạn không thể chấp nhận mất.
*Bài viết thuộc chuyên mục Giao dịch | vneconomy.huongnghiepdulieu.com*
