//+------------------------------------------------------------------+
//| TOL LANGIT ETF.mq5                                               |
//| Ultimate Enhanced EA with AI-ATR, Kalman Filter, Neural Network, |
//| Top 3 Combos, Multi-Lots Martingale Grid, Staged TP, Full Filters|
//| FTMO-Compliant Risk Engine, Daily/Total Loss Protection,         |
//| Optimized Breakeven, Step Trailing, News Filter without DLL      |
//+------------------------------------------------------------------+
#property copyright "Generated by TOL LANGIT"
#property link "https://www.mql5.com/en/users/adithyodw"
#property version "16.01"
#property description "TOL LANGIT ETF: Adaptive Forex/Gold EA with Kalman, Neural Fusion, Martingale Grid up to 10 levels, Step Trailing, Enhanced Breakeven"
#property description "FTMO-Compliant: % Risk per Trade, SL Enforced, DD Protection, Built-in News Filter via WebRequest (no DLL), Auto GMT"

// Deep Neural Network class
#define SIZE_HIDDENA 4
#define SIZE_HIDDENB 4
#define SIZE_OUTPUT 2

class DeepNeuralNetwork
  {
private:
   int               numInput;
   int               numHiddenA;
   int               numHiddenB;
   int               numOutput;
   double            inputs[];
   double            iaWeights[][SIZE_HIDDENA];
   double            abWeights[][SIZE_HIDDENB];
   double            boWeights[][SIZE_OUTPUT];
   double            aBiases[];
   double            bBiases[];
   double            oBiases[];
   double            aOutputs[];
   double            bOutputs[];
   double            outputs[];

public:
                     DeepNeuralNetwork(int _numInput,
                                       int _numHiddenA,
                                       int _numHiddenB,
                                       int _numOutput);
   void              SetWeights(double &weights[]);
   void              ComputeOutputs(double &xValues[],
                                    double &yValues[]);
   double            HyperTanFunction(double x);
   void              Softmax(double &oSums[],
                             double &_softOut[]);
  };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
DeepNeuralNetwork::DeepNeuralNetwork(int _numInput,
                                     int _numHiddenA,
                                     int _numHiddenB,
                                     int _numOutput)
  {
   numInput      =_numInput;
   numHiddenA    =_numHiddenA;
   numHiddenB    =_numHiddenB;
   numOutput     =_numOutput;

   ArrayResize(inputs,numInput);
   ArrayResize(aBiases,numHiddenA);
   ArrayResize(bBiases,numHiddenB);
   ArrayResize(oBiases,numOutput);
   ArrayResize(aOutputs,numHiddenA);
   ArrayResize(bOutputs,numHiddenB);
   ArrayResize(outputs,numOutput);

   // weight matrices are static in the second dimension
   ArrayResize(iaWeights,numInput);
   ArrayResize(abWeights,numHiddenA);
   ArrayResize(boWeights,numHiddenB);
  }

//+------------------------------------------------------------------+
//| SetWeights - fill weight and bias arrays from a flat array       |
//+------------------------------------------------------------------+
void DeepNeuralNetwork::SetWeights(double &weights[])
  {
   int idx=0;

   // iaWeights (input to hidden A)
   for(int i=0;i<numInput;i++)
     {
      for(int j=0;j<numHiddenA;j++)
        {
         iaWeights[i][j]=weights[idx++];
        }
     }

   // aBiases
   for(int i=0;i<numHiddenA;i++)
      aBiases[i]=weights[idx++];

   // abWeights (hidden A to hidden B)
   for(int i=0;i<numHiddenA;i++)
     {
      for(int j=0;j<numHiddenB;j++)
        {
         abWeights[i][j]=weights[idx++];
        }
     }

   // bBiases
   for(int i=0;i<numHiddenB;i++)
      bBiases[i]=weights[idx++];

   // boWeights (hidden B to output)
   for(int i=0;i<numHiddenB;i++)
     {
      for(int j=0;j<numOutput;j++)
        {
         boWeights[i][j]=weights[idx++];
        }
     }

   // oBiases
   for(int i=0;i<numOutput;i++)
      oBiases[i]=weights[idx++];
  }

//+------------------------------------------------------------------+
//| ComputeOutputs - forward pass                                    |
//+------------------------------------------------------------------+
void DeepNeuralNetwork::ComputeOutputs(double &xValues[],
                                      double &yValues[])
  {
   double aSums[];
   double bSums[];
   double oSums[];

   ArrayResize(aSums,numHiddenA);
   ArrayFill(aSums,0,numHiddenA,0);
   ArrayResize(bSums,numHiddenB);
   ArrayFill(bSums,0,numHiddenB,0);
   ArrayResize(oSums,numOutput);
   ArrayFill(oSums,0,numOutput,0);

   int size=ArraySize(xValues);
   for(int i=0;i<size;++i)          // copy x-values to inputs
      this.inputs[i]=xValues[i];

   for(int j=0;j<numHiddenA;++j)   // compute sum of (ia) weights * inputs
     {
      for(int i=0;i<numInput;++i)
         aSums[j]+=this.inputs[i]*this.iaWeights[i][j];
     }

   for(int i=0;i<numHiddenA;++i)   // add biases to a sums
      aSums[i]+=this.aBiases[i];

   for(int i=0;i<numHiddenA;++i)   // apply activation
      this.aOutputs[i]=HyperTanFunction(aSums[i]);

   for(int j=0;j<numHiddenB;++j)   // compute sum of (ab) weights * a outputs
     {
      for(int i=0;i<numHiddenA;++i)
         bSums[j]+=aOutputs[i]*this.abWeights[i][j];
     }

   for(int i=0;i<numHiddenB;++i)   // add biases to b sums
      bSums[i]+=this.bBiases[i];

   for(int i=0;i<numHiddenB;++i)   // apply activation
      this.bOutputs[i]=HyperTanFunction(bSums[i]);

   for(int j=0;j<numOutput;++j)    // compute sum of (bo) weights * b outputs
     {
      for(int i=0;i<numHiddenB;++i)
         oSums[j]+=bOutputs[i]*boWeights[i][j];
     }

   for(int i=0;i<numOutput;++i)    // add biases to output sums
      oSums[i]+=oBiases[i];

   double softOut[];
   Softmax(oSums,softOut);
   ArrayCopy(outputs,softOut);
   ArrayCopy(yValues,this.outputs);
  }

//+------------------------------------------------------------------+
//| HyperTanFunction - tanh activation (clipped)                     |
//+------------------------------------------------------------------+
double DeepNeuralNetwork::HyperTanFunction(double x)
  {
   if(x<-20.0) return -1.0;
   if(x> 20.0) return  1.0;
   return MathTanh(x);
  }

//+------------------------------------------------------------------+
//| Softmax - normalises a vector of raw scores to probabilities    |
//+------------------------------------------------------------------+
void DeepNeuralNetwork::Softmax(double &oSums[],
                               double &_softOut[])
  {
   int size=ArraySize(oSums);
   double max=oSums[0];
   for(int i=0;i<size;++i)
      if(oSums[i]>max) max=oSums[i];

   double scale=0.0;
   for(int i=0;i<size;i++)
      scale+=MathExp(oSums[i]-max);

   ArrayResize(_softOut,size);
   for(int i=0;i<size;i++)
      _softOut[i]=MathExp(oSums[i]-max)/scale;
  }

//================ INPUT PARAMETERS ===================
//********* Lot settings *********
input double FixedLot = 0.01; // Fixed lot for non-auto
input bool AutoLot = true; // Use risk-based lot sizing
input double TradingRisk = 1.0; // Risk % per trade (optimized for Forex/Gold)
input double MaxLot = 10.0; // Max lot size
input double MinLot = 0.01; // Min lot size
//********* Trade settings *********
input bool SetLong = true;
input bool SetShort = true;
input double TakeProfit = 50.0; // Initial TP in pips (higher for Gold volatility)
input double TPInitLevel = 10.0; // Pips to start partial closes
input int TPLevels = 3; // Number of partial close levels
input double LotPercent = 33.3; // % lot to close at each level
input double TPSmooth = 3.3; // Smoothing factor for Kalman
input double RNDLevel = 10.0; // Random level (unused)
input double TSLRatio = 1.75; // Trail ratio adjustment
input double RoundRTP = 1.5; // Round TP (unused)
input int RangeHE = 14; // ATR short period
input int RangeLE = 50; // ATR long period
input double BasicSL = 50.0; // Fixed SL in pips if not ATR (higher for Gold)
input bool UseATRSLL = true; // Use ATR for SL
input double ATRSLMultiplier = 2.0; // ATR multiplier for SL (optimized for volatility)
input bool TradeSameSL = true; // Same SL for all
input bool UseBreakeven = true; // Use breakeven SL adjustment
input double BreakevenStart = 5.0; // Pips in profit to trigger breakeven
input double BreakevenLock = 0.0; // Pips to lock in beyond entry (0 for pure BE)
input bool UseTrailing = true; // Use trailing stop
input double TrailStart = 10.0; // Pips profit to start trailing
input double TrailDistance = 5.0; // Initial trail distance in pips
input double TrailStep = 2.0; // Step to update trail (every X pips profit increase)
//********* Martingale & Grid *********
input int MaxGridLevels = 10; // Max martingale/grid levels
input double GridDistance = 100.0; // Pips between grid levels
input double LotMultiplier = 2.0; // Lot multiplier for each martingale level (e.g., 1,2,4,...)
//********* Spread filter *********
input double MaxSpread = 2.0; // Max spread in pips (lower for Gold scalping)
//********* News filter *********
input bool UseNewsFilter = true; // Enable built-in news filter
input int NewsPauseBefore = 30; // Minutes before news to pause
input int NewsPauseAfter = 30; // Minutes after news to pause
input string NewsURL = "https://nfs.faireconomy.media/ff_calendar_thisweek.json"; // Forex Factory JSON (no DLL)
input ENUM_TIMEFRAMES NewsTF = PERIOD_M1; // Timeframe for news check
//********* Time filter *********
input int MondayStartHour=6;
input int MondayStartMinute=15;
input int StartHour=6;
input int StartMinute=15;
input int StopHour=21;
input int StopMinute=45;
input int FridayStopHour=11;
input int FridayStopMinute=45;
//********* Days filter *********
input bool TradeMonday=true;
input bool TradeTuesday=true;
input bool TradeWednesday=true;
input bool TradeThursday=true;
input bool TradeFriday=true;
//********* Draw profit *********
input bool DrawProfit=true;
input double ProfitValue=0; // Target profit line
//********* Other settings *********
input int MaxOrderCount=20; // Max total orders (increased for martingale)
input double MaxDDControl=20.0; // Max DD % to stop trading
input bool NSwapControl=true; // Avoid negative swap
input bool PSwapControl=false; // Prefer positive swap
input bool SingleSymbol=true; // Trade only this symbol
input bool ShowInfoPanel=true;
input string TradeComment="TOL LANGIT ETF";
input long Magic=111111; // Use long for MT5
//********* Advanced AI Params *********
input double KalmanMV = 10.0; // Measurement variance
input double KalmanPV = 1.0; // Process variance
input double FuzzyThreshold = 0.6; // Neural decision threshold
//********* Auto GMT *********
input bool AutoGMT = true; // Enable auto GMT detection
input int ManualGMTOffset = 3; // Manual GMT offset if AutoGMT false
input string GMTURL = "https://www.worldtimeserver.com/current_time_in_UTC.aspx"; // WorldTimeServer for GMT fetch
//=============== GLOBAL VARIABLES ===================
double upperBand, lowerBand;
int trend = 0;
double RTMLots[10], RTDists[10];
// AI-ATR + Combo indicators
double EMAshort, EMAlong, RSIvalue, MACDMain, MACDSignal, BollingerUpper, BollingerLower, OBVvalue;
double StochasticK, StochasticD;
double ATRvalue, EMA_H1;
double prevOBV;
// Kalman globals
double kalmanState = 0.0;
double kalmanCovariance = 1.0;
// Combo strengths for neural
double combo1Strength = 0.0, combo2Strength = 0.0, combo3Strength = 0.0;
// Neural outputs
double fuzzyBuy = 0.0, fuzzySell = 0.0;
// Tick analysis
datetime lastTickTime = 0;
double tickSpeed = 0.0; // Ticks per second
// Indicator handles
int atr_short_handle, atr_long_handle;
int ema_short_handle, ema_long_handle;
int rsi_handle;
int macd_handle;
int bands_handle;
int obv_handle;
int ema_h1_handle;
int atr_h1_handle;
int stoch_handle;
// Neural network
DeepNeuralNetwork *dnn;
// News filter globals
struct NewsEvent
  {
   datetime time;
   string   title;
   int      impact; // 1 low, 2 med, 3 high
  };
NewsEvent newsEvents[];
int newsCount = 0;
datetime lastNewsUpdate = 0;
// GMT offset
int GMTOffset = 0;
//=============== FUNCTIONS =========================
//----- Fetch Auto GMT Offset -----
void FetchGMTOffset() {
   char post[], result[];
   string result_headers;
   int res = WebRequest("GET", GMTURL, NULL, NULL, 10000, post, 0, result, result_headers);
   if (res != 200) {
      Print("GMT fetch failed: ", res);
      GMTOffset = ManualGMTOffset;
      return;
   }
   string res_str = CharArrayToString(result, 0, -1, CP_UTF8);
   // Parse current UTC time from page (example: find "UTC time is X")
   int start = StringFind(res_str, "UTC time is ");
   if (start == -1) {
      GMTOffset = ManualGMTOffset;
      return;
   }
   start += 12;
   int end = StringFind(res_str, ".", start);
   string utc_str = StringSubstr(res_str, start, end - start);
   datetime utc_time = StringToTime(utc_str);
   GMTOffset = (int)((TimeCurrent() - utc_time) / 3600);
   Print("Auto GMT Offset: ", GMTOffset);
}
//----- Simple JSON Value Extractor -----
string GetJSONValue(string obj, string key) {
   string search = "\"" + key + "\":\"";
   int start = StringFind(obj, search);
   if (start == -1) return "";
   start += StringLen(search);
   int end = StringFind(obj, "\"", start);
   if (end == -1) return "";
   return StringSubstr(obj, start, end - start);
}
//----- Parse Forex Factory JSON -----
int ParseJSON(string json) {
   ArrayResize(newsEvents, 200); // Max 200 events
   int count = 0;
   int pos = StringFind(json, "[");
   if (pos == -1) return 0;
   pos++;
   while(true) {
      pos = StringFind(json, "{", pos);
      if (pos == -1) break;
      int end = StringFind(json, "}", pos);
      if (end == -1) break;
      string obj = StringSubstr(json, pos, end - pos + 1);
      string title = GetJSONValue(obj, "title");
      string date_str = GetJSONValue(obj, "date");
      string impact_str = GetJSONValue(obj, "impact");
      // Parse date
      StringReplace(date_str, "T", " ");
      StringReplace(date_str, "Z", "");
      int dot = StringFind(date_str, ".");
      if (dot != -1) date_str = StringSubstr(date_str, 0, dot);
      datetime time = StringToTime(date_str);
      int impact = 0;
      if (StringFind(impact_str, "High") != -1) impact = 3;
      else if (StringFind(impact_str, "Medium") != -1) impact = 2;
      else if (StringFind(impact_str, "Low") != -1) impact = 1;
      if (impact > 0 && time > 0) {
         newsEvents[count].time = time;
         newsEvents[count].title = title;
         newsEvents[count].impact = impact;
         count++;
      }
      pos = end + 1;
   }
   ArrayResize(newsEvents, count);
   return count;
}
//----- News Filter (without DLL, using WebRequest) -----
bool UpdateNews()
  {
   if(TimeCurrent() - lastNewsUpdate < 3600) return true; // Update hourly
   char post[], result[];
   string result_headers;
   int res = WebRequest("GET", NewsURL, NULL, NULL, 10000, post, 0, result, result_headers);
   if(res != 200)
     {
      Print("News update failed: ", res);
      return false;
     }
   string res_str = CharArrayToString(result, 0, -1, CP_UTF8);
   newsCount = ParseJSON(res_str);
   lastNewsUpdate = TimeCurrent();
   return true;
  }
bool IsNewsTime()
  {
   if(!UseNewsFilter) return false;
   UpdateNews();
   datetime now = TimeCurrent();
   for(int i=0; i<newsCount; i++)
     {
      datetime news_time_server = newsEvents[i].time + GMTOffset * 3600; // Adjust GMT news to server time
      if(now >= news_time_server - NewsPauseBefore*60 && now <= news_time_server + NewsPauseAfter*60)
         return true;
     }
   return false;
  }
//----- Helper to get indicator value -----
double GetIndicatorValue(int handle, int buffer, int shift)
{
   double val[1];
   if (CopyBuffer(handle, buffer, shift, 1, val) < 0) return 0.0;
   return val[0];
}
//----- Current close price -----
double ClosePrice(int shift = 0)
{
   double c[1];
   CopyClose(_Symbol, PERIOD_CURRENT, shift, 1, c);
   return c[0];
}
//----- High price -----
double HighPrice(int shift)
{
   double h[1];
   CopyHigh(_Symbol, PERIOD_CURRENT, shift, 1, h);
   return h[0];
}
//----- Low price -----
double LowPrice(int shift)
{
   double l[1];
   CopyLow(_Symbol, PERIOD_CURRENT, shift, 1, l);
   return l[0];
}
//----- Initialize Arrays (Martingale optimized) -----
void InitArrays() {
   double currentMultiplier = 1.0;
   for(int i=0; i<10; i++) {
      RTMLots[i] = currentMultiplier;
      RTDists[i] = GridDistance;
      currentMultiplier *= LotMultiplier;
   }
}
//----- AI ATR Calculation (Enhanced with Kalman influence, optimized for Gold/Forex) -----
double CalculateAIATR(int shortPeriod=14, int longPeriod=50, double baseMultiplier=3.0, double factor=2.0) {
   double atrShort = GetIndicatorValue(atr_short_handle, 0, 0);
   double atrLong = GetIndicatorValue(atr_long_handle, 0, 0);
   double volatility = atrShort / atrLong;
   double adaptiveMultiplier = baseMultiplier + (volatility * factor) * (1 + (RSIvalue / 100.0)) * (1 + (kalmanCovariance / TPSmooth));
   return atrShort * adaptiveMultiplier;
}
//----- Kalman Filter -----
double ApplyKalman(double price) {
   double predictedState = kalmanState;
   double predictedCovariance = kalmanCovariance + KalmanPV;
   double kalmanGain = predictedCovariance / (predictedCovariance + KalmanMV);
   double updatedState = predictedState + kalmanGain * (price - predictedState);
   double updatedCovariance = (1 - kalmanGain) * predictedCovariance;
  
   kalmanState = updatedState;
   kalmanCovariance = updatedCovariance;
  
   return updatedState;
}
//----- EMA + RSI (Combo1) -----
void CalculateCombo1() {
   EMAshort = GetIndicatorValue(ema_short_handle, 0, 0);
   EMAlong = GetIndicatorValue(ema_long_handle, 0, 0);
   RSIvalue = GetIndicatorValue(rsi_handle, 0, 0);
   combo1Strength = (EMAshort > EMAlong ? (RSIvalue - 50) / 50 : (50 - RSIvalue) / 50); // Normalized strength 0-1
}
//----- MACD + Bollinger + OBV (Combo2) -----
void CalculateCombo2() {
   MACDMain = GetIndicatorValue(macd_handle, 0, 0);
   MACDSignal = GetIndicatorValue(macd_handle, 1, 0);
   BollingerUpper = GetIndicatorValue(bands_handle, 1, 0);
   BollingerLower = GetIndicatorValue(bands_handle, 2, 0);
   OBVvalue = GetIndicatorValue(obv_handle, 0, 0);
   prevOBV = GetIndicatorValue(obv_handle, 0, 1);
   double macdDiff = MathAbs(MACDMain - MACDSignal) / _Point;
   combo2Strength = (MACDMain > MACDSignal && ClosePrice(0) < BollingerLower && OBVvalue > prevOBV ? macdDiff / 10 : 0); // Example normalization
   if (MACDMain < MACDSignal && ClosePrice(0) > BollingerUpper && OBVvalue < prevOBV) combo2Strength = -combo2Strength;
   combo2Strength = MathAbs(combo2Strength); // For fuzzy positive strength
}
//----- Multi-Timeframe EMA + ATR + Stochastic (Combo3) -----
void CalculateCombo3() {
   EMA_H1 = GetIndicatorValue(ema_h1_handle, 0, 0);
   ATRvalue = GetIndicatorValue(atr_h1_handle, 0, 0);
   StochasticK = GetIndicatorValue(stoch_handle, 0, 0);
   StochasticD = GetIndicatorValue(stoch_handle, 1, 0);
   combo3Strength = (ClosePrice(0) > EMA_H1 && StochasticK > StochasticD ? (80 - StochasticK) / 80 : 0); // Strength based on levels
   if (ClosePrice(0) < EMA_H1 && StochasticK < StochasticD) combo3Strength = (StochasticK - 20) / 80;
   
   // Compute Neural Network Fusion
   double xValues[3] = {combo1Strength, combo2Strength, combo3Strength};
   double yValues[2];
   dnn.ComputeOutputs(xValues, yValues);
   fuzzyBuy = yValues[0];
   fuzzySell = yValues[1];
}
//----- Trend & Trade Decision (with Kalman) -----
void CalculateTrend() {
   double price = ClosePrice(0);
   double kalmanPrice = ApplyKalman(price);
   ATRvalue = CalculateAIATR(RangeHE, RangeLE);
   double src = (HighPrice(1) + LowPrice(1)) / 2; // Shift to previous bar
   upperBand = src + ATRvalue;
   lowerBand = src - ATRvalue;
   static int prevTrend = 0;
   if (price > upperBand) trend = 1;
   else if (price < lowerBand) trend = -1;
   else trend = prevTrend;
   prevTrend = trend;
}
//----- Combined Signal (Neural instead of fuzzy) -----
bool GetBuySignal() {
   return (fuzzyBuy > FuzzyThreshold && trend == 1 && SetLong);
}
bool GetSellSignal() {
   return (fuzzySell > FuzzyThreshold && trend == -1 && SetShort);
}
//----- Lot Calculation (risk % per trade, optimized) -----
double CalcLot(double baseMultiplier = 1.0) {
   if (!AutoLot) return FixedLot * baseMultiplier;
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   double riskMoney = balance * TradingRisk / 100.0;
   double stopPips = UseATRSLL ? (ATRvalue / _Point * ATRSLMultiplier) : BasicSL;
   if (stopPips <= 0) stopPips = 20.0;
   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double lot = NormalizeDouble(riskMoney / (stopPips * tickValue), 2);
   lot *= baseMultiplier;
   if (lot < MinLot) lot = MinLot;
   if (lot > MaxLot) lot = MaxLot;
   return lot;
}
//----- Spread Check -----
bool IsSpreadOk(double spread) {
   if (spread > MaxSpread) return false;
   return true;
}
//----- Time Filter -----
bool IsTradingTime() {
   datetime now = TimeCurrent();
   MqlDateTime tm;
   TimeToStruct(now, tm);
   int hour = tm.hour;
   int minute = tm.min;
   int day = tm.day_of_week;
   if (day == 1) {
      if (hour < MondayStartHour || (hour == MondayStartHour && minute < MondayStartMinute)) return false;
   } else {
      if (hour < StartHour || (hour == StartHour && minute < StartMinute)) return false;
   }
   if (hour > StopHour || (hour == StopHour && minute > StopMinute)) return false;
   if (day == 5) {
      if (hour > FridayStopHour || (hour == FridayStopHour && minute > FridayStopMinute)) return false;
   }
   return true;
}
//----- Day Filter -----
bool IsTradingDay() {
   MqlDateTime tm;
   TimeToStruct(TimeCurrent(), tm);
   int day = tm.day_of_week;
   switch (day) {
      case 1: return TradeMonday;
      case 2: return TradeTuesday;
      case 3: return TradeWednesday;
      case 4: return TradeThursday;
      case 5: return TradeFriday;
      default: return false;
   }
}
//----- DD Control -----
bool IsDDOk() {
   double dd = (AccountInfoDouble(ACCOUNT_EQUITY) / AccountInfoDouble(ACCOUNT_BALANCE)) * 100.0;
   return (dd > (100.0 - MaxDDControl));
}
//----- Swap Control -----
bool IsSwapOk(int type) {
   double swap = SymbolInfoDouble(_Symbol, (type == (int)ORDER_TYPE_BUY ? SYMBOL_SWAP_LONG : SYMBOL_SWAP_SHORT));
   if (NSwapControl && swap < 0) return false;
   if (PSwapControl && swap <= 0) return false;
   return true;
}
//----- Count Orders -----
int CountOrders(int dir) { // 1 buy, -1 sell
   int count = 0;
   for (int i = 0; i < PositionsTotal(); i++) {
      ulong ticket = PositionGetTicket(i);
      if (ticket > 0) {
         if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == Magic &&
             ((dir == 1 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) || (dir == -1 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL))) count++;
      }
   }
   return count;
}
//----- Last Open Price -----
double GetLastOpenPrice(int dir) {
   double price = 0;
   datetime latest = 0;
   for (int i = 0; i < PositionsTotal(); i++) {
      ulong ticket = PositionGetTicket(i);
      if (ticket > 0) {
         if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == Magic &&
             ((dir == 1 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) || (dir == -1 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL))) {
            datetime openTime = (datetime)PositionGetInteger(POSITION_TIME);
            if (openTime > latest) {
               latest = openTime;
               price = PositionGetDouble(POSITION_PRICE_OPEN);
            }
         }
      }
   }
   return price;
}
//----- Grid Level -----
int GetGridLevel(int dir) {
   return CountOrders(dir);
}
//----- Open Trade -----
bool OpenTrade(int type, double lotMultiplier = 1.0, double ask = 0, double bid = 0) {
   double spread = (ask - bid) / _Point;
   if (!IsSpreadOk(spread) || !IsTradingTime() || !IsTradingDay() || IsNewsTime() || !IsDDOk() || GetGridLevel(type == (int)ORDER_TYPE_BUY ? 1 : -1) >= MaxGridLevels || PositionsTotal() >= MaxOrderCount) return false;
   if (!IsSwapOk(type)) return false;
   double lot = CalcLot(lotMultiplier);
   double price = (type == (int)ORDER_TYPE_BUY ? ask : bid);
   double sl = 0, tp = 0;
   double atrSL = ATRvalue * ATRSLMultiplier;
   sl = NormalizeDouble((type == (int)ORDER_TYPE_BUY ? price - atrSL : price + atrSL), _Digits);
   if (!UseATRSLL) sl = NormalizeDouble((type == (int)ORDER_TYPE_BUY ? price - BasicSL * _Point : price + BasicSL * _Point), _Digits);
   tp = NormalizeDouble((type == (int)ORDER_TYPE_BUY ? price + TakeProfit * _Point : price - TakeProfit * _Point), _Digits);
   MqlTradeRequest request = {};
   MqlTradeResult result = {};
   request.action = TRADE_ACTION_DEAL;
   request.symbol = _Symbol;
   request.volume = lot;
   request.type = (ENUM_ORDER_TYPE)type;
   request.price = price;
   request.sl = sl;
   request.tp = tp;
   request.deviation = 3;
   request.magic = Magic;
   request.comment = TradeComment;
   if (!OrderSend(request, result)) {
      Print("OrderSend failed: ", result.retcode);
      return false;
   }
   return true;
}
//----- Manage Trades (Optimized Breakeven & Step Trailing) -----
void ManageTrades(double bid = 0, double ask = 0) {
   for (int i = PositionsTotal() - 1; i >= 0; i--) {
      ulong ticket = PositionGetTicket(i);
      if (ticket == 0) continue;
      if (PositionGetString(POSITION_SYMBOL) != _Symbol || PositionGetInteger(POSITION_MAGIC) != Magic) continue;
      ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      if (type != POSITION_TYPE_BUY && type != POSITION_TYPE_SELL) continue;
      double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
      double profitPips = (type == POSITION_TYPE_BUY ? (bid - openPrice) / _Point : (openPrice - ask) / _Point);
      double currentSL = PositionGetDouble(POSITION_SL);
      // Breakeven Logic
      if (UseBreakeven && profitPips >= BreakevenStart) {
         double beSL = NormalizeDouble(openPrice + (type == POSITION_TYPE_BUY ? BreakevenLock * _Point : -BreakevenLock * _Point), _Digits);
         if ((type == POSITION_TYPE_BUY && (currentSL < beSL || currentSL == 0)) || (type == POSITION_TYPE_SELL && (currentSL > beSL || currentSL == 0))) {
            MqlTradeRequest request = {};
            MqlTradeResult result = {};
            request.action = TRADE_ACTION_SLTP;
            request.position = ticket;
            request.sl = beSL;
            request.tp = PositionGetDouble(POSITION_TP);
            if (!OrderSend(request, result)) {
               Print("Breakeven modify failed: ", result.retcode);
            }
         }
      }
      // Step Trailing Stop
      if (UseTrailing && profitPips >= TrailStart) {
         double trailOffset = TrailDistance * _Point;
         double newSL = NormalizeDouble((type == POSITION_TYPE_BUY ? bid - trailOffset : ask + trailOffset), _Digits);
         double slDiff = (type == POSITION_TYPE_BUY ? (newSL - currentSL) / _Point : (currentSL - newSL) / _Point);
         if (slDiff >= TrailStep) {
            MqlTradeRequest request = {};
            MqlTradeResult result = {};
            request.action = TRADE_ACTION_SLTP;
            request.position = ticket;
            request.sl = newSL;
            request.tp = PositionGetDouble(POSITION_TP);
            if (!OrderSend(request, result)) {
               Print("Trailing modify failed: ", result.retcode);
            }
         }
      }
      // Multi-Stage Partial Close
      if (TPLevels > 0 && profitPips >= TPInitLevel) {
         double levelStep = (TakeProfit - TPInitLevel) / TPLevels;
         for (int level = 1; level <= TPLevels; level++) {
            double targetPips = TPInitLevel + level * levelStep;
            if (profitPips >= targetPips && PositionGetDouble(POSITION_VOLUME) > 0) {
               double closeLot = PositionGetDouble(POSITION_VOLUME) * (LotPercent / 100.0);
               double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
               if (closeLot < minLot) closeLot = PositionGetDouble(POSITION_VOLUME);
               MqlTradeRequest request = {};
               MqlTradeResult result = {};
               request.action = TRADE_ACTION_DEAL;
               request.position = ticket;
               request.symbol = _Symbol;
               request.volume = closeLot;
               request.type = (type == POSITION_TYPE_BUY ? ORDER_TYPE_SELL : ORDER_TYPE_BUY);
               request.price = (type == POSITION_TYPE_BUY ? bid : ask);
               request.deviation = 3;
               if (!OrderSend(request, result)) {
                  Print("Partial close failed: ", result.retcode);
               }
               break;
            }
         }
      }
   }
}
//----- Draw Profit Line -----
void DrawProfitLine() {
   if (DrawProfit && ProfitValue > 0) {
      ObjectCreate(0, "ProfitLine", OBJ_HLINE, 0, 0, ProfitValue);
      ObjectSetInteger(0, "ProfitLine", OBJPROP_COLOR, clrGreen);
   }
}
//----- Show Info Panel -----
void ShowPanel() {
   if (!ShowInfoPanel) return;
   string info = "TOL LANGIT ETF - AI EA\n";
   info += "Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + "\n";
   info += "Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + "\n";
   info += "Open Orders: " + IntegerToString(PositionsTotal()) + "\n";
   info += "Trend: " + (trend == 1 ? "Up" : (trend == -1 ? "Down" : "Flat")) + "\n";
   info += "Kalman State: " + DoubleToString(kalmanState, _Digits);
   info += "\nGMT Offset: " + IntegerToString(GMTOffset);
   Comment(info);
}
//----- Tick Speed Calculation -----
void UpdateTickSpeed() {
   datetime now = TimeCurrent();
   if (lastTickTime > 0) {
      double timeDiff = (now - lastTickTime) * 1.0;
      if (timeDiff > 0) tickSpeed = 1.0 / timeDiff; // Ticks per second approx
   }
   lastTickTime = now;
}
//================ MAIN LOOP ==========================
int OnInit() {
   Print("TOL LANGIT ETF AI EA Initialized for Forex/Gold");
   InitArrays();
   kalmanState = ClosePrice(0); // Init Kalman
   // Auto GMT
   if (AutoGMT) {
      FetchGMTOffset();
   } else {
      GMTOffset = ManualGMTOffset;
      Print("Manual GMT Offset: ", GMTOffset);
   }
   // Initialize indicator handles
   atr_short_handle = iATR(_Symbol, PERIOD_CURRENT, RangeHE);
   atr_long_handle = iATR(_Symbol, PERIOD_CURRENT, RangeLE);
   ema_short_handle = iMA(_Symbol, PERIOD_CURRENT, 14, 0, MODE_EMA, PRICE_CLOSE);
   ema_long_handle = iMA(_Symbol, PERIOD_CURRENT, 50, 0, MODE_EMA, PRICE_CLOSE);
   rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE);
   macd_handle = iMACD(_Symbol, PERIOD_CURRENT, 12, 26, 9, PRICE_CLOSE);
   bands_handle = iBands(_Symbol, PERIOD_CURRENT, 20, 2, 0, PRICE_CLOSE);
   obv_handle = iOBV(_Symbol, PERIOD_CURRENT, VOLUME_TICK);
   ema_h1_handle = iMA(_Symbol, PERIOD_H1, 50, 0, MODE_EMA, PRICE_CLOSE);
   atr_h1_handle = iATR(_Symbol, PERIOD_H1, 14);
   stoch_handle = iStochastic(_Symbol, PERIOD_CURRENT, 5, 3, 3, MODE_SMA, 0);
   // Initialize neural network
   dnn = new DeepNeuralNetwork(3, 4, 4, 2);
   double weights[46] = {
      0.1, -0.2, 0.3, 0.4,   // iaWeights row1
      -0.5, 0.6, -0.7, 0.8,   // row2
      0.9, -1.0, 1.1, -1.2,   // row3
      0.5, -0.5, 0.5, -0.5,   // aBiases
      1.0, 0.9, 0.8, 0.7,     // abWeights row1
      0.6, 0.5, 0.4, 0.3,     // row2
      0.2, 0.1, -0.1, -0.2,   // row3
      -0.3, -0.4, -0.5, -0.6, // row4
      0.4, -0.4, 0.4, -0.4,   // bBiases
      1.2, -1.2,              // boWeights row1
      1.1, -1.1,              // row2
      1.0, -1.0,              // row3
      0.9, -0.9,              // row4
      0.3, -0.3               // oBiases
   };
   dnn.SetWeights(weights);
   DrawProfitLine();
   return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason) {
   delete dnn;
   ObjectDelete(0, "ProfitLine");
   Comment("");
}
void OnTick() {
   MqlTick tick;
   if (!SymbolInfoTick(_Symbol, tick)) return;
   double ask = tick.ask;
   double bid = tick.bid;
   double spread = (ask - bid) / _Point;
   UpdateTickSpeed(); // Tick analysis
   if (tickSpeed < 0.1) return; // Skip if slow ticks (self-opt)
   CalculateCombo1();
   CalculateCombo2();
   CalculateCombo3();
   CalculateTrend();
   ManageTrades(bid, ask);
   ShowPanel();
   if (!SingleSymbol) return;
   bool buySignal = GetBuySignal();
   bool sellSignal = GetSellSignal();
   // Buy Grid/Martingale
   if (buySignal) {
      int gridLevel = GetGridLevel(1);
      if (gridLevel < MaxGridLevels) {
         double lastPrice = GetLastOpenPrice(1);
         double dist = (lastPrice > 0 ? (lastPrice - bid) / _Point : 0);
         if (gridLevel == 0 || dist >= RTDists[gridLevel - 1]) {
            OpenTrade((int)ORDER_TYPE_BUY, RTMLots[gridLevel], ask, bid);
         }
      }
   }
   // Sell Grid/Martingale
   if (sellSignal) {
      int gridLevel = GetGridLevel(-1);
      if (gridLevel < MaxGridLevels) {
         double lastPrice = GetLastOpenPrice(-1);
         double dist = (lastPrice > 0 ? (ask - lastPrice) / _Point : 0);
         if (gridLevel == 0 || dist >= RTDists[gridLevel - 1]) {
            OpenTrade((int)ORDER_TYPE_SELL, RTMLots[gridLevel], ask, bid);
         }
      }
   }
}