DEV Community

Cover image for Guia Completo: Construindo um Bot Seguro de Integração entre Telegram e MetaTrader5 em Python
Henrique Vital
Henrique Vital

Posted on

Guia Completo: Construindo um Bot Seguro de Integração entre Telegram e MetaTrader5 em Python

Bem-vindo! Este guia é projetado desenvolvedores intusiastas em Python que desejam criar um bot que monitora mensagens no Telegram e interage com o MetaTrader5 (MT5) para executar ordens de negociação com base nos sinais recebidos. Vamos guiá-lo por todo o processo, desde a configuração do ambiente até a compreensão detalhada de cada parte do código.


Índice

  1. Introdução
  2. Pré-requisitos
  3. Configurando o Ambiente
  4. Criando o Arquivo .env
  5. Entendendo o Script Python
  6. Executando o Script
  7. Boas Práticas e Dicas
  8. Conclusão

Introdução

Neste projeto, você criará um bot em Python que realiza as seguintes tarefas:

  1. Monitora um Grupo no Telegram: O bot monitora mensagens em um grupo específico do Telegram em busca de sinais de negociação (por exemplo, "buy US30").

  2. Processa Sinais: Quando um sinal é detectado, o bot interpreta para determinar qual ativo negociar e se deve comprar ou vender.

  3. Interage com o MetaTrader5 (MT5): Com base no sinal, o bot envia ordens de negociação para sua conta MT5.

  4. Mantém Práticas de Segurança: Informações sensíveis, como chaves de API e senhas, são gerenciadas de forma segura usando variáveis de ambiente.

Ao final deste guia, você terá um bot funcional e seguro que automatiza ações de negociação com base em mensagens do Telegram.


Pré-requisitos

Antes de começar, certifique-se de ter o seguinte:

  1. Conhecimento Básico de Python: Familiaridade com a sintaxe do Python e conceitos como funções, loops e módulos.

  2. Python Instalado: Verifique se você tem o Python 3.7 ou posterior instalado em seu computador. Você pode baixá-lo do site oficial do Python.

  3. Conta no Telegram: Você precisará acessar a API do Telegram. Registre sua aplicação aqui para obter o API_ID e o API_HASH.

  4. Conta no MetaTrader5: Acesse uma conta MT5, incluindo credenciais de login e informações do servidor.

  5. Entendimento de Ambientes Virtuais: Embora opcional, usar ambientes virtuais é recomendado para gerenciar dependências.


Configurando o Ambiente

1. Criar um Diretório para o Projeto

Escolha um local em seu computador e crie um novo diretório para o seu projeto. Por exemplo:

mkdir telegram_mt5_bot
cd telegram_mt5_bot
Enter fullscreen mode Exit fullscreen mode

2. Configurar um Ambiente Virtual (Opcional, mas Recomendado)

Um ambiente virtual isola as dependências do seu projeto das de outros projetos Python em seu sistema.

  • Criar um Ambiente Virtual:

    python -m venv venv
    

    Este comando cria um ambiente virtual chamado venv dentro do diretório do seu projeto.

  • Ativar o Ambiente Virtual:

    • No Windows:

      venv\Scripts\activate
      
    • No macOS e Linux:

      source venv/bin/activate
      

    Após a ativação, o nome do ambiente virtual aparecerá no prompt do terminal, indicando que as dependências instaladas agora serão isoladas para este projeto.

3. Instalar Dependências Necessárias

Com o ambiente virtual ativado, instale as bibliotecas necessárias usando o pip:

pip install telethon MetaTrader5 python-dotenv
Enter fullscreen mode Exit fullscreen mode
  • telethon: Biblioteca para interagir com a API do Telegram.
  • MetaTrader5: Biblioteca para interagir com o MetaTrader5.
  • python-dotenv: Biblioteca para carregar variáveis de ambiente a partir de um arquivo .env.

Além disso, para facilitar o gerenciamento de pacotes, é recomendável criar um arquivo requirements.txt:

pip freeze > requirements.txt
Enter fullscreen mode Exit fullscreen mode

Este arquivo registra todas as dependências do projeto, permitindo que outras pessoas (ou você mesmo em outra máquina) instalem facilmente as mesmas versões com:

pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

Criando o Arquivo .env

Para gerenciar informações sensíveis de forma segura, usaremos um arquivo .env que armazenará variáveis de ambiente. Este arquivo não deve ser versionado ou compartilhado.

1. Criar o Arquivo .env

No diretório raiz do seu projeto (telegram_mt5_bot), crie um arquivo chamado .env e adicione as seguintes linhas, substituindo os valores pelos seus próprios:

# Credenciais do Telegram
TELEGRAM_API_ID=seu_api_id
TELEGRAM_API_HASH=seu_api_hash
TELEGRAM_PHONE_NUMBER=seu_numero_de_telefone
TELEGRAM_GROUP_USERNAME=@superus30

# Credenciais do MetaTrader5 (MT5)
MT5_LOGIN=seu_login_mt5
MT5_PASSWORD=sua_senha_mt5
MT5_SERVER=seu_servidor_mt5
MT5_PATH=C:\Caminho\Para\Seu\MetaTrader5\terminal64.exe

# Configuração de Logging
LOG_FILE=app.log
Enter fullscreen mode Exit fullscreen mode

Notas Importantes:

  • TELEGRAM_API_ID e TELEGRAM_API_HASH: Obtidos ao registrar sua aplicação no Telegram.
  • TELEGRAM_PHONE_NUMBER: Número de telefone associado à sua conta do Telegram.
  • TELEGRAM_GROUP_USERNAME: Nome de usuário do grupo Telegram que o bot irá monitorar.
  • MT5_LOGIN, MT5_PASSWORD, MT5_SERVER: Informações da sua conta MT5.
  • MT5_PATH: Caminho para o executável do MetaTrader5 no seu sistema.

2. Atualizar o Arquivo .gitignore

Para garantir que o arquivo .env não seja versionado (especialmente se você estiver usando o Git), crie ou atualize o arquivo .gitignore no diretório raiz com o seguinte conteúdo:

# Arquivo de variáveis de ambiente
.env

# Diretório do ambiente virtual
venv/
Enter fullscreen mode Exit fullscreen mode

Isso impede que informações sensíveis sejam acidentalmente compartilhadas em repositórios públicos ou privados.


Entendendo o Script Python

Agora, vamos criar o script Python que integrará o Telegram com o MetaTrader5. Este script realizará as seguintes funções:

  1. Carregar Variáveis de Ambiente: Utiliza o python-dotenv para carregar credenciais e configurações do arquivo .env.

  2. Configurar Logging: Define como e onde os logs serão armazenados, facilitando o monitoramento e a depuração.

  3. Configurar Contas MT5: Define as contas MT5 que serão utilizadas pelo bot.

  4. Funções de Reconexão e Envio de Ordens: Gerencia a conexão com o MT5 e envia ordens de negociação com base nos sinais recebidos.

  5. Processar Sinais do Telegram: Interpreta mensagens do Telegram para determinar as ações de negociação.

  6. Monitorar Conexões: Verifica periodicamente se as conexões com o MT5 estão ativas e tenta reconectar se necessário.

  7. Manipular Sinais de Encerramento: Garante que o bot encerre suas operações de forma suave ao receber sinais de interrupção.

  8. Função Principal (main): Orquestra todas as operações acima, iniciando o cliente Telegram e gerenciando as tarefas assíncronas.

Vamos explorar cada parte do código com detalhes.

1. Imports e Variáveis de Ambiente

Primeiro, importamos todas as bibliotecas necessárias e carregamos as variáveis de ambiente.

import os
import sys
import asyncio
import logging
import signal
import pkg_resources
from datetime import datetime
from dotenv import load_dotenv
from telethon import TelegramClient, events
import MetaTrader5 as mt5
Enter fullscreen mode Exit fullscreen mode

Explicação dos Imports:

  • os: Interage com o sistema operacional, acessando variáveis de ambiente e caminhos de arquivos.
  • sys: Fornece acesso a variáveis e funções que interagem com o interpretador Python.
  • asyncio: Facilita a escrita de código assíncrono, permitindo que múltiplas tarefas sejam executadas concorrentemente.
  • logging: Gerencia o registro de mensagens de log para monitoramento e depuração.
  • signal: Permite a captura de sinais do sistema (como interrupções) para realizar ações específicas.
  • pkg_resources: Gerencia recursos de pacotes Python, aqui utilizado para listar pacotes instalados.
  • datetime: Trabalha com datas e horas.
  • dotenv: Carrega variáveis de ambiente de um arquivo .env.
  • Telethon: Biblioteca para interagir com a API do Telegram de forma assíncrona.
  • MetaTrader5: Biblioteca para interagir com o MetaTrader5.

Carregando Variáveis de Ambiente:

# Carrega variáveis de ambiente do arquivo .env
load_dotenv()
Enter fullscreen mode Exit fullscreen mode

Esta linha lê o arquivo .env e carrega as variáveis de ambiente nele definidas, permitindo que sejam acessadas via os.getenv().

2. Configuração de Logging

O logging é essencial para monitorar o funcionamento do seu bot e depurar problemas. Vamos configurar o logging para registrar mensagens tanto no console quanto em um arquivo.

# Acessa as variáveis de ambiente para configuração
LOG_FILE = os.getenv('LOG_FILE', 'app.log')

# Configura o logging
logging.basicConfig(
    level=logging.INFO,  # Nível mínimo de severidade para registrar
    format='%(asctime)s - %(levelname)s - %(message)s',  # Formato das mensagens de log
    handlers=[
        logging.FileHandler(LOG_FILE),  # Registra logs em um arquivo
        logging.StreamHandler(sys.stdout)  # Exibe logs no console
    ]
)
logger = logging.getLogger(__name__)
Enter fullscreen mode Exit fullscreen mode

Detalhes da Configuração:

  • level: Define o nível mínimo de mensagens que serão registradas. INFO inclui INFO, WARNING, ERROR e CRITICAL.
  • format: Define como cada mensagem de log será formatada, incluindo o timestamp, o nível de severidade e a mensagem.
  • handlers: Define onde as mensagens de log serão enviadas. Aqui, estão sendo enviadas para um arquivo (app.log) e para o console.

3. Configuração das Contas MT5

Definimos as contas MT5 que o bot utilizará para enviar ordens de negociação.

# Configurações das contas MT5
CONTAS_MT5 = [
    {
        "login": os.getenv('MT5_LOGIN'),
        "senha": os.getenv('MT5_PASSWORD'),
        "servidor": os.getenv('MT5_SERVER'),
        "us30": "US30.cash",
        "nas100": "US100.cash",
        "lote": 0.01
    }
]

contas_ativas = []  # Lista para armazenar contas ativas
shutdown_event = asyncio.Event()  # Evento para sinalizar o encerramento do programa
Enter fullscreen mode Exit fullscreen mode

Explicação dos Campos:

  • login: Número de login da conta MT5.
  • senha: Senha da conta MT5.
  • servidor: Nome do servidor MT5.
  • us30 e nas100: Símbolos dos ativos que serão negociados.
  • lote: Volume padrão das ordens a serem enviadas.

4. Função de Reconexão com MT5

Esta função tenta reconectar à conta MT5 caso a conexão seja perdida.

async def reconectar_mt5(conta, max_tentativas=3):
    """
    Tenta reconectar à conta MT5 com um número máximo de tentativas.

    Args:
        conta (dict): Informações da conta MT5.
        max_tentativas (int): Número máximo de tentativas de reconexão.

    Returns:
        bool: True se a reconexão for bem-sucedida, False caso contrário.
    """
    for tentativa in range(max_tentativas):
        try:
            # Tenta inicializar a conexão com MT5
            if mt5.initialize(path=os.getenv('MT5_PATH'), login=int(conta['login']), server=conta['servidor'], password=conta['senha']):
                logger.info(f"Reconexão bem-sucedida para conta {conta['login']}")
                return True
            else:
                erro = mt5.last_error()
                logger.warning(f"Tentativa {tentativa + 1} de reconexão falhou para conta {conta['login']}: {erro}")
        except Exception as e:
            logger.error(f"Erro durante a tentativa {tentativa + 1} de reconexão para conta {conta['login']}: {e}")
        await asyncio.sleep(5)  # Espera 5 segundos antes de tentar novamente
    logger.error(f"Falha ao reconectar à conta {conta['login']} após {max_tentativas} tentativas")
    return False
Enter fullscreen mode Exit fullscreen mode

Detalhes da Função:

  • Objetivo: Garantir que o bot mantenha a conexão com o MT5, tentando reconectar em caso de falha.
  • Parâmetros:
    • conta: Dicionário contendo as informações da conta MT5.
    • max_tentativas: Número máximo de tentativas de reconexão antes de desistir.
  • Processo:
    • Tenta inicializar a conexão com MT5 usando as credenciais fornecidas.
    • Se falhar, registra um aviso e aguarda 5 segundos antes de tentar novamente.
    • Após exceder o número máximo de tentativas, registra um erro e retorna False.

5. Função para Enviar Ordens para MT5

Esta função envia ordens de negociação para o MT5 com base nos sinais processados.

async def enviar_ordem(conta, simbolo, acao, lote):
    """
    Envia uma ordem de negociação para o MT5.

    Args:
        conta (dict): Informações da conta MT5.
        simbolo (str): Símbolo do ativo a ser negociado.
        acao (int): Tipo de ação (compra ou venda).
        lote (float): Volume da ordem.

    Returns:
        bool: True se a ordem for enviada com sucesso, False caso contrário.
    """
    # Tenta reconectar à conta MT5 antes de enviar a ordem
    if not await reconectar_mt5(conta):
        logger.error(f"Não foi possível enviar ordem para {conta['login']} devido a falha na reconexão")
        return False

    # Obtém informações do símbolo
    symbol_info = mt5.symbol_info(simbolo)
    if symbol_info is None:
        logger.error(f"Símbolo {simbolo} não encontrado")
        return False

    # Verifica se o símbolo está disponível para trading
    if not symbol_info.visible:
        logger.warning(f"Símbolo {simbolo} não está visível, tentando habilitá-lo")
        if not mt5.symbol_select(simbolo, True):
            logger.error(f"Falha ao selecionar o símbolo {simbolo}")
            return False

    # Obtém o tick atual do símbolo
    tick = mt5.symbol_info_tick(simbolo)
    if tick is None:
        logger.error(f"Não foi possível obter o tick para o símbolo {simbolo}")
        return False

    # Define o preço baseado na ação (compra ou venda)
    price = tick.ask if acao == mt5.ORDER_TYPE_BUY else tick.bid

    # Prepara a estrutura do pedido
    pedido = {
        "action": mt5.TRADE_ACTION_DEAL,  # Tipo de ação de negociação
        "symbol": simbolo,                # Símbolo do ativo
        "volume": float(lote),            # Volume da ordem
        "type": acao,                     # Tipo de ordem (compra ou venda)
        "price": price,                   # Preço da ordem
        "deviation": 20,                  # Desvio permitido no preço
        "magic": 234000,                   # Identificador único para a ordem
        "comment": "python script order",  # Comentário para a ordem
        "type_time": mt5.ORDER_TIME_GTC,   # Tipo de tempo da ordem (Good Till Cancelled)
        "type_filling": mt5.ORDER_FILLING_IOC,  # Tipo de preenchimento (Immediate or Cancel)
    }

    # Envia a ordem para o MT5
    resultado = mt5.order_send(pedido)
    if resultado.retcode != mt5.TRADE_RETCODE_DONE:
        logger.error(f"Erro ao enviar ordem para {conta['login']}: {resultado.comment}")
        logger.debug(f"Detalhes do pedido: {pedido}")
        logger.debug(f"Código de retorno: {resultado.retcode}")
        return False
    else:
        logger.info(f"Ordem enviada com sucesso para {conta['login']} com lote {lote}")
        logger.debug(f"Detalhes da ordem: {resultado}")
        return True
Enter fullscreen mode Exit fullscreen mode

Detalhes da Função:

  • Objetivo: Enviar uma ordem de compra ou venda para o MT5 com base nos sinais recebidos.
  • Parâmetros:
    • conta: Informações da conta MT5.
    • simbolo: Símbolo do ativo a ser negociado (por exemplo, "US30.cash").
    • acao: Tipo de ação (mt5.ORDER_TYPE_BUY para compra ou mt5.ORDER_TYPE_SELL para venda).
    • lote: Volume da ordem (por exemplo, 0.01).
  • Processo:
    • Reconecta à conta MT5 se necessário.
    • Verifica se o símbolo está disponível e visível para negociação.
    • Obtém o preço atual do símbolo (ask para compra, bid para venda).
    • Prepara a estrutura do pedido com todos os parâmetros necessários.
    • Envia a ordem para o MT5 e verifica se foi bem-sucedida.

6. Processando Sinais do Telegram

Esta função interpreta mensagens recebidas no Telegram para determinar quais ações de negociação devem ser executadas.

async def processar_sinal(mensagem):
    """
    Processa uma mensagem recebida do Telegram para determinar a ação de negociação.

    Args:
        mensagem (str): Texto da mensagem recebida.

    """
    logger.info(f"Mensagem recebida do Telegram: {mensagem}")

    palavras = mensagem.lower().split()

    ativo = None
    acao = None

    # Verifica o ativo na mensagem
    if 'us30' in palavras:
        ativo = 'us30'
    elif 'nas100' in palavras:
        ativo = 'nas100'

    # Verifica a ação na mensagem
    if 'buy' in palavras:
        acao = mt5.ORDER_TYPE_BUY
    elif 'sell' in palavras:
        acao = mt5.ORDER_TYPE_SELL

    # Se nenhum ativo for reconhecido, encerra a função
    if not ativo:
        logger.info("Nenhum ativo reconhecido na mensagem.")
        return

    # Se nenhuma ação for reconhecida, encerra a função
    if acao is None:
        logger.info("Nenhuma ação (compra/venda) reconhecida na mensagem.")
        return

    acao_str = "COMPRA" se acao == mt5.ORDER_TYPE_BUY else "VENDA"
    logger.info(f"Interpretação: Ativo: {ativo.upper()}, Ação: {acao_str}")

    # Itera sobre todas as contas ativas para enviar a ordem
    for conta in contas_ativas[:]:  # Cria uma cópia da lista para iteração segura
        try:
            simbolo = conta.get(ativo)
            if not simbolo:
                logger.warning(f"Ativo {ativo} não configurado para a conta {conta['login']}. Pulando.")
                continue
            sucesso = await enviar_ordem(conta, simbolo, acao, conta['lote'])
            if not sucesso:
                logger.warning(f"Falha ao processar sinal para conta {conta['login']}. Removendo da lista de contas ativas.")
                contas_ativas.remove(conta)
        except Exception as e:
            logger.error(f"Erro ao processar sinal para conta {conta['login']}: {e}")
            logger.warning(f"Removendo conta {conta['login']} da lista de contas ativas devido a erro")
            contas_ativas.remove(conta)
Enter fullscreen mode Exit fullscreen mode

Detalhes da Função:

  • Objetivo: Interpretar a mensagem recebida do Telegram para determinar qual ativo negociar e se deve comprar ou vender.
  • Parâmetros:
    • mensagem: Texto da mensagem recebida no Telegram.
  • Processo:
    • Converte a mensagem para letras minúsculas e divide em palavras.
    • Identifica o ativo mencionado (us30 ou nas100).
    • Identifica a ação (buy para compra ou sell para venda).
    • Itera sobre todas as contas ativas e envia a ordem correspondente.
    • Se a ordem falhar, remove a conta da lista de contas ativas para evitar tentativas futuras.

7. Monitoramento de Conexões

Esta função verifica periodicamente se as conexões com as contas MT5 estão ativas e tenta reconectar se necessário.

async def verificar_conexoes():
    """
    Verifica periodicamente se as contas MT5 estão conectadas e tenta reconectar se necessário.
    """
    while not shutdown_event.is_set():
        for conta in contas_ativas[:]:
            if shutdown_event.is_set():
                break
            if not await reconectar_mt5(conta):
                logger.warning(f"Conta {conta['login']} removida da lista de contas ativas devido a falha na conexão")
                contas_ativas.remove(conta)
        await asyncio.sleep(60)  # Verifica a cada 60 segundos
Enter fullscreen mode Exit fullscreen mode

Detalhes da Função:

  • Objetivo: Garantir que todas as contas MT5 ativas mantenham uma conexão estável.
  • Processo:
    • Enquanto o evento de encerramento não for sinalizado, itera sobre todas as contas ativas.
    • Para cada conta, tenta reconectar.
    • Se a reconexão falhar após as tentativas definidas, remove a conta da lista de contas ativas.
    • Aguarda 60 segundos antes da próxima verificação.

8. Manipulação de Sinais para Encerramento Suave

Esta função lida com sinais de interrupção (como Ctrl+C) para encerrar o programa de forma limpa.

def signal_handler(signum, frame):
    """
    Manipula sinais de interrupção para encerrar o programa de forma suave.

    Args:
        signum: Número do sinal.
        frame: Frame atual.
    """
    logger.info("Sinal de interrupção recebido. Encerrando o programa...")
    asyncio.get_event_loop().call_soon_threadsafe(shutdown_event.set)
Enter fullscreen mode Exit fullscreen mode

Detalhes da Função:

  • Objetivo: Capturar sinais de interrupção e iniciar o processo de encerramento do bot de forma ordenada.
  • Processo:
    • Quando um sinal de interrupção é recebido (como SIGINT ou SIGTERM), registra uma mensagem de log.
    • Sinaliza o evento de encerramento para que todas as tarefas assíncronas possam finalizar adequadamente.

9. Função Principal (main)

Esta é a função central que coordena todas as operações do bot.

async def main():
    """
    Função principal que inicializa o bot e gerencia suas operações.
    """
    # Configurar o manipulador de sinais
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    # Inicializar contas MT5
    for conta in CONTAS_MT5:
        if await reconectar_mt5(conta):
            contas_ativas.append(conta)

    if not contas_ativas:
        logger.error("Nenhuma conta pôde ser inicializada. Encerrando o programa.")
        return

    logger.info(f"Programa continuando com {len(contas_ativas)} conta(s) ativa(s)")

    # Inicializar o cliente Telegram
    client = TelegramClient('session', os.getenv('TELEGRAM_API_ID'), os.getenv('TELEGRAM_API_HASH'))

    @client.on(events.NewMessage(chats=os.getenv('TELEGRAM_GROUP_USERNAME')))
    async def handler(event):
        """
        Manipulador de eventos para novas mensagens no Telegram.
        """
        if not shutdown_event.is_set():
            try:
                logger.info(f"Nova mensagem recebida do grupo {os.getenv('TELEGRAM_GROUP_USERNAME')}")
                await processar_sinal(event.message.text)
            except Exception as e:
                logger.error(f"Erro ao processar mensagem do Telegram: {e}")

    # Iniciar a tarefa de verificação de conexões
    verificar_task = asyncio.create_task(verificar_conexoes())

    try:
        await client.start(phone=os.getenv('TELEGRAM_PHONE_NUMBER'))
        logger.info("Bot Telegram iniciado. Aguardando mensagens...")
        await shutdown_event.wait()  # Aguarda até que o evento de encerramento seja sinalizado
    except Exception as e:
        logger.error(f"Erro no cliente Telegram: {e}")
    finally:
        await client.disconnect()
        verificar_task.cancel()
        for conta in contas_ativas:
            if mt5.shutdown():
                logger.info(f"MT5 desligado para a conta {conta['login']}")
            else:
                logger.warning(f"Falha ao desligar MT5 para a conta {conta['login']}")
        logger.info("Programa encerrado.")
Enter fullscreen mode Exit fullscreen mode

Detalhes da Função:

  • Configuração de Sinais:

    • Associa os sinais de interrupção (SIGINT e SIGTERM) à função signal_handler para garantir um encerramento suave.
  • Inicialização das Contas MT5:

    • Itera sobre todas as contas definidas em CONTAS_MT5.
    • Tenta reconectar a cada conta e adiciona à lista de contas_ativas se bem-sucedido.
    • Se nenhuma conta puder ser inicializada, registra um erro e encerra o programa.
  • Inicialização do Cliente Telegram:

    • Cria uma instância do TelegramClient usando as credenciais do Telegram.
    • Define um manipulador de eventos (handler) que será chamado sempre que uma nova mensagem for recebida no grupo especificado.
    • O manipulador chama a função processar_sinal para interpretar e agir sobre a mensagem.
  • Iniciar Tarefas Assíncronas:

    • Cria uma tarefa assíncrona para verificar periodicamente as conexões MT5 (verificar_conexoes).
    • Inicia o cliente Telegram e aguarda até que o evento de encerramento seja sinalizado.
  • Encerramento:

    • Ao receber um sinal de encerramento, desconecta o cliente Telegram.
    • Cancela a tarefa de verificação de conexões.
    • Desliga o MT5 para todas as contas ativas.
    • Registra que o programa foi encerrado.

10. Executando o Script

Para executar o script, crie um arquivo chamado bot.py no diretório do seu projeto e cole o seguinte código completo, que integra todas as partes que discutimos:

import os
import sys
import asyncio
import logging
import signal
import pkg_resources
from datetime import datetime
from dotenv import load_dotenv
from telethon import TelegramClient, events
import MetaTrader5 as mt5

# Carrega variáveis de ambiente do arquivo .env
load_dotenv()

# Acessa as variáveis de ambiente para configuração
API_ID = os.getenv('TELEGRAM_API_ID')
API_HASH = os.getenv('TELEGRAM_API_HASH')
PHONE_NUMBER = os.getenv('TELEGRAM_PHONE_NUMBER')
GROUP_USERNAME = os.getenv('TELEGRAM_GROUP_USERNAME')

MT5_LOGIN = os.getenv('MT5_LOGIN')
MT5_PASSWORD = os.getenv('MT5_PASSWORD')
MT5_SERVER = os.getenv('MT5_SERVER')
MT5_PATH = os.getenv('MT5_PATH')

LOG_FILE = os.getenv('LOG_FILE', 'app.log')

# Configura logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler(LOG_FILE),
        logging.StreamHandler(sys.stdout)
    ]
)
logger = logging.getLogger(__name__)

# Configurações das contas MT5
CONTAS_MT5 = [
    {
        "login": MT5_LOGIN,
        "senha": MT5_PASSWORD,
        "servidor": MT5_SERVER,
        "us30": "US30.cash",
        "nas100": "US100.cash",
        "lote": 0.01
    }
]

contas_ativas = []
shutdown_event = asyncio.Event()

async def reconectar_mt5(conta, max_tentativas=3):
    """
    Tenta reconectar à conta MT5 com um número máximo de tentativas.

    Args:
        conta (dict): Informações da conta MT5.
        max_tentativas (int): Número máximo de tentativas de reconexão.

    Returns:
        bool: True se a reconexão for bem-sucedida, False caso contrário.
    """
    for tentativa in range(max_tentativas):
        try:
            # Tenta inicializar a conexão com MT5
            if mt5.initialize(path=MT5_PATH, login=int(conta['login']), server=conta['servidor'], password=conta['senha']):
                logger.info(f"Reconexão bem-sucedida para conta {conta['login']}")
                return True
            else:
                erro = mt5.last_error()
                logger.warning(f"Tentativa {tentativa + 1} de reconexão falhou para conta {conta['login']}: {erro}")
        except Exception as e:
            logger.error(f"Erro durante a tentativa {tentativa + 1} de reconexão para conta {conta['login']}: {e}")
        await asyncio.sleep(5)  # Espera 5 segundos antes de tentar novamente
    logger.error(f"Falha ao reconectar à conta {conta['login']} após {max_tentativas} tentativas")
    return False

async def enviar_ordem(conta, simbolo, acao, lote):
    """
    Envia uma ordem de negociação para o MT5.

    Args:
        conta (dict): Informações da conta MT5.
        simbolo (str): Símbolo do ativo a ser negociado.
        acao (int): Tipo de ação (compra ou venda).
        lote (float): Volume da ordem.

    Returns:
        bool: True se a ordem for enviada com sucesso, False caso contrário.
    """
    # Tenta reconectar à conta MT5 antes de enviar a ordem
    if not await reconectar_mt5(conta):
        logger.error(f"Não foi possível enviar ordem para {conta['login']} devido a falha na reconexão")
        return False

    # Obtém informações do símbolo
    symbol_info = mt5.symbol_info(simbolo)
    if symbol_info is None:
        logger.error(f"Símbolo {simbolo} não encontrado")
        return False

    # Verifica se o símbolo está disponível para trading
    if not symbol_info.visible:
        logger.warning(f"Símbolo {simbolo} não está visível, tentando habilitá-lo")
        if not mt5.symbol_select(simbolo, True):
            logger.error(f"Falha ao selecionar o símbolo {simbolo}")
            return False

    # Obtém o tick atual do símbolo
    tick = mt5.symbol_info_tick(simbolo)
    if tick is None:
        logger.error(f"Não foi possível obter o tick para o símbolo {simbolo}")
        return False

    # Define o preço baseado na ação (compra ou venda)
    price = tick.ask if acao == mt5.ORDER_TYPE_BUY else tick.bid

    # Prepara a estrutura do pedido
    pedido = {
        "action": mt5.TRADE_ACTION_DEAL,  # Tipo de ação de negociação
        "symbol": simbolo,                # Símbolo do ativo
        "volume": float(lote),            # Volume da ordem
        "type": acao,                     # Tipo de ordem (compra ou venda)
        "price": price,                   # Preço da ordem
        "deviation": 20,                  # Desvio permitido no preço
        "magic": 234000,                   # Identificador único para a ordem
        "comment": "python script order",  # Comentário para a ordem
        "type_time": mt5.ORDER_TIME_GTC,   # Tipo de tempo da ordem (Good Till Cancelled)
        "type_filling": mt5.ORDER_FILLING_IOC,  # Tipo de preenchimento (Immediate or Cancel)
    }

    # Envia a ordem para o MT5
    resultado = mt5.order_send(pedido)
    if resultado.retcode != mt5.TRADE_RETCODE_DONE:
        logger.error(f"Erro ao enviar ordem para {conta['login']}: {resultado.comment}")
        logger.debug(f"Detalhes do pedido: {pedido}")
        logger.debug(f"Código de retorno: {resultado.retcode}")
        return False
    else:
        logger.info(f"Ordem enviada com sucesso para {conta['login']} com lote {lote}")
        logger.debug(f"Detalhes da ordem: {resultado}")
        return True

async def processar_sinal(mensagem):
    """
    Processa uma mensagem recebida do Telegram para determinar a ação de negociação.

    Args:
        mensagem (str): Texto da mensagem recebida.
    """
    logger.info(f"Mensagem recebida do Telegram: {mensagem}")

    palavras = mensagem.lower().split()

    ativo = None
    acao = None

    # Verifica o ativo na mensagem
    if 'us30' in palavras:
        ativo = 'us30'
    elif 'nas100' in palavras:
        ativo = 'nas100'

    # Verifica a ação na mensagem
    if 'buy' in palavras:
        acao = mt5.ORDER_TYPE_BUY
    elif 'sell' in palavras:
        acao = mt5.ORDER_TYPE_SELL

    # Se nenhum ativo for reconhecido, encerra a função
    if not ativo:
        logger.info("Nenhum ativo reconhecido na mensagem.")
        return

    # Se nenhuma ação for reconhecida, encerra a função
    if acao is None:
        logger.info("Nenhuma ação (compra/venda) reconhecida na mensagem.")
        return

    acao_str = "COMPRA" se acao == mt5.ORDER_TYPE_BUY else "VENDA"
    logger.info(f"Interpretação: Ativo: {ativo.upper()}, Ação: {acao_str}")

    # Itera sobre todas as contas ativas para enviar a ordem
    for conta in contas_ativas[:]:  # Cria uma cópia da lista para iteração segura
        try:
            simbolo = conta.get(ativo)
            if not simbolo:
                logger.warning(f"Ativo {ativo} não configurado para a conta {conta['login']}. Pulando.")
                continue
            sucesso = await enviar_ordem(conta, simbolo, acao, conta['lote'])
            if not sucesso:
                logger.warning(f"Falha ao processar sinal para conta {conta['login']}. Removendo da lista de contas ativas.")
                contas_ativas.remove(conta)
        except Exception as e:
            logger.error(f"Erro ao processar sinal para conta {conta['login']}: {e}")
            logger.warning(f"Removendo conta {conta['login']} da lista de contas ativas devido a erro")
            contas_ativas.remove(conta)

async def verificar_conexoes():
    """
    Verifica periodicamente se as contas MT5 estão conectadas e tenta reconectar se necessário.
    """
    while not shutdown_event.is_set():
        for conta in contas_ativas[:]:
            if shutdown_event.is_set():
                break
            if not await reconectar_mt5(conta):
                logger.warning(f"Conta {conta['login']} removida da lista de contas ativas devido a falha na conexão")
                contas_ativas.remove(conta)
        await asyncio.sleep(60)  # Verifica a cada 60 segundos

def signal_handler(signum, frame):
    """
    Manipula sinais de interrupção para encerrar o programa de forma suave.

    Args:
        signum: Número do sinal.
        frame: Frame atual.
    """
    logger.info("Sinal de interrupção recebido. Encerrando o programa...")
    asyncio.get_event_loop().call_soon_threadsafe(shutdown_event.set)

async def main():
    """
    Função principal que inicializa o bot e gerencia suas operações.
    """
    # Configurar o manipulador de sinais
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    # Inicializar contas MT5
    for conta in CONTAS_MT5:
        if await reconectar_mt5(conta):
            contas_ativas.append(conta)

    if not contas_ativas:
        logger.error("Nenhuma conta pôde ser inicializada. Encerrando o programa.")
        return

    logger.info(f"Programa continuando com {len(contas_ativas)} conta(s) ativa(s)")

    # Inicializar o cliente Telegram
    client = TelegramClient('session', API_ID, API_HASH)

    @client.on(events.NewMessage(chats=GROUP_USERNAME))
    async def handler(event):
        """
        Manipulador de eventos para novas mensagens no Telegram.
        """
        if not shutdown_event.is_set():
            try:
                logger.info(f"Nova mensagem recebida do grupo {GROUP_USERNAME}")
                await processar_sinal(event.message.text)
            except Exception as e:
                logger.error(f"Erro ao processar mensagem do Telegram: {e}")

    # Iniciar a tarefa de verificação de conexões
    verificar_task = asyncio.create_task(verificar_conexoes())

    try:
        await client.start(phone=PHONE_NUMBER)
        logger.info("Bot Telegram iniciado. Aguardando mensagens...")
        await shutdown_event.wait()  # Aguarda até que o evento de encerramento seja sinalizado
    except Exception as e:
        logger.error(f"Erro no cliente Telegram: {e}")
    finally:
        await client.disconnect()
        verificar_task.cancel()
        for conta in contas_ativas:
            if mt5.shutdown():
                logger.info(f"MT5 desligado para a conta {conta['login']}")
            else:
                logger.warning(f"Falha ao desligar MT5 para a conta {conta['login']}")
        logger.info("Programa encerrado.")

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except Exception as e:
        logger.critical(f"Erro crítico no programa: {e}")
        sys.exit(1)
Enter fullscreen mode Exit fullscreen mode

Explicação Completa do Código:

  1. Carregamento das Variáveis de Ambiente:

    • Usa load_dotenv() para carregar as variáveis definidas no arquivo .env.
    • Acessa essas variáveis usando os.getenv() para configurar o Telegram e MT5.
  2. Configuração de Logging:

    • Configura o logging para registrar mensagens tanto em um arquivo (app.log) quanto no console.
    • Define o nível mínimo de registro como INFO.
  3. Configuração das Contas MT5:

    • Define uma lista CONTAS_MT5 contendo dicionários com as informações necessárias para cada conta MT5.
    • Inicializa uma lista vazia contas_ativas para armazenar contas que estão conectadas.
  4. Função de Reconexão (reconectar_mt5):

    • Tenta reconectar à conta MT5 até um número máximo de tentativas.
    • Registra mensagens de sucesso ou falha no log.
  5. Função para Enviar Ordens (enviar_ordem):

    • Garante que a conexão com MT5 está ativa.
    • Verifica se o símbolo está disponível e visível.
    • Obtém o preço atual do símbolo (ask para compra, bid para venda).
    • Prepara a ordem com os parâmetros necessários e a envia para o MT5.
    • Registra o resultado da ordem no log.
  6. Função para Processar Sinais (processar_sinal):

    • Interpreta a mensagem recebida no Telegram para determinar qual ativo negociar e qual ação (compra ou venda) executar.
    • Itera sobre todas as contas ativas e envia a ordem correspondente.
    • Se uma ordem falhar, remove a conta da lista de contas ativas para evitar tentativas futuras.
  7. Função de Monitoramento de Conexões (verificar_conexoes):

    • Executa em um loop contínuo, verificando a conexão de cada conta MT5 ativa a cada 60 segundos.
    • Tenta reconectar se a conexão estiver perdida e remove contas que não puderam ser reconectadas.
  8. Manipulador de Sinais (signal_handler):

    • Captura sinais de interrupção (como Ctrl+C) e sinaliza o encerramento do programa.
    • Garante que todas as tarefas assíncronas sejam finalizadas corretamente.
  9. Função Principal (main):

    • Configura os manipuladores de sinais.
    • Inicializa as contas MT5 e adiciona as contas bem-sucedidas à lista de contas ativas.
    • Inicializa o cliente Telegram e define um manipulador de eventos para novas mensagens.
    • Inicia a tarefa assíncrona de monitoramento de conexões.
    • Aguarda até que um sinal de encerramento seja recebido.
    • Ao encerrar, desconecta o cliente Telegram, cancela a tarefa de monitoramento e desliga o MT5 para todas as contas ativas.
  10. Execução do Script:

    • Verifica se o script está sendo executado diretamente.
    • Executa a função principal usando asyncio.run().
    • Captura quaisquer exceções críticas, registra no log e encerra o programa com um código de erro.

Executando o Script

Após configurar o ambiente e criar o arquivo bot.py, siga os passos abaixo para executar o bot.

1. Ativar o Ambiente Virtual

Se você configurou um ambiente virtual, ative-o:

  • No Windows:

    venv\Scripts\activate
    
  • No macOS e Linux:

    source venv/bin/activate
    

2. Executar o Script

No diretório do seu projeto, execute o seguinte comando:

python bot.py
Enter fullscreen mode Exit fullscreen mode

O que esperar:

  • Logs no Console: Você verá mensagens informando sobre o status da conexão com o MT5 e do cliente Telegram.
  • Arquivo de Log: Todas as mensagens de log também serão gravadas no arquivo app.log (ou outro definido em LOG_FILE).

Interagindo com o Bot:

  • Envie mensagens no grupo do Telegram especificado (@superus30) com comandos como "buy US30" ou "sell NAS100".
  • O bot interpretará esses sinais e enviará ordens de negociação correspondentes para a conta MT5 configurada.

3. Encerrando o Bot

Para encerrar o bot de forma segura, pressione Ctrl+C no terminal onde o script está sendo executado. Isso acionará o manipulador de sinais, que garantirá que todas as conexões sejam fechadas corretamente antes de encerrar o programa.


Boas Práticas e Dicas

  1. Manutenção da Segurança:

    • Nunca compartilhe seu arquivo .env ou exponha suas credenciais em repositórios públicos.
    • Use Ambientes Virtuais para isolar as dependências do projeto.
  2. Monitoramento e Logs:

    • Revise regularmente os logs (app.log) para identificar e resolver possíveis problemas.
    • Implementar Rotação de Logs: Para evitar que o arquivo de log cresça indefinidamente, considere implementar rotação usando bibliotecas como logging.handlers.
  3. Testes em Ambiente de Demonstração:

    • Antes de operar em contas reais, teste seu bot em ambientes de demonstração para garantir que tudo funcione conforme o esperado.
    • Verifique as Ordens: Assegure-se de que as ordens estão sendo enviadas corretamente e que o volume está adequado.
  4. Gerenciamento de Erros:

    • Tratamento Robusto de Exceções: Certifique-se de que todas as possíveis exceções sejam tratadas para evitar que o bot pare inesperadamente.
    • Alertas: Considere implementar alertas (por exemplo, enviando mensagens de log críticas para seu email) para notificá-lo sobre falhas graves.
  5. Atualizações de Dependências:

    • Mantenha suas bibliotecas atualizadas para aproveitar melhorias de segurança e correções de bugs.
    • Verifique as Documentações: Consulte as documentações oficiais das bibliotecas para entender mudanças ou atualizações.
  6. Escalabilidade:

    • Gerencie Múltiplas Contas: Se você pretende usar múltiplas contas MT5, expanda a configuração de CONTAS_MT5 conforme necessário.
    • Adicionar Mais Ativos: Atualize o script para suportar mais ativos além de "US30" e "NAS100" conforme sua estratégia de negociação.
  7. Documentação e Comentários:

    • Comente Seu Código: Mantenha comentários atualizados e relevantes para facilitar futuras manutenções.
    • Documente Configurações: Mantenha um documento com detalhes sobre como configurar e executar o bot.

Conclusão

Parabéns! Você criou um bot em Python que integra o Telegram com o MetaTrader5, capaz de automatizar ordens de negociação com base em sinais recebidos no Telegram. Ao seguir este guia, você não apenas implementou a funcionalidade desejada, mas também adotou práticas de segurança e organização de código que são fundamentais para projetos de software robustos.

Próximos Passos:

  • Expandir Funcionalidades: Adicione mais funcionalidades, como diferentes tipos de ordens, gerenciamento de riscos ou integração com outras plataformas.
  • Interface de Usuário: Considere criar uma interface gráfica ou um dashboard para monitorar o desempenho do bot.
  • Automatizar Deployments: Se desejar que o bot funcione continuamente, explore opções de deployment em servidores ou serviços de nuvem.

Recursos Adicionais:


🔒 Fique Seguro e Boas Operações! 🔒

A segurança das suas credenciais é fundamental para proteger suas contas e dados. Sempre siga as melhores práticas para garantir a integridade e confidencialidade das suas informações.

Top comments (0)