Sincronização de gráficos para facilitar a análise técnica
Um problema recorrente na análise técnica é a falta de sincronização das marcações gráficas entre diferentes timeframes. Embora a maioria das plataformas de negociação permita exibir objetos, como linhas de tendência e zonas, em diferentes timeframes ao alternar entre eles, a análise simultânea de vários timeframes ainda apresenta limitações. Por exemplo, os traders podem abrir várias janelas de gráfico do mesmo instrumento, configurando um timeframe diferente em cada uma para obter uma visão mais completa do mercado. No entanto, sem sincronização, cada gráfico funciona de forma independente. Isso significa que ações como rolagem, zoom ou mudança de instrumento precisam ser repetidas em cada janela. Como resultado, a análise fica fragmentada, aumenta o trabalho manual e cresce o risco de deixar passar sinais importantes que se repetem em diferentes gráficos.
A sincronização resolve esse problema ao vincular várias janelas de gráfico do mesmo instrumento, configuradas em diferentes timeframes. Ações como rolagem, zoom ou mudança de instrumento são reproduzidas em todos os gráficos sincronizados, facilitando a visualização e a comparação do mesmo contexto em diferentes timeframes. Assim, é possível acompanhar e comparar simultaneamente o mesmo movimento de preços em diferentes escalas. Com isso, a análise se torna mais integrada, exige menos intervenções manuais e ganha precisão e consistência ao considerar múltiplos timeframes. Isso, por sua vez, ajuda os traders a tomar decisões de negociação mais rápidas e fundamentadas.
Planejamento e lógica do indicador de sincronização de gráficos
Objetivo principal
Desenvolver um sistema de sincronização de gráficos em múltiplos timeframes, no qual os objetos desenhados nos timeframes mais altos sejam exibidos automaticamente nos mais baixos, preservando a hierarquia e o estilo original.
1. Principais requisitos funcionais:
- Seleção do timeframe de referência: o usuário define o timeframe principal por meio de um parâmetro de entrada. Os demais gráficos do mesmo instrumento, configurados em outros timeframes, são sincronizados.
- Hierarquia dos timeframes:
PERIOD_H1 (Highest) ↓ PERIOD_M30 ↓ PERIOD_M15 ↓ PERIOD_M5 (Lowest)
- Regras de sincronização dos objetos: os objetos criados em qualquer timeframe são exibidos automaticamente em todos os timeframes inferiores. Por exemplo, um objeto criado no H1 também será exibido no M30, M15 e M5.
- Função de reset: remove os objetos de todos os gráficos sincronizados, sem afetar o gráfico de preços propriamente dito.
2. Arquitetura do sistema:

3. Lógica de funcionamento:

4. Lógica de sincronização

5. Etapas de desenvolvimento:
- Inicialização dos gráficos: o gráfico principal é configurado com o timeframe selecionado pelo usuário. Os demais gráficos recebem os timeframes H1/M30/M15/M5, e seus IDs são armazenados para compor o grupo de sincronização.
- Rastreamento dos objetos: cada objeto recebe uma marcação que identifica seu timeframe de origem, e as cópias recebem identificadores no formato [OriginalName]clone[ChartID].
- Gerenciamento da hierarquia: é mantida uma lista de prioridades dos timeframes.
const ENUM_TIMEFRAMES timeframes[4] = {PERIOD_H1, PERIOD_M30, PERIOD_M15, PERIOD_M5};
- Reset: remove todos os objetos, exceto os elementos do gráfico de preços e o botão de reset.
Primeiros passos
//+------------------------------------------------------------------+ //| ChartSyncIndicator.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "GIT under MetaQuotes Ltd." #property version "1.01" #property indicator_chart_window #property indicator_plots 0 input ENUM_TIMEFRAMES BaseTimeframe = PERIOD_H1; // Base timeframe for objects input bool SyncAllTimeframes = false; input color DefaultObjColor = clrDodgerBlue; input ENUM_LINE_STYLE DefaultObjStyle = STYLE_SOLID; input int DefaultObjWidth = 2; input bool DefaultObjBack = true; input bool SyncUnlockedObjects = false; input int ResetButtonX = 10; input int ResetButtonY = 20;
Primeiro, definimos os parâmetros do indicador de sincronização que podem ser configurados pelo usuário. O parâmetro BaseTimeFrame permite selecionar o timeframe principal (por exemplo, H1), a partir do qual os objetos gráficos serão sincronizados com os demais gráficos. O parâmetro SyncAllTimeFrames, do tipo bool, determina se os objetos serão sincronizados com todos os timeframes ou apenas com os selecionados. As propriedades visuais dos objetos, como cor (DefaultObjColor), estilo de linha (DefaultObjStyle) e espessura da linha (DefaultObjWidth), podem ser ajustadas para facilitar a visualização. O parâmetro DefaultObjBack determina se os objetos serão exibidos no plano de fundo do gráfico.
O parâmetro SyncUnlockedObjects determina se a sincronização será restrita aos objetos desbloqueados, permitindo controlar quais elementos editáveis serão sincronizados. Por fim, os parâmetros ResetButtonX e ResetButtonY definem a posição do botão de reset na tela, que permite remover os objetos sincronizados.
#define OBJ_ORIGIN_TF "OBJ_ORIGIN_TF_" #define RESET_BUTTON "resetBtn" long baseChartID; string baseSymbol; string timeframesAssigned[]; ENUM_TIMEFRAMES availableTFs[4] = {PERIOD_H1, PERIOD_M30, PERIOD_M15, PERIOD_M5};Em seguida, definimos as constantes e variáveis básicas do sistema. Usando #define, criamos as constantes de string OBJ_ORIGIN_TF_ (utilizada como prefixo para identificar o timeframe de origem de um objeto) e resetBtn (que identifica o botão de reset no gráfico). A variável baseChartID armazena o ID do gráfico principal, que serve de referência para a sincronização. A variável baseSymbol armazena o símbolo do instrumento negociado nesse gráfico. O array dinâmico timeframesAssigned armazena os IDs dos gráficos selecionados para sincronização. Já o array de tamanho fixo availableTFs contém quatro timeframes: H1, M30, M15 e M5.
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { baseChartID = ChartID(); baseSymbol = Symbol(); // Set this chart to user-selected base timeframe if(Period() != BaseTimeframe) { ChartSetSymbolPeriod(baseChartID, baseSymbol, BaseTimeframe); } AssignTimeframesToCharts(); CreateResetButtons(); return INIT_SUCCEEDED; }
Ao carregar o indicador, primeiro obtemos o ID do gráfico atual (baseChartID) e seu símbolo (baseSymbol). Em seguida, verificamos se o timeframe atual corresponde ao valor definido em BaseTimeFrame. Caso não corresponda, o gráfico é alternado automaticamente para o timeframe de referência por meio da função ChartSetSymbolPeriod(). Depois de configurar o timeframe desejado, chamamos as funções AssignTimeFramesToChart() (para atribuir os timeframes aos gráficos e rastreá-los) e CreateResetButtons() (para criar o botão de reset no gráfico).
void AssignTimeframesToCharts() { // Create a list of timeframes excluding the base TF ENUM_TIMEFRAMES timeframes[3]; int index = 0; for(int i = 0; i < 4; i++) { if(availableTFs[i] != BaseTimeframe) { timeframes[index] = availableTFs[i]; index++; } } int tfIndex = 0; long currChart = ChartFirst(); ArrayResize(timeframesAssigned, 0); while(currChart != -1 && tfIndex < ArraySize(timeframes)) { if(currChart != baseChartID) { ChartSetSymbolPeriod(currChart, baseSymbol, timeframes[tfIndex]); int size = ArraySize(timeframesAssigned); ArrayResize(timeframesAssigned, size + 1); timeframesAssigned[size] = (string)currChart; tfIndex++; } currChart = ChartNext(currChart); } }
A função responsável pela atribuição dos timeframes monta a lista dos timeframes que participarão da sincronização, excluindo o timeframe de referência. Primeiro, cria um array temporário chamado timeframes para armazenar os timeframes adicionais definidos na lista availableTFs. Em seguida, percorre os valores disponíveis em availableTFs e adiciona ao array timeframes todos aqueles que diferem do BaseTimeFrame selecionado pelo usuário. Assim, todos os timeframes, exceto o de referência (por exemplo, M30, M15 e M5, quando H1 é o timeframe principal), são atribuídos a janelas de gráfico separadas para sincronização.
Em seguida, percorremos todos os gráficos abertos usando ChartFirst() e ChartNext() para identificar aqueles que participarão da sincronização. O gráfico principal é ignorado com base em seu identificador baseChartID, enquanto os demais recebem novos timeframes por meio da função ChartSetSymbolPeriod(). À medida que os timeframes são atribuídos, o ID de cada gráfico é convertido em string e armazenado no array timeframesAssigend, que permite identificar quais gráficos participam da sincronização. O laço continua até que todos os timeframes tenham sido atribuídos ou não haja mais janelas disponíveis.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { switch(id) { case CHARTEVENT_OBJECT_CREATE: case CHARTEVENT_OBJECT_DRAG: PropagateObject(sparam, (ENUM_TIMEFRAMES)Period()); break; case CHARTEVENT_OBJECT_DELETE: DeleteSyncedInstances(sparam); break; case CHARTEVENT_OBJECT_CLICK: if(sparam == RESET_BUTTON) { DeleteAllSyncedObjects(); } break; } }
A função OnChartEvent() trata as ações do usuário sobre os objetos gráficos e executa as operações necessárias para manter a sincronização. Quando um objeto é criado ou arrastado (OBJECT_CREATE ou OBJECT_DRAG), a função PropagateObject() é chamada para duplicá-lo ou atualizá-lo em todos os gráficos sincronizados. Quando um objeto é excluído (OBJECT_DELETE), a função DeleteSyncedInstances() remove suas cópias dos demais gráficos. Ao clicar no botão de reset (OBJECT_CLICK, sparam == RESET_BUTTON), o usuário aciona a função DeleteAllSyncedObjects(), que remove todos os objetos sincronizados. Essa lógica orientada a eventos garante que qualquer alteração nos objetos gráficos seja refletida imediatamente em todos os timeframes correspondentes.
void PropagateObject(string objName, ENUM_TIMEFRAMES srcTF) { if(StringFind(objName, "_clone_") != -1) return; bool isLocked = ObjectGetInteger(ChartID(), objName, OBJPROP_SELECTABLE); if(!SyncUnlockedObjects && !isLocked) return; for(int i = 0; i < ArraySize(timeframesAssigned); i++) { long targetChart = (long)StringToInteger(timeframesAssigned[i]); ENUM_TIMEFRAMES targetTF = (ENUM_TIMEFRAMES)ChartPeriod(targetChart); bool shouldPropagate = false; if(SyncAllTimeframes) { shouldPropagate = true; } else { // Propagate from base timeframe to all others if(srcTF == BaseTimeframe) { shouldPropagate = true; } // Propagate from other timeframes only to lower timeframes else if(srcTF > targetTF) { shouldPropagate = true; } } if(shouldPropagate) { CloneObject(objName, ChartID(), targetChart); } } }
A função PropagateObject() é responsável por copiar objetos gráficos, como linhas de tendência e outras formas, para os demais timeframes. Primeiro, ignora os objetos clonados, identificados pela presença de "clone" no nome, para evitar a propagação recursiva dos objetos. Em seguida, verifica se o objeto pode ser selecionado. Se o parâmetro SyncUnlockedObjects estiver definido como false e o objeto não puder ser selecionado, a função encerra sua execução. Isso evita a sincronização acidental de objetos não interativos que podem não ter sido destinados à replicação.
Na etapa principal, a função percorre todos os gráficos incluídos na sincronização, cujos identificadores estão armazenados no array timeframesAsigned. Para cada gráfico, verifica se o objeto deve ser propagado de acordo com as regras de sincronização vigentes. Se o parâmetro SyncAllTimeframes estiver ativado, o objeto será copiado para todos os gráficos, independentemente dos timeframes de origem e destino. Caso contrário, aplica-se a seguinte regra: se o objeto tiver origem no timeframe de referência (BaseTimeframe), ele será propagado para todos os demais timeframes. Se estiver em um timeframe mais alto (por exemplo, H1) e o timeframe de destino for inferior (por exemplo, M15), também será copiado. Quando as condições de sincronização são atendidas, a função CloneObject() é chamada para copiar o objeto do gráfico de origem para o gráfico de destino, mantendo a sincronização visual.
void CloneObject(string objName, long srcChart, long targetChart) { int type = (int)ObjectGetInteger(srcChart, objName, OBJPROP_TYPE); string newName = objName + "_clone_" + (string)targetChart; if(!ObjectCreate(targetChart, newName, (ENUM_OBJECT)type, 0, 0, 0)) return; datetime time1 = (datetime)ObjectGetInteger(srcChart, objName, OBJPROP_TIME, 0); double price1 = ObjectGetDouble(srcChart, objName, OBJPROP_PRICE, 0); ObjectSetInteger(targetChart, newName, OBJPROP_TIME, 0, time1); ObjectSetDouble(targetChart, newName, OBJPROP_PRICE, 0, price1); datetime time2 = (datetime)ObjectGetInteger(srcChart, objName, OBJPROP_TIME, 1); double price2 = ObjectGetDouble(srcChart, objName, OBJPROP_PRICE, 1); if(time2 > 0 || price2 > 0) { ObjectSetInteger(targetChart, newName, OBJPROP_TIME, 1, time2); ObjectSetDouble(targetChart, newName, OBJPROP_PRICE, 1, price2); } color objColor = ObjectGetInteger(srcChart, objName, OBJPROP_COLOR, 0); ENUM_LINE_STYLE objStyle = (ENUM_LINE_STYLE)ObjectGetInteger(srcChart, objName, OBJPROP_STYLE, 0); int objWidth = ObjectGetInteger(srcChart, objName, OBJPROP_WIDTH, 0); bool objBack = ObjectGetInteger(srcChart, objName, OBJPROP_BACK, 0); ObjectSetInteger(targetChart, newName, OBJPROP_COLOR, objColor != clrNONE ? objColor : DefaultObjColor); ObjectSetInteger(targetChart, newName, OBJPROP_STYLE, objStyle); ObjectSetInteger(targetChart, newName, OBJPROP_WIDTH, objWidth); ObjectSetInteger(targetChart, newName, OBJPROP_BACK, objBack); ObjectSetInteger(targetChart, newName, OBJPROP_SELECTABLE, false); ObjectSetInteger(targetChart, newName, OBJPROP_HIDDEN, true); string originTag = OBJ_ORIGIN_TF + EnumToString((ENUM_TIMEFRAMES)Period()); ObjectSetString(targetChart, newName, OBJPROP_TOOLTIP, originTag); if(type == OBJ_TEXT || type == OBJ_LABEL) { string txt = ObjectGetString(srcChart, objName, OBJPROP_TEXT); ObjectSetString(targetChart, newName, OBJPROP_TEXT, txt); } if(type == OBJ_FIBO) { int levels = (int)ObjectGetInteger(srcChart, objName, OBJPROP_LEVELS); ObjectSetInteger(targetChart, newName, OBJPROP_LEVELS, levels); for(int i = 0; i < levels; i++) { double level = ObjectGetDouble(srcChart, objName, OBJPROP_LEVELVALUE, i); ObjectSetDouble(targetChart, newName, OBJPROP_LEVELVALUE, i, level); } } }
A função CloneObject() cria uma cópia de um objeto do gráfico de origem (srcChart) no gráfico de destino (targetChart). Primeiro, identifica o tipo do objeto (por exemplo, linha de tendência, retângulo ou níveis de Fibonacci) e gera um nome exclusivo, acrescentando clone e o ID do gráfico de destino ao nome original. Se a função ObjectCreate() não conseguir criar o objeto no gráfico de destino, a execução é encerrada antecipadamente. Em seguida, copia a primeira coordenada (tempo e preço) do objeto original para a posição correspondente no clone. Se houver um segundo ponto, como ocorre em canais e linhas de tendência, suas coordenadas também serão copiadas.
Depois, a função copia as propriedades visuais do objeto original para o clone, incluindo cor, estilo de linha (por exemplo, contínua ou tracejada), espessura e exibição em segundo plano (OBJPROP_BACK). O objeto clonado é configurado para não permitir seleção (OBJPROP_SELECTABLE = false) e fica oculto na lista de objetos (OBJPROP_HIDDEN = true), evitando alterações acidentais e reduzindo a quantidade de elementos exibidos nessa lista. Além disso, a propriedade OBJPROP_TOOLTIP é utilizada para adicionar uma dica de ferramenta que identifica o timeframe de origem. Isso facilita a identificação da origem de cada objeto, especialmente ao trabalhar com vários gráficos sincronizados.
Por fim, a função trata alguns tipos especiais de objetos. No caso de um objeto de texto ou de um rótulo simples, o conteúdo textual exibido também é copiado para o clone. Para objetos de Fibonacci (OBJ_FIBO), a função copia a quantidade de níveis e os respectivos valores de preço. Dessa forma, mantém-se a sincronização funcional e visual entre os gráficos.
//+------------------------------------------------------------------+ //| Required OnCalculate function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { return(rates_total); }
A lógica principal do indicador não depende da função OnCalculate(), pois seu funcionamento se baseia exclusivamente nos eventos dos gráficos e dos objetos. No entanto, a função OnCalculate() continua sendo obrigatória em indicadores personalizados, e sua ausência provoca um erro de compilação. Por isso, foi incluída uma implementação mínima que apenas retorna rates_total, sem realizar qualquer cálculo.
void DeleteSyncedInstances(string objName) { long thisChart = ChartID(); bool isClone = StringFind(objName, "_clone_") != -1; string baseName = isClone ? StringSubstr(objName, 0, StringFind(objName, "_clone_")) : objName; // Delete from all charts ObjectDelete(thisChart, objName); for(int i = 0; i < ArraySize(timeframesAssigned); i++) { long chartID = (long)StringToInteger(timeframesAssigned[i]); ObjectDelete(chartID, baseName); ObjectDelete(chartID, baseName + "_clone_" + (string)thisChart); } }
A função DeleteSyncedInstances() remove todas as instâncias correspondentes de um objeto gráfico nos demais gráficos quando ele é excluído de um deles. Primeiro, verifica se o objeto excluído é um clone, identificando a presença de "clone" em seu nome, e extrai o nome original correspondente. Em seguida, remove o objeto do gráfico atual e percorre todos os gráficos registrados em timeframesAssigned, excluindo tanto o objeto original quanto qualquer clone associado a ele.
void DeleteAllSyncedObjects() { long chartIDs[]; ArrayResize(chartIDs, ArraySize(timeframesAssigned) + 1); chartIDs[0] = baseChartID; for(int i = 0; i < ArraySize(timeframesAssigned); i++) chartIDs[i + 1] = (long)StringToInteger(timeframesAssigned[i]); for(int j = 0; j < ArraySize(chartIDs); j++) { int total = ObjectsTotal(chartIDs[j]); for(int i = total - 1; i >= 0; i--) { string name = ObjectName(chartIDs[j], i); if(StringFind(name, RESET_BUTTON) == -1) ObjectDelete(chartIDs[j], name); } } }
A função DeleteAllSyncedObjects() remove todos os objetos gráficos dos gráficos sincronizados, preservando apenas os botões de reset. Primeiro, cria um array chamado chartIDs, que reúne o ID do gráfico principal e todos os IDs armazenados em timeframesAsigned. Em seguida, percorre os objetos existentes em cada gráfico, em ordem inversa, e os exclui, exceto aqueles cujo nome contém RESET_BUTTON. Essa função permite limpar rapidamente a área de trabalho, removendo todos os objetos gráficos utilizados na sincronização.
void CreateResetButtons() { CreateResetButtonOnChart(baseChartID); for(int i=0; i<ArraySize(timeframesAssigned); i++) CreateResetButtonOnChart((long)StringToInteger(timeframesAssigned[i])); }A função CreateResetButtons() adiciona um botão de reset ao gráfico principal e a todos os gráficos sincronizados. Para isso, percorre as janelas de gráfico em um laço e chama a função CreateResetButtonOnChart() para criar o botão em cada uma delas. Assim, o usuário pode remover todos os objetos sincronizados do gráfico principal com um único clique.
void CreateResetButtonOnChart(long cid) { if(ObjectFind(cid, RESET_BUTTON) >= 0) ObjectDelete(cid, RESET_BUTTON); if(!ObjectCreate(cid, RESET_BUTTON, OBJ_BUTTON, 0, 0, 0)) return; ObjectSetInteger(cid, RESET_BUTTON, OBJPROP_XDISTANCE, ResetButtonX); ObjectSetInteger(cid, RESET_BUTTON, OBJPROP_YDISTANCE, ResetButtonY); ObjectSetInteger(cid, RESET_BUTTON, OBJPROP_XSIZE, 80); ObjectSetInteger(cid, RESET_BUTTON, OBJPROP_YSIZE, 20); ObjectSetInteger(cid, RESET_BUTTON, OBJPROP_BGCOLOR, clrGray); ObjectSetInteger(cid, RESET_BUTTON, OBJPROP_COLOR, clrWhite); ObjectSetString(cid, RESET_BUTTON, OBJPROP_TEXT, "Reset All"); ObjectSetInteger(cid, RESET_BUTTON, OBJPROP_SELECTABLE, false); ObjectSetInteger(cid, RESET_BUTTON, OBJPROP_HIDDEN, false); ChartRedraw(cid); }
A função CreateResetButtonOnChart() cria um botão de reset no gráfico especificado (cid). Primeiro, verifica se já existe um botão com o mesmo nome e, caso exista, remove-o para evitar duplicações. Em seguida, cria um novo botão (OBJ_BUTTON) e define sua posição por meio dos parâmetros ResetButtonX e ResetButtonY, além de configurar dimensões fixas, cor de fundo, cor do texto e rótulo. O botão é configurado para não permitir seleção, mas permanece visível na lista de objetos (HIDDEN = false), permitindo que o usuário interaja com ele.
void OnDeinit(const int reason) { DeleteAllSyncedObjects(); }Por fim, a função OnDeinit() é chamada quando o indicador é removido ou o gráfico é fechado. Ela remove todos os objetos sincronizados, evitando que elementos desnecessários permaneçam nos gráficos.
Demonstração:

Conclusão
Desenvolvemos um indicador de sincronização de gráficos em múltiplos timeframes que facilita a análise técnica ao duplicar automaticamente objetos gráficos, como linhas de tendência, retângulos e níveis de Fibonacci, nos timeframes selecionados. Começamos pela configuração dos parâmetros que permitem controlar o estilo dos objetos, as regras de sincronização e os elementos da interface, incluindo o botão de reset. Implementamos as principais funcionalidades: atribuição de timeframes às janelas de gráfico, monitoramento e tratamento dos eventos relacionados aos objetos (criação, movimentação e exclusão), além da manutenção da sincronização por meio da clonagem e de uma convenção uniforme para os nomes dos objetos. Funções auxiliares também foram implementadas para criar o botão de reset e remover todos os objetos durante a desinicialização ou quando o usuário solicita a limpeza.
Com isso, obtivemos uma ferramenta prática para analisar vários timeframes simultaneamente, sem precisar redesenhar ou atualizar manualmente os objetos em cada gráfico. O indicador mantém a consistência e a clareza das marcações em diferentes escalas temporais de um mesmo instrumento, simplificando a análise e reduzindo a carga cognitiva. Graças à sincronização dos objetos e à interação fluida entre os timeframes, os traders conseguem identificar com mais facilidade confirmações de sinais, acompanhar níveis importantes e tomar decisões de negociação mais fundamentadas.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/18937
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.