//+------------------------------------------------------------------+
//|                                              SmartLogic_V7.mqh   |
//| Core Engine - Ver 7.10 (Partial Trailing & Multi-Layer Logic)    |
//+------------------------------------------------------------------+
#property strict

class CSmartLogic {
private:
   int m_MagicMain, m_MagicHedge, m_MagicSoft; 
   string m_Name;
   bool m_IsTrailingActive, m_IsRescueTrailing; 
   double m_TrailMaxProfit, m_RescueMaxProfit;
   double m_PartMaxProfit[5]; // Menyimpan profit per batch

public:
   void Init(int m, int h, int s, string n) {
      m_MagicMain = m; m_MagicHedge = h; m_MagicSoft = s; m_Name = n;
      m_IsTrailingActive = false; m_IsRescueTrailing = false;
      ArrayInitialize(m_PartMaxProfit, 0);
   }

   // --- FUNGSI EKSEKUSI V7.10 ---
   void RunLogicV7(int signal, double lot, double mult, int step, int tp, int maxL, int xLayer, bool useSH, int shGap, int slPlus, bool useHH, double ddLimit, double resTarget, double buffer, double inject, bool useTrail, int tStart, int tStep) {
      
      int totalMain = CountOrdersByMagic(m_MagicMain);

      // --- FIRST ORDER LAYER --- [cite: 109]
      if(totalMain == 0 && signal != -1 && !IsHedged() && !IsSoftHedged()) {
         for(int i=0; i < xLayer; i++) {
            if(OrderSend(Symbol(), signal, lot, (signal==OP_BUY?Ask:Bid), 3, 0, 0, m_Name, m_MagicMain, 0, (signal==OP_BUY?clrBlue:clrRed)) < 0) continue;
         }
         return;
      }

      if(useSH && (totalMain / xLayer) >= maxL) ManageSoftHedge(shGap, slPlus);

      if(IsHedged()) {
         ManageRescue(resTarget, buffer, inject, step);
      } else {
         if(useTrail && totalMain > 0) CheckVirtualTrailingPoints(tStart, tStep);
         ProcessGridV7(mult, step, maxL, xLayer);
         if(useHH && totalMain > 0 && GetDrawdownPct() >= ddLimit) ExecuteLocking();
      }
   }

   // --- LOGIKA PARTIAL TRAILING (5 TAHAP) ---
   void CheckPartialTrailing(int batchSize, int startPoints, int dropPoints) {
      int totalOrders = CountOrdersByMagic(m_MagicMain);
      if(totalOrders < batchSize) return;

      int numStages = MathMin(5, totalOrders / batchSize);
      double vpp = (Point / MathMax(Point, MarketInfo(Symbol(), MODE_TICKSIZE))) * MarketInfo(Symbol(), MODE_TICKVALUE);

      for(int s = 0; s < numStages; s++) {
         double sPL = 0; double sLots = 0;
         int startIdx = s * batchSize;
         int endIdx = startIdx + batchSize;
         int found = 0; int tickets[]; ArrayResize(tickets, batchSize);

         int tempCount = 0;
         for(int i = 0; i < OrdersTotal(); i++) {
            if(OrderSelect(i, SELECT_BY_POS) && OrderMagicNumber() == m_MagicMain && OrderSymbol() == Symbol()) {
               if(tempCount >= startIdx && tempCount < endIdx) {
                  sPL += (OrderProfit() + OrderCommission() + OrderSwap());
                  sLots += OrderLots();
                  tickets[found] = OrderTicket();
                  found++;
               }
               tempCount++;
               if(found >= batchSize) break;
            }
         }

         double targetM = startPoints * sLots * vpp;
         double dropM = dropPoints * sLots * vpp;

         if(sPL >= targetM) {
            if(sPL > m_PartMaxProfit[s]) m_PartMaxProfit[s] = sPL;
            if(sPL <= (m_PartMaxProfit[s] - dropM)) {
               for(int j = 0; j < found; j++) {
                  if(OrderSelect(tickets[j], SELECT_BY_TICKET))
                     OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), 10, clrWhite);
               }
               m_PartMaxProfit[s] = 0;
            }
         } else if(sPL < 0) m_PartMaxProfit[s] = 0;
      }
   }

   // --- LOGIKA GRID MULTI-LAYER --- [cite: 134]
   void ProcessGridV7(double mult, int step, int maxL, int xLayer) {
      int totalOrders = CountOrdersByMagic(m_MagicMain);
      if((totalOrders / xLayer) >= maxL || totalOrders == 0) return;

      double lastP = GetLastPrice(m_MagicMain);
      int lastT = GetLastType(m_MagicMain);
      double lastL = GetLastLot(m_MagicMain);
      double dist = (lastT == OP_BUY) ? (lastP - Bid)/Point : (Ask - lastP)/Point;

      if(dist >= step) {
         double nextL = NormalizeDouble(lastL * mult, 2);
         if(mult > 1.0 && nextL <= lastL) nextL = lastL + 0.01;
         for(int i=0; i < xLayer; i++) {
            if(OrderSend(Symbol(), lastT, nextL, (lastT==OP_BUY?Ask:Bid), 3, 0, 0, "GridV7", m_MagicMain, 0, (lastT==OP_BUY?clrBlue:clrRed)) < 0) continue;
         }
      }
   }

   // --- SMART RESCUE & INJECT --- [cite: 145, 170]
   void ManageRescue(double resTarget, double buffer, double inject, int step) {
      double plG=0, plH=0; int tH=-1;
      for(int i=0; i<OrdersTotal(); i++) if(OrderSelect(i, SELECT_BY_POS) && OrderSymbol()==Symbol()) {
         double p=OrderProfit()+OrderCommission()+OrderSwap();
         if(OrderMagicNumber()==m_MagicMain) plG+=p; 
         if(OrderMagicNumber()==m_MagicHedge){plH+=p; tH=OrderTicket();}
      }
      double net=plG+plH; 
      if(net>=resTarget){m_IsRescueTrailing=true; m_RescueMaxProfit=MathMax(m_RescueMaxProfit,net);}
      if(m_IsRescueTrailing && net<=(m_RescueMaxProfit-buffer)) CloseAllPositions(); 
      if(net<0) m_IsRescueTrailing=false;

      if(!m_IsRescueTrailing && plH<0 && tH!=-1) {
         if(OrderSelect(tH, SELECT_BY_TICKET)){
            int iT=(OrderType()==OP_BUY)?OP_SELL:OP_BUY; 
            double cP=(iT==OP_BUY)?Ask:Bid; double lIP=GetLastInjectPrice();
            if(lIP==0 || MathAbs(cP-lIP)/Point>=step) 
               if(CountInject()<5) OrderSend(Symbol(), iT, inject, cP, 3, 0, 0, "INJECT", m_MagicMain, 0, clrGreen);
         }
      }
   }

   // --- VIRTUAL TRAILING GLOBAL --- [cite: 115]
   void CheckVirtualTrailingPoints(int st, int sp) {
      double pl = GetTotalProfitDollar(); double tl = GetTotalLots(m_MagicMain); if(tl <= 0) return;
      double vpp = (Point / MathMax(Point, MarketInfo(Symbol(), MODE_TICKSIZE))) * MarketInfo(Symbol(), MODE_TICKVALUE);
      double startM = st * tl * vpp; double dropM = sp * tl * vpp;
      if(pl >= startM && !m_IsTrailingActive) { m_IsTrailingActive = true; m_TrailMaxProfit = pl; }
      if(m_IsTrailingActive) {
         m_TrailMaxProfit = MathMax(m_TrailMaxProfit, pl);
         if(pl <= MathMax(m_TrailMaxProfit - dropM, startM * 0.10)) CloseAllPositions();
      }
      if(pl < 0) m_IsTrailingActive = false;
   }

   // --- TIME BLOCK LOGIC --- [cite: 53]
   bool IsTradingBlocked(string sS, string eS, bool u) {
      if(!u) return false; 
      MqlDateTime dt; TimeToStruct(TimeCurrent(), dt); int n=(dt.hour*60)+dt.min;
      string s[]; StringSplit(sS,':',s); int st=(int)StringToInteger(s[0])*60+(int)StringToInteger(s[1]);
      string e[]; StringSplit(eS,':',e); int en=(int)StringToInteger(e[0])*60+(int)StringToInteger(e[1]);
      return (st>en)?(n>=st||n<en):(n>=st&&n<en);
   }

   // --- UTILITIES ---
   void ManageSoftHedge(int gap, int slPlus) {
      if(CountOrdersByMagic(m_MagicSoft) == 0) {
         double lastP = GetLastPrice(m_MagicMain); double totalL = GetTotalLots(m_MagicMain); int lastT = GetLastType(m_MagicMain);
         if(lastP > 0 && ((lastT==OP_BUY && (lastP-Bid)/Point>=gap) || (lastT==OP_SELL && (Ask-lastP)/Point>=gap)))
            if(OrderSend(Symbol(), (lastT==OP_BUY?OP_SELL:OP_BUY), NormalizeDouble(totalL, 2), (lastT==OP_BUY?Bid:Ask), 3, 0, 0, "SH", m_MagicSoft, 0, clrYellow) < 0) return;
      } else {
         for(int i=OrdersTotal()-1; i>=0; i--) if(OrderSelect(i, SELECT_BY_POS) && OrderMagicNumber() == m_MagicSoft) {
            double p = (OrderType()==OP_BUY) ? (Bid-OrderOpenPrice())/Point : (OrderOpenPrice()-Ask)/Point;
            if(p >= slPlus || p < -500) if(!OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), 10, clrWhite)) continue;
         }
      }
   }
   void ExecuteLocking() {
      double b=0, s=0; for(int i=0; i<OrdersTotal(); i++) if(OrderSelect(i, SELECT_BY_POS) && OrderMagicNumber()==m_MagicMain) { if(OrderType()==OP_BUY) b+=OrderLots(); else s+=OrderLots(); }
      double l = NormalizeDouble(MathAbs(b-s), 2); if(l>0) if(OrderSend(Symbol(), (b>s?OP_SELL:OP_BUY), l, (b>s?Bid:Ask), 3, 0, 0, "HL", m_MagicHedge, 0, clrOrange) < 0) return;
   }
   void CloseAllPositions() { for(int i=OrdersTotal()-1; i>=0; i--) if(OrderSelect(i, SELECT_BY_POS) && (OrderMagicNumber()==m_MagicMain || OrderMagicNumber()==m_MagicHedge || OrderMagicNumber()==m_MagicSoft)) OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), 10, clrWhite); m_IsTrailingActive=false; m_IsRescueTrailing=false; }
   int CountOrdersByMagic(int m) { int c=0; for(int i=0; i<OrdersTotal(); i++) if(OrderSelect(i, SELECT_BY_POS) && OrderMagicNumber()==m) c++; return c; }
   int CountInject() { int c=0; for(int i=0; i<OrdersTotal(); i++) if(OrderSelect(i, SELECT_BY_POS) && OrderMagicNumber()==m_MagicMain && StringFind(OrderComment(),"INJECT")>=0) c++; return c; }
   double GetTotalLots(int m) { double l=0; for(int i=0; i<OrdersTotal(); i++) if(OrderSelect(i, SELECT_BY_POS) && OrderMagicNumber()==m) l+=OrderLots(); return l; }
   double GetLastPrice(int m) { datetime t=0; double p=0; for(int i=0; i<OrdersTotal(); i++) if(OrderSelect(i, SELECT_BY_POS) && OrderMagicNumber()==m && OrderOpenTime()>t) { t=OrderOpenTime(); p=OrderOpenPrice(); } return p; }
   double GetLastInjectPrice() { datetime t=0; double p=0; for(int i=0; i<OrdersTotal(); i++) if(OrderSelect(i, SELECT_BY_POS) && OrderMagicNumber()==m_MagicMain && StringFind(OrderComment(),"INJECT")>=0 && OrderOpenTime()>t) { t=OrderOpenTime(); p=OrderOpenPrice(); } return p; }
   int GetLastType(int m) { datetime t=0; int ty=-1; for(int i=0; i<OrdersTotal(); i++) if(OrderSelect(i, SELECT_BY_POS) && OrderMagicNumber()==m && OrderOpenTime()>t) { t=OrderOpenTime(); ty=OrderType(); } return ty; }
   double GetLastLot(int m) { datetime t=0; double l=0; for(int i=0; i<OrdersTotal(); i++) if(OrderSelect(i, SELECT_BY_POS) && OrderMagicNumber()==m && OrderOpenTime()>t) { t=OrderOpenTime(); l=OrderLots(); } return l; }
   double GetTotalProfitDollar() { double pl=0; for(int i=0; i<OrdersTotal(); i++) if(OrderSelect(i, SELECT_BY_POS) && (OrderMagicNumber()==m_MagicMain || OrderMagicNumber()==m_MagicHedge)) pl+=OrderProfit()+OrderCommission()+OrderSwap(); return pl; }
   double GetDrawdownPct() { double b=AccountBalance(); if(b<=0) return 0; double pl=0; for(int i=0; i<OrdersTotal(); i++) if(OrderSelect(i, SELECT_BY_POS) && OrderMagicNumber()==m_MagicMain) pl+=OrderProfit()+OrderCommission()+OrderSwap(); return (pl<0)?(MathAbs(pl)/b)*100.0:0; }
   bool IsHedged() { return CountOrdersByMagic(m_MagicHedge)>0; }
   bool IsSoftHedged() { return CountOrdersByMagic(m_MagicSoft)>0; }
};