| Nhị Quái V9.2.1 Pro: 6 Tuyến Phòng Thủ Toàn Diện Cho Nhà Đầu Tư Quant

Được viết bởi thanhdt vào ngày 25/04/2026 lúc 17:26 | 30 lượt xem

🛡️ 6 Tuyến Phòng Thủ Của Nhị Quái V9

Hệ thống bảo vệ đa tầng giúp nhà đầu tư kiểm soát rủi ro tuyệt đối:

  • Lớp 1: Giãn Step Thông Minh (Dynamic Step) – Tự động giãn khoảng cách Grid khi thị trường biến động mạnh.
  • Lớp 2: Hồi phục Thông minh (Smart Recovery) – Thuật toán tối ưu Lot Size để thoát lệnh nhanh.
  • Lớp 3: Chốt lời ròng (Realized Basket TP) – Quản lý lợi nhuận theo cụm giúp bảo vệ Margin.
  • Lớp 4: Bộ lọc Xu hướng & Khoảng cách (EMA Filter) – Chỉ giao dịch khi đúng xu hướng EMA.
  • Lớp 5: Watchdog (Hard Drawdown Cut) – Dừng giao dịch khẩn cấp khi chạm ngưỡng rủi ro.
  • Lớp 6: Tấm khiên cuối cùng (Auto Lockdown/Hedge) – Tự động Hedge 1:1 bảo vệ tài khoản tuyệt đối.

💻 Mã nguồn MQL5 (V9.2.1 Pro)

Anh/Chị có thể tham khảo mã nguồn chi tiết dưới đây:

//+------------------------------------------------------------------+
//|                                  Bot_Nhi_Quai_V9.2.1_Pro_23_04_1.mq5 |
//|                                  Copyright 2026, NQ              |
//|                    VERSION: 9.2.1 PRO (ULTRA STABLE + SMART RECOVERY)|
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, NQ"
#property link      "https://google.com"
#property version   "9.22"
#property strict

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Arrays\ArrayInt.mqh>

void ReportSymbolSpec() {
    Print("=== KIỂM TRA THÔNG SỐ SÀN (9.21.1 - PRO SMART) ===");
    Print("Symbol: ", _Symbol);
    if(HistorySelect(TimeCurrent()-3600*24, TimeCurrent())) {
        double total_comm = 0, total_vol = 0;
        for(int i=HistoryDealsTotal()-1; i>=0; i--) {
            ulong t = HistoryDealGetTicket(i);
            if(HistoryDealGetString(t, DEAL_SYMBOL) == _Symbol) {
                total_comm += HistoryDealGetDouble(t, DEAL_COMMISSION);
                total_vol += HistoryDealGetDouble(t, DEAL_VOLUME);
            }
        }
        if(total_vol > 0) {
            Print("--- DỮ LIỆU THỰC TẾ (24H QUA) ---");
            Print("Tổng Volume đã đánh: ", NormalizeDouble(total_vol, 2));
            Print("Tổng Phí Hoa Hồng: ", NormalizeDouble(total_comm, 2));
            Print("=> Phí trung bình: ", NormalizeDouble(MathAbs(total_comm/total_vol), 2), " USD / 1.0 Lot");
        }
    }
    Print("======================================");
}

//--- INPUT PARAMETERS ---
input group "=== GENERAL SETTINGS ==="
input string   InpBotName        = "NHI QUAI V9.2.1 PRO";
input string   InpStoragePrefix  = "NHỊ QUÁI V9"; 
input string   InpStartTime      = "00:00";
input string   InpEndTime        = "23:59";
input double   InpMaxSpread      = 50.0;
input double   InpSlippageAllow  = 50.0;
input int      InpMaxPositions   = 500;
input double   InpMaxPriceLimit  = 1000000.0;

input group "=== BUY CLUSTER SETTINGS ==="
input bool     InpBuyEnable      = true;
input double   InpBuyLot         = 0.01;
input double   InpBuyStep        = 5.0;
input double   InpBuyX           = 5.0;
input double   InpBuyMinEqui1    = 8000.0;            
input double   InpBuyMinEqui2    = 8000.0;            
input double   InpBuyMaxLot      = 5.0;               
input int      InpBuyMaxOrders   = 15;                 
input int      InpBuyMagic       = 2111;

input group "=== SELL CLUSTER SETTINGS ==="
input bool     InpSellEnable     = true;
input double   InpSellLot        = 0.01;
input double   InpSellStep       = 5.0;
input double   InpSellX          = 5.0;
input double   InpSellMinEqui1   = 8000.0;            
input double   InpSellMinEqui2   = 8000.0;            
input double   InpSellMaxLot     = 5.0;               
input int      InpSellMaxOrders  = 15;                 
input int      InpSellMagic      = 2112;

input group "=== NHỊ QUÁI (BASKET TP) SETTINGS ==="
input double   InpBuyBasketTP    = 5.0;
input double   InpBuyTPMult      = 1.0;
input double   InpBuyProfitOffset = 0.0;           
input double   InpSellBasketTP   = 5.0;
input double   InpSellTPMult     = 1.0;
input double   InpSellProfitOffset = 0.0;          
input double   InpBasketSafety   = 5.0;

input group "=== WATCHDOG SETTINGS ==="
input double   InpMinProfit      = 5.0;
input double   InHarvestLockNet  = -500.0;         
input double   InpMaxDrawdownCut = 0.0;         
input int      InpCooldownMinutes= 240;            
input bool     InpShowMarkers    = true;

input group "=== CAU HINH HEDGE (GIA TANG) ==="
input bool     InpDynamicStep    = true;           
input double   InpMinStepDistance = 5.0;          

input group "=== DCA DIRECTIONAL SETTINGS ==="
input bool     InpDCAEnable      = true;           
input double   InpDCATarget      = 5.0;           
input double   InpLotMultiplier  = 1.5;            

input group "=== TREND PREDICTION (SMART FILTER) ==="
input bool     InpUseTrendFilter = false;           
input int      InpMAPeriod       = 200;            
input ENUM_TIMEFRAMES InpMATF    = PERIOD_H1;      
input int      InpMAMethod       = MODE_EMA;       
input int      InpMAAppliedPrice = PRICE_CLOSE;    
input double   InpMADistanceLimit = 15.0;          

input group "=== SMART RECOVERY (NEW) ==="
input bool     InpEnableSmartRec = true;           // Kich hoat phuc hoi thong minh
input double   InpMaxRecMult     = 3.0;            // Toi da gap 3 lan Lot DCA
input double   InpTargetRetrace  = 2.0;            // Khoang hoi mong muon (Gia)
input int      InpSmartRecStart  = 3;              // Bat dau tu lenh thu may (Default: 3)

//--- GLOBAL VARIABLES ---
CTrade         m_trade_buy, m_trade_sell;
CSymbolInfo    m_symbol;
CPositionInfo  m_position;

int            m_ma_handle = INVALID_HANDLE;

double         m_buy_p0 = 0, m_sell_p0 = 0;
int            m_buy_reset_count = 0, m_sell_reset_count = 0;
int            m_buy_tp_count_x = 0, m_sell_tp_count_x = 0;
bool           m_buy_hedged = false, m_sell_hedged = false;

int            m_buy_surplus_count = 0, m_sell_surplus_count = 0;
double         m_buy_surplus_lot = 0, m_sell_surplus_lot = 0;
double         m_buy_realized_profit = 0, m_sell_realized_profit = 0; 

ulong          m_pos_buy_tickets[], m_pos_sell_tickets[];
int            m_pos_buy_steps[], m_pos_sell_steps[];
ENUM_POSITION_TYPE m_pos_buy_types[], m_pos_sell_types[];

bool           m_need_history_scan_buy = true;
bool           m_need_history_scan_sell = true;
datetime       m_last_trade_tick[3000];
int            m_last_trade_step[3000];

//--- HELPERS ---
bool IsClusterLocked(int m) { return GlobalVariableCheck(GetGVPrefix(m) + "_Locked"); }
void SetClusterLocked(int m, bool l) { string gv = GetGVPrefix(m) + "_Locked"; if(l) GlobalVariableSet(gv, 1.0); else GlobalVariableDel(gv); }
string GetGVPrefix(int magic) { return InpStoragePrefix + "_" + IntegerToString((int)AccountInfoInteger(ACCOUNT_LOGIN)) + "_" + IntegerToString(magic); }

int OnInit()
{
    if(!m_symbol.Name(_Symbol)) return(INIT_FAILED);
    m_trade_buy.SetExpertMagicNumber(InpBuyMagic);
    m_trade_buy.SetDeviationInPoints((ulong)InpSlippageAllow);
    m_trade_sell.SetExpertMagicNumber(InpSellMagic);
    m_trade_sell.SetDeviationInPoints((ulong)InpSlippageAllow);
    
    m_symbol.RefreshRates();
    double current_bid = m_symbol.Bid();

    string gv_buy_p0 = GetGVPrefix(InpBuyMagic) + "_P0";
    string gv_sell_p0 = GetGVPrefix(InpSellMagic) + "_P0";

    if(GlobalVariableCheck(gv_buy_p0)) { m_buy_p0 = GlobalVariableGet(gv_buy_p0); if(m_buy_p0 < 100 || m_buy_p0 > InpMaxPriceLimit) { m_buy_p0 = current_bid; GlobalVariableSet(gv_buy_p0, m_buy_p0); } }
    else { m_buy_p0 = current_bid; GlobalVariableSet(gv_buy_p0, m_buy_p0); }
    
    if(GlobalVariableCheck(gv_sell_p0)) { m_sell_p0 = GlobalVariableGet(gv_sell_p0); if(m_sell_p0 < 100 || m_sell_p0 > InpMaxPriceLimit) { m_sell_p0 = current_bid; GlobalVariableSet(gv_sell_p0, m_sell_p0); } }
    else { m_sell_p0 = current_bid; GlobalVariableSet(gv_sell_p0, m_sell_p0); }

    string gv_start_buy = GetGVPrefix(InpBuyMagic) + "_StartTime";
    string gv_start_sell = GetGVPrefix(InpSellMagic) + "_StartTime";
    if(!GlobalVariableCheck(gv_start_buy)) GlobalVariableSet(gv_start_buy, (double)TimeCurrent());
    if(!GlobalVariableCheck(gv_start_sell)) GlobalVariableSet(gv_start_sell, (double)TimeCurrent());

    if(GlobalVariableCheck(GetGVPrefix(InpBuyMagic)+"_ResetCount")) m_buy_reset_count = (int)GlobalVariableGet(GetGVPrefix(InpBuyMagic)+"_ResetCount");
    if(GlobalVariableCheck(GetGVPrefix(InpSellMagic)+"_ResetCount")) m_sell_reset_count = (int)GlobalVariableGet(GetGVPrefix(InpSellMagic)+"_ResetCount");
    if(GlobalVariableCheck(GetGVPrefix(InpBuyMagic)+"_HitXCount")) m_buy_tp_count_x = (int)GlobalVariableGet(GetGVPrefix(InpBuyMagic)+"_HitXCount");
    if(GlobalVariableCheck(GetGVPrefix(InpSellMagic)+"_HitXCount")) m_sell_tp_count_x = (int)GlobalVariableGet(GetGVPrefix(InpSellMagic)+"_HitXCount");
    
    for(int i=0; i<3000; i++) { m_last_trade_tick[i] = 0; m_last_trade_step[i] = -999999; }

    if(InpUseTrendFilter) {
        m_ma_handle = iMA(_Symbol, InpMATF, InpMAPeriod, 0, (ENUM_MA_METHOD)InpMAMethod, (ENUM_APPLIED_PRICE)InpMAAppliedPrice);
    }

    EventSetTimer(1);
    return(INIT_SUCCEEDED);
}

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

void OnTick()
{
    if(!IsTradingTime()) return;
    if(!m_symbol.RefreshRates() || m_symbol.Spread()*m_symbol.Point() > InpMaxSpread) return;
    string pos_sym = _Symbol;
    
    string gv_cooldown = InpStoragePrefix + "_" + IntegerToString((int)AccountInfoInteger(ACCOUNT_LOGIN)) + "_CooldownUntil";
    if(GlobalVariableCheck(gv_cooldown)) {
        datetime cooldown_until = (datetime)GlobalVariableGet(gv_cooldown);
        if(TimeCurrent() < cooldown_until) return;
        else GlobalVariableDel(gv_cooldown);
    }

    CacheAllPositions();
    if(InpBuyEnable) ProcessCluster(InpBuyMagic);
    if(InpSellEnable) ProcessCluster(InpSellMagic);
}

void OnTimer() { if(TimeCurrent() % 5 == 0) DrawDashboard(); }
void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { if(trans.type == TRADE_TRANSACTION_DEAL_ADD) { m_need_history_scan_buy = true; m_need_history_scan_sell = true; } }

void ProcessCluster(int magic)
{
    CheckClusterBasketTP(magic);
    double p0 = (magic == InpBuyMagic) ? m_buy_p0 : m_sell_p0;
    double base_step = (magic == InpBuyMagic) ? InpBuyStep : InpSellStep;
    double step_s = base_step;
    double pr_ref = m_symbol.Bid();

    int b_total = 0, s_total = 0; CountTotalOrders(magic, b_total, s_total);
    int cur_s = (int)MathRound((pr_ref - p0) / base_step);
    double equity_live = AccountInfoDouble(ACCOUNT_EQUITY);
    double min_eq1 = (magic == InpBuyMagic) ? InpBuyMinEqui1 : InpSellMinEqui1;
    double min_eq2 = (magic == InpBuyMagic) ? InpBuyMinEqui2 : InpSellMinEqui2;

    if(InpDynamicStep) {
        int g_orders = (magic == InpBuyMagic) ? b_total : s_total;
        if(g_orders >= 3 && g_orders < 6) step_s *= 2.0;    // Bat dau tu lenh 4 (khi dang co 3 lenh)
        else if(g_orders >= 6) step_s *= 3.0;               // Bat dau tu lenh 7 (khi dang co 6 lenh)
    }

    if(MathAbs(cur_s) > 20) {
        string prefix = GetGVPrefix(magic); double new_p0 = pr_ref;
        GlobalVariableSet(prefix + "_P0", new_p0);
        if(magic == InpBuyMagic) m_buy_p0 = new_p0; else m_sell_p0 = new_p0;
        cur_s = 0; p0 = new_p0;
    }
    UpdateTraversedRange(magic, cur_s);

    bool trigger_hedge = (min_eq2 > 0 && equity_live <= min_eq2);
    if(trigger_hedge) {
        CheckDynamicHedge(magic); 
        if(!IsClusterLocked(magic)) { SetClusterLocked(magic, true); Print("!!! LOCKDOWN !!! ", magic); }
        if(magic == InpBuyMagic) m_buy_hedged = true; else m_sell_hedged = true;
        return;
    }
    
    if(IsClusterLocked(magic)) {
        double bv=0, sv=0; CalculateVolume(magic, bv, sv); double delta = MathAbs(NormalizeDouble(bv - sv, 2));
        if(delta > 0.01 || (bv == 0 && sv == 0)) { SetClusterLocked(magic, false); if(magic == InpBuyMagic) m_buy_hedged = false; else m_sell_hedged = false; }
        else { if(magic == InpBuyMagic) m_buy_hedged = true; else m_sell_hedged = true; return; }
    } else { if(magic == InpBuyMagic) m_buy_hedged = false; else m_sell_hedged = false; }

    bool trigger_stop = (min_eq1 > 0 && equity_live <= min_eq1);
    if(trigger_stop) return;

    double buy_lot = NormalizeLot(((magic==InpBuyMagic)?InpBuyLot:InpSellLot) * MathPow(InpLotMultiplier, b_total));
    double sell_lot = NormalizeLot(((magic==InpBuyMagic)?InpBuyLot:InpSellLot) * MathPow(InpLotMultiplier, s_total));

    // --- SMART RECOVERY LOGIC (V9.2.1 Integration) ---
    if(InpEnableSmartRec) {
        double floating = 0; 
        for(int i=0; i<PositionsTotal(); i++) if(m_position.SelectByIndex(i) && m_position.Magic() == magic && m_position.Symbol() == _Symbol) floating += (m_position.Profit() + m_position.Swap() + m_position.Commission());
        
        if(floating < 0) {
            double target_p = ((magic == InpBuyMagic ? InpBuyBasketTP : InpSellBasketTP) * (magic == InpBuyMagic ? InpBuyTPMult : InpSellTPMult)) + InpBasketSafety;
            double req_p = target_p - floating;
            double point_val = m_symbol.TickValue() / m_symbol.TickSize();
            
            if(b_total >= InpSmartRecStart && b_total > s_total) {
                double opt_lot = req_p / (InpTargetRetrace * point_val);
                if(opt_lot > buy_lot) buy_lot = MathMin(opt_lot, buy_lot * InpMaxRecMult);
            } else if(s_total >= InpSmartRecStart && s_total > b_total) {
                double opt_lot = req_p / (InpTargetRetrace * point_val);
                if(opt_lot > sell_lot) sell_lot = MathMin(opt_lot, sell_lot * InpMaxRecMult);
            }
            buy_lot = NormalizeLot(buy_lot); sell_lot = NormalizeLot(sell_lot);
        }
    }

    double last_entry_price = 0;
    for(int i=PositionsTotal()-1; i>=0; i--) if(m_position.SelectByIndex(i) && m_position.Magic() == magic && StringCompare(m_position.Symbol(), _Symbol, false) == 0) { last_entry_price = m_position.PriceOpen(); break; }
    double dist_real = (last_entry_price > 0) ? MathAbs(pr_ref - last_entry_price) : 999999;
    double mandatory_dist = (InpDynamicStep && step_s > InpMinStepDistance) ? step_s : InpMinStepDistance;
    if(mandatory_dist > 0 && dist_real < mandatory_dist && last_entry_price > 0) return;

    CTrade *trade = (magic == InpBuyMagic) ? &m_trade_buy : &m_trade_sell;
    int targets[2]; targets[0] = 0; targets[1] = cur_s;
    
    int trend_dir = 0; bool is_panic = false;
    if(InpUseTrendFilter && m_ma_handle != INVALID_HANDLE) {
        double ma[]; if(CopyBuffer(m_ma_handle, 0, 0, 1, ma) > 0) {
            trend_dir = (pr_ref > ma[0]) ? 1 : -1;
            if(InpMADistanceLimit > 0 && MathAbs(pr_ref - ma[0]) >= InpMADistanceLimit) is_panic = true;
        }
    }

    for(int i=0; i<2; i++) {
        int s = targets[i];
        if(MathAbs(s - (pr_ref - p0) / base_step) > 0.65) continue; 
        int b_c, s_c; CountOrdersAtStep(magic, s, b_c, s_c);
        if(TimeCurrent() - m_last_trade_tick[magic % 3000] < 2) continue;

        string c = InpBotName + ((magic == InpBuyMagic) ? "_B_S" : "_S_S") + IntegerToString(s);
        bool allow_b = !is_panic; bool allow_s = !is_panic;
        if(InpUseTrendFilter && trend_dir != 0) { if(trend_dir == -1 && b_total >= 3) allow_b = false; if(trend_dir == 1 && s_total >= 3) allow_s = false; }
        if(InpBuyMaxOrders > 0 && b_total >= InpBuyMaxOrders) allow_b = false;
        if(InpSellMaxOrders > 0 && s_total >= InpSellMaxOrders) allow_s = false;
        if(magic == InpBuyMagic) allow_s = false; else allow_b = false;
        
        if(IsClusterLocked(magic)) { allow_b = false; allow_s = false; }

        if(allow_b && b_c < (s == 0 ? 2 : 1)) {
            if(s != 0 && pr_ref < p0 && b_c < 1) { if(trade.Buy(buy_lot,_Symbol,0,0,0,c)) { m_last_trade_tick[magic%3000]=TimeCurrent(); AddCacheTicket(magic,trade.ResultOrder(),s,POSITION_TYPE_BUY); break; } }
            if(s != 0 && pr_ref > p0 && b_c < 2) { if(trade.Buy(buy_lot,_Symbol,0,0,0,c)) { m_last_trade_tick[magic%3000]=TimeCurrent(); AddCacheTicket(magic,trade.ResultOrder(),s,POSITION_TYPE_BUY); break; } }
            if(s == 0 && b_c < 2) { if(trade.Buy(buy_lot,_Symbol,0,0,0,c)) { m_last_trade_tick[magic%3000]=TimeCurrent(); AddCacheTicket(magic,trade.ResultOrder(),s,POSITION_TYPE_BUY); break; } }
        }
        if(allow_s && s_c < (s == 0 ? 2 : 1)) {
            if(s != 0 && pr_ref > p0 && s_c < 1) { if(trade.Sell(sell_lot,_Symbol,0,0,0,c)) { m_last_trade_tick[magic%3000]=TimeCurrent(); AddCacheTicket(magic,trade.ResultOrder(),s,POSITION_TYPE_SELL); break; } }
            if(s != 0 && pr_ref < p0 && s_c < 2) { if(trade.Sell(sell_lot,_Symbol,0,0,0,c)) { m_last_trade_tick[magic%3000]=TimeCurrent(); AddCacheTicket(magic,trade.ResultOrder(),s,POSITION_TYPE_SELL); break; } }
            if(s == 0 && s_c < 2) { if(trade.Sell(sell_lot,_Symbol,0,0,0,c)) { m_last_trade_tick[magic%3000]=TimeCurrent(); AddCacheTicket(magic,trade.ResultOrder(),s,POSITION_TYPE_SELL); break; } }
        }
    }
}

void CheckClusterBasketTP(int magic) {
    string gv_start = GetGVPrefix(magic) + "_StartTime"; datetime start_time = GlobalVariableCheck(gv_start) ? (datetime)GlobalVariableGet(gv_start) : 0;
    if(magic == InpBuyMagic ? m_need_history_scan_buy : m_need_history_scan_sell) {
        double realized = 0; if(HistorySelect(start_time, TimeCurrent())) {
            for(int i=HistoryDealsTotal()-1; i>=0; i--) { ulong t = HistoryDealGetTicket(i); if(HistoryDealGetString(t, DEAL_SYMBOL) == _Symbol && HistoryDealGetInteger(t, DEAL_MAGIC) == magic) realized += (HistoryDealGetDouble(t, DEAL_PROFIT) + HistoryDealGetDouble(t, DEAL_SWAP) + HistoryDealGetDouble(t, DEAL_COMMISSION)); }
        }
        realized += (magic == InpBuyMagic ? InpBuyProfitOffset : InpSellProfitOffset);
        if(magic == InpBuyMagic) { m_buy_realized_profit = realized; m_need_history_scan_buy = false; } else { m_sell_realized_profit = realized; m_need_history_scan_sell = false; }
    }
    double floating = 0; for(int i=PositionsTotal()-1; i>=0; i--) if(m_position.SelectByIndex(i) && m_position.Magic() == magic && StringCompare(m_position.Symbol(), _Symbol, false) == 0) floating += (m_position.Profit() + m_position.Swap() + m_position.Commission());
    double total_net = (magic == InpBuyMagic ? m_buy_realized_profit : m_sell_realized_profit) + floating;
    double target = ((magic == InpBuyMagic ? InpBuyBasketTP : InpSellBasketTP) * (magic == InpBuyMagic ? InpBuyTPMult : InpSellTPMult)) + InpBasketSafety;
    if(total_net >= target && target > 0) ResetCluster(magic);
}

void CheckDynamicHedge(int m) {
    double bv=0, sv=0; CalculateVolume(m, bv, sv); double delta = NormalizeDouble(bv - sv, 2);
    CTrade *tr = (m == InpBuyMagic) ? &m_trade_buy : &m_trade_sell;
    if(MathAbs(delta) >= 0.01) {
        string comment = "DYN_HEDGE_" + IntegerToString(m);
        if(delta > 0) { if(tr.Sell(MathAbs(delta), _Symbol, 0, 0, 0, comment)) Print(">>> HEDGE: Added Sell ", MathAbs(delta)); }
        else { if(tr.Buy(MathAbs(delta), _Symbol, 0, 0, 0, comment)) Print(">>> HEDGE: Added Buy ", MathAbs(delta)); }
    }
}

void ResetCluster(int magic) {
    CTrade tr; tr.SetExpertMagicNumber(magic); for(int i=PositionsTotal()-1; i>=0; i--) if(m_position.SelectByIndex(i) && m_position.Magic() == magic && StringCompare(m_position.Symbol(), _Symbol, false) == 0) tr.PositionClose(m_position.Ticket());
    string pr = GetGVPrefix(magic); GlobalVariableSet(pr + "_StartTime", (double)TimeCurrent() + 1);
    GlobalVariableDel(pr + "_P0"); GlobalVariableDel(pr + "_LastX");
    double new_p0 = m_symbol.Bid(); GlobalVariableSet(pr + "_P0", new_p0);
    if(magic == InpBuyMagic) { m_buy_p0 = new_p0; m_buy_reset_count++; } else { m_sell_p0 = new_p0; m_sell_reset_count++; }
    m_need_history_scan_buy = true; m_need_history_scan_sell = true;
}

void CheckXProfit(int magic, double p0, double x_sz, double pr) { TP_Logic(magic); int cur_x = (int)MathFloor(MathAbs(pr - p0) / x_sz); string gv = GetGVPrefix(magic) + "_LastX"; int last_x = GlobalVariableCheck(gv) ? (int)GlobalVariableGet(gv) : -1; if(cur_x != last_x) { GlobalVariableSet(gv, (double)cur_x); m_need_history_scan_buy = true; m_need_history_scan_sell = true; } }

void TP_Logic(int magic) {
    int min_s=0, max_s=0; GetTraversedRange(magic, min_s, max_s);
    for(int s = min_s; s <= max_s; s++) {
        int b_cnt=0, s_cnt=0; CountOrdersAtStep(magic, s, b_cnt, s_cnt);
        if(b_cnt >= 2) HarvestSurplusAtStep(magic, s, POSITION_TYPE_BUY, b_cnt - 1);
        if(s_cnt >= 2) HarvestSurplusAtStep(magic, s, POSITION_TYPE_SELL, s_cnt - 1);
    }
}

void HarvestSurplusAtStep(int magic, int s_idx, ENUM_POSITION_TYPE t, int count) {
    int closed = 0; CTrade tr; tr.SetExpertMagicNumber(magic);
    for(int i=PositionsTotal()-1; i>=0; i--) {
        if(m_position.SelectByIndex(i) && StringCompare(m_position.Symbol(), _Symbol, false) == 0 && (m_position.Magic() == InpBuyMagic || m_position.Magic() == InpSellMagic)) {
            if(GetStepFromComment(m_position.Comment()) == s_idx && m_position.PositionType() == t) {
                double net = m_position.Profit() + m_position.Swap() + m_position.Commission();
                if(net >= InpMinProfit) { if(tr.PositionClose(m_position.Ticket())) closed++; }
            }
        }
    }
}

void CacheAllPositions() {
    ArrayResize(m_pos_buy_tickets, 0); ArrayResize(m_pos_sell_tickets, 0);
    for(int i=0; i<PositionsTotal(); i++) if(m_position.SelectByIndex(i) && StringCompare(m_position.Symbol(), _Symbol, false) == 0) {
        int m = (int)m_position.Magic();
        if(m == InpBuyMagic) { int n=ArraySize(m_pos_buy_tickets); ArrayResize(m_pos_buy_tickets,n+1); ArrayResize(m_pos_buy_steps,n+1); ArrayResize(m_pos_buy_types,n+1); m_pos_buy_tickets[n]=m_position.Ticket(); m_pos_buy_steps[n]=GetStepFromComment(m_position.Comment()); m_pos_buy_types[n]=m_position.PositionType(); }
        else if(m == InpSellMagic) { int n=ArraySize(m_pos_sell_tickets); ArrayResize(m_pos_sell_tickets,n+1); ArrayResize(m_pos_sell_steps,n+1); ArrayResize(m_pos_sell_types,n+1); m_pos_sell_tickets[n]=m_position.Ticket(); m_pos_sell_steps[n]=GetStepFromComment(m_position.Comment()); m_pos_sell_types[n]=m_position.PositionType(); }
    }
}

void CountOrdersAtStep(int magic, int s_idx, int &b, int &s) {
    b = 0; s = 0; int lim = (magic==InpBuyMagic)?ArraySize(m_pos_buy_tickets):ArraySize(m_pos_sell_tickets);
    for(int i=0; i<lim; i++) if((magic==InpBuyMagic?m_pos_buy_steps[i]:m_pos_sell_steps[i]) == s_idx) { if((magic==InpBuyMagic?m_pos_buy_types[i]:m_pos_sell_types[i]) == POSITION_TYPE_BUY) b++; else s++; }
}

int GetStepFromComment(string c) { int cur=0, last=-1; while((cur=StringFind(c,"_S",cur))!=-1){ last=cur; cur+=1; } if(last!=-1){ return (int)StringToInteger(StringSubstr(c,last+2)); } return 0; }
void SetLastStep(int m, int s) { GlobalVariableSet(GetGVPrefix(m)+"_LastStep", (double)s); }
void GetTraversedRange(int m, int &mi, int &ma) { string k_mi=GetGVPrefix(m)+"_MinStep", k_ma=GetGVPrefix(m)+"_MaxStep"; mi=GlobalVariableCheck(k_mi)?(int)GlobalVariableGet(k_mi):0; ma=GlobalVariableCheck(k_ma)?(int)GlobalVariableGet(k_ma):0; }
void UpdateTraversedRange(int m, int s) { string mi=GetGVPrefix(m)+"_MinStep", ma=GetGVPrefix(m)+"_MaxStep"; if(!GlobalVariableCheck(mi)||s<GlobalVariableGet(mi)) GlobalVariableSet(mi, (double)s); if(!GlobalVariableCheck(ma)||s>GlobalVariableGet(ma)) GlobalVariableSet(ma, (double)s); }
void CalculateVolume(int m, double &bv, double &sv) { bv=0; sv=0; for(int i=0;i<PositionsTotal();i++) if(m_position.SelectByIndex(i)&&m_position.Magic()==m){ if(m_position.PositionType()==POSITION_TYPE_BUY) bv+=m_position.Volume(); else sv+=m_position.Volume(); } }
bool IsTradingTime() { MqlDateTime dt; TimeToStruct(TimeCurrent(),dt); string t=StringFormat("%02d:%02d",dt.hour,dt.min); return (t>=InpStartTime && t<=InpEndTime); }
void AddCacheTicket(int m, ulong t, int s, ENUM_POSITION_TYPE ty) { CacheAllPositions(); }
double NormalizeLot(double vol) { double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); if(step <= 0) return vol; double lot = MathRound(vol / step) * step; return NormalizeDouble(lot, 2); }
void CountTotalOrders(int magic, int &b, int &s) { b = 0; s = 0; for(int i=0; i<PositionsTotal(); i++) if(m_position.SelectByIndex(i) && m_position.Magic() == magic && StringCompare(m_position.Symbol(), _Symbol, false) == 0) { if(m_position.PositionType() == POSITION_TYPE_BUY) b++; else s++; } }

void DrawDashboard() {
    double b_f = 0, s_f = 0; for(int i=0; i<PositionsTotal(); i++) if(m_position.SelectByIndex(i) && StringCompare(m_position.Symbol(), _Symbol, false) == 0) { double p = m_position.Profit() + m_position.Swap() + m_position.Commission(); if(m_position.Magic() == InpBuyMagic) b_f += p; else if(m_position.Magic() == InpSellMagic) s_f += p; }
    string t = "=== NHI QUAI V9.2.1 PRO (V23.04_1) ===\n";
    t += StringFormat("Buy Cluster: Float %.2f | %s\n", b_f, (m_buy_hedged?"HEDGED":"NORMAL"));
    t += StringFormat("Sell Cluster: Float %.2f | %s\n", s_f, (m_sell_hedged?"HEDGED":"NORMAL"));
    Comment(t);
}

© 2026 Hướng Nghiệp Dữ Liệu | Hệ thống giao dịch tự động chuyên nghiệp.