建立一个频谱分析程序(基础篇)
📘

建立一个频谱分析程序(基础篇)

第 1/3 篇

「在 MT5 里搭一个频谱分析程序」

想在 MT5 里做价格序列的频域分析,最直接的方式是写一段独立脚本,把收盘价数组送进离散傅里叶变换(DFT),观察能量在不同周期上的分布。这套思路不依赖任何第三方插件,纯 MQL5 标准库即可跑通。 下面这段示例脚本在 2013 年 12 月社区示例中累计被查看 2609 次、评论 6 条,说明手动做谱分析的需求一直存在。它先把历史收盘价读入数组,再做复数变换,输出各频率分量的模长。 实际使用时,把 COPY_RATES_CLOSE 的计数调大,比如取 512 根 K 线,频率分辨率会明显更细;但贵金属与外汇波动具有高风险,频谱峰值只代表历史周期倾向,不预示未来必然重复。

MQL5 / C++
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,原外观就回来。外汇和贵金属图表叠加这类自绘面板时波动风险高,别在实盘图表上直接试。

MQL5 / C++
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 五分钟图,退出时看配色是否复原。

MQL5 / C++
  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 分钟图加载上百根后容易卡顿。

MQL5 / C++
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() 与坐标恢复,编译加载就能看到面板。若开图表刻度显示,可直观确认面板相对放置的像素映射关系。

MQL5 / C++
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);

常见问题

检查用的是像素锚点还是价格锚点:价格锚点会让对象随K线和报价重算坐标,面板跟着价格跳动就用价格锚点绑定。
用类接管图表属性的保存与还原,把参数序列化到文件或全局变量,启动时读取还原即可。
小布可基于你的品种页直接生成带价格锚点的面板对象并自动重算坐标,你只需调分析周期和阈值。
必须实现 OnInit 和 OnCalculate 两个必填入口,前者初始化后者逐根处理报价,缺一个都无法加载。
把主程序、图表属性类、面板对象三个文件摆在同一目录并正确引用,否则编译报找不到依赖。