在MQL5中置换价格柱·综合运用
◍ 用置换数据给 EURUSD 造一批平行行情
做价格行为的统计检验,常需要判断一段走势是不是纯随机噪声。MQL5 里可以基于真实品种的历史,打乱其 bar 或 tick 顺序,生成多组「置换行情」作为零假设参照。
下面这段脚本以 EURUSD 为基准,在 2022.01.01 至 2023.01.01 区间生成 100 组置换数据,每组名称会追加自定义后缀 _p。跑完后在日志打出实际完成组数与耗时(分钟),方便你估算更大样本的成本。
核心入口是 CPermuteSymbolData 类:先 Initiate 绑定基准符号与时间窗,再调用 Generate 指定置换次数。外汇与贵金属属高风险品种,置换结果只用于概率对照,不构成方向判断。
别把正态当圣经:100 组在分钟级 K 线可能几十秒跑完,但 tick 级置换会显著拖慢,先小样本探耗时再放大。
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 自己跑一遍置换检验最直观。
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。该值偏高,意味着当前参数集在样本内拟合优势并不显著,实盘直接套用外汇月线策略属高风险,建议先缩小样本外验证。
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 脚本,你就能自己判断那个‘漂亮曲线’到底靠不靠谱。