交易员的正则表达式·进阶篇
🔍

交易员的正则表达式·进阶篇

(2/3)· 当成交记录和 EA 回测表堆成乱麻,几个元字符就能替你筛出想要的那一行

含代码示例实战向 第 2/3 篇
把整本交易历史导出成文本后靠肉眼翻页找某类平仓记录,既漏看又费时。很多人不知道 MetaTrader 5 的 MQL5 已内置正则引擎,却还在用字符串包含判断硬凑。用错工具处理范式匹配,等于拿螺丝刀敲钉子。

◍ 用字典与枚举器搭一个可遍历容器

在 MT5 的 MQL5 里,标准模板库(STL)的 Dictionary 和 List 能直接拿来管键值对与顺序集合。上面这段演示了先建一个以字符串为键、整型为值的字典,并指定用 StringEqualityComparer 做键比较,避免默认比较器在中文或特殊字符上出问题。 紧接着新建一个 List<int>,依次压入 0、1、2 三个元素,再通过 GetEnumerator() 拿枚举器。用 while(en.MoveNext()) 逐位前进,Print(en.Current()) 把当前值打到专家日志,输出顺序就是 0、1、2——这是一个可验证的确定现象。 注意枚举器和 List 本身都是堆上 new 出来的指针,跑完必须手动 delete en 和 delete list,否则 MT5 终端会报内存泄漏。外汇与贵金属 EA 跑长周期时,这种泄漏可能拖慢盯盘终端,属高风险运维点。 下面附上可直接贴进 MT5 脚本区验证的代码,改一下 list.Add 里的数字就能看不同遍历结果。

MQL5 / C++
Dictionary<class="type">class="kw">string,class="type">int> *dictionary= new Dictionary<class="type">class="kw">string,class="type">int>(new StringEqualityComparer);
List<class="type">int>* list = new List<class="type">int>();
list.Add(class="num">0);
list.Add(class="num">1);
list.Add(class="num">2);
IEnumerator<class="type">int> *en = list.GetEnumerator();
while(en.MoveNext())
  {
   Print(en.Current());
  }
class="kw">delete en;
class="kw">delete list;
class="macro">#include <RegularExpressions\Regex.mqh>

用正则从 HTML 历史里抠出订单与成交

要把 MT5 导出的 HTML 交易报告变成可筛选的数据,核心是先按标签拆文本。实操时进终端「工具箱 → 历史」,右键导出一个 HTML(IE 格式)报告,存到 \MetaTrader 5\MQL5\Files 沙箱,EA 的 file_name 参数填这个文件名即可跑。 TradeHistory 构造器只用一个正则范式 "(>)([^<>]*)(<)" 扫描整份 HTML:它抓出所有以 '>' 开头、'<' 结尾、中间不含尖括号的子串,也就是标签之间的纯文本。匹配数若等于 23,判定为订单表一行;等于 27 则是成交表一行;其余直接丢弃。 拆出的偶数位匹配去掉首尾括号后写进数组,订单填 11 字段、成交填 13 字段,分别进 List<OrderRecord*> 和 List<DealRecord*>。每读一行都要 delete matches,读完关文件、清正则缓存,否则内存会慢慢漏。 过滤不想自己写循环也行,List<T> 的 FindAll(Predicate match) 直接返回子表。关键是 match 函数怎么知道按哪一列、比什么值——下面代码里用两个静态变量 s_index、s_value 承接 SetIndex/SetValue 的设置,再在 FindRecord 里拿 record.m_data[s_index] == s_value 做判断,越界就 Print 报错并返回 false。 过滤完的表可绘到 EA 界面,也能存 Result.csv。注意:保存时文件名写死会覆盖,要留多列就得手动改名逐次存,不然前一次白筛。外汇和贵金属交易历史波动大、滑点复杂,回测结论仅代表历史样本,实盘复制需自担高风险。

MQL5 / C++
                TradeHistory(const class="type">class="kw">string path)
{
m_file_name=path;
m_handel= FileOpen(path,FILE_READ|FILE_TXT);
m_list1 = new List<OrderRecord*>();
m_list2 = new List<DealRecord*>();
Regex *rgx=new Regex("(>)([^<>]*)(<)");
while(!FileIsEnding(m_handel))
  {
   class="type">class="kw">string str=FileReadString(m_handel);
   MatchCollection *matches=rgx.Matches(str);
   if(matches.Count()==class="num">23)
     {
      class="type">class="kw">string in[class="num">11];
      for(class="type">int i=class="num">0,j=class="num">1; i<class="num">11; i++,j+=class="num">2)
        {
         in[i]=StringSubstr(matches[j].Value(),class="num">1,StringLen(matches[j].Value())-class="num">2);
        }
      m_list1.Add(new OrderRecord(in));
     }
   else if(matches.Count()==class="num">27)
     {
      class="type">class="kw">string in[class="num">13];
      for(class="type">int i=class="num">0,j=class="num">1; i<class="num">13; i++,j+=class="num">2)
        {
         in[i]=StringSubstr(matches[j].Value(),class="num">1,StringLen(matches[j].Value())-class="num">2);
        }
      m_list2.Add(new DealRecord(in));
     }
   class="kw">delete matches;
  }
FileClose(m_handel);
class="kw">delete rgx;
Regex::ClearCache();
}
in[i]=StringSubstr(matches[j].Value(),class="num">1,StringLen(matches[j].Value())-class="num">2);
typedef class="type">bool (*Predicate)(IComparable*);
class="kw">static class="type">bool FindRecord(IComparable *value)
  {
   Record *record=class="kw">dynamic_cast<Record*>(value);
   if(s_index>=ArraySize(record.m_data) || s_index<class="num">0)
     {
      Print("Iindex 超出范围。");
      class="kw">return(false);
     }
   class="kw">return (record.m_data[s_index] == s_value);
  }

「用 FindAll 按列筛出目标记录」

在 MQL5 里处理 Record 集合时,先给单条记录写入数值并绑定列索引,再拿 source_list 做条件过滤,是常见做法。 下面这段代码片段展示了具体调用链:先 SetValue 写入待匹配值,SetIndex 指定查哪一列,最后用 FindAll 配合 Record::FindRecord 回调,从原列表里捞出符合条件的 Record 指针集合。 [CODE] Record::SetValue(value); Record::SetIndex(columnIndex); List<Record*> *new_list = source_list.FindAll(Record::FindRecord); [/CODE] 逐行拆解:第一行把 value 写进当前 Record 实例,作为后续查找的参照值;第二行设定 columnIndex,决定 FindRecord 比对的是第几列;第三行对 source_list 执行 FindAll,返回的新链表 new_list 只含匹配项,原列表不被改动。 在 MT5 里跑这段前,先确认 Record::FindRecord 的匹配逻辑(等于 / 包含 / 区间),否则可能返回空链。外汇与贵金属数据高频跳动,用此方式筛 tick 级记录时需注意内存与耗时,属高风险环境下的工具性操作。

MQL5 / C++
Record::SetValue(value);
Record::SetIndex(columnIndex);
List<Record*> *new_list = source_list.FindAll(Record::FindRecord);

◍ 用过滤器筛 EA 优化结果 XML

MetaTrader 5 导出的 EA 优化结果 .xml 文件,可以用一个带图形界面的工具直接读入。数据被拆成两张表:测试器结果表装回测统计,输入参数表存所有输入参数取值,后者最多只显示十个参数,超出的部分直接不渲染。 过滤逻辑很直接——选列名再给数值区间。比如对“通过”列限定 [0; 10]、对“盈利因子”列限定 [0.4; 0.5],界面就会只留下落在这两个区间里的跑次。 开 MT5 跑完优化后导出 xml,用这类过滤器先卡盈利因子 0.4~0.5 再卡通过次数,能快速排掉明显过拟合的批次。外汇与贵金属回测高风险,区间筛选只是降噪手段,不代表实盘概率。

把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到正则抽取后的成交分类与异常标注,你只管读结论。

常见问题

在 ASCII 范畴内二者等效,都匹配 0 到 9;若涉及全角或本地化数字字符集,需结合 \w 或显式字符类补充。
可以,小布盯盘的品种页支持把优化输出粘贴进诊断框,用正则范式自动标出达标参数组合,省去手动筛表。
正则默认找最长不超过上限的连续匹配,progressss 含 4 个 s 命中上限,但前后字母不属范式故不纳入整体。
可用类似 MARKET.*slip\d+ 的范式限定订单类型与滑点数字,具体写法是本篇第 3 节例子覆盖的内容。