English Русский 中文 Español Deutsch 日本語
preview
Desenvolvimento de ferramentas para análise de Price Action (Parte 37): Indicador de viés do sentimento de mercado

Desenvolvimento de ferramentas para análise de Price Action (Parte 37): Indicador de viés do sentimento de mercado

MetaTrader 5 — Exemplos |
25 1
Christian Benjamin
Christian Benjamin

Introdução

Chegamos à Parte 37 da série Desenvolvimento de ferramentas para análise de Price Action. Meu principal objetivo sempre foi desenvolver ferramentas práticas que ajudem os traders a interpretar o comportamento do mercado e, quando apropriado, automatizar determinados aspectos dessa análise. Até aqui, já exploramos utilitários autônomos em MQL5, integrações externas e soluções híbridas. Neste artigo, vamos ampliar esse conjunto com mais uma ferramenta útil e prática.

As visualizações de dados frequentemente ficam sobrecarregadas por rótulos sobrepostos, indicadores em excesso e anotações dispersas. Isso dificulta identificar as alterações do sinal e compreender a lógica por trás delas. Essa falta de clareza prejudica a tomada de decisões, reduz a confiança em sistemas baseados em regras e dificulta a criação de um histórico verificável de sinais, essencial para uma automação disciplinada.

O Indicador de Viés do Sentimento de Mercado (Sentiment Tilt Meter, STM) resolve esse problema ao agregar métricas de direcionalidade dos candles em diferentes timeframes configuráveis em uma única medida suavizada, que varia de -100 a +100. O resultado é exibido em um painel compacto, sem sobreposição de elementos. As marcações permanentes, como setas e rótulos de texto, são associadas com precisão ao horário de fechamento de cada barra, permitindo verificar cada alerta de forma independente com base na marca de tempo e no nível de preço.

O STM utiliza regras explícitas e parametrizáveis, como limiares de reversão, magnitude e duração da inversão de sinal, critérios de momentum e pesos atribuídos a cada timeframe. Isso permite ajustar a sensibilidade do indicador e encontrar um equilíbrio entre a redução do ruído e a rapidez de resposta.

Começaremos com uma visão geral da estratégia, depois passaremos à implementação em MQL5, analisaremos os testes e seus resultados e encerraremos com uma breve conclusão. O artigo está organizado da seguinte forma:


Visão geral da estratégia

Os mercados estão repletos de ruído, e esse ruído é o principal obstáculo à tomada de decisões consistentes. Os indicadores se acumulam, os rótulos se sobrepõem e, em pouco tempo, torna-se impossível entender o que realmente movimentou o preço e por quê. O Indicador de Viés do Sentimento de Mercado (STM) foi desenvolvido para trazer clareza a esse cenário: fornecer uma medida única e verificável do viés de curto prazo do mercado, apresentar no gráfico os elementos que fundamentam essa avaliação e permitir a tomada de decisões disciplinadas, sem depender de suposições.

Neste contexto, o sentimento de mercado corresponde ao viés de curto prazo, ou seja, à pressão compradora ou vendedora acumulada nos candles recentes. O STM mede esse desequilíbrio diretamente pelo Price Action, convertendo cada candle fechado em uma pontuação de pequena magnitude, mas interpretável. As características individuais extraídas de cada candle são:

  • Proporção do corpo do candle (Candle Body Ratio, CBR):

  • Posição percentual do fechamento (Close Position Percent, CPP):


  • Distância ajustada pela volatilidade (Volatility-Adjusted Distance, VAD):

em que ATR representa o valor de referência recente da amplitude verdadeira, e

clamp(x, a, b) = max(a, min(b, x)).

A pontuação individual de cada candle é calculada como uma soma ponderada. Por exemplo, o valor:

é limitado ao intervalo [-1, +1] e, em seguida, multiplicado por fatores multiplicativos simples que representam o grau de confiança e as condições de mercado calmo. As pontuações são primeiro agregadas pela média dentro da amostra de cada timeframe e depois combinadas de acordo com os pesos definidos pelo usuário:

Por fim, as pontuações passam por uma suavização exponencial:

A seguir, são apresentados os gráficos de preço e da pontuação:

O gráfico da pontuação mostra a evolução do sentimento suavizado ao longo do tempo. Cada cruzamento da linha zero observado antes da filtragem (inversão de sinal) é marcado com um pequeno símbolo ×, enquanto os sinais de mudança de direção confirmados após a aplicação dos filtros de magnitude, persistência e momentum são destacados por pontos maiores. Essa visualização facilita a distinção entre pequenas oscilações causadas pelo ruído, que poderiam gerar sinais falsos, e mudanças efetivas de direção que atenderam aos critérios dos filtros.

O gráfico de preço mostra onde esses sinais confirmados ocorreram na série de preços. As setas e os rótulos BUY ou SELL são posicionados exatamente no nível de preço correspondente ao sinal. Isso permite verificar imediatamente se o sinal coincide com estruturas relevantes do gráfico, como níveis de suporte e resistência, direção da tendência ou máximas e mínimas locais mais próximas.

Para que os resultados sejam considerados satisfatórios, o EA deve gerar sinais que possam ser rastreados diretamente pela marca de tempo e pelo nível de preço, apresentar desempenho confiável em backtests e em testes breves com dados reais, além de minimizar a quantidade de sinais falsos evidentes a um nível que você considere aceitável para operar. Quando esses requisitos são atendidos, o STM cumpre seu objetivo: fornecer uma medida clara, configurável e verificável do viés de curto prazo do mercado, que possa servir de referência para uma tomada de decisão disciplinada.


Implementação em MQL5

Antes de começar a programar, reserve dois minutos para configurar corretamente o ambiente. Esse cuidado economizará bastante tempo nas etapas seguintes. Verifique se o MetaTrader 5 e o MetaEditor estão instalados. O MetaEditor pode ser iniciado como aplicativo independente ou aberto diretamente pelo MetaTrader 5. Ambas as opções funcionam.

Para preparar o EA:

  1. abra o MetaEditor;
  2. acesse Arquivo → Novo arquivo → EA (modelo);
  3. cole o código-fonte do EA no arquivo recém-criado;
  4. salve o arquivo com um nome claro e descritivo;
  5. compile o código pressionando F7.

Verifique se a compilação termina sem erros nem avisos. Se o compilador identificar algum problema, revise e corrija o código e compile novamente até obter uma compilação sem erros nem avisos.

De volta ao MetaTrader 5:

  • execute o EA no Testador de Estratégias ou adicione-o diretamente ao gráfico;
  • monitore o painel, os logs e os sinais;
  • verifique as abas "Experts" e "Diário" para identificar mensagens de execução e informações de depuração.

Essa configuração garante que o ambiente esteja preparado corretamente para desenvolver e testar o EA com eficiência. A seguir, vamos desenvolver o EA Sentiment Tilt Meter (STM) passo a passo e examinar cada etapa da implementação.

O bloco de cabeçalho e metadados define as informações de identificação do EA, os direitos autorais, o link para o perfil do autor e o modo de compilação (#property strict). Essas linhas têm caráter exclusivamente declarativo, mas são importantes: incorporam as informações de versão e autoria ao programa compilado e ativam verificações mais rigorosas durante a compilação. Logo depois, o EA inclui a biblioteca padrão de negociação Trade.mqh, que disponibiliza a classe CTrade e outros recursos auxiliares para operações de trading. Embora o objeto g_trade, da classe CTrade, seja pouco utilizado neste arquivo (ou nem sequer utilizado no código apresentado), a inclusão antecipada da biblioteca deixa o código preparado para enviar ordens posteriormente, sem necessidade de refatoração.

//+------------------------------------------------------------------+
//|                                                            STM EA|
//|                                   Copyright 2025, MetaQuotes Ltd.|
//|                           https://www.mql5.com/ru/users/lynnchris|
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/ru/users/lynnchris"
#property version   "1.0"
#property strict

#include <Trade\Trade.mqh>

O bloco de parâmetros de entrada reúne todas as configurações disponíveis ao usuário. Os parâmetros são agrupados por finalidade: seleção dos timeframes, configuração do algoritmo de sinais, aparência e estilo do painel, notificações sonoras e push, além do comportamento das setas e dos marcadores. O timeframe principal e os timeframes adicionais permitem que o EA combine sinais de diferentes períodos gráficos, enquanto os pesos (InpWeightPrimary, InpWeightExtra1, InpWeightExtra2) determinam a contribuição relativa de cada um nessa combinação. O parâmetro de suavização (InpSmoothAlpha) controla a suavização exponencial das pontuações brutas combinadas entre os timeframes, enquanto os parâmetros de limiar (como InpFlipThreshold, InpPositiveZone, InpNegativeZone) definem quando o EA considera significativa uma mudança de direção e quando a pontuação entra na zona positiva ou negativa.

Os parâmetros visuais controlam a aparência do painel no gráfico, incluindo suas dimensões, o canto de ancoragem, as fontes, as cores, a quantidade de colunas do histograma e os espaçamentos internos. Os parâmetros de setas e marcadores permitem ativar e personalizar os sinais exibidos no gráfico. Além disso, verificações adicionais de proteção, como quantidade mínima de barras, magnitude da inversão de sinal e requisitos de momentum, ajudam a reduzir o ruído e a ocorrência de reversões falsas. Todos os parâmetros de entrada têm tipos explicitamente definidos, permitindo que sejam exibidos na janela de propriedades do EA e alterados sem necessidade de editar o código.

// --- User inputs (layout / visuals tuned) ----------------------------
input ENUM_TIMEFRAMES InpTFPrimary = PERIOD_M5;
input bool InpUseMultiTF = true;
input ENUM_TIMEFRAMES InpExtraTF1 = PERIOD_M15;
input ENUM_TIMEFRAMES InpExtraTF2 = PERIOD_H1;
input int InpLookback = 20;
input int InpSSWindow = 5;
input int InpATRPeriod = 14;
input double InpATRMultiplier = 1.0;
input double InpVolQuietThreshold = 0.4;
input int InpHistogramMaxBars = 24;
input int InpPanelPadding = 10;
input int InpPanelCorner = CORNER_LEFT_UPPER;
input int InpPanelX = 5;
input int InpPanelY = 25;
input string InpFont = "Arial";
input int InpTitleFontSize = 12;
input int InpScoreFontSize = 18;
input int InpSmallFontSize = 9;
input color InpPanelColor = clrBlack;
input int InpPanelAlpha = 200;
input bool InpEnableSound = true;
input string InpSoundFile = "alert.wav";
input bool InpEnablePush = false;
input double InpWeightPrimary = 0.6;
input double InpWeightExtra1 = 0.2;
input double InpWeightExtra2 = 0.2;
input double InpSmoothAlpha = 0.28;
input double InpFlipThreshold = 60.0;
input double InpPositiveZone = 30.0;
input double InpNegativeZone = -30.0;
input int InpMinBarsForSignal = 3;

// arrows / marker options
input bool InpShowArrows = true;
input int  InpArrowFontSize = 16;
input int  InpArrowOffsetPoints = 8;
input int  InpMaxSignalsToKeep = 50;
input bool InpSignalOnSignFlip = true;
input double InpMinSignFlipAbs = 6.0;
input int    InpSignFlipHoldBars = 1;
input bool   InpRequireMomentum = true;
input double InpMinMomentum = 0.5;
//+------------------------------------------------------------------+
Em seguida, vêm as constantes e as variáveis globais. O EA cria um g_prefix exclusivo para cada gráfico, combinando o símbolo e o identificador do gráfico. Esse prefixo define um espaço de nomes para os objetos gráficos, evitando conflitos com outros EAs ou objetos criados manualmente. Também são definidos os handles do indicador ATR para cada timeframe, o buffer do histograma (g_hist_buffer) e os índices necessários para gerenciar o armazenamento circular. As variáveis globais incluem ainda os valores suavizados atual e anterior (g_smoothed_score, g_prev_smoothed), o último valor usado para detectar mudanças bruscas e o estado atual da zona, armazenado em g_zone_state (1, -1 ou 0). O objeto g_trade, da classe CTrade, é declarado para permitir uma futura ampliação do EA com funcionalidades de envio de ordens. As variáveis g_last_signal_text e g_last_signal_time armazenam as informações necessárias para exibir o último sinal no painel.
// globals
long g_chart_id = 0;
string g_prefix = "";
string ui_bg_name = "";
string ui_shadow_name = "";
string ui_title_name = "";
string ui_score_name = "";
string ui_zone_name = "";
string ui_hist_base = "";
string ui_recent_name = "";
string ui_advice_name = "";
string ui_signal_name = "";
int g_atr_handle_primary = INVALID_HANDLE;
int g_atr_handle_extra1 = INVALID_HANDLE;
int g_atr_handle_extra2 = INVALID_HANDLE;
double g_hist_buffer[];
int g_hist_idx = 0;
int g_hist_count = 0;
double g_smoothed_score = 0.0;
double g_prev_smoothed = 0.0;
double g_last_alert_score = 0.0;
int g_zone_state = 0;
CTrade g_trade;
string g_last_signal_text = "None";
datetime g_last_signal_time = 0;
const string BASE_HIST = "STM_HBAR_";
//+------------------------------------------------------------------+
Na sequência, temos algumas funções auxiliares que tornam a interface e o código de renderização mais robustos. A função ARGB_uint constrói um valor inteiro sem sinal no formato ARGB a partir do canal alfa e de uma cor do MQL, permitindo configurar com precisão fundos retangulares semitransparentes. EstimateTextWidth fornece uma estimativa da largura de uma string em pixels, com base no tamanho da fonte e em um coeficiente de largura dos caracteres ajustado empiricamente. Essa estimativa é usada para evitar a sobreposição de rótulos ao posicionar o ícone da zona e a linha Recent. Essas aproximações são adequadas para um painel gráfico, no qual o posicionamento exato não é essencial, mas é importante evitar sobreposições.
// ARGB helper - returns a color as unsigned int
uint ARGB_uint(int a, color c)
  {
   uint u = ((uint)c) & 0x00FFFFFF;
   return ((((uint)a) & 0xFF) << 24) | u;
  }

// approximate text width estimator (pixels)
int EstimateTextWidth(string txt,int fontSize)
  {
   if(StringLen(txt) <= 0)
      return 6;
   double factor = 0.58;
   int w = (int)MathRound(StringLen(txt) * fontSize * factor);
   return MathMax(8, w);
  }
//+------------------------------------------------------------------+
DrawText e DrawCell são funções auxiliares que encapsulam, de forma segura, a criação e a configuração de rótulos de texto e objetos gráficos do tipo rótulo retangular. Elas centralizam a criação dos objetos e a definição de suas propriedades, como canto de ancoragem, deslocamentos nos eixos X e Y, fonte, sinalizador de seleção do objeto e ordem de sobreposição no eixo Z. Assim, todos os rótulos da interface seguem as mesmas regras. O uso dessas funções evita a repetição de trechos padronizados de código e garante um comportamento uniforme durante a reconstrução ou atualização do painel.
// safe DrawText - sets font explicitly and uses exact x/y
void DrawText(string id,string txt,int x,int y,color clr,int sz)
  {
   if(ObjectFind(g_chart_id,id) < 0)
      ObjectCreate(g_chart_id,id,OBJ_LABEL,0,0,0);
   ObjectSetInteger(g_chart_id,id,OBJPROP_CORNER,InpPanelCorner);
   ObjectSetInteger(g_chart_id,id,OBJPROP_XDISTANCE,x);
   ObjectSetInteger(g_chart_id,id,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(g_chart_id,id,OBJPROP_COLOR,(int)clr);
   ObjectSetInteger(g_chart_id,id,OBJPROP_FONTSIZE,sz);
   ObjectSetString(g_chart_id,id,OBJPROP_FONT,InpFont);
   ObjectSetString(g_chart_id,id,OBJPROP_TEXT,txt);
   ObjectSetInteger(g_chart_id,id,OBJPROP_SELECTABLE,false);
   ObjectSetInteger(g_chart_id,id,OBJPROP_ZORDER,0);
  }

// small helper to draw rectangle label cell
void DrawCell(string id,int x,int y,int w,int h,color bg,color br)
  {
   if(ObjectFind(g_chart_id,id) < 0)
      ObjectCreate(g_chart_id,id,OBJ_RECTANGLE_LABEL,0,0,0);
   ObjectSetInteger(g_chart_id,id,OBJPROP_CORNER,InpPanelCorner);
   ObjectSetInteger(g_chart_id,id,OBJPROP_XDISTANCE,x);
   ObjectSetInteger(g_chart_id,id,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(g_chart_id,id,OBJPROP_XSIZE,w);
   ObjectSetInteger(g_chart_id,id,OBJPROP_YSIZE,h);
   ObjectSetInteger(g_chart_id,id,OBJPROP_BGCOLOR,(int)bg);
   ObjectSetInteger(g_chart_id,id,OBJPROP_BORDER_COLOR,(int)br);
   ObjectSetInteger(g_chart_id,id,OBJPROP_SELECTABLE,false);
   ObjectSetInteger(g_chart_id,id,OBJPROP_ZORDER,1);
  }
As funções CreateUIObjects e DeleteUIObjects gerenciam o ciclo de vida de todos os elementos da interface. CreateUIObjects cria antecipadamente os objetos de fundo, sombra, título, pontuação, zona, linha Recent e rótulos do histograma, além de definir as fontes padrão e a ordem de sobreposição. DeleteUIObjects remove esses objetos durante a desinicialização. Essa prática evita que elementos gráficos permaneçam no gráfico após a remoção do EA. A criação antecipada de uma quantidade fixa de objetos para os rótulos do histograma, determinada por InpHistogramMaxBars, evita a criação frequente de objetos durante a execução, melhorando o desempenho e a estabilidade.
void CreateUIObjects()
  {
   int default_w = 200;
   int default_h = 80;
   int scol = (int)ARGB_uint(InpShadowAlpha, InpShadowColor);
   DrawCell(ui_shadow_name, InpPanelX + 3, InpPanelY + 3, default_w, default_h, (color)scol, InpGridClr);
   DrawCell(ui_bg_name, InpPanelX, InpPanelY, default_w, default_h, InpPanelBG, InpGridClr);

   DrawText(ui_title_name, "", InpPanelX + InpPanelPadding, InpPanelY + InpPanelPadding, InpTxtClr, InpTitleFontSize);
   DrawText(ui_score_name, "", InpPanelX + InpPanelPadding, InpPanelY + InpPanelPadding + InpTitleFontSize + 4, InpTxtClr, InpScoreFontSize);
   DrawText(ui_zone_name, "", InpPanelX + default_w - InpPanelPadding - 80, InpPanelY + InpPanelPadding + 4, InpTxtClr, InpSmallFontSize);
   DrawText(ui_advice_name, "", InpPanelX + InpPanelPadding, InpPanelY + InpPanelPadding + InpTitleFontSize + InpScoreFontSize + 8, InpTxtClr, InpSmallFontSize);
   DrawText(ui_recent_name, "", InpPanelX + InpPanelPadding, InpPanelY + default_h - InpPanelPadding - 18, InpTxtClr, InpSmallFontSize);
   DrawText(ui_signal_name, "", InpPanelX + InpPanelPadding + 120, InpPanelY + InpPanelPadding + InpTitleFontSize + InpScoreFontSize + 8, InpTxtClr, InpSmallFontSize);

   for(int i = 0; i < InpHistogramMaxBars; i++)
     {
      string name = ui_hist_base + IntegerToString(i);
      if(ObjectFind(g_chart_id, name) < 0)
        {
         ObjectCreate(g_chart_id, name, OBJ_LABEL, 0, 0, 0);
         ObjectSetInteger(g_chart_id, name, OBJPROP_CORNER, InpPanelCorner);
         ObjectSetString(g_chart_id, name, OBJPROP_FONT, InpFont);
         ObjectSetInteger(g_chart_id, name, OBJPROP_FONTSIZE, InpSmallFontSize);
         ObjectSetInteger(g_chart_id, name, OBJPROP_SELECTABLE, false);
         ObjectSetInteger(g_chart_id, name, OBJPROP_ZORDER,0);
        }
     }
  }

void DeleteUIObjects()
  {
   if(g_chart_id == 0) return;
   if(ObjectFind(g_chart_id, ui_shadow_name) >= 0) ObjectDelete(g_chart_id, ui_shadow_name);
   if(ObjectFind(g_chart_id, ui_bg_name) >= 0) ObjectDelete(g_chart_id, ui_bg_name);
   if(ObjectFind(g_chart_id, ui_title_name) >= 0) ObjectDelete(g_chart_id, ui_title_name);
   if(ObjectFind(g_chart_id, ui_score_name) >= 0) ObjectDelete(g_chart_id, ui_score_name);
   if(ObjectFind(g_chart_id, ui_zone_name) >= 0) ObjectDelete(g_chart_id, ui_zone_name);
   if(ObjectFind(g_chart_id, ui_recent_name) >= 0) ObjectDelete(g_chart_id, ui_recent_name);
   if(ObjectFind(g_chart_id, ui_advice_name) >= 0) ObjectDelete(g_chart_id, ui_advice_name);
   if(ObjectFind(g_chart_id, ui_signal_name) >= 0) ObjectDelete(g_chart_id, ui_signal_name);

   for(int i = 0; i < InpHistogramMaxBars; i++)
     {
      string name = ui_hist_base + IntegerToString(i);
      if(ObjectFind(g_chart_id, name) >= 0) ObjectDelete(g_chart_id, name);
     }
  }

A função RefreshUI é responsável por calcular o layout do painel e renderizar seus elementos e o histograma. Primeiro, determina a altura da fonte e reserva espaço para o título, a pontuação, os textos menores e a linha do histograma. Em seguida, calcula dinamicamente a largura do painel para acomodar o histograma, respeitando uma largura mínima adequada. A função desenha os retângulos de sombra e fundo, calcula a posição do ícone da zona com auxílio de EstimateTextWidth para evitar que ele se sobreponha ao título e seleciona a cor da pontuação suavizada (scoreCol) com base nos limiares configurados. A linha "Recent:" é preenchida com informações das barras mais recentes do timeframe principal, comparando os preços de fechamento e abertura e considerando a amplitude de cada barra em pontos. Se o conteúdo ultrapassar o espaço disponível, o texto é truncado e recebe reticências.

Para o histograma, a função calcula a quantidade de caracteres de bloco necessários para representar visualmente a magnitude da pontuação, associa cada caractere a um objeto de rótulo criado antecipadamente e seleciona as cores conforme o sinal positivo ou negativo do valor. Essa rotina também inclui verificações de proteção: avalia o espaço disponível e, quando necessário, reduz os espaçamentos para evitar a sobreposição dos rótulos, mantendo o painel compacto e legível.

void RefreshUI(double rawScore, double smoothScore)
  {
   int title_h = InpTitleFontSize + 6;
   int score_h = InpScoreFontSize + 8;
   int small_h = InpSmallFontSize + 4;
   int hist_area_h = 22;
   int gap_between_sections = 6;

   int hist_px_step = 7;
   int hist_px_width = InpHistogramMaxBars * hist_px_step;
   int panel_width = MathMax(340, hist_px_width + InpPanelPadding*2 + 30);
   int extra_recent_space = small_h + 12;
   int panel_height = InpPanelPadding*2 + title_h + score_h + small_h + hist_area_h + (gap_between_sections * 4) + extra_recent_space;

   int scol = (int)ARGB_uint(InpShadowAlpha, InpShadowColor);
   DrawCell(ui_shadow_name, InpPanelX + 3, InpPanelY + 3, panel_width, panel_height, (color)scol, InpGridClr);
   DrawCell(ui_bg_name, InpPanelX, InpPanelY, panel_width, panel_height, InpPanelBG, InpGridClr);

   int x_start = InpPanelX + InpPanelPadding;
   int y_title = InpPanelY + InpPanelPadding;
   string titleText = StringFormat("Sentiment Tilt Meter — %s  [%s]", Symbol(), TFToText(InpTFPrimary));
   DrawText(ui_title_name, titleText, x_start, y_title, InpTxtClr, InpTitleFontSize);

   // ... zone, score, advice, recent and histogram painting (omitted here for brevity)
   // The full EA code contains the rest and iterates label objects to draw histogram blocks.
  }

TFToText é uma função auxiliar simples que converte valores de ENUM_TIMEFRAMES em rótulos curtos e fáceis de entender (M1, M5, M15, H1 etc.). Ela é utilizada no título do painel para que o usuário sempre saiba qual timeframe principal o EA está utilizando.
string TFToText(ENUM_TIMEFRAMES tf)
  {
   switch(tf)
     {
      case PERIOD_M1:  return "M1";
      case PERIOD_M5:  return "M5";
      case PERIOD_M15: return "M15";
      case PERIOD_M30: return "M30";
      case PERIOD_H1:  return "H1";
      case PERIOD_H4:  return "H4";
      case PERIOD_D1:  return "D1";
      case PERIOD_W1:  return "W1";
      case PERIOD_MN1: return "MN";
      default:         return IntegerToString((int)tf);
     }
  }

OnInit executa a inicialização: obtém o identificador do gráfico e gera o prefixo dos objetos, redimensiona e inicializa o buffer do histograma, cria os handles do indicador ATR (iATR) para os timeframes solicitados (somente quando o modo multitimeframe está ativado), calcula a pontuação bruta inicial do sentimento combinado por meio de ComputeFusedSentiment e define o valor suavizado e o estado da zona. Em seguida, OnInit cria os objetos da interface, renderiza o painel inicial por meio de RefreshUI e ativa um temporizador de 1 segundo (EventSetTimer(1)), permitindo que OnTimer detecte novas barras no timeframe principal. Nessa implementação, o estado de suavização é inicializado com a primeira pontuação bruta, garantindo que o primeiro valor suavizado exibido seja estável. Observe também que o código chama UpdateZoneState já nessa etapa para determinar corretamente a zona inicial.

OnDeinit realiza a limpeza dos recursos quando o EA é removido: interrompe o temporizador, exclui os objetos da interface e remove todos os marcadores de sinais criados pelo próprio EA, cujos nomes seguem o padrão g_prefix + "SIG_". A função também libera os handles do indicador ATR por meio de IndicatorRelease. Isso garante a liberação adequada dos recursos, evita que rótulos desnecessários permaneçam no gráfico e impede vazamentos de handles de indicadores.

int OnInit()
  {
   g_chart_id = ChartID();
   g_prefix = Symbol() + "_" + IntegerToString((int)g_chart_id) + "_";
   ui_bg_name = g_prefix + "BG";
   ui_shadow_name = g_prefix + "SHDW";
   ui_title_name = g_prefix + "TITLE";
   ui_score_name = g_prefix + "SCORE";
   ui_zone_name = g_prefix + "ZONE";
   ui_hist_base = g_prefix + BASE_HIST;
   ui_recent_name = g_prefix + "RECENT";
   ui_advice_name = g_prefix + "ADVICE";
   ui_signal_name = g_prefix + "LASTSIG";

   ArrayResize(g_hist_buffer, InpHistogramMaxBars);
   ArrayInitialize(g_hist_buffer, 0.0);
   g_hist_idx = 0;
   g_hist_count = 0;

   g_atr_handle_primary = iATR(Symbol(), InpTFPrimary, InpATRPeriod);
   if(InpUseMultiTF)
     {
      g_atr_handle_extra1 = iATR(Symbol(), InpExtraTF1, InpATRPeriod);
      g_atr_handle_extra2 = iATR(Symbol(), InpExtraTF2, InpATRPeriod);
     }

   double raw_init = ComputeFusedSentiment();
   g_smoothed_score = raw_init;
   g_prev_smoothed = g_smoothed_score;
   g_last_alert_score = raw_init;
   UpdateZoneState(raw_init);

   CreateUIObjects();
   RefreshUI(raw_init, g_smoothed_score);

   EventSetTimer(1);
   return(INIT_SUCCEEDED);
  }

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

   // delete signal markers created by this EA (prefix-based)
   string name;
   int total = ObjectsTotal(g_chart_id);
   for(int i = total - 1; i >= 0; --i)
     {
      name = ObjectName(g_chart_id, i);
      if(StringFind(name, g_prefix + "SIG_") == 0)
         ObjectDelete(g_chart_id, name);
     }

   if(g_atr_handle_primary != INVALID_HANDLE)
      IndicatorRelease(g_atr_handle_primary);
   if(g_atr_handle_extra1 != INVALID_HANDLE)
      IndicatorRelease(g_atr_handle_extra1);
   if(g_atr_handle_extra2 != INVALID_HANDLE)
      IndicatorRelease(g_atr_handle_extra2);
  }
O EA utiliza OnTimer como principal mecanismo de verificação periódica. A função consulta a marca de tempo da barra atual no timeframe principal (iTime(Symbol(), InpTFPrimary, 0)) e, caso seja diferente da última marca de tempo processada, chama HandleNewPrimaryBar. Essa abordagem evita executar o processamento a cada disparo do temporizador e permite que a rotina seja acionada apenas uma vez para cada nova barra concluída. É uma solução útil quando os sinais devem ser calculados com base em barras fechadas, mas o monitoramento é realizado por um temporizador em vez de OnTick.
datetime g_last_primary_time = 0;
void OnTimer()
  {
   datetime t = iTime(Symbol(), InpTFPrimary, 0);
   if(t == g_last_primary_time) return;
   g_last_primary_time = t;
   HandleNewPrimaryBar();
  }

void HandleNewPrimaryBar()
  {
   double raw = ComputeFusedSentiment();
   double alpha = MathMax(0.0, MathMin(1.0, InpSmoothAlpha));
   g_prev_smoothed = g_smoothed_score;
   g_smoothed_score = alpha * raw + (1.0 - alpha) * g_smoothed_score;
   g_hist_buffer[g_hist_idx] = g_smoothed_score;
   g_hist_idx = (g_hist_idx + 1) % InpHistogramMaxBars;
   if(g_hist_count < InpHistogramMaxBars) g_hist_count++;
   RefreshUI(raw, g_smoothed_score);
   ProcessAlerts(raw, g_smoothed_score);
  }

HandleNewPrimaryBar calcula uma nova pontuação bruta do sentimento combinado por meio de ComputeFusedSentiment, aplica a suavização exponencial com o coeficiente alpha configurado (g_smoothed_score = alpha * raw + (1 - alpha) * prev), armazena a pontuação suavizada no buffer circular do histograma, atualiza os índices e o contador de registros, atualiza a interface e chama ProcessAlerts para verificar se o novo valor suavizado atende às condições para gerar notificações ou sinais. O buffer circular utiliza g_hist_idx com aritmética modular baseada em InpHistogramMaxBars, enquanto g_hist_count acompanha a quantidade de registros válidos. Essa estrutura mantém um histórico móvel das pontuações, utilizado tanto na representação visual do histograma quanto nas verificações da manutenção do sinal algébrico (positivo ou negativo) da pontuação.

ComputeFusedSentiment é a função de agregação de alto nível responsável por calcular a pontuação final. Primeiro, ela normaliza os valores absolutos dos três pesos para que sua soma seja igual a 1. Caso a soma seja zero, utiliza um mecanismo de segurança que considera apenas o timeframe principal. Em seguida, chama ComputeTFScore para o timeframe principal e, se o modo multitimeframe estiver ativado, para os dois timeframes adicionais. Por fim, calcula a média ponderada e limita a pontuação combinada ao intervalo [-100, 100]. Isso mantém a apresentação no painel e a lógica dos limiares consistentes, independentemente da escala original das pontuações de cada timeframe.

double ComputeFusedSentiment()
  {
   double w1 = MathAbs(InpWeightPrimary);
   double w2 = MathAbs(InpWeightExtra1);
   double w3 = MathAbs(InpWeightExtra2);
   double sum = w1 + w2 + w3;
   if(sum <= 0.0) { w1 = 1.0; w2 = 0.0; w3 = 0.0; sum = 1.0; }
   w1 /= sum; w2 /= sum; w3 /= sum;

   double s1 = ComputeTFScore(Symbol(), InpTFPrimary, InpLookback, InpSSWindow, g_atr_handle_primary);
   if(!InpUseMultiTF) return s1;
   double s2 = ComputeTFScore(Symbol(), InpExtraTF1, InpLookback, InpSSWindow, g_atr_handle_extra1);
   double s3 = ComputeTFScore(Symbol(), InpExtraTF2, InpLookback, InpSSWindow, g_atr_handle_extra2);
   double fused = s1 * w1 + s2 * w2 + s3 * w3;
   return MathMax(-100.0, MathMin(100.0, fused));
  }

double ComputeTFScore(string sym, ENUM_TIMEFRAMES tf, int lookback, int ssWindow, int atrHandle)
  {
   MqlRates rates[];
   int needed = MathMax(lookback, ssWindow) + 6;
   int copied = CopyRates(sym, tf, 0, needed, rates);
   if(copied <= 0) return 0.0;

   double atr = 0.0001;
   if(atrHandle != INVALID_HANDLE)
     {
      double abuf[];
      if(CopyBuffer(atrHandle, 0, 1, 1, abuf) > 0)
         atr = abuf[0] * InpATRMultiplier;
     }
   else
     {
      int rc = MathMin(10, copied - 1);
      double ar = 0.0;
      for(int i = 1; i <= rc; i++) ar += (rates[i].high - rates[i].low);
      if(rc > 0) ar /= rc;
      if(ar > 0) atr = ar * 0.6;
     }

   int limit = MathMin(ssWindow, copied - 1);
   double avgRange = 0.0;
   int vr = MathMin(10, copied - 1);
   for(int k = 1; k <= vr; k++) avgRange += (rates[k].high - rates[k].low);
   if(vr > 0) avgRange /= vr;

   double sumSS = 0.0;
   int count = 0;
   for(int i = 1; i <= limit; i++)
     {
      double open = rates[i].open;
      double close = rates[i].close;
      double high = rates[i].high;
      double low = rates[i].low;
      double range = high - low;
      if(range <= 0.0) continue;
      double dir = (close > open) ? 1.0 : -1.0;
      double cbr = (MathAbs(close - open) / range) * dir;
      double cpp = (((close - low) / range) - 0.5) * 2.0 * dir;
      double vad = 0.0;
      if(atr > 0.0) vad = (range / atr) - 1.0;
      vad = MathMax(-1.0, MathMin(3.0, vad));
      double ss = cbr * 0.4 + cpp * 0.3 + vad * 0.3;
      ss = MathMax(-1.0, MathMin(1.0, ss));

      bool conf = false;
      if((i - 1) >= 0 && (i - 1) <= copied - 1)
        {
         double nOpen = rates[i - 1].open;
         double nClose = rates[i - 1].close;
         if(dir > 0.0 && nClose > nOpen) conf = true;
         if(dir < 0.0 && nClose < nOpen) conf = true;
        }

      double confFactor = conf ? 1.0 : 0.6;
      double volFactor = 1.0;
      if(atr > 0.0 && avgRange / atr < InpVolQuietThreshold) volFactor = 0.6;
      double finalSS = ss * confFactor * volFactor;
      sumSS += finalSS;
      count++;
     }
   if(count == 0) return 0.0;
   double avgSS = sumSS / count;
   return avgSS * 100.0;
  }

ComputeTFScore é a função responsável por calcular as características básicas dos sinais em cada timeframe. Ela copia um conjunto de barras recentes do tipo MqlRates do timeframe solicitado e obtém uma estimativa do ATR: se houver um handle válido do indicador ATR, copia os valores de seu buffer; caso contrário, utiliza como alternativa a média simples dos valores de amplitude verdadeira. O código calcula avgRange e percorre até ssWindow barras anteriores para extrair três características normalizadas de cada uma: cbr (diferença entre fechamento e abertura em relação à amplitude da barra, que representa a direção e a força do movimento), cpp (posição do fechamento dentro da barra, normalizada e ajustada conforme a direção) e vad (distância ajustada pela volatilidade, calculada pela razão entre a amplitude da barra e o ATR, menos uma unidade). Cada característica recebe um peso de 0,4, 0,3 ou 0,3, respectivamente, tem seu valor limitado ao intervalo [-1, 1] e, em seguida, é multiplicada por dois fatores de confiança: confFactor, que verifica se a barra anterior confirmou a direção do movimento, e volFactor, que reduz a contribuição quando a amplitude dos movimentos de preço está anormalmente baixa em relação ao ATR.

As pontuações finais de sentimento de cada barra (finalSS) são acumuladas, e a média desses valores é calculada ao longo da janela de análise. O resultado é multiplicado por 100 para produzir uma faixa de valores semelhante à de uma escala percentual. Em resumo, essa função combina a direcionalidade do movimento, a posição do fechamento dentro da barra, a amplitude relativa da barra e uma confirmação simples entre barras em uma única pontuação para o timeframe analisado.

RecentSmoothedSignHold é uma função auxiliar utilizada pela lógica de inversão de sinal algébrico para verificar se todos os valores suavizados das barras recentes consideradas mantêm o mesmo novo sinal, positivo ou negativo. Ela percorre o buffer circular g_hist_buffer em ordem inversa e retorna false caso algum dos valores suavizados exigidos não satisfaça a condição. Isso evita que uma única barra isolada provoque oscilações indevidas na sinalização.

ProcessAlerts é a função responsável pelas decisões de emissão de alertas e sinais no gráfico. Primeiro, verifica se existe histórico suficiente (InpMinBarsForSignal). Em seguida, verifica se ocorreu uma reversão brusca e significativa, caracterizada por uma diferença absoluta em relação a g_last_alert_score superior a InpFlipThreshold. Nesse caso, emite um alerta, atualiza o estado da zona e, quando necessário, desenha um marcador de texto BUY/SELL no gráfico. Se não houver uma reversão de grande magnitude, a função determina a nova zona com base em InpPositiveZone/InpNegativeZone. Quando InpSignalOnSignFlip está ativado, ela também verifica se ocorreu uma inversão de sinal algébrico (de positivo para negativo ou de negativo para positivo) e aplica três critérios de confirmação: magnitude absoluta mínima da nova pontuação suavizada (InpMinSignFlipAbs), manutenção do novo sinal algébrico nas barras recentes (InpSignFlipHoldBars) e uma verificação opcional de momentum (InpRequireMomentum + InpMinMomentum). Somente quando todos os critérios são atendidos a função reconhece a inversão como um sinal válido, desenha o marcador no gráfico, atualiza g_last_alert_score e define g_zone_state.

Quando ocorre uma transição convencional de zona, com a entrada da pontuação na zona positiva ou negativa, o código também emite alertas, registra o último sinal e sua marca de tempo e desenha um marcador no gráfico. O resultado é uma estratégia conservadora de geração de sinais, com várias camadas de confirmação, que prioriza mudanças estruturais mais claras em vez de oscilações aleatórias causadas pelo ruído.

bool RecentSmoothedSignHold(int bars, int desiredSign)
  {
   if(bars <= 1) return true;
   if(g_hist_count < bars) return false;
   int max = InpHistogramMaxBars;
   int idx = (g_hist_idx - 1 + max) % max;
   for(int i = 0; i < bars; i++)
     {
      double v = g_hist_buffer[(idx - i + max) % max];
      if(desiredSign == 1 && v <= 0.0) return false;
      if(desiredSign == -1 && v >= 0.0) return false;
     }
   return true;
  }

void ProcessAlerts(double rawScore, double smoothScore)
  {
   if(g_hist_count < InpMinBarsForSignal)
     {
      PrintFormat("%s: waiting hist_count=%d (need %d)", __FUNCTION__, g_hist_count, InpMinBarsForSignal);
      return;
     }

   // large flip detection
   if(MathAbs(smoothScore - g_last_alert_score) >= InpFlipThreshold)
     {
      string m = StringFormat("STM Flip: %.1f -> %.1f on %s", g_last_alert_score, smoothScore, Symbol());
      SendAlert(m);
      g_last_alert_score = smoothScore;
      UpdateZoneState(smoothScore);

      int newZoneFlip = 0;
      if(smoothScore >= InpPositiveZone) newZoneFlip = 1;
      else if(smoothScore <= InpNegativeZone) newZoneFlip = -1;
      if(newZoneFlip != 0)
        {
         g_last_signal_text = (newZoneFlip == 1) ? "BUY" : "SELL";
         g_last_signal_time = TimeCurrent();
         DrawSignalOnChart(newZoneFlip, smoothScore);
         PruneOldSignals(InpMaxSignalsToKeep);
        }
      RefreshUI(rawScore, g_smoothed_score);
      return;
     }

  }
UpdateZoneState é uma função simples que atribui a g_zone_state o valor 1, -1 ou 0, conforme a pontuação suavizada e os limiares definidos para as zonas positiva e negativa. SendAlert centraliza a emissão de alertas: reproduz um som quando essa opção está ativada, envia uma notificação push quando habilitada e chama Alert() para exibir uma janela de alerta local no terminal e Print() para registrar a mensagem no log. Essa centralização facilita a ampliação futura das funcionalidades, por exemplo, com notificações por e-mail ou webhooks.
void UpdateZoneState(double smoothScore)
  {
   if(smoothScore >= InpPositiveZone) g_zone_state = 1;
   else if(smoothScore <= InpNegativeZone) g_zone_state = -1;
   else g_zone_state = 0;
  }

void SendAlert(string msg)
  {
   if(InpEnableSound) PlaySound(InpSoundFile);
   if(InpEnablePush) SendNotification(msg);
   Alert(msg);
   Print(__FILE__, ": ", msg);
  }

DrawSignalOnChart é responsável por posicionar o texto BUY/SELL e o símbolo de seta exatamente na última barra fechada do timeframe principal (índice 1). A função gera um nome exclusivo para cada objeto a partir da marca de tempo e de g_hist_idx, evitando conflitos entre sinais diferentes. Em seguida, calcula um pequeno deslocamento vertical em relação ao preço para posicionar o rótulo de texto acima ou abaixo da seta, mantendo a própria seta exatamente no nível de preço do sinal. Para isso, cria dois objetos de texto no gráfico: um para o rótulo BUY/SELL e outro para o símbolo de seta. O EA permite selecionar o símbolo gráfico utilizado para a seta e configura as fontes, cores, ordem de sobreposição e bloqueio de seleção para que essas anotações não interfiram na interação manual do trader com o gráfico. Após criar o sinal, a função chama PruneOldSignals para evitar o acúmulo excessivo de marcadores.

PruneOldSignals executa uma rotina simples de limpeza: percorre todos os objetos do gráfico, seleciona aqueles cujos nomes começam com o prefixo do EA seguido de SIG_, extrai a marca de tempo codificada em cada nome e ordena os objetos pela marca de tempo, retirando sucessivamente o menor valor durante a ordenação. Depois, exclui do gráfico os objetos mais antigos até que a quantidade de objetos restantes corresponda ao limite definido por keep. Isso garante que o gráfico mantenha somente os InpMaxSignalsToKeep marcadores mais recentes, evita o excesso de elementos visuais e mantém baixo o custo de processamento da varredura dos objetos, desde que o limite keep seja definido em um valor razoável.

void DrawSignalOnChart(int zone, double score)
  {
   datetime tm = iTime(Symbol(), InpTFPrimary, 1);
   double price = iClose(Symbol(), InpTFPrimary, 1);
   if(tm == 0 || price == 0.0) return;

   double point = SymbolInfoDouble(Symbol(), SYMBOL_POINT);
   double textOffsetPts = MathMax(1, InpArrowOffsetPoints);
   double textOffset = point * textOffsetPts * 1.5;

   string name = g_prefix + "SIG_" + IntegerToString((int)tm) + "_" + IntegerToString(g_hist_idx);
   string arrName = g_prefix + "SIG_ARR_" + IntegerToString((int)tm) + "_" + IntegerToString(g_hist_idx);

   if(ObjectFind(g_chart_id, name) >= 0) ObjectDelete(g_chart_id, name);
   if(ObjectFind(g_chart_id, arrName) >= 0) ObjectDelete(g_chart_id, arrName);

   double text_price = (zone == 1) ? price + textOffset : price - textOffset;
   if(ObjectCreate(g_chart_id, name, OBJ_TEXT, 0, tm, text_price))
     {
      string txt = (zone == 1) ? "BUY" : ((zone == -1) ? "SELL" : "NEUTRAL");
      color col = (zone == 1) ? clrLime : ((zone == -1) ? clrRed : clrSilver);
      ObjectSetString(g_chart_id, name, OBJPROP_TEXT, txt);
      ObjectSetInteger(g_chart_id, name, OBJPROP_COLOR, (int)col);
      ObjectSetInteger(g_chart_id, name, OBJPROP_FONTSIZE, 12);
      ObjectSetString(g_chart_id, name, OBJPROP_FONT, InpFont);
      ObjectSetInteger(g_chart_id, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(g_chart_id, name, OBJPROP_ZORDER, 2);
     }

   if(InpShowArrows)
     {
      string arrowTxt = (zone == 1) ? "▲" : ((zone == -1) ? "v" : "■");
      if(ObjectCreate(g_chart_id, arrName, OBJ_TEXT, 0, tm, price))
        {
         color acol = (zone == 1) ? clrLime : ((zone == -1) ? clrRed : clrSilver);
         ObjectSetString(g_chart_id, arrName, OBJPROP_TEXT, arrowTxt);
         ObjectSetInteger(g_chart_id, arrName, OBJPROP_COLOR, (int)acol);
         ObjectSetInteger(g_chart_id, arrName, OBJPROP_FONTSIZE, InpArrowFontSize);
         ObjectSetString(g_chart_id, arrName, OBJPROP_FONT, InpFont);
         ObjectSetInteger(g_chart_id, arrName, OBJPROP_SELECTABLE, false);
         ObjectSetInteger(g_chart_id, arrName, OBJPROP_ZORDER, 3);
        }
     }

   PruneOldSignals(InpMaxSignalsToKeep);
  }

void PruneOldSignals(int keep)
  {
   if(keep <= 0) return;
   int total = ObjectsTotal(g_chart_id);
   string names[];
   int times[];
   int n = 0;
   string sigPrefix = g_prefix + "SIG_";
   for(int i = 0; i < total; i++)
     {
      string nm = ObjectName(g_chart_id, i);
      if(StringFind(nm, sigPrefix) == 0)
        {
         string rest = StringSubstr(nm, StringLen(sigPrefix));
         int pos = StringFind(rest, "_");
         if(pos > 0)
           {
            string tsStr = StringSubstr(rest, 0, pos);
            int ts = (int)StringToInteger(tsStr);
            ArrayResize(names, n+1);
            ArrayResize(times, n+1);
            names[n] = nm;
            times[n] = ts;
            n++;
           }
        }
     }
   if(n <= keep) return;

   while(n > keep)
     {
      int minIdx = 0;
      for(int j = 1; j < n; j++) if(times[j] < times[minIdx]) minIdx = j;
      ObjectDelete(g_chart_id, names[minIdx]);
      for(int k = minIdx; k < n-1; k++)
        {
         names[k] = names[k+1];
         times[k] = times[k+1];
        }
      ArrayResize(names, n-1);
      ArrayResize(times, n-1);
      n--;
     }
  }

Por fim, o EA incorpora diversos recursos de proteção e melhorias voltadas à facilidade de uso: prefixos exclusivos para os nomes dos objetos, evitando conflitos; cálculo alternativo do ATR caso os handles do indicador falhem; limitação das pontuações combinadas a um intervalo fixo; suavização com coeficiente alpha configurável para reduzir o ruído; e critérios de confirmação dos sinais em várias etapas, incluindo limiar de reversão, magnitude, manutenção do sinal algébrico e momentum na inversão de sinal, além da entrada em uma nova zona. O código da interface evita a sobreposição de elementos ao posicionar o ícone da zona e a linha Recent. Já o histograma utiliza caracteres de bloco em vez de elementos gráficos, o que reduz o consumo de recursos e mantém a compatibilidade com versões anteriores do MetaTrader 5.


Testes e resultados

Nesta seção, analisaremos os resultados dos testes do EA, incluindo tanto os backtests com dados históricos quanto as observações realizadas durante os testes em condições reais de mercado.

A seguir, apresentamos uma animação GIF do backtest realizado com o índice Crash 300. O painel oferece um resumo compacto e fácil de verificar do estado do STM. A linha superior mostra o nome do indicador, o instrumento (Crash 300 Index) e o timeframe ativo (M5). Logo abaixo, uma pontuação numérica de sentimento, exibida em destaque e com cores, varia de -100 a +100, enquanto um rótulo curto de direção (como BULL, BEAR ou NEUTRAL) permite identificar imediatamente o viés do mercado. Abaixo da pontuação, o painel apresenta o último sinal (por exemplo, "Bias: Long BUY @ 16:30"), permitindo verificar sua direção e o horário exato da barra. Uma barra horizontal de intensidade representa a magnitude da pontuação, enquanto a linha compacta "Recent" exibe as métricas dos candles mais recentes, ajudando a interpretar o resultado no contexto do comportamento dos preços. Todos os elementos estão associados a barras fechadas, permitindo relacionar cada alerta e marcador a uma marca de tempo e a um nível de preço específicos, sem ambiguidades.

Durante os testes em tempo real com o Crash 1000 Index, o EA gerou um sinal BUY que, ao ser conferido pelo horário de fechamento da barra, mostrou-se confirmado após um breve período de manutenção do sinal algébrico. O painel indicava um viés positivo evidente, e o marcador BUY estava associado ao instante exato do fechamento da barra e ao nível de preço correspondente. Assim, tanto o movimento quanto seu momento de ocorrência podem ser verificados diretamente no gráfico. Esse resultado demonstra a capacidade do STM de identificar um viés de alta persistente no curto prazo: a suavização e as verificações de momentum filtraram o ruído e reduziram o risco de um sinal falso antecipado em condições de mercado lateral e errático. O gerenciamento da operação, incluindo dimensionamento da posição, definição de stops e metas de lucro, continuou sob responsabilidade do trader.

  • Teste com o Crash 300 Index


Conclusão

O EA Sentiment Tilt Meter (STM) cumpriu exatamente o objetivo para o qual foi desenvolvido: identificar com clareza e confiabilidade o viés de curto prazo durante os testes com os índices Crash 300 e Crash 1000. Os sinais são fáceis de verificar, pois cada seta e cada rótulo estão associados a uma barra fechada e a um nível de preço. Dessa forma, é possível identificar o que aconteceu e quando, sem depender de suposições. A suavização e as verificações de momentum impediram que o indicador reagisse excessivamente a pequenas oscilações, enquanto a ponderação entre timeframes permite dar maior importância aos horizontes de análise mais relevantes para cada estilo de trading.

Essa ferramenta é um gerador de sinais e um mecanismo de registro desses sinais, não um sistema completo de trading. Ela indica quando surge um viés de mercado, mas não calcula o tamanho da posição, não posiciona stops nem gerencia a saída das operações. Os resultados variam conforme o instrumento e a sessão de negociação. Nos testes apresentados, os índices Crash tiveram bom desempenho, mas outros mercados, como Forex, ações e futuros, podem exigir ajustes diferentes.

Utilize o STM como filtro ou como uma camada adicional de confirmação. Registre os sinais em arquivos CSV, execute backtests com dados históricos de diferentes instrumentos e testes curtos com dados reais. Somente depois disso considere automatizar as entradas. Se você operar com base nos sinais do STM, incorpore-os a um plano simples de gestão de risco, com R fixo, regras claras para o posicionamento do stop e verificação em um timeframe superior. O objetivo é evitar que uma única sessão marcada por oscilações erráticas anule a vantagem acumulada ao longo de um mês.

Em resumo, o STM fornece uma medida clara e verificável do viés de curto prazo do mercado. Ele agiliza a tomada de decisões e ajuda a manter a disciplina operacional. Antes de utilizá-lo em uma conta real, complemente o EA com mecanismos de controle de risco e verificações em várias etapas.







Chart Projector
Analytical Comment
Analytics Master
Analytics Forecaster
Volatility Navigator
Mean Reversion Signal Reaper
Signal Pulse
Metrics Board
External Flow
VWAP
Heikin Ashi FibVWAP
RSI DIVERGENCE
Parabolic Stop and Reverse (PSAR)
Script Quarters Drawer
Intrusion Detector
TrendLoom Tool Quarters Board
ZigZag Analyzer Correlation Pathfinder Market Structure Flip Detector Tool
Correlation Dashboard Currency Strength Meter
PAQ Analysis Tool
Dual EMA Fractal Breaker
Pin bar, Engulfing and RSI divergence
Liquidity Sweep Opening Range Breakout Tool Boom and Crash Interceptor CCI Zer-Line EA
Candlestick Recognition Candlestick Detection using TA-Lib Candle Range Tool MetaTrader 5 Data Ingestor Model Training and Deployment Use of Python Lib
Sentiment Tilt Meter          

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/19137

Arquivos anexados |
Últimos Comentários | Ir para discussão (1)
Mustafa Nail Sertoglu
Mustafa Nail Sertoglu | 25 ago. 2025 em 21:40
Obrigado por compartilhar novas ideias para administrar as negociações; que você tenha dias cheios de saúde ao lado das pessoas que te amam.
Repensando as estratégias clássicas (Parte 15): Estratégia de rompimento da faixa de preços do dia anterior Repensando as estratégias clássicas (Parte 15): Estratégia de rompimento da faixa de preços do dia anterior
Muito antes do surgimento dos computadores, os traders já atuavam nos mercados financeiros e desenvolviam regras práticas para orientar suas decisões. Neste artigo, voltaremos a examinar uma conhecida estratégia de rompimento para avaliar se essa lógica de mercado, adquirida por meio da experiência, pode competir com abordagens sistemáticas. Nossos resultados mostram que, embora a estratégia original apresentasse uma elevada taxa de acerto, seus resultados eram instáveis e sua gestão de risco era deficiente. Ao aperfeiçoar essa abordagem, demonstraremos como o conhecimento acumulado pelos traders discricionários pode ser incorporado a estratégias de trading algorítmico mais robustas.
Operações CRUD no Firebase usando MQL Operações CRUD no Firebase usando MQL
Este artigo apresenta um guia passo a passo para dominar as operações CRUD (Create, Read, Update, Delete, ou criação, leitura, atualização e exclusão) no Firebase, com foco no Realtime Database e no Firestore. Você aprenderá a utilizar os métodos do SDK do Firebase para gerenciar dados de forma eficiente em aplicações web e móveis, desde a criação de novos registros até sua consulta, atualização e exclusão. O artigo também apresenta exemplos práticos de código e boas práticas para estruturar e processar dados em tempo real, ajudando os desenvolvedores a criar aplicações dinâmicas e escaláveis com a arquitetura NoSQL flexível do Firebase.
Recursos do Assistente MQL5 que você precisa conhecer (Parte 79): Uso dos osciladores Gator e A/D no aprendizado supervisionado Recursos do Assistente MQL5 que você precisa conhecer (Parte 79): Uso dos osciladores Gator e A/D no aprendizado supervisionado
No artigo anterior, concluímos a análise da combinação dos osciladores Gator e Acumulação/Distribuição (A/D) em sua aplicação convencional, baseada nos sinais brutos gerados por esses indicadores. Eles se complementam: o Gator atua como indicador de tendência, enquanto o oscilador Acumulação/Distribuição funciona como indicador de volume. Agora, daremos continuidade ao tema, investigando como o aprendizado supervisionado pode melhorar alguns dos padrões de sinal estudados anteriormente. Nossa abordagem utiliza uma rede neural convolucional (CNN), na qual a regressão por kernel e a similaridade baseada no produto escalar são empregadas para definir os tamanhos dos kernels e a quantidade de canais. Como de costume, implementaremos essa abordagem em um arquivo personalizado da classe de sinal, que interage com o Assistente MQL5 durante a criação do EA.
EAs com auto-otimização em MQL5 (Parte 12): Construção de classificadores lineares por meio da fatoração de matrizes EAs com auto-otimização em MQL5 (Parte 12): Construção de classificadores lineares por meio da fatoração de matrizes
Neste artigo, exploraremos o papel fundamental da fatoração de matrizes no trading algorítmico, especialmente em aplicações MQL5. Dos modelos de regressão aos classificadores multiclasse, apresentaremos exemplos práticos que mostram como é simples integrar esses métodos usando as funções nativas do MQL5. Seja para prever a direção dos preços ou modelar o comportamento de indicadores, este guia fornecerá uma base sólida para desenvolver sistemas de negociação inteligentes com métodos matriciais.