在MQL5中置换价格柱·综合运用
📘

在MQL5中置换价格柱·综合运用

第 3/3 篇

◍ 用置换数据给 EURUSD 造一批平行行情

做价格行为的统计检验,常需要判断一段走势是不是纯随机噪声。MQL5 里可以基于真实品种的历史,打乱其 bar 或 tick 顺序,生成多组「置换行情」作为零假设参照。 下面这段脚本以 EURUSD 为基准,在 2022.01.01 至 2023.01.01 区间生成 100 组置换数据,每组名称会追加自定义后缀 _p。跑完后在日志打出实际完成组数与耗时(分钟),方便你估算更大样本的成本。 核心入口是 CPermuteSymbolData 类:先 Initiate 绑定基准符号与时间窗,再调用 Generate 指定置换次数。外汇与贵金属属高风险品种,置换结果只用于概率对照,不构成方向判断。 别把正态当圣经:100 组在分钟级 K 线可能几十秒跑完,但 tick 级置换会显著拖慢,先小样本探耗时再放大。

MQL5 / C++
class=class="str">"cmt">//|                                                                 PrepareSymbolsForPermutationTests.mq5 |
class=class="str">"cmt">//|                     Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                       https://www.MQL5.com |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "https:class=class="str">"cmt">//www.MQL5.com"
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#include<PermutedSymbolData.mqh>
class="macro">#class="kw">property script_show_inputs
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string   BaseSymbol="EURUSD";
input ENUM_RATES_TICKS PermuteRatesOrTicks=ENUM_USE_RATES;
input class="type">class="kw">datetime StartDate=D&class="macro">#x27;class="num">2022.01.class="num">01 class="num">00:class="num">00&class="macro">#x27;;
input class="type">class="kw">datetime EndDate=D&class="macro">#x27;class="num">2023.01.class="num">01 class="num">00:class="num">00&class="macro">#x27;;
input class="type">uint     Permutations=class="num">100;
input class="type">class="kw">string   CustomID="_p";class=class="str">"cmt">//SymID to be added to symbol permutation names
class=class="str">"cmt">//---
CPermuteSymbolData *symdata;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
   class="type">class="kw">ulong startime = GetTickCount64();
   class="type">uint permutations_completed=class="num">0; class=class="str">"cmt">// number of successfully added permuted data
class=class="str">"cmt">//---intialize the permuted symbol object
   symdata = new CPermuteSymbolData(PermuteRatesOrTicks);
class=class="str">"cmt">//---set the properties of the permuted symbol object
   if(symdata.Initiate(BaseSymbol,CustomID,StartDate,EndDate))
      permutations_completed = symdata.Generate(Permutations);   class=class="str">"cmt">// do the permutations
class=class="str">"cmt">//---print number of symbols whose bar or tick data has been replenished.
   Print("Number of permuted symbols is ", permutations_completed, ", Runtime ",NormalizeDouble(class="type">class="kw">double(GetTickCount64()-startime)/class="type">class="kw">double(class="num">60000),class="num">1),"mins");
class=class="str">"cmt">//---clean up
   class="kw">delete symdata;
   }

「用置换测试拆穿美化净值的EA」

代码库里有一类EA专门用来生成好看的净值曲线,本质是用未来函数或过度拟合制造诱导信号。无良卖家常把这类回测截图当稳定盈利证据,买家实盘一跑就崩。 置换测试的思路是打乱价格序列的时间顺序,再跑同一套EA逻辑。若原曲线显著优于随机置换后的结果,才说明策略可能真有边缘;若两者差不多,那条漂亮曲线大概率是拟合出来的。 外汇和贵金属品种波动受事件驱动,这类欺骗性EA在实盘高杠杆下风险极高,用置换测试先过滤一遍能省掉真金白银的学费。

置换检验到底在耗什么

置换测试不是点一下回测就出结果的便宜活。它要把原始行情序列打乱重排,至少造出 100 个置换数据集,再逐个跑优化和验证,时间和算力开销都不小。 做法上先把选好的样本拆成样本内和样本外两段。EA 只在样本内做参数优化,拿到最优参数后丢到样本外去跑,记录真实泛化表现。 原始数据序列本身也要走一遍同样流程,和那 100+ 个置换结果放一起比。如果 EA 在原始数据上的样本外成绩明显好于绝大多数置换集,策略才倾向有真实边缘;否则大概率只是拟合噪声。外汇和贵金属市场高波动、高杠杆,这类验证能帮你避开把运气当能力的坑。

◍ 用置换检验拆穿测试圣杯EA

文档里被吹成“测试圣杯”的 grr-al EA,在 MT5 策略测试器用 1 分钟 OHLC 或仅开盘价模式跑,净值曲线确实漂亮。我们改了代码,把 SL(止损)和 DELTA 两个参数暴露出来做优化,用 2022 全年 EURUSD 小时图:前六个月样本内优化,后六个月做样本外验证。 先用 PrepareSymbolsForPermutationsTests 生成置换数据的自定义品种。第一次因硬盘空间不足只生成 99 个(报错 5310),重跑后 M1 周期 100 个品种耗时 1.4 分钟,MN1 周期 99 个耗时 48.9 分钟;全年分时数据置换 100 次接近 40G,用报价模式则快得多也省空间。 样本内对每个品种优化,样本外取绝对利润最大的参数组合,全用开盘价分时模式跑——这等于给 EA 最舒服的舞台。结果 CSV 里 EURUSD 原始集 IS 利润 31995.60、OOS 利润 32347.20,大量 p_ 后缀置换品种利润同量级。 算出来 p 值 0.82178,意思是原始数据集上看到的高性能,超过 80% 概率只是运气。外汇和贵金属交易高风险,这类“圣杯”EA 大概率是过拟合噪音,开 MT5 自己跑一遍置换检验最直观。

MQL5 / C++
class="macro">#define MAGIC_NUMBER class="num">12937
class="macro">#define DEV class="num">20
class="macro">#define RISK class="num">0.0
class="macro">#define BASELOT class="num">0.1
input class="type">class="kw">double DELTA =class="num">30;
input class="type">class="kw">double SL =class="num">700;
input class="type">class="kw">double TP =class="num">100;
PR      class="num">0    class="num">11:class="num">53:class="num">04.548    PrepareSymbolsForPermutationTests(EURUSD,MN1)   CNewSymbol::TicksReplace: failed to replace ticks! Error code: class="num">5310
EL      class="num">0    class="num">11:class="num">53:class="num">04.702    PrepareSymbolsForPermutationTests(EURUSD,MN1)   Number of permuted symbols is class="num">99, Runtime class="num">48.9 mins
NK      class="num">0    class="num">12:class="num">51:class="num">23.166    PrepareSymbolsForPermutationTests(EURUSD,M1)    Number of permuted symbols is class="num">100, Runtime class="num">1.4 mins
<SYMBOL> <OPTIMAL DELTA> <OPTIMAL SL> <IS PROFIT> <OOS PROFIT>
EURUSD class="num">3.00 class="num">250.00 class="num">31995.60 class="num">32347.20
EURUSD_p_1 class="num">3.00 class="num">50.00 class="num">29283.40 class="num">34168.20
EURUSD_p_2 class="num">5.00 class="num">50.00 class="num">32283.50 class="num">21047.60
EURUSD_p_3 class="num">3.00 class="num">20.00 class="num">33696.20 class="num">34915.30
EURUSD_p_4 class="num">3.00 class="num">20.00 class="num">32589.30 class="num">38693.20
EURUSD_p_5 class="num">3.00 class="num">230.00 class="num">33771.10 class="num">40458.20
EURUSD_p_6 class="num">3.00 class="num">40.00 class="num">30899.10 class="num">34061.50
EURUSD_p_7 class="num">3.00 class="num">250.00 class="num">34309.10 class="num">31861.20
EURUSD_p_8 class="num">3.00 class="num">40.00 class="num">33729.00 class="num">35359.90
EURUSD_p_9 class="num">3.00 class="num">300.00 class="num">36027.90 class="num">38174.50
EURUSD_p_10 class="num">3.00 class="num">30.00 class="num">33405.90 class="num">35693.70
EURUSD_p_11 class="num">3.00 class="num">30.00 class="num">32723.30 class="num">36453.00
EURUSD_p_12 class="num">11.00 class="num">300.00 class="num">34191.20 class="num">34277.80
EURUSD_p_13 class="num">3.00 class="num">130.00 class="num">35029.70 class="num">33930.00
EURUSD_p_14 class="num">11.00 class="num">290.00 class="num">33924.40 class="num">34851.70
EURUSD_p_15 class="num">3.00 class="num">140.00 class="num">33920.50 class="num">32263.20
EURUSD_p_16 class="num">3.00 class="num">20.00 class="num">34388.00 class="num">33694.40
EURUSD_p_17 class="num">3.00 class="num">60.00 class="num">35081.70 class="num">35612.20
EURUSD_p_18 class="num">5.00 class="num">70.00 class="num">36830.00 class="num">40442.30
EURUSD_p_19 class="num">3.00 class="num">170.00 class="num">37693.70 class="num">37404.90
EURUSD_p_20 class="num">3.00 class="num">50.00 class="num">31265.30 class="num">34875.10
EURUSD_p_21 class="num">3.00 class="num">20.00 class="num">30248.10 class="num">38426.00
EURUSD_p_22 class="num">5.00 class="num">250.00 class="num">32369.80 class="num">37263.80
EURUSD_p_23 class="num">7.00 class="num">50.00 class="num">31197.50 class="num">35466.40
EURUSD_p_24 class="num">7.00 class="num">30.00 class="num">26252.20 class="num">34963.10
EURUSD_p_25 class="num">3.00 class="num">20.00 class="num">31343.90 class="num">37156.00
EURUSD_p_26 class="num">25.00 class="num">280.00 class="num">29762.10 class="num">27336.10
EURUSD_p_27 class="num">3.00 class="num">60.00 class="num">33775.10 class="num">37034.60
EURUSD_p_28 class="num">3.00 class="num">260.00 class="num">35341.70 class="num">36744.20
EURUSD_p_29 class="num">5.00 class="num">50.00 class="num">31775.80 class="num">34673.60
EURUSD_p_30 class="num">3.00 class="num">20.00 class="num">32520.30 class="num">37907.10
EURUSD_p_31 class="num">3.00 class="num">230.00 class="num">35481.40 class="num">42938.20
EURUSD_p_32 class="num">3.00 class="num">100.00 class="num">32862.70 class="num">38291.70
EURUSD_p_33 class="num">3.00 class="num">190.00 class="num">36511.70 class="num">26714.30
EURUSD_p_34 class="num">3.00 class="num">290.00 class="num">29809.10 class="num">35312.40
EURUSD_p_35 class="num">3.00 class="num">290.00 class="num">34044.60 class="num">33460.00
EURUSD_p_36 class="num">3.00 class="num">90.00 class="num">32203.10 class="num">35730.90
EURUSD_p_37 class="num">3.00 class="num">180.00 class="num">39506.50 class="num">30947.30
EURUSD_p_38 class="num">3.00 class="num">180.00 class="num">35844.90 class="num">41717.30
EURUSD_p_39 class="num">3.00 class="num">90.00 class="num">30602.30 class="num">35390.10
EURUSD_p_40 class="num">3.00 class="num">250.00 class="num">29592.20 class="num">33025.90
EURUSD_p_41 class="num">3.00 class="num">140.00 class="num">34281.80 class="num">31501.40
EURUSD_p_42 class="num">3.00 class="num">30.00 class="num">34235.70 class="num">39422.40
EURUSD_p_43 class="num">3.00 class="num">170.00 class="num">35580.10 class="num">35994.20
EURUSD_p_44 class="num">3.00 class="num">20.00 class="num">34400.60 class="num">36250.50
EURUSD_p_45 class="num">5.00 class="num">190.00 class="num">35942.70 class="num">31068.30
EURUSD_p_46 class="num">3.00 class="num">20.00 class="num">32560.60 class="num">37114.70
EURUSD_p_47 class="num">3.00 class="num">200.00 class="num">36837.30 class="num">40843.10
EURUSD_p_48 class="num">3.00 class="num">20.00 class="num">29188.30 class="num">33418.10
EURUSD_p_49 class="num">3.00 class="num">40.00 class="num">33985.60 class="num">29720.50
EURUSD_p_50 class="num">3.00 class="num">250.00 class="num">36849.00 class="num">38007.00
EURUSD_p_51 class="num">3.00 class="num">50.00 class="num">33867.90 class="num">39323.30
EURUSD_p_52 class="num">3.00 class="num">120.00 class="num">33066.30 class="num">39852.40
EURUSD_p_53 class="num">3.00 class="num">60.00 class="num">36977.30 class="num">37284.40
EURUSD_p_54 class="num">3.00 class="num">20.00 class="num">29990.30 class="num">35975.70
EURUSD_p_55 class="num">15.00 class="num">70.00 class="num">29872.80 class="num">34179.40
EURUSD_p_56 class="num">3.00 class="num">250.00 class="num">35909.60 class="num">35911.50
EURUSD_p_57 class="num">3.00 class="num">200.00 class="num">37642.70 class="num">34849.80
EURUSD_p_58 class="num">3.00 class="num">290.00 class="num">39164.00 class="num">35440.90
EURUSD_p_59 class="num">3.00 class="num">100.00 class="num">28312.70 class="num">33917.80
EURUSD_p_60 class="num">3.00 class="num">60.00 class="num">28141.60 class="num">38826.00
EURUSD_p_61 class="num">3.00 class="num">50.00 class="num">29670.90 class="num">34973.70
EURUSD_p_62 class="num">3.00 class="num">40.00 class="num">32170.80 class="num">31062.60
EURUSD_p_63 class="num">3.00 class="num">260.00 class="num">28312.80 class="num">29236.50
EURUSD_p_64 class="num">3.00 class="num">20.00 class="num">31632.50 class="num">35458.30
EURUSD_p_65 class="num">3.00 class="num">260.00 class="num">35345.20 class="num">38522.70
EURUSD_p_66 class="num">7.00 class="num">270.00 class="num">31077.60 class="num">34531.10
EURUSD_p_67 class="num">3.00 class="num">90.00 class="num">33893.70 class="num">30969.00
EURUSD_p_68 class="num">3.00 class="num">170.00 class="num">34118.70 class="num">37280.50
EURUSD_p_69 class="num">3.00 class="num">40.00 class="num">33867.50 class="num">35256.20
EURUSD_p_70 class="num">3.00 class="num">180.00 class="num">37710.60 class="num">30337.20
EURUSD_p_71 class="num">5.00 class="num">200.00 class="num">40851.10 class="num">40985.60
EURUSD_p_72 class="num">3.00 class="num">20.00 class="num">29258.40 class="num">31194.70
EURUSD_p_73 class="num">3.00 class="num">20.00 class="num">30956.50 class="num">38021.40
EURUSD_p_74 class="num">3.00 class="num">90.00 class="num">35807.40 class="num">32625.70
EURUSD_p_75 class="num">3.00 class="num">260.00 class="num">32801.10 class="num">36161.70
EURUSD_p_76 class="num">3.00 class="num">260.00 class="num">34825.40 class="num">28957.70
EURUSD_p_77 class="num">3.00 class="num">90.00 class="num">39725.80 class="num">35923.00
EURUSD_p_78 class="num">3.00 class="num">180.00 class="num">37880.80 class="num">37090.90
EURUSD_p_79 class="num">3.00 class="num">180.00 class="num">34191.50 class="num">38190.70

「EURUSD 月线参数扫描的尾部样本与 P 值」

上面这段是 EURUSD 在 MN1 周期下、参数编号 p_80 到 p_100 的优化输出残片。每一行五列分别对应:参数标记、某约束值(如 3.00/5.00/7.00/11.00)、另一阈值(20~300 区间)、两组回测统计值(落在 29260~39986 之间)。 从 p_86 起约束值跳到 5.00,p_88 到 7.00,p_94 到 11.00,但对应回测值并没有单调放大,p_94 的 31548.40 / 32684.70 反而低于多个 3.00 行,说明单纯加约束不保证结果更优。 日志末行给出 ProcessOptFiles 在 09:49:57.991 的收口:P 值 = 0.8217821782178217。该值偏高,意味着当前参数集在样本内拟合优势并不显著,实盘直接套用外汇月线策略属高风险,建议先缩小样本外验证。

MQL5 / C++
EURUSD_p_80 class="num">3.00 class="num">40.00 class="num">29235.30 class="num">33207.70
EURUSD_p_81 class="num">3.00 class="num">20.00 class="num">29923.50 class="num">34291.00
EURUSD_p_82 class="num">3.00 class="num">90.00 class="num">35077.80 class="num">37203.40
EURUSD_p_83 class="num">3.00 class="num">40.00 class="num">32901.50 class="num">32182.40
EURUSD_p_84 class="num">3.00 class="num">50.00 class="num">31302.60 class="num">34339.00
EURUSD_p_85 class="num">3.00 class="num">60.00 class="num">30336.90 class="num">37948.10
EURUSD_p_86 class="num">5.00 class="num">50.00 class="num">35166.10 class="num">37898.60
EURUSD_p_87 class="num">5.00 class="num">290.00 class="num">33005.20 class="num">32648.30
EURUSD_p_88 class="num">7.00 class="num">140.00 class="num">34349.70 class="num">31435.50
EURUSD_p_89 class="num">3.00 class="num">20.00 class="num">30680.20 class="num">37002.30
EURUSD_p_90 class="num">3.00 class="num">100.00 class="num">35382.50 class="num">37643.80
EURUSD_p_91 class="num">3.00 class="num">50.00 class="num">35187.20 class="num">36392.00
EURUSD_p_92 class="num">3.00 class="num">120.00 class="num">32423.10 class="num">35943.20
EURUSD_p_93 class="num">3.00 class="num">100.00 class="num">31722.70 class="num">39913.30
EURUSD_p_94 class="num">11.00 class="num">300.00 class="num">31548.40 class="num">32684.70
EURUSD_p_95 class="num">3.00 class="num">100.00 class="num">30094.00 class="num">38929.70
EURUSD_p_96 class="num">3.00 class="num">170.00 class="num">35400.30 class="num">29260.30
EURUSD_p_97 class="num">3.00 class="num">300.00 class="num">35696.50 class="num">35772.20
EURUSD_p_98 class="num">3.00 class="num">20.00 class="num">31336.20 class="num">35935.70
EURUSD_p_99 class="num">3.00 class="num">20.00 class="num">32466.30 class="num">39986.40
EURUSD_p_100 class="num">3.00 class="num">20.00 class="num">32082.40 class="num">33625.10
MO          class="num">0      class="num">09:class="num">49:class="num">57.991     ProcessOptFiles(EURUSD,MN1)   P值为 class="num">0.8217821782178217

置换数据为何能戳穿假策略

把历史价格顺序打乱再做回测,本质是把 EA 赖以盈利的时空结构拆掉。若策略真在抓某种价格行为模式,置换后那套规则就失效,净值曲线会明显塌下来;若置换前后曲线几乎长一个样,反而说明它根本没在交易模式,只是在蹭分时生成的随机性。 演示里那个被点名的 EA,置换测试一跑就露馅:未置换样本里漂亮,置换后照样不差,证明它没用任何真实规则。外汇与贵金属市场高杠杆、跳空频繁,这种假优势在实盘大概率迅速归零。 置换还能当过拟合的尺子。取样本内数据,分别跑未置换与置换版本,两者绩效差得越大,过拟合越轻;要是差不离,p 值飙到很高,就说明优化只是把噪声记熟了。开 MT5 用框架自带的 random shuffle 对收盘价序列重排,对比原 EA 报告里的 profit factor,就能自己读数。

◍ 一点提醒

置换测试的本质,是把真实行情的时序打乱后重跑 EA,看原本的盈利是否只是随机巧合。上文给出的 grr-al.mq5 配合 NewSymbol.mqh、PermuteRates.mqh 等六个 include 文件,能在 MT5 里生成自定义交易品种并做完整置换回测。 若你在优化后做置换,重点对比原序列与置换序列的净值曲线差异:差异越小,过拟合概率越高。社区讨论里有人提到用 p 值量化,但需注意随机数据上表现好不等于模型稳健,外汇与贵金属市场高杠杆、高波动,任何测试结果都只是概率倾向。 把这些 mqh 丢进 MQL5\Include,跑一次 PrepareSymbolsForPermutationTests 脚本,你就能自己判断那个‘漂亮曲线’到底靠不靠谱。

常见问题

把真实 EURUSD 的涨跌顺序随机打乱生成多组置换序列,跑同一套规则看收益分布,若原序列不在随机区间内就说明可能真有边缘。
用置换数据重跑该 EA,若随机行情也能跑出相近净值,原表现大概率是碰巧;只有真实数据显著优于置换群才值得信。
小布可自动对选中品种生成置换行情并批量回测,直接标出原策略相对随机群的胜率与 P 值,省去手写代码。
尾部样本下置换 P 值偏差大,结论只能作倾向参考;建议拉长周期或换高频率数据再交叉验证。
主要耗在重复生成随机序列并多次回测,行情越长、置换次数越多越吃计算资源,单品种千次置换常需数分钟。