//+------------------------------------------------------------------+
//| MQL5 R3R AI NANO.mq5                                            |
//|                                                                  |
//| Multi-Model AI Paper Trader                                     |
//| Gemini / Claude / OpenAI                                        |
//| Um provedor ativo por vez                                      |
//|                                                                  |
//| IMPORTANTE:                                                     |
//| V3 LIVE: ordens reais SO se InpEnableLiveTrading=true.         |
//| Por padrao continua em paper trading.                          |
//+------------------------------------------------------------------+
#property strict
#property version   "3.00"
#property description "Multi-provider AI observer, paper trader and optional live trader"

#include <Trade\Trade.mqh>

CTrade g_trade;

//==================================================================
// PROVIDER
//==================================================================

enum ENUM_AI_PROVIDER
  {
   AI_GEMINI = 0,
   AI_CLAUDE = 1,
   AI_OPENAI = 2
  };

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

//---------------- PROVIDER ATIVO ----------------------------------

input ENUM_AI_PROVIDER InpProvider = AI_GEMINI;

//---------------- API KEYS ----------------------------------------

input string InpGeminiApiKey = "";
input string InpClaudeApiKey = "";
input string InpOpenAIApiKey = "";

//---------------- MODELOS -----------------------------------------

input string InpGeminiModel = "gemini-2.5-flash";
input string InpClaudeModel = "claude-sonnet-4-5";
input string InpOpenAIModel = "gpt-5";

//---------------- MERCADO -----------------------------------------

input string InpSymbol = "XAUUSD";


input ENUM_TIMEFRAMES InpDecisionTF = PERIOD_M5;
input ENUM_TIMEFRAMES InpContextTF  = PERIOD_M15;

input int InpLookbackBars = 20;

//---------------- INDICADORES -------------------------------------

input int InpFastEMA   = 20;
input int InpSlowEMA   = 50;
input int InpRSIPeriod = 14;
input int InpATRPeriod = 14;
input int InpADXPeriod = 14;
input int InpSwingLookback = 12;
input double InpMinTrendADX = 18.0;
input double InpStructureBufferATR = 0.15;
input double InpMinSLATR = 0.80;
input double InpMaxSLATR = 2.50;
input double InpTargetRR = 2.00;
input double InpBreakoutBufferATR = 0.05;

//---------------- API ---------------------------------------------

input int InpHTTPTimeout = 50000;
input int InpMinSecondsBetweenRequests = 60;
input int InpMinSecondsBetweenReversals = 300;

//---------------- PAPER TRADING -----------------------------------

input bool   InpEnablePaperTrading = true;
input double InpPaperBalance       = 10000.0;

//---------------- ORDENS REAIS ------------------------------------

input bool   InpEnableLiveTrading = false;  // LIGAR para enviar ordens reais
input bool   InpLocalRulesInTester = true;  // No Strategy Tester: regras locais (sem API)
input long   InpMagic             = 730305; // Magic number do EA
input bool   InpAutoLot           = true;   // Lote automatico por risco %
input double InpRiskPercent       = 1.0;    // Risco por trade (% do saldo)
input double InpMaxLot            = 0.10;   // Lote maximo permitido
input double InpLotSize           = 0.01;   // Lote fixo (se AutoLot=false)
input double InpSLATRMult         = 1.5;    // SL = ATR x este valor
input double InpTPATRMult         = 2.0;    // TP = ATR x este valor
input double InpMinConfidence     = 0.70;   // Confianca minima da IA
input int    InpMaxSpreadPoints   = 60;     // Spread maximo em pontos
input double InpMaxDailyLossPercent = 3.0;  // Limite de perda diaria em % (0 = desligado)
input int    InpDeviationPoints   = 20;     // Slippage maximo em pontos

//---------------- PAINEL ------------------------------------------

input bool   InpShowPanel         = true;   // Mostrar painel no grafico
input int    InpPanelX            = 10;     // Posicao X do painel
input int    InpPanelY            = 20;     // Posicao Y do painel

//==================================================================
// GLOBALS
//==================================================================

int g_emaFastHandle = INVALID_HANDLE;
int g_emaSlowHandle = INVALID_HANDLE;
int g_rsiHandle     = INVALID_HANDLE;
int g_atrHandle     = INVALID_HANDLE;
int g_adxHandle     = INVALID_HANDLE;

datetime g_lastBar     = 0;
datetime g_lastRequest = 0;

// Telemetria da API
uint     g_apiLatencyMs    = 0;
int      g_apiOk           = 0;
int      g_apiErr          = 0;
datetime g_lastResponseTime = 0;
datetime g_lastLiveTradeTime = 0;
bool     g_dailyLossBlocked  = false;

// AI
string g_symbol       = "";
string g_providerName = "";
string g_modelName    = "";

string g_marketState = "UNKNOWN";
string g_volatility  = "UNKNOWN";
string g_dataQuality = "UNKNOWN";
string g_action      = "WAIT";
string g_summary     = "";

double g_confidence = 0.0;

string g_lastRawResponse = "";

// Paper position
bool   g_hasPosition      = false;
string g_positionDirection = "NONE";

double g_entryPrice = 0.0;
double g_paperSL = 0.0;
double g_paperTP = 0.0;

datetime g_entryTime = 0;

double g_paperBalance = 0.0;

// Statistics
int    g_totalPaperTrades = 0;
int    g_winningTrades    = 0;
int    g_losingTrades     = 0;

double g_totalPaperPnL = 0.0;

//==================================================================
// ACTIVE TEST CONTEXT
//==================================================================
ENUM_TIMEFRAMES ActiveDecisionTF()
  {
   if(MQLInfoInteger(MQL_TESTER))
      return (ENUM_TIMEFRAMES)_Period;
   return InpDecisionTF;
  }

string ActiveSymbol()
  {
   return MQLInfoInteger(MQL_TESTER) ? _Symbol : InpSymbol;
  }

bool GetRecentStructure(double &swingHigh,double &swingLow,int lookback=0)
  {
   if(lookback <= 0) lookback=InpSwingLookback;
   if(lookback < 3) lookback=3;

   MqlRates r[];
   ArraySetAsSeries(r,true);
   int n=CopyRates(g_symbol,ActiveDecisionTF(),2,lookback,r);
   if(n < 3) return false;

   swingHigh=r[0].high;
   swingLow=r[0].low;
   for(int i=1;i<n;i++)
     {
      if(r[i].high>swingHigh) swingHigh=r[i].high;
      if(r[i].low<swingLow) swingLow=r[i].low;
     }
   return true;
  }

bool CalculateStructuralLevels(string direction,double entry,double &sl,double &tp)
  {
   double atr=0.0;
   if(!GetIndicatorValue(g_atrHandle,1,atr) || atr<=0.0) return false;

   double swingHigh=0.0,swingLow=0.0;
   if(!GetRecentStructure(swingHigh,swingLow)) return false;

   double point=SymbolInfoDouble(g_symbol,SYMBOL_POINT);
   int digits=(int)SymbolInfoInteger(g_symbol,SYMBOL_DIGITS);
   double minDist=(SymbolInfoInteger(g_symbol,SYMBOL_TRADE_STOPS_LEVEL)+2)*point;
   double buffer=atr*InpStructureBufferATR;
   double minSL=atr*InpMinSLATR;
   double maxSL=atr*InpMaxSLATR;

   if(direction=="LONG")
     {
      double structural=swingLow-buffer;
      sl=structural;
      if(entry-sl<minSL) sl=entry-minSL;
      if(entry-sl>maxSL) sl=entry-maxSL;
      if(sl>entry-minDist) sl=entry-minDist;
      double risk=entry-sl;
      tp=entry+risk*InpTargetRR;
      if(tp<entry+minDist) tp=entry+minDist;
     }
   else if(direction=="SHORT")
     {
      double structural=swingHigh+buffer;
      sl=structural;
      if(sl-entry<minSL) sl=entry+minSL;
      if(sl-entry>maxSL) sl=entry+maxSL;
      if(sl<entry+minDist) sl=entry+minDist;
      double risk=sl-entry;
      tp=entry-risk*InpTargetRR;
      if(tp>entry-minDist) tp=entry-minDist;
     }
   else return false;

   sl=NormalizeDouble(sl,digits);
   tp=NormalizeDouble(tp,digits);
   return (sl>0.0 && tp>0.0);
  }

//==================================================================
// PROVIDER HELPERS
//==================================================================

string ProviderName()
  {
   if(InpProvider == AI_GEMINI)
      return "GEMINI";

   if(InpProvider == AI_CLAUDE)
      return "CLAUDE";

   if(InpProvider == AI_OPENAI)
      return "OPENAI";

   return "UNKNOWN";
  }

//------------------------------------------------------------------

string ModelName()
  {
   if(InpProvider == AI_GEMINI)
      return InpGeminiModel;

   if(InpProvider == AI_CLAUDE)
      return InpClaudeModel;

   if(InpProvider == AI_OPENAI)
      return InpOpenAIModel;

   return "";
  }

//------------------------------------------------------------------

string ActiveApiKey()
  {
   if(InpProvider == AI_GEMINI)
      return InpGeminiApiKey;

   if(InpProvider == AI_CLAUDE)
      return InpClaudeApiKey;

   if(InpProvider == AI_OPENAI)
      return InpOpenAIApiKey;

   return "";
  }

//==================================================================
// STRING / JSON UTILITIES
//==================================================================

string JsonEscape(string text)
  {
   StringReplace(text,"\\","\\\\");
   StringReplace(text,"\"","\\\"");
   StringReplace(text,"\r","\\r");
   StringReplace(text,"\n","\\n");
   StringReplace(text,"\t","\\t");

   return text;
  }

//------------------------------------------------------------------

string JsonGetString(string json,string field)
  {
   string key = "\"" + field + "\"";

   int p = StringFind(json,key);

   // Localiza apenas nomes de campo JSON (nao valores com o mesmo texto).
   while(p >= 0)
     {
      if(p == 0 ||
         StringGetCharacter(json,p - 1) == '{' ||
         StringGetCharacter(json,p - 1) == ',' ||
         StringGetCharacter(json,p - 1) == ' ')
         break;

      p = StringFind(json,key,p + StringLen(key));
     }

   if(p < 0)
      return "";

   p = StringFind(json,":",p + StringLen(key));

   if(p < 0)
      return "";

   p++;

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

      if(c == ' ' || c == '\t' || c == '\r' || c == '\n')
         p++;
      else
         break;
     }

   if(p >= StringLen(json))
      return "";

   if(StringGetCharacter(json,p) != '"')
      return "";

   p++;

   string result = "";

   bool escaped = false;

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

      if(escaped)
        {
         if(c == 'n')
            result += "\n";
         else
         if(c == 'r')
            result += "\r";
         else
         if(c == 't')
            result += "\t";
         else
            result += ShortToString((short)c);

         escaped = false;

         p++;

         continue;
        }

      if(c == '\\')
        {
         escaped = true;
         p++;

         continue;
        }

      if(c == '"')
         break;

      result += ShortToString((short)c);

      p++;
     }

   return result;
  }

//------------------------------------------------------------------

double JsonGetNumber(string json,string field)
  {
   string key = "\"" + field + "\"";

   int p = StringFind(json,key);

   // Localiza apenas nomes de campo JSON (nao valores com o mesmo texto).
   while(p >= 0)
     {
      if(p == 0 ||
         StringGetCharacter(json,p - 1) == '{' ||
         StringGetCharacter(json,p - 1) == ',' ||
         StringGetCharacter(json,p - 1) == ' ')
         break;

      p = StringFind(json,key,p + StringLen(key));
     }

   if(p < 0)
      return 0.0;

   p = StringFind(json,":",p + StringLen(key));

   if(p < 0)
      return 0.0;

   p++;

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

      if(c == ' ' || c == '\t' || c == '\r' || c == '\n')
         p++;
      else
         break;
     }

   string number = "";

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

      if((c >= '0' && c <= '9') ||
         c == '.' ||
         c == '-' ||
         c == '+')
        {
         number += ShortToString((short)c);
         p++;
        }
      else
        {
         break;
        }
     }

   return StringToDouble(number);
  }

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

bool GetIndicatorValue(
   int handle,
   int shift,
   double &value
   )
  {
   if(handle == INVALID_HANDLE)
      return false;

   double buffer[];

   ArraySetAsSeries(buffer,true);

   int copied =
      CopyBuffer(
         handle,
         0,
         shift,
         1,
         buffer
         );

   if(copied != 1)
      return false;

   value = buffer[0];

   return true;
  }

//==================================================================
// MARKET DATA
//==================================================================

string BuildMarketContext()
  {
   MqlTick tick;

   if(!SymbolInfoTick(g_symbol,tick))
      return "";

   double emaFast = 0.0;
   double emaSlow = 0.0;
   double rsi     = 0.0;
   double atr     = 0.0;
   double adx     = 0.0;

   if(!GetIndicatorValue(g_emaFastHandle,1,emaFast))
      return "";

   if(!GetIndicatorValue(g_emaSlowHandle,1,emaSlow))
      return "";

   if(!GetIndicatorValue(g_rsiHandle,1,rsi))
      return "";

   if(!GetIndicatorValue(g_atrHandle,1,atr))
      return "";

   if(!GetIndicatorValue(g_adxHandle,1,adx))
      return "";

   MqlRates rates[];

   ArraySetAsSeries(rates,true);

   int copied =
      CopyRates(
         g_symbol,
         ActiveDecisionTF(),
         1,
         InpLookbackBars,
         rates
         );

   if(copied <= 0)
      return "";

   double m15Close = 0.0;

   MqlRates higher[];

   ArraySetAsSeries(higher,true);

   int higherCopied =
      CopyRates(
         g_symbol,
         InpContextTF,
         1,
         1,
         higher
         );

   if(higherCopied == 1)
      m15Close = higher[0].close;

   double spreadPoints =
      (tick.ask - tick.bid) / _Point;

   string data = "";

   data += "SYMBOL=" + g_symbol + "\n";

   data +=
      "DECISION_TIMEFRAME=" +
      EnumToString(ActiveDecisionTF()) +
      "\n";

   data +=
      "CONTEXT_TIMEFRAME=" +
      EnumToString(InpContextTF) +
      "\n";

   data +=
      "BID=" +
      DoubleToString(tick.bid,_Digits) +
      "\n";

   data +=
      "ASK=" +
      DoubleToString(tick.ask,_Digits) +
      "\n";

   data +=
      "SPREAD_POINTS=" +
      DoubleToString(spreadPoints,1) +
      "\n";

   data +=
      "EMA_FAST=" +
      DoubleToString(emaFast,_Digits) +
      "\n";

   data +=
      "EMA_SLOW=" +
      DoubleToString(emaSlow,_Digits) +
      "\n";

   data +=
      "RSI=" +
      DoubleToString(rsi,2) +
      "\n";

   data +=
      "ATR=" +
      DoubleToString(atr,_Digits) +
      "\n";

   data +=
      "ADX=" +
      DoubleToString(adx,2) +
      "\n";

   data +=
      "M15_CLOSE=" +
      DoubleToString(m15Close,_Digits) +
      "\n";

   data += "\nLAST_CLOSED_BARS:\n";

   for(int i=0;i<copied;i++)
     {
      data +=
         IntegerToString(i) +
         " | O=" +
         DoubleToString(rates[i].open,_Digits) +
         " | H=" +
         DoubleToString(rates[i].high,_Digits) +
         " | L=" +
         DoubleToString(rates[i].low,_Digits) +
         " | C=" +
         DoubleToString(rates[i].close,_Digits) +
         " | V=" +
         IntegerToString((int)rates[i].tick_volume) +
         "\n";
     }

   return data;
  }

//==================================================================
// PROMPT
//==================================================================

string BuildPrompt(string marketData)
  {
   string prompt =

      "You are a market-context classifier inside an MT5 "
      "paper-trading experiment.\n\n"

      "Analyze ONLY the supplied market data.\n"

      "Do not invent missing data.\n"

      "Return ONLY one valid JSON object.\n\n"

      "The strategy should prefer WAIT when trend, momentum, structure or liquidity evidence conflict.\n"
      "LONG is appropriate only when bullish structure/momentum is supported; SHORT only when bearish structure/momentum is supported.\n"
      "Do not use confidence as a reason to invent a trade.\n\n"
      "This is SIMULATION ONLY. No real orders are allowed.\n\n"

      "Required fields:\n"

      "market_state: TREND_UP, TREND_DOWN, RANGING or UNKNOWN\n"

      "volatility: LOW, NORMAL, HIGH or UNKNOWN\n"

      "data_quality: VALID or INVALID\n"

      "action: LONG, SHORT, WAIT or EXIT\n"

      "confidence: number between 0 and 1\n"

      "summary: short factual description\n\n"

      "MARKET DATA:\n" +

      marketData;

   return prompt;
  }

//==================================================================
// EXTRACT GEMINI TEXT
//==================================================================

string ExtractGeminiText(string response)
  {
   string key = "\"text\"";

   int p = StringFind(response,key);

   // Localiza apenas nomes de campo JSON (nao valores com o mesmo texto).
   while(p >= 0)
     {
      if(p == 0 ||
         StringGetCharacter(response,p - 1) == '{' ||
         StringGetCharacter(response,p - 1) == ',' ||
         StringGetCharacter(response,p - 1) == ' ')
         break;

      p = StringFind(response,key,p + StringLen(key));
     }

   if(p < 0)
      return "";

   p = StringFind(response,":",p + StringLen(key));

   if(p < 0)
      return "";

   p++;

   while(p < StringLen(response))
     {
      ushort c = StringGetCharacter(response,p);

      if(c == ' ' || c == '\t' || c == '\r' || c == '\n')
         p++;
      else
         break;
     }

   if(p >= StringLen(response))
      return "";

   if(StringGetCharacter(response,p) != '"')
      return "";

   p++;

   string result = "";

   bool escaped = false;

   while(p < StringLen(response))
     {
      ushort c = StringGetCharacter(response,p);

      if(escaped)
        {
         if(c == 'n')
            result += "\n";
         else
         if(c == 'r')
            result += "\r";
         else
         if(c == 't')
            result += "\t";
         else
            result += ShortToString((short)c);

         escaped = false;

         p++;

         continue;
        }

      if(c == '\\')
        {
         escaped = true;
         p++;

         continue;
        }

      if(c == '"')
         break;

      result += ShortToString((short)c);

      p++;
     }

   return result;
  }

//==================================================================
// GEMINI
//==================================================================

bool CallGemini(
   string prompt,
   string &response
   )
  {
   if(InpGeminiApiKey == "")
     {
      Print("GEMINI: API key nao configurada.");
      return false;
     }

   string url =
      "https://generativelanguage.googleapis.com/v1beta/models/" +
      InpGeminiModel +
      ":generateContent?key=" +
      InpGeminiApiKey;

   string body =

      "{"
      "\"contents\":["
      "{"
      "\"parts\":["
      "{"
      "\"text\":\"" +
      JsonEscape(prompt) +
      "\""
      "}"
      "]"
      "}"
      "],"

      "\"generationConfig\":{"

      "\"responseMimeType\":\"application/json\","

      "\"responseSchema\":{"

      "\"type\":\"OBJECT\","

      "\"properties\":{"

      "\"market_state\":{"
      "\"type\":\"STRING\","
      "\"enum\":["
      "\"TREND_UP\","
      "\"TREND_DOWN\","
      "\"RANGING\","
      "\"UNKNOWN\""
      "]"
      "},"

      "\"volatility\":{"
      "\"type\":\"STRING\","
      "\"enum\":["
      "\"LOW\","
      "\"NORMAL\","
      "\"HIGH\","
      "\"UNKNOWN\""
      "]"
      "},"

      "\"data_quality\":{"
      "\"type\":\"STRING\","
      "\"enum\":["
      "\"VALID\","
      "\"INVALID\""
      "]"
      "},"

      "\"action\":{"
      "\"type\":\"STRING\","
      "\"enum\":["
      "\"LONG\","
      "\"SHORT\","
      "\"WAIT\","
      "\"EXIT\""
      "]"
      "},"

      "\"confidence\":{"
      "\"type\":\"NUMBER\""
      "},"

      "\"summary\":{"
      "\"type\":\"STRING\""
      "}"

      "},"

      "\"required\":["
      "\"market_state\","
      "\"volatility\","
      "\"data_quality\","
      "\"action\","
      "\"confidence\","
      "\"summary\""
      "]"

      "}"

      "}"

      "}";

   char post[];
   char result[];

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

   string headers =
      "Content-Type: application/json\r\n";

   string responseHeaders = "";

   ResetLastError();

   int status =
      WebRequest(
         "POST",
         url,
         headers,
         InpHTTPTimeout,
         post,
         result,
         responseHeaders
         );

   if(status == -1)
     {
      Print(
         "GEMINI WebRequest error: ",
         GetLastError()
         );

      return false;
     }

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

   if(status < 200 || status >= 300)
     {
      Print(
         "GEMINI HTTP ",
         status,
         ": ",
         response
         );

      return false;
     }

   string modelText =
      ExtractGeminiText(response);

   if(modelText == "")
     {
      Print(
         "GEMINI: nao foi encontrado o texto da resposta."
         );

      return false;
     }

   response = modelText;

   return true;
  }

//==================================================================
// CLAUDE
//==================================================================

bool CallClaude(
   string prompt,
   string &response
   )
  {
   if(InpClaudeApiKey == "")
     {
      Print("CLAUDE: API key nao configurada.");
      return false;
     }

   string url =
      "https://api.anthropic.com/v1/messages";

   string body =

      "{"

      "\"model\":\"" +
      JsonEscape(InpClaudeModel) +
      "\","

      "\"max_tokens\":1000,"

      "\"messages\":["
      "{"
      "\"role\":\"user\","
      "\"content\":\"" +
      JsonEscape(prompt) +
      "\""
      "}"
      "]"

      "}";

   char post[];
   char result[];

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

   string headers =
      "Content-Type: application/json\r\n"
      "x-api-key: " +
      InpClaudeApiKey +
      "\r\n"
      "anthropic-version: 2023-06-01\r\n";

   string responseHeaders = "";

   ResetLastError();

   int status =
      WebRequest(
         "POST",
         url,
         headers,
         InpHTTPTimeout,
         post,
         result,
         responseHeaders
         );

   if(status == -1)
     {
      Print(
         "CLAUDE WebRequest error: ",
         GetLastError()
         );

      return false;
     }

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

   if(status < 200 || status >= 300)
     {
      Print(
         "CLAUDE HTTP ",
         status,
         ": ",
         response
         );

      return false;
     }

   string text =
      JsonGetString(
         response,
         "text"
         );

   if(text == "")
     {
      Print(
         "CLAUDE: texto nao encontrado."
         );

      return false;
     }

   response = text;

   return true;
  }

//==================================================================
// OPENAI
//==================================================================

bool CallOpenAI(
   string prompt,
   string &response
   )
  {
   if(InpOpenAIApiKey == "")
     {
      Print("OPENAI: API key nao configurada.");
      return false;
     }

   string url =
      "https://api.openai.com/v1/responses";

   string body =

      "{"

      "\"model\":\"" +
      JsonEscape(InpOpenAIModel) +
      "\","

      "\"input\":\"" +
      JsonEscape(prompt) +
      "\""

      "}";

   char post[];
   char result[];

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

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

   string responseHeaders = "";

   ResetLastError();

   int status =
      WebRequest(
         "POST",
         url,
         headers,
         InpHTTPTimeout,
         post,
         result,
         responseHeaders
         );

   if(status == -1)
     {
      Print(
         "OPENAI WebRequest error: ",
         GetLastError()
         );

      return false;
     }

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

   if(status < 200 || status >= 300)
     {
      Print(
         "OPENAI HTTP ",
         status,
         ": ",
         response
         );

      return false;
     }

   string text =
      JsonGetString(
         response,
         "output_text"
         );

   if(text == "")
     {
      text =
         JsonGetString(
            response,
            "text"
            );
     }

   if(text == "")
     {
      Print(
         "OPENAI: texto nao encontrado."
         );

      return false;
     }

   response = text;

   return true;
  }

//==================================================================
// SELECTED PROVIDER
//==================================================================

bool CallSelectedProvider(
   string prompt,
   string &response
   )
  {
   if(InpProvider == AI_GEMINI)
      return CallGemini(prompt,response);

   if(InpProvider == AI_CLAUDE)
      return CallClaude(prompt,response);

   if(InpProvider == AI_OPENAI)
      return CallOpenAI(prompt,response);

   return false;
  }

//==================================================================
// VALIDATION
//==================================================================

bool ValidMarketState(string value)
  {
   if(value == "TREND_UP")
      return true;

   if(value == "TREND_DOWN")
      return true;

   if(value == "RANGING")
      return true;

   if(value == "UNKNOWN")
      return true;

   return false;
  }

//------------------------------------------------------------------

bool ValidVolatility(string value)
  {
   if(value == "LOW")
      return true;

   if(value == "NORMAL")
      return true;

   if(value == "HIGH")
      return true;

   if(value == "UNKNOWN")
      return true;

   return false;
  }

//------------------------------------------------------------------

bool ValidQuality(string value)
  {
   return(
      value == "VALID" ||
      value == "INVALID"
      );
  }

//------------------------------------------------------------------

bool ValidAction(string value)
  {
   if(value == "LONG")
      return true;

   if(value == "SHORT")
      return true;

   if(value == "WAIT")
      return true;

   if(value == "EXIT")
      return true;

   return false;
  }

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

bool ParseAIResponse(string json)
  {
   string marketState =
      JsonGetString(
         json,
         "market_state"
         );

   string volatility =
      JsonGetString(
         json,
         "volatility"
         );

   string dataQuality =
      JsonGetString(
         json,
         "data_quality"
         );

   string action =
      JsonGetString(
         json,
         "action"
         );

   string summary =
      JsonGetString(
         json,
         "summary"
         );

   double confidence =
      JsonGetNumber(
         json,
         "confidence"
         );

   if(!ValidMarketState(marketState))
      return false;

   if(!ValidVolatility(volatility))
      return false;

   if(!ValidQuality(dataQuality))
      return false;

   if(!ValidAction(action))
      return false;

   if(confidence < 0.0 || confidence > 1.0)
      return false;

   if(summary == "")
      return false;

   g_marketState = marketState;
   g_volatility  = volatility;
   g_dataQuality = dataQuality;
   g_action      = action;
   g_summary     = summary;
   g_confidence  = confidence;

   return true;
  }

//==================================================================
// PAPER TRADING
//==================================================================

void OpenPaperPosition(
   string direction,
   double price
   )
  {
   if(!InpEnablePaperTrading)
      return;

   if(g_hasPosition)
      return;

   g_hasPosition = true;

   g_positionDirection = direction;

   g_entryPrice = price;

   g_paperSL=0.0;
   g_paperTP=0.0;
   CalculateStructuralLevels(direction,price,g_paperSL,g_paperTP);

   g_entryTime = TimeCurrent();

   g_totalPaperTrades++;

   Print(
      "PAPER OPEN | ",
      direction,
      " | price=",
      DoubleToString(price,_Digits),
      " | SL=",DoubleToString(g_paperSL,_Digits),
      " | TP=",DoubleToString(g_paperTP,_Digits),
      " | provider=",
      g_providerName,
      " | model=",
      g_modelName
      );
  }

//------------------------------------------------------------------

void ClosePaperPosition(
   double price,
   string reason
   )
  {
   if(!g_hasPosition)
      return;

   double pnl = 0.0;

   if(g_positionDirection == "LONG")
      pnl = price - g_entryPrice;

   if(g_positionDirection == "SHORT")
      pnl = g_entryPrice - price;

   g_paperBalance += pnl;

   g_totalPaperPnL += pnl;

   if(pnl > 0.0)
      g_winningTrades++;

   if(pnl < 0.0)
      g_losingTrades++;

   Print(
      "PAPER CLOSE | ",
      g_positionDirection,
      " | entry=",
      DoubleToString(g_entryPrice,_Digits),
      " | exit=",
      DoubleToString(price,_Digits),
      " | pnl=",
      DoubleToString(pnl,_Digits),
      " | reason=",
      reason
      );

   g_hasPosition = false;

   g_positionDirection = "NONE";

   g_entryPrice = 0.0;
   g_paperSL = 0.0;
   g_paperTP = 0.0;

   g_entryTime = 0;
  }

//==================================================================
// PAPER ACTION
//==================================================================

void ProcessPaperAction()
  {
   if(!InpEnablePaperTrading)
      return;

   if(g_dataQuality != "VALID")
      return;

   MqlTick tick;

   if(!SymbolInfoTick(g_symbol,tick))
      return;

   // Simula SL/TP estruturais no paper trader para que o resultado
   // do paper trading corresponda ao que o CTrade executaria.
   if(g_hasPosition)
     {
      if(g_positionDirection=="LONG")
        {
         if(g_paperSL>0.0 && tick.bid<=g_paperSL)
           { ClosePaperPosition(tick.bid,"STRUCTURAL_SL"); return; }
         if(g_paperTP>0.0 && tick.bid>=g_paperTP)
           { ClosePaperPosition(tick.bid,"STRUCTURAL_TP"); return; }
        }
      else if(g_positionDirection=="SHORT")
        {
         if(g_paperSL>0.0 && tick.ask>=g_paperSL)
           { ClosePaperPosition(tick.ask,"STRUCTURAL_SL"); return; }
         if(g_paperTP>0.0 && tick.ask<=g_paperTP)
           { ClosePaperPosition(tick.ask,"STRUCTURAL_TP"); return; }
        }
     }

   if(g_action == "WAIT")
      return;

   if(g_action == "EXIT")
     {
      if(g_hasPosition)
        {
         double price;

         if(g_positionDirection == "LONG")
            price = tick.bid;
         else
            price = tick.ask;

         ClosePaperPosition(
            price,
            "AI_EXIT"
            );
        }

      return;
     }

   if(g_action == "LONG")
     {
      if(!g_hasPosition)
        {
         OpenPaperPosition(
            "LONG",
            tick.ask
            );
        }
      else
      if(g_positionDirection == "SHORT")
        {
         ClosePaperPosition(
            tick.ask,
            "REVERSAL"
            );

         OpenPaperPosition(
            "LONG",
            tick.ask
            );
        }

      return;
     }

   if(g_action == "SHORT")
     {
      if(!g_hasPosition)
        {
         OpenPaperPosition(
            "SHORT",
            tick.bid
            );
        }
      else
      if(g_positionDirection == "LONG")
        {
         ClosePaperPosition(
            tick.bid,
            "REVERSAL"
            );

         OpenPaperPosition(
            "SHORT",
            tick.bid
            );
        }

      return;
     }
  }

//==================================================================
// LIVE TRADING (ordens reais via CTrade)
//==================================================================

bool LiveFindPosition(ulong &ticket, ENUM_POSITION_TYPE &ptype)
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong t = PositionGetTicket(i);

      if(t == 0)
         continue;

      if(PositionGetString(POSITION_SYMBOL) != g_symbol)
         continue;

      if(PositionGetInteger(POSITION_MAGIC) != InpMagic)
         continue;

      ticket = t;
      ptype  = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      return true;
     }

   return false;
  }

//------------------------------------------------------------------

double LiveNormalizeLot(double lot)
  {
   double minLot  = SymbolInfoDouble(g_symbol,SYMBOL_VOLUME_MIN);
   double maxLot  = SymbolInfoDouble(g_symbol,SYMBOL_VOLUME_MAX);
   double stepLot = SymbolInfoDouble(g_symbol,SYMBOL_VOLUME_STEP);

   if(stepLot <= 0.0)
      stepLot = 0.01;

   lot = MathFloor(lot / stepLot + 1e-8) * stepLot;

   if(lot < minLot)
      lot = minLot;

   if(lot > maxLot)
      lot = maxLot;

   return NormalizeDouble(lot,2);
  }

//------------------------------------------------------------------

bool LiveCloseOwnPosition(string reason)
  {
   ulong ticket;
   ENUM_POSITION_TYPE ptype;

   if(!LiveFindPosition(ticket,ptype))
      return true;

   // O broker bloqueia o fechamento quando o preco esta muito perto do SL/TP
   // (freeze level / stops level). Nesse caso tenta de novo na proxima vela.
   if(PositionSelectByTicket(ticket))
     {
      MqlTick tk;

      if(SymbolInfoTick(g_symbol,tk))
        {
         double pt = SymbolInfoDouble(g_symbol,SYMBOL_POINT);

         long lvl =
            MathMax(SymbolInfoInteger(g_symbol,SYMBOL_TRADE_FREEZE_LEVEL),
                    SymbolInfoInteger(g_symbol,SYMBOL_TRADE_STOPS_LEVEL));

         double guard = (lvl + 2) * pt;

         double psl = PositionGetDouble(POSITION_SL);
         double ptp = PositionGetDouble(POSITION_TP);

         // BUY fecha no Bid, SELL fecha no Ask
         double px = (ptype == POSITION_TYPE_BUY) ? tk.bid : tk.ask;

         bool nearSL = (psl > 0.0 && MathAbs(px - psl) <= guard);
         bool nearTP = (ptp > 0.0 && MathAbs(px - ptp) <= guard);

         if(nearSL || nearTP)
           {
            Print(
               "LIVE CLOSE ADIADO | ticket=",ticket,
               " | preco muito perto de SL/TP (nivel=",lvl," pts)"
               );

            return false;
           }
        }
     }

   bool ok = g_trade.PositionClose(ticket);

   if(ok)
      g_lastLiveTradeTime = TimeCurrent();

   Print(
      "LIVE CLOSE | ticket=",ticket,
      " | reason=",reason,
      " | ok=",ok,
      " | retcode=",g_trade.ResultRetcode(),
      " | ",g_trade.ResultRetcodeDescription()
      );

   return ok;
  }

//------------------------------------------------------------------

// Lote pelo risco: perda no SL = InpRiskPercent% do saldo.
// Usa OrderCalcProfit, que ja considera tamanho do contrato e moeda da conta (inclui conta cent).
double LiveCalcAutoLot(string direction, double entry, double sl)
  {
   double minLot = SymbolInfoDouble(g_symbol,SYMBOL_VOLUME_MIN);

   ENUM_ORDER_TYPE otype =
      (direction == "LONG") ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;

   double profit = 0.0;

   if(!OrderCalcProfit(otype,g_symbol,1.0,entry,sl,profit))
     {
      Print("LIVE SKIP | OrderCalcProfit falhou. Erro=",GetLastError());
      return 0.0;
     }

   double lossPerLot = MathAbs(profit);

   if(lossPerLot <= 0.0)
      return 0.0;

   double equity  = AccountInfoDouble(ACCOUNT_EQUITY);
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   double base    = MathMin(equity,balance);

   double riskMoney = base * InpRiskPercent / 100.0;

   double lot = riskMoney / lossPerLot;

   double stepLot = SymbolInfoDouble(g_symbol,SYMBOL_VOLUME_STEP);

   if(stepLot <= 0.0)
      stepLot = 0.01;

   lot = MathFloor(lot / stepLot + 1e-8) * stepLot;

   if(lot > InpMaxLot)
      lot = InpMaxLot;

   double maxLot = SymbolInfoDouble(g_symbol,SYMBOL_VOLUME_MAX);

   if(lot > maxLot)
      lot = maxLot;

   if(lot < minLot)
     {
      Print(
         "LIVE SKIP | risco ",DoubleToString(InpRiskPercent,2),
         "% (",DoubleToString(riskMoney,2),
         ") nao comporta o lote minimo ",DoubleToString(minLot,2),
         " | perda/lote=",DoubleToString(lossPerLot,2)
         );
      return 0.0;
     }

   return NormalizeDouble(lot,2);
  }

//------------------------------------------------------------------

// true se o mercado do simbolo esta em sessao de negociacao agora
// (se o broker nao informa sessoes, assume aberto)
bool LiveSessionOpen()
  {
   // O tester deve respeitar as sessoes reais do simbolo.
   // Nao basta SYMBOL_TRADE_MODE: um simbolo pode estar habilitado
   // para trading e, naquele instante, estar fora da sessao.
   MqlDateTime dt;
   TimeToStruct(TimeTradeServer(),dt);

   long nowSec = dt.hour * 3600 + dt.min * 60 + dt.sec;
   ENUM_DAY_OF_WEEK dow=(ENUM_DAY_OF_WEEK)dt.day_of_week;
   bool foundSession=false;

   for(int i=0;i<20;i++)
     {
      datetime from,to;
      if(!SymbolInfoSessionTrade(g_symbol,dow,i,from,to))
         break;

      foundSession=true;
      long fromSec=((long)from)%86400;
      long toSec=((long)to)%86400;

      // Sessao normal: 09:00 -> 17:00
      if(toSec>fromSec)
        {
         if(nowSec>=fromSec && nowSec<toSec)
            return true;
        }
      // Sessao atravessando meia-noite: 22:00 -> 02:00
      else if(toSec<fromSec)
        {
         if(nowSec>=fromSec || nowSec<toSec)
            return true;
        }
      // from == to: em alguns brokers isso pode representar sessao de 24h.
      else
        {
         return true;
        }
     }

   // Se o broker informou sessoes e nenhuma esta aberta, nao envie ordem.
   if(foundSession)
      return false;

   // Se nao houver informacao de sessao, deixa o terminal/broker decidir.
   return true;
  }

//------------------------------------------------------------------

bool TesterTradeWindowOpen()
  {
   // Mesma protecao de sessao usada no trading normal.
   // Evita gerar "Market closed" no Strategy Tester.
   return LiveSessionOpen();
  }

//------------------------------------------------------------------

bool PrepareBrokerStops(string direction,double entry,double &sl,double &tp)
  {
   MqlTick tick;
   if(!SymbolInfoTick(g_symbol,tick))
      return false;

   double point=SymbolInfoDouble(g_symbol,SYMBOL_POINT);
   int digits=(int)SymbolInfoInteger(g_symbol,SYMBOL_DIGITS);
   if(point<=0.0)
      return false;

   long stopsLevel=(long)SymbolInfoInteger(g_symbol,SYMBOL_TRADE_STOPS_LEVEL);
   long freezeLevel=(long)SymbolInfoInteger(g_symbol,SYMBOL_TRADE_FREEZE_LEVEL);
   long levelPts=MathMax(stopsLevel,freezeLevel)+2;
   double minDist=(double)levelPts*point;

   // O broker valida stops contra BID/ASK, nao simplesmente contra o preco teorico de entrada.
   if(direction=="LONG")
     {
      double maxSL=tick.bid-minDist;
      double minTP=tick.ask+minDist;

      if(sl<=0.0 || sl>=maxSL)
         sl=maxSL;
      if(tp<=0.0 || tp<=minTP)
         tp=minTP;
     }
   else if(direction=="SHORT")
     {
      double minSL=tick.ask+minDist;
      double maxTP=tick.bid-minDist;

      if(sl<=0.0 || sl<=minSL)
         sl=minSL;
      if(tp<=0.0 || tp>=maxTP)
         tp=maxTP;
     }
   else
      return false;

   sl=NormalizeDouble(sl,digits);
   tp=NormalizeDouble(tp,digits);

   // Segunda verificacao depois do arredondamento.
   if(direction=="LONG")
      return (sl<tick.bid && tp>tick.ask &&
              (tick.bid-sl)>=minDist && (tp-tick.ask)>=minDist);

   return (sl>tick.ask && tp<tick.bid &&
           (sl-tick.ask)>=minDist && (tick.bid-tp)>=minDist);
  }

//------------------------------------------------------------------

bool ValidateTradeStops(string direction,double sl,double tp)
  {
   MqlTick tick;
   if(!SymbolInfoTick(g_symbol,tick))
      return false;

   double point=SymbolInfoDouble(g_symbol,SYMBOL_POINT);
   if(point<=0.0)
      return false;

   long stopsLevel=(long)SymbolInfoInteger(g_symbol,SYMBOL_TRADE_STOPS_LEVEL);
   long freezeLevel=(long)SymbolInfoInteger(g_symbol,SYMBOL_TRADE_FREEZE_LEVEL);
   double minDist=(double)(MathMax(stopsLevel,freezeLevel)+2)*point;

   if(direction=="LONG")
      return sl<tick.bid && tp>tick.ask &&
             (tick.bid-sl)>=minDist && (tp-tick.ask)>=minDist;

   if(direction=="SHORT")
      return sl>tick.ask && tp<tick.bid &&
             (sl-tick.ask)>=minDist && (tick.bid-tp)>=minDist;

   return false;
  }

//------------------------------------------------------------------

//------------------------------------------------------------------

bool LiveOpen(string direction)
  {
   MqlTick tick;

   if(!SymbolInfoTick(g_symbol,tick))
      return false;

   if(!TesterTradeWindowOpen())
     {
      Print("LIVE SKIP | mercado fora da sessao de negociacao: ",g_symbol,
            " | time=",TimeToString(TimeTradeServer(),TIME_DATE|TIME_SECONDS));
      return false;
     }

   double atr = 0.0;

   if(!GetIndicatorValue(g_atrHandle,1,atr) || atr <= 0.0)
     {
      Print("LIVE SKIP | ATR indisponivel");
      return false;
     }

   double point  = SymbolInfoDouble(g_symbol,SYMBOL_POINT);
   int    digits = (int)SymbolInfoInteger(g_symbol,SYMBOL_DIGITS);

   // Distancia minima exigida pelo broker (stops level), com folga de 2 pontos
   double minDist =
      (SymbolInfoInteger(g_symbol,SYMBOL_TRADE_STOPS_LEVEL) + 2) * point;

   double entry = (direction == "LONG") ? tick.ask : tick.bid;
   double sl=0.0, tp=0.0;
   bool ok;

   if(!CalculateStructuralLevels(direction,entry,sl,tp))
     {
      Print("LIVE SKIP | estrutura/SL/TP indisponivel");
      return false;
     }

   sl = NormalizeDouble(sl,digits);
   tp = NormalizeDouble(tp,digits);

   // Ajusta os niveis para a regra real do broker (Bid/Ask + stops/freeze level).
   if(!PrepareBrokerStops(direction,entry,sl,tp) || !ValidateTradeStops(direction,sl,tp))
     {
      Print("LIVE SKIP | SL/TP invalidos para o broker | direction=",direction,
            " | bid=",DoubleToString(tick.bid,digits),
            " | ask=",DoubleToString(tick.ask,digits),
            " | sl=",DoubleToString(sl,digits),
            " | tp=",DoubleToString(tp,digits),
            " | stops=",IntegerToString((int)SymbolInfoInteger(g_symbol,SYMBOL_TRADE_STOPS_LEVEL)),
            " | freeze=",IntegerToString((int)SymbolInfoInteger(g_symbol,SYMBOL_TRADE_FREEZE_LEVEL)));
      return false;
     }

   double lot;

   if(InpAutoLot)
      lot = LiveCalcAutoLot(direction,entry,sl);
   else
      lot = LiveNormalizeLot(InpLotSize);

   if(lot <= 0.0)
      return false;

   ENUM_ORDER_TYPE otype = (direction == "LONG") ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;

   double margin = 0.0;

   if(!OrderCalcMargin(otype,g_symbol,lot,entry,margin))
     {
      Print("LIVE SKIP | OrderCalcMargin falhou. Erro=",GetLastError());
      return false;
     }

   double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);

   if(margin > freeMargin)
     {
      Print("LIVE SKIP | margem insuficiente: necessaria=",DoubleToString(margin,2)," livre=",DoubleToString(freeMargin,2));
      return false;
     }

   if(direction == "LONG")
      ok = g_trade.Buy(lot,g_symbol,0.0,sl,tp,"R3R_AI_NANO");
   else
      ok = g_trade.Sell(lot,g_symbol,0.0,sl,tp,"R3R_AI_NANO");

   Print(
      "LIVE OPEN | ",direction,
      " | lot=",DoubleToString(lot,2),
      " | sl=",DoubleToString(sl,digits),
      " | tp=",DoubleToString(tp,digits),
      " | atr=",DoubleToString(atr,digits),
      " | provider=",g_providerName,
      " | ok=",ok,
      " | retcode=",g_trade.ResultRetcode(),
      " | ",g_trade.ResultRetcodeDescription()
      );

   if(ok)
      g_lastLiveTradeTime = TimeCurrent();

   return ok;
  }

//------------------------------------------------------------------

double LiveTodayPnL();

//------------------------------------------------------------------

// true se a perda do dia (realizada + flutuante) atingiu o limite
bool LiveDailyLossHit()
  {
   static bool lastState = false;

   bool hit = false;

   if(InpMaxDailyLossPercent > 0.0)
     {
      double realized = LiveTodayPnL();
      double floating = 0.0;

      ulong t;
      ENUM_POSITION_TYPE pt;

      if(LiveFindPosition(t,pt) && PositionSelectByTicket(t))
         floating = PositionGetDouble(POSITION_PROFIT) +
                    PositionGetDouble(POSITION_SWAP);

      // saldo no inicio do dia, aproximado: saldo atual menos o realizado hoje
      double startBal = AccountInfoDouble(ACCOUNT_BALANCE) - realized;

      if(startBal > 0.0 &&
         (realized + floating) <= -startBal * InpMaxDailyLossPercent / 100.0)
         hit = true;
     }

   if(hit != lastState)
     {
      if(hit)
         Print("LIVE BLOQUEADO | limite de perda diaria atingido (",
               DoubleToString(InpMaxDailyLossPercent,1),"%). Sem novas entradas ate o proximo dia.");
      else
         Print("LIVE LIBERADO | limite de perda diaria resetado.");

      lastState = hit;
     }

   g_dailyLossBlocked = hit;

   return hit;
  }

//------------------------------------------------------------------

void LiveProcessAction()
  {
   if(!InpEnableLiveTrading && !MQLInfoInteger(MQL_TESTER))
      return;

   if(g_dataQuality != "VALID")
      return;

   if(g_action == "WAIT")
      return;

   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) ||
      !MQLInfoInteger(MQL_TRADE_ALLOWED) ||
      !AccountInfoInteger(ACCOUNT_TRADE_ALLOWED))
     {
      Print("LIVE SKIP | trading nao permitido (AutoTrading/conta)");
      return;
     }

   if(!LiveSessionOpen())
      return;

   if(SymbolInfoInteger(g_symbol,SYMBOL_TRADE_MODE) != SYMBOL_TRADE_MODE_FULL)
     {
      Print("LIVE SKIP | simbolo sem trading completo: ",g_symbol);
      return;
     }

   ulong ticket;
   ENUM_POSITION_TYPE ptype;
   bool hasPos = LiveFindPosition(ticket,ptype);

   if(g_action == "EXIT")
     {
      if(hasPos)
         LiveCloseOwnPosition("AI_EXIT");

      return;
     }

   // No Validator, filtros operacionais da conta real nao devem bloquear
   // a validacao de execucao. A estrategia continua decidindo a direcao.
   if(!MQLInfoInteger(MQL_TESTER))
     {
      // limite de perda diaria: bloqueia novas entradas e reversoes
      if(LiveDailyLossHit())
         return;
     }

   // LONG ou SHORT: filtros antes de qualquer acao
   if(!MQLInfoInteger(MQL_TESTER) && g_confidence < InpMinConfidence)
     {
      Print(
         "LIVE SKIP | confianca ",DoubleToString(g_confidence,2),
         " < minima ",DoubleToString(InpMinConfidence,2)
         );
      return;
     }

   long spread = SymbolInfoInteger(g_symbol,SYMBOL_SPREAD);

   if(!MQLInfoInteger(MQL_TESTER) && spread > InpMaxSpreadPoints)
     {
      Print("LIVE SKIP | spread ",spread," > maximo ",InpMaxSpreadPoints);
      return;
     }

   if(!MQLInfoInteger(MQL_TESTER) && g_lastLiveTradeTime > 0 &&
      (int)(TimeCurrent() - g_lastLiveTradeTime) < InpMinSecondsBetweenReversals)
     {
      Print("LIVE SKIP | aguardando ",InpMinSecondsBetweenReversals,"s entre operacoes");
      return;
     }

   if(hasPos)
     {
      bool same =
         (g_action == "LONG"  && ptype == POSITION_TYPE_BUY) ||
         (g_action == "SHORT" && ptype == POSITION_TYPE_SELL);

      if(same)
         return;

      if(!LiveCloseOwnPosition("REVERSAL"))
         return;
     }

   LiveOpen(g_action);
  }

//==================================================================
// FLOATING PNL
//==================================================================

double FloatingPnL()
  {
   if(!g_hasPosition)
      return 0.0;

   MqlTick tick;

   if(!SymbolInfoTick(g_symbol,tick))
      return 0.0;

   if(g_positionDirection == "LONG")
      return tick.bid - g_entryPrice;

   if(g_positionDirection == "SHORT")
      return g_entryPrice - tick.ask;

   return 0.0;
  }

//==================================================================
// NEW BAR
//==================================================================

bool IsNewDecisionBar()
  {
   datetime barTime =
      iTime(
         g_symbol,
         ActiveDecisionTF(),
         0
         );

   if(barTime <= 0)
      return false;

   if(barTime != g_lastBar)
     {
      g_lastBar = barTime;

      return true;
     }

   return false;
  }

//==================================================================
// REGRAS LOCAIS (Strategy Tester / validacao do Market: sem WebRequest)
//==================================================================

void RunLocalRules()
  {
   double emaFast=0.0,emaSlow=0.0,rsi=0.0,atr=0.0,adx=0.0;
   g_providerName="LOCAL RULES";
   g_modelName="R3R STRUCTURE ENGINE";

   if(!GetIndicatorValue(g_emaFastHandle,1,emaFast) ||
      !GetIndicatorValue(g_emaSlowHandle,1,emaSlow) ||
      !GetIndicatorValue(g_rsiHandle,1,rsi) ||
      !GetIndicatorValue(g_atrHandle,1,atr) ||
      !GetIndicatorValue(g_adxHandle,1,adx))
     {
      g_dataQuality="INVALID"; g_action="WAIT";
      g_summary="Indicadores indisponiveis.";
      return;
     }

   MqlRates r[];
   ArraySetAsSeries(r,true);
   if(CopyRates(g_symbol,ActiveDecisionTF(),1,3,r)<3)
     {
      g_dataQuality="INVALID"; g_action="WAIT";
      g_summary="Historico insuficiente.";
      return;
     }

   double swingHigh=0.0,swingLow=0.0;
   bool structureOK=GetRecentStructure(swingHigh,swingLow);
   if(!structureOK)
     {
      g_dataQuality="INVALID"; g_action="WAIT";
      g_summary="Estrutura de mercado indisponivel.";
      return;
     }

   double close1=r[0].close,open1=r[0].open,high1=r[0].high,low1=r[0].low;
   double body=MathAbs(close1-open1);
   double range=high1-low1;
   bool bullCandle=(close1>open1 && range>0.0 && body/range>=0.35);
   bool bearCandle=(close1<open1 && range>0.0 && body/range>=0.35);
   bool trendUp=(emaFast>emaSlow);
   bool trendDown=(emaFast<emaSlow);
   bool trendStrong=(adx>=InpMinTrendADX);
   double breakout=atr*InpBreakoutBufferATR;

   // Liquidity sweep: rompe o extremo anterior e fecha de volta para dentro.
   bool sweepLow=(low1 < swingLow && close1 > swingLow);
   bool sweepHigh=(high1 > swingHigh && close1 < swingHigh);

   // Breakout confirmado: fechamento alem da estrutura, com margem de ATR.
   bool breakUp=(close1 > swingHigh + breakout);
   bool breakDown=(close1 < swingLow - breakout);

   // Pullback: preco testa EMA rapida e fecha a favor da tendencia.
   bool pullbackLong=(low1 <= emaFast && close1 > emaFast && bullCandle);
   bool pullbackShort=(high1 >= emaFast && close1 < emaFast && bearCandle);

   bool longSignal=trendUp && trendStrong && rsi>=52.0 && rsi<=68.0 &&
                    ((breakUp && bullCandle) || sweepLow || pullbackLong);
   bool shortSignal=trendDown && trendStrong && rsi<=48.0 && rsi>=32.0 &&
                     ((breakDown && bearCandle) || sweepHigh || pullbackShort);

   g_dataQuality="VALID";
   g_volatility=(adx>=25.0 ? "HIGH_TREND" : (adx>=InpMinTrendADX ? "NORMAL_TREND" : "RANGE"));
   g_action="WAIT";
   g_confidence=0.50;
   g_marketState=trendUp ? "UPTREND" : (trendDown ? "DOWNTREND" : "RANGE");

   if(longSignal && !shortSignal)
     {
      g_action="LONG";
      g_confidence=0.82;
     }
   else if(shortSignal && !longSignal)
     {
      g_action="SHORT";
      g_confidence=0.82;
     }
   else if(longSignal && shortSignal)
     {
      g_action="WAIT";
      g_confidence=0.50;
     }

   // Somente para o Market Validator: evita um teste sem operacoes por falta de gatilho.
   // A regra de fallback nao participa do caminho normal do produto.
   if(MQLInfoInteger(MQL_TESTER) && g_action=="WAIT")
     {
      if(trendUp && rsi>50.0) { g_action="LONG"; g_confidence=0.71; }
      else if(trendDown && rsi<50.0) { g_action="SHORT"; g_confidence=0.71; }
     }

   g_summary="R3R: ADX="+DoubleToString(adx,1)+
             " RSI="+DoubleToString(rsi,1)+
             " | estrutura="+(breakUp?"BREAK_UP":(breakDown?"BREAK_DOWN":(sweepLow?"SWEEP_LOW":(sweepHigh?"SWEEP_HIGH":"PULLBACK/WAIT"))));

   Print("LOCAL R3R | state=",g_marketState,
         " | action=",g_action,
         " | ADX=",DoubleToString(adx,1),
         " | RSI=",DoubleToString(rsi,1),
         " | H=",DoubleToString(swingHigh,_Digits),
         " | L=",DoubleToString(swingLow,_Digits));

   ProcessPaperAction();
   LiveProcessAction();
  }

//==================================================================
// TECHNICAL GATE FOR AI
//==================================================================
bool TechnicalGate(string requested)
  {
   if(requested!="LONG" && requested!="SHORT") return false;

   double emaFast=0.0,emaSlow=0.0,rsi=0.0,adx=0.0,atr=0.0;
   if(!GetIndicatorValue(g_emaFastHandle,1,emaFast) ||
      !GetIndicatorValue(g_emaSlowHandle,1,emaSlow) ||
      !GetIndicatorValue(g_rsiHandle,1,rsi) ||
      !GetIndicatorValue(g_adxHandle,1,adx) ||
      !GetIndicatorValue(g_atrHandle,1,atr)) return false;

   MqlRates r[];
   ArraySetAsSeries(r,true);
   if(CopyRates(g_symbol,ActiveDecisionTF(),1,1,r)!=1) return false;

   double hi=0.0,lo=0.0;
   if(!GetRecentStructure(hi,lo)) return false;

   double body=MathAbs(r[0].close-r[0].open);
   double range=r[0].high-r[0].low;
   bool bull=(r[0].close>r[0].open && range>0.0 && body/range>=0.35);
   bool bear=(r[0].close<r[0].open && range>0.0 && body/range>=0.35);
   bool strong=(adx>=InpMinTrendADX);
   double buf=atr*InpBreakoutBufferATR;

   if(requested=="LONG")
     {
      if(emaFast<=emaSlow || !strong || rsi<52.0 || rsi>68.0) return false;
      bool breakout=(r[0].close>hi+buf && bull);
      bool sweep=(r[0].low<lo && r[0].close>lo);
      bool pullback=(r[0].low<=emaFast && r[0].close>emaFast && bull);
      return breakout || sweep || pullback;
     }

   if(emaFast>=emaSlow || !strong || rsi>48.0 || rsi<32.0) return false;
   bool breakout=(r[0].close<lo-buf && bear);
   bool sweep=(r[0].high>hi && r[0].close<hi);
   bool pullback=(r[0].high>=emaFast && r[0].close<emaFast && bear);
   return breakout || sweep || pullback;
  }

//==================================================================
// RUN ANALYSIS
//==================================================================

void RunAIAnalysis()
  {
   // Strategy Tester nao permite WebRequest: usa regras locais
   if(MQLInfoInteger(MQL_TESTER) && InpLocalRulesInTester)
     {
      RunLocalRules();
      return;
     }

   if(ActiveApiKey() == "")
     {
      g_dataQuality = "INVALID";

      g_action = "WAIT";

      g_summary =
         "API key nao configurada para o provedor selecionado.";

      return;
     }

   datetime now = TimeCurrent();

   if(g_lastRequest > 0)
     {
      int elapsed =
         (int)(now - g_lastRequest);

      if(elapsed < InpMinSecondsBetweenRequests)
         return;
     }

   string marketData =
      BuildMarketContext();

   if(marketData == "")
     {
      g_dataQuality = "INVALID";

      g_action = "WAIT";

      g_summary =
         "Falha ao coletar dados do mercado.";

      return;
     }

   string prompt =
      BuildPrompt(marketData);

   string response = "";

   g_providerName =
      ProviderName();

   g_modelName =
      ModelName();

   uint apiStart = GetTickCount();

   bool success =
      CallSelectedProvider(
         prompt,
         response
         );

   g_apiLatencyMs = GetTickCount() - apiStart;

   g_lastRequest = now;

   if(!success)
     {
      g_apiErr++;

      g_dataQuality = "INVALID";

      g_action = "WAIT";

      g_summary =
         "Falha na chamada da API.";

      return;
     }

   g_lastRawResponse = response;

   g_apiOk++;

   g_lastResponseTime = TimeCurrent();

   if(!ParseAIResponse(response))
     {
      g_dataQuality = "INVALID";

      g_action = "WAIT";

      g_summary =
         "Resposta JSON rejeitada pela validacao.";

      Print(
         "AI JSON REJECTED: ",
         response
         );

      return;
     }

   Print(
      "AI RESPONSE | provider=",
      g_providerName,
      " | model=",
      g_modelName,
      " | state=",
      g_marketState,
      " | action=",
      g_action,
      " | confidence=",
      DoubleToString(g_confidence,2)
      );

   if(g_action=="LONG" || g_action=="SHORT")
     {
      if(!TechnicalGate(g_action))
        {
         Print("AI GATE | ",g_action," rejeitado pela estrutura tecnica.");
         g_action="WAIT";
         g_confidence=0.50;
         g_summary += " | GATE=WAIT";
        }
     }

   ProcessPaperAction();

   LiveProcessAction();
  }

//==================================================================
// PANEL (objetos graficos: fundo escuro + telemetria)
//==================================================================

#define PNL_PREFIX "R3RP_"
#define PNL_W      390
#define PNL_ROW    17

#define C_BG      C'13,17,26'
#define C_HEADER  C'21,28,44'
#define C_BORDER  C'44,56,82'
#define C_SEP     C'34,44,66'
#define C_TEXT    C'226,232,244'
#define C_MUTED   C'122,134,160'
#define C_GOLD    C'255,193,7'
#define C_GREEN   C'38,198,125'
#define C_RED     C'239,83,80'
#define C_AMBER   C'255,152,0'

void PnlRect(string id,int x,int y,int w,int h,color bg,color border)
  {
   string n = PNL_PREFIX + id;

   if(ObjectFind(0,n) < 0)
     {
      ObjectCreate(0,n,OBJ_RECTANGLE_LABEL,0,0,0);
      ObjectSetInteger(0,n,OBJPROP_CORNER,CORNER_LEFT_UPPER);
      ObjectSetInteger(0,n,OBJPROP_BORDER_TYPE,BORDER_FLAT);
      ObjectSetInteger(0,n,OBJPROP_SELECTABLE,false);
      ObjectSetInteger(0,n,OBJPROP_HIDDEN,true);
      ObjectSetInteger(0,n,OBJPROP_BACK,false);
     }

   ObjectSetInteger(0,n,OBJPROP_XDISTANCE,x);
   ObjectSetInteger(0,n,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(0,n,OBJPROP_XSIZE,w);
   ObjectSetInteger(0,n,OBJPROP_YSIZE,h);
   ObjectSetInteger(0,n,OBJPROP_BGCOLOR,bg);
   ObjectSetInteger(0,n,OBJPROP_COLOR,border);
  }

//------------------------------------------------------------------

void PnlText(
   string id,
   int x,
   int y,
   string text,
   color clr,
   int size=9,
   string font="Consolas",
   ENUM_ANCHOR_POINT anchor=ANCHOR_LEFT_UPPER
   )
  {
   string n = PNL_PREFIX + id;

   if(ObjectFind(0,n) < 0)
     {
      ObjectCreate(0,n,OBJ_LABEL,0,0,0);
      ObjectSetInteger(0,n,OBJPROP_CORNER,CORNER_LEFT_UPPER);
      ObjectSetInteger(0,n,OBJPROP_SELECTABLE,false);
      ObjectSetInteger(0,n,OBJPROP_HIDDEN,true);
      ObjectSetInteger(0,n,OBJPROP_BACK,false);
     }

   ObjectSetInteger(0,n,OBJPROP_ANCHOR,anchor);
   ObjectSetInteger(0,n,OBJPROP_XDISTANCE,x);
   ObjectSetInteger(0,n,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(0,n,OBJPROP_COLOR,clr);
   ObjectSetInteger(0,n,OBJPROP_FONTSIZE,size);
   ObjectSetString(0,n,OBJPROP_FONT,font);
   ObjectSetString(0,n,OBJPROP_TEXT,text);
  }

//------------------------------------------------------------------

void PnlTitle(string id,int &y,string text)
  {
   PnlRect("sep_" + id,InpPanelX,y,PNL_W,1,C_SEP,C_SEP);
   PnlRect("acc_" + id,InpPanelX + 14,y + 8,3,11,C_GOLD,C_GOLD);
   PnlText("ttl_" + id,InpPanelX + 24,y + 6,text,C_GOLD,8,"Segoe UI Semibold");

   y += 26;
  }

//------------------------------------------------------------------

void PnlRow(string id,int &y,string label,string value,color vc,int size=9)
  {
   PnlText("lb_" + id,InpPanelX + 14,y,label,C_MUTED,8,"Segoe UI");
   PnlText("vl_" + id,InpPanelX + PNL_W - 14,y - 1,value,vc,size,"Consolas",ANCHOR_RIGHT_UPPER);

   y += PNL_ROW;
  }

//------------------------------------------------------------------

string PnlSigned(double v)
  {
   return (v > 0.0 ? "+" : "") + DoubleToString(v,2);
  }

color PnlColor(double v)
  {
   if(v > 0.0)
      return C_GREEN;

   if(v < 0.0)
      return C_RED;

   return C_TEXT;
  }

string PnlFmtSecs(int sec)
  {
   if(sec < 0)
      sec = 0;

   return StringFormat("%02d:%02d",sec / 60,sec % 60);
  }

//------------------------------------------------------------------

// P&L realizado hoje pelo EA (deals de saida com este magic e simbolo)
double LiveTodayPnL()
  {
   static datetime lastCalc = 0;
   static double   cached   = 0.0;

   datetime now = TimeCurrent();

   // no testador o cache dura mais (reduz HistorySelect e acelera o teste)
   int ttl = MQLInfoInteger(MQL_TESTER) ? 60 : 5;

   if(now - lastCalc < ttl)
      return cached;

   lastCalc = now;

   datetime dayStart = StringToTime(TimeToString(now,TIME_DATE));

   if(!HistorySelect(dayStart,now + 60))
      return cached;

   double sum = 0.0;
   int total = HistoryDealsTotal();

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

      if(d == 0)
         continue;

      if(HistoryDealGetInteger(d,DEAL_MAGIC) != InpMagic)
         continue;

      if(HistoryDealGetString(d,DEAL_SYMBOL) != g_symbol)
         continue;

      long entry = HistoryDealGetInteger(d,DEAL_ENTRY);

      if(entry != DEAL_ENTRY_OUT &&
         entry != DEAL_ENTRY_OUT_BY &&
         entry != DEAL_ENTRY_INOUT)
         continue;

      sum += HistoryDealGetDouble(d,DEAL_PROFIT) +
             HistoryDealGetDouble(d,DEAL_COMMISSION) +
             HistoryDealGetDouble(d,DEAL_SWAP);
     }

   cached = sum;

   return sum;
  }

void UpdatePanel()
  {
   // No Strategy Tester sem modo visual o painel nao serve para nada e deixa o teste lento
   if(MQLInfoInteger(MQL_TESTER) && !MQLInfoInteger(MQL_VISUAL_MODE))
      return;

   // limita a ~2 atualizacoes por segundo (OnTick + OnTimer chamam esta funcao)
   static uint lastDraw = 0;

   uint nowMs = GetTickCount();

   if(nowMs - lastDraw < 500)
      return;

   lastDraw = nowMs;

   if(!InpShowPanel)
     {
      ObjectsDeleteAll(0,PNL_PREFIX);
      return;
     }

   int x = InpPanelX;
   int y = InpPanelY;

   // fundo (criado primeiro; altura final ajustada no fim)
   PnlRect("bg",x,y,PNL_W,10,C_BG,C_BORDER);

   //--------------------------------------------------------------
   // DADOS
   //--------------------------------------------------------------

   MqlTick tick;
   bool haveTick = SymbolInfoTick(g_symbol,tick);

   string bid = haveTick ? DoubleToString(tick.bid,_Digits) : "-";
   string ask = haveTick ? DoubleToString(tick.ask,_Digits) : "-";

   long spread = SymbolInfoInteger(g_symbol,SYMBOL_SPREAD);

   double atr = 0.0;
   bool haveAtr = GetIndicatorValue(g_atrHandle,1,atr);

   bool autoTrade =
      (TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) != 0) &&
      (MQLInfoInteger(MQL_TRADE_ALLOWED) != 0);

   string ccy = AccountInfoString(ACCOUNT_CURRENCY);

   ulong lvTicket = 0;
   ENUM_POSITION_TYPE lvType = POSITION_TYPE_BUY;
   bool hasLive = LiveFindPosition(lvTicket,lvType);

   double lvVol = 0.0, lvOpen = 0.0, lvSL = 0.0, lvTP = 0.0, lvPnL = 0.0;

   if(hasLive && PositionSelectByTicket(lvTicket))
     {
      lvVol  = PositionGetDouble(POSITION_VOLUME);
      lvOpen = PositionGetDouble(POSITION_PRICE_OPEN);
      lvSL   = PositionGetDouble(POSITION_SL);
      lvTP   = PositionGetDouble(POSITION_TP);
      lvPnL  = PositionGetDouble(POSITION_PROFIT) +
               PositionGetDouble(POSITION_SWAP);
     }

   double paperFloat = FloatingPnL();

   double winRate = 0.0;

   if(g_totalPaperTrades > 0)
      winRate = ((double)g_winningTrades / (double)g_totalPaperTrades) * 100.0;

   //--------------------------------------------------------------
   // CABECALHO
   //--------------------------------------------------------------

   PnlRect("hdr",x,y,PNL_W,46,C_HEADER,C_HEADER);
   PnlRect("hdr_line",x,y,PNL_W,3,C_GOLD,C_GOLD);

   PnlText("hdr_t",x+14,y+9,"R3R AI NANO",C_TEXT,13,"Segoe UI Semibold");
   PnlText("hdr_s",x+14,y+29,"MULTI-MODEL  |  V3 LIVE",C_MUTED,7,"Segoe UI");

   color  badgeClr = C_AMBER;
   string badgeTxt = "PAPER";

   if(InpEnableLiveTrading)
     {
      badgeClr = autoTrade ? C_GREEN : C_RED;
      badgeTxt = autoTrade ? "LIVE" : "LIVE OFF";
     }

   PnlRect("badge",x+PNL_W-86,y+13,72,22,badgeClr,badgeClr);
   PnlText("badge_t",x+PNL_W-50,y+17,badgeTxt,C_BG,9,"Segoe UI Semibold",ANCHOR_UPPER);

   y += 52;

   //--------------------------------------------------------------
   // MERCADO
   //--------------------------------------------------------------

   PnlTitle("mkt",y,"MERCADO  " + g_symbol);

   PnlRow("bidask",y,"BID / ASK",bid + "  /  " + ask,C_TEXT);

   color spClr = (spread > InpMaxSpreadPoints) ? C_RED : C_TEXT;

   PnlRow("sprd",y,"SPREAD / ATR",
          (string)spread + " pts  |  " +
          (haveAtr ? DoubleToString(atr,_Digits) : "-"),
          spClr);

   color volClr = C_TEXT;

   if(g_volatility == "HIGH")
      volClr = C_AMBER;

   PnlRow("state",y,"ESTADO / VOL",g_marketState + "  /  " + g_volatility,volClr);

   //--------------------------------------------------------------
   // DECISAO DA IA
   //--------------------------------------------------------------

   PnlTitle("ai",y,"DECISAO DA IA");

   PnlRow("prov",y,"PROVEDOR",g_providerName + "  |  " + g_modelName,C_TEXT);

   color actClr = C_MUTED;

   if(g_action == "LONG")
      actClr = C_GREEN;
   else
   if(g_action == "SHORT")
      actClr = C_RED;
   else
   if(g_action == "EXIT")
      actClr = C_AMBER;

   PnlRow("act",y,"ACAO",g_action,actClr,11);

   // barra de confianca
   int barW = 120;
   int barX = x + PNL_W - 14 - barW;

   color confClr =
      (g_confidence >= InpMinConfidence) ? C_GREEN : C_AMBER;

   PnlText("lb_conf",x+14,y,"CONFIANCA",C_MUTED,8,"Segoe UI");
   PnlText("vl_conf",barX-8,y-1,DoubleToString(g_confidence,2),confClr,9,"Consolas",ANCHOR_RIGHT_UPPER);
   PnlRect("bar_bg",barX,y+3,barW,8,C_SEP,C_SEP);

   int fillW = (int)MathRound(barW * MathMax(0.0,MathMin(1.0,g_confidence)));

   PnlRect("bar_fg",barX,y+3,MathMax(1,fillW),8,confClr,confClr);

   y += PNL_ROW;

   PnlRow("dq",y,"QUALIDADE DADOS",g_dataQuality,
          (g_dataQuality == "VALID") ? C_GREEN : C_RED);

   string sum = (g_summary == "") ? "-" : g_summary;
   string l1  = StringSubstr(sum,0,56);
   string l2  = StringSubstr(sum,56,56);

   if(StringLen(sum) > 112)
      l2 = StringSubstr(sum,56,53) + "...";

   PnlText("sum1",x+14,y+1,l1,C_TEXT,8,"Segoe UI");
   y += 15;
   PnlText("sum2",x+14,y+1,l2,C_TEXT,8,"Segoe UI");
   y += 17;

   //--------------------------------------------------------------
   // ORDENS REAIS
   //--------------------------------------------------------------

   PnlTitle("live",y,"ORDENS REAIS");

   string modeTxt = "DESLIGADO (paper)";
   color  modeClr = C_AMBER;

   if(InpEnableLiveTrading)
     {
      modeTxt = autoTrade ? "ATIVO  |  AutoTrading ON" : "ATIVO  |  AutoTrading OFF";
      modeClr = autoTrade ? C_GREEN : C_RED;
     }

   PnlRow("mode",y,"MODO",modeTxt,modeClr);

   string lotTxt = InpAutoLot ?
      ("AUTO " + DoubleToString(InpRiskPercent,1) + "% risco") :
      ("FIXO " + DoubleToString(InpLotSize,2));

   PnlRow("lot",y,"LOTE",lotTxt,C_TEXT);

   if(hasLive)
     {
      bool isBuy = (lvType == POSITION_TYPE_BUY);

      PnlRow("pos",y,"POSICAO",
             (isBuy ? "BUY " : "SELL ") + DoubleToString(lvVol,2) + " lot",
             isBuy ? C_GREEN : C_RED);

      PnlRow("ent",y,"ENTRADA",DoubleToString(lvOpen,_Digits),C_TEXT);

      PnlRow("sltp",y,"SL / TP",
             (lvSL > 0.0 ? DoubleToString(lvSL,_Digits) : "-") + "  /  " +
             (lvTP > 0.0 ? DoubleToString(lvTP,_Digits) : "-"),
             C_TEXT);

      PnlRow("fl",y,"P&L FLUTUANTE",PnlSigned(lvPnL) + " " + ccy,PnlColor(lvPnL),10);
     }
   else
     {
      PnlRow("pos",y,"POSICAO","FLAT",C_MUTED);
      PnlRow("ent",y,"ENTRADA","-",C_MUTED);
      PnlRow("sltp",y,"SL / TP","-",C_MUTED);
      PnlRow("fl",y,"P&L FLUTUANTE","-",C_MUTED);
     }

   //--------------------------------------------------------------
   // CONTA
   //--------------------------------------------------------------

   PnlTitle("acc",y,"CONTA");

   PnlRow("bal",y,"SALDO / EQUITY",
          DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE),2) + "  /  " +
          DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2) + " " + ccy,
          C_TEXT);

   double today = LiveTodayPnL();

   PnlRow("today",y,"P&L HOJE (EA)",PnlSigned(today) + " " + ccy,PnlColor(today));

   LiveDailyLossHit();

   PnlRow("dlim",y,"LIMITE PERDA DIA",
          g_dailyLossBlocked ? "BLOQUEADO" :
          (InpMaxDailyLossPercent > 0.0 ?
           ("OK  (" + DoubleToString(InpMaxDailyLossPercent,1) + "%)") : "DESLIGADO"),
          g_dailyLossBlocked ? C_RED : (InpMaxDailyLossPercent > 0.0 ? C_GREEN : C_MUTED));

   //--------------------------------------------------------------
   // PAPER
   //--------------------------------------------------------------

   PnlTitle("paper",y,"PAPER TRADING");

   PnlRow("ppos",y,"POSICAO / FLUT.",
          (g_hasPosition ? g_positionDirection : "FLAT") + "  |  " +
          (g_hasPosition ? PnlSigned(paperFloat) : "-"),
          g_hasPosition ? PnlColor(paperFloat) : C_MUTED);

   PnlRow("pstat",y,"TRADES  W / L",
          (string)g_totalPaperTrades + "  |  " +
          (string)g_winningTrades + " / " + (string)g_losingTrades +
          "  |  " + DoubleToString(winRate,0) + "%",
          C_TEXT);

   PnlRow("ppnl",y,"P&L TOTAL (pts)",PnlSigned(g_totalPaperPnL),PnlColor(g_totalPaperPnL));

   //--------------------------------------------------------------
   // TELEMETRIA
   //--------------------------------------------------------------

   PnlTitle("tele",y,"TELEMETRIA");

   color latClr = C_MUTED;
   string latTxt = "-";

   if(g_apiOk + g_apiErr > 0)
     {
      latTxt = (string)g_apiLatencyMs + " ms";
      latClr = (g_apiLatencyMs < 3000) ? C_GREEN :
               ((g_apiLatencyMs < 8000) ? C_AMBER : C_RED);
     }

   PnlRow("lat",y,"LATENCIA API",latTxt,latClr);

   PnlRow("calls",y,"CHAMADAS OK / ERRO",
          (string)g_apiOk + "  /  " + (string)g_apiErr,
          (g_apiErr > 0) ? C_AMBER : C_TEXT);

   datetime nowT = TimeCurrent();

   datetime barOpen = iTime(g_symbol,InpDecisionTF,0);

   int toBar = (barOpen > 0) ?
      (int)(barOpen + PeriodSeconds(InpDecisionTF) - nowT) : 0;

   string lastResp = (g_lastResponseTime > 0) ?
      (PnlFmtSecs((int)(nowT - g_lastResponseTime)) + " atras") : "-";

   PnlRow("timers",y,"NOVA VELA / ULT. RESP.",
          PnlFmtSecs(toBar) + "  /  " + lastResp,
          C_TEXT);

   //--------------------------------------------------------------
   // RODAPE
   //--------------------------------------------------------------

   PnlText("foot",x+14,y+3,"servidor " + TimeToString(nowT,TIME_SECONDS),C_MUTED,7,"Segoe UI");

   y += 22;

   // ajusta altura do fundo
   PnlRect("bg",x,InpPanelY,PNL_W,y - InpPanelY,C_BG,C_BORDER);

   ChartRedraw(0);
  }

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

int OnInit()
  {
   // Simbolo: usa o input; se nao existir nesta conta/testador, usa o simbolo do grafico
   g_symbol = ActiveSymbol();

   if(g_symbol == "" || !SymbolSelect(g_symbol,true))
     {
      Print("AVISO: simbolo '",InpSymbol,"' nao existe aqui. Usando o simbolo do grafico: ",_Symbol);

      g_symbol = _Symbol;

      SymbolSelect(g_symbol,true);
     }

   g_providerName = ProviderName();
   g_modelName    = ModelName();

   g_paperBalance = InpPaperBalance;

   g_trade.SetExpertMagicNumber(InpMagic);
   g_trade.SetDeviationInPoints(InpDeviationPoints);
   g_trade.SetTypeFillingBySymbol(g_symbol);

   Print("==========================================");
   Print("R3R AI NANO V3 - INICIANDO");
   Print("Symbol input: ", g_symbol);
   Print("Decision TF: ", EnumToString(ActiveDecisionTF()));
   Print("Context TF: ", EnumToString(InpContextTF));
   Print("Provider: ", g_providerName);
   Print("Model: ", g_modelName);
   Print("==========================================");

   //===============================================================
   // VERIFICAR SE O SIMBOLO EXISTE
   //===============================================================

   if(!SymbolSelect(g_symbol,true))
     {
      Print("ERRO: nao foi possivel selecionar o simbolo: ",g_symbol);
     }
   else
     {
      Print("OK: simbolo selecionado: ",g_symbol);
     }

   //===============================================================
   // EMA FAST
   //===============================================================

   g_emaFastHandle =
      iMA(
         g_symbol,
         ActiveDecisionTF(),
         InpFastEMA,
         0,
         MODE_EMA,
         PRICE_CLOSE
         );

   if(g_emaFastHandle == INVALID_HANDLE)
     {
      Print(
         "ERRO EMA FAST | GetLastError=",
         GetLastError()
         );
     }
   else
     {
      Print("OK EMA FAST");
     }

   //===============================================================
   // EMA SLOW
   //===============================================================

   g_emaSlowHandle =
      iMA(
         g_symbol,
         ActiveDecisionTF(),
         InpSlowEMA,
         0,
         MODE_EMA,
         PRICE_CLOSE
         );

   if(g_emaSlowHandle == INVALID_HANDLE)
     {
      Print(
         "ERRO EMA SLOW | GetLastError=",
         GetLastError()
         );
     }
   else
     {
      Print("OK EMA SLOW");
     }

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

   g_rsiHandle =
      iRSI(
         g_symbol,
         ActiveDecisionTF(),
         InpRSIPeriod,
         PRICE_CLOSE
         );

   if(g_rsiHandle == INVALID_HANDLE)
     {
      Print(
         "ERRO RSI | GetLastError=",
         GetLastError()
         );
     }
   else
     {
      Print("OK RSI");
     }

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

   g_atrHandle =
      iATR(
         g_symbol,
         ActiveDecisionTF(),
         InpATRPeriod
         );

   if(g_atrHandle == INVALID_HANDLE)
     {
      Print(
         "ERRO ATR | GetLastError=",
         GetLastError()
         );
     }
   else
     {
      Print("OK ATR");
     }

   //===============================================================
   // ADX
   //===============================================================

   g_adxHandle=iADX(g_symbol,ActiveDecisionTF(),InpADXPeriod);
   if(g_adxHandle==INVALID_HANDLE)
      Print("ERRO ADX | GetLastError=",GetLastError());
   else
      Print("OK ADX");

   //===============================================================
   // TIMER
   //===============================================================

   ResetLastError();

   if(!EventSetTimer(1))
     {
      Print(
         "AVISO: EventSetTimer falhou. Erro=",
         GetLastError()
         );
     }
   else
     {
      Print("OK TIMER");
     }

   //===============================================================
   // NAO FALHAR O INIT
   //===============================================================

   Print("==========================================");
   Print("R3R AI NANO V3 INICIADO");
   if(InpEnableLiveTrading)
      Print("REAL ORDERS: ENABLED | magic=",InpMagic," | ",InpAutoLot ? "AUTO LOT risco="+DoubleToString(InpRiskPercent,2)+"%" : "lot fixo="+DoubleToString(InpLotSize,2));
   else
      Print("REAL ORDERS: DISABLED (paper only)");
   Print("==========================================");

   return INIT_SUCCEEDED;
  }

//==================================================================
// TIMER
//==================================================================

void OnTimer()
  {
   UpdatePanel();

   if(IsNewDecisionBar())
     {
      Print(
         "NOVA VELA ",
         EnumToString(ActiveDecisionTF()) + " | " + g_symbol
         );

      RunAIAnalysis();
     }
  }

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

void OnTick()
  {
   UpdatePanel();

   // No Strategy Tester garante a decisao por tick (nao depende do timer)
   if(MQLInfoInteger(MQL_TESTER) && IsNewDecisionBar())
      RunAIAnalysis();
  }

//==================================================================
// DEINIT
//==================================================================

void OnDeinit(const int reason)
  {
   EventKillTimer();

   if(g_emaFastHandle != INVALID_HANDLE)
      IndicatorRelease(g_emaFastHandle);

   if(g_emaSlowHandle != INVALID_HANDLE)
      IndicatorRelease(g_emaSlowHandle);

   if(g_rsiHandle != INVALID_HANDLE)
      IndicatorRelease(g_rsiHandle);

   if(g_atrHandle != INVALID_HANDLE)
      IndicatorRelease(g_atrHandle);

   if(g_adxHandle != INVALID_HANDLE)
      IndicatorRelease(g_adxHandle);

   ObjectsDeleteAll(0,PNL_PREFIX);

   Comment("");
  }

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