//+------------------------------------------------------------------+ //| ICT EA – FVG Edition | //| Architecture: 3 TF | 4 State Machine | //+------------------------------------------------------------------+ #property strict //==================================================== // INPUTS //==================================================== input ENUM_TIMEFRAMES InpBiasTF = PERIOD_D1; // Bias Timeframe input ENUM_TIMEFRAMES InpMiddleTF = PERIOD_H1; // Middle TF – nơi quét FVG input ENUM_TIMEFRAMES InpTriggerTF = PERIOD_M5; // Trigger TF – xác nhận vào lệnh input double InpRiskPercent = 1.0; input double InpRiskReward = 2.0; input double InpMaxDailyLossPct = 3.0; input int InpLondonStartHour = 8; input int InpLondonEndHour = 17; input int InpNYStartHour = 13; input int InpNYEndHour = 22; input int InpSessionAvoidLastMin = 60; input int InpFVGMaxAliveMin = 4320; // FVG sống tối đa bao nhiêu phút: 3 ngày input int InpFVGScanBars = 50; // Số bar MiddleTF quét ngược để tìm FVG input double InpFVGMinBodyPct = 60.0; // Nến giữa phải có body >= X% range input int InpTriggerMaxBars = 30; // Số bar TriggerTF chờ sau khi touched input bool InpDebugLog = true; input bool InpDebugDraw = true; //==================================================== // ENUMS //==================================================== enum EAState { EA_IDLE, // Chờ điều kiện: bias hợp lệ + tìm FVG candidate EA_WAIT_TOUCH, // Có FVG candidate, chờ price chạm vào vùng FVG EA_WAIT_TRIGGER, // Price đã chạm FVG, chờ Trigger TF xác nhận entry EA_IN_TRADE // Đang có lệnh, chờ kết thúc }; enum HTFBias { BIAS_NONE = 0, BIAS_UP = 1, BIAS_DOWN = -1, BIAS_SIDEWAY = 2 }; enum MarketDir { DIR_NONE = 0, DIR_UP = 1, DIR_DOWN = -1 }; enum BlockReason { BLOCK_NONE, BLOCK_SESSION, BLOCK_DAILY_LOSS, BLOCK_BIAS_MISMATCH, BLOCK_NO_BIAS }; //==================================================== // STRUCTS //==================================================== struct BiasContext { HTFBias bias; double rangeHigh; // Dùng khi sideway double rangeLow; datetime lastBarTime; // Guard: chỉ update khi bar mới }; struct FVGContext { bool active; // Đang có FVG candidate bool touched; // Price đã chạm vào FVG chưa MarketDir direction; // FVG thuận chiều bias double high; // Cạnh trên FVG double low; // Cạnh dưới FVG double mid; // Midpoint – dùng làm entry tham chiếu datetime createdTime; // Thời điểm nến phải đóng = FVG confirmed datetime touchTime; // Thời điểm price chạm lần đầu int barsAlive; // Số bar TriggerTF đã qua kể từ touched int touchBarIdx; // Bar index TriggerTF lúc touched }; struct TriggerContext { bool valid; MarketDir direction; double swingHigh; double swingLow; int idxHigh; int idxLow; double breakLevel; // Level cần phá để confirm entry }; struct OrderPlan { bool valid; int direction; double entry; double stopLoss; double takeProfit; double lot; }; struct DailyRiskContext { double startBalance; double currentBalance; // Cập nhật mỗi tick – dùng lại trong DrawContextDebug datetime dayStartTime; bool limitHit; }; //==================================================== // GLOBAL STATE //==================================================== EAState g_State = EA_IDLE; BlockReason g_BlockReason = BLOCK_NONE; BiasContext g_Bias; FVGContext g_FVG; TriggerContext g_Trigger; DailyRiskContext g_DailyRisk; int g_TradeBarIndex = -1; // Tên objects vẽ FVG trên chart const string FVG_RECT_NAME = "FVG_RECT"; const string FVG_MID_NAME = "FVG_MID"; const string FVG_LABEL_NAME = "FVG_LABEL"; //==================================================== // CONTEXT UPDATERS //==================================================== HTFBias ResolveBias( double b1High, double b1Low, double b1Close, double b2High, double b2Low) { if (b1Close > b2High) return BIAS_UP; // Rule 1: Breakout lên if (b1Close < b2Low) return BIAS_DOWN; // Rule 2: Breakout xuống if (b1High > b2High && b1Close < b2High) return BIAS_DOWN; // Rule 3: Sweep high → reject if (b1Low < b2Low && b1Close > b2Low) return BIAS_UP; // Rule 4: Sweep low → recover return BIAS_SIDEWAY; // Rule 5: Còn lại } void UpdateBiasContext() { datetime currentBarTime = iTime(_Symbol, InpBiasTF, 0); if (currentBarTime == g_Bias.lastBarTime) return; // Guard: 1 lần/bar g_Bias.lastBarTime = currentBarTime; if (Bars(_Symbol, InpBiasTF) < 4) { g_Bias.bias = BIAS_NONE; return; } double b1High = iHigh (_Symbol, InpBiasTF, 1); double b1Low = iLow (_Symbol, InpBiasTF, 1); double b1Close = iClose(_Symbol, InpBiasTF, 1); double b2High = iHigh (_Symbol, InpBiasTF, 2); double b2Low = iLow (_Symbol, InpBiasTF, 2); HTFBias prevBias = g_Bias.bias; g_Bias.bias = ResolveBias(b1High, b1Low, b1Close, b2High, b2Low); g_Bias.rangeHigh = (g_Bias.bias == BIAS_SIDEWAY) ? b2High : 0; g_Bias.rangeLow = (g_Bias.bias == BIAS_SIDEWAY) ? b2Low : 0; if (InpDebugLog && g_Bias.bias != prevBias) PrintFormat("[BIAS] %s → %s | b1[H=%.5f L=%.5f C=%.5f] b2[H=%.5f L=%.5f]", EnumToString(prevBias), EnumToString(g_Bias.bias), b1High, b1Low, b1Close, b2High, b2Low); } void UpdateDailyRiskContext() { datetime todayBarTime = iTime(_Symbol, PERIOD_D1, 0); // "ID" của ngày – đổi khi sang ngày mới if (todayBarTime != g_DailyRisk.dayStartTime) { g_DailyRisk.dayStartTime = todayBarTime; g_DailyRisk.startBalance = AccountInfoDouble(ACCOUNT_BALANCE); // Chụp balance đầu ngày (không tính floating P&L) g_DailyRisk.limitHit = false; // Reset – cho phép trade ngày mới if (InpDebugLog) PrintFormat("[DAILY RISK] New day | startBalance=%.2f", g_DailyRisk.startBalance); } if (g_DailyRisk.limitHit) return; // Đã hit → không tính lại mỗi tick g_DailyRisk.currentBalance = AccountInfoDouble(ACCOUNT_BALANCE); // Lưu vào struct, DrawDebug dùng lại double lostPct = (g_DailyRisk.startBalance - g_DailyRisk.currentBalance) / g_DailyRisk.startBalance * 100.0; if (lostPct >= InpMaxDailyLossPct) { g_DailyRisk.limitHit = true; PrintFormat("[DAILY RISK] ⛔ Limit hit | lost=%.2f%% | limit=%.2f%% | balance=%.2f", lostPct, InpMaxDailyLossPct, g_DailyRisk.currentBalance); } } void UpdateAllContexts() { UpdateDailyRiskContext(); // Mỗi tick (early-exit khi limitHit) UpdateBiasContext(); // 1 lần/bar (guard lastBarTime) } //==================================================== // GUARDS //==================================================== bool IsSessionAllowed() { return true; /* TODO */ } bool IsDailyLossOK() { return !g_DailyRisk.limitHit; } bool IsBiasValid() { return g_Bias.bias == BIAS_UP || g_Bias.bias == BIAS_DOWN; } bool EvaluateGuards() { g_BlockReason = BLOCK_NONE; if (!IsSessionAllowed()) { g_BlockReason = BLOCK_SESSION; return false; } if (!IsDailyLossOK()) { g_BlockReason = BLOCK_DAILY_LOSS; return false; } if (!IsBiasValid()) { g_BlockReason = BLOCK_NO_BIAS; return false; } return true; } //==================================================== // TRANSITIONS //==================================================== void TransitionTo(EAState next) { if (InpDebugLog) PrintFormat("[STATE] %s → %s", EnumToString(g_State), EnumToString(next)); g_State = next; } void ResetToIdle(string reason = "") { if (InpDebugLog && reason != "") PrintFormat("[RESET] %s", reason); ZeroMemory(g_FVG); ZeroMemory(g_Trigger); g_TradeBarIndex = -1; // Xóa FVG objects khi reset ObjectDelete(0, FVG_RECT_NAME); ObjectDelete(0, FVG_MID_NAME); ObjectDelete(0, FVG_LABEL_NAME); TransitionTo(EA_IDLE); } //==================================================== // FVG HELPERS //==================================================== bool IsCandleStrong(ENUM_TIMEFRAMES tf, int barIndex) { double high = iHigh (_Symbol, tf, barIndex); double low = iLow (_Symbol, tf, barIndex); double open = iOpen (_Symbol, tf, barIndex); double close = iClose(_Symbol, tf, barIndex); double range = high - low; if (range < _Point) return false; double body = MathAbs(close - open); return (body / range * 100.0) >= InpFVGMinBodyPct; } // Fill = bar sau FVG có close đi vào vùng gap bool IsFVGAlreadyFilled( ENUM_TIMEFRAMES tf, int fvgRightIdx, // Index nến phải của FVG pattern double fvgHigh, double fvgLow, MarketDir dir) { for (int j = fvgRightIdx - 1; j >= 1; j--) { double close = iClose(_Symbol, tf, j); if (dir == DIR_UP && close <= fvgHigh) return true; // Retraced vào gap bullish if (dir == DIR_DOWN && close >= fvgLow) return true; // Retraced vào gap bearish } return false; } bool IsFVGExpired() { if (g_FVG.createdTime <= 0) return false; return (int)(TimeCurrent() - g_FVG.createdTime) > InpFVGMaxAliveMin * 60; } // Invalidate = MiddleTF close xuyên hoàn toàn qua FVG ngược chiều bool IsFVGInvalidated() { double lastClose = iClose(_Symbol, InpMiddleTF, 1); if (g_FVG.direction == DIR_UP && lastClose < g_FVG.low) return true; if (g_FVG.direction == DIR_DOWN && lastClose > g_FVG.high) return true; return false; } //==================================================== // FVG DETECTION // // Pattern 3 nến tại index i (i = nến giữa): // bar[i+1] = nến trái (older) // bar[i] = nến giữa (impulse – phải mạnh) // bar[i-1] = nến phải (newer) // // Bullish FVG: bar[i+1].high < bar[i-1].low // gapLow = bar[i+1].high (đỉnh nến trái = cạnh dưới gap) // gapHigh = bar[i-1].low (đáy nến phải = cạnh trên gap) // // Bearish FVG: bar[i+1].low > bar[i-1].high // gapHigh = bar[i+1].low (đáy nến trái = cạnh trên gap) // gapLow = bar[i-1].high (đỉnh nến phải = cạnh dưới gap) //==================================================== bool FindFVGCandidate(FVGContext &fvg) { MarketDir targetDir = (g_Bias.bias == BIAS_UP) ? DIR_UP : DIR_DOWN; int maxBar = MathMin(InpFVGScanBars, Bars(_Symbol, InpMiddleTF) - 2); for (int i = 2; i <= maxBar; i++) { double leftHigh = iHigh (_Symbol, InpMiddleTF, i + 1); double leftLow = iLow (_Symbol, InpMiddleTF, i + 1); double rightHigh = iHigh (_Symbol, InpMiddleTF, i - 1); double rightLow = iLow (_Symbol, InpMiddleTF, i - 1); double midOpen = iOpen (_Symbol, InpMiddleTF, i); double midClose = iClose(_Symbol, InpMiddleTF, i); double gapHigh, gapLow; if (targetDir == DIR_UP) { if (leftHigh >= rightLow) continue; // Không có gap if (midClose <= midOpen) continue; // Nến giữa phải bullish if (!IsCandleStrong(InpMiddleTF, i)) continue; // Body >= InpFVGMinBodyPct% gapLow = leftHigh; // Đỉnh nến trái = cạnh dưới gap gapHigh = rightLow; // Đáy nến phải = cạnh trên gap } else // DIR_DOWN { if (leftLow <= rightHigh) continue; // Không có gap if (midClose >= midOpen) continue; // Nến giữa phải bearish if (!IsCandleStrong(InpMiddleTF, i)) continue; // Body >= InpFVGMinBodyPct% gapHigh = leftLow; // Đáy nến trái = cạnh trên gap gapLow = rightHigh; // Đỉnh nến phải = cạnh dưới gap } if (IsFVGAlreadyFilled(InpMiddleTF, i - 1, gapHigh, gapLow, targetDir)) continue; // ── FVG hợp lệ → điền struct ──────────────────────────────── fvg.active = true; fvg.touched = false; fvg.direction = targetDir; fvg.high = gapHigh; fvg.low = gapLow; fvg.mid = (gapHigh + gapLow) / 2.0; fvg.createdTime = iTime(_Symbol, InpMiddleTF, i - 1); // Nến phải đóng = FVG confirmed fvg.touchTime = 0; fvg.barsAlive = 0; fvg.touchBarIdx = -1; return true; } return false; } //==================================================== // STATE HANDLERS //==================================================== void OnStateIdle() { FVGContext candidate; ZeroMemory(candidate); if (!FindFVGCandidate(candidate)) return; g_FVG = candidate; if (InpDebugLog) PrintFormat("[FVG FOUND] dir=%s | high=%.5f | low=%.5f | mid=%.5f | created=%s", EnumToString(g_FVG.direction), g_FVG.high, g_FVG.low, g_FVG.mid, TimeToString(g_FVG.createdTime)); TransitionTo(EA_WAIT_TOUCH); } void OnStateWaitTouch() { if (IsFVGExpired()) { ResetToIdle("FVG expired"); return; } if (IsFVGInvalidated()) { ResetToIdle("FVG invalidated"); return; } double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); bool touched = false; if (g_FVG.direction == DIR_UP && bid <= g_FVG.high) touched = true; // Retraced xuống chạm cạnh trên gap if (g_FVG.direction == DIR_DOWN && bid >= g_FVG.low) touched = true; // Retraced lên chạm cạnh dưới gap if (!touched) return; g_FVG.touched = true; g_FVG.touchTime = TimeCurrent(); g_FVG.touchBarIdx = iBarShift(_Symbol, InpTriggerTF, TimeCurrent()); g_FVG.barsAlive = 0; if (InpDebugLog) PrintFormat("[FVG TOUCHED] dir=%s | price=%.5f | FVG[%.5f – %.5f]", EnumToString(g_FVG.direction), bid, g_FVG.low, g_FVG.high); TransitionTo(EA_WAIT_TRIGGER); } void OnStateWaitTrigger() { // TODO: Kiểm tra FVG bị phá → ResetToIdle("FVG broken") // TODO: barsAlive > InpTriggerMaxBars → ResetToIdle("trigger timeout") // TODO: Bar TriggerTF mới → UpdateTriggerContext() // TODO: Điều kiện trigger đủ → BuildOrderPlan → ExecuteOrder → EA_IN_TRADE } void OnStateInTrade() { if (Bars(_Symbol, InpTriggerTF) <= g_TradeBarIndex) return; // Cùng bar vừa gửi lệnh → bỏ qua // TODO: Còn position / pending? Không còn → ResetToIdle("trade closed") } //==================================================== // STATE MACHINE //==================================================== void RunStateMachine() { switch (g_State) { case EA_IDLE: OnStateIdle(); break; case EA_WAIT_TOUCH: OnStateWaitTouch(); break; case EA_WAIT_TRIGGER: OnStateWaitTrigger(); break; case EA_IN_TRADE: OnStateInTrade(); break; } } //==================================================== // FVG DRAWING // // Rectangle: // Cạnh trái = createdTime (nến phải của pattern = thời điểm FVG confirmed) // Cạnh phải = WAIT_TOUCH : bar MiddleTF hiện tại (live, cập nhật mỗi tick) // = WAIT_TRIGGER: touchTime + 1 bar MiddleTF (cố định sau khi touched) // Cạnh trên = FVG.high // Cạnh dưới = FVG.low // Đường giữa = FVG.mid (đứt nét) //==================================================== void DrawFVGRectangle() { if (!InpDebugDraw) return; if (!g_FVG.active) { ObjectDelete(0, FVG_RECT_NAME); ObjectDelete(0, FVG_MID_NAME); ObjectDelete(0, FVG_LABEL_NAME); return; } // ── Xác định cạnh phải của rectangle ──────────────────────────────── datetime rectEnd; if (!g_FVG.touched) { rectEnd = iTime(_Symbol, InpMiddleTF, 0); // Live: kéo tới bar hiện tại } else { int touchShift = iBarShift(_Symbol, InpMiddleTF, g_FVG.touchTime); int nextShift = MathMax(touchShift - 1, 0); // Bar MiddleTF liền sau touch rectEnd = iTime(_Symbol, InpMiddleTF, nextShift); // Cố định tại n+1 } // ── Màu theo state và chiều ────────────────────────────────────────── color rectColor = !g_FVG.touched ? (g_FVG.direction == DIR_UP ? C'0,80,160' : C'140,40,0') // Đậm khi candidate : (g_FVG.direction == DIR_UP ? C'0,160,255' : C'255,100,0'); // Sáng khi touched // ── Rectangle ──────────────────────────────────────────────────────── if (ObjectFind(0, FVG_RECT_NAME) < 0) ObjectCreate(0, FVG_RECT_NAME, OBJ_RECTANGLE, 0, g_FVG.createdTime, g_FVG.high, rectEnd, g_FVG.low); ObjectSetInteger(0, FVG_RECT_NAME, OBJPROP_COLOR, rectColor); ObjectSetInteger(0, FVG_RECT_NAME, OBJPROP_FILL, true); ObjectSetInteger(0, FVG_RECT_NAME, OBJPROP_BACK, true); ObjectSetInteger(0, FVG_RECT_NAME, OBJPROP_WIDTH, 1); ObjectMove(0, FVG_RECT_NAME, 0, g_FVG.createdTime, g_FVG.high); // Cạnh trái cố định ObjectMove(0, FVG_RECT_NAME, 1, rectEnd, g_FVG.low); // Cạnh phải cập nhật // ── Đường midpoint (đứt nét) ───────────────────────────────────────── if (ObjectFind(0, FVG_MID_NAME) < 0) ObjectCreate(0, FVG_MID_NAME, OBJ_TREND, 0, g_FVG.createdTime, g_FVG.mid, rectEnd, g_FVG.mid); ObjectSetInteger(0, FVG_MID_NAME, OBJPROP_COLOR, clrSilver); ObjectSetInteger(0, FVG_MID_NAME, OBJPROP_STYLE, STYLE_DOT); ObjectSetInteger(0, FVG_MID_NAME, OBJPROP_WIDTH, 1); ObjectSetInteger(0, FVG_MID_NAME, OBJPROP_RAY_RIGHT, false); ObjectMove(0, FVG_MID_NAME, 0, g_FVG.createdTime, g_FVG.mid); ObjectMove(0, FVG_MID_NAME, 1, rectEnd, g_FVG.mid); // ── Label ───────────────────────────────────────────────────────────── string labelText = StringFormat("FVG %s%s", (g_FVG.direction == DIR_UP ? "▲" : "▼"), (g_FVG.touched ? " [TOUCHED]" : "")); if (ObjectFind(0, FVG_LABEL_NAME) < 0) ObjectCreate(0, FVG_LABEL_NAME, OBJ_TEXT, 0, g_FVG.createdTime, g_FVG.high); ObjectMove(0, FVG_LABEL_NAME, 0, g_FVG.createdTime, g_FVG.high); ObjectSetString (0, FVG_LABEL_NAME, OBJPROP_TEXT, labelText); ObjectSetInteger(0, FVG_LABEL_NAME, OBJPROP_COLOR, rectColor); ObjectSetInteger(0, FVG_LABEL_NAME, OBJPROP_FONTSIZE, 8); ObjectSetInteger(0, FVG_LABEL_NAME, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); } //==================================================== // DEBUG DRAW //==================================================== void DrawContextDebug() { if (!InpDebugDraw) return; #define SET_LABEL(name, text, ypos, clr) \ if (ObjectFind(0, name) < 0) \ ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0); \ ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_RIGHT_UPPER); \ ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 10); \ ObjectSetInteger(0, name, OBJPROP_YDISTANCE, ypos); \ ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 9); \ ObjectSetInteger(0, name, OBJPROP_COLOR, clr); \ ObjectSetString (0, name, OBJPROP_TEXT, text); SET_LABEL("DBG_HEADER", "── Context Debug ──", 10, clrSilver) // ── BIAS ────────────────────────────────────────────────────────────── color biasColor = (g_Bias.bias == BIAS_UP) ? clrLime : (g_Bias.bias == BIAS_DOWN) ? clrTomato : (g_Bias.bias == BIAS_SIDEWAY) ? clrOrange : clrGray; SET_LABEL("DBG_BIAS", StringFormat("Bias : %s", EnumToString(g_Bias.bias)), 30, biasColor) if (g_Bias.bias == BIAS_SIDEWAY) { SET_LABEL("DBG_BIAS_RANGE", StringFormat(" Range : %.5f – %.5f", g_Bias.rangeLow, g_Bias.rangeHigh), 48, clrOrange) } else ObjectDelete(0, "DBG_BIAS_RANGE"); // ── DAILY RISK ──────────────────────────────────────────────────────── double lostPct = g_DailyRisk.startBalance > 0 ? (g_DailyRisk.startBalance - g_DailyRisk.currentBalance) / g_DailyRisk.startBalance * 100.0 : 0.0; color riskColor = g_DailyRisk.limitHit ? clrRed : lostPct > InpMaxDailyLossPct * 0.7 ? clrOrange : clrLime; SET_LABEL("DBG_RISK", StringFormat("Risk : %.2f%% / %.2f%%", lostPct, InpMaxDailyLossPct), 66, riskColor) SET_LABEL("DBG_BAL", StringFormat("Bal : %.2f (start %.2f)", g_DailyRisk.currentBalance, g_DailyRisk.startBalance), 84, clrSilver) SET_LABEL("DBG_LIMIT", g_DailyRisk.limitHit ? "⛔ DAILY LOSS HIT" : "✅ Loss OK", 102, g_DailyRisk.limitHit ? clrRed : clrLime) // ── EA STATE ────────────────────────────────────────────────────────── color stateColor = (g_State == EA_IDLE) ? clrSilver : (g_State == EA_WAIT_TOUCH) ? clrOrange : (g_State == EA_WAIT_TRIGGER) ? clrYellow : (g_State == EA_IN_TRADE) ? clrLime : clrGray; SET_LABEL("DBG_STATE", StringFormat("State : %s", EnumToString(g_State)), 120, stateColor) // ── FVG INFO ────────────────────────────────────────────────────────── if (g_FVG.active) { color fvgColor = g_FVG.touched ? clrDeepSkyBlue : clrDodgerBlue; SET_LABEL("DBG_FVG", StringFormat("FVG : %s [%.5f – %.5f]", EnumToString(g_FVG.direction), g_FVG.low, g_FVG.high), 138, fvgColor) } else ObjectDelete(0, "DBG_FVG"); // ── BLOCK REASON ────────────────────────────────────────────────────── if (g_BlockReason != BLOCK_NONE) { SET_LABEL("DBG_BLOCK", StringFormat("Block : %s", EnumToString(g_BlockReason)), 156, clrTomato) } else ObjectDelete(0, "DBG_BLOCK"); #undef SET_LABEL ChartRedraw(0); } void DrawVisuals() { DrawFVGRectangle(); DrawContextDebug(); } //==================================================== // EA LIFECYCLE //==================================================== int OnInit() { ZeroMemory(g_Bias); ZeroMemory(g_FVG); ZeroMemory(g_Trigger); ZeroMemory(g_DailyRisk); g_State = EA_IDLE; g_BlockReason = BLOCK_NONE; g_TradeBarIndex = -1; UpdateAllContexts(); PrintFormat("✅ EA initialized | BiasTF=%s | MiddleTF=%s | TriggerTF=%s", EnumToString(InpBiasTF), EnumToString(InpMiddleTF), EnumToString(InpTriggerTF)); return INIT_SUCCEEDED; } void OnTick() { UpdateAllContexts(); if (EvaluateGuards()) RunStateMachine(); else if (g_State != EA_IDLE) // Chỉ reset 1 lần khi vừa bị block ResetToIdle(EnumToString(g_BlockReason)); if (InpDebugDraw) DrawVisuals(); } void OnDeinit(const int reason) { ObjectDelete(0, FVG_RECT_NAME); ObjectDelete(0, FVG_MID_NAME); ObjectDelete(0, FVG_LABEL_NAME); ObjectsDeleteAll(0, "DBG_"); ChartRedraw(0); PrintFormat("EA deinitialized | reason=%d", reason); }