Hướng dẫn tạo functions với parameters, return values, overloading và best practices trong EA development.
Functions (hàm) là khối code có thể tái sử dụng, giúp tổ chức logic phức tạp thành các đơn vị nhỏ, dễ quản lý. Đây là nền tảng của clean code trong MQL5.
// Syntax: return_type FunctionName(parameters) { code }
// Function không trả về giá trị (void)
void PrintHello() {
Print("Hello, MQL5!");
}
// Function trả về integer
int Add(int a, int b) {
return a + b;
}
// Function trả về double
double CalculateLotSize(double riskPercent, double stopLossPips) {
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double riskAmount = balance * riskPercent / 100.0;
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double pipValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE) * 10;
double lotSize = riskAmount / (stopLossPips * pipValue);
lotSize = NormalizeDouble(lotSize, 2);
return lotSize;
}
// Sử dụng functions
void OnTick() {
PrintHello(); // Call void function
int sum = Add(10, 20); // Call with return value
Print("Sum: ", sum);
double lot = CalculateLotSize(2.0, 50); // 2% risk, 50 pips SL
Print("Lot size: ", lot);
}
// Pass by value (copy of variable)
void ModifyValue(int value) {
value = value + 10;
Print("Inside function: ", value); // 20
}
void TestPassByValue() {
int x = 10;
ModifyValue(x);
Print("After function: ", x); // Still 10 (not modified)
}
// Pass by reference (actual variable)
void ModifyReference(int &value) { // Note the &
value = value + 10;
Print("Inside function: ", value); // 20
}
void TestPassByReference() {
int x = 10;
ModifyReference(x);
Print("After function: ", x); // Now 20 (modified!)
}
// Practical example: Multiple return values via references
void CalculateMacdValues(int shift, double &macdMain, double &macdSignal, double &macdHistogram) {
double macd[];
double signal[];
ArraySetAsSeries(macd, true);
ArraySetAsSeries(signal, true);
int handle = iMACD(_Symbol, PERIOD_CURRENT, 12, 26, 9, PRICE_CLOSE);
CopyBuffer(handle, 0, shift, 1, macd);
CopyBuffer(handle, 1, shift, 1, signal);
macdMain = macd[0];
macdSignal = signal[0];
macdHistogram = macdMain - macdSignal;
}
// Usage
void OnTick() {
double main, signal, histogram;
CalculateMacdValues(0, main, signal, histogram);
Print("MACD Main: ", main, " Signal: ", signal, " Histogram: ", histogram);
}
// Parameters with default values
double CalculateMA(int period = 50, ENUM_MA_METHOD method = MODE_SMA, int shift = 0) {
double ma = iMA(_Symbol, PERIOD_CURRENT, period, shift, method, PRICE_CLOSE);
return ma;
}
// Call with different combinations
void OnTick() {
double ma1 = CalculateMA(); // Uses defaults: 50, SMA, 0
double ma2 = CalculateMA(200); // 200, SMA, 0
double ma3 = CalculateMA(50, MODE_EMA); // 50, EMA, 0
double ma4 = CalculateMA(100, MODE_SMA, 1); // 100, SMA, 1
}
// ⚠️ Default parameters must be at the end
void ValidFunction(int required, double optional = 1.0) { } // ✅ OK
// void InvalidFunction(int optional = 10, double required) { } // ❌ ERROR
// Same function name, different parameters
// Version 1: Buy with default lot
bool OpenBuy() {
return OpenBuy(0.1); // Call version 2 with default lot
}
// Version 2: Buy with specified lot
bool OpenBuy(double lotSize) {
return OpenBuy(lotSize, 50, 100); // Call version 3 with defaults
}
// Version 3: Buy with full parameters
bool OpenBuy(double lotSize, int stopLossPips, int takeProfitPips) {
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double sl = ask - stopLossPips * point;
double tp = ask + takeProfitPips * point;
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = lotSize;
request.type = ORDER_TYPE_BUY;
request.price = ask;
request.sl = sl;
request.tp = tp;
request.deviation = 10;
request.magic = EA_MAGIC;
return OrderSend(request, result);
}
// Usage - all valid:
void OnTick() {
OpenBuy(); // Default: 0.1 lot, 50 SL, 100 TP
OpenBuy(0.2); // 0.2 lot, 50 SL, 100 TP
OpenBuy(0.1, 100, 200); // 0.1 lot, 100 SL, 200 TP
}
// ❌ Bad: Nested ifs
bool CanTrade() {
if(IsTradingTime()) {
if(IsSpreadOK()) {
if(IsRiskAcceptable()) {
if(HasSignal()) {
return true;
}
}
}
}
return false;
}
// ✅ Good: Return early
bool CanTrade_Better() {
if(!IsTradingTime()) return false;
if(!IsSpreadOK()) return false;
if(!IsRiskAcceptable()) return false;
if(!HasSignal()) return false;
return true;
}
// Practical example
bool OpenTradeIfValid() {
// Check 1: Trading time
MqlDateTime timeStruct;
TimeToStruct(TimeCurrent(), timeStruct);
if(timeStruct.hour < 8 || timeStruct.hour >= 17) {
Print("Outside trading hours");
return false;
}
// Check 2: Spread
int spread = (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
if(spread > 20) {
Print("Spread too high: ", spread);
return false;
}
// Check 3: Max orders
if(PositionsTotal() >= 5) {
Print("Max orders reached");
return false;
}
// All checks passed - open trade
Print("Opening trade");
return OpenBuy();
}
// Small, focused functions
// Check spread
bool IsSpreadAcceptable(int maxSpread = 20) {
int spread = (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
return (spread <= maxSpread);
}
// Check trading time
bool IsTradingHours(int startHour = 8, int endHour = 17) {
MqlDateTime timeStruct;
TimeToStruct(TimeCurrent(), timeStruct);
return (timeStruct.hour >= startHour && timeStruct.hour < endHour);
}
// Check max positions
bool CanOpenNewPosition(int maxPositions = 5) {
return (PositionsTotal() < maxPositions);
}
// Count positions by type
int CountPositions(ENUM_POSITION_TYPE type, long magic = 0) {
int count = 0;
for(int i = 0; i < PositionsTotal(); i++) {
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(magic > 0 && PositionGetInteger(POSITION_MAGIC) != magic)
continue;
if(PositionGetInteger(POSITION_TYPE) == type)
count++;
}
return count;
}
// Main logic uses helper functions
void OnTick() {
// Clear, readable checks
if(!IsSpreadAcceptable(20)) return;
if(!IsTradingHours(8, 17)) return;
if(!CanOpenNewPosition(5)) return;
int signal = CheckSignal();
if(signal == 1 && CountPositions(POSITION_TYPE_BUY) == 0) {
OpenBuy();
}
}
// Function that calls itself
// Calculate factorial: 5! = 5 * 4 * 3 * 2 * 1 = 120
int Factorial(int n) {
if(n <= 1) return 1; // Base case
return n * Factorial(n - 1); // Recursive call
}
// Calculate Fibonacci: 0, 1, 1, 2, 3, 5, 8, 13, ...
int Fibonacci(int n) {
if(n <= 1) return n; // Base cases
return Fibonacci(n - 1) + Fibonacci(n - 2); // Recursive calls
}
// ⚠️ Recursion can be slow and cause stack overflow!
// ✅ Better: Use iteration when possible
int Fibonacci_Iterative(int n) {
if(n <= 1) return n;
int a = 0, b = 1;
for(int i = 2; i <= n; i++) {
int temp = a + b;
a = b;
b = temp;
}
return b;
}
// Static variable retains value between calls
int GetAndIncrementCounter() {
static int counter = 0; // Initialized only once
counter++;
return counter;
}
void OnTick() {
Print(GetAndIncrementCounter()); // 1
Print(GetAndIncrementCounter()); // 2
Print(GetAndIncrementCounter()); // 3
// Counter persists between calls
}
// Practical: Detect new bar
bool IsNewBar() {
static datetime lastBarTime = 0;
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
if(currentBarTime != lastBarTime) {
lastBarTime = currentBarTime;
return true;
}
return false;
}
void OnTick() {
if(IsNewBar()) {
Print("New bar detected!");
// Execute logic only on new bars
}
}
CalculateLotSize(), OpenTrade()IsPriceNearMA(double price, int maPeriod, double tolerance)GetPositionProfit(long magic) tính tổng profit của positionsCloseAllPositions(ENUM_POSITION_TYPE type = -1) với optional type filter