Trang chủ Kiến thức Functions trong MQL5: Khai Báo và Sử Dụng Hàm
Knowledge

Functions trong MQL5: Khai Báo và Sử Dụng Hàm

14 tháng 11, 2025

Hướng dẫn tạo functions với parameters, return values, overloading và best practices trong EA development.

Functions: Tổ Chức Code Hiệu Quả

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.

Khai Báo Function Cơ Bản

// 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);
}

Parameters: Pass by Value vs Pass by Reference

// 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);
}

Default Parameters

// 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

Function Overloading

// 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
}

Return Early Pattern

// ❌ 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();
}

Helper Functions Pattern

// 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();
    }
}

Recursive Functions

// 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 Variables in Functions

// 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
    }
}

Best Practices

  • ✅ One function = one purpose (Single Responsibility)
  • ✅ Function names should be verbs: CalculateLotSize(), OpenTrade()
  • ✅ Keep functions short (<50 lines)
  • ✅ Use return early pattern
  • ✅ Pass by reference for large objects/arrays
  • ✅ Add comments for complex logic
  • ⛔ Tránh global variables trong functions
  • ⛔ Không modify input parameters (use const if possible)
  • ⛔ Tránh quá nhiều parameters (>5)

Bài Tập Thực Hành

  1. Viết function IsPriceNearMA(double price, int maPeriod, double tolerance)
  2. Tạo function GetPositionProfit(long magic) tính tổng profit của positions
  3. Implement CloseAllPositions(ENUM_POSITION_TYPE type = -1) với optional type filter

Tài Liệu Tham Khảo