| 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 | 80 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.

Thành ĐT

Thành ĐT

Founder & Chief Technology Officer, HNDL
1.325 Bài viết
15.4k Người theo dõi
120k+ Lượt đọc

Chuyên gia với hơn 10 năm kinh nghiệm trong phát triển hệ thống giao dịch tự động (Trading Bot), Fintech, Mobile App và phân tích dữ liệu tài chính (Quantitative Analysis). Người sáng lập và trực tiếp dẫn dắt các khóa học thực chiến tại Hướng Nghiệp Dữ Liệu.