Desenvolvemos um gerenciador de terminais (Parte 2): Execução de várias instâncias
Introdução
Na primeira parte da série "Gerenciador de terminais por conta própria", definimos nossa tarefa: criar uma interface web para gerenciar a inicialização e o encerramento de instâncias do MetaTrader 5 em um computador local. Definimos a arquitetura do sistema e começamos pelo desenvolvimento de um produto mínimo viável (MVP): um servidor web capaz de receber requisições HTTP e gerenciar uma única instância do terminal. Aproveitamos também para analisar conceitos importantes, como API, REST, requisições HTTP e decoradores.
Para implementar o projeto, escolhemos a linguagem Python devido ao seu amplo ecossistema de bibliotecas e à facilidade de integração com APIs externas, neste caso, com a biblioteca MetaTrader 5.
O framework FastAPI permite criar aplicações web com pouco código e oferece, nativamente, suporte a tipagem, operações assíncronas e geração automática de documentação por meio do Swagger UI.
Em conjunto com ele, usamos o Uvicorn, um servidor ASGI leve e rápido, capaz de oferecer alto desempenho mesmo com um grande número de requisições simultâneas.
Para gerenciar os processos dos terminais, usamos as bibliotecas psutil e subprocess, que permitem obter facilmente informações sobre as instâncias em execução e gerenciá-las.
Mais adiante, planejamos adicionar o SQLite ou outro banco de dados para armazenar o estado das instâncias, além de uma camada de frontend em JavaScript ou Vue.js para criar uma interface mais interativa.
Agora chegou a hora de ampliar a funcionalidade e avançar para as próximas etapas, implementando recursos mais complexos, como o gerenciamento de várias instâncias, o armazenamento de estado, a integração com a API do MetaTrader 5 e uma interface web com informações completas sobre os terminais.
Traçamos o caminho
Vamos tentar definir os limites aproximados da próxima etapa da implementação. Em primeiro lugar, como ainda não fizemos uma separação efetiva da funcionalidade geral entre dois servidores web distintos, teremos de adicionar, ainda que de forma mínima, alguns elementos de interface ao servidor web que já começamos a desenvolver. Esse trabalho não será perdido, pois futuramente poderemos transferir o código existente para outro servidor web com alguns ajustes. Portanto, começaremos criando arquivos adicionais para separar as diferentes partes do código do projeto e deixá-lo um pouco mais estruturado.
Em segundo lugar, daremos o próximo passo mais natural: adicionar suporte a várias instâncias do terminal MetaTrader 5. Ou seja, implementaremos a criação, a inicialização e o encerramento das instâncias, além da obtenção da lista das que estiverem em execução no momento. Deixaremos para mais adiante a obtenção de informações detalhadas sobre a conta de negociação.
Isso exigirá que tomemos previamente algumas decisões arquiteturais, o que não é tão simples quanto pode parecer à primeira vista. Neste momento, somos responsáveis pelos rumos futuros do projeto. A facilidade de implementação das próximas etapas e até onde será possível ampliar a funcionalidade dependerão de quão acertadas forem as decisões que tomarmos agora. Seria muito desagradável chegar a determinada etapa do projeto e descobrir que, para continuar avançando, precisamos voltar ao início e refazer tudo. Isso, naturalmente, também acontece, mas tentaremos evitar essa situação analisando nossas escolhas com mais cuidado.
Por outro lado, é importante não nos perdermos em um mar de possibilidades. Por isso, em algumas questões, é melhor tomar alguma decisão agora do que tentar prever indefinidamente como cada escolha poderá repercutir no futuro. Em outros casos, pode ser mais eficiente optar inicialmente por uma solução simples, mesmo sabendo que mais adiante ela será substituída por uma solução mais sofisticada, em uma etapa posterior da implementação. Assim, evitamos ficar presos à tentativa de criar uma arquitetura perfeita para uma parte isolada do projeto e conseguimos avançar mais rapidamente com o projeto como um todo.
Separação do código
Vamos adicionar alguns arquivos novos ao projeto. Para o arquivo mt5_control.py, transferiremos todas as funções do arquivo main.py responsáveis pela lógica de gerenciamento dos terminais. Dessa forma, por enquanto, o arquivo principal da aplicação ficará apenas com os handlers dos endpoints (rotas) e com a criação do objeto FastAPI com as configurações necessárias.
Para armazenar os templates de código HTML das páginas que precisam ser geradas durante o processamento de determinados endpoints, criaremos a pasta templates. Por enquanto, colocaremos nela apenas o arquivo index.html, usado para gerar o código HTML da página principal, correspondente à rota GET /.
Para armazenar arquivos estáticos, que serão enviados ao navegador sem qualquer processamento pelo servidor web, criaremos a pasta static. Entre esses arquivos podem estar, por exemplo, folhas de estilo CSS ou arquivos com código JavaScript referenciados pelo HTML das páginas. Vamos adicionar desde já dois arquivos vazios desse tipo para a página principal.
Depois disso, a árvore de arquivos do projeto ficará assim:
mt5_manager/
│
├── main.py # Arquivo principal do FastAPI
├── mt5_control.py # Lógica de gerenciamento da inicialização e do encerramento dos terminais
├── templates/
│ └── index.html # Template da página principal
└── static/
├── styles.css # Estilos CSS
└── script.js # Código JavaScript
Daqui em diante, seguiremos as convenções adotadas para a organização dos arquivos do projeto. Se necessário, poderemos criar novas subpastas dentro das já existentes sempre que isso ajudar a estruturar melhor os arquivos do projeto.
Para podermos usar o mecanismo de templates e arquivos estáticos em nossa aplicação, precisamos importar as bibliotecas correspondentes no arquivo main.py:
from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates
Em seguida, depois de criar o objeto da aplicação (app), registramos o novo endpoint GET /static. Ao processá-la, qualquer nome presente na URL após static/ será interpretado como um arquivo da pasta static do nosso projeto:
# Creating an Application Object app = FastAPI() # Mounting Static Files app.mount("/static", StaticFiles(directory="static"), name="static")
Observe que, neste caso, não criamos uma função separada para processar a rota GET /static. Em vez disso, chamamos um método do objeto da aplicação, que criará automaticamente a função necessária com a lógica correspondente.
Agora criaremos um objeto que será usado quando precisarmos retornar código HTML gerado a partir de um determinado template da pasta templates:
# Creating an Object for Working with HTML Templates templates = Jinja2Templates(directory="templates")
Vamos reescrever o handler da rota GET / da seguinte forma:
@app.get('/', response_class=HTMLResponse) async def index(request: Request): '''GET handler / (root directory)''' instances = load_instances() return templates.TemplateResponse("index.html", {"request": request, "instances": instances})
Nele, obtemos o dicionário instances, que contém informações sobre as instâncias de terminal registradas e seu estado atual (em execução ou parada). Em seguida, retornamos uma resposta gerada a partir do arquivo index.html, levando em conta as informações contidas no dicionário instances. Veremos um pouco mais adiante quais informações estarão presentes nesse dicionário instances e como elas serão transformadas em código HTML.
Também faremos pequenas alterações nos dois handlers das rotas restantes. As alterações serão do mesmo tipo, ou seja, faremos no handler responsável pela inicialização de uma instância o mesmo que no handler responsável pelo encerramento. Isso é necessário porque agora precisamos informar ao handler qual instância queremos iniciar ou encerrar. Quando havia apenas uma instância, esse problema não existia.
Adicionaremos às URLs dessas rotas uma parte variável, indicada como {name}. Com isso, precisamos acrescentar à lista de argumentos do handler um novo argumento chamado name, do tipo str, já que o nome da instância é uma string:
@app.post('/start/{name}') async def start_instance(name: str): '''POST handler /start - terminal launch''' result = start_mt5(name) return JSONResponse(result) @app.post('/stop/{name}') async def stop_instance(name: str): '''POST handler /stop - terminal stop''' result = stop_mt5(name) return JSONResponse(result)
Dessa forma, quando o handler for chamado, ele receberá como argumento a string extraída da parte da URL correspondente a {name}. A partir daí, poderemos usar esse valor dentro do próprio handler.
Vamos atualizar nossa tabela de rotas para refletir as alterações feitas:
| # | Método | URL | Descrição, parâmetros | Formato da resposta |
|---|---|---|---|---|
| 1 | GET | / | Exibe a página principal com a interface web para gerenciar as instâncias do MetaTrader 5. Por enquanto, vamos exibir apenas um título com o nome do projeto. | HTML |
| 2 | POST | /start/{name} | Inicia a instância do terminal MetaTrader 5 chamada {name} | JSON |
| 3 | POST | /stop/{name} | Encerra o processo do MetaTrader 5 em execução correspondente à instância {name} | JSON |
Mesmo que o projeto ainda esteja em uma fase de desenvolvimento ativo, é importante começar desde já a consolidar uma arquitetura sólida. O ideal é que o código da aplicação seja facilmente extensível, de modo que a adição de novas funcionalidades, rotas ou lógica de gerenciamento não exija reescrever as partes já existentes.
É justamente por isso que faz sentido seguir os princípios de separação de responsabilidades e arquitetura limpa:
- O módulo mt5_control.py fica responsável apenas pelo gerenciamento dos processos dos terminais.
- O arquivo main.py concentra as rotas e a interface web.
- As pastas templates e static mantêm a parte visual da aplicação separada do restante do código.
Essa separação torna o projeto mais resistente a alterações e facilita os testes. No futuro, se necessário, essas partes poderão ser transferidas para microsserviços separados.
Instâncias dos terminais
Para podermos executar várias instâncias do MetaTrader 5 no servidor, elas precisam estar fisicamente presentes nele. Ou seja, o servidor deve conter várias pastas, cada uma com o arquivo executável do terminal. Existem diferentes maneiras de fazer isso, mas, para simplificar neste momento, vamos assumir que essa parte do trabalho será realizada manualmente pelo usuário.
Vamos fazer essa preparação. Se ainda não houver nenhum terminal instalado no computador, será necessário baixá-lo do site da corretora ou de www.metatrader5.com e instalá-lo. Criaremos uma pasta MT5 com três subpastas chamadas MetaTrader5.x e copiaremos para elas os arquivos executáveis do terminal (terminal64.exe) e do editor (metaeditor64.exe) a partir de qualquer instalação existente do MetaTrader 5:

A escolha dos nomes específicos das pastas dos terminais e da pasta raiz não tem qualquer importância. Escolhemos esses nomes apenas por serem curtos e fáceis de entender.
Na pasta de cada terminal, executaremos o arquivo terminal64.exe no modo portable e nos conectaremos a alguma conta de negociação. Aqui, usaremos contas de demonstração da MetaQuotes. Se for necessário instalar atualizações, aguardaremos a conclusão desse processo. Assim, passamos a ter três instâncias do terminal instaladas nos seguintes caminhos completos:
C:/MT5/MetaTrader5.1/terminal64.exe
C:/MT5/MetaTrader5.2/terminal64.exe
C:/MT5/MetaTrader5.3/terminal64.exe
São justamente essas instâncias que iremos iniciar e encerrar por meio do servidor web que estamos desenvolvendo. Por enquanto, também não trataremos da alteração das contas de negociação às quais os terminais estão conectados, concentrando-nos apenas na inicialização e no encerramento.
Mais adiante, o procedimento que acabamos de realizar manualmente será automatizado de alguma forma. Neste momento, porém, não precisamos desviar a atenção para essa implementação: depois de fazer essa preparação uma única vez, podemos deixá-la de lado por bastante tempo e concentrar os esforços na parte principal do projeto.
Formação da lista de terminais
Na parte anterior, implementamos a inicialização e o encerramento de uma única instância do terminal, localizada em um caminho fixo no servidor. Agora vamos implementar a inicialização e o encerramento individual de cada instância de uma lista fixa. Como já mencionamos, transferimos esse código para o novo arquivo mt5_control.py e continuaremos desenvolvendo-o nesse arquivo.
Por enquanto, não precisaremos de nenhuma biblioteca adicional além das já incluídas, subprocess e psutil. Criamos três pastas com instâncias do terminal que diferem apenas pelo nome da última pasta no caminho até o arquivo terminal64.exe. Portanto, vamos declarar duas constantes contendo as partes do caminho que são iguais para todas as instâncias:
# Path to the folder containing terminal instances MT5_FOLDER = 'C:/MT5' # Name of the terminal executable file MT5_EXE = 'terminal64.exe'
Agora podemos criar uma função que, a partir do nome da instância, monte o caminho completo até o arquivo executável do terminal correspondente. Neste momento, simplesmente concatenaremos três partes do caminho completo: a pasta comum, o nome da instância e o nome do arquivo executável:
def instance_path(name: str) -> str: ''' Build the full path from the terminal instance name Args: name (str): Terminal instance name. Returns: path (str): Full path to the terminal instance executable file. ''' return f'{MT5_FOLDER}/{name}/{MT5_EXE}'
Para armazenar a lista de nomes das instâncias, usaremos por enquanto uma lista comum contendo os nomes das pastas que escolhemos:
# List of instance names (fixed for now) instances_names = ['MetaTrader5.1', 'MetaTrader5.2', 'MetaTrader5.3']
A partir dessa lista de nomes, podemos determinar diretamente o caminho completo até o arquivo executável de cada instância do terminal. Como esses caminhos também permanecerão constantes por enquanto, podemos criar antecipadamente dois dicionários auxiliares, que serão usados para obter o caminho a partir de um determinado nome e o nome a partir de um determinado caminho:
# Name-to-path mapping dictionary instances_paths = {name: instance_path(name) for name in instances_names} # Path-to-name mapping dictionary paths_instances = {instance_path(name): name for name in instances_names}
Depois de refletirmos bastante, decidimos não reutilizar as informações sobre o estado das instâncias obtidas durante a inicialização do servidor. Em vez disso, a cada operação atualizaremos a lista das instâncias em execução. Para isso, criaremos a função load_instances(), responsável por montar o dicionário instances. Nesse dicionário, a chave será o nome da instância, e o valor será outro dicionário com duas chaves: pid e status. Nesses campos, armazenaremos o identificador exclusivo do processo em execução (PID) e o status da instância correspondente.
É possível que armazenar esses dois tipos de informação seja redundante, pois, em princípio, o próprio valor de PID já permite determinar se a instância está em execução ou não. Se PID > 0, significa que o processo está em execução. No entanto, ainda não está totalmente claro qual seria a melhor solução, então, por enquanto, manteremos os dois campos. Mais adiante, provavelmente ampliaremos o dicionário que armazena as informações de cada instância.
Primeiro, inicializamos o dicionário instances atribuindo os mesmos valores a todas as instâncias:
{'pid': 0, 'status': 'stopped'} o que corresponde a uma instância que não está em execução. Em seguida, usando a biblioteca psutil, percorremos todos os processos em execução no computador. Para cada um deles, verificaremos se ele corresponde ao processo de uma das nossas instâncias do terminal. Em caso afirmativo, determinamos seu nome e PID e armazenamos essas informações no elemento correspondente do dicionário instances.
def load_instances() -> dict: '''Get MetaTrader 5 instance status info from the specified folders. Returns: instances (dict): Terminal instance info. ''' # Create a dictionary for all instances from the list of names instances = {name: {'pid': 0, 'status': 'stopped'} for name in instances_names} # Iterate through all running processes for proc in psutil.process_iter(['pid', 'name', 'exe']): try: # Get the path to the process executable exe_path = str(proc.info['exe']).replace('\\', '/') # If it is among the terminal instance paths if exe_path and exe_path in paths_instanses: # Determine the instance name for this path name = paths_instanses[exe_path] # Get the required instance instance = instances[name] # Store the process ID and status instance['pid'] = proc.info['pid'] instance['status'] = 'running' except (psutil.NoSuchProcess, psutil.AccessDenied): continue return instances
Essa função será usada no handler da rota GET / para obter o estado atualizado de todas as instâncias registradas. Além disso, também será chamada a cada operação de inicialização e encerramento. Com isso, poderemos evitar tanto a inicialização de uma instância que já esteja em execução quanto a tentativa de encerrar uma que já esteja parada.
Inicialização dos terminais
Vamos agora analisar a implementação da função responsável por iniciar uma instância do terminal cujo nome é recebido como argumento. Primeiro, obtemos informações atualizadas sobre o estado de todos os terminais registrados. Se o nome informado estiver entre os nomes existentes, verificamos se já há um processo em execução para essa instância.
Se não houver, iniciamos um novo processo usando a função Popen() da biblioteca subprocess. Como argumento, precisamos passar uma lista com os argumentos da linha de comando usados para executar o arquivo correspondente. O primeiro elemento dessa lista é o caminho completo até o arquivo executável e, em seguida, vem a opção /portable, para usar a própria pasta do terminal como pasta de trabalho.
Depois que o processo é iniciado com sucesso, obtemos o valor de seu identificador exclusivo no sistema operacional (PID) e, juntamente com o status "running", armazenamos essas informações no elemento correspondente do dicionário de instâncias que será retornado pela função:
def start_mt5(name: str) -> dict: ''' Terminal instance launch Args: name (str): Terminal instance name. Returns: instance (dict): Info on launched terminal. ''' # Getting information about terminal instances instances = load_instances() # If the instance name is in the list of available names if name in instances: # Get the required instance instance = instances[name] # If there is no running process for it, then if not instance['pid']: # Start a new process process = subprocess.Popen([instanсes_paths[name], '/portable']) # ID of the running process pid = process.pid # Save the process ID and status instance['pid'] = pid instance['status'] = 'running' # Return the result: started successfully return {name: instance} # Return the result: name not found return {name: {'status': 'not found'}}
Se a requisição contiver o nome de uma instância que não esteja entre os nomes registrados, não fazemos nada e retornamos a informação de que essa instância não foi encontrada.
Encerramento dos terminais
A função de encerramento de uma instância do terminal funciona de maneira semelhante. Ela também recebe o nome da instância obtido da requisição. Em seguida, obtemos a lista atualizada de todas as instâncias registradas com seus respectivos estados. Se o nome da instância for válido, obtemos seu PID e criamos um objeto que permite gerenciar o processo com esse identificador. Enviamos ao processo o comando de encerramento e aguardamos no máximo 10 segundos até que o processo seja encerrado:def stop_mt5(name: str) -> dict: ''' Stop terminal Args: name (str): Terminal instance name. Returns: instance (dict): Info on launched terminal. ''' # Retrieving information about terminal instances instances = load_instances() # If the instance name is in the list of available names if name in instances: # Get the required instance instance = instances[name] # Get its process ID pid = instance['pid'] # If the terminal was previously started if pid: try: # Get the terminal process object p = psutil.Process(pid) # Stop the process p.terminate() p.wait(10) except psutil.NoSuchProcess: pass # Clear the information about the previously running process instance['pid'] = 0 instance['status'] = 'stopped' # Return the result: stopped successfully return {name: instance} # Return the result: process not found return {name: {'status': 'not found'}}
Se o processo for encerrado normalmente, retornamos essa informação. Caso contrário, ainda assim o consideramos encerrado. Nas condições atuais, ainda não encontramos nenhum problema ao encerrar os processos, portanto deixaremos essa função dessa forma. Quando surgirem problemas, pensaremos em como aprimorá-la.
E, se estivermos tentando encerrar um processo que já foi finalizado, por exemplo, porque fechamos manualmente a janela do terminal em execução, será retornada apenas uma mensagem informando que o processo não foi encontrado.
Página principal
Agora chegou a vez de preparar o template HTML usado para gerar a página principal. Precisamos fazer algumas coisas nele. Primeiro, vamos incluir os arquivos estáticos externos com o código CSS e JavaScript. Em segundo lugar, adicionaremos à página uma lista não ordenada, na qual cada item apresentará informações sobre uma instância do terminal.
Para gerar essa lista, usaremos o mecanismo de templates Jinja2. No handler da rota GET /, passamos ao objeto do template o dicionário que criamos com as informações sobre as instâncias registradas. Ele se parece aproximadamente com isto:
{
"MetaTrader5.1": {
"pid": 23820,
"status": "running"
},
"MetaTrader5.2": {
"pid": 24888,
"status": "running"
},
"MetaTrader5.3": {
"pid": 0,
"status": "stopped"
}
} Usando a construção {% for name, data in instances.items() %} ... {% endfor %}, podemos criar um laço que gera blocos de código HTML com a mesma estrutura, substituindo os valores das variáveis a cada iteração. Neste caso, a variável name receberá, um após o outro, os nomes das nossas instâncias de terminal: 'MetaTrader5.1', 'MetaTrader5.2', 'MetaTrader5.3'. Já a variável data receberá, para cada instância do dicionário instances, o dicionário correspondente com as chaves pid e status.
Também podemos usar, quando necessário, a construção condicional {% if data.pid > 0 %} ... {% else %} ... {% endif %}. Graças a ela, exibimos para cada instância apenas um botão correspondente à ação disponível: iniciar uma instância parada ou encerrar uma instância em execução.
<!DOCTYPE html> <html> <head> <title>MT5 Manager</title> <link rel="stylesheet" href="/static/styles.css"> </head> <body> <h1>MT5 Manager</h1> <div class="instances"> <h2>Instances</h2> <ul id="instanceList"> {% for name, data in instances.items() %} <li class="{{ data.status }}"> <strong>{{ name }}</strong> {% if data.pid > 0 %} (PID: {{ data.pid }}) <button onclick="stopInstance('{{ name }}')">Stop</button> {% else %} <button onclick="startInstance('{{ name }}')">Start</button> {% endif %} </li> {% endfor %} </ul> </div> <script src="/static/script.js"></script> </body> </html>
Ao clicar nos botões da página principal, será executado o código JavaScript das funções startInstance() e stopInstance().
Tratamento dos cliques
As funções responsáveis pelo tratamento dos cliques nos botões Start e Stop ficarão em um arquivo separado, script.js, que colocamos na pasta /static. O código delas é praticamente idêntico, diferindo apenas no endpoint para o qual cada função enviará a requisição POST. O nome da instância do terminal recebido como argumento é inserido na URL da requisição e, em seguida, aguardamos a resposta do nosso próprio servidor web. Depois que a resposta é recebida, ela é exibida no console JavaScript do navegador e a página atual é recarregada:
/** * Terminal launch * @param {string} name - Instance name */ async function startInstance(name) { // Send a request to the required route const res = await fetch(`/start/${name}`, { method: "POST" }); // Get the response const data = await res.json(); // You can add additional actions here console.log(data); // Reload the page location.reload(); }
/** * Terminal stop * @param {string} name - Instance name */ async function stopInstance(name) { // Send a request to the required route const res = await fetch(`/stop/${name}`, { method: "POST" }); // Get the response const data = await res.json(); // You can add additional actions here console.log(data); // Reload the page location.reload(); }
Mais adiante, as informações recebidas na variável data poderão ser usadas para exibir mensagens adicionais ou atualizar o conteúdo da página sem recarregá-la por completo. Por enquanto, porém, como a página principal ainda exibe muito pouca informação, podemos simplesmente recarregar a página por completo.
Para melhorar a aparência da página principal, vamos adicionar alguns estilos ao arquivo styles.css, localizado na pasta /static. Por enquanto, não recorreremos a frameworks mais completos de estilização HTML, como o Bootstrap. Sempre poderemos usá-los depois, quando o projeto estiver mais bem definido. Neste momento, ainda estamos apenas experimentando como pode ser a interface de interação com o usuário, portanto não faz sentido dedicar muita atenção à aparência desde já.
Mas podemos fazer alguns ajustes simples. Por exemplo, adicionamos a cada item da lista uma classe CSS adicional correspondente ao status da instância. No arquivo styles.css, incluímos regras que definem uma cor de fundo diferente para o item conforme a presença da classe running:
.instances li.running {
background: #d1ffd1;
border: 1px solid #89fd85;
} Agora será fácil distinguir visualmente as instâncias em execução das que estão paradas.
Testes
Vamos iniciar o servidor web a partir da pasta de trabalho do projeto com o comando:
uvicorn main:app --reload --no-use-colors
e abrir no navegador a página http://127.0.0.1:8000.
Enquanto nenhuma instância do terminal estiver em execução, veremos algo semelhante a isto:

Vamos clicar no botão Start da primeira e da segunda instância. Após alguns instantes, veremos que ambas passaram a estar em execução. Isso também pode ser confirmado no Gerenciador de Tarefas do Windows, onde aparecem dois processos com os mesmos identificadores exibidos na página principal da nossa interface web:

Também podemos verificar se o sistema de documentação automática incorporou os comentários e os esclarecimentos que adicionamos sobre as rotas e seus respectivos parâmetros:

Como podemos ver, a rota POST /start/{name} contém uma descrição tanto da própria rota quanto do parâmetro {name}, incluindo um exemplo de valor possível.
Conclusão
Nesta etapa, o projeto MetaTrader 5 Manager deixou de ser apenas uma ideia e um protótipo simples e passou a ser uma aplicação completa, já capaz de gerenciar várias instâncias dos terminais de negociação MetaTrader 5 por meio de uma interface web prática. Passo a passo, separamos a lógica de gerenciamento dos processos da parte responsável pelo servidor, adicionamos rotas para controlar as instâncias, implementamos uma visualização simples do estado dos terminais e demos os primeiros passos na organização do código do projeto.
Embora a solução atual ainda esteja longe da versão definitiva, ela já cumpre sua principal função: permite iniciar e encerrar terminais de forma centralizada e obter informações sobre o estado atual de cada um deles. Com isso, passamos a ter uma base sólida sobre a qual poderemos desenvolver funcionalidades mais avançadas.
Mais adiante, desenvolveremos o projeto em várias direções:
- automação do registro de novas instâncias de terminal e armazenamento das informações correspondentes em um banco de dados;
- ampliação da integração com a API MetaTrader 5 para Python, a fim de obter estatísticas, ordens e histórico de operações;
- aprimoramento da interface, com atualizações dinâmicas sem recarregar a página e novos elementos visuais de controle;
- possível separação da parte de interface em um serviço de frontend independente.
Também precisamos pensar em um sistema de controle de acesso. Para isso, podemos adicionar autenticação básica ou usar uma conexão protegida por HTTPS.
Além disso, vale a pena prever o registro das operações: quem iniciou ou encerrou os terminais e quando. Essas informações podem ser úteis para auditoria e análise do uso do sistema.
Mas o principal já pode ser percebido agora: a arquitetura e o conjunto de tecnologias escolhidos (Python + FastAPI + Jinja2 + psutil) permitem desenvolver o projeto com flexibilidade, sem perder o controle nem a transparência. Este é um ótimo exemplo de como uma REST API simples pode evoluir, passo a passo, até se transformar em um sistema completo para gerenciar processos reais de negociação.
Obrigado pela atenção e até a próxima!
Conteúdo do arquivo compactado
| # | Nome | Versão | Descrição | Últimas alterações |
|---|---|---|---|---|
| mt5-manager/ | Pasta de trabalho do projeto do servidor web dos terminais | |||
| 1 | ├─ main.py | 0.1.0 | Aplicação web para o servidor web dos terminais | Parte 2 |
| 2 | ├─ mt5_control.py | 0.1.0 | Lógica de gerenciamento da inicialização e do encerramento dos terminais | Parte 2 |
| ├─ templates/ | ||||
| 3 | │ └─ index.html | 0.1.0 | Template da página principal | Parte 2 |
| └─ static/ | ||||
| 4 | ├─ styles.css | 0.1.0 | Estilos CSS | Parte 2 |
| 5 | └─ script.js | 0.1.0 | Código JavaScript | Parte 2 |
O código-fonte do projeto também está disponível no repositório mt5-manager.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/19852
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Redes neurais em trading: Abordagem semântica baseada em spikes para identificação espaço-temporal (S3CE-Net)
Redes neurais em trading: Treinamento de modelos spiking profundos (Conclusão)
Está chegando o novo MetaTrader 5 e MQL5
Desenvolvemos um gerenciador de terminais (Parte 1): Definição do problema
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso