帧分析器(Frames Analyzer)工具带来的时间片交易魔法·进阶篇
⏱️

帧分析器(Frames Analyzer)工具带来的时间片交易魔法·进阶篇

(2/3)· 多数交易者优化完 EA 就直接实盘,却从没看过那些拖垮曲线的无盈利时间片

案例拆解 第 2/3 篇

很多人在 MetaTrader 5 测试器跑完优化,导出参数便匆忙上实盘,忽略了结果里藏着的长期横盘与回撤片段。帧分析器提供的选卡与数据库视图,正是把这些隐性损耗摊开来看的入口。本篇接着基础篇,把余额、前100、收藏与数据库联动,以及被移除时间片如何写回智能系统逐一拆开。

余额选卡里的两种曲线对照

余额选卡并排给了两张图。左边只画初始余额,以及把亏损交易序列整段删掉后、每一步中间改进出来的余额曲线;一眼能看出,砍掉无盈利片段后资金曲线被拉直了多少。 右边放的是左边挑出的最优改进结果作为终态余额,再叠上每个交易周期里跑出来的全部余额轨迹,方便比对「单周期真实回撤」和「剔亏后理想路径」差多远。 两张图都跟随数据表里高亮行实时刷新,所以你点选某一行,就能看到那一笔或那一段被排除前后,余额从多少变到多少。 实操上,开 MT5 把回测报告按周期高亮,观察左图删亏后余额相较初始抬升的幅度,若多数周期高亮后左图曲线仍频繁下破右图终值,说明该策略剔亏后的稳定性可能仍偏弱,外汇与贵金属品种杠杆高、滑点敏感,此类结论仅作概率参考。

「剔除无盈利片后的前百结果对比」

工具界面左图展示原始回测里前 100 个盈利结果,右图则按 Profit、Trades、DD、RF、BI_Profit、BI_Trades、BI_DD、BI_RF 八个准则筛选出的前 100 个结果。表中高亮行会实时驱动图形刷新——比如左图黑色余额曲线对应的高亮行,在右图里就变成了排除一段连续亏损交易后的改进曲线。 用户可以指定从历史中剔除若干「无盈利时间片」来重算结果。实测中,即便原本看起来不赚钱的 EA,在剥掉这些片之后,前 100 个样本的最终余额和回撤表现都可能明显好转。 把看中的参数组合点 Add to favorites 存进收藏表,整张结果表里这些行会用不同颜色标出,方便后续横向比对。外汇与贵金属市场杠杆高、滑点跳空频繁,这类样本外改善只代表历史概率倾向,实盘仍需在小周期上手动复核。

◍ 把筛选结果落盘到收藏与数据库

收藏选卡的结构和 Results 选卡一致,差异只在能把挑出来的验算结果同时写进文件和本地库。窗口上方三个按钮直接控制生命周期:Delete selected 删左表选中项并清库,Delete all 清空左表与库,Save parameters 负责落盘。 点 Save parameters 后,选中结果会进库里的 FAVORITE_RESULTS 表;若 MQD 文件是读取模式,就写到 MQL5/Files/Reports/FramesA/[当前时间],FRAME_MODE(优化后立刻存)则写到 MQL5/Files/Reports/[当前时间][EA名]。每个验算编号单独成文件夹,前缀统一挂在文件名前,例如 422462.set 存 EA 外部参数,422462_balance_sub_bi.png 是剔掉无盈利时间片后的余额图,422462_bi_table.png 是对应终值表截图,422462_gui.png 为完整界面。 外汇与贵金属优化结果高度依赖样本区间,回测漂亮不等于实盘能跑,杠杆品种随时可能击穿止损。下面这段 set 是实际落盘样例,MACD 快线周期 15、慢线 25、信号线 25,止盈 270 点、止损 115 点,参数范围都标了 Y 允许优化。

MQL5 / C++
; <span class="keyword">this</span> file contains last used <span class="keyword">input</span> parameters <span class="keyword">for</span> testing/optimizing FA expert advisor
; Experts\Advisors\ExpertMACD.ex5
;
Inp_Expert_Title=ExpertMACD||<span class="number">class="num">0.0</span>||<span class="number">class="num">0.0</span>||<span class="number">class="num">0.0</span>||N
Inp_Signal_MACD_PeriodFast=<span class="number">class="num">15</span>||<span class="number">class="num">5.0</span>||<span class="number">class="num">5.0</span>||<span class="number">class="num">30.0</span>||Y
Inp_Signal_MACD_PeriodSlow=<span class="number">class="num">25</span>||<span class="number">class="num">5.0</span>||<span class="number">class="num">5.0</span>||<span class="number">class="num">30.0</span>||Y
Inp_Signal_MACD_PeriodSignal=<span class="number">class="num">25</span>||<span class="number">class="num">5.0</span>||<span class="number">class="num">5.0</span>||<span class="number">class="num">30.0</span>||Y
Inp_Signal_MACD_TakeProfit=<span class="number">class="num">270</span>||<span class="number">class="num">30.0</span>||<span class="number">class="num">5.0</span>||<span class="number">class="num">300.0</span>||Y
Inp_Signal_MACD_StopLoss=<span class="number">class="num">115</span>||<span class="number">class="num">20.0</span>||<span class="number">class="num">5.0</span>||<span class="number">class="num">200.0</span>||Y

优化后数据库落盘到哪了

在测试器跑完一轮参数优化,帧分析器会马上把全部结果写进本地数据库,而不是只留在界面里。每次优化都会在终端目录 MQL5/Files/DB 下新建一个 .db 文件,文件名由当前时间和 EA 名拼成:[CURRENT_TIME] [EXPERT_NAME].db,所以同一 EA 多轮优化不会互相覆盖。

库里实际有三张表。OPTIMIZATION_RESULTS 存每遍验算的核心指标:Pass 索引、Profit 盈利、Trades 笔数、PF 盈利因子、DD 回撤、RF 恢复因子,以及所有外部参数列和 Deals 交易历史。EXPERT_PARAMETERS 只有 Parameter 和 Value 两列,记录参数名与值;以标准版 ExpertMACD 为例,INPUT_n 的 Value 会用隔开外部参数名和优化参数名。

FАVORITE_RESULTS 只留三列:FavoriteID、Pass、RemovedIntervals,专门存被标收藏的验算,若真实交易时段被禁,就在 RemovedIntervals 记掉。这样优化加筛选完,后续 EA 实盘要用的东西基本都能直接从库里捞,外汇和贵金属品种波动大、滑点跳空频繁,拿这些历史 Pass 做样本前先确认回测与真实环境偏差。

「把帧分析器接进自己的EA」

帧分析器本身只是个EA,直接挂进终端跑,图形和表格全是空的——它不吃实时行情,只认优化结果。想立刻看效果,得去把附带的优化数据库放进 MQL5/Files/DB,然后在EA界面的 Frames 选卡点 Open DB,弹窗里挑对应文件就能载入,我本地那个目录常备好几个不同品种的库。 要让它自动存你自己的优化数据,得把它当函数库引到EA主文件里。下载完后在 MQL5/Experts/Market 能找到 FramesA.ex5,用 #import 指过去,再把几个事件函数挂到EA原有的回调上就行,代码量极小。 如果你EA本来就用了 EasyAndFastGUI v2.0 做界面,那什么都不用额外调,帧分析器内置的同款库会自己协同,它跑在 FRAME_MODE 下不抢事件。没用这个GUI库也没关系,分析器照样收数据。文末给的 ExpertMACD 示例已经焊好了模块,拆开照抄最快。

MQL5 / C++
class="macro">#class="kw">import "..\Experts\Market\FramesA.ex5"
  class="type">void OnEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam);
  class="type">void OnTesterEvent(class="type">void);
  class="type">void OnTesterInitEvent(class="type">void);
  class="type">void OnTesterPassEvent(class="type">void);
  class="type">void OnTesterDeinitEvent(class="type">void);
class="macro">#class="kw">import
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) {
  FramesA::OnEvent(id, lparam, dparam, sparam);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Test completion event handler                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double OnTester(class="type">void) {
  FramesA::OnTesterEvent();
  class="kw">return(class="num">0.0);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| TesterInit function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTesterInit(class="type">void) {
  FramesA::OnTesterInitEvent();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| TesterPass function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTesterPass(class="type">void) {
  FramesA::OnTesterPassEvent();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| TesterDeinit function                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTesterDeinit(class="type">void) {
  FramesA::OnTesterDeinitEvent();
}
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 把删掉的时段接回EA交易闸门

帧分析器跑完优化后,你手头会有一个存满验算结果的数据库,以及一张自己挑出来的收藏表(FAVORITE_RESULTS),表里对应的时间片已经被你主动移除。下一步不是看报表,而是让EA在实盘里真的避开这些时段。 用 EasyAndFastGUI 2.0 里的 CTimeRanges 元素能直接在窗体上画一条日内时间标尺,虚线标当前时间,鼠标悬停拉出竖线和标签。点击标尺会抛出 ON_CLICK_TIME_RANGE 自定义事件,dparam 是当天逝去的秒数,sparam 是点中的被删区间字符串(空白代表没命中任何范围)。 下面这段 CreateGUI 演示了最少代码量搭出界面:窗体、Open DB 按钮、验算下拉框、以及写死的一组禁交易时段。注意背景高亮的那几行,time_ranges 数组里塞了 5 个具体区间,例如 00:45:00-01:20:01、15:30:00-17:39:59,SetTimeRanges 之后标尺立刻可视化。 打开数据库后调 GetPassNumbersOfFavoriteResults() 从收藏表抽验算索引,再经 GetFavoriteRemovedIntervalsByPassNumber() 拿到对应删除时段,填进全局数组并刷给 CTimeRanges。真正的闸门在 CheckTradeTime():每次 OnTick 先问一句现在是不是落在禁交易串里,命中就直接不下单。外汇与贵金属杠杆高,时段过滤只降概率不消风险,参数自己回测再定。 事件拦截那段,先判 id 再核 lparam 的元素ID,命中就把 dparam/sparam 打到 Experts 日志,顺手 GetTimeRanges() 把数组 ArrayPrint 出来,方便你核对图形和底层数据是否一致。

MQL5 / C++
class="type">void CApp::CreateGUI(class="type">void) {
class=class="str">"cmt">//--- Form
  m_window1.ResizeMode(true);
  m_window1.ThemeButtonIsUsed(true);
  CCoreCreate::CreateWindow(m_window1, "TIME TRADE RANGES", class="num">1, class="num">1, class="num">350, class="num">100, true, true, true, true);
  
class=class="str">"cmt">//--- Button
  CCoreCreate::CreateButton(m_button1, m_window1, class="num">0, "Open DB...", class="num">10, class="num">30, class="num">100);
  
class=class="str">"cmt">//--- Combobox
  class="type">class="kw">string items1[] = {"class="num">12345", "class="num">19876", "class="num">45678", "class="num">23456", "class="num">67890"};
  CCoreCreate::CreateCombobox(m_combobox1, m_window1, class="num">0, "Passes: ", class="num">120, class="num">30, class="num">135, class="num">90, items1, class="num">103, class="num">0);
  
class=class="str">"cmt">//--- Time ranges
  class="type">class="kw">string time_ranges[] = {
    "class="num">00:class="num">45:class="num">00 - class="num">01:class="num">20:class="num">01",
    "class="num">08:class="num">55:class="num">00 - class="num">09:class="num">25:class="num">01",
    "class="num">12:class="num">55:class="num">00 - class="num">13:class="num">50:class="num">01",
    "class="num">15:class="num">30:class="num">00 - class="num">17:class="num">39:class="num">59",
    "class="num">20:class="num">10:class="num">00 - class="num">21:class="num">05:class="num">00"
  };
  m_time_ranges1.SetTimeRanges(time_ranges);
  m_time_ranges1.AutoXResizeMode(true);
  m_time_ranges1.AutoXResizeRightOffset(class="num">5);
  CCoreCreate::CreateTimeRanges(m_time_ranges1, m_window1, class="num">0, class="num">5, class="num">60, class="num">390);
}
class="type">void CApp::OnEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) {
  if(id == CHARTEVENT_CUSTOM + ON_CLICK_TIME_RANGE) {
    
    if(m_time_ranges1.Id() == lparam) {
      Print(__FUNCTION__, " > pressed time: ", ::TimeToString((class="type">class="kw">datetime)dparam, TIME_MINUTES|TIME_SECONDS), "; pressed time range: ", sparam);
      
      class="type">class="kw">string time_ranges[];
      m_time_ranges1.GetTimeRanges(time_ranges);
      
      ArrayPrint(time_ranges);
      class="kw">return;
    }
  }
}
把帧重播交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到优化帧的重播与无盈利区间标注,你只需判断该不该剔除对应时间片。

常见问题

余额选卡读取 MQD 或数据库里的逐帧余额,能按 criterions 重排并标出无盈利区间,测试器原生曲线只给总览不分层。
可以依据 Profit、Recovery Factor 等准则勾选,工具按勾选集合在前 100 里过滤,方便快速锁定稳健参数区。
目前小布盯盘内置的 AIGC 诊断可识别标准优化数据库结构,自动生成时间片分布图,不必手动跑帧分析器界面。
读取模式仅做索引映射,曲线渲染限制 Curves total 数量可缓解,分库存储历史优化能进一步降载。
仅跳过对应历史区间的回测与信号计算,实盘若遇到同结构时段倾向不触发,外汇贵金属高风险下仍需样本外验证。