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)
{
   return TimeToString(t, TIME_DATE) + "T" +
          TimeToString(t, TIME_SECONDS) + "Z";
}

void EnviarTelemetria(long ticket, string symbol, string tipo, double volume,
                      double preco_abertura, double preco_fechamento,
                      double lucro, datetime abertura, datetime fechamento)
{
   long conta = AccountInfoInteger(ACCOUNT_LOGIN);

   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)
      + "&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, 5000, 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) ? TimeCurrent() - 86400 : 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;

      if(HistoryDealGetInteger(deal, DEAL_ENTRY) != DEAL_ENTRY_OUT) continue;

      datetime fechamento = (datetime)HistoryDealGetInteger(deal, DEAL_TIME);
      if(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   = HistoryDealGetDouble(deal, DEAL_PRICE);
      double lucro   = HistoryDealGetDouble(deal, DEAL_PROFIT);

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

      EnviarTelemetria(deal, simbolo, lado, volume, preco, preco,
                       lucro, fechamento, 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";

//+------------------------------------------------------------------+
//| 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);
}

//+------------------------------------------------------------------+
//| POST form-urlencoded generico com tratamento de WebRequest       |
//+------------------------------------------------------------------+
int PostForm(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_URL + endpoint, headers, 8000,
                         post, result, result_headers);

   if(code == -1)
   {
      Print("[BortolaIA-X] WebRequest bloqueado (erro ", GetLastError(), ").");
      Print("[BortolaIA-X] Autorize ", BASE_URL,
            " em Ferramentas > Opcoes > Expert Advisors > WebRequest.");
      resposta = "";
      return -1;
   }

   resposta = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);
   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=" + broker
               + "&hardware_id=" + 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 body = "key=" + InpLicenseKey
      + "&account=" + (string)conta
      + "&ticket=" + (string)ticket
      + "&symbol=" + 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)
      + "&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()
{
   datetime de = (g_ultimo_envio == 0) ? TimeCurrent() - 86400 : 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;

      // Apenas negocios que ENCERRAM posicao possuem resultado financeiro
      if(HistoryDealGetInteger(deal, DEAL_ENTRY) != DEAL_ENTRY_OUT) continue;

      datetime fechamento = (datetime)HistoryDealGetInteger(deal, DEAL_TIME);
      if(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   = HistoryDealGetDouble(deal, DEAL_PRICE);
      double lucro   = HistoryDealGetDouble(deal, DEAL_PROFIT);
      double com     = HistoryDealGetDouble(deal, DEAL_COMMISSION);
      double swap    = HistoryDealGetDouble(deal, DEAL_SWAP);

      // 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, preco,
                       lucro, com, swap, fechamento, fechamento);
   }
   g_ultimo_envio = TimeCurrent();
}

//+------------------------------------------------------------------+
int OnInit()
{
   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);
   }

   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();
}
//+------------------------------------------------------------------+