通过图表同步简化技术分析
交易者在开展技术分析时经常面临一个挑战:不同周期图表的标注无法统一。虽然大多数交易平台支持在切换周期时,趋势线、价格区域等图形对象跨周期显示,但并排对多周期进行分析仍然效率低下。交易者常常为同一个品种打开多个图表窗口,每个窗口设置不同周期,以此获取更全面的技术视角。然而,如果没有同步功能,每张图表都各自独立运行。这就意味着滚动、缩放、切换品种这类操作必须在每张图表上重复执行,造成分析碎片化、工作量增加,也更容易遗漏关键共振区域。
图表同步功能通过关联同一品种、不同周期的多个图表窗口解决该问题。诸如平移、缩放、切换品种等操作会在所有已同步图表上体现,交易者能够在多个周期下流畅地查看并对比同一价格走势环境。这种统一联动方式简化了分析流程,减少人工操作,保障多周期分析更加连贯和准确,最终帮助交易者更快做出具备充分依据的交易决策。
图表同步指标的方案规划与逻辑
核心目标
搭建一套多周期图表同步系统:在大周期上绘制的图形对象,可自动呈现在小周期图表中,并保持正确的周期层级关系与样式。
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
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
MetaTrader 5中交易品种及其价差的小时级波动分析
新手在交易中的10个基本错误
市场模拟:仓位视图(三)