按小时、星期几和每月日期分析的季节性指标
价格在某些时间点会呈现重复性的节奏,在每月的特定日期、每周的特定日子,甚至每天的特定时段反复出现。这些重复出现的节律,或称季节性模式,能够为交易者提供线索,帮助判断市场何时可能上涨、何时可能下跌。金融市场中的季节性不仅仅是一个有趣的现象,更是一种帮助在价格混沌中识别可预测时刻的工具。例如,你是否注意到某些货币对经常在周一上涨,或在月末下跌?这就是季节性,研究它能够为交易者带来优势。
在本指南中,我们将在 MetaTrader 5 平台上用 MQL5 创建一个季节性指标。我们的指标将分析历史价格数据,以识别每月日期(1 日至 31 日)、星期几(周一至周日)或每日小时(0 时至 23 时)的平均收益率。结果将以直方图形式显示在单独的图表窗口中,并有一条预测线连接各季节性数值,同时用圆点标注后续 K 线上的预期值。此外,该指标还会显示文本统计信息,包括预测值、最佳和最差时段。我们将逐步带您完成开发过程,解释代码的每一部分,使您不仅能使用这个指标,还能根据自己的想法进行调整。本指南结合了编程与金融分析,代码则成为连接数据与交易决策的桥梁。
为什么要研究季节性?
季节性就像市场隐藏的脉搏。它由诸多因素共同形成:交易者行为、经济事件,甚至人类心理。例如,在月末,大型基金可能会平仓,从而引发可预测的价格波动。又或者在一天中的某些时段,由于伦敦或纽约交易时段开盘,市场活跃度会增加。我们的指标将帮助您识别这些模式,将混乱的数据转化为清晰的图表和统计信息。这不是魔法,而是数据分析在为交易者服务。
我们将要创建的指标是通用型的。用户可以选择季节性类型、设置分析的 K 线数量、以百分比或绝对值显示收益率,以及开启或关闭图表上的预测。灵活性是让该指标适用于不同市场和时间周期的关键,无论是欧元兑美元的日线图,还是黄金的小时图。
定义指标的结构
创建指标首先要理解它的外观和工作方式。它将显示在价格图表下方的单独窗口中,以免占用主图空间。我们使用三种元素进行可视化:显示每个周期平均收益率的直方图、连接季节性数值与预测数据的预测线,以及标注后两根 K 线预期值的预测点。每个元素都将关联一个数据缓冲区 —— 即存储待绘制数值的数组。我们还需要全局变量来存储季节性数据和周期标签,以及供用户自定义指标的输入参数。
//+------------------------------------------------------------------+ //| SeasonalityIndicator.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 3 #property indicator_plots 3 //--- plot Seasonality #property indicator_label1 "Seasonality" #property indicator_type1 DRAW_HISTOGRAM #property indicator_color1 clrDodgerBlue #property indicator_style1 STYLE_SOLID #property indicator_width1 2 //--- plot Forecast Line #property indicator_label2 "Forecast" #property indicator_type2 DRAW_LINE #property indicator_color2 clrRed #property indicator_style2 STYLE_DASH #property indicator_width2 2 //--- plot Forecast Points #property indicator_label3 "Forecast Points" #property indicator_type3 DRAW_ARROW #property indicator_color3 clrOrange #property indicator_width3 3
在这段代码中,我们设置了指标的基本信息:版权、MetaQuotes 官网链接以及版本号。#property indicator_separate_window 指令指定该指标将显示在单独的窗口中。我们定义了三个缓冲区和三个绘图序列。DRAW_HISTOGRAM 直方图使用蓝色,DRAW_LINE 预测线使用红色,DRAW_ARROW 预测点使用橙色,线宽为 3 像素。这些设置定义了视觉样式,使指标清晰易读且美观。
设置输入参数
为了让指标具备灵活性,我们添加了用户可在 MetaTrader 5 设置窗口中修改的输入参数。季节性类型选择参数允许我们分析每月日期、星期几或每日小时。待分析的 K 线数量决定了我们能回溯多远的历史数据。可以选择以百分比或绝对值显示收益率,也可以选择仅显示为正的季节性数值。开启图表预测以及自定义预测线和预测点颜色的选项,使指标的可定制性进一步增强。
//--- Input parameters enum ENUM_SEASONALITY_TYPE { SEASONALITY_DAYS_OF_MONTH = 0, // Days of the month (1-31) SEASONALITY_DAYS_OF_WEEK = 1, // Days of the week (Mon-Sun) SEASONALITY_HOURS = 2 // Hours (0-23) }; input ENUM_SEASONALITY_TYPE SeasonalityType = SEASONALITY_DAYS_OF_MONTH; // Seasonality type input int BarsToAnalyze = 1000; // Number of bars to analyze input bool ShowPercentage = true; // Show as a percentage input bool ShowPositiveOnly = false; // Show only positive values input bool ShowForecastOnChart = true; // Show forecast on chart input color ForecastColor = clrRed; // Forecast line color input color ForecastPointsColor = clrOrange; // Color of forecast points
ENUM_SEASONALITY_TYPE 枚举定义了三种季节性分析选项。默认选中按每月日期进行分析。BarsToAnalyze 参数设为 1000 根 K 线,对应日线图上约四年的数据。ShowPercentage 选项默认开启,以百分比形式显示收益率,更加直观。ShowPositiveOnly 和 ShowForecastOnChart 参数提供了额外的灵活性,颜色则可通过 MetaTrader 界面修改。
定义缓冲区和变量
为存储数据,指标使用三个缓冲区:一个用于直方图,第二个用于预测线,第三个用于预测点。此外还需要全局变量:一个用于存储各周期平均收益率的数组、一个用于周期标签的数组(例如 "周一" 或 "1"),以及一个周期数量变量 —— 具体数量取决于季节性类型(每月日期为 31 个,星期为 7 个,小时为 24 个)。
//--- Indicator buffers double SeasonalityBuffer[]; double ForecastLineBuffer[]; double ForecastPointsBuffer[]; //--- Global variables double seasonality_data[]; string period_labels[]; int periods_count;
缓冲区存放待渲染的数据,而 seasonality_data 和 period_labels 变量用于存储和展示分析结果。periods_count 变量决定分析的周期数量,根据季节性类型进行设置。
指标初始化
OnInit 函数在指标启动时执行,负责完成初始设置。它将缓冲区与指标绘图对象绑定,为预测点设置箭头样式,并根据用户参数设置颜色。根据季节性类型,创建各周期的标签,并将存储季节性数据的数组初始化为零。显示精度会根据选择的是百分比还是绝对值进行调整。
int OnInit() { SetIndexBuffer(0, SeasonalityBuffer, INDICATOR_DATA); SetIndexBuffer(1, ForecastLineBuffer, INDICATOR_DATA); SetIndexBuffer(2, ForecastPointsBuffer, INDICATOR_DATA); PlotIndexSetInteger(2, PLOT_ARROW, 159); PlotIndexSetInteger(1, PLOT_LINE_COLOR, ForecastColor); PlotIndexSetInteger(2, PLOT_LINE_COLOR, ForecastPointsColor); switch(SeasonalityType) { case SEASONALITY_DAYS_OF_MONTH: periods_count = 31; ArrayResize(period_labels, periods_count); for(int i = 0; i < periods_count; i++) period_labels[i] = IntegerToString(i + 1); IndicatorSetString(INDICATOR_SHORTNAME, "Seasonality by day of the month"); break; case SEASONALITY_DAYS_OF_WEEK: periods_count = 7; ArrayResize(period_labels, periods_count); period_labels[0] = "Mon"; period_labels[1] = "Tue"; period_labels[2] = "Wed"; period_labels[3] = "Thu"; period_labels[4] = "Fri"; period_labels[5] = "Sat"; period_labels[6] = "Sun"; IndicatorSetString(INDICATOR_SHORTNAME, "Seasonality by day of the week"); break; case SEASONALITY_HOURS: periods_count = 24; ArrayResize(period_labels, periods_count); for(int i = 0; i < periods_count; i++) period_labels[i] = IntegerToString(i) + ":00"; IndicatorSetString(INDICATOR_SHORTNAME, "Seasonality by hours"); break; } ArrayResize(seasonality_data, periods_count); ArrayInitialize(seasonality_data, 0.0); IndicatorSetInteger(INDICATOR_DIGITS, ShowPercentage ? 2 : _Digits); return(INIT_SUCCEEDED); }
该函数为指标的运行奠定了基础。例如,对于星期几分析,会创建从 "周一" 到 "周日" 的标签数组;对于小时分析,则创建从 "0:00" 到 "23:00" 的标签。指标名称会随之变化,使用户能立即了解当前选择的季节性类型。这让指标的使用更加直观。
季节性计算
主要计算在 OnCalculate 函数中完成。它分析历史价格数据,计算每个周期的平均收益率,并填充缓冲区以供渲染。首先,我们检查图表上是否有足够的 K 线。然后创建辅助数组,用于累加收益率和统计每个周期的 K 线数量。
对于每根 K 线,通过计算收盘价差值与当前收盘价的比值来确定收益率。GetPeriodIndex 函数确定该 K 线属于哪个周期,并将数据写入数组。之后计算平均收益率,如有需要则转换为百分比形式,或对负值清零。缓冲区填充数据后,调用单独的函数进行预测。
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[]) { if(rates_total < BarsToAnalyze + 1) return(0); ArrayInitialize(seasonality_data, 0.0); double period_returns[]; int period_counts[]; ArrayResize(period_returns, periods_count); ArrayResize(period_counts, periods_count); ArrayInitialize(period_returns, 0.0); ArrayInitialize(period_counts, 0); int start_pos = MathMax(0, rates_total - BarsToAnalyze - 1); for(int i = start_pos; i < rates_total - 1; i++) { double return_value = 0.0; if(close[i] != 0) return_value = (close[i+1] - close[i]) / close[i]; int period_index = GetPeriodIndex(time[i]); if(period_index >= 0 && period_index < periods_count) { period_returns[period_index] += return_value; period_counts[period_index]++; } } for(int i = 0; i < periods_count; i++) { if(period_counts[i] > 0) { seasonality_data[i] = period_returns[i] / period_counts[i]; if(ShowPercentage) seasonality_data[i] *= 100.0; if(ShowPositiveOnly && seasonality_data[i] < 0) seasonality_data[i] = 0.0; } } for(int i = 0; i < rates_total; i++) { int period_index = GetPeriodIndex(time[i]); if(period_index >= 0 && period_index < periods_count) { SeasonalityBuffer[i] = seasonality_data[period_index]; ForecastLineBuffer[i] = seasonality_data[period_index]; } else { SeasonalityBuffer[i] = 0.0; ForecastLineBuffer[i] = 0.0; } ForecastPointsBuffer[i] = EMPTY_VALUE; } if(ShowForecastOnChart) DrawForecastOnChart(rates_total, time); return(rates_total); }
这段代码是指标的核心。它将原始价格数据转化为有意义的模式。想象一下,你正在分析某个货币对的日线图。对于一周中的每一天,指标计算价格变化率并求出平均值。如果你发现市场在周五平均下跌 0.05%,这可能成为交易策略的一个信号。
周期判定
为了确定每根 K 线属于哪个周期,我们创建了 GetPeriodIndex 函数。它根据季节性类型,将 K 线时间转换为周期索引。对于每月日期分析,取日期数字并减 1,得到 0 到 30 的索引。对于星期几分析,将星期值(周日为 0,周一为 1)转换为周一为 0、周日为 6 的星期顺序。对于小时分析,直接返回 0 到 23 的小时值。
int GetPeriodIndex(datetime bar_time) { MqlDateTime dt; TimeToStruct(bar_time, dt); switch(SeasonalityType) { case SEASONALITY_DAYS_OF_MONTH: return(dt.day - 1); case SEASONALITY_DAYS_OF_WEEK: return(dt.day_of_week == 0 ? 6 : dt.day_of_week - 1); case SEASONALITY_HOURS: return(dt.hour); } return(-1); }
这个函数就像一个指南针,帮助指标确定 K 线数据应该归入哪个 "分类箱"。没有它,我们就无法将价格与特定的日期或小时关联起来。
绘制预测
预测功能让指标不仅具备分析价值,更具实用意义。DrawForecastOnChart 函数在图表的最后两根 K 线上绘制预期的季节性数值。它使用 GetNextPeriod 函数确定当前周期和后续两个周期。这些周期的季节性数值被写入预测线和预测点缓冲区。为使预测线更平滑,当前周期的值会写入更靠近预测区段的前一根参考柱缓冲区。
void DrawForecastOnChart(int rates_total, const datetime &time[]) { if(rates_total < 3) return; datetime current_time = time[rates_total - 1]; int current_period = GetPeriodIndex(current_time); int next_period1 = GetNextPeriod(current_period); int next_period2 = GetNextPeriod(next_period1); int forecast_pos1 = rates_total - 2; int forecast_pos2 = rates_total - 1; if(next_period1 >= 0 && next_period1 < periods_count) { ForecastLineBuffer[forecast_pos1] = seasonality_data[next_period1]; ForecastPointsBuffer[forecast_pos1] = seasonality_data[next_period1]; } if(next_period2 >= 0 && next_period2 < periods_count) { ForecastLineBuffer[forecast_pos2] = seasonality_data[next_period2]; ForecastPointsBuffer[forecast_pos2] = seasonality_data[next_period2]; } if(rates_total >= 3) { int current_pos = rates_total - 3; int current_period_idx = GetPeriodIndex(time[current_pos]); if(current_period_idx >= 0 && current_period_idx < periods_count) { double current_value = seasonality_data[current_period_idx]; double next_value = (next_period1 >= 0) ? seasonality_data[next_period1] : current_value; ForecastLineBuffer[current_pos] = current_value; if(forecast_pos1 < rates_total) ForecastLineBuffer[forecast_pos1] = next_value; } } }
预测就像窥探未来:指标根据历史数据告诉你可以预期什么。例如,如果周二的平均收益率为 + 0.07%,指标会在图表上显示出来,帮助交易者提前做好准备。
计算下一周期
GetNextPeriod 函数确定当前周期之后的下一个周期。它使用模运算实现循环过渡:每月 31 日之后是 1 日,周日之后是周一,23:00 之后是 0:00。
int GetNextPeriod(int current_period) { if(current_period < 0) return(-1); switch(SeasonalityType) { case SEASONALITY_DAYS_OF_MONTH: return((current_period + 1) % 31); case SEASONALITY_DAYS_OF_WEEK: return((current_period + 1) % 7); case SEASONALITY_HOURS: return((current_period + 1)rate_total % 24); } return(-1); }
该函数确保预测始终指向正确的未来周期,保持时间的循环逻辑。它相当于一个精确的周期切换机制,保证指标在各周期之间正确切换 —— 无论是每月日期、星期几还是小时的更替。把市场想象成一座巨大的时钟,每一次跳动不仅记录着时间,也预示着潜在的价格波动。每月 31 号之后,函数平滑地将我们带回 1 号;周日之后切换到周一;23:00 之后则回到 0:00。这种循环方式反映了金融市场的真实时间结构,使指标能够预测哪些周期对交易者可能具有重要意义。
统计信息展示

为了让交易者能够即时了解分析结果,指标直接在图表上显示详细的统计信息。ShowStatistics 像讲述者一样,把枯燥的数字转化为清晰可读的市场节律故事。
开头是根据所选季节性类型变化的标题 —— 无论是按每月日期、星期几还是小时分析。接着标明分析的 K 线数量,让交易者了解计算所依据的历史数据深度。然后加入从 GetForecast 函数获取的预测结果,根据历史趋势预测未来两个周期市场可能的表现。之后,该函数仔细遍历 seasonality_data 数组,找出收益率最高和最低的周期。这些周期被标记为最佳和最差,其数值保留三位小数显示,方便交易者评估其重要性。
信息的最后是完整统计表,每个周期都显示其标签(例如 "周一" 或 "15")以及平均收益率。文本出现在图表右上角,作为市场节律的简明分析摘要,让其易于理解并有助于做出交易决策。
结论
金融市场中的季节性并非神秘现象,而是经过数学验证的规律,可以成为交易者工具箱中的强大工具。我们创建的这个指标将混乱的价格运动转化为可理解的模式,按每月日期、星期几或交易时段揭示隐藏的市场节律。
该工具凭借灵活的参数设置、包含预测的直方图可视化展示以及详尽的统计信息,使交易者不仅能分析历史数据,还能对未来价格走势做出有依据的判断。预测元素的集成使该指标在开发交易策略时具备实际应用价值。
指标代码可轻松适配各类交易品种和时间周期,而对每个函数的逐步讲解也让我们能够根据自己的交易思路修改算法。归根结底,这个指标是连接复杂统计计算与具体交易决策的桥梁,将数据转化为知识,将知识转化为潜在的盈利机会。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/18672
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
开发多币种 EA(第 27 部分):多行文本显示组件
您应该了解的MQL5向导技巧(第七十二部分):MACD与OBV组合形态的监督学习应用
新手在交易中的10个基本错误
数据科学与机器学习(第四十五部分):利用Facebook的Prophet模型进行外汇时间序列预测