| Bài 9: Equity Lockdown – Quản Trị Rủi Ro Cực Hạn Bằng Thuật Toán Hedging Tuyệt Đối

Được viết bởi thanhdt vào ngày 01/06/2026 lúc 10:18 | 31 lượt xem

Giới Thiệu: Lưới An Toàn Cuối Cùng

Trong suốt series này, chúng ta đã xây dựng nhiều lớp bảo vệ: FSM chống trùng lệnh, Reopen Buffer ngăn mở lại quá sớm, Basket TP chốt cluster khi đủ lợi nhuận. Nhưng thị trường đôi khi vượt ra ngoài mọi kịch bản đã tính toán — một sự kiện black swan, một thông báo kinh tế bất ngờ, hoặc đơn giản là lưới bị kéo dài quá mức cho phép.

Equity Lockdown là thuật toán phòng thủ cuối cùng: khi equity (vốn thực tế) rơi xuống ngưỡng nguy hiểm InpMinEqui2, hệ thống đóng băng hoàn toàn tài khoản bằng cách mở một lệnh đối ứng với khối lượng bằng đúng |ΔV| (Volume Bias). Từ thời điểm đó, mọi biến động giá không còn ảnh hưởng đến tài khoản — cho đến khi trader can thiệp thủ công.

Đây không phải là giải pháp kiếm tiền. Đây là giải pháp không mất thêm tiền khi mọi thứ đã đi sai hướng nghiêm trọng.


Kiến Trúc Equity Lockdown: Hai Tầng Ngưỡng

EQUITY LOCKDOWN — HAI TẦNG NGƯỠNG BẢO VỆ


Equity

Balance $1000

InpMinEqui1
$700 (Cảnh báo)

InpMinEqui2
$500 (LOCKDOWN)

⚠️ VÙNG CẢNH BÁO — Gửi Alert, hạn chế mở lệnh mới

⛔ VÙNG LOCKDOWN — Mở lệnh hedge |ΔV|
GlobalVariableSet(“_LOCKED”, 1.0)
Tài khoản đóng băng hoàn toàn

Alert + Push Notification

CheckEquityLockdown() kích hoạt
ΔV → OpenHedgeLock() → FROZEN

Equity đóng băng

Hình 1: Hai tầng ngưỡng — InpMinEqui1 (cảnh báo) và InpMinEqui2 (lockdown). Khi equity chạm MinEqui2, lệnh hedge được mở và tài khoản đóng băng.

Hai Tầng Ngưỡng: Cảnh Báo Và Đóng Băng


// === CẤU HÌNH EQUITY LOCKDOWN ===
input double InpMinEqui1      = 700.0;  // Ngưỡng cảnh báo (gửi Alert)
input double InpMinEqui2      = 500.0;  // Ngưỡng Lockdown (mở hedge + đóng băng)
input bool   InpEnableLockdown = true;  // Bật/tắt tính năng
input long   InpMagicNumber   = 20240101;

// GlobalVariable keys
string GV_LOCKED;       // "_LOCKED_{magic}"
string GV_HEDGE_TICKET; // "_HEDGE_TICKET_{magic}"
string GV_HEDGE_DELTA;  // "_HEDGE_DELTA_{magic}"
string GV_LOCK_TIME;    // "_LOCK_TIME_{magic}"
string GV_LOCK_EQUITY;  // "_LOCK_EQUITY_{magic}" — equity tại thời điểm lock

int OnInit()
{
    GV_LOCKED       = "_LOCKED_"       + IntegerToString((int)InpMagicNumber);
    GV_HEDGE_TICKET = "_HEDGE_TICKET_" + IntegerToString((int)InpMagicNumber);
    GV_HEDGE_DELTA  = "_HEDGE_DELTA_"  + IntegerToString((int)InpMagicNumber);
    GV_LOCK_TIME    = "_LOCK_TIME_"    + IntegerToString((int)InpMagicNumber);
    GV_LOCK_EQUITY  = "_LOCK_EQUITY_"  + IntegerToString((int)InpMagicNumber);

    // Kiểm tra trạng thái từ lần chạy trước
    if(GlobalVariableCheck(GV_LOCKED) && GlobalVariableGet(GV_LOCKED) == 1.0)
    {
        datetime lock_time   = (datetime)GlobalVariableGet(GV_LOCK_TIME);
        double   lock_equity = GlobalVariableGet(GV_LOCK_EQUITY);
        double   delta       = GlobalVariableGet(GV_HEDGE_DELTA);

        Print("⚠️ EQUITY LOCKDOWN VẪN CÒN ACTIVE từ lần trước!");
        Print("   Thời điểm lock: ", TimeToString(lock_time, TIME_DATE|TIME_SECONDS));
        Print("   Equity tại lock: $", lock_equity);
        Print("   Delta Volume: ", delta);

        Alert("EQUITY LOCKDOWN đang active! Kiểm tra tài khoản ngay!");
    }

    // Validate tham số
    if(InpMinEqui2 >= InpMinEqui1)
    {
        Alert("Lỗi: InpMinEqui2 phải nhỏ hơn InpMinEqui1!");
        return INIT_PARAMETERS_INCORRECT;
    }

    return INIT_SUCCEEDED;
}

Hàm CheckEquityLockdown: Logic Đầy Đủ


//+------------------------------------------------------------------+
//| Kiểm tra và thực thi Equity Lockdown (gọi đầu tiên trong OnTick)|
//| Trả về true nếu tài khoản đang bị lock → dừng mọi hành động    |
//+------------------------------------------------------------------+
bool CheckEquityLockdown()
{
    if(!InpEnableLockdown) return false;

    // === KIỂM TRA 1: ĐÃ LOCK CHƯA? ===
    if(GlobalVariableCheck(GV_LOCKED) && GlobalVariableGet(GV_LOCKED) == 1.0)
    {
        // Tài khoản đang bị đóng băng — báo cáo status và return
        double lock_equity  = GlobalVariableGet(GV_LOCK_EQUITY);
        double current_eq   = AccountInfoDouble(ACCOUNT_EQUITY);
        double hedge_ticket = GlobalVariableGet(GV_HEDGE_TICKET);

        Comment(StringFormat(
            "⛔ EQUITY LOCKDOWN ACTIVEn"
            "   Lock tại: $%.2fn"
            "   Hiện tại: $%.2fn"
            "   Hedge ticket: %.0fn"
            "   Để mở khóa: đóng lệnh hedge và gọi ReleaseHedgeLock()",
            lock_equity, current_eq, hedge_ticket
        ));
        return true;  // Báo hiệu cho OnTick dừng mọi hành động
    }

    double equity = AccountInfoDouble(ACCOUNT_EQUITY);

    // === KIỂM TRA 2: NGƯỠNG CẢNH BÁO (MinEqui1) ===
    if(equity  300)  // Cảnh báo mỗi 5 phút
        {
            Print("⚠️ CẢNH BÁO: Equity=", equity,
                  " = InpMinEqui2)
    {
        return false;  // An toàn, không cần làm gì
    }

    // === KÍCH HOẠT LOCKDOWN ===
    Print("🚨 EQUITY LOCKDOWN TRIGGER! Equity=", equity,
          "  0)
    {
        _SetLockdownState(hedge_ticket, delta_vol, equity);

        // Gửi thông báo khẩn cấp
        string msg = StringFormat(
            "🚨 EQUITY LOCKDOWN! Equity=$%.2f | Delta=%.2f | Ticket=%d",
            equity, delta_vol, (int)hedge_ticket
        );
        Alert(msg);
        SendNotification(msg);

        Print("✅ Lockdown thành công! Tài khoản đóng băng.");
    }
    else
    {
        // Hedge thất bại — khẩn cấp tối đa
        string err_msg = StringFormat(
            "🚨🚨 CRITICAL: Equity Lockdown THẤT BẠI! Equity=$%.2f",
            equity
        );
        Print(err_msg);
        Alert(err_msg);
        SendNotification(err_msg);
    }

    return true;
}

//+------------------------------------------------------------------+
//| Lưu trạng thái lockdown vào GlobalVariables                      |
//+------------------------------------------------------------------+
void _SetLockdownState(ulong ticket, double delta, double equity_at_lock)
{
    GlobalVariableSet(GV_LOCKED,       1.0);
    GlobalVariableSet(GV_HEDGE_TICKET, (double)ticket);
    GlobalVariableSet(GV_HEDGE_DELTA,  delta);
    GlobalVariableSet(GV_LOCK_TIME,    (double)TimeCurrent());
    GlobalVariableSet(GV_LOCK_EQUITY,  equity_at_lock);
}

Hàm OpenHedgeLock: Mở Lệnh Đối Ứng


//+------------------------------------------------------------------+
//| Mở lệnh hedge để đóng băng tài khoản                            |
//| (Tái sử dụng từ Bài 3 — Volume Bias)                            |
//+------------------------------------------------------------------+
ulong OpenHedgeLock(double buy_vol, double sell_vol, long magic)
{
    double delta     = buy_vol - sell_vol;
    double abs_delta = NormalizeDouble(MathAbs(delta), 2);

    double vol_min = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
    double vol_max = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
    double vol_step= SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

    // Edge case: delta quá nhỏ
    if(abs_delta < vol_min)
    {
        Print("HedgeLock: |ΔV|=", abs_delta, "  vol_max)
    {
        Print("CẢNH BÁO: |ΔV|=", abs_delta,
              " > vol_max=", vol_max, ". Giảm xuống vol_max.");
        abs_delta = vol_max;
    }

    // Làm tròn theo vol_step của broker
    abs_delta = MathRound(abs_delta / vol_step) * vol_step;
    abs_delta = NormalizeDouble(abs_delta, 2);

    // Hướng lệnh đối ứng
    ENUM_ORDER_TYPE hedge_type = (delta > 0)
                                 ? ORDER_TYPE_SELL   // Thừa Buy → bán
                                 : ORDER_TYPE_BUY;   // Thừa Sell → mua

    double price = (hedge_type == ORDER_TYPE_BUY)
                   ? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
                   : SymbolInfoDouble(_Symbol, SYMBOL_BID);

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

    request.action    = TRADE_ACTION_DEAL;
    request.symbol    = _Symbol;
    request.volume    = abs_delta;
    request.type      = hedge_type;
    request.price     = NormalizeDouble(price,
                        (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
    request.deviation = 50;  // Slippage cao hơn bình thường (tình huống khẩn)
    request.magic     = magic;
    request.comment   = "EQUITY_LOCKDOWN";

    bool sent = OrderSend(request, result);

    if(!sent || result.retcode != TRADE_RETCODE_DONE)
    {
        Print("HedgeLock FAILED: retcode=", result.retcode,
              " error=", GetLastError());
        ResetLastError();
        return 0;
    }

    Print("HedgeLock DONE: ticket=", result.deal,
          " type=", EnumToString(hedge_type),
          " vol=", abs_delta,
          " price=", result.price);

    return result.deal;
}

Hàm ReleaseHedgeLock: Mở Khóa Thủ Công


//+------------------------------------------------------------------+
//| Mở khóa Equity Lockdown — PHẢI được trader xác nhận thủ công   |
//| Trả về true nếu mở khóa thành công                              |
//+------------------------------------------------------------------+
bool ReleaseHedgeLock()
{
    // Kiểm tra có đang lock không
    if(!GlobalVariableCheck(GV_LOCKED) ||
       GlobalVariableGet(GV_LOCKED) != 1.0)
    {
        Print("Không có Lockdown nào để giải phóng.");
        return false;
    }

    ulong hedge_ticket = (ulong)GlobalVariableGet(GV_HEDGE_TICKET);

    // Kiểm tra lệnh hedge còn tồn tại không
    if(hedge_ticket > 0 && PositionSelectByTicket(hedge_ticket))
    {
        double hedge_vol  = PositionGetDouble(POSITION_VOLUME);
        ENUM_POSITION_TYPE hedge_type =
            (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

        Print("CẢNH BÁO: Lệnh hedge ticket=", hedge_ticket,
              " vol=", hedge_vol,
              " type=", EnumToString(hedge_type),
              " vẫn đang mở!");
        Print("Hãy đóng lệnh hedge này trước khi mở khóa.");
        Print("Để đóng thủ công trên MetaTrader, tìm ticket=", hedge_ticket);
        return false;
    }

    // Lấy thông tin để log
    datetime lock_time   = (datetime)GlobalVariableGet(GV_LOCK_TIME);
    double   lock_equity = GlobalVariableGet(GV_LOCK_EQUITY);
    double   lock_delta  = GlobalVariableGet(GV_HEDGE_DELTA);
    double   unlock_equity = AccountInfoDouble(ACCOUNT_EQUITY);

    Print("=== EQUITY LOCKDOWN RELEASED ===");
    Print("   Lock time: ", TimeToString(lock_time, TIME_DATE|TIME_SECONDS));
    Print("   Equity tại lock: $", lock_equity);
    Print("   Equity hiện tại: $", unlock_equity);
    Print("   Thay đổi: $", unlock_equity - lock_equity);
    Print("   Delta vol khi lock: ", lock_delta);

    // Xóa tất cả GlobalVariables liên quan
    GlobalVariableDel(GV_LOCKED);
    GlobalVariableDel(GV_HEDGE_TICKET);
    GlobalVariableDel(GV_HEDGE_DELTA);
    GlobalVariableDel(GV_LOCK_TIME);
    GlobalVariableDel(GV_LOCK_EQUITY);

    Print("✅ Lockdown đã được giải phóng. EA tiếp tục hoạt động.");
    return true;
}

Tại Sao Bắt Buộc Phải Đóng Lệnh Hedge Trước?

Nếu mở khóa khi lệnh hedge vẫn còn mở, EA sẽ tự động mở thêm lệnh lưới mới — trong khi lệnh hedge đang “triệt tiêu” chúng. Kết quả là tài khoản có nhiều lệnh dư thừa, chi phí spread/commission tăng, và khó quản lý hơn nhiều. Luôn đóng lệnh hedge trước, kiểm tra lại trạng thái tài khoản, rồi mới mở khóa.


Tích Hợp Với Toàn Bộ Series: Thứ Tự Ưu Tiên Trong OnTick


void OnTick()
{
    // === ƯU TIÊN 1: EQUITY LOCKDOWN (cao nhất) ===
    // Nếu locked → return ngay, không làm gì khác
    if(CheckEquityLockdown()) return;

    // === ƯU TIÊN 2: REOPEN BUFFER ===
    // Mở khóa các tầng đã đủ điều kiện
    CheckReopenBuffer();

    // === ƯU TIÊN 3: BASKET TP ===
    // Kiểm tra và đóng cluster nếu đủ target
    if(CheckBasketTP()) return;  // Sau BasketTP, cluster đã reset

    // === ƯU TIÊN 4: CHỐT LỜI ĐƠN LẺ ===
    int total = PositionsTotal();
    for(int i = total - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if(ticket == 0) continue;
        if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
        if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;

        string comment = PositionGetString(POSITION_COMMENT);
        int step = GetStepFromComment(comment);
        if(step == STEP_INVALID) continue;

        // Trailing Plowing trước
        bool plowed = CheckTrailingPlowing(ticket, step);
        if(plowed) continue;
    }

    // Gặt X sau khi đã chạy plowing
    CheckHarvestX();

    // === ƯU TIÊN 5: MỞ LỆNH MỚI (thấp nhất) ===
    // Grid Engine — chỉ mở nếu tầng chưa có lệnh VÀ chưa bị lock
    // (không trình bày ở đây)
}

Monitoring Dashboard: Hiển Thị Toàn Bộ Trạng Thái


void DisplayFullStatus()
{
    double equity   = AccountInfoDouble(ACCOUNT_EQUITY);
    double balance  = AccountInfoDouble(ACCOUNT_BALANCE);
    double floating = GetFloatingProfit(InpMagicNumber);
    double realized = GetRealizedProfit(InpMagicNumber);
    bool   locked   = (GlobalVariableCheck(GV_LOCKED) &&
                       GlobalVariableGet(GV_LOCKED) == 1.0);

    int locked_steps = 0;
    for(int s = -InpMaxStep; s <= InpMaxStep; s++)
        if(GetActiveFlag(InpMagicNumber, s) != FLAG_NONE) locked_steps++;

    string status = StringFormat(
        "=== EA STATUS ===n"
        "Balance: $%.2f | Equity: $%.2f (%.1f%%)n"
        "Floating: $%.2f | Realized: $%.2fn"
        "Total Net: $%.2f / Basket Target: $%.2fn"
        "Equity Lock: %sn"
        "Reopen Locked Steps: %dn"
        "MinEqui1: $%.0f | MinEqui2: $%.0f",
        balance, equity, (equity/balance)*100,
        floating, realized,
        realized + floating, InpBasketTarget,
        locked ? "⛔ ACTIVE" : "✅ CLEAR",
        locked_steps,
        InpMinEqui1, InpMinEqui2
    );

    Comment(status);
}

Edge Cases Đặc Biệt Của Equity Lockdown

Tình huống Nguyên nhân Xử lý
Hedge thất bại lần đầu Mạng chậm, requote Tick tiếp theo sẽ thử lại (chưa set GV_LOCKED)
Equity dao động quanh MinEqui2 Spread thay đổi GV_LOCKED = 1 sau lần đầu, tránh mở hedge lần 2
ΔV = 0 (đã cân bằng) Lưới đối xứng Vẫn set GV_LOCKED để chặn lệnh mới
Equity phục hồi sau khi lock Giá đảo chiều KHÔNG tự động mở khóa — trader phải quyết định
Stop Out xảy ra trước khi hedge Equity rớt quá nhanh Không tránh được — InpMinEqui2 cần đủ cao

Cách Chọn InpMinEqui2 Hợp Lý


// Công thức tham khảo:
// InpMinEqui2 >= Margin_required + (max_drawdown_acceptable / 2)

// Ví dụ:
// Balance = $1000
// Margin mỗi lot EURUSD = $33 (leverage 1:30)
// Tổng margin 10 lots = $330
// InpMinEqui2 = $330 + $100 (buffer) = $430
// Nên đặt: InpMinEqui2 = $500 (làm tròn, thêm margin an toàn)

// Quy tắc: InpMinEqui2 KHÔNG bao giờ được thấp hơn mức margin call của broker
double CalcSafeMinEqui2()
{
    double margin_call_level = AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL);
    double equity            = AccountInfoDouble(ACCOUNT_EQUITY);
    double margin            = AccountInfoDouble(ACCOUNT_MARGIN);

    double margin_call_equity = margin * (margin_call_level / 100.0);
    double safe_min          = margin_call_equity * 1.5;  // 50% buffer

    Print("Margin Call Level: ", margin_call_level, "%");
    Print("Margin hiện tại: $", margin);
    Print("Equity tại Margin Call: $", margin_call_equity);
    Print("Khuyến nghị InpMinEqui2 >= $", safe_min);

    return safe_min;
}

Tổng Kết Series: 9 Thuật Toán Hoàn Chỉnh

Chúng ta đã đi qua toàn bộ 9 thuật toán cốt lõi của hệ thống lưới MQL5 nâng cao:

Bài Thuật toán Chức năng chính Key function
3 Volume Bias Tính ΔV = V_Buy – V_Sell CalculateVolume()
4 Comment Parser Khôi phục tầng sau restart GetStepFromComment()
5 Reopen Buffer Khóa tầng sau chốt lời IsSafeToReopen()
6 FSM Chống Trùng READY/BUSY với timestamp GetFSMState()
7 Gặt X Chốt từng tầng tại P±X CheckHarvestX()
8 Trailing Plowing 3 stage Act/Ret CheckTrailingPlowing()
9 Reopen Buffer (2 cờ) _X_CLOSED_ / _PLOW_CLOSED_ CheckReopenBuffer()
10 Basket TP Total_Net = realized + floating CheckBasketTP()
11 Equity Lockdown Đóng băng tài khoản khi nguy hiểm CheckEquityLockdown()

Mỗi thuật toán giải quyết một vấn đề cụ thể và phối hợp với các thuật toán khác qua GlobalVariables — bộ nhớ dùng chung duy nhất trong hệ thống. Đây là thiết kế loosely coupled: mỗi module có thể được bật/tắt độc lập mà không phá vỡ phần còn lại.


Sẵn Sàng Xây Dựng EA Thực Chiến Với Cả 9 Thuật Toán?

Khóa học MQL5 Nâng Cao tổng hợp toàn bộ series này thành một EA hoàn chỉnh với source code production-ready, backtesting trên nhiều năm dữ liệu, và framework deployment lên VPS. Bạn sẽ ra khỏi khóa học với một EA thực sự có thể chạy trên tài khoản thực.

Xem Khóa Học MQL5 Nâng Cao →

Đặng Trí Thanh

Đặng Trí Thanh

Giám đốc Công nghệ · DNT Digital · Giảng viên HNDL
1.322 Bài viết
15.4k Người theo dõi
120k+ Lượt đọc

Đào tạo và triển khai thực chiến Python, MT5 và hệ thống bot auto trading / IB cho học viên và doanh nghiệp.