Bài viết gần đây
-
-
So sánh Flutter và React Native
Tháng mười một 17, 2025 -
So sánh Flutter và React Native
Tháng mười một 17, 2025 -
Chiến lược RSI 30–70 trong Bot Auto Trading Python
Tháng mười một 17, 2025 -
Chiến Lược Giao Dịch News Filter sử dụng API Python
Tháng mười một 17, 2025
| 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
- Tải MetaTrader 5 từ trang chủ của broker
- Cài đặt và đăng nhập tài khoản
- 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
- Mở Strategy Tester trong MT5 (View -> Strategy Tester hoặc Ctrl+R)
- Chọn EA từ danh sách
- Chọn symbol và timeframe
- Chọn khoảng thời gian backtest
- Chọn chế độ: Every tick (chính xác nhất)
- 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
- Trong Strategy Tester, chọn tab “Inputs”
- Đánh dấu các tham số muốn tối ưu
- Đặt phạm vi giá trị cho mỗi tham số
- Chọn “Genetic Algorithm” trong Optimization
- 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
- MQL5 Documentation
- MetaTrader 5 User Guide
- MQL5 Reference
- Kiến Thức Giao Dịch Tài Chính Crypto MQL MT5
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.