indicador Z-Score
Entenda a estrutura do código, os cálculos utilizados e a lógica por trás da implementação do indicador Z-Score para MetaTrader 5 e Python. Se você ainda não conhece o conceito do Z-Score, leia primeiro este artigo antes de continuar.
Neste artigo vamos direto ao código. Você vai ver como as duas versões foram construídas, MQL5 para o MetaTrader 5 e Python com integração nativa ao MT5, comparando os trechos mais importantes e entendendo as decisões técnicas por trás de cada uma. Os repositórios completos estão disponíveis no final.
Uma Versão Diferente: Z-Score com MME como Referência
A maioria dos Z-Score usa a média simples como referência. Esta implementação usa a Média Móvel Exponencial (MME), o que torna o indicador mais responsivo a mudanças de ritmo do mercado.
A fórmula aplicada nas duas versões é:
O desvio padrão dinâmico é a chave da implementação: ele não mede a volatilidade do preço em torno de si mesmo, mas o quanto o preço historicamente se afasta da MME funcionando como um denominador que se ajusta ao regime atual do ativo.
Estrutura dos Parâmetros: MQL5 vs Python
As duas versões expõem os mesmos controles ao usuário, período da MME, período do Z-Score e níveis extremos, mas de formas bem diferentes. No MQL5 os inputs ficam declarados diretamente no código com a palavra-chave input. No Python eles ficam centralizados em um dicionário no topo do arquivo, facilitando ajustes sem tocar na lógica de cálculo.
MQL5
input group "Configurações do Indicador" input ENUM_TIMEFRAMES InpTimeFrame = PERIOD_CURRENT; input int InpPeriodoMM = 200; // Período da MME input int InpPeriodoZ = 20; // Período do Z-Score input group "Níveis Extremos" input double InpNivelAlto = 2.5; input double InpNivelBaixo = -2.5;
Python
USER_INPUTS = {
"symbol": "EURUSD", # Ativo: EURUSD, NAS100, AAPL...
"timeframe": "M15",
"period_mm": 200, # Período da MME
"period_z": 20, # Período do Z-Score
"level_ext_pos": 2.5, # Extremo de venda
"level_ext_neg": -2.5, # Extremo de compra
"level_alert_pos": 1.5, # Alerta moderado de venda
"level_alert_neg":-1.5, # Alerta moderado de compra
}
Note que o Python tem dois níveis a mais, os alertas moderados de ±1.5 usados para colorização intermediária do gráfico. No MQL5 esses níveis intermediários foram omitidos para manter o visual limpo na janela separada do terminal.
O Núcleo do Cálculo: Desvio Padrão Dinâmico
Aqui está a parte mais importante e onde as duas versões fazem a mesma coisa por caminhos opostos. No Python, o pandas resolve em duas linhas vetorizadas. No MQL5, um loop percorre as barras manualmente.
Python – vetorizado
# Passo 1 — MME como referência dinâmica
df['EMA'] = df['close'].ewm(span=period_mm, adjust=False).mean()
# Passo 2 — Quadrado da distância (close - EMA)²
df['diff_sq'] = (df['close'] - df['EMA']) ** 2
# Passo 3 — Raiz da média dos quadrados nas últimas N barras
df['dynamic_std'] = np.sqrt(
df['diff_sq'].rolling(window=period_z).mean()
)
# Passo 4 — Z-Score final
df['zscore'] = (df['close'] - df['EMA']) / df['dynamic_std']
MQL5 – loop barra a barra
// Passo 1+2 — Acumula (close - MME)² nas últimas InpPeriodoZ barras
double soma = 0;
for(int j = 0; j < InpPeriodoZ; j++)
{
double diff = close[i + j] - mm_history[i + j];
soma += diff * diff;
}
// Passo 3 — Desvio padrão dinâmico (divisor N, não N-1)
double dp = MathSqrt(soma / InpPeriodoZ);
// Passo 4 — Z-Score com proteção contra divisão por zero
if(dp > 0)
ZScoreBuffer[i] = (close[i] - valorMM_i) / dp;
else
ZScoreBuffer[i] = 0;
O resultado numérico é matematicamente idêntico nas duas versões. A diferença está na execução: o Python processa todas as barras históricas de uma vez em memória, enquanto o MQL5 recalcula apenas as barras novas a cada tick comportamento necessário para operar em tempo real no terminal.
Como o MQL5 Gerencia a MME com Handle
No MQL5 a MME não é recalculada dentro do loop. O indicador cria um handle, um ponteiro para o buffer interno da MME já calculada pela plataforma e apenas copia os valores quando precisa.
// OnInit() — cria o handle uma única vez ao carregar o indicador
handleMM = iMA(_Symbol, InpTimeFrame, InpPeriodoMM,
0, MODE_EMA, PRICE_CLOSE);
if(handleMM == INVALID_HANDLE)
{
Print("Erro ao criar handle da MME");
return(INIT_FAILED);
}
// OnCalculate() — copia os valores da MME para o array mm_history
CopyBuffer(handleMM, 0, 0, to_copy, mm_history);
Essa abordagem tem duas vantagens diretas: evita recalcular a MME tick a tick (o MT5 já cuida disso internamente) e garante que o valor usado no cálculo do Z-Score é exatamente o mesmo exibido no gráfico, sem risco de divergência entre o indicador visual e o robô.
Python: Conectando Diretamente ao MetaTrader 5
A versão Python não depende de exportação manual de dados. Ela se conecta ao MT5 instalado na máquina usando a biblioteca MetaTrader5 e baixa o histórico em tempo real com uma chamada direta.
def get_mt5_data(symbol, tf_str, count):
if not mt5.initialize():
print("MetaTrader 5 não encontrado.")
return None
timeframe = TF_MAP.get(tf_str, mt5.TIMEFRAME_M15)
# Copia N candles a partir da barra mais recente (posição 0)
rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, count)
mt5.shutdown() # Libera a conexão imediatamente após copiar
df = pd.DataFrame(rates)
df['time'] = pd.to_datetime(df['time'], unit='s')
df.set_index('time', inplace=True)
return df
O mt5.shutdown() logo após a coleta é uma boa prática: libera o processo do MT5 sem deixar a conexão aberta desnecessariamente. O resultado é um DataFrame com índice de tempo pronto para entrar direto na função de cálculo.
Visualização: Cores por Zona no Python, Linha no MQL5
As duas versões têm abordagens visuais distintas que refletem o contexto de uso de cada uma. O Python coloriza cada barra do histograma de acordo com a zona do Z-Score, ideal para leitura rápida em análises e relatórios. O MQL5 mantém uma linha simples com níveis horizontais tracejados, adequado para uso contínuo no terminal de trading.
Python – colorização dinâmica por zona
def get_color(z):
if z <= inputs["level_ext_neg"]: return "lime" # ≤ −2.5 → compra extrema
if z <= inputs["level_alert_neg"]: return "springgreen" # ≤ −1.5 → compra moderada
if z >= inputs["level_ext_pos"]: return "red" # ≥ +2.5 → venda extrema
if z >= inputs["level_alert_pos"]: return "orangered" # ≥ +1.5 → venda moderada
return "gray" # zona neutra
df['color'] = df['zscore'].apply(get_color)
MQL5 – níveis horizontais configurados no OnInit()
IndicatorSetInteger(INDICATOR_LEVELS, 3); IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, InpNivelAlto); // +2.5 IndicatorSetDouble(INDICATOR_LEVELVALUE, 1, 0.0); // linha zero IndicatorSetDouble(INDICATOR_LEVELVALUE, 2, InpNivelBaixo); // −2.5 IndicatorSetInteger(INDICATOR_LEVELCOLOR, InpCorNiveis); IndicatorSetInteger(INDICATOR_LEVELSTYLE, InpEstiloNivel);

Comparativo Técnico: MQL5 vs Python
| Aspecto | MQL5 (MetaTrader 5) | Python |
|---|---|---|
| Cálculo da MME | Handle iMA() — reutiliza buffer nativo do MT5 | ewm(span=N).mean() via pandas |
| Desvio padrão dinâmico | Loop: soma += diff² → MathSqrt(soma/N) | rolling().mean() + np.sqrt() |
| Divisor do desvio | N (RMS) | N (RMS) — idêntico |
| Proteção div/zero | if(dp > 0) → retorna 0 | Implícita — retorna NaN |
| Execução | Barra a barra, tick a tick | Batch — todas as barras de uma vez |
| Fonte de dados | Gráfico aberto no terminal | copy_rates_from_pos() via API |
| Visualização | Linha + níveis no MT5 | Histograma colorido (Plotly) |
| Uso principal | Trading ao vivo, robôs | Backtesting e análise |
Repositórios: Baixe os Códigos Completos
Os dois projetos estão disponíveis gratuitamente. O código MQL5 está no MQL5 Forge com instalação direta pelo MetaEditor. O Python está no GitHub com instruções de uso no README.
MQL5 – MetaTrader 5 – MQ5 Forge
Python – MT5 + Plotly – GitHub
Para o MQL5: faça o download pelo Forge, copie o arquivo .mq5 para a pasta MQL5/Indicators/ do seu MetaTrader 5, compile com F7 no MetaEditor e arraste para qualquer gráfico.
Para o Python: clone o repositório, instale as dependências com pip install -r requirements.txt, configure o símbolo e o timeframe no dicionário USER_INPUTS e execute python zscore.py com o MetaTrader 5 aberto e logado.
Conclusão
As duas implementações chegam ao mesmo Z-Score por caminhos diferentes e foi intencional. O MQL5 foi construído para operar em tempo real dentro do MetaTrader 5, aproveitando o sistema de handles e o loop incremental do OnCalculate(). O Python foi construído para análise e desenvolvimento, com acesso direto aos dados do MT5 via API e visualização interativa em Plotly.
Usar as duas versões em conjunto é a forma mais eficiente de trabalhar: desenvolva e valide a estratégia no Python, depois aplique no terminal com o indicador MQL5, sabendo que a matemática por trás é exatamente a mesma.
FAQ
Z-Score e desvio padrão são a mesma coisa?
Não. O desvio padrão (σ) mede a dispersão dos dados em torno da média. O Z-Score usa o desvio padrão como unidade de medida para expressar o quanto um valor específico está acima ou abaixo da média. São conceitos relacionados, mas distintos.
Qual é o período ideal para calcular o Z-Score?
Não existe um período universal. Traders intraday costumam usar janelas curtas, de 10 a 20 barras. Estratégias de swing trade utilizam 20 a 60 pregões. Abordagens de longo prazo podem usar 252 pregões (um ano) ou mais. O período deve ser calibrado ao horizonte da estratégia.
Z-Score negativo significa que o ativo está em queda?
Não necessariamente. Um Z-Score negativo indica que o preço atual está abaixo da média histórica do período analisado não que ele esteja caindo no momento. É uma medida de posição relativa, não de direção.
É possível usar Z-Score em qualquer ativo?
Sim. O Z-Score pode ser aplicado a qualquer série temporal com dados suficientes: ações, contratos futuros, câmbio, criptomoedas, ETFs, spreads entre ativos e muito mais. Realize o Backtesting.
Z-Score alto garante uma reversão?
Não. Um Z-Score extremo indica que o preço está estatisticamente distante da média, mas não garante reversão. Mudanças estruturais, notícias fundamentalistas ou novos regimes de mercado podem manter o ativo em território extremo por períodos prolongados. O Z-Score é um sinal de probabilidade, não uma certeza.
Por que o desvio padrão usa divisor N e não N−1?
O divisor N calcula o desvio RMS (Root Mean Square), mais adequado quando o objetivo é medir a dispersão histórica em relação a uma referência externa (a MME) e não a variância amostral de uma distribuição. Usar N−1 produziria valores ligeiramente maiores no denominador, suavizando o Z-Score desnecessariamente para janelas longas
O indicador MQL5 funciona com qualquer timeframe?
Sim. O código usa PERIOD_CURRENT por padrão, adaptando-se automaticamente ao gráfico onde for aplicado. O parâmetro InpTimeFrame também permite forçar um timeframe específico, útil para criar indicadores multi-timeframe.
Preciso do MetaTrader 5 aberto para rodar o Python?
Sim, a função mt5.initialize() requer o terminal instalado e logado (conta real ou demo). Se quiser rodar sem o MT5, substitua a função get_mt5_data() por qualquer outro feed de dados como yfinance para ações americanas ou um CSV exportado do próprio terminal.





