价格直方图(市场概况)及其在 MQL5 中的实施
“市场概况试图在市场背景下提供这一内在逻辑。
它是一种分析方法,以价格本身
并不会向参与者传递信息这一理念为前提,这与缺乏语法
或上下文的字词没有意义是一个道理。交易量是市场直接表达
的一个不可分割的部分 - 明白这一点您就理解了市场语言。”
Robin Mesh
简介
很久以前,我在浏览杂志订阅信息时在一份俄国杂志 "Valutny Spekulant"(现名 "Active Trader") 上读过一篇文章“市场概况和理解市场语言”(2002 年 10 月刊)。原文出处如下:"New Thinking in Technical Analysis:Trading Models from the Masters"。
“市场概况”由真正才华横溢的思想家 Peter Steidlmayer 所提出。他发现了市场(交易量)的本质表达,并以一种易懂的方式(钟形曲线)加以组织,从而使市场参与者能够获得市场产生的客观信息。Steidlmayer 建议使用有关“水平”和“垂直”市场动态信息的替代表示法,从而给出一套完全不同的模型。他认为存在市场深层次的摆动或称之为平衡和失衡周期的基本模式。
市场概况衡量市场的水平动态和垂直动态。我们称其为贯穿“平衡”和“失衡”。这一关系是市场的基本组织原则。交易者的整体交易风格可能会根据市场处于平衡/失衡周期的具体阶段而有所改变。市场概况可决定市场从平衡向失衡转变的时间以及转变的程度。
市场概况的两大基本理念如下所述:
- 市场是一场竞拍,它沿供求大致平衡的价格范围的方向移动。
- 市场有两个阶段:水平活动阶段和垂直活动阶段。市场在供求不等或失衡时垂直移动,在供求均势或平衡时水平移动。
下图中依托市场概况显示的均衡市场趋向于形成一个几乎完美的钟形曲线(由于图表方向原因,已旋转 90 度):
图 1. 均衡市场的市场概况
非均衡市场的趋势同样构成了一个钟形曲线,但其中心会向上或向下偏移。形成钟形两个波峰的其他配置(取决于价格变动和市场参与者的信心),亦有可能。
图 2. 非均衡(趋势)市场的市场概况
使用日线图来确定市场平衡/不平衡水平的程度十分有用,因为它为您在众多市场参与者中提供了一个理解转变的起点。
当从平衡到不平衡的转变即将发生时,将会出现最大效益的交易机会。此外,如果您能够识别该交易机会并精确估计转变的可能程度,则您可以估计出交易的质量以及所需的时间量。
必须要注意的是,在本文中我们将会探讨绘制市场概况简化版图形的代码,即所谓的基于价格和时间关系的价格直方图。使用此工具的方法示例可在以下网址找到:http://www.enthios.com/,一群交易者自 1998 年起就在这里研究价格直方图。Enthios Universal 策略和及其使用示例同样可以在这里找到。
1. 价格直方图
价格直方图是一款非常可靠的工具。它有一点基于直觉,但却非常有效。价格直方图简单地向您显示市场“最方便”的交易点。这是一个重要指标,因为它可以提前指出市场能够改变其走向的点的位置。而移动平均线或动量等指标无法精确指出阻力点和支撑点,它们只能反映市场是否买空或卖空的事实。
通常,价格直方图(或市场概况)应用于 30 分钟的价格图表以研究一天的市场动态。我自己更倾向于在股票市场中使用 5 分钟的图表,在外汇市场中使用 15-30 分钟的图表。
2. 控制点
在上图中,您可以看到市场交易时间最长所在的水平;它在直方图中通过最长的线条绘出。我们称其为控制点或 POC。有时候,直方图具有两个顶部,其中一个会稍稍低矮一些,如图所示。在这种情况下,我们看到指标仅显示一个 POC,但事实上存在两个 POC,且必须将另一个也考虑在内。
此外,直方图中范围的百分率水平也产生了额外的水平,即所谓的次级 POC 水平:
图 3. 控制点
POC 标示了什么?它标示被大多数交易者所记住的价格。市场在此价格上交易的时间越长,市场记住该价格的时间就越长。
从心理上而言,POC 起到了吸引中心的作用。
下图显示了数天前的情景。它是价格直方图性能的有力佐证。
图 4. 控制点不是绝对的;它显示的是交易的范围。
控制点不是绝对的;它指示的是交易的范围。因此,在市场逼近 POC 时,交易者应准备好采取行动。依托历史观测值,它有助于优化订单。
接下来我们讨论图 4。29.12.2009 的 POC 位于价位 68.87 处。市场几乎全天都在 68.82~68.96 的范围内波动,即使没有直方图和 POC 线,这一点也是显而易见的。市场在当天结束时以低于 POC 5 点的价位收盘。在接下来的一天,它导致市场下跌开盘。
我们无法预测市场是上涨还是下跌,明白这一点十分重要。我们只能推测市场将返回 POC 线以及返回直方图线的最大累积。而当价格触及 POC 时会发生什么情况?产生的情形和弹性物件触底反弹是一样的。如果这个过程发生得很快,比如使用球拍将网球击回,价格将很快回归到初始水平。
当市场在 30.12.2009 开盘后,我们可以观察到一个缺口,接下来市场触及前一天的 POC,然后很快返回开盘价并刷新最低价。
请注意,POC 不是绝对精确的(有经验的交易者知道,当价格触及最高价、最低价或集中范围时,不存在明确的阻力水平)。在这一点上会发生什么取决于市场参与者。如果集体愿望(例如,新闻出版)一致,则市场将通过 POC,但这很少见,并且它可以用于开发交易系统。
请注意,31.12.2009 的市场行为是一样的。当价格触及 POC 时,买家屈服于卖家。
3. 原始控制点
原始 POC(原始控制点)是价格在未来几天未触及的水平。
道理很简单,如上文所述,POC 是市场的吸引点。随着价格偏离 POC,吸引力增大。并且价格偏离原始 POC 越远,价格返回此水平时发生反弹的可能性就越大,且价格还可能呈反向变化。
图 5. 之前的和当前的原始 POC
在图 5 中,我们将属于支撑位和阻力位的原始 POC 用圆圈标出。工作的原始 POC 通过价格值标出。
一旦价格触及原始 POC,就不再“原始”。从心理上而言,市场不再将其视为真正的支撑位或阻力位。交易者仍然能看到价格水平,该价格水平最初形成了 POC,但只是价格的简单累积。
更多有关价格水平的详细信息,请参见Eric Naiman 所著的《Master-trading: The X-Files》一书(第四章 "Price Level is a base line" (价格水平是基线))。4. 价格直方图在 MQL5 中的实施
我的第一个价格直方图版本于 2006 年出炉,采用 MQL4 在 MetaTrader4 中编写,供个人使用。在该指标的开发过程中我碰到了不少问题,现摘录部分如下:
- M5 的历史数据中柱的编号极短,更遑论 M1;
- 必须编写特殊函数以使用历史数据,例如考虑到假日而返回一天、检查周五的市场收盘时间、检查 CFD 市场的开盘和收盘时间等;
- 改变时间表时对指标进行了重复计算,从而导致终端延迟。
因此,当 MetaTrader5 和 MQL5 的测试版开始测试时,我决定将其转换到 MQL5。
正如人们常说的“万事开头难”,我尝试将它作为指标来实施。
先说有利的一面:囊括所有交易品种的分钟报价的长期历史数据,获取任何时间范围内具体时间周期的历史数据的可能性。
现在我将解释价格直方图横空出世的原因。我尚未考虑 MQL5 指标的特性:
- 指标的运行时至关重要;
- 时间表改变后指标工作的特性。
函数 OnCalculate() 的执行具有一个至关重要的运行时间,这和 Calculate 事件句柄相对应。相应地,使用分钟柱历史数据处理 260 天(年周期)需要较长的时间,达数分钟之久。当然,如果指标附加至图表后计算立即执行,我们也还可以接受。然而当时间表改变时情况并非如此。当指标切换至不同的时间表时,系统将销毁指标的旧版本并创建新的指标。这正是时间表改变后我们不得不再次计算相同水平的原因,并且这会花费大量的时间。
不过俗话说得好,“磨刀不误砍柴工”,在我们的示例中,如果您不知道该怎么做,请先阅读 MQL5 文档。解决方案十分简单 - 将该指标作为未交易的“EA 交易”实施。
“EA 交易”的优势如下:
“EA 交易”与指标有以下区别:时间表改变后,“EA 交易”仅生成带 REASON_CHARTCHANGE 原因参数的 DeInit 事件,而不会从内存卸载“EA 交易”和操作全局变量的值。允许我们在附加“EA 交易”后立即执行所有计算,更改其参数和出现的新数据,在我们的示例中为新交易日。
接下来我们将介绍在后面要用到的几个定义。
面向对象编程 (OOP) - 是一种编程风格,其基本概念为对象和类的概念。
“对象”是虚拟空间中的实体,具有指定的状态和行为;它具有属性(“属性”)的值以及和属性相关的操作(“方法”)。
在 OOP 中,“类”是一种特殊的抽象数据类型,以其构造方法为特征。类是 OOP 的一个关键概念。类和其他抽象数据类型有所不同。类中的数据定义同样包含其数据处理的类方法(接口)。
在编程中有一个软件接口概念,即可被程序的某个部分执行的可能的计算列表,包含算法、自变量说明、输入参数的处理顺序及其返回值。抽象数据类型接口被开发用于此列表的形式化描述。算法本身和将执行所有这些计算的代码并未指定,称为接口的实现。
类创建即创建含字段和方法的结构。可将整个类作为模板用于对象的创建,即类的实例。类实例使用同一模板创建,因而具有相同的字段和方法。
我们开始吧...
源代码位于 4 个文件内。主文件是 PriceHistogram.mq5,其他文件包括:ClassExpert.mqh、ClassPriceHistogram.mqh 和 ClassProgressBar.mqh。具有 .mqh 扩展名的文件包含类的描述和方法。所有文件必须位于同一目录下,我的目录是:\MQL5\ Experts\PriceHistogram。
4.1. PriceHistogram.mq5
源代码的第一条语句是:
#include "ClassExpert.mqh"
#include 编译程序指令包含来自指定文件的文本。在我们的示例中是对类 CExpert 的说明(下文中讨论)。
接下来的是输入变量代码块,输入变量是“EA 交易”的参数。
// 输入参数区块 input int DayTheHistogram = 10; // 直方图天数 input int DaysForCalculation= 500; // 用于计算的天数(-1代表所有) input uint RangePercent = 70; // 百分比范围 input color InnerRange =Indigo; // 内部范围 input color OuterRange =Magenta; // 外部范围 input color ControlPoint =Orange; // 控制点 input bool ShowValue =true; // 显示值
然后是变量 ExtExpert(CExpert 类类型)的声明。
接下来是 MQL5 程序中的标准事件句柄。事件句柄调用相应的 CExpert 类的方法。
在执行 CExpert 前,仅有一个执行一些操作的方法 - OnInit() 方法:int OnInit() { //--- // 在开始计算前,检查交易品种的同步情况 int err=0; while(!(bool)SeriesInfoInteger(Symbol(),0,SERIES_SYNCRONIZED) && err<AMOUNT_OF_ATTEMPTS) { Sleep(500); err++; } // CExpert 类的初始化 ExtExpert.RangePercent=RangePercent; ExtExpert.InnerRange=InnerRange; ExtExpert.OuterRange=OuterRange; ExtExpert.ControlPoint=ControlPoint; ExtExpert.ShowValue=ShowValue; ExtExpert.DaysForCalculation=DaysForCalculation; ExtExpert.DayTheHistogram=DayTheHistogram; ExtExpert.Init(); return(0); }
在我写出并运行第一个“EA 交易”版本时,我不太明白为什么当客户端重启或交易品种改变时“EA 交易”会出现错误终止。这在断开客户端或长期未使用某个交易品种时发生。
开发人员为 MetaEditor5 添加了调试程序,这无疑令人振奋。我记得在 MetaEditor4 中有大量的 Print() 和 Comment() 命令用于检查变量的值。非常感谢 MetaEditor5 的开发人员。
对于我而言,一切都很容易;EA 交易程序在连接至服务器前启动并更新历史数据。要解决这个问题,我只能使用 SeriesInfoInteger(Symbol(),0,SERIES_SYNCRONIZED),该函数用于报告数据已同步或未同步、while () 循环、连接缺失的情况下函数使用计数器变量错误。
一旦数据同步,或循环由于计数器连接缺失而完成,我们传递我们的 EA 交易程序类 CExpert 的输入参数,并调用类初始化方法 Init ()。
如您所见,得益于 MQL5 中类的理念,我们的文件 PriceHistogram.mq5 得以转化成为一个简单的模板,并且所有后续处理都在于 ClassExpert.mqh 文件中声明的 CExpert 类中进行。
4.2. ClassExpert.mqh
让我们考虑它的描述。
//+------------------------------------------------------------------+ //| CExpert类 | //| 类的描述 | //+------------------------------------------------------------------+ class CExpert { public: int DaysForCalculation; // 计算的天数(-1代表所有) int DayTheHistogram; // 直方图的天数 int RangePercent; // 百分比范围 color InnerRange; // 范围内的颜色 color OuterRange; // 范围外的颜色 color ControlPoint; // 控制点(POC)的颜色 bool ShowValue; // 显示值
公共部分开放,可以从外部变量进行访问。您将注意到,变量的名称与 PriceHistogram.mq5 中描述的输入参数部分的名称一致。这并不是必须的,因为输入参数是全局的。但在本例中,则希望避免在类中使用外部变量,这对于良好的教育规则大有裨益。
private: CList list_object; // CObject类的动态列表 string name_symbol; // 交易品种名称 int count_bars; // 日线数量 bool event_on; // 事件处理标识
私有部分对外部关闭,仅可在类中访问。在此我希望对 CList 类型的变量 list_object 作一简述,CList 类型是标准 MQL5 库的一个类。CList 类是具有 CObject 类及其子类的实例列表的动态类。我将使用该列表作为 CPriceHistogram 类元素的参考存储,CPriceHistogram 类元素是 CObject 类的子类;我们将在下文中进行详细讨论。CList 类在 List.mqh 中描述,并通过使用编译程序指令 #include <Arrays\List.mqh> 包含。
public: // 类的构造函数 CExpert(); // 类的析构函数 ~CExpert(){Deinit(REASON_CHARTCLOSE);} // 初始化方法 bool Init(); // 反初始化方法 void Deinit(const int reason); // OnTick方法 void OnTick(); // OnChartEvent()事件处理方法 void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); // OnTimer() 事件处理方法 void OnTimer(); };
接下来是公共方法部分。正如您所预料的那样,这些方法(函数)在类的外部可用。
最后通过后跟分号的括号完成类的描述。
接下来我们详细讨论类方法。
类构造函数是特殊语句块,在对象创建时调用。构造函数与方法类似,但与未明确指出返回数据的具体类型的方法有所不同。
在 MQL5 语言中,构造函数没有任何输入参数,且每个类应具有唯一的构造函数。在我们的示例中,构造函数是变量的初级初始化。
析构函数是用于对象取消初始化(如释放内存)的特殊类方法。在我们的例子中,该方法为 Deinit (REASON_CHARTCLOSE);
Init() 是用于类初始化的方法。它是 CExpert 类最重要的方法;直方图对象的创建正是在此执行。详情请参见注释。此外,我希望与大家讨论以下三点。
首先,要建立一个每日价格直方图,我们需要开放时间数据以用于处理日。在此,我希望暂时偏离我们的议题并提请你们注意使用时间序列的功能。对于来自其他时间表的数据请求我们需要一个时间,因此函数 Bars () 和 CopyTime () 以及其他使用时间序列的函数不会总是从第一个调用返回所需的数据。
因此,我不得不将此函数置于 do (...) while () 循环中,并使用计数器变量以使其有限循环。
int err=0; do { // 计算历史数据中的可用天数 count_bars=Bars(NULL,PERIOD_D1); if(DaysForCalculation+1<count_bars) count=DaysForCalculation+1; else count=count_bars; if(DaysForCalculation<=0) count=count_bars; rates_total=CopyTime(NULL,PERIOD_D1,0,count,day_time_open); Sleep(1); err++; } while(rates_total<=0 && err<AMOUNT_OF_ATTEMPTS); if(err>=AMOUNT_OF_ATTEMPTS) { Print("There is no accessible history PERIOD_D1"); name_symbol=NULL; return(false); }
其次,MetaTrader 5 的分钟历史数据等同于可用天数,因此处理将花费大量的时间,从而使计算过程的可视化成为必要。类 CProgressBar (#include "ClassProgreussBar.mqh") 的开发正是针对此目的。该类在图表窗口中创建进度条,并在计算过程中进行更新。
// 我们在图标上创建进度条,来观察加载进度 CProgressBar *progress=new CProgressBar; progress.Create(0,"Loading",0,150,20); progress.Text("Calculation:"); progress.Maximum=rates_total;
再次,在循环中,我们使用 "new" 语句创建 CPriceHistogram 对象,使用其方法对其进行配置,并调用 Init() 对其进行初始化。如果成功,我们将其添加至 list_object 列表,否则使用删除语句删除 hist_obj。我们将进一步探讨 CPriceHistogram 类描述,请参见代码中的注释。
// 在本循环中,创建CPriceHistogram的对象并初始化 // 以及添加到对象列表中 for(int i=0;i<rates_total;i++) { CPriceHistogram *hist_obj=new CPriceHistogram(); // hist_obj.StepHistigram(step); // 设置标志显示文本标签 hist_obj.ShowLevel(ShowValue); // 设置POCs颜色 hist_obj.ColorPOCs(ControlPoint); // 设置范围内的颜色 hist_obj.ColorInner(InnerRange); // 设置范围外的颜色 hist_obj.ColorOuter(OuterRange); // 设置范围百分比 hist_obj.RangePercent(RangePercent); // hist_obj.ShowSecondaryPOCs((i>=rates_total-DayTheHistogram),PeriodSeconds(PERIOD_D1)); if(hist_obj.Init(day_time_open[i],day_time_open[i]+PeriodSeconds(PERIOD_D1),(i>=rates_total-DayTheHistogram))) list_object.Add(hist_obj); else delete hist_obj; // 如果有错误则删除对象 progress.Value(i); };
OnTick() 是在您接收到某交易品种新的价格跳动时调用的方法。我们将存储在变量 count_bars 中的天数的值与 Bars (Symbol (), PERIOD_D1) 返回的日柱的数量进行比较:如果不相等,我们将强制为类初始化调用方法 Init (),清除列表 list_object 并改变变量为 NULL name_symbol。如果天数未变,循环将遍历存储在类 CPriceHistogram list_object 中的所有对象,然后针对那些原始 POC(“原始”) 执行方法 Redraw ()。
Deinit() 是用于类取消初始化的方法。对于 REASON_PARAMETERS(输入参数被用户更改)情形,我们清除 list_object 列表并将 name_symbol 变量设置为 NULL。在其他情况下,EA 交易程序不执行任何操作,如要执行添加则请参见注释。
OnEvent() 是用于客户端事件处理的方法。当用户操作图表时,事件由客户端生成。详细信息请参见 MQL5 语言文档。在本“EA 交易”中,图表事件 CHARTEVENT_OBJECT_CLICK 已被使用。通过点击直方图元素,显示次级 POC 水平并反转直方图颜色。
OnTimer(void) 是用于计时器事件处理的方法。我没有在程序中使用该方法,但如果您想要添加一些计时器操作(例如显示时间),它就在这里。使用前,必须将以下代码行添加至类构造函数:
EventSetTimer(time in seconds);
并添加以下代码行至析构函数:
EventKillTimer();
在调用方法 Deinit (REASON_CHARTCLOSE) 前。
我们已对 CExpert 类进行了讨论;它被创建用于展示 CPriceHistogram 类方法。
4.3. ClassPriceHistogram.mqh//+------------------------------------------------------------------+ //| CPriceHistogram类 | //| 类的描述 | //+------------------------------------------------------------------+ class CPriceHistogram : public CObject { private: // 类的变量 double high_day,low_day; bool Init_passed; // 初始化是否通过的标识 CChartObjectTrend *POCLine; CChartObjectTrend *SecondTopPOCLine,*SecondBottomPOCLine; CChartObjectText *POCLable; CList ListHistogramInner; // 存储内部行的列表 CList ListHistogramOuter; // 存储外部行的列表 bool show_level; // 显示水平的值 bool virgin; // 原始的 bool show_second_poc; // 显示次级POCs水平 double second_poc_top; // 顶部次级POC水平 值 double second_poc_bottom; // 底部次级POC水平值 double poc_value; // POC水平值 color poc_color; // POC水平颜色 datetime poc_start_time; datetime poc_end_time; bool show_histogram; // 显示直方图 color inner_color; // 直方图内部颜色 color outer_color; // 直方图外部颜色 uint range_percent; // 百分比范围 datetime time_start; // 构建的开始时间 datetime time_end; // 构建的最后时间 public: // 类的构造函数 CPriceHistogram(); // 类的析构函数 ~CPriceHistogram(){Delete();} // 类的初始化 bool Init(datetime time_open,datetime time_close,bool showhistogram); // 水平值 void ShowLevel(bool show){show_level=show; if(Init_passed) RefreshPOCs();} bool ShowLevel(){return(show_level);} // 显示直方图 void ShowHistogram(bool show); bool ShowHistogram(){return(show_histogram);} // 显示次级POC水平 void ShowSecondaryPOCs(bool show){show_second_poc=show;if(Init_passed)RefreshPOCs();} bool ShowSecondaryPOCs(){return(show_second_poc);} // 设置POC水平颜色 void ColorPOCs(color col){poc_color=col; if(Init_passed)RefreshPOCs();} color ColorPOCs(){return(poc_color);} // 设置直方图的内部颜色 void ColorInner(color col); color ColorInner(){return(inner_color);} // 设置直方图的外部颜色 void ColorOuter(color col); color ColorOuter(){return(outer_color);} // 设置百分比范围 void RangePercent(uint percent){range_percent=percent; if(Init_passed)calculationPOCs();} uint RangePercent(){return(range_percent);} // 返回POC水平原始值 bool VirginPOCs(){return(virgin);} // 返回直方图的构建时间 datetime GetStartDateTime(){return(time_start);} // 更新POC水平 bool RefreshPOCs(); private: // 计算直方图和POC水平 bool calculationPOCs(); // 删除类 void Delete(); };
在类的说明中,我尝试为类变量和方法添加注释。接下来我们详细讨论其中的部分内容。
//+------------------------------------------------------------------+ //| 类的初始化 | //+------------------------------------------------------------------+ bool CPriceHistogram::Init(datetime time_open,datetime time_close,bool showhistogram)
此方法使用三个输入参数 - 构造开始、构建的结束时间以及指示构建直方图的标志,或仅仅是 POC 的水平。
在我的示例(类 CExpert)中,输入参数在开始日和第二天开始时间 day_time_open [i] + PeriodSeconds (PERIOD_D1) 传递。但是当您使用这个类时,没有什么阻碍您要求,比如,欧洲时间、美洲时段或周、月中的缺口大小等。
//+---------------------------------------------------------------------------------------+ //| 计算直方图和POC水平 | //+---------------------------------------------------------------------------------------+ bool CPriceHistogram::calculationPOCs()
在此方法中,所有水平的原点及其构造的计算,它是一个封闭的私有方法,无法从外部访问。
// 从 time_start 到 time_end 中获取数据 int err=0; do { //--- 复制每个柱形的开盘时间 rates_time=CopyTime(NULL,PERIOD_M1,time_start,time_end,iTime); if(rates_time<0) PrintErrorOnCopyFunction("CopyTime",_Symbol,PERIOD_M1,GetLastError()); //--- 复制每个柱形的最高价 rates_high=CopyHigh(NULL,PERIOD_M1,time_start,time_end,iHigh); if(rates_high<0) PrintErrorOnCopyFunction("CopyHigh",_Symbol,PERIOD_M1,GetLastError()); //--- 复制每个柱形的最低价 rates_total=CopyLow(NULL,PERIOD_M1,time_start,time_end,iLow); if(rates_total<0) PrintErrorOnCopyFunction("CopyLow",_Symbol,PERIOD_M1,GetLastError()); err++; } while((rates_time<=0 || (rates_total!=rates_high && rates_total!=rates_time)) && err<AMOUNT_OF_ATTEMPTS&&!IsStopped()); if(err>=AMOUNT_OF_ATTEMPTS) { return(false); } poc_start_time=iTime[0]; high_day=iHigh[ArrayMaximum(iHigh,0,WHOLE_ARRAY)]; low_day=iLow[ArrayMinimum(iLow,0,WHOLE_ARRAY)]; int count=int((high_day-low_day)/_Point)+1; // 对每一个水平,计算某一价格在此持续的时间 int ThicknessOfLevel[]; // 创建一个数组用于计算ticks ArrayResize(ThicknessOfLevel,count); ArrayInitialize(ThicknessOfLevel,0); for(int i=0;i<rates_total;i++) { double C=iLow[i]; while(C<iHigh[i]) { int Index=int((C-low_day)/_Point); ThicknessOfLevel[Index]++; C+=_Point; } } int MaxLevel=ArrayMaximum(ThicknessOfLevel,0,count); poc_value=low_day+_Point*MaxLevel;
首先,我们获得某个时间周期的分钟柱历史数据 (iTime [], iHigh[], iLow[])。然后我们找出 iHigh[] 和 iLow[] 的最大和最小元素。接下来我们计算从最小到最大的点数(计数),使用 ThicknessOfLevel 元素保存数组 ThicknessOfLevel。在循环中,我们从最低价到最高价遍历每个分钟烛形,并添加该价格水平的时间周期数据。然后我们找出 ThicknessOfLevel 数组中的最大元素,它将是价格维持最长时间所在的水平。这就是我们的 POC 水平。
// 查找次级POCs int range_min=ThicknessOfLevel[MaxLevel]-ThicknessOfLevel[MaxLevel]*range_percent/100; int DownLine=0; int UpLine=0; for(int i=0;i<count;i++) { if(ThicknessOfLevel[i]>=range_min) { DownLine=i; break; } } for(int i=count-1;i>0;i--) { if(ThicknessOfLevel[i]>=range_min) { UpLine=i; break; } } if(DownLine==0) DownLine=MaxLevel; if(UpLine==0) UpLine=MaxLevel; second_poc_top=low_day+_Point*UpLine; second_poc_bottom=low_day+_Point*DownLine;
下一步是找出次级 POC 水平。回想一下我们已对图进行了划分。我们的直方图已划分为两个范围,内部和外部(以不同的颜色显示),且尺寸范围使用位于该水平的价格的时间百分比进行定义。内边界的范围为次级 POC 水平。
在找出次级 POC - 边界百分比范围后,继续构建直方图。
// 直方图信息 if(show_histogram) { datetime Delta=(iTime[rates_total-1]-iTime[0]-PeriodSeconds(PERIOD_H1))/ThicknessOfLevel[MaxLevel]; int step=1; if(count>100) step=count/100; // 计算直方图的步长(最大100行) ListHistogramInner.Clear(); ListHistogramOuter.Clear(); for(int i=0;i<count;i+=step) { string name=TimeToString(time_start)+" "+IntegerToString(i); double StartY= low_day+_Point*i; datetime EndX= iTime[0]+(ThicknessOfLevel[i])*Delta; CChartObjectTrend *obj=new CChartObjectTrend(); obj.Create(0,name,0,poc_start_time,StartY,EndX,StartY); obj.Background(true); if(i>=DownLine && i<=UpLine) { obj.Color(inner_color); ListHistogramInner.Add(obj); } else { obj.Color(outer_color); ListHistogramOuter.Add(obj); } } }
值得一提的是,为了减轻终端的负载,我将屏幕上的每个直方图限制在 100 根线以内。直方图的线存储在两个列表中,ListHistogramInner 和 ListHistogramOuter,它们是我们已知的类 CList 的对象。但这些指针存储在对象 CChartObjectTrend 的标准类中。为什么是两个列表,我想您从标题不难猜出,是为了能够更改直方图颜色。
// 我们从直方图的最后时间开始接收数据,一直到当前时间 err=0; do { rates_time=CopyTime(NULL,PERIOD_M1,time_end,last_tick.time,iTime); rates_high=CopyHigh(NULL,PERIOD_M1,time_end,last_tick.time,iHigh); rates_total=CopyLow(NULL,PERIOD_M1,time_end,last_tick.time,iLow); err++; } while((rates_time<=0 || (rates_total!=rates_high && rates_total!=rates_time)) && err<AMOUNT_OF_ATTEMPTS); // 如果没有历史数据,当前天是原始水平,则我们提升颜色 if(rates_time==0) { virgin=true; } else // 否则我们检查历史数据 { for(index=0;index<rates_total;index++) if(poc_value<iHigh[index] && poc_value>iLow[index]) break; if(index<rates_total) // 如果水平被穿越 poc_end_time=iTime[index]; else virgin=true; } if(POCLine==NULL) { POCLine=new CChartObjectTrend(); POCLine.Create(0,TimeToString(time_start)+" POC ",0,poc_start_time,poc_value,0,0); } POCLine.Color(poc_color); RefreshPOCs();
我已尽量设计 CPriceHistogram 及所有必要方法,如果尚有疏漏之处,您可以添加自己的方法,我将乐意提供帮助。
总结
我要再次提醒的是,价格直方图是可靠的直觉型工具,因此它的使用必须结合确认信号。
感谢您阅读拙作。我乐意为您解答任何问题。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/17