Voltar
BortolaIA-X Docs
Tecnologia de Alta Performance

Integração Zero-Touch.

Esqueça configurações manuais complexas. O BortolaIA-X foi desenhado para ser plug-and-play: instale o EA e o licenciamento se resolve sozinho.

Guia de Ativação Profissional

Otimize seu tempo com o provisionamento automático.

01

Aquisição & Checkout

Escolha seu plano. Sua chave EA-XXXX é gerada instantaneamente após a aprovação do pagamento no Mercado Pago.

02

Autorização WebRequest

Autorize o domínio da BortolaIA-X no seu MetaTrader 5 para permitir a comunicação segura via TLS.

03

Instalação & Hardware

Baixe o MQ5, instale na pasta Experts e insira sua chave. O HWID é vinculado automaticamente no primeiro tick.

04

Vínculo Inteligente

O sistema detecta seu número de conta e vincula à licença de forma transparente, sem formulários chatos.

05

Heartbeat de Segurança

O EA revalida a licença a cada hora. Se detectar acesso não autorizado, a operação é bloqueada imediatamente.

06

Dashboard em Tempo Real

Acompanhe métricas avançadas, curva de patrimônio e telemetria de qualquer lugar do mundo via web.

1. Autorização de Tráfego MT5

Configure seu terminal para aceitar conexões criptografadas.

O MetaTrader 5 bloqueia conexões externas por padrão. Você precisa autorizar explicitamente o domínio da BortolaIA-X para permitir a validação em tempo real.

1

Abra o MT5 e vá em Ferramentas > Opções (Ctrl+O).

2

Acesse a aba Expert Advisors.

3

Marque 'Permitir WebRequest para as URLs listadas'.

4

Clique no botão '+' e adicione a URL abaixo:

https://bortolaia.com.br

2. Segurança & HWID

Proteção de ponta a ponta vinculada ao seu hardware.

Utilizamos um protocolo híbrido de autenticação. Sua licença não é apenas uma senha, é um vínculo digital único com sua estação de trading.

1

Provisionamento Imediato

Aprovação automática em milissegundos após o checkout.

2

Auto-Vínculo de HWID

Seu Hardware ID é registrado no primeiro acesso, sem intervenção humana.

3

Criptografia de Ponta

Toda telemetria é assinada digitalmente via HMAC-SHA256.

4

Gestão Dinâmica

Troca de conta MT5 permitida a cada 15 dias direto no painel.

Protocolo de Validação

Endpoint Público v2
// Chamada segura via POST
POST /api/public/license/validate
key=EA-XXXX-XXXX-XXXX&account=51204488&broker=BortolaIA
valid: trueAcesso total autorizado.
not_foundChave inexistente no sistema.
expiredPlano vencido. Requer renovação.
account_limitLimite de terminais atingido.
suspendedBloqueio administrativo ativo.
device_mismatchTentativa de uso em outro hardware.

3. Módulo MQL5 Native

Framework robusto para validação e telemetria.

BortolaIA_X_Protocol.mqh
//+------------------------------------------------------------------+
//|                                            BortolaIA_X_License.mqh|
//|  Validacao de licenca individual por usuario / conta MT5          |
//+------------------------------------------------------------------+
#property strict

//--- Cada usuario cola AQUI a chave gerada no painel (formato EA-XXXX-XXXX-XXXX)
input string  InpLicenseKey        = "";     // Sua chave de licenca
input int     InpSRNiveisDist      = 2500;   // SR Niveis — distancia em pontos
input int     InpParcialPercent    = 50;     // Parcial — percentual do lote a fechar
input ENUM_TIMEFRAMES InpBotaoTF1  = PERIOD_M1;  // Botao TF1
input ENUM_TIMEFRAMES InpBotaoTF2  = PERIOD_M5;  // Botao TF2
input ENUM_TIMEFRAMES InpBotaoTF3  = PERIOD_M15; // Botao TF3

//--- Constantes internas (nao aparecem nos inputs do EA)
const bool InpBloquearSeInvalida = true;

string BASE_URL = "https://bortolaia.com.br";

//+------------------------------------------------------------------+
//| Gera o HMAC-SHA256 para seguranca da transmissao                 |
//+------------------------------------------------------------------+
void StringToUtf8NoNull(const string value, char &buffer[])
{
   StringToCharArray(value, buffer, 0, WHOLE_ARRAY, CP_UTF8);
   while(ArraySize(buffer) > 0 && buffer[ArraySize(buffer)-1] == 0)
      ArrayResize(buffer, ArraySize(buffer)-1);
}

string Sha256Hex(const string value)
{
   char source[];
   uchar data[], key[], hash[];
   StringToUtf8NoNull(value, source);
   ArrayResize(data, ArraySize(source));
   for(int i=0; i<ArraySize(source); i++) data[i] = (uchar)source[i];
   ArrayResize(key, 0);
   int res = CryptEncode(CRYPT_HASH_SHA256, data, key, hash);
   if(res <= 0) return "";

   string hex = "";
   for(int i=0; i<ArraySize(hash); i++) hex += StringFormat("%02x", hash[i]);
   return hex;
}

string ApiSignature(const string body)
{
   return Sha256Hex(body + InpLicenseKey);
}


//--- Estado global da licenca
bool   g_licenca_ok      = false;
string g_plano           = "";
int    g_dias_restantes  = -1;
datetime g_ultima_checagem = 0;

//+------------------------------------------------------------------+
//| Extrai um valor simples de um JSON plano                         |
//+------------------------------------------------------------------+
string JsonGet(const string json, const string campo)
{
   string alvo = "\"" + campo + "\":";
   int p = StringFind(json, alvo);
   if(p < 0) return "";
   p += StringLen(alvo);
   while(p < StringLen(json) && (StringGetCharacter(json, p) == ' ' ||
         StringGetCharacter(json, p) == '"')) p++;
   int fim = p;
   while(fim < StringLen(json))
   {
      ushort c = StringGetCharacter(json, fim);
      if(c == ',' || c == '}' || c == '"') break;
      fim++;
   }
   return StringSubstr(json, p, fim - p);
}

//+------------------------------------------------------------------+
//| Valida a licenca do usuario contra o servidor BortolaIA-X        |
//| Vincula automaticamente a conta MT5 atual a chave informada.     |
//+------------------------------------------------------------------+
bool ValidarLicenca()
{
   if(StringLen(InpLicenseKey) < 15)
   {
      Print("[BortolaIA-X] ERRO: Chave de licenca invalida ou ausente. Insira sua chave nos parametros (ex: EA-XXXX-XXXX-XXXX).");
      return false;
   }

   long   conta  = AccountInfoInteger(ACCOUNT_LOGIN);
   string broker = AccountInfoString(ACCOUNT_COMPANY);
   
   // Device Fingerprint para seguranca extra (bloqueio por hardware)
   string hardware = TerminalInfoString(TERMINAL_CPU_NAME) + TerminalInfoString(TERMINAL_PATH);

   // Payload canonico: evita problemas de aspas, barras e  no WebRequest do MT5.
   string body = "key=" + InpLicenseKey
               + "&account=" + (string)conta
               + "&broker=" + broker
               + "&hardware_id=" + hardware;

   char post[], result[];
   string result_headers;
   string signature = ApiSignature(body);
   string headers = "Content-Type: application/x-www-form-urlencoded\r\n" +
                  "X-Signature: " + signature + "\r\n";

   StringToUtf8NoNull(body, post);
   ResetLastError();

   int code = WebRequest("POST", BASE_URL + "/api/public/license/validate",
                         headers, 8000, post, result, result_headers);

   if(code == -1)
   {
      int err = GetLastError();
      Print("[BortolaIA-X] WebRequest falhou (erro ", err, ").");
      Print("Adicione ", BASE_URL, " em Ferramentas > Opcoes > Expert Advisors.");
      return false;
   }

   string resposta = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);

   if(JsonGet(resposta, "valid") != "true")
   {
      Print("[BortolaIA-X] Licenca recusada: ", JsonGet(resposta, "message"),
            " (codigo: ", JsonGet(resposta, "code"), ")");
      return false;
   }

   g_plano          = JsonGet(resposta, "plan");
   g_dias_restantes = (int)StringToInteger(JsonGet(resposta, "days_left"));
   g_ultima_checagem = TimeCurrent();

   Print("[BortolaIA-X] Licenca ATIVA | Conta: ", conta,
         " | Plano: ", g_plano,
         " | Dias restantes: ", g_dias_restantes);
   return true;
}

//+------------------------------------------------------------------+
//| OnInit — bloqueia o EA se a licenca nao for valida               |
//+------------------------------------------------------------------+
int OnInit()
{
   g_licenca_ok = ValidarLicenca();


   if(!g_licenca_ok && InpBloquearSeInvalida)
   {
      Print("[BortolaIA-X] Licenca invalida para esta conta. EA nao sera iniciado.");
      return(INIT_FAILED);
   }

   EventSetTimer(3600); // revalida a cada 1 hora
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Revalidacao periodica (expiracao / bloqueio remoto)              |
//+------------------------------------------------------------------+
void OnTimer()
{
   if(!ValidarLicenca() && InpBloquearSeInvalida)
   {
      Print("[BortolaIA-X] Licenca revogada ou expirada. Encerrando o EA.");
      ExpertRemove();
   }
}

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

Sincronização de Dados

Data_Sync_Logic.mq5
//--- Telemetria (Protocolo de Envio)
//+------------------------------------------------------------------+
//| Envio de telemetria de operacoes para o painel BortolaIA-X       |
//| Chame apos o fechamento de cada posicao.                         |
//+------------------------------------------------------------------+
string IsoTime(datetime t)
{
   MqlDateTime d;
   TimeToStruct(t, d);
   return StringFormat("%04d-%02d-%02dT%02d:%02d:%02d.000Z",
                       d.year, d.mon, d.day, d.hour, d.min, d.sec);
}

void EnviarTelemetria(ulong ticket, string symbol, string tipo, double volume,
                      double preco_abertura, double preco_fechamento,
                      double lucro, double comissao, double swap,
                      datetime abertura, datetime fechamento)
{
   long conta = AccountInfoInteger(ACCOUNT_LOGIN);
   string moeda = AccountInfoString(ACCOUNT_CURRENCY);
   if(StringLen(moeda) == 0) moeda = "USD";

   string body = "key=" + InpLicenseKey
      + "&account=" + (string)conta
      + "&ticket=" + (string)ticket
      + "&symbol=" + symbol
      + "&type=" + tipo
      + "&volume=" + DoubleToString(volume, 2)
      + "&open_price=" + DoubleToString(preco_abertura, 5)
      + "&close_price=" + DoubleToString(preco_fechamento, 5)
      + "&profit=" + DoubleToString(lucro, 2)
      + "&commission=" + DoubleToString(comissao, 2)
      + "&swap=" + DoubleToString(swap, 2)
      + "&currency=" + moeda
      + "&open_time=" + IsoTime(abertura)
      + "&close_time=" + IsoTime(fechamento);

   char post[], result[];
   string result_headers;
   string signature = ApiSignature(body);
   string headers = "Content-Type: application/x-www-form-urlencoded\r\n" +
                  "X-Signature: " + signature + "\r\n";

   StringToUtf8NoNull(body, post);

   int code = WebRequest("POST", BASE_URL + "/api/public/telemetry",
                         headers, 8000, post, result, result_headers);

   if(code != 200)
      Print("[BortolaIA-X] Falha ao sincronizar operacao ", ticket, " (HTTP ", code, ")");
}


//--- Sincronização (Lógica de Varredura de Histórico)
void SincronizarHistorico()
{
   datetime de = (g_ultimo_envio == 0) ? 0 : g_ultimo_envio;
   if(!HistorySelect(de, TimeCurrent())) return;

   int total = HistoryDealsTotal();
   for(int i = 0; i < total; i++)
   {
      ulong deal = HistoryDealGetTicket(i);
      if(deal == 0) continue;

      long entry = HistoryDealGetInteger(deal, DEAL_ENTRY);
      if(entry != DEAL_ENTRY_OUT && entry != DEAL_ENTRY_OUT_BY && entry != DEAL_ENTRY_INOUT) continue;

      datetime fechamento = (datetime)HistoryDealGetInteger(deal, DEAL_TIME);
      if(g_ultimo_envio > 0 && fechamento <= g_ultimo_envio) continue;

      string simbolo = HistoryDealGetString(deal, DEAL_SYMBOL);
      long   tipo    = HistoryDealGetInteger(deal, DEAL_TYPE);
      double volume  = HistoryDealGetDouble(deal, DEAL_VOLUME);
      double preco_fechamento = HistoryDealGetDouble(deal, DEAL_PRICE);
      double lucro   = HistoryDealGetDouble(deal, DEAL_PROFIT);
      double com     = HistoryDealGetDouble(deal, DEAL_COMMISSION);
      double swap    = HistoryDealGetDouble(deal, DEAL_SWAP);

      long pos_id = HistoryDealGetInteger(deal, DEAL_POSITION_ID);
      double preco_abertura = preco_fechamento;
      datetime abertura = fechamento;
      double comissao_total = com;

      if(pos_id > 0 && HistorySelectByPosition(pos_id))
      {
         comissao_total = 0;
         int deals_pos = HistoryDealsTotal();
         for(int d = 0; d < deals_pos; d++)
         {
            ulong d_ticket = HistoryDealGetTicket(d);
            if(d_ticket == 0) continue;
            comissao_total += HistoryDealGetDouble(d_ticket, DEAL_COMMISSION);
            if(HistoryDealGetInteger(d_ticket, DEAL_ENTRY) == DEAL_ENTRY_IN)
            {
               preco_abertura = HistoryDealGetDouble(d_ticket, DEAL_PRICE);
               abertura = (datetime)HistoryDealGetInteger(d_ticket, DEAL_TIME);
            }
         }
         HistorySelect(de, TimeCurrent());
      }

      string lado = (tipo == DEAL_TYPE_SELL) ? "buy" : "sell";

      EnviarTelemetria(deal, simbolo, lado, volume, preco_abertura, preco_fechamento,
                       lucro, comissao_total, swap, abertura, fechamento);
   }
   g_ultimo_envio = TimeCurrent();
}

4. Expert Advisor Full Stack

O BortolaIA-X completo, integrado e pronto para o gráfico.

BortolaIA_X_Full_Expert.mq5
//+------------------------------------------------------------------+
//|                                                  BortolaIA_X.mq5 |
//|   Expert Advisor com licenciamento individual + telemetria       |
//|   Copie este arquivo para MQL5/Experts e compile no MetaEditor.  |
//+------------------------------------------------------------------+
#property copyright "BortolaIA-X"
#property link      "https://bortolaia.com.br"
#property version   "1.00"
#property strict

//==================== PARAMETROS DE ENTRADA ========================
input string  InpLicenseKey        = "";     // Sua chave de licenca
input int     InpSRNiveisDist      = 2500;   // SR Niveis — distancia em pontos
input int     InpParcialPercent    = 50;     // Parcial — percentual do lote a fechar
input ENUM_TIMEFRAMES InpBotaoTF1  = PERIOD_M1;  // Botao TF1
input ENUM_TIMEFRAMES InpBotaoTF2  = PERIOD_M5;  // Botao TF2
input ENUM_TIMEFRAMES InpBotaoTF3  = PERIOD_M15; // Botao TF3

//--- Constantes internas (nao aparecem nos inputs do EA)
const bool InpBloquearSeInvalida = true;
const bool InpEnviarTelemetria   = true;
const int  InpIntervaloCheckMin  = 60;

const string BASE_URL     = "https://bortolaia.com.br";
const string FALLBACK_URL = "https://project--2c8a9427-3bac-401e-bb7e-6883fc60121c.lovable.app";

//+------------------------------------------------------------------+
//| Gera o HMAC-SHA256 para seguranca da transmissao                 |
//+------------------------------------------------------------------+
void StringToUtf8NoNull(const string value, char &buffer[])
{
   StringToCharArray(value, buffer, 0, WHOLE_ARRAY, CP_UTF8);
   while(ArraySize(buffer) > 0 && buffer[ArraySize(buffer)-1] == 0)
      ArrayResize(buffer, ArraySize(buffer)-1);
}

string Sha256Hex(const string value)
{
   char source[];
   uchar data[], key[], hash[];
   StringToUtf8NoNull(value, source);
   ArrayResize(data, ArraySize(source));
   for(int i=0; i<ArraySize(source); i++) data[i] = (uchar)source[i];
   ArrayResize(key, 0);
   int res = CryptEncode(CRYPT_HASH_SHA256, data, key, hash);
   if(res <= 0) return "";

   string hex = "";
   for(int i=0; i<ArraySize(hash); i++) hex += StringFormat("%02x", hash[i]);
   return hex;
}

string ApiSignature(const string body)
{
   return Sha256Hex(body + InpLicenseKey);
}


//==================== ESTADO GLOBAL ================================
bool     g_licenca_ok       = false;
string   g_plano            = "";
int      g_dias_restantes   = -1;
datetime g_ultimo_envio     = 0;

//+------------------------------------------------------------------+
//| Leitor minimalista de JSON plano (chave -> valor)                |
//+------------------------------------------------------------------+
string JsonGet(const string json, const string campo)
{
   string alvo = "\"" + campo + "\":";
   int p = StringFind(json, alvo);
   if(p < 0) return "";
   p += StringLen(alvo);
   while(p < StringLen(json) &&
        (StringGetCharacter(json,p) == ' ' || StringGetCharacter(json,p) == '"')) p++;
   int fim = p;
   while(fim < StringLen(json))
   {
      ushort c = StringGetCharacter(json, fim);
      if(c == ',' || c == '}' || c == '"') break;
      fim++;
   }
   return StringSubstr(json, p, fim - p);
}

//+------------------------------------------------------------------+
//| Codifica valores para o corpo form-urlencoded                    |
//+------------------------------------------------------------------+
string UrlEncode(const string valor)
{
   char bytes[];
   StringToUtf8NoNull(valor, bytes);
   string saida = "";
   for(int i = 0; i < ArraySize(bytes); i++)
   {
      uchar c = (uchar)bytes[i];
      bool seguro = (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
                    (c >= 'a' && c <= 'z') || c == '-' || c == '_' ||
                    c == '.' || c == '~';
      if(seguro) saida += CharToString((uchar)c);
      else       saida += StringFormat("%%%02X", c);
   }
   return saida;
}

//+------------------------------------------------------------------+
//| POST form-urlencoded generico com tratamento de WebRequest       |
//| Tenta o dominio principal e cai para o dominio alternativo,      |
//| evitando os erros internos 1001/1003 (redirecionamento/SSL).     |
//+------------------------------------------------------------------+
int PostFormEm(const string base, const string endpoint, const string body,
               string &resposta)
{
   char post[], result[];
   string result_headers;
   string signature = ApiSignature(body);
   string headers = "Content-Type: application/x-www-form-urlencoded\r\n" +
                  "X-Signature: " + signature + "\r\n";

   StringToUtf8NoNull(body, post);
   ResetLastError();

   int code = WebRequest("POST", base + endpoint, headers, 8000,
                         post, result, result_headers);

   resposta = (code > 0) ? CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8) : "";
   return code;
}

int PostForm(const string endpoint, const string body, string &resposta)
{
   int code = PostFormEm(BASE_URL, endpoint, body, resposta);

   // -1 = URL nao autorizada | >=1000 = erro interno do WebRequest (rede/redirect)
   if(code == -1 || code >= 1000)
   {
      int primeiro = code;
      code = PostFormEm(FALLBACK_URL, endpoint, body, resposta);

      if(code == -1 || code >= 1000)
      {
         Print("[BortolaIA-X] Sem conexao com o painel (codigo ", primeiro,
               " / ", code, ", erro ", GetLastError(), ").");
         Print("[BortolaIA-X] Autorize EXATAMENTE estas duas URLs em Ferramentas > ",
               "Opcoes > Expert Advisors > WebRequest: ", BASE_URL, " e ", FALLBACK_URL);
         resposta = "";
         return -1;
      }
   }

   return code;
}

//+------------------------------------------------------------------+
//| Converte datetime para ISO-8601 UTC (exigido pela API)           |
//+------------------------------------------------------------------+
string IsoTime(datetime t)
{
   MqlDateTime d;
   TimeToStruct(t, d);
   return StringFormat("%04d-%02d-%02dT%02d:%02d:%02d.000Z",
                       d.year, d.mon, d.day, d.hour, d.min, d.sec);
}

//+------------------------------------------------------------------+
//| Valida a licenca desta conta MT5 contra o servidor               |
//+------------------------------------------------------------------+
bool ValidarLicenca()
{
   if(StringLen(InpLicenseKey) < 15)
   {
      Print("[BortolaIA-X] ERRO: Chave de licenca invalida ou ausente. Insira sua chave nos parametros (ex: EA-XXXX-XXXX-XXXX).");
      return false;
   }

   long   conta  = AccountInfoInteger(ACCOUNT_LOGIN);
   string broker = AccountInfoString(ACCOUNT_COMPANY);
   string hardware = TerminalInfoString(TERMINAL_CPU_NAME) + TerminalInfoString(TERMINAL_PATH);

   string body = "key=" + InpLicenseKey
               + "&account=" + (string)conta
               + "&broker=" + UrlEncode(broker)
               + "&hardware_id=" + UrlEncode(hardware);

   string resposta = "";
   int code = PostForm("/api/public/license/validate", body, resposta);
   if(code < 0) return false;

   if(JsonGet(resposta, "valid") != "true")
   {
      Print("[BortolaIA-X] Licenca recusada: ", JsonGet(resposta, "message"),
            " (codigo: ", JsonGet(resposta, "code"), ")");
      return false;
   }

   g_plano          = JsonGet(resposta, "plan");
   g_dias_restantes = (int)StringToInteger(JsonGet(resposta, "days_left"));

   Print("[BortolaIA-X] Licenca ATIVA | Conta ", conta,
         " | Plano: ", g_plano, " | Dias restantes: ", g_dias_restantes);
   return true;
}

//+------------------------------------------------------------------+
//| Envia uma operacao fechada para o painel                         |
//+------------------------------------------------------------------+
void EnviarTelemetria(ulong ticket, string simbolo, string tipo, double volume,
                      double preco_abertura, double preco_fechamento,
                      double lucro, double comissao, double swap,
                      datetime abertura, datetime fechamento)
{
   if(!InpEnviarTelemetria) return;

   long conta = AccountInfoInteger(ACCOUNT_LOGIN);
   string moeda = AccountInfoString(ACCOUNT_CURRENCY);
   if(StringLen(moeda) == 0) moeda = "USD";

   string body = "key=" + InpLicenseKey
      + "&account=" + (string)conta
      + "&ticket=" + (string)ticket
      + "&symbol=" + UrlEncode(simbolo)
      + "&type=" + tipo
      + "&volume=" + DoubleToString(volume, 2)
      + "&open_price=" + DoubleToString(preco_abertura, 5)
      + "&close_price=" + DoubleToString(preco_fechamento, 5)
      + "&profit=" + DoubleToString(lucro, 2)
      + "&commission=" + DoubleToString(comissao, 2)
      + "&swap=" + DoubleToString(swap, 2)
      + "&currency=" + moeda
      + "&open_time=" + IsoTime(abertura)
      + "&close_time=" + IsoTime(fechamento);

   string resposta = "";
   int code = PostForm("/api/public/telemetry", body, resposta);

   if(code != 200)
      Print("[BortolaIA-X] Falha ao sincronizar operacao ", ticket,
            " (HTTP ", code, ") ", resposta);
}

//+------------------------------------------------------------------+
//| Varre o historico recente e envia os negocios de saida           |
//+------------------------------------------------------------------+
void SincronizarHistorico()
{
   // Na primeira execucao, varre todo o historico da conta (de = 0)
   // para garantir 100% de precisao no saldo realizado total.
   datetime de = (g_ultimo_envio == 0) ? 0 : g_ultimo_envio;
   if(!HistorySelect(de, TimeCurrent())) return;

   int total = HistoryDealsTotal();
   for(int i = 0; i < total; i++)
   {
      ulong deal = HistoryDealGetTicket(i);
      if(deal == 0) continue;

      long entry = HistoryDealGetInteger(deal, DEAL_ENTRY);
      // Processa negocios de fechamento ou inversao de lote
      if(entry != DEAL_ENTRY_OUT && entry != DEAL_ENTRY_OUT_BY && entry != DEAL_ENTRY_INOUT) continue;

      datetime fechamento = (datetime)HistoryDealGetInteger(deal, DEAL_TIME);
      if(g_ultimo_envio > 0 && fechamento <= g_ultimo_envio) continue;

      string simbolo = HistoryDealGetString(deal, DEAL_SYMBOL);
      long   tipo    = HistoryDealGetInteger(deal, DEAL_TYPE);
      double volume  = HistoryDealGetDouble(deal, DEAL_VOLUME);
      double preco_fechamento = HistoryDealGetDouble(deal, DEAL_PRICE);
      double lucro   = HistoryDealGetDouble(deal, DEAL_PROFIT);
      double com     = HistoryDealGetDouble(deal, DEAL_COMMISSION);
      double swap    = HistoryDealGetDouble(deal, DEAL_SWAP);

      // Extrai dados da posicao (preco/data de abertura e comissao total da operacao)
      long pos_id = HistoryDealGetInteger(deal, DEAL_POSITION_ID);
      double preco_abertura = preco_fechamento;
      datetime abertura = fechamento;
      double comissao_total = com;

      if(pos_id > 0 && HistorySelectByPosition(pos_id))
      {
         comissao_total = 0;
         int deals_pos = HistoryDealsTotal();
         for(int d = 0; d < deals_pos; d++)
         {
            ulong d_ticket = HistoryDealGetTicket(d);
            if(d_ticket == 0) continue;
            comissao_total += HistoryDealGetDouble(d_ticket, DEAL_COMMISSION);
            if(HistoryDealGetInteger(d_ticket, DEAL_ENTRY) == DEAL_ENTRY_IN)
            {
               preco_abertura = HistoryDealGetDouble(d_ticket, DEAL_PRICE);
               abertura = (datetime)HistoryDealGetInteger(d_ticket, DEAL_TIME);
            }
         }
         // Restaura a selecao principal do lote
         HistorySelect(de, TimeCurrent());
      }

      // O tipo do negocio de saida e invertido em relacao a posicao original
      string lado = (tipo == DEAL_TYPE_SELL) ? "buy" : "sell";

      EnviarTelemetria(deal, simbolo, lado, volume, preco_abertura, preco_fechamento,
                       lucro, comissao_total, swap, abertura, fechamento);
   }
   g_ultimo_envio = TimeCurrent();
}

//+------------------------------------------------------------------+
int OnInit()
{
   // Carimbo de build: confirma no log que a versao nova do arquivo esta rodando.
   Print("[BortolaIA-X] Build 2026.09.17-a | URL principal: ", BASE_URL,
         " | alternativa: ", FALLBACK_URL);

   g_licenca_ok = ValidarLicenca();

   if(!g_licenca_ok && InpBloquearSeInvalida)
   {
      Print("[BortolaIA-X] Licenca invalida para esta conta. O EA nao sera iniciado.");
      return(INIT_FAILED);
   }

   // Executa sincronizacao inicial de historico ao carregar o EA
   if(g_licenca_ok) SincronizarHistorico();

   EventSetTimer(MathMax(60, InpIntervaloCheckMin * 60));
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
void OnTimer()
{
   g_licenca_ok = ValidarLicenca();

   if(!g_licenca_ok && InpBloquearSeInvalida)
   {
      Print("[BortolaIA-X] Licenca revogada ou expirada. Encerrando o EA.");
      ExpertRemove();
      return;
   }
   SincronizarHistorico();
}

//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
{
   if(!g_licenca_ok) return;
   if(trans.type == TRADE_TRANSACTION_DEAL_ADD) SincronizarHistorico();
}

//+------------------------------------------------------------------+
void OnTick()
{
   if(!g_licenca_ok) return;

   // >>> Insira aqui a logica de trading do seu Expert Advisor <<<
}

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   EventKillTimer();
}
//+------------------------------------------------------------------+