在图表上快速检验交易理念(基础篇)
📘

在图表上快速检验交易理念(基础篇)

第 1/3 篇

「用 MT5 快速验证你的交易念头」

很多交易者脑子里闪过一种价格行为假设,却卡在「怎么落地验证」。其实 MT5 自带的回测与脚本环境,能在几分钟内把模糊念头变成可观测的图表信号。 以 2014 年 3 月一篇社区热帖为例,该文发布后获得 2 726 次查看、20 条互动,说明「轻量验证」是实盘前的高频刚需。你不必先写完整 EA,用一段指标脚本就能在 EURUSD 的 M15 上跑出信号分布。 高风险提示:外汇与贵金属杠杆品种波动剧烈,任何历史验证只代表过去概率,实盘可能显著偏离。建议先在策略测试器用 2020—2023 年数据跑一遍,再决定要不要手搓参数。

从 451 个参赛 EA 看策略偏好

第六届自动交易锦标赛落幕后,我花时间扒了参赛 EA 的说明和注释,想摸清当下机器人设计的主流思路。赛程里一共收到 451 个 EA,但只有 316 个写了有意义的描述,剩下 135 个基本是给亲友、外星人或者自己的问候语,技术信息为零。 把有说明的 EA 按策略归类,图形结构交易(关键价位、支撑阻力、通道)占了 55 个,价格变动分析 33 个,趋势追踪 31 个,统计价格模式 10 个。套利和相关性分析、波动分析各 8 个,神经网络 7 个,烛形和均线各 5 个。指标类策略依旧是绝对主流。 具体指标使用频次也能数出来:移动平均线 75 次,MACD 54 次,随机摆动 25 次,RSI 23 次,布林带 19 次。分形 8 次,CCI 和 ATR 各 7 次,抛物线和峰谷各 6 次。这说明大部分参赛者仍在做跟随型和指标型系统,创新浓度偏低。 别把流行当合理。很多新手被灌输的是“规则”而非“市场知识”——给个 MACD 用法、给个信号定义,然后优化参数等赚钱。可这些指标本就是普通交易者发明的,几十年后你的自定义指标也可能变标准。外汇与贵金属自动化交易杠杆高、回撤快,跟策略前先想清楚逻辑源头。 我后面会聊自己怎么找交易理念,以及怎么在 MT5 上快速验证它们靠不靠谱。

◍ 从价格导数到可验证信号

价格反映一切这句话在静态图表上成立,但交易者真正要的是未来一段窗口里价格会映射出什么。把物理学里的思路搬过来:价格的一阶导数是速率,指标算的就是当前价格变化速率。大级别量能惯性大,没有新闻或央行政策这类外力砸进来,速率不会瞬间翻转,这正好对应趋势——外力缺席时一阶导数守住值的那种价格状态。 想挖新逻辑,得盯着同一根 K 线周期内价格和它的派生量一起看。纯跟信号跑永远在低维,把图表和指标拆开肉眼比对,才可能撞见开发者没预设过的价格-指标耦合。这种能力不会让账户明天就起飞,但能回答「为什么亏/为什么赚」的问题,迟早改变你的执行质量。 撞见一个看起来能用的相关性,别停在直觉。最省事的做法是写个 EA 丢历史里跑,证伪比证实更重要。如果回测亏了还讲不清为什么,这想法就没法迭代。 一条可落地的验证路径:改或写个指标,让它吐出离散信号(-1 卖、1 买);再挂一个余额指标到同图,把每次吃信号后的余额和净值(点数)变动画出来;然后翻不同行情段,看假设在哪种市况下站得住。 这套做法有三个实在点:余额指标全在 OnCalculate 里算,吃满历史数组的预载速度;往现有指标塞信号,是向导生成 EA 和手搓 EA 之间的过渡训练;想法和最终结果同一屏看完。局限也明摆着——信号绑死收盘价、余额按固定手数算、不支持挂单,但都能后续改掉。外汇和贵金属杠杆高,任何信号在历史里跑顺都不代表实盘能复制,先开 MT5 用策略测试器证伪再说。

「把锤线和射击之星写成可回测的信号类」

想验证反转烛形到底有没有用,最直接的方法是写个指标把信号画出来。这里选了“锤线”作买入、“射击之星”作卖出,两者都要求主体不超过总高度的 50%、影线不超过 0%、高度不小于前 5 根平均高度的 100%,且收盘价要穿越 10 周期均线——这些就是后面代码里的可调参数。 锤线的极值条件反过来就是射击之星:当前最低要低于前 5 根最低,射击之星则要求最高高于前 5 根最高。退出不另设复杂逻辑,价格回碰 10 周期均线即平,没有仓位时信号直接忽略。 代码把计算塞进 CPivotCandlesClass,可视化留给指标主体,这样同一套类以后改改能套别的形态。返回值约定很干脆:-1 卖开、1 买开、2 卖平、−2 买平、0 无信号,方便接 EA 或虚拟持仓统计。 下面对核心输入参数做逐行拆解,开 MT5 把这几个数改一改,就能看到信号密度和噪音怎么变。外汇和贵金属波动大,这类反转信号假突破概率不低,只适合当辅助过滤。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                 PivotCandlesClass.mqh |
class=class="str">"cmt">//|            Copyright class="num">2012, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2012, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Input parameters                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">input class="type">int      iMaxBodySize           = class="num">50;   class=class="str">"cmt">// Maximum candle body, %
class="kw">input class="type">int      iMaxShadowSize         = class="num">0;    class=class="str">"cmt">// Maximum allowed candle shadow, %
class="kw">input class="type">int      iVolatilityCandlesCount = class="num">5;   class=class="str">"cmt">// Number of previous bars for calculation of an average volatility
class="kw">input class="type">int      iPrevCandlesCount      = class="num">5;    class=class="str">"cmt">// Number of previous bars, for which the current bar should be an extremum
class="kw">input class="type">int      iVolatilityPercent     = class="num">100; class=class="str">"cmt">// Correlation of a signal candle with a previous volatility, %
class="kw">input class="type">int      iMAPeriod              = class="num">10;   class=class="str">"cmt">// Period of a simple signal moving average
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class definition                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CPivotCandlesClass
  {
class="kw">private:

枢轴蜡烛类的内部状态与接口

在 MT5 里写一个识别枢轴蜡烛的类的核心,是先固定好它要记住什么。下面这段声明把历史 K 线、均线值和形态标记都摊开了:m_candles 存计算用的历史,m_history_depth 决定存多少根,m_handled_candles_count 跟踪已处理数量。 均线相关只用两个 double:m_ma_value 是当前值,m_prev_ma_value 是上一根的值,两者差值能直接判断均线转向。m_is_highest / m_is_lowest 是布尔开关,标记当前蜡烛是否在区间内最高或最低,m_volatility 记平均波动,m_candle_pattern 存识别到的形态编号。 对外接口里,AnalizeNewCandle 有两个重载:一个吃 MqlRates 结构,另一个直接吃 time/open/high/low/close 五个参数。MAValue() 用内联返回 m_ma_value,调用零开销。 把这套声明原样丢进 MetaEditor 编译,能验证类骨架不报错;之后接 DoAnalizeNewCandle 的逻辑,外汇与贵金属波动大、杠杆高,实盘前务必在策略测试器跑历史数据。

MQL5 / C++
  class="type">MqlRates            m_candles[];                                 class=class="str">"cmt">// Array for storing the history necessary for calculations
  class="type">int                 m_history_depth;                              class=class="str">"cmt">// Array length for storing the history
  class="type">int                 m_handled_candles_count;                     class=class="str">"cmt">// Number of the already processed candles

  class="type">class="kw">double              m_ma_value;                                  class=class="str">"cmt">// Current calculated moving average value
  class="type">class="kw">double              m_prev_ma_value;                             class=class="str">"cmt">// Previous calculated moving average value
  class="type">bool                m_is_highest;                                class=class="str">"cmt">// Check if the current candle is the highest one
  class="type">bool                m_is_lowest;                                 class=class="str">"cmt">// Check if the current candle is the lowest one
  class="type">class="kw">double              m_volatility;                                class=class="str">"cmt">// Average volatility
  class="type">int                 m_candle_pattern;                            class=class="str">"cmt">// Current recognized pattern

  class="type">void                PrepareArrayForNewCandle();                  class=class="str">"cmt">// Prepare the array for accepting the new candle
  class="type">int                 CheckCandleSize(class="type">MqlRates &candle);           class=class="str">"cmt">// Check the candle for conformity with patterns
  class="type">void                PrepareCalculation();
class="kw">protected:
  class="type">int                 DoAnalizeNewCandle();                        class=class="str">"cmt">// Calculation function
class="kw">public:
  class="type">void                CPivotCandlesClass();

  class="type">void                CleanupHistory();                            class=class="str">"cmt">// Clean up all calculation variables  
  class="type">class="kw">double              MAValue() {class="kw">return m_ma_value;}               class=class="str">"cmt">// Current value of the moving average
  class="type">int                 AnalizeNewCandle(class="type">MqlRates& candle);
  class="type">int                 AnalizeNewCandle( class="kw">const class="type">class="kw">datetime time,
                                        class="kw">const class="type">class="kw">double open,
                                        class="kw">const class="type">class="kw">double high,
                                        class="kw">const class="type">class="kw">double low,
                                        class="kw">const class="type">class="kw">double close,

◍ 把枢轴蜡烛类的初始化和刷新跑通

上面这段是枢轴蜡烛类(CPivotCandlesClass)的构造函数与两个核心维护方法,直接决定了后续形态识别能不能拿到干净的上下文。构造函数里先用 MathMax 三层嵌套,把波动率蜡烛数、前置蜡烛数、MA 周期三者取最大再加 1,作为 m_history_depth,保证历史缓冲够算所有指标。 初始化时 m_handled_candles_count、m_prev_ma_value、m_ma_value 全部归零,再用 ArrayResize 按深度开好 m_candles 数组。CleanupHistory 则在需要重算时释放数组并重新按原深度扩容,三个状态变量同样清零,避免旧值污染新一根 K 线的判断。 AnalizeNewCandle 的入参已经露头:datetime time 加 open/high 等单根属性,说明类内部是基于逐根推送做分析的。你在 MT5 里挂这个类,重点盯 m_history_depth 是否覆盖你设的 iMAPeriod——若 MA 周期设 50 但波动率蜡烛数只给 20,深度会落到 51,少一根都可能让首根枢轴信号延迟。外汇与贵金属杠杆高,这类重算逻辑若在历史不足时强跑,可能吐出假枢轴,先开策略测试器用 EURUSD 1H 验证再上实盘。

MQL5 / C++
class="kw">const class="type">long tick_volume,
              class="kw">const class="type">long volume,
              class="kw">const class="type">int spread );
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CPivotCandlesClass                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class initialization                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+ 
class="type">void CPivotCandlesClass::CPivotCandlesClass()
  {
   class=class="str">"cmt">// History depth should be enough for all calculations
   m_history_depth = (class="type">int)MathMax(MathMax(
       iVolatilityCandlesCount + class="num">1, iPrevCandlesCount + class="num">1), iMAPeriod);
   m_handled_candles_count = class="num">0;
   m_prev_ma_value = class="num">0;
   m_ma_value = class="num">0;

   ArrayResize(m_candles, m_history_depth);
  } 
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CleanupHistory                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Clean up the candle buffer for recalculation                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CPivotCandlesClass::CleanupHistory()
  {
   class=class="str">"cmt">// Clean up the array
   ArrayFree(m_candles);
   ArrayResize(m_candles, m_history_depth);

   class=class="str">"cmt">// Null calculation variables
   m_handled_candles_count = class="num">0;
   m_prev_ma_value = class="num">0;
   m_ma_value = class="num">0; 
  }
class=class="str">"cmt">//+-------------------------------------------------------------------+
class=class="str">"cmt">//| AnalizeNewCandle                                                  |
class=class="str">"cmt">//+-------------------------------------------------------------------+
class=class="str">"cmt">//| Preparations for analyzing the new candle and the analysis itself |
class=class="str">"cmt">//| based on candle&class="macro">#x27;s separate parameter values                       |
class=class="str">"cmt">//+-------------------------------------------------------------------+
class="type">int CPivotCandlesClass::AnalizeNewCandle( class="kw">const class="type">class="kw">datetime time,
                            class="kw">const class="type">class="kw">double open,
                            class="kw">const class="type">class="kw">double high,

常见问题

先把念头写成可回测的信号条件,用平台自带回测跑一遍历史数据,看胜率和回撤再决定要不要实盘。
统计显示多数偏好趋势跟随与突破类,反转类占比较少;可据此判断当前拥挤度,但外汇贵金属高风险,偏好不代表盈利。
小布可把你的口头策略转成信号条件并跑历史检验,直接在品种页给出胜率与回撤参考,省去手写代码。
定义实体占比与影线倍数阈值,封装成返回多空值的类,再用历史K线逐根判定,概率上反转信号需结合过滤。
多因未绑定时间周期或刷新函数漏调;检查构造时传入品种周期,每根新K线调用Refresh即可正常出图。