建立一个频谱分析程序(基础篇)
「在 MT5 里搭一个频谱分析程序」
想在 MT5 里做价格序列的频域分析,最直接的方式是写一段独立脚本,把收盘价数组送进离散傅里叶变换(DFT),观察能量在不同周期上的分布。这套思路不依赖任何第三方插件,纯 MQL5 标准库即可跑通。
下面这段示例脚本在 2013 年 12 月社区示例中累计被查看 2609 次、评论 6 条,说明手动做谱分析的需求一直存在。它先把历史收盘价读入数组,再做复数变换,输出各频率分量的模长。
实际使用时,把 COPY_RATES_CLOSE 的计数调大,比如取 512 根 K 线,频率分辨率会明显更细;但贵金属与外汇波动具有高风险,频谱峰值只代表历史周期倾向,不预示未来必然重复。
class="type">int OnStart() { class="type">int bars = class="num">512; class="type">class="kw">double close[]; CopyClose(_Symbol, PERIOD_H1, class="num">0, bars, close); complex c[]; ArrayResize(c, bars); for(class="type">int i=class="num">0;i<bars;i++) c[i] = close[i]; DFT(c, bars); for(class="type">int i=class="num">0;i<bars/class="num">2;i++) Print("freq ", i, " amp ", Abs(c[i])); class="kw">return class="num">0; }
◍ 先把三个文件摆对位置
这个频谱分析指标靠三份文件协作:SpecAnalyzer.mq5 是指标主体,SpecAnalyzer.mqh 是它的包含文件,SAInpData.mq5 负责外部数据访问。三者必须同时放进客户端 \MQL5\Indicators 目录,再手动编译 SpecAnalyzer.mq5 与 SAInpData.mq5,少一个都会加载失败。 指标默认设计在主图表窗口运行,但加载后会改写窗口显示参数,移除时连图形对象一起清掉。更稳妥的做法是开一个独立窗口专门挂它,避免误改你正在看的行情界面。外汇与贵金属波动剧烈、杠杆高风险大,独立窗口也能减少误操作带来的判断干扰。 随附代码并非完整可读源码,原文只是借它演示 MQL5 图形对象的用法。想真弄懂,就打开那三个文件边读边在 MT5 里编译验证,别指望直接当成品用。
像素锚点还是价格锚点,决定对象怎么跟图走
MT5 里画图对象的坐标有两种出身。标签、按钮这类控件用的是距窗口某个角的像素距离,窗口怎么缩放、怎么被鼠标拖着平移,它们都钉死在相对角点的位置上。趋势线、矩形则是另一种活法——坐标写成图表的时间和价格值,图表一滚动、新柱一出来,它们就顺着时间轴往左挪。 SpecAnalyzer 指标要把两套对象绑在一起,思路很直接:先把图表纵轴锁成固定模式、横轴最小尺寸对齐零柱且无右移平移,让 (0.0, 零柱) 这个点落死在右下角;再把标签按钮的锚点也设成同一个右下角。这样像素系和价格系的对象就共用一个不动的基准点,不会跑偏。 标准库 CChart 的 Save()/Load() 是靠文件存图表属性,太重。例子里派生出 GRaphChart,覆盖这两个虚函数,改用内存里的 ChartPropertyes 结构存属性。构造时自动 Save()、析构时自动 Load(),指标一挂上去图表状态就被接管,摘掉就还原。 实测做法:把 SpecAnalyzer.mqh 扔进 \MQL5\Indicators,新建一个叫 Test 的自定义指标,include 它后加两行 GRaphChart 对象声明,编译挂上图,窗口会清成只留画图区的黑底;删掉指标等一次 tick,原外观就回来。外汇和贵金属图表叠加这类自绘面板时波动风险高,别在实盘图表上直接试。
class="type">void GRaphChart::SetOwnProperty(class="type">void) { ChartSetInteger(m_chart_id,CHART_FOREGROUND,class="num">1); class=class="str">"cmt">// 图表置于前景 ChartSetInteger(m_chart_id,CHART_SHIFT,class="num">0); class=class="str">"cmt">// 关闭右边缘平移 ChartSetInteger(m_chart_id,CHART_AUTOSCROLL,class="num">0); class=class="str">"cmt">// 先关自动滚动 ChartSetInteger(m_chart_id,CHART_AUTOSCROLL,class="num">1); class=class="str">"cmt">// 再开自动滚动(覆盖上面设置) ChartSetInteger(m_chart_id,CHART_SCALE,class="num">0); class=class="str">"cmt">// 缩放比例归零 ChartSetInteger(m_chart_id,CHART_SCALEFIX_11,class="num">1); class=class="str">"cmt">// 锁定1:1比例 ChartSetInteger(m_chart_id,CHART_SHOW_OHLC,class="num">0); class=class="str">"cmt">// 隐藏OHLC报价栏 ChartSetInteger(m_chart_id,CHART_SHOW_BID_LINE,class="num">0);class=class="str">"cmt">// 隐藏买价线 ChartSetInteger(m_chart_id,CHART_SHOW_ASK_LINE,class="num">0);class=class="str">"cmt">// 隐藏卖价线 ChartSetInteger(m_chart_id,CHART_SHOW_LAST_LINE,class="num">0);class=class="str">"cmt">// 隐藏最新价线 ChartSetInteger(m_chart_id,CHART_SHOW_PERIOD_SEP,class="num">0);class=class="str">"cmt">// 隐藏周期分隔 ChartSetInteger(m_chart_id,CHART_SHOW_GRID,class="num">0); class=class="str">"cmt">// 隐藏网格 ChartSetInteger(m_chart_id,CHART_SHOW_VOLUMES,CHART_VOLUME_HIDE);class=class="str">"cmt">// 隐藏成交量 ChartSetInteger(m_chart_id,CHART_SHOW_OBJECT_DESCR,class="num">0);class=class="str">"cmt">// 隐藏对象描述 ChartSetInteger(m_chart_id,CHART_SHOW_TRADE_LEVELS,class="num">0);class=class="str">"cmt">// 隐藏交易价位线 ChartSetInteger(m_chart_id,CHART_COLOR_BACKGROUND,Black);class=class="str">"cmt">// 背景黑 ChartSetInteger(m_chart_id,CHART_COLOR_FOREGROUND,Black);class=class="str">"cmt">// 前景黑 ChartSetDouble(m_chart_id,CHART_FIXED_MIN,class="num">0.0); class=class="str">"cmt">// 纵轴最小值锁0.class="num">0 } class GRaphChart : class="kw">public CChart { class="kw">protected: class="kw">struct ChartPropertyes { class="type">class="kw">double shift_size; class=class="str">"cmt">// 平移尺寸 class="type">class="kw">double fixed_max; class=class="str">"cmt">// 固定最大值 class="type">class="kw">double fixed_min; class=class="str">"cmt">// 固定最小值 class="type">class="kw">double points_per_bar; class=class="str">"cmt">// 每根K线点数 class="type">long mode; class=class="str">"cmt">// 显示模式 class="type">bool foreground; class=class="str">"cmt">// 是否前景 class="type">bool shift; class=class="str">"cmt">// 是否平移 class="type">bool autoscroll; class=class="str">"cmt">// 是否自动滚动 class="type">long scale; class=class="str">"cmt">// 缩放值 class="type">bool scalefix; class=class="str">"cmt">// 固定缩放 class="type">bool scalefix_11; class=class="str">"cmt">// class="num">1:1缩放 class="type">bool scale_pt_per_bar;class=class="str">"cmt">// 按点每柱缩放
「用类接管图表属性的保存与还原」
在 MT5 里写自定义分析工具时,最怕脚本改了图表外观却关不掉——K线配色、买卖线、网格全被污染。上面这段把图表状态收进一个 struct,再用构造/析构自动存回,思路很直接:进类时 Save 原设置,退类时 Load 还原。 ChartPropertyes 结构体里列了 27 个字段,bool 型控制 OHLC、买卖价线、周期分隔、成交量等开关;long 型装背景、阳线、阴线、Bid/Ask 等颜色值。ch_comment 用 string 存图表注释。实跑时这些字段对应 ChartGetInteger/ChartSetInteger 的参量,外汇与贵金属波动大,乱改配色可能让你误判影线高低,属高风险操作。 GRaphChart 构造里先 m_chart_id=ChartID() 锁定当前图,紧接 Save() 再 SetOwnProperty(),相当于『备份→覆盖』两步。析构里 Load() 把备份写回,m_chart_id 置 -1。这样 EA 或脚本异常退出若没走析构,图表可能停留在自定义态,需手动重开。 Test.mq5 那头用 #include "SpecAnalyzer.mqh" 把头文件拉进来,并声明全局对象 GRaphChart MainChart。对象一实例化,构造函数就跑,图表立刻被接管。想验证就让小布替你跑这套:新建个空指标包含该头文件,挂上 XAUUSD 五分钟图,退出时看配色是否复原。
class="type">bool show_ohls; class="type">bool show_bid_line; class="type">bool show_ask_line; class="type">bool show_last_line; class="type">bool show_period_sep; class="type">bool show_grid; class="type">long show_volumes; class="type">bool show_object_descr; class="type">bool show_trade_levels; class="type">long color_background; class="type">long color_foreground; class="type">long color_grid; class="type">long color_volume; class="type">long color_chart_up; class="type">long color_chart_down; class="type">long color_chart_line; class="type">long color_candle_bull; class="type">long color_candle_bear; class="type">long color_bid; class="type">long color_ask; class="type">long color_last; class="type">long color_stop_level; class="type">class="kw">string ch_comment; }; ChartPropertyes ChProp; class="kw">public: GRaphChart(); ~GRaphChart(); class="type">void SetOwnProperty(); class="kw">virtual class="type">void Save(); class="kw">virtual class="type">void Load(); }; class=class="str">"cmt">//---------------------------------------------------- GRaphChart 构造函数-- GRaphChart::GRaphChart() { m_chart_id=ChartID(); Save(); class=class="str">"cmt">// 保存原有图表设置 SetOwnProperty(); class=class="str">"cmt">// 设置自己的图表设置 } class=class="str">"cmt">//----------------------------------------------------- GRaphChart 析构函数-- GRaphChart::~GRaphChart() { Load(); class=class="str">"cmt">// 恢复之前的图表设置 m_chart_id=-class="num">1; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test.mq5 | class=class="str">"cmt">//| Copyright class="num">2010, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "class="num">2010, MetaQuotes Software Corp." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_chart_window class="macro">#include "SpecAnalyzer.mqh" class=class="str">"cmt">// <----- 包含头文件 GRaphChart MainChart; class=class="str">"cmt">// <----- 创建 GRaphChart 类对象 class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 指标骨架的两个必填入口
在 MT5 里写自定义指标,编译器只认两个硬性入口:OnInit 负责初始化,OnCalculate 负责每根 K 线推送时算值。上面这段就是最薄的一层壳,没挂任何缓冲区,但结构对了就能编译通过。 OnInit 里目前只写了 return(0),意思是初始化无报错即退出;真正要映射指标线到缓冲区时,得在这里调 SetIndexBuffer,否则图表上画不出东西。 OnCalculate 的参数列表是固定的:rates_total 是当前总柱数,prev_calculated 是上一次已算过的柱数,后面 time/open/high/low/close 等数组按引用传入。示例里直接 return(rates_total),等于告诉终端「全部重算」,小周期跑起来 CPU 占用会偏高,实盘外汇或贵金属高频刷新时可能拖慢反应。 别把正态当圣经:这段返回 rates_total 只是占位,真做价格行为指标时应利用 prev_calculated 做增量计算,否则 1 分钟图加载上百根后容易卡顿。
class=class="str">"cmt">//| 自定义指标初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 指标缓冲区映射 class=class="str">"cmt">//--- class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标迭代函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime& time[], const class="type">class="kw">double& open[], const class="type">class="kw">double& high[], const class="type">class="kw">double& low[], const class="type">class="kw">double& close[], const class="type">long& tick_volume[], const class="type">long& volume[], const class="type">int& spread[]) { class=class="str">"cmt">//--- class=class="str">"cmt">//--- 返回 prev_calculated 值用于下次调用 class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+
面板对象怎么跟着价格跳动重算坐标
SpecAnalyzer 里的控制面板不是画死在图上的,它由 AllGrObject 类统一捏合创建与交互逻辑。所有图形对象的坐标都以图表右下角为原点按像素偏移,但趋势线、箭头线、矩形这类需要时间和价格坐标的对象,必须在赋值前于 MoveGrObject() 里做像素到价量的换算:水平 1 像素对应 1 根柱,垂直 1 像素对应 1 个点。 对象只在 OnInit() 调用 gr_object_create() 时建一次,普通标签按钮坐标当场设好,那三类线形对象的坐标要等 OnCalculate() 里跑 gr_object_coordinate()。因为 OnCalculate 每次 tick 都触发,新柱一出来坐标就重算一遍,面板不会漂。 按钮分三组:左边四个切频谱显示模式(振幅/线、振幅/dB、力量/线、力量/dB),中间四个选数据源(外部 SAInpData 或三种内置测试序列),最后两个滚文本列表。点击处理全在 OnChartEvent(),同组按钮互锁——按下一个其余弹起,靠 YPowerFlag 和 YdBFlag 记状态。 想自己验证,把 Test.mq5 里挂上 gr_object_create() 和 gr_object_coordinate(),OnInit 建对象、OnCalculate 里补 MainChart.SetOwnProperty() 与坐标恢复,编译加载就能看到面板。若开图表刻度显示,可直观确认面板相对放置的像素映射关系。
class AllGrObject : class="kw">public CChart { class="kw">protected: class="type">long m_chart_id; class=class="str">"cmt">// 图表ID class="kw">public: AllGrObject(); ~AllGrObject(); class="type">void AddLabel(class="type">class="kw">string name,class="type">int fsize,class="type">class="kw">string font, class="type">color col,class="type">int x,class="type">int y,class="type">class="kw">string text=""); class="type">void AddButton(class="type">class="kw">string name,class="type">int xsize,class="type">int ysize,class="type">color bgcol, class="type">int fsize,class="type">class="kw">string font,class="type">color col,class="type">int x,class="type">int y, class="type">class="kw">string text="",class="type">int state=class="num">0); class="type">void AddEdit(class="type">class="kw">string name,class="type">int xsize,class="type">int ysize,class="type">color bgcol,class="type">int fsize, class="type">class="kw">string font,class="type">color col,class="type">int x,class="type">int y,class="type">class="kw">string text=""); class="type">void AddTrendLine(class="type">class="kw">string name,class="type">color col,class="type">int style=class="num">0,class="type">int width=class="num">1); class="type">void AddArrowline(class="type">class="kw">string name,class="type">color col,class="type">int style=class="num">0,class="type">int width=class="num">1); class="type">void AddRectangle(class="type">class="kw">string name,class="type">color col,class="type">int style=class="num">0,class="type">int width=class="num">1); class="type">void MoveGrObject(class="type">class="kw">string name,class="type">int x1,class="type">int y1,class="type">int x2,class="type">int y2);