Lập Trình Robot Giao Dịch Bằng MQL5 – Trực Tiếp Trên MT5

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?

Minh họa 1
Tiêu chíMQL5Python
Tốc độ thực thi⭐⭐⭐⭐⭐ Cực nhanh⭐⭐⭐ Trung bình
Tích hợp MT5Native, không cần cài thêmCần thư viện MetaTrader5
BacktestStrategy Tester tích hợpTự xây dựng
Machine LearningHạn chếRất mạnh
Đa sànChỉ MT5Binance, SSI, MT5…
Cộng đồngMQL5.com marketplacePyPI + GitHub
IDEMetaEditor (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.5Tổ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.0Lợi nhuận / Rủi ro
Recovery Factor> 3.0Net Profit / Max Drawdown
Expected Payoff> 0Lợ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ỗiNguyên nhânGiải pháp
1OrderSend error 10013Invalid requestKiểm tra price, sl, tp hợp lệ
2OrderSend error 10016Invalid stopsSL/TP quá gần giá hiện tại (< STOPS_LEVEL)
3OrderSend error 10014Invalid volumeLots không đúng bội số volume_step
4OrderSend error 10006RequoteTăng deviation hoặc dùng ORDER_FILLING_IOC
5EA không giao dịchKhông check nến mớiThêm logic kiểm tra lastBarTime
6Indicator trả về 0Buffer chưa sẵn sàngKiể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ặtkhô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*