//+------------------------------------------------------------------+
//|                                                   AI_Gold_Trader |
//|                 MT5 + Groq AI Trading Expert                    |
//+------------------------------------------------------------------+
#property strict
#property version "1.00"

#include <Trade/Trade.mqh>

CTrade trade;

//====================================================================
// INPUTS
//====================================================================

input string InpGroqAPIKey = "";
input bool UseAI = true;
input bool UseLocalFallback = true;

input string InpModel = "llama-3.3-70b-versatile";

input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M5;

input double RiskPercent = 0.25;
input double MaxTotalRiskPercent = 0.75;

input double MaxLot = 0.01;

input int MinConfidence = 65;

input int ATR_Period = 14;

input double SL_ATR_Multiplier = 1.35;

input double TP_ATR_Multiplier = 0.85;
input double BreakEvenTriggerATR = 0.45;
input double BreakEvenLockATR = 0.12;
input double TrailingStartATR = 0.35;
input double TrailingATRMultiplier = 0.65;
input double TrailingStepATR = 0.10;

input double ZoneATRDistance = 0.60;
input int ZoneLookbackBars = 30;
input int MaxEntriesPerZone = 3;
input int MaxOpenPositions = 1;
input int AnalysisIntervalMinutes = 5;
input int MaxTradesPerDay = 0;
input double ZoneResetATR = 1.50;

input ulong MagicNumber = 20260909;

input bool EnableTrading = true;

//====================================================================

string API_URL =
   "https://api.groq.com/openai/v1/chat/completions";

datetime LastAnalysisTime = 0;
double ActiveZonePrice = 0.0;
int ActiveZoneType = 0;
int EntriesInActiveZone = 0;
datetime LastReversalBarTime = 0;

//====================================================================
// INIT
//====================================================================

int OnInit()
{
   trade.SetExpertMagicNumber(MagicNumber);
   trade.SetDeviationInPoints(30);

   Print("==========================================");
   Print("GROQ AI GOLD TRADER STARTED");
   Print("Symbol: ", _Symbol);
   Print("Timeframe: ", EnumToString(InpTimeframe));
   Print("Groq Model: ", InpModel);
   Print("Groq AI: ", UseAI);
   Print("Local M5 fallback: ", UseLocalFallback);
   Print("Trading: ", EnableTrading);
   Print("==========================================");

   return(INIT_SUCCEEDED);
}

//====================================================================
// TICK
//====================================================================

void OnTick()
{
   ManageOpenPositions();
   CheckReversalAndSwitch();

   datetime now = TimeCurrent();

   if(LastAnalysisTime > 0 &&
      (now - LastAnalysisTime) < AnalysisIntervalMinutes * 60)
      return;

   LastAnalysisTime = now;

   AnalyzeMarket();
}

//====================================================================
// MARKET ANALYSIS
//====================================================================

void AnalyzeMarket()
{
   if(!TerminalInfoInteger(TERMINAL_CONNECTED))
   {
      Print("Terminal is not connected.");
      return;
   }

   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

   if(ask <= 0 || bid <= 0)
      return;

   double spread = (ask - bid) / _Point;

   Print("Current spread (information only): ", spread);

   //=================================================================
   // INDICATORS
   //=================================================================

   double ema20  = GetEMA(20);
   double ema50  = GetEMA(50);
   double ema200 = GetEMA(200);

   double rsi = GetRSI();

   double macdMain = 0;
   double macdSignal = 0;

   GetMACD(macdMain, macdSignal);

   double atr = GetATR();

   if(ema20 == 0 ||
      ema50 == 0 ||
      ema200 == 0 ||
      rsi == 0 ||
      atr == 0)
   {
      Print("Indicator data unavailable.");
      return;
   }

   //=================================================================
   // STRONG ZONE
   //=================================================================

   double zonePrice = 0.0;
   int zoneType = 0;

   bool hasZone =
      GetStrongZone(
         atr,
         zonePrice,
         zoneType
      );

   if(hasZone)
   {
      UpdateZoneState(
         zonePrice,
         zoneType,
         atr
      );

      if(EntriesInActiveZone >= MaxEntriesPerZone)
      {
         Print("Maximum entries for active zone reached.");
         return;
      }
   }
   else
   {
      Print("No nearby strong zone. Local M5 strategy may still trade.");
   }

   //=================================================================
   // CANDLE DATA
   //=================================================================

   string candles = GetCandleData();

   //=================================================================
   // AI PROMPT
   //=================================================================

   string prompt =

      "You are an algorithmic trading analysis engine. "

      "Analyze XAUUSD using the supplied market data. "

      "You must be conservative. "

      "Do NOT invent missing data. "

      "Return HOLD when the setup is unclear. "

      "Only return BUY or SELL when multiple indicators agree. "

      "Minimum confidence for a trade is 65. "

      "Use the zone as a confirmation when a valid zone exists, "
      "but do not force HOLD when no zone exists. "

      "Prefer a realistic nearby target rather than an excessively distant target. "

      "Market information: "

      "Symbol=" + _Symbol +

      ", Timeframe=M5" +

      ", Bid=" + DoubleToString(bid,2) +

      ", Ask=" + DoubleToString(ask,2) +

      ", Spread=" + DoubleToString(spread,1) +

      ", EMA20=" + DoubleToString(ema20,2) +

      ", EMA50=" + DoubleToString(ema50,2) +

      ", EMA200=" + DoubleToString(ema200,2) +

      ", RSI=" + DoubleToString(rsi,2) +

      ", MACD=" + DoubleToString(macdMain,5) +

      ", MACD_SIGNAL=" + DoubleToString(macdSignal,5) +

      ", ATR=" + DoubleToString(atr,2) +

      ", Zone=" +
      (zoneType == 1 ?
       "SUPPORT" :
       (zoneType == -1 ?
        "RESISTANCE" :
        "NONE")) +

      ", ZonePrice=" +
      DoubleToString(zonePrice,2) +

      ", RecentCandles=" +
      candles +

      ". "

      "Return ONLY valid JSON in exactly this format: "

      "{\"decision\":\"BUY|SELL|HOLD\","
      "\"confidence\":0,"
      "\"reason\":\"short reason\"}";

   string response = "";
   string decision = "HOLD";
   int confidence = 0;
   string reason = "";

   bool success = false;

   //=================================================================
   // GROQ
   //=================================================================

   if(UseAI &&
      StringLen(InpGroqAPIKey) >= 20)
   {
      success =
         SendToGroq(
            prompt,
            response
         );
   }
   else
   if(UseAI)
   {
      Print("Groq API key is missing. Using local M5 strategy.");
   }

   //=================================================================
   // AI RESPONSE
   //=================================================================

   if(success)
   {
      Print("AI RAW RESPONSE:");
      Print(response);

      decision =
         ExtractJSONText(
            response,
            "decision"
         );

      confidence =
         ExtractJSONInt(
            response,
            "confidence"
         );

      reason =
         ExtractJSONText(
            response,
            "reason"
         );

      StringToUpper(decision);
   }
   else
   if(UseLocalFallback || !UseAI)
   {
      confidence =
         LocalM5Signal(
            ema20,
            ema50,
            ema200,
            rsi,
            macdMain,
            macdSignal,
            bid,
            ask,
            zoneType
         );

      decision =
         LocalM5Decision(
            ema20,
            ema50,
            ema200,
            rsi,
            macdMain,
            macdSignal,
            zoneType
         );

      if(confidence < MinConfidence)
         decision = "HOLD";

      reason = "Local M5 strategy";
   }

   if(decision == "")
      decision = "HOLD";

   Print("==========================================");
   Print("SIGNAL: ", decision);
   Print("CONFIDENCE: ", confidence);
   Print("REASON: ", reason);
   Print("==========================================");

   //=================================================================
   // HOLD
   //=================================================================

   if(decision == "HOLD")
   {
      Print("Signal is HOLD. No trade.");
      return;
   }

   //=================================================================
   // CONFIDENCE
   //=================================================================

   if(confidence < MinConfidence)
   {
      Print("Confidence below threshold.");
      return;
   }

   //=================================================================
   // POSITION LIMIT
   //=================================================================

   if(CountOpenPositions() >= MaxOpenPositions)
   {
      Print("Maximum open positions reached.");
      return;
   }

   //=================================================================
   // DAILY LIMIT
   //=================================================================

   if(MaxTradesPerDay > 0 &&
      CountTradesToday() >= MaxTradesPerDay)
   {
      Print("Maximum trades for today reached.");
      return;
   }

   //=================================================================
   // ZONE LIMIT
   //=================================================================

   if(hasZone &&
      EntriesInActiveZone >= MaxEntriesPerZone)
   {
      Print("Maximum entries in this zone reached.");
      return;
   }

   //=================================================================
   // ZONE DIRECTION AGREEMENT
   //=================================================================

   if((decision == "BUY" &&
       zoneType != 0 &&
       zoneType != 1) ||

      (decision == "SELL" &&
       zoneType != 0 &&
       zoneType != -1))
   {
      Print("AI decision does not agree with active zone. No trade.");
      return;
   }

   //=================================================================
   // SL / TP
   //=================================================================

   double slDistance =
      atr * SL_ATR_Multiplier;

   double tpDistance =
      atr * TP_ATR_Multiplier;

   double lot =
      CalculateLotSize(
         slDistance
      );

   if(lot <= 0)
   {
      Print("Invalid lot size.");
      return;
   }

   //=================================================================
   // BUY
   //=================================================================

   if(decision == "BUY")
   {
      double sl =
         ask - slDistance;

      double tp =
         ask + tpDistance;

      sl =
         NormalizeDouble(
            sl,
            _Digits
         );

      tp =
         NormalizeDouble(
            tp,
            _Digits
         );

      Print("BUY request");
      Print("Lot: ", lot);
      Print("Entry: ", ask);
      Print("SL: ", sl);
      Print("TP: ", tp);

      if(EnableTrading)
      {
         bool result =
            trade.Buy(
               lot,
               _Symbol,
               0,
               sl,
               tp,
               "AI BUY"
            );

         if(result)
         {
            EntriesInActiveZone++;

            Print(
               "BUY ORDER SUCCESS. Zone entries: ",
               EntriesInActiveZone
            );
         }
         else
         {
            Print(
               "BUY ORDER FAILED: ",
               trade.ResultRetcode(),
               " ",
               trade.ResultRetcodeDescription()
            );
         }
      }

      return;
   }

   //=================================================================
   // SELL
   //=================================================================

   if(decision == "SELL")
   {
      double sl =
         bid + slDistance;

      double tp =
         bid - tpDistance;

      sl =
         NormalizeDouble(
            sl,
            _Digits
         );

      tp =
         NormalizeDouble(
            tp,
            _Digits
         );

      Print("SELL request");
      Print("Lot: ", lot);
      Print("Entry: ", bid);
      Print("SL: ", sl);
      Print("TP: ", tp);

      if(EnableTrading)
      {
         bool result =
            trade.Sell(
               lot,
               _Symbol,
               0,
               sl,
               tp,
               "AI SELL"
            );

         if(result)
         {
            EntriesInActiveZone++;

            Print(
               "SELL ORDER SUCCESS. Zone entries: ",
               EntriesInActiveZone
            );
         }
         else
         {
            Print(
               "SELL ORDER FAILED: ",
               trade.ResultRetcode(),
               " ",
               trade.ResultRetcodeDescription()
            );
         }
      }

      return;
   }

   Print(
      "Unknown AI decision: ",
      decision
   );
}

//====================================================================
// LOCAL M5 FALLBACK SIGNAL
//====================================================================

int LocalM5Signal(
   double ema20,
   double ema50,
   double ema200,
   double rsi,
   double macdMain,
   double macdSignal,
   double bid,
   double ask,
   int zoneType
)
{
   int score = 0;

   MqlRates rates[];

   ArraySetAsSeries(
      rates,
      true
   );

   if(CopyRates(
         _Symbol,
         InpTimeframe,
         1,
         3,
         rates
      ) < 3)
      return 0;

   double body =
      MathAbs(
         rates[0].close -
         rates[0].open
      );

   double range =
      rates[0].high -
      rates[0].low;

   double upperWick =
      rates[0].high -
      MathMax(
         rates[0].open,
         rates[0].close
      );

   double lowerWick =
      MathMin(
         rates[0].open,
         rates[0].close
      ) -
      rates[0].low;

   bool bullishCandle =
      rates[0].close >
      rates[0].open;

   bool bearishCandle =
      rates[0].close <
      rates[0].open;

   bool bullishEngulf =
      bullishCandle &&
      rates[1].close < rates[1].open &&
      rates[0].open <= rates[1].close &&
      rates[0].close >= rates[1].open;

   bool bearishEngulf =
      bearishCandle &&
      rates[1].close > rates[1].open &&
      rates[0].open >= rates[1].close &&
      rates[0].close <= rates[1].open;

   bool bullishReject =
      range > 0 &&
      lowerWick > body * 1.2 &&
      rates[0].close >
      rates[0].low + range * 0.60;

   bool bearishReject =
      range > 0 &&
      upperWick > body * 1.2 &&
      rates[0].close <
      rates[0].high - range * 0.60;

   if(ema20 > ema50)
      score += 25;

   if(ema50 > ema200)
      score += 15;

   if(ema20 < ema50)
      score += 25;

   if(ema50 < ema200)
      score += 15;

   if(rsi >= 51.0 &&
      rsi <= 68.0)
      score += 10;

   if(rsi >= 32.0 &&
      rsi <= 49.0)
      score += 10;

   if(macdMain >= macdSignal)
      score += 10;

   if(macdMain <= macdSignal)
      score += 10;

   if(zoneType == 1)
      score += 10;

   if(zoneType == -1)
      score += 10;

   if(bullishEngulf ||
      bullishReject)
      score += 10;

   if(bearishEngulf ||
      bearishReject)
      score += 10;

   if(ask > bid)
      score += 5;

   if(score > 100)
      score = 100;

   return score;
}

//====================================================================
// LOCAL M5 DECISION
//====================================================================

string LocalM5Decision(
   double ema20,
   double ema50,
   double ema200,
   double rsi,
   double macdMain,
   double macdSignal,
   int zoneType
)
{
   MqlRates rates[];

   ArraySetAsSeries(
      rates,
      true
   );

   if(CopyRates(
         _Symbol,
         InpTimeframe,
         1,
         3,
         rates
      ) < 3)
      return "HOLD";

   double body =
      MathAbs(
         rates[0].close -
         rates[0].open
      );

   double range =
      rates[0].high -
      rates[0].low;

   double upperWick =
      rates[0].high -
      MathMax(
         rates[0].open,
         rates[0].close
      );

   double lowerWick =
      MathMin(
         rates[0].open,
         rates[0].close
      ) -
      rates[0].low;

   bool bullish =
      rates[0].close >
      rates[0].open;

   bool bearish =
      rates[0].close <
      rates[0].open;

   bool bullEngulf =
      bullish &&
      rates[1].close < rates[1].open &&
      rates[0].open <= rates[1].close &&
      rates[0].close >= rates[1].open;

   bool bearEngulf =
      bearish &&
      rates[1].close > rates[1].open &&
      rates[0].open >= rates[1].close &&
      rates[0].close <= rates[1].open;

   bool bullReject =
      range > 0 &&
      lowerWick > body * 1.2 &&
      rates[0].close >
      rates[0].low + range * 0.60;

   bool bearReject =
      range > 0 &&
      upperWick > body * 1.2 &&
      rates[0].close <
      rates[0].high - range * 0.60;

   int buy = 0;
   int sell = 0;

   if(ema20 > ema50)
      buy += 2;

   if(ema50 > ema200)
      buy += 1;

   if(rsi >= 50.0 &&
      rsi <= 70.0)
      buy += 1;

   if(macdMain >= macdSignal)
      buy += 1;

   if(bullish ||
      bullEngulf ||
      bullReject)
      buy += 2;

   if(zoneType == 1)
      buy += 2;

   if(ema20 < ema50)
      sell += 2;

   if(ema50 < ema200)
      sell += 1;

   if(rsi >= 30.0 &&
      rsi <= 50.0)
      sell += 1;

   if(macdMain <= macdSignal)
      sell += 1;

   if(bearish ||
      bearEngulf ||
      bearReject)
      sell += 2;

   if(zoneType == -1)
      sell += 2;

   if(buy >= 6 &&
      buy > sell + 1)
      return "BUY";

   if(sell >= 6 &&
      sell > buy + 1)
      return "SELL";

   return "HOLD";
}

//====================================================================
// GROQ REQUEST
//====================================================================

bool SendToGroq(
   string prompt,
   string &response
)
{
   if(StringLen(InpGroqAPIKey) < 20)
   {
      Print("Groq API key is missing.");
      return false;
   }

   string escapedPrompt =
      JsonEscape(prompt);

   string json =
      "{"
      "\"model\":\"" +
      InpModel +
      "\","
      "\"messages\":[{"
      "\"role\":\"user\","
      "\"content\":\"" +
      escapedPrompt +
      "\"}"
      "],"
      "\"temperature\":0,"
      "\"max_completion_tokens\":300"
      "}";

   string headers =
      "Content-Type: application/json\r\n"
      "Authorization: Bearer " +
      InpGroqAPIKey +
      "\r\n";

   char post[];
   char result[];

   string resultHeaders;

   StringToCharArray(
      json,
      post,
      0,
      StringLen(json),
      CP_UTF8
   );

   ResetLastError();

   int status =
      WebRequest(
         "POST",
         API_URL,
         headers,
         30000,
         post,
         result,
         resultHeaders
      );

   if(status == -1)
   {
      Print(
         "Groq WebRequest ERROR: ",
         GetLastError()
      );

      return false;
   }

   response =
      CharArrayToString(
         result,
         0,
         -1,
         CP_UTF8
      );

   Print(
      "GROQ HTTP STATUS: ",
      status
   );

   if(status != 200)
   {
      Print("Groq HTTP ERROR");
      Print(response);

      return false;
   }

   string content =
      ExtractMessageContent(
         response
      );

   if(StringLen(content) > 0)
      response = content;

   return true;
}

//====================================================================
// EXTRACT GROQ MESSAGE CONTENT
//====================================================================

string ExtractMessageContent(
   string json
)
{
   string key =
      "\"content\":\"";

   int start =
      StringFind(
         json,
         key
      );

   if(start < 0)
      return "";

   start +=
      StringLen(key);

   int end = start;

   bool escaped = false;

   while(
      end <
      StringLen(json)
   )
   {
      ushort c =
         StringGetCharacter(
            json,
            end
         );

      if(c == '\\' &&
         !escaped)
      {
         escaped = true;
         end++;
         continue;
      }

      if(c == '"' &&
         !escaped)
         break;

      escaped = false;

      end++;
   }

   if(end <= start)
      return "";

   string value =
      StringSubstr(
         json,
         start,
         end - start
      );

   value =
      JsonUnescape(
         value
      );

   return value;
}

//====================================================================
// JSON TEXT
//====================================================================

string ExtractJSONText(
   string json,
   string key
)
{
   string search =
      "\"" +
      key +
      "\":\"";

   int start =
      StringFind(
         json,
         search
      );

   if(start < 0)
      return "";

   start +=
      StringLen(search);

   int end = start;

   bool escaped = false;

   while(
      end <
      StringLen(json)
   )
   {
      ushort c =
         StringGetCharacter(
            json,
            end
         );

      if(c == '\\' &&
         !escaped)
      {
         escaped = true;
         end++;
         continue;
      }

      if(c == '"' &&
         !escaped)
         break;

      escaped = false;

      end++;
   }

   if(end <= start)
      return "";

   return JsonUnescape(
      StringSubstr(
         json,
         start,
         end - start
      )
   );
}

//====================================================================
// JSON INT
//====================================================================

int ExtractJSONInt(
   string json,
   string key
)
{
   string search =
      "\"" +
      key +
      "\":";

   int start =
      StringFind(
         json,
         search
      );

   if(start < 0)
      return 0;

   start +=
      StringLen(search);

   string number = "";

   for(
      int i = start;
      i < StringLen(json);
      i++
   )
   {
      ushort c =
         StringGetCharacter(
            json,
            i
         );

      if(
         (c >= '0' &&
          c <= '9') ||
         c == '-'
      )
      {
         number +=
            CharToString(
               (uchar)c
            );
      }
      else
      {
         break;
      }
   }

   return(
      (int)StringToInteger(
         number
      )
   );
}

//====================================================================
// JSON ESCAPE
//====================================================================

string JsonEscape(
   string text
)
{
   StringReplace(
      text,
      "\\",
      "\\\\"
   );

   StringReplace(
      text,
      "\"",
      "\\\""
   );

   StringReplace(
      text,
      "\r",
      "\\r"
   );

   StringReplace(
      text,
      "\n",
      "\\n"
   );

   return text;
}

//====================================================================
// JSON UNESCAPE
//====================================================================

string JsonUnescape(
   string text
)
{
   StringReplace(
      text,
      "\\\"",
      "\""
   );

   StringReplace(
      text,
      "\\\\",
      "\\"
   );

   StringReplace(
      text,
      "\\n",
      "\n"
   );

   StringReplace(
      text,
      "\\r",
      "\r"
   );

   return text;
}

//====================================================================
// EMA
//====================================================================

double GetEMA(
   int period
)
{
   int handle =
      iMA(
         _Symbol,
         InpTimeframe,
         period,
         0,
         MODE_EMA,
         PRICE_CLOSE
      );

   if(handle == INVALID_HANDLE)
      return 0;

   double buffer[];

   ArraySetAsSeries(
      buffer,
      true
   );

   double value = 0;

   if(
      CopyBuffer(
         handle,
         0,
         1,
         1,
         buffer
      ) > 0
   )
   {
      value = buffer[0];
   }

   IndicatorRelease(
      handle
   );

   return value;
}

//====================================================================
// RSI
//====================================================================

double GetRSI()
{
   int handle =
      iRSI(
         _Symbol,
         InpTimeframe,
         14,
         PRICE_CLOSE
      );

   if(handle == INVALID_HANDLE)
      return 0;

   double buffer[];

   ArraySetAsSeries(
      buffer,
      true
   );

   double value = 0;

   if(
      CopyBuffer(
         handle,
         0,
         1,
         1,
         buffer
      ) > 0
   )
   {
      value = buffer[0];
   }

   IndicatorRelease(
      handle
   );

   return value;
}

//====================================================================
// MACD
//====================================================================

void GetMACD(
   double &mainValue,
   double &signalValue
)
{
   int handle =
      iMACD(
         _Symbol,
         InpTimeframe,
         12,
         26,
         9,
         PRICE_CLOSE
      );

   if(handle == INVALID_HANDLE)
      return;

   double mainBuffer[];
   double signalBuffer[];

   ArraySetAsSeries(
      mainBuffer,
      true
   );

   ArraySetAsSeries(
      signalBuffer,
      true
   );

   if(
      CopyBuffer(
         handle,
         0,
         1,
         1,
         mainBuffer
      ) > 0
   )
   {
      mainValue =
         mainBuffer[0];
   }

   if(
      CopyBuffer(
         handle,
         1,
         1,
         1,
         signalBuffer
      ) > 0
   )
   {
      signalValue =
         signalBuffer[0];
   }

   IndicatorRelease(
      handle
   );
}

//====================================================================
// ATR
//====================================================================

double GetATR()
{
   int handle =
      iATR(
         _Symbol,
         InpTimeframe,
         ATR_Period
      );

   if(handle == INVALID_HANDLE)
      return 0;

   double buffer[];

   ArraySetAsSeries(
      buffer,
      true
   );

   double value = 0;

   if(
      CopyBuffer(
         handle,
         0,
         1,
         1,
         buffer
      ) > 0
   )
   {
      value = buffer[0];
   }

   IndicatorRelease(
      handle
   );

   return value;
}

//====================================================================
// CANDLE DATA
//====================================================================

string GetCandleData()
{
   MqlRates rates[];

   ArraySetAsSeries(
      rates,
      true
   );

   int copied =
      CopyRates(
         _Symbol,
         InpTimeframe,
         1,
         10,
         rates
      );

   if(copied <= 0)
      return "";

   string result = "";

   for(
      int i = 0;
      i < copied;
      i++
   )
   {
      result +=
         "[O=" +
         DoubleToString(
            rates[i].open,
            2
         ) +

         ",H=" +
         DoubleToString(
            rates[i].high,
            2
         ) +

         ",L=" +
         DoubleToString(
            rates[i].low,
            2
         ) +

         ",C=" +
         DoubleToString(
            rates[i].close,
            2
         ) +

         ",V=" +
         IntegerToString(
            (int)rates[i].tick_volume
         ) +

         "]";

      if(i < copied - 1)
         result += ",";
   }

   return result;
}

//====================================================================
// STRONG ZONE DETECTION
//====================================================================

bool GetStrongZone(
   double atr,
   double &zonePrice,
   int &zoneType
)
{
   zonePrice = 0.0;
   zoneType = 0;

   if(atr <= 0)
      return false;

   MqlRates rates[];

   ArraySetAsSeries(
      rates,
      true
   );

   int copied =
      CopyRates(
         _Symbol,
         InpTimeframe,
         1,
         ZoneLookbackBars,
         rates
      );

   if(copied < 10)
      return false;

   double ask =
      SymbolInfoDouble(
         _Symbol,
         SYMBOL_ASK
      );

   double bid =
      SymbolInfoDouble(
         _Symbol,
         SYMBOL_BID
      );

   if(ask <= 0 ||
      bid <= 0)
      return false;

   double recentLow =
      rates[0].low;

   double recentHigh =
      rates[0].high;

   for(
      int i = 1;
      i < copied;
      i++
   )
   {
      if(
         rates[i].low <
         recentLow
      )
      {
         recentLow =
            rates[i].low;
      }

      if(
         rates[i].high >
         recentHigh
      )
      {
         recentHigh =
            rates[i].high;
      }
   }

   double supportDistance =
      MathAbs(
         bid -
         recentLow
      );

   double resistanceDistance =
      MathAbs(
         ask -
         recentHigh
      );

   bool nearSupport =
      supportDistance <=
      atr * ZoneATRDistance;

   bool nearResistance =
      resistanceDistance <=
      atr * ZoneATRDistance;

   if(!nearSupport &&
      !nearResistance)
      return false;

   if(
      nearSupport &&
      nearResistance
   )
   {
      if(
         supportDistance <=
         resistanceDistance
      )
      {
         zonePrice =
            recentLow;

         zoneType = 1;
      }
      else
      {
         zonePrice =
            recentHigh;

         zoneType = -1;
      }

      return true;
   }

   if(nearSupport)
   {
      zonePrice =
         recentLow;

      zoneType = 1;

      return true;
   }

   zonePrice =
      recentHigh;

   zoneType = -1;

   return true;
}

//====================================================================
// ZONE STATE
//====================================================================

void UpdateZoneState(
   double zonePrice,
   int zoneType,
   double atr
)
{
   if(
      zonePrice <= 0 ||
      zoneType == 0 ||
      atr <= 0
   )
      return;

   if(
      ActiveZoneType != zoneType ||
      ActiveZonePrice <= 0 ||
      MathAbs(
         zonePrice -
         ActiveZonePrice
      ) > atr * 0.50
   )
   {
      ActiveZonePrice =
         zonePrice;

      ActiveZoneType =
         zoneType;

      EntriesInActiveZone =
         0;

      Print(
         "NEW ACTIVE ZONE: ",
         (zoneType == 1 ?
          "SUPPORT" :
          "RESISTANCE"),
         " @ ",
         DoubleToString(
            zonePrice,
            _Digits
         )
      );
   }

   double price =
      (
         zoneType == 1 ?
         SymbolInfoDouble(
            _Symbol,
            SYMBOL_BID
         ) :
         SymbolInfoDouble(
            _Symbol,
            SYMBOL_ASK
         )
      );

   if(price <= 0)
      return;

   if(
      MathAbs(
         price -
         ActiveZonePrice
      ) >
      atr * ZoneResetATR
   )
   {
      ActiveZonePrice = 0.0;
      ActiveZoneType = 0;
      EntriesInActiveZone = 0;

      Print(
         "Active zone cleared: price moved away."
      );
   }
}

//====================================================================
// OPEN POSITION COUNT
//====================================================================

int CountOpenPositions()
{
   int count = 0;

   for(
      int i = PositionsTotal() - 1;
      i >= 0;
      i--
   )
   {
      ulong ticket =
         PositionGetTicket(i);

      if(ticket == 0)
         continue;

      string symbol =
         PositionGetString(
            POSITION_SYMBOL
         );

      long magic =
         PositionGetInteger(
            POSITION_MAGIC
         );

      if(
         symbol == _Symbol &&
         (ulong)magic == MagicNumber
      )
      {
         count++;
      }
   }

   return count;
}

//====================================================================
// POSITION MANAGEMENT
//====================================================================

void ManageOpenPositions()
{
   double atr =
      GetATR();

   if(atr <= 0)
      return;

   double trigger =
      atr *
      BreakEvenTriggerATR;

   double lock =
      atr *
      BreakEvenLockATR;

   for(
      int i = PositionsTotal() - 1;
      i >= 0;
      i--
   )
   {
      ulong ticket =
         PositionGetTicket(i);

      if(ticket == 0)
         continue;

      string symbol =
         PositionGetString(
            POSITION_SYMBOL
         );

      long magic =
         PositionGetInteger(
            POSITION_MAGIC
         );

      if(
         symbol != _Symbol ||
         (ulong)magic != MagicNumber
      )
         continue;

      long type =
         PositionGetInteger(
            POSITION_TYPE
         );

      double openPrice =
         PositionGetDouble(
            POSITION_PRICE_OPEN
         );

      double sl =
         PositionGetDouble(
            POSITION_SL
         );

      double tp =
         PositionGetDouble(
            POSITION_TP
         );

      double bid =
         SymbolInfoDouble(
            _Symbol,
            SYMBOL_BID
         );

      double ask =
         SymbolInfoDouble(
            _Symbol,
            SYMBOL_ASK
         );

      //==============================================================
      // BUY MANAGEMENT
      //==============================================================

      if(type == POSITION_TYPE_BUY)
      {
         double profitDistance =
            bid -
            openPrice;

         if(
            profitDistance >=
            trigger
         )
         {
            double protectedSL =
               NormalizeDouble(
                  openPrice +
                  lock,
                  _Digits
               );

            if(
               sl == 0 ||
               protectedSL >
               sl + _Point
            )
            {
               if(
                  trade.PositionModify(
                     ticket,
                     protectedSL,
                     tp
                  )
               )
               {
                  sl =
                     protectedSL;

                  Print(
                     "BUY profit protected at: ",
                     protectedSL
                  );
               }
            }
         }

         //===========================================================
         // BUY TRAILING
         //===========================================================

         if(
            profitDistance >=
            atr *
            TrailingStartATR
         )
         {
            double trailSL =
               NormalizeDouble(
                  bid -
                  atr *
                  TrailingATRMultiplier,
                  _Digits
               );

            if(
               trailSL > openPrice &&
               (
                  sl == 0 ||
                  trailSL >
                  sl +
                  atr *
                  TrailingStepATR
               )
            )
            {
               if(
                  trade.PositionModify(
                     ticket,
                     trailSL,
                     tp
                  )
               )
               {
                  sl =
                     trailSL;

                  Print(
                     "BUY trailing SL: ",
                     trailSL
                  );
               }
            }
         }
      }

      //==============================================================
      // SELL MANAGEMENT
      //==============================================================

      else
      if(type == POSITION_TYPE_SELL)
      {
         double profitDistance =
            openPrice -
            ask;

         if(
            profitDistance >=
            trigger
         )
         {
            double protectedSL =
               NormalizeDouble(
                  openPrice -
                  lock,
                  _Digits
               );

            if(
               sl == 0 ||
               protectedSL <
               sl - _Point
            )
            {
               if(
                  trade.PositionModify(
                     ticket,
                     protectedSL,
                     tp
                  )
               )
               {
                  sl =
                     protectedSL;

                  Print(
                     "SELL profit protected at: ",
                     protectedSL
                  );
               }
            }
         }

         //===========================================================
         // SELL TRAILING
         //===========================================================

         if(
            profitDistance >=
            atr *
            TrailingStartATR
         )
         {
            double trailSL =
               NormalizeDouble(
                  ask +
                  atr *
                  TrailingATRMultiplier,
                  _Digits
               );

            if(
               trailSL < openPrice &&
               (
                  sl == 0 ||
                  trailSL <
                  sl -
                  atr *
                  TrailingStepATR
               )
            )
            {
               if(
                  trade.PositionModify(
                     ticket,
                     trailSL,
                     tp
                  )
               )
               {
                  sl =
                     trailSL;

                  Print(
                     "SELL trailing SL: ",
                     trailSL
                  );
               }
            }
         }
      }
   }
}

//====================================================================
// REVERSAL: SELL -> BUY / BUY -> SELL
//====================================================================

void CheckReversalAndSwitch()
{
   if(!EnableTrading)
      return;

   if(CountOpenPositions() <= 0)
      return;

   datetime closedBar =
      iTime(
         _Symbol,
         InpTimeframe,
         1
      );

   if(
      closedBar <= 0 ||
      closedBar ==
      LastReversalBarTime
   )
      return;

   MqlRates r[];

   ArraySetAsSeries(
      r,
      true
   );

   if(
      CopyRates(
         _Symbol,
         InpTimeframe,
         1,
         3,
         r
      ) < 3
   )
      return;

   double ema20 =
      GetEMA(20);

   double ema50 =
      GetEMA(50);

   double rsi =
      GetRSI();

   double macdMain = 0;
   double macdSignal = 0;

   GetMACD(
      macdMain,
      macdSignal
   );

   double body =
      MathAbs(
         r[0].close -
         r[0].open
      );

   double range =
      r[0].high -
      r[0].low;

   double upperWick =
      r[0].high -
      MathMax(
         r[0].open,
         r[0].close
      );

   double lowerWick =
      MathMin(
         r[0].open,
         r[0].close
      ) -
      r[0].low;

   bool bullish =
      r[0].close >
      r[0].open;

   bool bearish =
      r[0].close <
      r[0].open;

   bool bullEngulf =
      bullish &&
      r[1].close < r[1].open &&
      r[0].open <= r[1].close &&
      r[0].close >= r[1].open;

   bool bearEngulf =
      bearish &&
      r[1].close > r[1].open &&
      r[0].open >= r[1].close &&
      r[0].close <= r[1].open;

   bool bullReject =
      range > 0 &&
      lowerWick >
      body * 1.2 &&
      r[0].close >
      r[0].low +
      range * 0.60;

   bool bearReject =
      range > 0 &&
      upperWick >
      body * 1.2 &&
      r[0].close <
      r[0].high -
      range * 0.60;

   int bullScore = 0;
   int bearScore = 0;

   if(bullish)
      bullScore += 2;

   if(bearish)
      bearScore += 2;

   if(bullEngulf)
      bullScore += 3;

   if(bearEngulf)
      bearScore += 3;

   if(bullReject)
      bullScore += 2;

   if(bearReject)
      bearScore += 2;

   if(ema20 > ema50)
      bullScore += 1;

   if(ema20 < ema50)
      bearScore += 1;

   if(rsi >= 52.0)
      bullScore += 1;

   if(rsi <= 48.0)
      bearScore += 1;

   if(macdMain > macdSignal)
      bullScore += 1;

   if(macdMain < macdSignal)
      bearScore += 1;

   for(
      int i = PositionsTotal() - 1;
      i >= 0;
      i--
   )
   {
      ulong ticket =
         PositionGetTicket(i);

      if(ticket == 0)
         continue;

      string symbol =
         PositionGetString(
            POSITION_SYMBOL
         );

      long magic =
         PositionGetInteger(
            POSITION_MAGIC
         );

      if(
         symbol != _Symbol ||
         (ulong)magic != MagicNumber
      )
         continue;

      long type =
         PositionGetInteger(
            POSITION_TYPE
         );

      //==============================================================
      // SELL -> BUY
      //==============================================================

      if(
         type == POSITION_TYPE_SELL &&
         bullScore >= 6 &&
         bullScore >
         bearScore + 1
      )
      {
         LastReversalBarTime =
            closedBar;

         Print(
            "CONFIRMED REVERSAL: SELL -> BUY"
         );

         if(
            trade.PositionClose(
               ticket
            )
         )
         {
            OpenReversePosition(
               POSITION_TYPE_BUY,
               "REVERSAL BUY"
            );
         }
         else
         {
            Print(
               "SELL close failed: ",
               trade.ResultRetcodeDescription()
            );
         }

         return;
      }

      //==============================================================
      // BUY -> SELL
      //==============================================================

      if(
         type == POSITION_TYPE_BUY &&
         bearScore >= 6 &&
         bearScore >
         bullScore + 1
      )
      {
         LastReversalBarTime =
            closedBar;

         Print(
            "CONFIRMED REVERSAL: BUY -> SELL"
         );

         if(
            trade.PositionClose(
               ticket
            )
         )
         {
            OpenReversePosition(
               POSITION_TYPE_SELL,
               "REVERSAL SELL"
            );
         }
         else
         {
            Print(
               "BUY close failed: ",
               trade.ResultRetcodeDescription()
            );
         }

         return;
      }
   }
}

//====================================================================
// OPEN REVERSE POSITION
//====================================================================

void OpenReversePosition(
   long type,
   string comment
)
{
   if(CountOpenPositions() > 0)
      return;

   double atr =
      GetATR();

   if(atr <= 0)
      return;

   double slDistance =
      atr *
      SL_ATR_Multiplier;

   double tpDistance =
      atr *
      TP_ATR_Multiplier;

   double lot =
      CalculateLotSize(
         slDistance
      );

   if(lot <= 0)
      return;

   bool result = false;

   //=================================================================
   // REVERSE BUY
   //=================================================================

   if(type == POSITION_TYPE_BUY)
   {
      double ask =
         SymbolInfoDouble(
            _Symbol,
            SYMBOL_ASK
         );

      double sl =
         NormalizeDouble(
            ask -
            slDistance,
            _Digits
         );

      double tp =
         NormalizeDouble(
            ask +
            tpDistance,
            _Digits
         );

      result =
         trade.Buy(
            lot,
            _Symbol,
            0,
            sl,
            tp,
            comment
         );

      if(result)
      {
         Print(
            "REVERSAL BUY OPENED. Lot=",
            lot,
            " SL=",
            sl,
            " TP=",
            tp
         );
      }
   }

   //=================================================================
   // REVERSE SELL
   //=================================================================

   else
   if(type == POSITION_TYPE_SELL)
   {
      double bid =
         SymbolInfoDouble(
            _Symbol,
            SYMBOL_BID
         );

      double sl =
         NormalizeDouble(
            bid +
            slDistance,
            _Digits
         );

      double tp =
         NormalizeDouble(
            bid -
            tpDistance,
            _Digits
         );

      result =
         trade.Sell(
            lot,
            _Symbol,
            0,
            sl,
            tp,
            comment
         );

      if(result)
      {
         Print(
            "REVERSAL SELL OPENED. Lot=",
            lot,
            " SL=",
            sl,
            " TP=",
            tp
         );
      }
   }

   if(!result)
   {
      Print(
         "Reverse order failed: ",
         trade.ResultRetcode(),
         " ",
         trade.ResultRetcodeDescription()
      );
   }
}

//====================================================================
// TODAY'S TRADE COUNT
//====================================================================

int CountTradesToday()
{
   datetime dayStart =
      StringToTime(
         TimeToString(
            TimeCurrent(),
            TIME_DATE
         )
      );

   if(dayStart <= 0)
      return 0;

   if(
      !HistorySelect(
         dayStart,
         TimeCurrent()
      )
   )
      return 0;

   int count = 0;

   int total =
      HistoryDealsTotal();

   for(
      int i = 0;
      i < total;
      i++
   )
   {
      ulong ticket =
         HistoryDealGetTicket(i);

      if(ticket == 0)
         continue;

      string symbol =
         HistoryDealGetString(
            ticket,
            DEAL_SYMBOL
         );

      long magic =
         HistoryDealGetInteger(
            ticket,
            DEAL_MAGIC
         );

      long entry =
         HistoryDealGetInteger(
            ticket,
            DEAL_ENTRY
         );

      if(
         symbol == _Symbol &&
         (ulong)magic == MagicNumber &&
         entry == DEAL_ENTRY_IN
      )
      {
         count++;
      }
   }

   return count;
}

//====================================================================
// CURRENT OPEN RISK
//====================================================================

double CurrentOpenRiskMoney()
{
   double tickSize =
      SymbolInfoDouble(
         _Symbol,
         SYMBOL_TRADE_TICK_SIZE
      );

   double tickValue =
      SymbolInfoDouble(
         _Symbol,
         SYMBOL_TRADE_TICK_VALUE
      );

   if(
      tickSize <= 0 ||
      tickValue <= 0
   )
      return 0;

   double totalRisk = 0;

   for(
      int i = PositionsTotal() - 1;
      i >= 0;
      i--
   )
   {
      ulong ticket =
         PositionGetTicket(i);

      if(ticket == 0)
         continue;

      string symbol =
         PositionGetString(
            POSITION_SYMBOL
         );

      long magic =
         PositionGetInteger(
            POSITION_MAGIC
         );

      if(
         symbol != _Symbol ||
         (ulong)magic != MagicNumber
      )
         continue;

      double sl =
         PositionGetDouble(
            POSITION_SL
         );

      double openPrice =
         PositionGetDouble(
            POSITION_PRICE_OPEN
         );

      double volume =
         PositionGetDouble(
            POSITION_VOLUME
         );

      if(
         sl <= 0 ||
         openPrice <= 0 ||
         volume <= 0
      )
         continue;

      double distance =
         MathAbs(
            openPrice -
            sl
         );

      totalRisk +=
         distance /
         tickSize *
         tickValue *
         volume;
   }

   return totalRisk;
}

//====================================================================
// LOT CALCULATION
//====================================================================

double CalculateLotSize(
   double stopDistance
)
{
   double balance =
      AccountInfoDouble(
         ACCOUNT_BALANCE
      );

   double desiredRiskMoney =
      balance *
      RiskPercent /
      100.0;

   double maxTotalRiskMoney =
      balance *
      MaxTotalRiskPercent /
      100.0;

   double currentRiskMoney =
      CurrentOpenRiskMoney();

   double remainingRiskMoney =
      maxTotalRiskMoney -
      currentRiskMoney;

   if(remainingRiskMoney <= 0)
   {
      Print(
         "Total risk cap reached. No new position."
      );

      return 0;
   }

   double riskMoney =
      MathMin(
         desiredRiskMoney,
         remainingRiskMoney
      );

   double tickSize =
      SymbolInfoDouble(
         _Symbol,
         SYMBOL_TRADE_TICK_SIZE
      );

   double tickValue =
      SymbolInfoDouble(
         _Symbol,
         SYMBOL_TRADE_TICK_VALUE
      );

   if(
      tickSize <= 0 ||
      tickValue <= 0 ||
      stopDistance <= 0
   )
   {
      return 0;
   }

   double lossPerLot =
      stopDistance /
      tickSize *
      tickValue;

   if(lossPerLot <= 0)
      return 0;

   double lot =
      riskMoney /
      lossPerLot;

   double minLot =
      SymbolInfoDouble(
         _Symbol,
         SYMBOL_VOLUME_MIN
      );

   double maxBrokerLot =
      SymbolInfoDouble(
         _Symbol,
         SYMBOL_VOLUME_MAX
      );

   double lotStep =
      SymbolInfoDouble(
         _Symbol,
         SYMBOL_VOLUME_STEP
      );

   lot =
      MathMin(
         lot,
         MaxLot
      );

   lot =
      MathMin(
         lot,
         maxBrokerLot
      );

   if(lot < minLot)
      lot = minLot;

   if(lotStep > 0)
   {
      lot =
         MathFloor(
            lot /
            lotStep
         ) *
         lotStep;
   }

   int digits = 2;

   if(lotStep == 1.0)
      digits = 0;

   else
   if(lotStep == 0.1)
      digits = 1;

   else
   if(lotStep == 0.01)
      digits = 2;

   lot =
      NormalizeDouble(
         lot,
         digits
      );

   return lot;
}

//+------------------------------------------------------------------+