Bài viết gần đây

| Xây Dựng Bot Auto Trading Cho Forex MT5

Được viết bởi thanhdt vào ngày 15/11/2025 lúc 21:24 | 11 lượt xem

Hướng dẫn chi tiết cách xây dựng bot auto trading cho Forex trên MetaTrader 5 với MQL5, từ cơ bản đến nâng cao.

Xây Dựng Bot Auto Trading Cho Forex MT5

MetaTrader 5 (MT5) là một trong những nền tảng giao dịch phổ biến nhất cho Forex. Bài viết này sẽ hướng dẫn bạn cách xây dựng bot auto trading (Expert Advisor – EA) cho Forex trên MT5 sử dụng ngôn ngữ MQL5.

Tổng Quan Về Expert Advisor (EA)

Expert Advisor (EA) là chương trình tự động thực hiện giao dịch trên MetaTrader dựa trên các quy tắc đã được lập trình sẵn. EA có thể:

  • Hoạt động 24/7 không cần giám sát
  • Loại bỏ cảm xúc trong giao dịch
  • Thực hiện chiến lược phức tạp
  • Phản ứng nhanh với biến động thị trường
  • Backtest để kiểm chứng chiến lược

Chuẩn Bị Môi Trường

Cài Đặt MetaTrader 5

  1. Tải MetaTrader 5 từ trang chủ của broker
  2. Cài đặt và đăng nhập tài khoản
  3. Mở MetaEditor (F4 trong MT5)

Hiểu Về MQL5

MQL5 (MetaQuotes Language 5) là ngôn ngữ lập trình để viết EA cho MT5. Cú pháp tương tự C++ nhưng đơn giản hơn.

Cấu Trúc Cơ Bản Của EA

Template EA Đơn Giản

//+------------------------------------------------------------------+
//|                                                  SimpleEA.mq5    |
//|                                  Copyright 2025, CoinGetMarket   |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, CoinGetMarket"
#property link      "https://www.mql5.com"
#property version   "1.00"

// Input parameters
input double   LotSize = 0.01;           // Lot size
input int      MagicNumber = 123456;    // Magic number
input int      StopLoss = 50;            // Stop Loss (points)
input int      TakeProfit = 100;         // Take Profit (points)

// Global variables
int handleMA;  // Handle for Moving Average indicator

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // Create Moving Average indicator
   handleMA = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE);

   if(handleMA == INVALID_HANDLE)
   {
      Print("Error creating MA indicator");
      return(INIT_FAILED);
   }

   Print("EA initialized successfully");
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // Release indicator handle
   if(handleMA != INVALID_HANDLE)
      IndicatorRelease(handleMA);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // Check if we have enough bars
   if(Bars(_Symbol, PERIOD_CURRENT) < 20)
      return;

   // Get MA values
   double ma[];
   ArraySetAsSeries(ma, true);
   if(CopyBuffer(handleMA, 0, 0, 2, ma) < 2)
      return;

   // Get current price
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

   // Check for buy signal
   if(ma[1] < ask && ma[0] > ask)
   {
      OpenBuyOrder();
   }

   // Check for sell signal
   if(ma[1] > bid && ma[0] < bid)
   {
      OpenSellOrder();
   }
}

//+------------------------------------------------------------------+
//| Open Buy Order                                                   |
//+------------------------------------------------------------------+
void OpenBuyOrder()
{
   // Check if we already have a position
   if(PositionSelect(_Symbol))
      return;

   MqlTradeRequest request = {};
   MqlTradeResult  result = {};

   request.action = TRADE_ACTION_DEAL;
   request.symbol = _Symbol;
   request.volume = LotSize;
   request.type = ORDER_TYPE_BUY;
   request.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   request.deviation = 10;
   request.magic = MagicNumber;
   request.comment = "EA Buy Order";
   request.type_filling = ORDER_FILLING_FOK;

   // Set Stop Loss and Take Profit
   if(StopLoss > 0)
      request.sl = request.price - StopLoss * _Point;
   if(TakeProfit > 0)
      request.tp = request.price + TakeProfit * _Point;

   // Send order
   if(!OrderSend(request, result))
   {
      Print("Error opening buy order: ", GetLastError());
      return;
   }

   if(result.retcode == TRADE_RETCODE_DONE)
      Print("Buy order opened successfully. Ticket: ", result.order);
}

//+------------------------------------------------------------------+
//| Open Sell Order                                                  |
//+------------------------------------------------------------------+
void OpenSellOrder()
{
   // Check if we already have a position
   if(PositionSelect(_Symbol))
      return;

   MqlTradeRequest request = {};
   MqlTradeResult  result = {};

   request.action = TRADE_ACTION_DEAL;
   request.symbol = _Symbol;
   request.volume = LotSize;
   request.type = ORDER_TYPE_SELL;
   request.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   request.deviation = 10;
   request.magic = MagicNumber;
   request.comment = "EA Sell Order";
   request.type_filling = ORDER_FILLING_FOK;

   // Set Stop Loss and Take Profit
   if(StopLoss > 0)
      request.sl = request.price + StopLoss * _Point;
   if(TakeProfit > 0)
      request.tp = request.price - TakeProfit * _Point;

   // Send order
   if(!OrderSend(request, result))
   {
      Print("Error opening sell order: ", GetLastError());
      return;
   }

   if(result.retcode == TRADE_RETCODE_DONE)
      Print("Sell order opened successfully. Ticket: ", result.order);
}

Chiến Lược Giao Dịch

Chiến Lược 1: Moving Average Crossover

//+------------------------------------------------------------------+
//|                                                  MACrossover.mq5 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, CoinGetMarket"
#property version   "1.00"

input double   LotSize = 0.01;
input int      MagicNumber = 123456;
input int      FastMA = 10;
input int      SlowMA = 20;
input int      StopLoss = 50;
input int      TakeProfit = 100;

int handleFastMA;
int handleSlowMA;

int OnInit()
{
   handleFastMA = iMA(_Symbol, PERIOD_CURRENT, FastMA, 0, MODE_EMA, PRICE_CLOSE);
   handleSlowMA = iMA(_Symbol, PERIOD_CURRENT, SlowMA, 0, MODE_EMA, PRICE_CLOSE);

   if(handleFastMA == INVALID_HANDLE || handleSlowMA == INVALID_HANDLE)
   {
      Print("Error creating MA indicators");
      return(INIT_FAILED);
   }

   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   if(handleFastMA != INVALID_HANDLE)
      IndicatorRelease(handleFastMA);
   if(handleSlowMA != INVALID_HANDLE)
      IndicatorRelease(handleSlowMA);
}

void OnTick()
{
   if(Bars(_Symbol, PERIOD_CURRENT) < SlowMA)
      return;

   double fastMA[], slowMA[];
   ArraySetAsSeries(fastMA, true);
   ArraySetAsSeries(slowMA, true);

   if(CopyBuffer(handleFastMA, 0, 0, 2, fastMA) < 2)
      return;
   if(CopyBuffer(handleSlowMA, 0, 0, 2, slowMA) < 2)
      return;

   // Golden Cross: Fast MA crosses above Slow MA
   if(fastMA[1] <= slowMA[1] && fastMA[0] > slowMA[0])
   {
      CloseAllPositions();
      OpenBuyOrder();
   }

   // Death Cross: Fast MA crosses below Slow MA
   if(fastMA[1] >= slowMA[1] && fastMA[0] < slowMA[0])
   {
      CloseAllPositions();
      OpenSellOrder();
   }
}

void CloseAllPositions()
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket > 0)
      {
         if(PositionGetString(POSITION_SYMBOL) == _Symbol && 
            PositionGetInteger(POSITION_MAGIC) == MagicNumber)
         {
            MqlTradeRequest request = {};
            MqlTradeResult  result = {};

            request.action = TRADE_ACTION_DEAL;
            request.position = ticket;
            request.symbol = _Symbol;
            request.volume = PositionGetDouble(POSITION_VOLUME);
            request.deviation = 10;
            request.magic = MagicNumber;

            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
               request.type = ORDER_TYPE_SELL;
               request.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            }
            else
            {
               request.type = ORDER_TYPE_BUY;
               request.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            }

            request.type_filling = ORDER_FILLING_FOK;

            OrderSend(request, result);
         }
      }
   }
}

void OpenBuyOrder()
{
   if(PositionSelect(_Symbol))
      return;

   MqlTradeRequest request = {};
   MqlTradeResult  result = {};

   request.action = TRADE_ACTION_DEAL;
   request.symbol = _Symbol;
   request.volume = LotSize;
   request.type = ORDER_TYPE_BUY;
   request.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   request.deviation = 10;
   request.magic = MagicNumber;
   request.comment = "MA Crossover Buy";
   request.type_filling = ORDER_FILLING_FOK;

   if(StopLoss > 0)
      request.sl = request.price - StopLoss * _Point;
   if(TakeProfit > 0)
      request.tp = request.price + TakeProfit * _Point;

   OrderSend(request, result);
}

void OpenSellOrder()
{
   if(PositionSelect(_Symbol))
      return;

   MqlTradeRequest request = {};
   MqlTradeResult  result = {};

   request.action = TRADE_ACTION_DEAL;
   request.symbol = _Symbol;
   request.volume = LotSize;
   request.type = ORDER_TYPE_SELL;
   request.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   request.deviation = 10;
   request.magic = MagicNumber;
   request.comment = "MA Crossover Sell";
   request.type_filling = ORDER_FILLING_FOK;

   if(StopLoss > 0)
      request.sl = request.price + StopLoss * _Point;
   if(TakeProfit > 0)
      request.tp = request.price - TakeProfit * _Point;

   OrderSend(request, result);
}

Chiến Lược 2: RSI Overbought/Oversold

//+------------------------------------------------------------------+
//|                                                      RSI_EA.mq5   |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, CoinGetMarket"
#property version   "1.00"

input double   LotSize = 0.01;
input int      MagicNumber = 123456;
input int      RSIPeriod = 14;
input double   Overbought = 70;
input double   Oversold = 30;
input int      StopLoss = 50;
input int      TakeProfit = 100;

int handleRSI;

int OnInit()
{
   handleRSI = iRSI(_Symbol, PERIOD_CURRENT, RSIPeriod, PRICE_CLOSE);

   if(handleRSI == INVALID_HANDLE)
   {
      Print("Error creating RSI indicator");
      return(INIT_FAILED);
   }

   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   if(handleRSI != INVALID_HANDLE)
      IndicatorRelease(handleRSI);
}

void OnTick()
{
   if(Bars(_Symbol, PERIOD_CURRENT) < RSIPeriod)
      return;

   double rsi[];
   ArraySetAsSeries(rsi, true);

   if(CopyBuffer(handleRSI, 0, 0, 1, rsi) < 1)
      return;

   // Buy signal: RSI crosses above oversold level
   if(rsi[0] > Oversold && rsi[0] < 50)
   {
      if(!PositionSelect(_Symbol))
         OpenBuyOrder();
   }

   // Sell signal: RSI crosses below overbought level
   if(rsi[0] < Overbought && rsi[0] > 50)
   {
      if(!PositionSelect(_Symbol))
         OpenSellOrder();
   }
}

void OpenBuyOrder()
{
   MqlTradeRequest request = {};
   MqlTradeResult  result = {};

   request.action = TRADE_ACTION_DEAL;
   request.symbol = _Symbol;
   request.volume = LotSize;
   request.type = ORDER_TYPE_BUY;
   request.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   request.deviation = 10;
   request.magic = MagicNumber;
   request.comment = "RSI Buy";
   request.type_filling = ORDER_FILLING_FOK;

   if(StopLoss > 0)
      request.sl = request.price - StopLoss * _Point;
   if(TakeProfit > 0)
      request.tp = request.price + TakeProfit * _Point;

   OrderSend(request, result);
}

void OpenSellOrder()
{
   MqlTradeRequest request = {};
   MqlTradeResult  result = {};

   request.action = TRADE_ACTION_DEAL;
   request.symbol = _Symbol;
   request.volume = LotSize;
   request.type = ORDER_TYPE_SELL;
   request.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   request.deviation = 10;
   request.magic = MagicNumber;
   request.comment = "RSI Sell";
   request.type_filling = ORDER_FILLING_FOK;

   if(StopLoss > 0)
      request.sl = request.price + StopLoss * _Point;
   if(TakeProfit > 0)
      request.tp = request.price - TakeProfit * _Point;

   OrderSend(request, result);
}

Quản Lý Rủi Ro

Trailing Stop

//+------------------------------------------------------------------+
//| Trailing Stop Function                                           |
//+------------------------------------------------------------------+
void TrailingStop(int trailingPoints)
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket > 0)
      {
         if(PositionGetString(POSITION_SYMBOL) == _Symbol && 
            PositionGetInteger(POSITION_MAGIC) == MagicNumber)
         {
            double currentSL = PositionGetDouble(POSITION_SL);
            double currentTP = PositionGetDouble(POSITION_TP);
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);

            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
               double newSL = SymbolInfoDouble(_Symbol, SYMBOL_BID) - trailingPoints * _Point;

               if(newSL > currentSL && newSL > openPrice)
               {
                  MqlTradeRequest request = {};
                  MqlTradeResult  result = {};

                  request.action = TRADE_ACTION_SLTP;
                  request.position = ticket;
                  request.symbol = _Symbol;
                  request.sl = newSL;
                  request.tp = currentTP;

                  OrderSend(request, result);
               }
            }
            else // SELL
            {
               double newSL = SymbolInfoDouble(_Symbol, SYMBOL_ASK) + trailingPoints * _Point;

               if((currentSL == 0 || newSL < currentSL) && newSL < openPrice)
               {
                  MqlTradeRequest request = {};
                  MqlTradeResult  result = {};

                  request.action = TRADE_ACTION_SLTP;
                  request.position = ticket;
                  request.symbol = _Symbol;
                  request.sl = newSL;
                  request.tp = currentTP;

                  OrderSend(request, result);
               }
            }
         }
      }
   }
}

Quản Lý Vốn

//+------------------------------------------------------------------+
//| Calculate Lot Size Based on Risk                                 |
//+------------------------------------------------------------------+
double CalculateLotSize(double riskPercent, int stopLossPoints)
{
   double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
   double riskAmount = accountBalance * riskPercent / 100;

   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 / (stopLossPoints * point * tickValue / tickSize);

   // Normalize lot size
   double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

   lotSize = MathFloor(lotSize / lotStep) * lotStep;

   if(lotSize < minLot)
      lotSize = minLot;
   if(lotSize > maxLot)
      lotSize = maxLot;

   return lotSize;
}

Xử Lý Lỗi và Logging

Hàm Logging

//+------------------------------------------------------------------+
//| Log Function                                                     |
//+------------------------------------------------------------------+
void LogTrade(string message)
{
   string filename = "EA_Log_" + TimeToString(TimeCurrent(), TIME_DATE) + ".txt";
   int fileHandle = FileOpen(filename, FILE_WRITE | FILE_READ | FILE_TXT);

   if(fileHandle != INVALID_HANDLE)
   {
      FileSeek(fileHandle, 0, SEEK_END);
      FileWrite(fileHandle, TimeToString(TimeCurrent()), message);
      FileClose(fileHandle);
   }
}

//+------------------------------------------------------------------+
//| Error Handling                                                   |
//+------------------------------------------------------------------+
string GetErrorDescription(int errorCode)
{
   string errorString;

   switch(errorCode)
   {
      case 10004: errorString = "Requote"; break;
      case 10006: errorString = "Request rejected"; break;
      case 10007: errorString = "Request canceled"; break;
      case 10008: errorString = "Order placed"; break;
      case 10009: errorString = "Request partially executed"; break;
      case 10010: errorString = "Request completed"; break;
      case 10011: errorString = "Request processing error"; break;
      case 10012: errorString = "Request canceled by trader"; break;
      case 10013: errorString = "Request placed, order awaiting execution"; break;
      case 10014: errorString = "Request placed, order partially executed"; break;
      case 10015: errorString = "Request processing error"; break;
      case 10016: errorString = "Request canceled automatically by server"; break;
      case 10017: errorString = "Request partially filled"; break;
      case 10018: errorString = "Request processing error"; break;
      case 10019: errorString = "Request rejected"; break;
      case 10020: errorString = "Request canceled"; break;
      case 10021: errorString = "Request processing error"; break;
      default: errorString = "Unknown error: " + IntegerToString(errorCode);
   }

   return errorString;
}

Backtesting

Cách Backtest EA

  1. Mở Strategy Tester trong MT5 (View -> Strategy Tester hoặc Ctrl+R)
  2. Chọn EA từ danh sách
  3. Chọn symbol và timeframe
  4. Chọn khoảng thời gian backtest
  5. Chọn chế độ: Every tick (chính xác nhất)
  6. Nhấn Start để bắt đầu backtest

Đánh Giá Kết Quả

Sau khi backtest, bạn sẽ thấy các chỉ số:

  • Total Net Profit: Tổng lợi nhuận ròng
  • Profit Factor: Tỷ lệ lợi nhuận/thua lỗ
  • Expected Payoff: Lợi nhuận kỳ vọng
  • Drawdown: Mức sụt giảm tối đa
  • Win Rate: Tỷ lệ lệnh thắng
  • Sharpe Ratio: Tỷ lệ lợi nhuận/rủi ro

Tối Ưu Hóa EA

Sử Dùng Genetic Algorithm

  1. Trong Strategy Tester, chọn tab “Inputs”
  2. Đánh dấu các tham số muốn tối ưu
  3. Đặt phạm vi giá trị cho mỗi tham số
  4. Chọn “Genetic Algorithm” trong Optimization
  5. Nhấn Start để bắt đầu tối ưu

Ví Dụ Tối Ưu Hóa

input int      FastMA = 10;        // Fast MA (5-20)
input int      SlowMA = 20;        // Slow MA (15-50)
input int      StopLoss = 50;      // Stop Loss (20-100)
input int      TakeProfit = 100;   // Take Profit (50-200)

Best Practices

1. Sử Dụng Magic Number

Luôn sử dụng Magic Number để phân biệt lệnh của EA với lệnh thủ công:

input int MagicNumber = 123456;

2. Kiểm Tra Số Dư

Luôn kiểm tra số dư trước khi mở lệnh:

double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);

if(freeMargin < requiredMargin)
{
   Print("Not enough margin");
   return;
}

3. Xử Lý Lỗi

Luôn kiểm tra kết quả của OrderSend:

if(!OrderSend(request, result))
{
   Print("OrderSend error: ", GetErrorDescription(result.retcode));
   return;
}

4. Tránh Giao Dịch Quá Nhiều

Sử dụng biến để theo dõi thời gian giao dịch cuối:

datetime lastTradeTime = 0;
int minMinutesBetweenTrades = 60;

void OnTick()
{
   if(TimeCurrent() - lastTradeTime < minMinutesBetweenTrades * 60)
      return;

   // Trading logic here
   lastTradeTime = TimeCurrent();
}

Kết Luận

Xây dựng bot auto trading cho Forex MT5 là một quá trình học hỏi liên tục. Bắt đầu với các chiến lược đơn giản, backtest kỹ lưỡng, và luôn quản lý rủi ro cẩn thận.

Lưu ý quan trọng:

  • Giao dịch Forex có rủi ro cao, chỉ đầu tư số tiền bạn có thể chấp nhận mất
  • Luôn backtest EA trước khi chạy trên tài khoản thật
  • Bắt đầu với tài khoản demo để test EA
  • Monitor EA thường xuyên khi chạy trên tài khoản thật
  • Học hỏi và cải thiện EA liên tục

Tài Liệu Tham Khảo


Bài viết được biên soạn bởi CoinGetMarket – Nền tảng giáo dục về crypto và trading bot.