From 82d164dbc93f1a7f2dfcb75754f3743aa21b14b7 Mon Sep 17 00:00:00 2001 From: Bell Date: Wed, 3 Dec 2025 20:52:43 +0700 Subject: [PATCH] FIxxing bug only run 1 month data --- Experts/EA_ICT.mq5 | 409 +++++++++++++++++++-------------------------- 1 file changed, 169 insertions(+), 240 deletions(-) diff --git a/Experts/EA_ICT.mq5 b/Experts/EA_ICT.mq5 index b89a0f5..12ca98b 100644 --- a/Experts/EA_ICT.mq5 +++ b/Experts/EA_ICT.mq5 @@ -1,231 +1,214 @@ //+------------------------------------------------------------------+ -//| EA ICT-style: Daily Bias -> H1 FVG -> M5 pullback entries | -//| - Xác định Daily bias theo yêu cầu của bạn | -//| - Tìm FVG trên H1 thuận chiều với daily bias (phương pháp đơn giản) -//| - Khi giá hồi về H1 FVG, chuyển xuống M5: tìm M5 FVG + MSS (đơn giản) -//| - Đặt BuyLimit / SellLimit tại M5 FVG, SL tính theo đáy/đỉnh pullback -//| - Lot được tính sao cho rủi ro entry->SL = 1% equity (tùy biến RiskPerTrade) -//| - TP = entry + 3 * (entry - SL) (R:R = 1:3) +//| EA ICT-style: Daily Bias -> MidTf FVG -> LowTf pullback entries | //+------------------------------------------------------------------+ #property strict #include CTrade trade; //--- Inputs -input double RiskPerTrade = 1.0; // % equity risk per trade (mặc định 1%) +input double RiskPerTrade = 1.0; // % equity risk per trade input int MagicNumber = 33333; -input int H1_FVG_lookback = 200; // bars to scan for H1 FVG -input int M5_lookback = 200; // bars to scan for M5 FVG/MSS -input double MinADX = 10.0; // optional ADX filter (không bắt buộc) +input int Mid_TF_FVG_lookback = 200; // bars to scan for MidTf FVG +input int Low_TF_FVG_lookback = 200; // bars to scan for LowTf FVG/MSS +input double MinADX = 10.0; input int ADXPeriod = 14; input double MaxAcceptableSpread = 200; // points -input double RR = 3.0; // Risk:Reward multiplier (TP = RR * risk distance) // points +input double RR = 3.0; // Risk:Reward multiplier (TP = RR * risk distance) + +//--- Timeframe configuration (configurable) +input ENUM_TIMEFRAMES TF_High = PERIOD_D1; // Higher timeframe used for bias +input ENUM_TIMEFRAMES TF_Mid = PERIOD_H1; // Mid timeframe for FVG +input ENUM_TIMEFRAMES TF_Low = PERIOD_M5; // Low timeframe for entries //--- Internal structs struct Zone { double top; double bottom; int from_index; int to_index; }; - enum DailyBias { BIAS_UNKNOWN=0, BIAS_UP=1, BIAS_DOWN=-1 }; -//--- Utility forward declarations +//--- Forward DailyBias DetermineDailyBias(); -int FindH1FVGs(DailyBias bias, Zone &foundZone); -int FindM5FVGAtZone(Zone &h1zone, DailyBias bias, Zone &m5zone); -bool DetectM5MSS(int &mssType, double &mssPrice); // returns 1 for bullish MSS (break to upside), -1 for bearish +int FindMidTfFvg(DailyBias bias, Zone &foundZone); +int FindLowTfFvg(Zone &midTfZone, DailyBias bias, Zone &lowTfZone); +bool DetectLowTfMSS(int &mssType, double &mssPrice); double CalculateLotForRisk(double entryPrice, double stopPrice); +bool HasActiveOrders(); void PlaceLimitOrder(int side, double price, double sl, double tp, double lot); //+------------------------------------------------------------------+ int OnInit() { - Print("EA ICT-style initialized"); + PrintFormat("EA init: TF_High=%d TF_Mid=%d TF_Low=%d", (int)TF_High, (int)TF_Mid, (int)TF_Low); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ void OnTick() { - // chỉ xử lý trên M5 khi có bar mới static datetime lastBarTime=0; - datetime t = iTime(_Symbol, PERIOD_M5, 0); + datetime t = iTime(_Symbol, TF_Low, 0); if(t==lastBarTime) return; lastBarTime = t; + // basic symbol params check + double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + if(point <= 0) { Print("Invalid SYMBOL_POINT -> abort tick"); return; } + // spread check double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); - double point = SymbolInfoDouble(_Symbol,SYMBOL_POINT); - double spreadPts = (ask-bid)/point; + double spreadPts = (ask - bid)/point; if(spreadPts > MaxAcceptableSpread) { PrintFormat("Spread too high: %.1f pts", spreadPts); return; } - // 1) xác định daily bias + // 1) bias on TF_High DailyBias bias = DetermineDailyBias(); - if(bias==BIAS_UNKNOWN) { Print("Daily bias unknown -> skip"); return; } - PrintFormat("Daily bias = %d", bias); + if(bias==BIAS_UNKNOWN) { Print("Bias unknown -> skip"); return; } + PrintFormat("Bias = %d", bias); - // 2) tìm H1 FVG thuận chiều với bias - Zone h1zone; bool foundH1 = (FindH1FVGs(bias, h1zone) > 0); - if(!foundH1) { Print("No H1 FVG found in bias direction"); return; } - PrintFormat("Found H1 FVG: top=%.5f bottom=%.5f from=%d to=%d", h1zone.top, h1zone.bottom, h1zone.from_index, h1zone.to_index); + // 2) Mid TF FVG + Zone midZone; + if(FindMidTfFvg(bias, midZone) == 0) { Print("No MidTf FVG -> skip"); return; } + PrintFormat("Mid zone: top=%.5f bottom=%.5f", midZone.top, midZone.bottom); - // 3) khi giá hiện tại đã từng (hoặc đang) thuộc H1 FVG region -> tìm M5 FVG inside that H1 zone - Zone m5zone; bool foundM5 = (FindM5FVGAtZone(h1zone,bias,m5zone) > 0); - if(!foundM5) { Print("No M5 FVG inside H1 FVG -> skip"); return; } - PrintFormat("Found M5 FVG: top=%.5f bottom=%.5f", m5zone.top, m5zone.bottom); + // 3) Low TF FVG inside mid zone + Zone lowZone; + if(FindLowTfFvg(midZone, bias, lowZone) == 0) { Print("No LowTf FVG -> skip"); return; } + PrintFormat("Low zone: top=%.5f bottom=%.5f", lowZone.top, lowZone.bottom); - // 4) xác nhận MSS trên M5 (đơn giản: break of structure recent swing) + // 4) MSS detect on low TF int mssType=0; double mssPrice=0; - if(!DetectM5MSS(mssType,mssPrice)) { Print("No M5 MSS detected -> skip"); return; } - PrintFormat("M5 MSS type=%d price=%.5f", mssType, mssPrice); + if(!DetectLowTfMSS(mssType, mssPrice)) { Print("No LowTf MSS -> skip"); return; } + PrintFormat("LowTf MSS type=%d price=%.5f", mssType, mssPrice); - // 5) chuẩn bị entry: nếu bias up -> place buy limit at bottom of m5zone; if bias down -> sell limit at top - double entryPrice = (bias==BIAS_UP) ? m5zone.bottom : m5zone.top; + // 5) Entry at lowZone.bottom (buy) or top (sell) + double entry = (bias==BIAS_UP) ? lowZone.bottom : lowZone.top; + double sl=0, tp=0; - // compute SL: if buy -> SL = lowest low of the pullback swing on M5 (we approximate by minimum low in zone window) - double sl=0,tp=0; + // compute swing extreme on TF_Low with validation if(bias==BIAS_UP) { - // find lowest low in recent M5 bars inside/near the m5zone range double swingLow = DBL_MAX; - for(int i=0;i= entryPrice) { Print("Computed SL >= entry -> skip"); return; } - double dist = entryPrice - sl; - tp = entryPrice + RR * dist; // R:R=1:3 + for(int i=0;i skip"); return; } + sl = swingLow - 5*point; + if(sl >= entry) { Print("SL >= entry -> skip"); return; } + tp = entry + RR * (entry - sl); } else { double swingHigh = -DBL_MAX; - for(int i=0;i swingHigh) swingHigh = high; } + for(int i=0;i swingHigh) swingHigh = hv; + } + if(swingHigh==-DBL_MAX) { Print("Not enough low TF bars -> skip"); return; } sl = swingHigh + 5*point; - if(sl <= entryPrice) { Print("Computed SL <= entry -> skip"); return; } - double dist = sl - entryPrice; - tp = entryPrice - RR * dist; + if(sl <= entry) { Print("SL <= entry -> skip"); return; } + tp = entry - RR * (sl - entry); } - // 6) tính lot theo risk = RiskPerTrade% equity cho khoảng cách entry->SL - double lot = CalculateLotForRisk(entryPrice, sl); - if(lot <= 0) { Print("Calculated lot <=0 -> skip"); return; } + // 6) lot calc + double lot = CalculateLotForRisk(entry, sl); + if(lot <= 0) { Print("Lot <= 0 -> skip"); return; } - // 7) đặt pending limit + // 7) only one active for EA on symbol int side = (bias==BIAS_UP) ? POSITION_TYPE_BUY : POSITION_TYPE_SELL; - // Check if EA already has active position or pending order (only 1 at a time) - if(HasActiveOrders()) - { - Print("Already have an active position or pending order for this EA -> skip placing another"); - } - else - { - PlaceLimitOrder(side, entryPrice, sl, tp, lot); - } + if(HasActiveOrders()) { Print("Active order exists -> skip"); return; } + + PlaceLimitOrder(side, entry, sl, tp, lot); } //+------------------------------------------------------------------+ DailyBias DetermineDailyBias() { - // theo yêu cầu: xét 2 cây D1 đã đóng gần nhất (không tính nến hiện tại -> shift 1 và 2) - // nếu D[1].close > D[2].high -> up. nếu D[1].close < D[2].low -> down. nếu D[1] nằm trong D[2] thì bỏ qua D[1] và dùng D2 & D3 - - int idx1 = 1; // D[1] - int idx2 = 2; // D[2] - int tries = 0; - while(tries < 5) + int idx1=1, idx2=2, tries=0; + while(tries<5) { - double close1 = iClose(_Symbol,PERIOD_D1,idx1); - double high2 = iHigh(_Symbol,PERIOD_D1,idx2); - double low2 = iLow(_Symbol,PERIOD_D1,idx2); - + double close1 = iClose(_Symbol, TF_High, idx1); + double high2 = iHigh (_Symbol, TF_High, idx2); + double low2 = iLow (_Symbol, TF_High, idx2); if(close1 > high2) return BIAS_UP; if(close1 < low2) return BIAS_DOWN; - - // close1 inside candle2 -> shift window down (use D2 & D3) idx1++; idx2++; tries++; - // ensure there are bars - if(idx2 > 200) break; + if(idx2>500) break; } return BIAS_UNKNOWN; } //+------------------------------------------------------------------+ -int FindH1FVGs(DailyBias bias, Zone &foundZone) +int FindMidTfFvg(DailyBias bias, Zone &foundZone) { - // Phương pháp đơn giản: - // Tìm gap "Fair Value Gap" kiểu: giữa 2 candle (i and i+2) có khoảng trống - // Bullish FVG (hỗ trợ): low[i] > high[i+2] -> vùng FVG là (high[i+2], low[i]) - // Bearish FVG (kháng cự): high[i] < low[i+2] -> vùng FVG là (high[i], low[i+2]) - - int limit = H1_FVG_lookback; + int limit = Mid_TF_FVG_lookback; for(int i=1;i high_i2 + SymbolInfoDouble(_Symbol,SYMBOL_POINT)*0.0) // allow equality - { - foundZone.top = low_i; - foundZone.bottom = high_i2; - foundZone.from_index = i+2; - foundZone.to_index = i; - return(1); - } - } - else if(bias==BIAS_DOWN) - { - if(high_i < low_i2 - SymbolInfoDouble(_Symbol,SYMBOL_POINT)*0.0) - { - foundZone.top = high_i2; // caution: for clarity we set top>bottom - foundZone.bottom = low_i; - // normalize so top>bottom - double t = MathMax(high_i, low_i2); - double b = MathMin(high_i, low_i2); - foundZone.top = t; foundZone.bottom = b; - foundZone.from_index = i+2; - foundZone.to_index = i; - return(1); - } - } - } - return(0); -} - -//+------------------------------------------------------------------+ -int FindM5FVGAtZone(Zone &h1zone, DailyBias bias, Zone &m5zone) -{ - // Scan M5 recent bars. We look for small FVGs within the price range of H1 FVG - int limit = M5_lookback; - for(int i=1;i high_i2) { - double top = low_i; - double bottom = high_i2; - // check overlap with H1 zone - if(bottom <= h1zone.top && top >= h1zone.bottom) - { - m5zone.top = top; m5zone.bottom = bottom; return 1; - } + foundZone.top = low_i; foundZone.bottom = high_i2; + foundZone.from_index = i+2; foundZone.to_index = i; + return 1; } } else if(bias==BIAS_DOWN) { if(high_i < low_i2) { - double top = low_i2; double bottom = high_i; - if(bottom <= h1zone.top && top >= h1zone.bottom) + double t = MathMax(high_i, low_i2); + double b = MathMin(high_i, low_i2); + foundZone.top = t; foundZone.bottom = b; + foundZone.from_index = i+2; foundZone.to_index = i; + return 1; + } + } + } + return 0; +} + +//+------------------------------------------------------------------+ +int FindLowTfFvg(Zone &midTfZone, DailyBias bias, Zone &lowTfZone) +{ + int limit = Low_TF_FVG_lookback; + for(int i=1;i high_i2) + { + double top = low_i; double bottom = high_i2; + if(bottom <= midTfZone.top && top >= midTfZone.bottom) { - m5zone.top = top; m5zone.bottom = bottom; return 1; + lowTfZone.top = top; lowTfZone.bottom = bottom; return 1; + } + } + } + else + { + if(high_i < low_i2) + { + double top = low_i2; double bottom = high_i; + if(bottom <= midTfZone.top && top >= midTfZone.bottom) + { + lowTfZone.top = top; lowTfZone.bottom = bottom; return 1; } } } @@ -234,55 +217,51 @@ int FindM5FVGAtZone(Zone &h1zone, DailyBias bias, Zone &m5zone) } //+------------------------------------------------------------------+ -bool DetectM5MSS(int &mssType, double &mssPrice) +bool DetectLowTfMSS(int &mssType, double &mssPrice) { - // Rất đơn giản: nếu price vừa break swing high -> bullish MSS (return 1) - // nếu price just break swing low -> bearish MSS (return -1) - // Implementation: compute last 3 swing highs and lows and check current candle - - // get last swing high (local maxima) and swing low (local minima) in M5 double lastSwingHigh = -DBL_MAX; int idxHigh=-1; - double lastSwingLow = DBL_MAX; int idxLow=-1; + double lastSwingLow = DBL_MAX; int idxLow=-1; int look = 50; for(int i=2;i lastSwingHigh) { lastSwingHigh=h; idxHigh=i; } - if(l < lastSwingLow) { lastSwingLow=l; idxLow=i; } + double h = iHigh(_Symbol, TF_Low, i); + double l = iLow (_Symbol, TF_Low, i); + if(h>0 && h > lastSwingHigh) { lastSwingHigh=h; idxHigh=i; } + if(l>0 && l < lastSwingLow) { lastSwingLow=l; idxLow=i; } } - double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); - - // bullish MSS detection: current price (ask) > lastSwingHigh - if(ask > lastSwingHigh) - { - mssType = 1; mssPrice = lastSwingHigh; return true; - } - if(bid < lastSwingLow) - { - mssType = -1; mssPrice = lastSwingLow; return true; - } + if(ask > lastSwingHigh) { mssType=1; mssPrice = lastSwingHigh; return true; } + if(bid < lastSwingLow) { mssType=-1; mssPrice = lastSwingLow; return true; } return false; } //+------------------------------------------------------------------+ double CalculateLotForRisk(double entryPrice, double stopPrice) { - // Tính lot sao cho khoảng cách entry->SL tương ứng RiskPerTrade% equity double equity = AccountInfoDouble(ACCOUNT_EQUITY); double riskMoney = equity * (RiskPerTrade/100.0); double point = SymbolInfoDouble(_Symbol,SYMBOL_POINT); double tickValue = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE); double tickSize = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE); + + // Fallback: if broker returns 0 for tickValue/tickSize, estimate valuePerPoint using contract size: + double contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); + if(tickValue<=0 || tickSize<=0) + { + // rough fallback: assume 1 lot -> contractSize * point movement value (this might be instrument-specific) + tickValue = contractSize; tickSize = 1.0; + } + if(point<=0 || tickValue<=0 || tickSize<=0) { Print("Invalid symbol params for lot calc"); return 0; } double stopPoints = MathAbs(entryPrice - stopPrice)/point; if(stopPoints <= 0) return 0; double valuePerPoint = tickValue * (point / tickSize); + if(valuePerPoint <= 0) { Print("valuePerPoint invalid"); return 0; } + double rawLot = riskMoney / (stopPoints * valuePerPoint); double minLot = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); @@ -300,10 +279,6 @@ double CalculateLotForRisk(double entryPrice, double stopPrice) return lot; } -//+------------------------------------------------------------------+ -//+------------------------------------------------------------------+ -//| HasActiveOrders: kiểm tra xem EA đã có position hoặc pending order | -//| - Trả về true nếu tồn tại position mở hoặc pending order cùng MagicNumber trên symbol //+------------------------------------------------------------------+ bool HasActiveOrders() { @@ -332,15 +307,13 @@ bool HasActiveOrders() return false; } +//+------------------------------------------------------------------+ void PlaceLimitOrder(int side, double price, double sl, double tp, double lot) { - // Create and send a pending limit order via MqlTradeRequest/OrderSend - MqlTradeRequest request; - MqlTradeResult result; - ZeroMemory(request); - ZeroMemory(result); + MqlTradeRequest request; MqlTradeResult result; + ZeroMemory(request); ZeroMemory(result); - request.action = TRADE_ACTION_PENDING; // we're placing a pending order + request.action = TRADE_ACTION_PENDING; request.symbol = _Symbol; request.volume = lot; request.price = price; @@ -349,64 +322,35 @@ void PlaceLimitOrder(int side, double price, double sl, double tp, double lot) request.deviation = 20; request.magic = MagicNumber; request.comment = (side==POSITION_TYPE_BUY) ? "ICT_BUY_LIMIT" : "ICT_SELL_LIMIT"; - request.type_time = ORDER_TIME_GTC; // good-till-cancelled - request.type_filling = ORDER_FILLING_RETURN; // safe default filling + request.type_time = ORDER_TIME_GTC; + request.type_filling = ORDER_FILLING_RETURN; + request.type = (side==POSITION_TYPE_BUY) ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_SELL_LIMIT; - // set exact pending type - if(side == POSITION_TYPE_BUY) - request.type = ORDER_TYPE_BUY_LIMIT; - else - request.type = ORDER_TYPE_SELL_LIMIT; - - // Send the order request to the server if(!OrderSend(request,result)) { - // OrderSend can fail immediately (client-side) — print result for debugging PrintFormat("OrderSend() failed: retcode=%d comment=%s", result.retcode, result.comment); return; } - // Check the server response (retcode) if(result.retcode == TRADE_RETCODE_DONE || result.retcode == TRADE_RETCODE_PLACED) { ulong ticket = result.order; - PrintFormat("Placed pending order: ticket=%I64u side=%d price=%.5f lot=%.2f SL=%.5f TP=%.5f", + PrintFormat("Placed pending order ticket=%I64u side=%d price=%.5f lot=%.2f SL=%.5f TP=%.5f", ticket, side, price, lot, sl, tp); - - // Draw SL/TP and entry lines on chart for visual confirmation - string name_sl = StringFormat("ICT_SL_%I64u", ticket); - string name_tp = StringFormat("ICT_TP_%I64u", ticket); - string name_entry = StringFormat("ICT_ENTRY_%I64u", ticket); - - // Create horizontal lines at SL, TP and entry price - if(ObjectFind(0, name_sl) == -1) - { - ObjectCreate(0, name_sl, OBJ_HLINE, 0, 0, sl); - ObjectSetDouble(0, name_sl, OBJPROP_PRICE, sl); - ObjectSetInteger(0, name_sl, OBJPROP_COLOR, clrRed); - ObjectSetInteger(0, name_sl, OBJPROP_WIDTH, 1); - ObjectSetString(0, name_sl, OBJPROP_TEXT, "SL: " + DoubleToString(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS))); - } - if(ObjectFind(0, name_tp) == -1) - { - ObjectCreate(0, name_tp, OBJ_HLINE, 0, 0, tp); - ObjectSetDouble(0, name_tp, OBJPROP_PRICE, tp); - ObjectSetInteger(0, name_tp, OBJPROP_COLOR, clrLime); - ObjectSetInteger(0, name_tp, OBJPROP_WIDTH, 1); - ObjectSetString(0, name_tp, OBJPROP_TEXT, "TP: " + DoubleToString(tp, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS))); - } - if(ObjectFind(0, name_entry) == -1) - { - ObjectCreate(0, name_entry, OBJ_HLINE, 0, 0, price); - ObjectSetDouble(0, name_entry, OBJPROP_PRICE, price); - ObjectSetInteger(0, name_entry, OBJPROP_COLOR, clrYellow); - ObjectSetInteger(0, name_entry, OBJPROP_STYLE, STYLE_DOT); - ObjectSetString(0, name_entry, OBJPROP_TEXT, "Entry: " + DoubleToString(price, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS))); - } + // draw lines + string n_sl = StringFormat("ICT_SL_%I64u", ticket); + string n_tp = StringFormat("ICT_TP_%I64u", ticket); + string n_en = StringFormat("ICT_ENTRY_%I64u", ticket); + if(ObjectFind(0,n_sl) == -1) ObjectCreate(0,n_sl,OBJ_HLINE,0,0,sl); + if(ObjectFind(0,n_tp) == -1) ObjectCreate(0,n_tp,OBJ_HLINE,0,0,tp); + if(ObjectFind(0,n_en) == -1) ObjectCreate(0,n_en,OBJ_HLINE,0,0,price); + ObjectSetInteger(0,n_sl,OBJPROP_COLOR,clrRed); + ObjectSetInteger(0,n_tp,OBJPROP_COLOR,clrLime); + ObjectSetInteger(0,n_en,OBJPROP_COLOR,clrYellow); + ObjectSetString(0,n_sl,OBJPROP_TEXT,"SL: "+DoubleToString(sl,(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS))); } else { - // Broker may reject or modify pending order; log retcode & comment PrintFormat("OrderSend returned retcode=%d comment=%s", result.retcode, result.comment); } } @@ -414,28 +358,13 @@ void PlaceLimitOrder(int side, double price, double sl, double tp, double lot) //+------------------------------------------------------------------+ void OnDeinit(const int reason) { - // Xoá các đối tượng SL/TP/ENTRY còn lại trên chart khi EA đóng int total = ObjectsTotal(0); - for(int i = total - 1; i >= 0; i--) + for(int i=total-1;i>=0;i--) { - string name = ObjectName(0, i); - if(StringFind(name, "ICT_ENTRY_") == 0 || - StringFind(name, "ICT_SL_") == 0 || - StringFind(name, "ICT_TP_") == 0) - { - ObjectDelete(0, name); - } + string name = ObjectName(0,i); + if(StringFind(name,"ICT_ENTRY_")==0 || StringFind(name,"ICT_SL_")==0 || StringFind(name,"ICT_TP_")==0) + ObjectDelete(0,name); } - - Print("EA deinitialized — cleaned objects."); + Print("EA deinitialized - cleaned objects."); } //+------------------------------------------------------------------+ - -// NOTES / CAVEATS: -// - Đây là bản mẫu triển khai logic theo mô tả của bạn, nhưng có nhiều điểm được đơn giản hóa -// (phát hiện FVG và MSS là dạng heuristic đơn giản). Nên backtest kỹ và điều chỉnh -// - Bạn có thể muốn vẽ các zone (OBJ_RECTANGLE) để debug và quan sát H1/M5 FVG -// - Tinh chỉnh: lookback, cách xác định FVG, buffer SL, ADX filter, điều kiện trước khi đặt lệnh -// - EA hiện đặt 1 pending limit khi điều kiện thỏa. Nó không kiểm tra overlap với các pending/positions hiện tại -// - Hãy chạy trên demo/backtest trước khi dùng real -