建立一个频谱分析程序
简介
本文旨在让读者熟悉使用 MQL5 语言图形对象的一种可能变量。它会对一个利用图形对象管理简单频谱分析程序的控制面板的实施指标进行分析。
本文随附三份文件:
- SpecAnalyzer.mq5 – 本文中讲述的指标。
- SpecAnalyzer.mqh – SpecAnalyzer.mq5 的包含文件。
- SAInpData.mq5 – 用于组织外部数据访问的指标。
为了正常加载 SpecAnalyzer 指标,您要将全部三个文件放到客户端 \MQL5\Indicators 文件夹中。之后,您还要编译 SpecAnalyzer.mq5 和 SAInpData.mq5 文件。该指标设计于主图表窗口中加载。一旦指标被加载,它就会更改此窗口显示的参数,而当其被移除时,窗口中的所有图形对象也被被删除。正因如此,您要在其被创建的专用独立窗口中加载该指标,以避免终端现有窗口显示模式的意外变更。
您要认清一点:本文中并不包含该指标的完整源代码,阅读本文时,建议您打开随附文件中的代码。
本文所述指标无意充当即用型应用程序。它只是使用该语言图形对象的一个示例而已。而有意者可自行升级和优化文中所示之代码。
坐标
MQL5 中绘制图形对象时,有两种指定坐标的方式。有些对象的坐标是作为距窗口某指定点位的像素数指定,而其它坐标则被指定为窗口中显示图表的价格与时间值。
比如说,如果要将“标签”或“按钮”之类的对象放上图表,则您应将其坐标指定为距图表窗口某个角的像素距离。不管当前的窗口属性和其中显示的图表尺度如何,这种方式都能让对象保持其位置。就算是窗口尺寸有变化,此类对象也会保持其位置和彼此的绑定。如果您用鼠标左键在窗口内移动图表,上述对象则会保持其相对于窗口选定固定点的位置。
其它对象组表示绑定到窗口中的某个图表,而非窗口坐标。其中包括“趋势线”、“矩形”等对象。创建和转入此类对象时,坐标是指定为窗口中显示图表的时间与价格值。在这种坐标指定模式下,如果图表尺寸有变化或滚动,则对象会相对于图表窗口和彼此之间距离改变其位置。随着图表上方新柱的出现,这些对象也会改变其位置,因为时间轴会根据时间表大小向左移动。
在 SpecAnalyzer 指标中,创建频谱分析程序控制面板时,要同时用到上述两种图形对象。针对绑定于图表、不相对窗口移动的对象,我们设定为图表沿着纵轴显示的固定模式,以及对应着沿图表水平标度显示的最小可能尺寸的模式。此外,纵向尺寸的最小值设置为 0.0;而横轴的模式设置条件,则是零柱没有于右边缘的平移,且没有于图表右边缘的自动滚动。因此,符合零柱及图表 0.0 值的坐标点似乎是在右下角,而且具备固定尺寸的它,可被用作“趋势线“和”矩形“之类对象的一个固定点。于是,如果我们将同一个右下角设定为”标签“或”按钮“之类对象的固定点,则两种对象类型一定会彼此绑定。
所有的必要图表属性均于 SpecAnalyzer.mqh 文件中的 SetOwnProperty() 函数中设定。
void GRaphChart::SetOwnProperty(void) { ChartSetInteger(m_chart_id,CHART_FOREGROUND,1); ChartSetInteger(m_chart_id,CHART_SHIFT,0); ChartSetInteger(m_chart_id,CHART_AUTOSCROLL,0); ChartSetInteger(m_chart_id,CHART_AUTOSCROLL,1); ChartSetInteger(m_chart_id,CHART_SCALE,0); ChartSetInteger(m_chart_id,CHART_SCALEFIX_11,1); ChartSetInteger(m_chart_id,CHART_SHOW_OHLC,0); ChartSetInteger(m_chart_id,CHART_SHOW_BID_LINE,0); ChartSetInteger(m_chart_id,CHART_SHOW_ASK_LINE,0); ChartSetInteger(m_chart_id,CHART_SHOW_LAST_LINE,0); ChartSetInteger(m_chart_id,CHART_SHOW_PERIOD_SEP,0); ChartSetInteger(m_chart_id,CHART_SHOW_GRID,0); ChartSetInteger(m_chart_id,CHART_SHOW_VOLUMES,CHART_VOLUME_HIDE); ChartSetInteger(m_chart_id,CHART_SHOW_OBJECT_DESCR,0); ChartSetInteger(m_chart_id,CHART_SHOW_TRADE_LEVELS,0); ChartSetInteger(m_chart_id,CHART_COLOR_BACKGROUND,Black); ChartSetInteger(m_chart_id,CHART_COLOR_FOREGROUND,Black); ChartSetDouble(m_chart_id,CHART_FIXED_MIN,0.0); }
除了设置图表的必要属性以实现图形对象的绑定之外,此函数还会设定允许赋予颜色、限制图表中某些元素显示的相关属性。
由于 SpecAnalyzer 指标在运行的同时改变了图表属性,所以我们会在指标加载时提供”保持此前图表设置“,在其上传时提供”恢复设置“。MQL5 的标准库中包含作此用途的专用虚拟函数 - CChart 类的 Save() 与 Load()。上述函数旨在将 CChart 类某对象的属性保存到文件中,并从创建的文件恢复这些属性。要更改已保存属性集,且不想在保存某图表属性时动用文件操作,则 CChart 类的虚拟函数 Save() 与 Load() 就会在创建新类 GRaphChart 时被覆盖(参见 SpecAnalyzer.mqh 文件)。
class GRaphChart : public CChart { protected: struct ChartPropertyes { double shift_size; double fixed_max; double fixed_min; double points_per_bar; long mode; bool foreground; bool shift; bool autoscroll; long scale; bool scalefix; bool scalefix_11; bool scale_pt_per_bar; bool show_ohls; bool show_bid_line; bool show_ask_line; bool show_last_line; bool show_period_sep; bool show_grid; long show_volumes; bool show_object_descr; bool show_trade_levels; long color_background; long color_foreground; long color_grid; long color_volume; long color_chart_up; long color_chart_down; long color_chart_line; long color_candle_bull; long color_candle_bear; long color_bid; long color_ask; long color_last; long color_stop_level; string ch_comment; }; ChartPropertyes ChProp; public: GRaphChart(); ~GRaphChart(); void SetOwnProperty(); virtual void Save(); virtual void Load(); };
GRaphChart 的基类是 MQL5 标准库中的 CChart。GRaphChart 类中包含 ChartPropertyes 结构的描述,而 ChProp object 对象的创建,旨在存储内存中的图表属性,而非如基类中实施的某个文件。Save() 函数会根据图表的当前属性填充 ChProp 结构,而 Load() 函数则会从中恢复之前保存的属性。
Save() 函数于 GRaphChart 类的构造函数中调用,而 Load() 函数则于析构函数中调用。正因如此,创建和删除 GRaphChart 类的对象时,图表状态的保存和恢复之前状态都会自动执行。上面提到的 SetOwnProperty() 也是在类构造函数中调用。
//---------------------------------------------------- GRaphChart 构造函数-- GRaphChart::GRaphChart() { m_chart_id=ChartID(); Save(); // 保存原有图表设置 SetOwnProperty(); // 设置自己的图表设置 } //----------------------------------------------------- GRaphChart 析构函数-- GRaphChart::~GRaphChart() { Load(); // 恢复之前的图表设置 m_chart_id=-1; }
我们举一个简单的例子来说明 GRaphChart 类的使用。为此,我们在 MetaEditor 中创建一个新的自定义指标,并命名为 Test (测试)。将头文件 SpecAnalyzer.mqh 纳入指标代码中,再添加两行,创建一个 GRaphChart 类的对象。
//+------------------------------------------------------------------+ //| Test.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #property indicator_chart_window #include "SpecAnalyzer.mqh" // <----- 包含头文件 GRaphChart MainChart; // <----- 创建 GRaphChart 类对象 //+------------------------------------------------------------------+ //| 自定义指标初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //--- 指标缓冲区映射 //--- return(0); } //+------------------------------------------------------------------+ //| 自定义指标迭代函数 | //+------------------------------------------------------------------+ 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[]) { //--- //--- 返回 prev_calculated 值用于下次调用 return(rates_total); } //+------------------------------------------------------------------+
要想上述代码能够成功编译,则务必将 SpecAnalyzer.mqh 文件置入客户端的 \MQL5\Indicators 文件夹中。
如果您是在客户端创建一个图形,并于其中尝试加载我们的测试示例,那么,图表属性就会被改变,而您则只能看到一个准备在其中显示图形对象的空白窗口。随着您从图形中移除我们的测试指标,一旦出现新的价格跳动,就会恢复其初始外观。
我们再回到 SpecAnalyzer 指标上来。在指标的开头(参见 SpecAnalyzer.mq5 文件),即执行了 GRaphChart 类 MainChart 对象的创建,进而实现了指标的加载和图形属性的保存。
button. button. button.
GRaphChart MainChart; // 创建 MainChart 对象
button. button. button.
上传该指标时,只要类析构函数中图形初始属性一恢复,MainChart 对象就会自动终止。
控制面板
SpecAnalyzer 指标中控制面板的外观,由窗口中置入的图形对象决定。AllGrObject 类将创建和彼此交互的所有必要函数都捏合到了一起,参见 SpecAnalyzer.mqh 文件。
class AllGrObject : public CChart { protected: long m_chart_id; // 图表ID public: AllGrObject(); ~AllGrObject(); void AddLabel(string name,int fsize,string font, color col,int x,int y,string text=""); void AddButton(string name,int xsize,int ysize,color bgcol, int fsize,string font,color col,int x,int y, string text="",int state=0); void AddEdit(string name,int xsize,int ysize,color bgcol,int fsize, string font,color col,int x,int y,string text=""); void AddTrendLine(string name,color col,int style=0,int width=1); void AddArrowline(string name,color col,int style=0,int width=1); void AddRectangle(string name,color col,int style=0,int width=1); void MoveGrObject(string name,int x1,int y1,int x2,int y2); void SetButtonProp(string name,bool state,color col); long GetRowNumber(string name); void LabelTxt(string name,string str); };
名称以 Add 开头的类函数,专为创建图形对象而设计。比如说,AddButton() 就会创建”按钮“对象。
在此应用中,所有图形对象的坐标都被设置为与图表右下角的像素距离。至于”趋势线“、”箭头线“和”矩形“对象,我们则要将此类坐标转换成为时间和价格值。此类转换要在将坐标赋予某对象之前于 MoveGrObject() 函数内完成。一个水平像素对应一个柱,而一个垂直像素对应一个点。
void AllGrObject::MoveGrObject(string name,int x1,int y1,int x2,int y2) { datetime t1[1],t2[1]; long typ; typ=ObjectGetInteger(m_chart_id,name,OBJPROP_TYPE); if(typ==OBJ_TREND||typ==OBJ_ARROWED_LINE||typ==OBJ_RECTANGLE) { CopyTime(_Symbol,_Period,x1,1,t1); CopyTime(_Symbol,_Period,x2,1,t2); ObjectSetInteger(m_chart_id,name,OBJPROP_TIME,0,t1[0]); ObjectSetDouble(m_chart_id,name,OBJPROP_PRICE,0,_Point*y1); ObjectSetInteger(m_chart_id,name,OBJPROP_TIME,1,t2[0]); ObjectSetDouble(m_chart_id,name,OBJPROP_PRICE,1,_Point*y2); } }
指标中的图形对象都只创建一次,而创建则通过调用指标的 OnInit() 函数的 gr_object_create() 来完成;参见 SpecAnalyzer.mq5 文件。除了”趋势线”、”箭头线“和”矩形“之外,所有对象的坐标都能即刻设置。而像”趋势线”、“箭头线”和“矩形”之类的对象坐标,则通过调用 gr_object_coordinate() 函数(使用上述变换处理模式的 MoveGrObject() 函数)来设置。
void gr_object_coordinate() { GObj.MoveGrObject("Trdline1",48,150,48,360); GObj.MoveGrObject("Trdline2",176,150,176,360); GObj.MoveGrObject("Trdline3",304,150,304,360); GObj.MoveGrObject("Trdline4",432,150,432,360); GObj.MoveGrObject("Trdline5",42,350,560,350); GObj.MoveGrObject("Trdline6",42,300,560,300); GObj.MoveGrObject("Trdline7",42,250,560,250); GObj.MoveGrObject("Trdline8",42,200,560,200); GObj.MoveGrObject("Arrline1",560,150,28,150); GObj.MoveGrObject("Arrline2",560,150,560,370); GObj.MoveGrObject("Rect1",0,1,208,110); GObj.MoveGrObject("Rect2",208,1,416,110); GObj.MoveGrObject("Rect3",416,1,624,110); GObj.MoveGrObject("Rect4",0,1,624,400); GObj.MoveGrObject("Rect5",20,10,195,80); }
gr_object_coordinate() 函数的调用被纳入到了指标的 OnCalculate() 函数中。因为该函数每次有新的价格跳动时都会被调用,所以图表上形成新柱时,它就会重新执行一次正确的坐标计算。
指标面板上的按钮可分为三组。第一组由左侧的四个按钮构成,它们允许选择一种按指标显示输入序列频谱评估结果的模式。支持四种显示模式(根据按钮的数量):
- Amplitude/Line (振幅/线) - 以沿着 Y 轴的线性标度,显示傅里叶变换模块。
- Amplitude/dB (振幅/dB) - 以沿着 Y 轴的对数标度,显示傅里叶变换模块。
- Power/Line (力量/线) - 以沿着 Y 轴的线性标度,显示傅里叶变换模块平方。
- Power/dB (力量/dB) - 以沿着 Y 轴的对数标度,显示傅里叶变换模块平方。
要进行处理,则在此组的按钮上方点击,指标 OnChartEvent() 函数中包含下述代码:
if(id==CHARTEVENT_OBJECT_CLICK) // 点击在图表对象上 { if(sparam=="Butt1") // 点击了按钮 { GObj.SetButtonProp("Butt1",1,Chocolate); GObj.SetButtonProp("Butt2",0,SlateGray); GObj.SetButtonProp("Butt3",0,SlateGray); GObj.SetButtonProp("Butt4",0,SlateGray); YPowerFlag=0;YdBFlag=0; } if(sparam=="Butt2") // 点击了按钮 { GObj.SetButtonProp("Butt1",0,SlateGray); GObj.SetButtonProp("Butt2",1,Chocolate); GObj.SetButtonProp("Butt3",0,SlateGray); GObj.SetButtonProp("Butt4",0,SlateGray); YPowerFlag=0;YdBFlag=1; } if(sparam=="Butt3") // 点击了按钮 { GObj.SetButtonProp("Butt1",0,SlateGray); GObj.SetButtonProp("Butt2",0,SlateGray); GObj.SetButtonProp("Butt3",1,Chocolate); GObj.SetButtonProp("Butt4",0,SlateGray); YPowerFlag=1;YdBFlag=0; } if(sparam=="Butt4") // 点击了按钮 { GObj.SetButtonProp("Butt1",0,SlateGray); GObj.SetButtonProp("Butt2",0,SlateGray); GObj.SetButtonProp("Butt3",0,SlateGray); GObj.SetButtonProp("Butt4",1,Chocolate); YPowerFlag=1;YdBFlag=1; }
如果探测到某个按钮上方有点击,则其它按钮都会变成未按下状态,从而防止同时按下一组内的多个按钮。同时,确定当前显示模式的 YPowerFlag 与 YdBFlag 标志的相应值亦予设定。
由四个按钮构成的第二个组,则提供了选取输入数据源的可能性。可以是通过调用 SAInpData.mq5 指标获得的外部数据,也可以是通过该应用本身生成的三个测试序列。最后一个按钮组中包含两个用于滚动输入文本信息字段中相应列表内容的按钮。上述所有按钮的点击处理,也和第一组按钮一样,都在 OnChartEvent() 函数中执行,参见 SpecAnalyzer.mq5 文件。
我们用之前创建的测试指标 Test.mq5 作为示例,讲解一下 AllGrObject 的使用。为此,向其源代码添加几行,并加入之前提到的 SpecAnalyzer.mq5 文件中的 gr_object_create() 与 gr_object_coordinate() 函数。
//+------------------------------------------------------------------+ //| Test.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #property indicator_chart_window #include "SpecAnalyzer.mqh" GRaphChart MainChart; AllGrObject GObj; // <----- 创建 AllGrObject 类的对象 //+------------------------------------------------------------------+ //| 自定义指标初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //--- 指标缓冲区映射 //--- gr_object_create(); // <----- 创建图形对象 return(0); } //+------------------------------------------------------------------+ //| 自定义指标迭代函数 | //+------------------------------------------------------------------+ 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[]) { //--- MainChart.SetOwnProperty(); // <----- 恢复图表的当前属性 gr_object_coordinate(); // <----- 设置图形对象的坐标 //--- 返回 prev_calculated 值用于下次调用 return(rates_total); } //----------------------------------------------- 创建所有的图形对象 -- void gr_object_create() { GObj.AddLabel("Title",10,"Arial",DarkGray,256,367,"频谱分析仪"); Obj.AddLabel("Label1",9,"Arial",LightSteelBlue,557,128,"0"); GObj.AddLabel("Label2",9,"Arial",LightSteelBlue,422,128,"128"); GObj.AddLabel("Label3",9,"Arial",LightSteelBlue,294,128,"256"); GObj.AddLabel("Label4",9,"Arial",LightSteelBlue,166,128,"384"); GObj.AddLabel("Label5",9,"Arial",LightSteelBlue,40,128,"512"); GObj.AddLabel("Label6",9,"Arial",LightSteelBlue,28,156,"N"); GObj.AddLabel("Label7",9,"Arial",LightSteelBlue,569,141,"0.00"); GObj.AddLabel("Label8",9,"Arial",LightSteelBlue,569,191,"0.25"); GObj.AddLabel("Label9",9,"Arial",LightSteelBlue,569,241,"0.50"); GObj.AddLabel("Label10",9,"Arial",LightSteelBlue,569,291,"0.75"); GObj.AddLabel("Label11",9,"Arial",LightSteelBlue,569,341,"1.00"); GObj.AddLabel("Label12",9,"Arial",LightSteelBlue,569,358,"U"); GObj.AddLabel("Label13",9,"Arial",DarkGray,490,86,"Y-轴模式"); GObj.AddLabel("Label14",9,"Arial",DarkGray,285,86,"输入数据"); GObj.AddLabel("Label15",9,"Arial",DarkGray,75,86,"水平"); GObj.AddLabel("Label16",9,"Arial",DarkGray,185,86,"N"); GObj.AddLabel("Label17",8,"Courier",DarkOliveGreen,64,64); GObj.AddLabel("Label18",8,"Courier",DarkOliveGreen,64,51); GObj.AddLabel("Label19",8,"Courier",DarkOliveGreen,64,38); GObj.AddLabel("Label20",8,"Courier",DarkOliveGreen,64,25); GObj.AddLabel("Label21",8,"Courier",DarkOliveGreen,64,12); GObj.AddButton("Butt1",185,18,C'32,32,32',8,"Arial",SlateGray,612,79,"振幅 (线)",0); GObj.AddButton("Butt2",185,18,C'32,32,32',8,"Arial",Chocolate,612,61,"振幅 (对数)",1); GObj.AddButton("Butt3",185,18,C'32,32,32',8,"Arial",SlateGray,612,43,"能量 (线)",0); GObj.AddButton("Butt4",185,18,C'32,32,32',8,"Arial",SlateGray,612,25,"能量 (对数)",0); GObj.AddButton("Butt5",185,18,C'32,32,32',8,"Arial",SlateGray,403,79,"外部数据",0); GObj.AddButton("Butt6",185,18,C'32,32,32',8,"Arial",SlateGray,403,61,"测试 1. SMA(3)",0); GObj.AddButton("Butt7",185,18,C'32,32,32',8,"Arial",Chocolate,403,43,"测试 2. SMA(32)",1); GObj.AddButton("Butt8",185,18,C'32,32,32',8,"Arial",SlateGray,403,25,"测试 3. LWMA(12)",0); GObj.AddButton("Butt9",14,34,C'32,32,32',8,"Wingdings",SlateGray,36,78,"\x0431",0); GObj.AddButton("Butt10",14,34,C'32,32,32',8,"Wingdings",SlateGray,36,44,"\x0432",0); GObj.AddEdit("Edit1",35,18,Black,9,"Arial",SlateGray,180,102); GObj.AddTrendLine("Trdline1",C'32,32,32'); GObj.AddTrendLine("Trdline2",C'32,32,32'); GObj.AddTrendLine("Trdline3",C'32,32,32'); GObj.AddTrendLine("Trdline4",C'32,32,32'); GObj.AddTrendLine("Trdline5",C'32,32,32'); GObj.AddTrendLine("Trdline6",C'32,32,32'); GObj.AddTrendLine("Trdline7",C'32,32,32'); GObj.AddTrendLine("Trdline8",C'32,32,32'); GObj.AddArrowline("Arrline1",LightSteelBlue); GObj.AddArrowline("Arrline2",LightSteelBlue); GObj.AddRectangle("Rect1",C'72,72,72'); GObj.AddRectangle("Rect2",C'72,72,72'); GObj.AddRectangle("Rect3",C'72,72,72'); GObj.AddRectangle("Rect4",DarkGray); GObj.AddRectangle("Rect5",C'72,72,72'); } //---------- 设置趋势线,箭头线和长方形对象的坐标 -- void gr_object_coordinate() { GObj.MoveGrObject("Trdline1",48,150,48,360); GObj.MoveGrObject("Trdline2",176,150,176,360); GObj.MoveGrObject("Trdline3",304,150,304,360); GObj.MoveGrObject("Trdline4",432,150,432,360); GObj.MoveGrObject("Trdline5",42,350,560,350); GObj.MoveGrObject("Trdline6",42,300,560,300); GObj.MoveGrObject("Trdline7",42,250,560,250); GObj.MoveGrObject("Trdline8",42,200,560,200); GObj.MoveGrObject("Arrline1",560,150,28,150); GObj.MoveGrObject("Arrline2",560,150,560,370); GObj.MoveGrObject("Rect1",0,1,208,110); GObj.MoveGrObject("Rect2",208,1,416,110); GObj.MoveGrObject("Rect3",416,1,624,110); GObj.MoveGrObject("Rect4",0,1,624,400); GObj.MoveGrObject("Rect5",20,10,195,80); } //+------------------------------------------------------------------+
要实现对于 AllGrObject 类函数的访问,则创建此类的 GObj 对象。在指标的 OnInit() 函数中加入 gr_object_create() 函数的调用,而该函数会创建确定指标控制面板外观与功能的所有必要图形对象。
在 OnCalculate 函数中添加 MainChart.SetOwnProperty() 与 gr_object_coordinate() 函数的调用;由此,图表属性与所绘对象的坐标也都会随着每一次出现新的价格跳动而恢复。如果初始图表形成了新柱,或是用鼠标左键移动了图表,或者是用户更改了图表属性,都需要这种恢复。完成此测试示例的编译和加载后,我们就会看到控制面板的界面了,请参见图 1。
图 1. 控制面板界面
如您想要控制面板于图表相对式放置的可视化呈现,则启用上例中的图表标度显示;参见图 2。
图 2. 图表标度。
频谱分析程序
指标中的频谱分析是通过输入序列的 1024 个刻度完成的。而其执行则要靠快傅里叶变换算法。实施 FFT 算法的函数,取自 www.mql4.com 网站的发布内容。我们采用输入真实时间序列的 FFT 函数进行计算,其代码位于 SpecAnalyzer.mqh 文件中。频谱分析所需的所有动作,均于 fft_calc() 函数内实施。
void fft_calc() { int i,k; realfastfouriertransform(InpData,ArraySize(InpData),false); // FFT for(i=1;i<ArraySize(Spectrum);i++) { k=i*2; Spectrum[i]=InpData[k]*InpData[k]+InpData[k+1]*InpData[k+1]; } Spectrum[0]=0.0; // 清除组件水平常数 }
调用 fft_calc() 函数时,InpData[] 数组中必须包含一个待分析的输入序列。执行 realfastfouriertransform() 之后,FFT 的结果就会被放到该数组。而且,针对每一个来自频谱真实及想像估计部分的谐波计算模块平方,且将结果写入到 Spectrum[] 数组。Spectrum[] 数组中的元素索引与谐波数对应。由于指标中未采用恒定分量的计算值,所以总是为该数组的 Spectrum[0] 元素赋予零值。
根据变量 InputSource 的值,InpData[] 数组或者利用测试序列填充,或是利用由某个外部指标获得的数据填充。输入数据在 get_input_data() 函数内形成。
void get_input_data() { int i; ArrayInitialize(InpData,0.0); switch(InputSource) { case 0: // 外部数据 if(ExtHandle!=INVALID_HANDLE) CopyBuffer(ExtHandle,0,0,ArraySize(InpData),InpData); break; case 1: // 测试 1. SMA(3) for(i=0;i<3;i++)InpData[i]=1; break; case 2: // 测试 2. SMA(32) for(i=0;i<32;i++)InpData[i]=1; break; case 3: // 测试 3. LWMA(12) for(i=0;i<12;i++)InpData[i]=12-i; break; } }
如果 InputSource 的值等于零,则会有 1024 个值从 SAInpData.mq5 指标的零缓冲区复制到输入数组 InpData[]。分析用数据可以在该指标本身中形成,也可以通过由其调用其它指标形成。要实现对于 SAInpData.mq5 指标的访问,就要在确定 ExtHandle 变量值的 OnInit() 函数中添加下述行。
int OnInit() { button. button. button. ExtHandle=iCustom(NULL,0,"SAInpData"); // 外部指标句柄 return(0); }
务必将 SAInpData.mq5 指标放入客户端的 \MQL5\Indicators 目录。本文随附的 SAInpData.mq5 示例指标,会向分析程序传递一个随机序列。要让 SAInpData.mq5 向分析程序传递另一序列,则请更改其源代码。
作为 get_input_data() 函数的一个测试序列,会生成 SMA(3)、SMA(32) 与 LWMA(12) 移动平均线的脉冲特性。考虑到过滤器某脉冲特性的傅里叶变换与该过滤器的幅度频率特性相符,所以,我们可以观测移动平均线的幅度频率(如果我们选择它们作为测试序列)。
至于存储于 Spectrum[] 数组中的频谱估计结果的标准化,以及令其于指定模式中显示的相关准备,则用 norm_and_draw() 函数来实现。参见 SpecAnalyzer.mq5 文件。此函数会根据选定的显示模式,取代 Y 轴的文本标记。
输入序列频谱估计的结果不仅以图形形式显示,亦以文本形式呈现。为以文本形式呈现结果,创建了五个“标签”类型的图形对象,它们分别对应着显示的五个文本行。list_levels() 函数会利用相关信息填充这些行。
void list_levels() { int m; string str; if(YdBFlag==0) str="%3u %.5f"; // 如果 Y-轴模式 = 线性 else str="%3u %6.1f dB"; // 如果 Y-轴模式 = 分贝 m=ArraySize(ListArray)-5; if(ListPointer<1)ListPointer=1; if(ListPointer>m)ListPointer=m; GObj.LabelTxt("Label17",StringFormat(str,ListPointer,ListArray[ListPointer])); GObj.LabelTxt("Label18",StringFormat(str,ListPointer+1,ListArray[ListPointer+1])); GObj.LabelTxt("Label19",StringFormat(str,ListPointer+2,ListArray[ListPointer+2])); GObj.LabelTxt("Label20",StringFormat(str,ListPointer+3,ListArray[ListPointer+3])); GObj.LabelTxt("Label21",StringFormat(str,ListPointer+4,ListArray[ListPointer+4])); }
这些行会显示来自 ListArray[] 数组、利用 StringFormat() 函数格式化的级别值。此数组会根据当前显示模式,利用 norm_and_draw() 函数中的相关信息进行填充,请参阅 SpecAnalyzer.mq5 文件。ListArray[] 数组信息显示的起点数组索引,等于 ListPointer 变量中存储的相应值。您可以更改 ListPointer 变量的值,并由此改变待显示的起始索引行,方法是按下输出字段右侧的按钮,或是在输入字段中指定必要的索引。按下上述按钮以及完成输入字段修改的相关事件,均于该指标的 OnChartEvent() 函数中进行处理,参阅 SpecAnalyzer.mq5 文件。
SpecAnalyzer 指标的外观如下图所示。
图 3. SpecAnalyzer 指标的外观。
总结
我们说过,这个 SpecAnalyzer.mq5 指标只是某种完整频谱分析程序的一个原型。本文中,它也只是作为使用图形对象的一个示例。而要创建一个完善的指标,您很可能还需要改善其外观,塑造一个功能更强的界面,并改进频谱估计的算法。
您可以极大程度上地改进指标界面,利用“位图”图形对象进行美化,利用某种图形编辑器为其前控制面板创建一个图像,将其作为衬底,在其上方显示各种各样的控制元素。可利用这种方法来创建可换皮肤的指标。
文献
- Yukio Sato。Introduction to Signal Management (《信号管理简介》)。
- L. Rabiner, B. Gold。Theory and Application of Digital Signal Processing (《数字信号处理理论与应用》)。
- S.L. Marple, Jr。Digital Spectral Analysis with Applications (应用程序数字频谱分析)。
文件
- SpecAnalyzer.mq5 – 本文中讲述的指标。
- SpecAnalyzer.mqh – SpecAnalyzer.mq5 的包含文件。
- SAInpData.mq5 – 用于组织外部数据访问的指标。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/185