在图表上快速检验交易理念(基础篇)
「用 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 把这几个数改一改,就能看到信号密度和噪音怎么变。外汇和贵金属波动大,这类反转信号假突破概率不低,只适合当辅助过滤。
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 的逻辑,外汇与贵金属波动大、杠杆高,实盘前务必在策略测试器跑历史数据。
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 验证再上实盘。
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,