MetaTrader 5中的蒙特卡罗置换测试·进阶篇
🎲

MetaTrader 5中的蒙特卡罗置换测试·进阶篇

(2/3)·靠单次回测下结论太冒险?用随机重排价格序列拆穿EA的偶然盈利

含代码示例偏理论 第 2/3 篇
不少交易者把EA在原始历史数据上的回测盈利直接当成能力证明,忽略了那可能只是价格顺序碰巧有利。若不做分布层面的检验,一个亏钱策略也能跑出漂亮曲线。本文用MetaTrader 5演示如何随机重排价格序列来逼出真实p值。

把真实tick喂进置换类的初始化门槛

做 tick 序列随机置换前,得先通过 Initialize() 把原始 MqlTick 数组塞进类里。这里有个硬门槛:传入数组长度必须 ≥5,否则直接返回 false 并打印 Insufficient amount of data supplied,意味着少于 5 个 tick 的样本连差分都做不出来。 初始化里先 ArrayCopy 到本地 m_ticks,再按 m_ticks.Size()-1 重设 m_differenced 尺寸,随后跑 LogTransformTicks() 做对数变换。之后从 i=1 开始循环,把相邻 log tick 的 bid_d、ask_d、vol_d、volreal_d 逐项相减填进 m_differenced,完成一阶差分。 Permute() 调用前必须确认 m_initialized 为 true,否则报 not initialized。输出数组若尺寸不对会自动 ArrayResize 到 m_ticks.Size()。下面这段是 Initialize 与 Permute 骨架,注意 Permute 里用 GetTickCount64() 取种子、while(i>1) 做倒序抽取——典型的 Fisher-Yates 思路,但原文在此处截断。 外汇与贵金属 tick 数据高频易变,实盘回放置换仅用于假设检验,不预示任何方向,杠杆品种风险极高。

MQL5 / C++
  ~CPermuteTicks(class="type">void);
  class="type">bool      Initialize(class="type">MqlTick &in_ticks[]);
  class="type">bool      Permute(class="type">MqlTick &out_ticks[]);
};
class=class="str">"cmt">//+--------------------------------------------------------------------+
class=class="str">"cmt">//|Initialize the permutation process by supplying ticks to be permuted|
class=class="str">"cmt">//+--------------------------------------------------------------------+
class="type">bool CPermuteTicks::Initialize(class="type">MqlTick &in_ticks[])
  {
class=class="str">"cmt">//---set or reset initialization flag
   m_initialized=class="kw">false;
class=class="str">"cmt">//---check arraysize
   if(in_ticks.Size()<class="num">5)
     {
       Print("Insufficient amount of data supplied ");
       class="kw">return class="kw">false;
     }
class=class="str">"cmt">//---copy ticks to local array
   if(ArrayCopy(m_ticks,in_ticks)!=class="type">int(in_ticks.Size()))
     {
       Print("Error copying ticks ", GetLastError());
       class="kw">return class="kw">false;
     }
class=class="str">"cmt">//---ensure the size of m_differenced array
   if(m_differenced.Size()!=m_ticks.Size()-class="num">1)
      ArrayResize(m_differenced,m_ticks.Size()-class="num">1);
class=class="str">"cmt">//---apply log transformation to relevant tick data members
   if(!LogTransformTicks())
     {
       Print("Log transformation failed ", GetLastError());
       class="kw">return class="kw">false;
     }
class=class="str">"cmt">//---fill m_differenced with differenced values, excluding the first tick
   for(class="type">uint i=class="num">1; i<m_logticks.Size(); i++)
     {
       m_differenced[i-class="num">1].bid_d=(m_logticks[i].bid_d)-(m_logticks[i-class="num">1].bid_d);
       m_differenced[i-class="num">1].ask_d=(m_logticks[i].ask_d)-(m_logticks[i-class="num">1].ask_d);
       m_differenced[i-class="num">1].vol_d=(m_logticks[i].vol_d)-(m_logticks[i-class="num">1].vol_d);
       m_differenced[i-class="num">1].volreal_d=(m_logticks[i].volreal_d)-(m_logticks[i-class="num">1].volreal_d);
     }
class=class="str">"cmt">//---set the initilization flag
   m_initialized=true;
class=class="str">"cmt">//---
   class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Public method which applies permutation and gets permuted ticks    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CPermuteTicks::Permute(class="type">MqlTick &out_ticks[])
  {
class=class="str">"cmt">//---zero out tick array
   ZeroMemory(out_ticks);
class=class="str">"cmt">//---ensure required data already supplied through initialization
   if(!m_initialized)
     {
       Print("not initialized");
       class="kw">return class="kw">false;
     }
class=class="str">"cmt">//---resize output array if necessary
   if(out_ticks.Size()!=m_ticks.Size())
      ArrayResize(out_ticks,m_ticks.Size());
class=class="str">"cmt">//---
   class="type">int i,j;
   CMqlTick tempvalue;
   i=(class="type">int)m_ticks.Size()-class="num">1;

   class="type">int error_value;
   class="type">class="kw">double unif_rando;
   class="type">class="kw">ulong time = GetTickCount64();
   class="kw">while(i>class="num">1)
     {

「tick 置换与对数变换的实现细节」

做 tick 数据随机置换时,先用 MathRandomUniform 在 [MIN_THRESHOLD, MAX_THRESHOLD] 区间取均匀随机数,并用 error_value 接收错误码;若 MathIsValidNumber 判定无效立即打印错误并 return(false),避免脏数据进数组。 随机下标 j 由 (int)(unif_rando*i) 得到,若 j>=i 则强制 j=i-1,保证交换不越界。随后把 m_differenced[i] 与 m_differenced[j] 的 bid_d / ask_d / vol_d / volreal_d 四字段互换,完成一次 Fisher-Yates 风格的乱序。 置换完要做差分还原:从 k=1 开始,m_logticks[k] 各字段等于前一项加 m_differenced[k-1] 对应字段,把差分序列累加成原始尺度。第 0 根 tick 直接拷 m_ticks[0] 全部成员(bid/ask/volume/volume_real/flags/last/time/time_msc),最后 return ExpTransformTicks(out_ticks)。 LogTransformTicks 里先按 m_ticks.Size() 重设 m_logticks,再逐 tick 对 bid/ask/volume 做 MathLog;任一值 <=0 时落 MathLog(1e0) 即 0,绕开对数域负无穷。外汇与贵金属 tick 流在高波动时可能出现 0 成交量或异常价,这套保护能让变换稳定跑完,但回测结论仅代表历史样本,实盘仍属高风险。

MQL5 / C++
error_value=class="num">0;
unif_rando=MathRandomUniform(MIN_THRESHOLD,MAX_THRESHOLD,error_value);
if(!MathIsValidNumber(unif_rando))
  {
   Print("Invalid random value ",error_value);
   class="kw">return(class="kw">false);
  }
j=(class="type">int)(unif_rando*i);
if(j>=i)
   j=i-class="num">1;
--i;
class=class="str">"cmt">//---swap tick data randomly
   tempvalue.bid_d=m_differenced[i].bid_d;
   tempvalue.ask_d=m_differenced[i].ask_d;
   tempvalue.vol_d=m_differenced[i].vol_d;
   tempvalue.volreal_d=m_differenced[i].volreal_d;
   m_differenced[i].bid_d=m_differenced[j].bid_d;
   m_differenced[i].ask_d=m_differenced[j].ask_d;
   m_differenced[i].vol_d=m_differenced[j].vol_d;
   m_differenced[i].volreal_d=m_differenced[j].volreal_d;
   m_differenced[j].bid_d=tempvalue.bid_d;
   m_differenced[j].ask_d=tempvalue.ask_d;
   m_differenced[j].vol_d=tempvalue.vol_d;
   m_differenced[j].volreal_d=tempvalue.volreal_d;
   }
class=class="str">"cmt">//---undo differencing 
   for(class="type">uint k = class="num">1; k<m_ticks.Size(); k++)
    {
     m_logticks[k].bid_d=m_logticks[k-class="num">1].bid_d + m_differenced[k-class="num">1].bid_d;
     m_logticks[k].ask_d=m_logticks[k-class="num">1].ask_d + m_differenced[k-class="num">1].ask_d;
     m_logticks[k].vol_d=m_logticks[k-class="num">1].vol_d + m_differenced[k-class="num">1].vol_d;
     m_logticks[k].volreal_d=m_logticks[k-class="num">1].volreal_d + m_differenced[k-class="num">1].volreal_d;
    }
class=class="str">"cmt">//---copy the first tick  
   out_ticks[class="num">0].bid=m_ticks[class="num">0].bid;
   out_ticks[class="num">0].ask=m_ticks[class="num">0].ask;
   out_ticks[class="num">0].volume=m_ticks[class="num">0].volume;
   out_ticks[class="num">0].volume_real=m_ticks[class="num">0].volume_real;
   out_ticks[class="num">0].flags=m_ticks[class="num">0].flags;
   out_ticks[class="num">0].last=m_ticks[class="num">0].last;
   out_ticks[class="num">0].time=m_ticks[class="num">0].time;
   out_ticks[class="num">0].time_msc=m_ticks[class="num">0].time_msc;   
class=class="str">"cmt">//---class="kw">return transformed data
   class="kw">return ExpTransformTicks(out_ticks);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+-------------------------------------------------------------------+
class=class="str">"cmt">//|Helper method applying log transformation                        |
class=class="str">"cmt">//+-------------------------------------------------------------------+
class="type">bool CPermuteTicks::LogTransformTicks(class="type">void)
  {
class=class="str">"cmt">//---resize m_logticks if necessary  
   if(m_logticks.Size()!=m_ticks.Size())
      ArrayResize(m_logticks,m_ticks.Size());
class=class="str">"cmt">//---log transform only relevant data members, avoid negative and zero values
   for(class="type">uint i=class="num">0; i<m_ticks.Size(); i++)
    {
     m_logticks[i].bid_d=(m_ticks[i].bid>class="num">0)?MathLog(m_ticks[i].bid):MathLog(class="num">1e0);
     m_logticks[i].ask_d=(m_ticks[i].ask>class="num">0)?MathLog(m_ticks[i].ask):MathLog(class="num">1e0);
     m_logticks[i].vol_d=(m_ticks[i].volume>class="num">0)?MathLog(m_ticks[i].volume):MathLog(class="num">1e0);

◍ 对数变换与逆变换的落地写法

做 tick 序列重排时,直接对原始成交量和价格跑运算会被极端值带偏。常见做法是先取对数压一下动态范围,输出前再指数还原。 上面这段把 volume_real 做了保护式对数处理:大于 0 才 MathLog,否则落 MathLog(1e0) 也就是 0,避免对 0 取对数报 NaN。重排完成后,ExpTransformTicks 用 MathExp 把 bid_d、ask_d、vol_d、volreal_d 还原回线性空间,并原样拷贝 flags、last、time 等不参与重排的字段。 在 MT5 里实测,若某 tick 的 volume_real 为 0,逆变换后 out_ticks[k].volume_real 会得到 0 而非 1,说明对数端用 1e0 兜底、指数端用三元判断共同保证了边界安全。外汇与贵金属 tick 重排属高频微观操作,滑点和重排假象风险高,结论仅作概率性参考。

MQL5 / C++
   m_logticks[i].volreal_d=(m_ticks[i].volume_real>class="num">0)?MathLog(m_ticks[i].volume_real):MathLog(class="num">1e0);
   }
class=class="str">"cmt">//---
   class="kw">return true;
   }
class=class="str">"cmt">//+-----------------------------------------------------------------------+
class=class="str">"cmt">//|Helper method undoes log transformation before outputting permuted tick|
class=class="str">"cmt">//+-----------------------------------------------------------------------+
class="type">bool CPermuteTicks::ExpTransformTicks(class="type">MqlTick &out_ticks[])
  {
class=class="str">"cmt">//---apply exponential transform to data and copy original tick data member info
class=class="str">"cmt">//---not involved in permutation operations
   for(class="type">uint k = class="num">1;k<m_ticks.Size(); k++)
     {
     out_ticks[k].bid=(m_logticks[k].bid_d)?MathExp(m_logticks[k].bid_d):class="num">0;
     out_ticks[k].ask=(m_logticks[k].ask_d)?MathExp(m_logticks[k].ask_d):class="num">0;
     out_ticks[k].volume=(m_logticks[k].vol_d)?(class="type">class="kw">ulong)MathExp(m_logticks[k].vol_d):class="num">0;
     out_ticks[k].volume_real=(m_logticks[k].volreal_d)?MathExp(m_logticks[k].volreal_d):class="num">0;
     out_ticks[k].flags=m_ticks[k].flags;
     out_ticks[k].last=m_ticks[k].last;
     out_ticks[k].time=m_ticks[k].time;
     out_ticks[k].time_msc=m_ticks[k].time_msc;
     }
class=class="str">"cmt">//---
   class="kw">return true;
   }

用脚本批量造出置换品种

置换测试依赖 MT5 两个能力:建自定义品种、对市场观察里启用的品种跑 EA 优化。把分时打乱再绑到新品种上,就能基于任意一个现有品种派生出一批等价但顺序不同的样本。 手动建品种纯属折磨自己。PrepareSymbolsForPermutationTests 脚本把建品种和塞置换分时全包了:你给基础品种、分时日期区间、要造多少置换、以及名字后缀,它用基础名加枚举自动命名。底层由 GenerateSymbols.mqh 里的 CGenerateSymbols 类干活,它又拽上 NewSymbol.mqh(品种创建)和 PermuteTicks.mqh(分时打乱)。 CGenerateSymbols 就两个要管的成员函数。Initiate() 在对象建完先调,吃 4 个参,跟脚本输入一一对应;Generate() 吃想要的置换数,返回实际加进市场观察的新品种数,结果打印在「专家」标签页。 跑完脚本别闲着,进策略测试器选最后一种优化法、挂上要测的 EA。优化可能跑很久——EURUSD 造 100 个置换、区间 2023.06.01 至 2023.08.01,这种量级够你喝几杯咖啡。测完就有一堆性能数据可挖。外汇与贵金属属高风险,置换结果只反映历史样本扰动下的概率表现,不构成实盘指引。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| PrepareSymbolsForPermutationTests.mq5 |
class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//| [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link "[MQL5官方文档]
class="macro">#class="kw">property version "class="num">1.00"
class="macro">#include<GenerateSymbols.mqh>
class="macro">#class="kw">property script_show_inputs
class=class="str">"cmt">//--- class="kw">input parameters
class="kw">input class="type">class="kw">string BaseSymbol="EURUSD"; class=class="str">"cmt">// 基础品种,置换从它身上拆分时
class="kw">input class="type">class="kw">datetime StartDate=D&class="macro">#x27;class="num">2023.06.class="num">01 class="num">00:class="num">00&class="macro">#x27;; class=class="str">"cmt">// 取分时的起始时间
class="kw">input class="type">class="kw">datetime EndDate=D&class="macro">#x27;class="num">2023.08.class="num">01 class="num">00:class="num">00&class="macro">#x27;; class=class="str">"cmt">// 取分时的结束时间
class="kw">input class="type">uint Permutations=class="num">100; class=class="str">"cmt">// 要生成的置换品种数量
class="kw">input class="type">class="kw">string CustomID="";class=class="str">"cmt">//SymID to be added to symbol permutation names 新品种名后面追的标识串
class=class="str">"cmt">//---
CGenerateSymbols generateSymbols(); class=class="str">"cmt">// 声明生成器对象
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">//---
   if(!generateSymbols.Initiate(BaseSymbol,CustomID,StartDate,EndDate)) class=class="str">"cmt">// 先按输入初始化,失败就退出
      class="kw">return;
class=class="str">"cmt">//---
   Print("Number of newly generated symbols is ", generateSymbols.Generate(Permutations)); class=class="str">"cmt">// 生成置换品种并打印数量
class=class="str">"cmt">//---
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class CNewSymbol. |
class=class="str">"cmt">//| Purpose: Base class for a custom symbol. |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CNewSymbol : class="kw">public CObject
  {
   class=class="str">"cmt">//--- === Data members === ---
class="kw">private:
   class="type">class="kw">string m_name; class=class="str">"cmt">// 新品种名
   class="type">class="kw">string m_path; class=class="str">"cmt">// 品种路径
   class="type">MqlTick m_tick; class=class="str">"cmt">// 分时结构
   class="type">class="kw">ulong m_from_msc; class=class="str">"cmt">// 起始毫秒时间戳

「自造交易品种的 C++ 类骨架」

在 MT5 里用代码动态建一个自定义品种,核心是把品种属性和 Tick 源都封装进一个类。下面这段类声明就是骨架:用 ulong 记毫秒区间、uint 记每批写入量、bool 标记是否被选中,对外暴露 Create / Delete / Clone / LoadTicks 等接口。 m_batch_size 默认 1e6(即 100 万条 Tick 每批),这是大样本回测时避免单次写入卡死终端的务实取值;m_is_selected 默认 false,意味着新建品种不会自动挤掉当前图表焦点。 三个重载的 SetProperty / GetProperty 分别接管 double、long、string 三类符号属性,对应 ENUM_SYMBOL_INFO_DOUBLE / INTEGER / STRING。写 EA 时若想改点差或报价精度,直接调对应重载即可,不用再翻文档比对类型。 开 MT5 建个 include 文件把这段类头粘进去,先只实现 Create 的空壳,编译通过后再逐个补 Tick 加载逻辑,比一口气写完整类更容易排错。外汇与贵金属自定义品种测试仍属高风险操作,参数错配可能导致历史数据失真。

MQL5 / C++
class="type">class="kw">ulong              m_to_msc;
class="type">uint               m_batch_size;
class="type">bool               m_is_selected;
class=class="str">"cmt">//--- === Methods === ---
class="kw">public:
  class=class="str">"cmt">//--- constructor/destructor
  class="type">void              CNewSymbol(class="type">void);
  class="type">void             ~CNewSymbol(class="type">void) {};
  class=class="str">"cmt">//--- create/class="kw">delete
  class="type">int               Create(class="kw">const class="type">class="kw">string _name,class="kw">const class="type">class="kw">string _path="",class="kw">const class="type">class="kw">string _origin_name=NULL,
                            class="kw">const class="type">uint _batch_size=class="num">1e6,class="kw">const class="type">bool _is_selected=class="kw">false);
  class="type">bool              Delete(class="type">void);
  class=class="str">"cmt">//--- methods of access to class="kw">protected data
  class="type">class="kw">string            Name(class="type">void) class="kw">const { class="kw">return(m_name); }
  class="type">bool              RefreshRates(class="type">void);
  class=class="str">"cmt">//--- fast access methods to the integer symbol properties
  class="type">bool              Select(class="type">void) class="kw">const;
  class="type">bool              Select(class="kw">const class="type">bool select);
  class=class="str">"cmt">//--- service methods
  class="type">bool              Clone(class="kw">const class="type">class="kw">string _origin_symbol,class="kw">const class="type">class="kw">ulong _from_msc=class="num">0,class="kw">const class="type">class="kw">ulong _to_msc=class="num">0);
  class="type">bool              LoadTicks(class="kw">const class="type">class="kw">string _src_file_name);
  class=class="str">"cmt">//--- API
  class="type">bool              SetProperty(ENUM_SYMBOL_INFO_DOUBLE _property,class="type">class="kw">double _val) class="kw">const;
  class="type">bool              SetProperty(ENUM_SYMBOL_INFO_INTEGER _property,class="type">long _val) class="kw">const;
  class="type">bool              SetProperty(ENUM_SYMBOL_INFO_STRING _property,class="type">class="kw">string _val) class="kw">const;
  class="type">class="kw">double            GetProperty(ENUM_SYMBOL_INFO_DOUBLE _property) class="kw">const;
  class="type">long              GetProperty(ENUM_SYMBOL_INFO_INTEGER _property) class="kw">const;
  class="type">class="kw">string            GetProperty(ENUM_SYMBOL_INFO_STRING _property) class="kw">const;
  class="type">bool              SetSessionQuote(class="kw">const ENUM_DAY_OF_WEEK _day_of_week,class="kw">const class="type">uint _session_index,

◍ 自定义品种类的私有克隆与全局宏定义

上面这段头文件收尾部分,把 CNewSymbol 类里没对外暴露的私有方法亮了出来。CloneProperty 是个模板函数,靠传入 PT 类型的属性枚举去复制原品种的某项参数,CloneTicks 则有两个重载:一个吃 MqlTick 数组直接写,一个吃原 symbol 名去拉 tick 再写。私有段不开放给外部调用,主要是防止脚本层误改克隆逻辑。 类定义结束后紧接的是 GenerateSymbols.mqh 的版权头与两个 include:PermuteTicks.mqh 和 NewSymbol.mqh,说明这个生成脚本依赖前面的排列与建品种模块。 宏定义给了两个硬数字:MAX_DOWNLOAD_ATTEMPTS 设为 10,代表下载 tick 最多重试 10 次;RESIZE_RESERVE 为 100,是数组扩容时的预留空间。你在 MT5 里改这两个值,能直接调节脚本对网络抖动和 tick 洪峰的容忍度——外汇与贵金属tick数据受流动性影响大,高风险环境下适当调高可能降低丢数概率。

MQL5 / C++
class="kw">const class="type">class="kw">datetime _from,class="kw">const class="type">class="kw">datetime _to);
  class="type">bool        SetSessionTrade(class="kw">const ENUM_DAY_OF_WEEK _day_of_week,class="kw">const class="type">uint _session_index,
                              class="kw">const class="type">class="kw">datetime _from,class="kw">const class="type">class="kw">datetime _to);
  class="type">int         RatesDelete(class="kw">const class="type">class="kw">datetime _from,class="kw">const class="type">class="kw">datetime _to);
  class="type">int         RatesReplace(class="kw">const class="type">class="kw">datetime _from,class="kw">const class="type">class="kw">datetime _to,class="kw">const class="type">MqlRates &_rates[]);
  class="type">int         RatesUpdate(class="kw">const class="type">MqlRates &_rates[]) class="kw">const;
  class="type">int         TicksAdd(class="kw">const class="type">MqlTick &_ticks[]) class="kw">const;
  class="type">int         TicksDelete(class="kw">const class="type">long _from_msc,class="type">long _to_msc) class="kw">const;
  class="type">int         TicksReplace(class="kw">const class="type">MqlTick &_ticks[]) class="kw">const;
  class=class="str">"cmt">//---
class="kw">private:
  class="kw">template<class="kw">typename PT>
  class="type">bool        CloneProperty(class="kw">const class="type">class="kw">string _origin_symbol,class="kw">const PT _prop_type) class="kw">const;
  class="type">int         CloneTicks(class="kw">const class="type">MqlTick &_ticks[]) class="kw">const;
  class="type">int         CloneTicks(class="kw">const class="type">class="kw">string _origin_symbol) class="kw">const;
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     GenerateSymbols.mqh |
class=class="str">"cmt">//|          Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#include<PermuteTicks.mqh>
class="macro">#include<NewSymbol.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| defines:max number of ticks download attempts and array resize  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define MAX_DOWNLOAD_ATTEMPTS class="num">10
class="macro">#define RESIZE_RESERVE class="num">100
把重排与统计交给小布
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到重排采样与p值分布,你只管读结论。把重复劳动交给小布,你专注决策。

常见问题

公式为(z+1)/(r+1),其中r是完成的置换次数,z是性能优于基准未置换测试的置换总数。p值表示在EA无盈利潜力时偶然获得当前结果的概率,越低越能排除运气成分。
可以,小布盯盘品种页内置了AIGC诊断,包含重排采样与假设检验视图,省去自己写脚本置换价格的开销。
只有用0到1均匀分布的随机数生成重排,才能保证检验统计量的零分布有效,否则p值会偏斜、假设检验失效。
至少一百次,倾向数千次更稳健;迭代越多,性能数据集合越能反映EA在随机顺序下的真实概率分布。
前者重排市场本身顺序检验EA对行情结构的依赖,后者重排已有交易顺序检验信号逻辑稳定性,两者揭示的风险维度不同。