English Русский Deutsch 日本語
preview
通过图表同步简化技术分析

通过图表同步简化技术分析

MetaTrader 5示例 |
25 1
Hlomohang John Borotho
Hlomohang John Borotho

交易者在开展技术分析时经常面临一个挑战:不同周期图表的标注无法统一。虽然大多数交易平台支持在切换周期时,趋势线、价格区域等图形对象跨周期显示,但并排对多周期进行分析仍然效率低下。交易者常常为同一个品种打开多个图表窗口,每个窗口设置不同周期,以此获取更全面的技术视角。然而,如果没有同步功能,每张图表都各自独立运行。这就意味着滚动、缩放、切换品种这类操作必须在每张图表上重复执行,造成分析碎片化、工作量增加,也更容易遗漏关键共振区域。

图表同步功能通过关联同一品种、不同周期的多个图表窗口解决该问题。诸如平移、缩放、切换品种等操作会在所有已同步图表上体现,交易者能够在多个周期下流畅地查看并对比同一价格走势环境。这种统一联动方式简化了分析流程,减少人工操作,保障多周期分析更加连贯和准确,最终帮助交易者更快做出具备充分依据的交易决策。



图表同步指标的方案规划与逻辑

核心目标

搭建一套多周期图表同步系统:在大周期上绘制的图形对象,可自动呈现在小周期图表中,并保持正确的周期层级关系与样式。

1. 核心功能需求

  • 基准周期选择:用户通过输入参数选定所需基准周期;其余所有图表均绑定至该交易品种,并使用不同周期。
  • 周期层级:          
PERIOD_H1 (Highest)
   ↓
PERIOD_M30
   ↓
PERIOD_M15
   ↓
PERIOD_M5 (Lowest)
  • 对象传递规则:在任意周期创建的图形对象,会自动显示在所有更小周期的图表上(即向下级周期自动传递)。例如,H1周期绘制的对象,在M30、M15、M5周期均可见。
  • 重置功能:清空所有同步图表上的绘图对象(保留价格图表本身)。

2. 系统架构:

系统架构图

3. 工作流逻辑:

工作流逻辑:

4. 传递逻辑:

传递逻辑图

5. 开发流程:

  • 图表初始化:将基准图表设置为用户选定的周期。其余图表分配至剩余周期(H1/M30/M15/M5),并保存图表ID,归入同步组。
  • 对象追踪:给图形对象附加来源周期元数据,生成克隆ID:[OriginalName]_clone_[ChartID]
  • 层级管理:维护周期优先级列表。            
const ENUM_TIMEFRAMES timeframes[4] = {PERIOD_H1, PERIOD_M30, PERIOD_M15, PERIOD_M5};
  • 系统重置:删除所有同步图表上的对象(保留价格图表元素及重置按钮本身)。


入门指南

//+------------------------------------------------------------------+
//|                                        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;

首先,我们为图表同步指标定义可供用户配置的输入参数。BaseTimeframe(基准周期)输入项供用户选择主周期(例如H1),在该周期绘制的图形对象将同步至其他图表。布尔型参数SyncAllTimeframes用于控制对象是同步到全部周期,还是仅同步到选定周期。图形对象的可视化属性支持自定义,包括颜色(DefaultObjColor)、线型(DefaultObjStyle)、线宽(DefaultObjWidth),以此保证显示清晰和风格统一。DefaultObjBack参数用于设置图形对象是否绘制在图表底层(背景层)。

SyncUnlockedObjects用于指定是否只同步未锁定的对象,以此管控可编辑绘图元素。最后,ResetButtonX与ResetButtonY参数决定重置按钮在屏幕上的坐标位置,该按钮用于清除所有已同步的绘图对象。

#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};
这里,我们定义图表同步系统所需的基础常量与变量。#define预处理指令创建两个字符串常量:OBJ_ORIGIN_TF用于标记图形对象来源周期的前缀;RESET_BUTTON代表图表上的重置按钮。变量baseChartID存储主图表(基准图表)的ID,该图表作为同步操作的参照基准。baseSymbol保存该图表对应的交易品种。timeframesAssigned为动态字符串数组,用于记录参与同步的图表ID。最后,availableTFs是固定数组,包含4个指定周期:H1、M30、M15和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;
}

当加载指标时,首先获取当前图表ID(baseChartID)以及交易品种(baseSymbol)。随后校验当前图表的周期是否和用户设定的BaseTimeframe一致。如果不一致,则调用ChartSetSymbolPeriod()函数自动将图表切换至指定的基准周期。确认周期设置无误后,指标会调用两个辅助函数:AssignTimeframesToCharts()负责分配并跟踪参与同步流程的图表与周期;CreateResetButtons()负责在图表上绘制UI按钮,供用户重置或刷新已同步的绘图对象。

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);
   }
}

分配周期至图表函数负责准备并分配用于同步的各类图表周期(基准周期除外)。函数首先创建临时数组timeframes,从预定义的availableTFs列表中筛选出非基准周期并存入该数组。遍历availableTFs,将所有与用户选定BaseTimeframe不匹配的周期加入timeframes数组。这样能保证只有备选周期会被分配到独立图表窗口进行同步(例如基准周期为H1时,则备选周期为M30、M15、M5)。

随后函数借助ChartFirst()与ChartNext()遍历所有已打开的图表窗口,寻找可参与同步的候选图表。跳过基准图表(依据baseChartID判断),并调用ChartSetSymbolPeriod()为每一个可用图表分配筛选后的周期。每完成一张图表的周期分配,就将该图表ID转为字符串存入timeframesAssigned数组中,该数组用于记录参与同步的图表。当所有的非基准周期分配完毕,或是没有更多可用图表时,循环终止。

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;
   }
}

OnChartEvent()函数处理用户与图表对象之间的交互事件,并做出相应响应,维持同步机制正常工作。当对象被新建或拖拽移动时(事件类型:OBJECT_CREATE、OBJECT_DRAG),系统调用PropagateObject()函数,依据当前周期,在所有同步图表上复制或更新该绘图对象。如果对象被删除(事件类型:OBJECT_DELETE),则调用 DeleteSyncedInstances(),从其他图表移除该对象的全部同步副本。此外,如果用户点击重置按钮(事件类型:OBJECT_CLICK,且sparam等于RESET_BUTTON),将触发DeleteAllSyncedObjects(),清除所有周期上已同步的绘图对象。该套事件驱动逻辑保证:对图表对象执行的任何修改,都会在整套多周期图表环境中即时生效并得到处理。

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);
      }
   }
}

PropagateObject()函数负责在不同图表周期之间同步趋势线、图形等绘图对象。该函数首先过滤掉所有本身就是克隆体的对象(对象名称包含"_clone_"标记),避免出现递归复制的问题。接下来,判断对象是否可被选中(具有可选性);如果SyncUnlockedObjects的设置为false,且该对象处于不可选中状态,则函数直接退出,以此保证仅同步经过用户许可或可编辑的对象。该机制可以防止无意同步那些不打算复制的非交互绘图对象。

函数主体会遍历存储在timeframesAssigned数组中的所有已绑定图表。针对每一个目标图表,函数会依据当前同步规则判断是否需要传递该对象。如果开启了SyncAllTimeframes(全周期同步),无论源周期与目标周期,对象都会被克隆至所有图表。反之,关闭该选项,则遵循如下逻辑规则:如果对象源自基准周期,则同步至其余所有周期;如果对象源自更高周期(例如H1),而目标是更低周期(例如M15),同样执行同步。当满足上述条件后,调用CloneObject()将源图表的对象复制到目标图表,保证各周期图表视觉样式保持一致。

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);
      }
   }
}

CloneObject()函数负责将绘图对象从源图表(srcChart)复制到目标图表(targetChart)中。该函数首先获取对象类型(例如趋势线、矩形或斐波那契工具),并在原始对象名称后追加_clone_和目标图表ID,生成唯一的新名称。如果调用ObjectCreate()无法在目标图表上创建对象,则函数直接退出。随后从源对象读取第一组坐标(时间与价格),并赋值给目标图表内克隆对象的对应位置。如果存在第二组坐标(趋势线、通道类对象通常具备双坐标),同样完成对应复制。

接下来,函数为克隆对象设置可视化属性,保证外观一致。包含颜色、线型(如实线或虚线)、线宽以及背景层属性(OBJPROP_BACK)。克隆对象设置为不可选中(OBJPROP_SELECTABLE = false),并在对象列表隐藏(OBJPROP_HIDDEN = true),防止误编辑,同时保持对象列表整洁。此外,通过OBJPROP_TOOLTIP添加悬浮提示文本,标记该对象的原始周期,方便查阅。交易者在多图表同步场景下,可以借此识别对象来源周期。

最后,函数处理特殊类型绘图对象:如果是文本对象(OBJ_TEXT)或标签对象(OBJ_LABEL),会将显示的文本内容复制至克隆对象;如果是斐波那契对象(OBJ_FIBO),函数复制斐波那契层级数量以及各层级价格,保证目标图表上斐波那契结构准确不变。通过完整复制坐标、外观属性与结构参数,CloneObject()能够保证同步对象和原图完全一致,让交易者在全部选定周期上开展连贯统一的技术分析。

//+------------------------------------------------------------------+
//| 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);
}

该指标的核心逻辑并不依靠OnCalculate()实现,其全部功能都通过图表事件和绘图对象操作驱动。但所有自定义指标都必须声明OnCalculate()函数;如果缺少该函数,代码会报编译错误。为满足语法要求,代码里写了一个极简的占位存根函数,仅直接返回rates_total,不执行任何计算。

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);
   }
}

DeleteSyncedInstances()函数确保当某个绘图对象在一张图表上被删除时,该对象在其他所有图表上对应的同步副本也一并移除。该函数首先判断被删除的对象是否为克隆体(检测对象名称是否包含_clone_),并据此提取原始对象的基础名称。随后函数删除当前图表上的该对象,并遍历timeframesAssigned数组内记录的全部图表,删除基准对象以及所有关联的、引用当前图表的克隆对象。

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);
      }
   }
}

DeleteAllSyncedObjects()函数用于删除所有同步图表上的对象,但保留重置按钮本身。该函数首先构建数组chartIDs,数组包含基准图表ID以及timeframesAssigned列表里的所有图表ID。接下来,针对上述每一张图表,按逆序遍历图表内所有现存对象并执行删除操作;对象名称包含RESET_BUTTON的会跳过,保证重置UI控件保留。该函数提供了一种快速方式,一键清空工作区里所有同步生成的绘图。

void CreateResetButtons() {
   CreateResetButtonOnChart(baseChartID);
   for(int i=0; i<ArraySize(timeframesAssigned); i++)
      CreateResetButtonOnChart((long)StringToInteger(timeframesAssigned[i]));
}
CreateResetButtons()函数用于在基准图表以及全部同步图表上添加重置按钮。该函数遍历每一张已绑定的图表,调用CreateResetButtonOnChart(),用户只需点击该按钮,即可清除所有已同步图表内的绘图对象。

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);
}

CreateResetButtonOnChart()函数用于在指定图表(图表ID)上创建“全部重置”按钮。该函数首先检查是否已存在同名按钮,如果存在则先删除,避免重复创建。随后新建按钮对象(OBJ_BUTTON),通过ResetButtonX和ResetButtonY设定按钮位置,同时配置固定尺寸、背景色、文字颜色以及按钮显示文本。设置按钮为不可选中,但保持可见状态(HIDDEN = false),供用户点击交互。

void OnDeinit(const int reason) {
   DeleteAllSyncedObjects();
}
最后,当指标被移除或者图表关闭时,会调用OnDeinit()函数。该函数触发DeleteAllSyncedObjects(),清理全部同步绘图对象,确保不会遗留残余对象。


演示:



结论

综上所述,我们开发了一款多周期图表同步指标。该指标可以将趋势线、矩形、斐波那契位等绘图对象自动复制到选定的各个周期,以此提升技术分析效率。项目首先定义了可供用户自定义的输入参数,用于控制对象样式、同步行为,以及重置按钮的屏幕位置这类UI配置。我们实现了一系列核心函数:为图表窗口分配周期;捕获并传递对象事件(新建、移动、删除);依靠对象克隆与统一命名规则,保障同步状态整洁可靠。配套工具函数负责创建重置按钮,并且在指标卸载或者用户主动操作时,完成全部对象的清理工作。

总而言之,该指标为交易者提供了强大高效的多周期分析工具,不再需要在多张图表上手动重复绘制或管理绘图对象。在从同一交易品种的多周期视角开展分析时,指标保证绘图对象统一且界面清晰,简化工作流程,降低思考负担。依托绘图对象同步显示和不同周期间流畅交互的特性,交易者可以更容易识别信号共振、追踪关键价位,做出更有依据的交易决策。 

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18937

附加的文件 |
最近评论 | 前往讨论 (1)
kingsley2
kingsley2 | 3 9月 2026 在 08:22
感谢分享这些内容!我 根据自己的时间周期进行了调整 ,效果非常棒!
交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
MetaTrader 5中交易品种及其价差的小时级波动分析 MetaTrader 5中交易品种及其价差的小时级波动分析
带简单移动平均线(SMA)的ProSpread季节性指数指标是一款技术分析工具,用于识别价格走势中的季节性规律、分析特定交易时段的价格行为,该指标既支持单个交易品种,也可用于两个资产之间的价差分析。同时,它能够可视化价格定向波动的统计概率。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
市场模拟:仓位视图(三) 市场模拟:仓位视图(三)
在之前的文章中,我们提到过有时需要为 ZOrder 属性设置一个值。但是为什么呢?原因在于,许多向图表添加对象的代码根本不使用(或者更准确地说,不定义)此属性的值。关键在于,我并不是来规定每个程序员应该或不应该做什么,或者他们应该或不应该如何编写代码的。亲爱的读者,我在此向您以及所有真正想了解这些过程内部运作原理的人展示,幕后究竟发生了什么。