Skip to content

散户也能做微型量化?用MQL5鲁棒搭建Tick级别策略引擎

作者:老余捞鱼

原创不易,转载请标明出处及原作者。

写在前面的话:这篇文章我拆解了一个在MQL5上用MAD鲁棒Z分数做Tick级别均值回归的策略框架:从为什么标准差不靠谱,到信号怎么算、进出怎么管、代码怎么写,全部摊开讲。

大家好,我是老余捞鱼。今天拆一篇让我眼前一亮的内容:有人在MetaTrader 5上用MQL5搭了一个Tick级别的均值回归策略桌面,核心武器不是什么花哨指标,而是统计学里一个朴实但极其实用的东西:中位数绝对偏差,也就是MAD。

这篇文章我不仅讲思路,还会结合我自己团队在鲁棒统计和微结构策略上的经验,把里头的设计逻辑、容易踩的坑,以及国内做量化时需要注意的差异,全部补充完整。老规矩,干货直接上,不绕弯子。

① 你做的不是HFT

美国SEC的文献综述对机构级高频交易的定义,是关于极致速度、机房托管、超低延迟行情源、海量订单流和极短持仓周期的世界。而一个散户坐在MetaTrader终端前面,是拿不到这种条件的。

把个人环境下的策略硬叫”HFT”,最大的危害不是名字好不好听,而是容易让自己对回测结果产生不切实际的信任。所以我给了一个更诚实的定位:MFT(Medium Frequency Trading,中频交易),带微结构色彩

国内很多朋友在CTP上写策略,回测飞起,实盘拉胯,一个核心原因就是回测环境模拟不了真实的订单延迟和滑点。先把自己定位清楚,才知道该优化什么。

但换个角度想,散户也有散户的优势:你可以做得很小、很专注、很灵活。一个机构不可能把整个业务押在某个品种上少数几次均值回归的小波动上,但你一个账户可以。这就是”小而精”的优势。

② 为什么选MAD而不是标准差?

策略的灵魂在于度量”价格偏离了多少”。传统做法是用标准差算Z分数,但选MAD的理由非常实际。

2.1 Tick数据的”脏”你想象不到

Tick数据是什么?就是每一笔报价的原始记录。它不像K线那样经过平滑处理,里面充斥着各种异常:一笔报价突然跳了5个点、价差瞬间从1个点扩大到20个点、某家流动性提供商抽了一下风……这些极端值如果用标准差来度量,会发生什么?

一个极端报价就能把标准差拉偏一大截,导致你的Z分数整体失真。本该触发的信号没触发,不该触发的时候反而触发了。

2.2 MAD的鲁棒性原理

MAD(Median Absolute Deviation,中位数绝对偏差)的计算过程是这样的:

  • 第一步:算出数据集的中位数 median
  • 第二步:计算每个数据点与中位数的绝对偏差 |x_i – median|
  • 第三步:再对这些绝对偏差求中位数,得到MAD

关键在于,MAD用的是两次中位数运算。中位数本身对极端值就不敏感:只要不超过50%的数据是极端值,中位数就不会被拉跑偏。而标准差用的是均值和平方运算,一个极端值的影响会被放大。

对比维度标准差 (SD)MAD
中心度量均值 (Mean)中位数 (Median)
偏离度量偏差的平方和绝对偏差的中位数
抗极端值能力弱,一个异常值影响大强,需50%以上异常才失效
适用场景干净、近正态数据含噪声、非对称数据
Tick数据适配度较差较好

美国国家标准与技术研究院(NIST)也明确指出:中位数绝对偏差比标准差更不容易受极端观测值的影响。这正是我们做Tick级别分析所需要的:我们要度量的是”局部恐慌”,不是”平滑趋势”。

2.3 那个1.4826是哪来的?

很多人看到公式里乘以1.4826就懵了。其实这个系数是一个正态一致性修正因子。假设数据确实服从正态分布,那么:正态分布下MAD与标准差的关系E(MAD) ≈ 0.6745 × σ因此修正系数为1 / 0.6745 ≈ 1.4826

乘上这个系数后,MAD就可以和标准差在同一个尺度上比较了。这样算出来的Z分数,在正态假设下和传统Z分数等价,但在非正态、含异常值的情况下远比传统Z分数稳定。

一句话总结:
MAD不是替代标准差的"另一种计算方式",而是对真实数据环境更友好的度量工具。Tick数据越脏,MAD的优势越明显。

③ 信号逻辑:从MAD到交易信号

策略框架需要非常克制,核心逻辑如下:

3.1 只做一个品种,只持一个仓位

这一点太重要了。很多人刚开始写策略就想搞多品种联动、多因子融合,结果代码越来越复杂,效果越来越差。回归最简单的模式:EURUSD,单一品种,单一仓位,不做马丁格尔,不做加仓摊薄

3.2 核心信号计算流程

  • 维护滚动窗口:保留最近301个中间价(Mid-Price)Tick,中间价 = (Bid + Ask) / 2
  • 计算窗口中位数和MAD:对这301个中间价,算出median和MAD
  • 计算鲁棒Z分数:将最新中间价转化为标准化偏离度z = (mid – median) / (1.4826 × MAD)
  • 价差过滤:当前实时价差必须低于硬性上限(如12个点),否则不交易
  • 做多条件:z ≤ -3.2 且最新Tick出现微小反弹(价格比上一个Tick高)
  • 做空条件:z ≥ +3.2 且最新Tick出现微小回落(价格比上一个Tick低)
重点说反弹过滤器:
那个"微小反弹"看起来不起眼,但这又是极其关键的环节。没有它,你会在价格最极端的那个Tick上直接入场——可能是在一根急速下跌的K线中间"接飞刀"。反弹过滤器的本质是:等价格至少出现了"不再恶化"的迹象,才考虑进场。

3.3 信号参数一览

参数默认值含义
WinTicks301滚动窗口大小(Tick数量)
EntryZ3.2Z分数入场阈值
MaxSpreadPts12最大允许价差(点)
Bounce过滤1 Tick方向入场时需有反向微动

④ 退出逻辑:五重保护让策略”活着出来”

进场只是开始,真正决定策略能不能存活的是出场规则。

退出条件触发规则设计意图
Z分数回归多头:z回升到-0.5或以上; 空头:z回落到0.5或以下捕捉均值回归的主要利润段
时间止损持仓超过18秒强制离场避免均值回归失效后的慢性亏损
MAD止损价格再朝不利方向移动1.8×MAD在统计偏离进一步恶化前截断亏损
价差异常持仓期间价差突然扩大市场可能发生异常,立即离场
强制平仓以上任一条件满足即执行不留死角,确保快速退出
18秒的时间止损在Tick级别策略里是合理的。均值回归的核心假设是"偏离是短暂的",如果18秒还没回归,说明这很可能不是短暂偏离,而是趋势性运动。此时再等下去,就是在用均值回归的逻辑去做趋势跟踪的事,逻辑上已经矛盾了。

另外,价差异常退出也是一个很实用的设计。在真实市场中,价差突然扩大往往意味着流动性骤降或者即将有大新闻出来,此时继续持仓的风险是不可控的。

⑤ MQL5的技术要点:别只信任OnTick()

这段是最有实战价值的技术细节。

5.1 OnTick()的陷阱

在MQL5里,OnTick()是一个”新报价到达”的通知函数,但它不保证你能捕获每一个Tick。MetaQuotes官方文档明确说明:如果当前已经有一个NewTick事件正在排队或正在被处理,后续的NewTick事件不会被加入队列

这意味着什么?在行情剧烈波动、Tick密集涌入的时候,你会丢失Tick。而你的Z分数计算依赖于完整的Tick序列,丢Tick就等于信号失真。

5.2 正确做法:OnTick() + CopyTicks()

OnTick()或者定时器作为”唤醒信号”,然后用CopyTicks()拉取自上次毫秒级时间戳以来的所有Tick,更新自己的环形缓冲区。

MetaQuotes官方文档对CopyTicks()的描述是:用于请求和分析已接收的Tick数据,包括本地Tick数据库的同步。这才是获取完整Tick数据的正确方式。

最佳实践:
OnTick() = 门铃,告诉你有人来了
CopyTicks() = 监控录像,让你看清来了几个人、长什么样

两者配合使用,才是Tick级别策略的数据基础。

5.3 订单执行的两个注意点

两个容易被忽略的执行层面细节:

  • OrderCheck()前置校验:每次发单之前先调用OrderCheck()验证。因为MetaQuotes明确说明,OrderSend()返回成功只表示”请求被接受处理”,不等于”已经成交”。
  • OnTradeTransaction()追踪成交:一个OrderSend请求可能产生多个交易事务事件,而且MetaQuotes文档指出这些事件的到达顺序是不保证的。所以需要用OnTradeTransaction()逐笔追踪实际成交情况。

5.4 虚拟托管:7×24运行的方案

对于全自动运行,MetaTrader 5提供了虚拟托管服务(Virtual Hosting)。MetaQuotes表示它支持平台7×24小时运行,并且可以选择离经纪商较近的服务器来减少网络延迟。

踩坑提醒:
MetaTrader的文档提到,迁移到虚拟平台后自动交易仍然是允许的。这意味着你需要保持图表列表非常干净:只放需要运行的策略图表,避免误触发或者资源浪费。

⑥ 核心代码实现

下面是MQL5核心信号函数。你可以在MetaEditor中创建一个Expert Advisor,在通过CopyTicks()维护好mid[]缓冲区之后,调用ShouldEnter()获取信号。

input int WinTicks = 301; # 滚动窗口大小(Tick数量)input double EntryZ = 3.2; # Z分数入场阈值input int MaxSpreadPts = 12;# 最大允许价差(点)enum Signal { SIG_NONE, SIG_LONG, SIG_SHORT };Signal ShouldEnter(const double &mid[], double spread_pts){   int n = ArraySize(mid); # 数据不足或价差过大,不交易   if(n < WinTicks || spread_pts > MaxSpreadPts) return SIG_NONE;   double w[], dev[];   ArrayResize(w, WinTicks);   ArrayResize(dev, WinTicks);   int start = n - WinTicks; # 提取最近WinTicks个中间价   for(int i = 0; i < WinTicks; i++) w[i] = mid[start + i];   # 计算中位数   ArraySort(w);   double med = w[WinTicks / 2]; #  计算MAD(中位数绝对偏差)    for(int i = 0; i < WinTicks; i++)      dev[i] = MathAbs(mid[start + i] - med);   ArraySort(dev);   double mad = dev[WinTicks / 2]; #  MAD过小(数据几乎不动),不交易    if(mad <= _Point) return SIG_NONE; #  计算鲁棒Z分数   double z = (mid[n - 1] - med) / (1.4826 * mad); #  反弹过滤器    bool bounce_up = mid[n - 1] > mid[n - 2];   bool bounce_down = mid[n - 1] < mid[n - 2]; #  信号判断   if(z <= -EntryZ && bounce_up) return SIG_LONG;   if(z >=  EntryZ && bounce_down) return SIG_SHORT;   return SIG_NONE;}

6.1 代码逻辑逐段解读

  • 前置检查:数据量不足(少于301个Tick)或实时价差超过12点,直接返回SIG_NONE,不产生任何信号。
  • 窗口提取:从mid[]数组末尾取最近301个数据点,复制到临时数组w[]。
  • 中位数计算:对w[]排序后取中间值,得到窗口中位数med。
  • MAD计算:计算每个数据点与med的绝对偏差,再排序取中位数,得到mad。
  • MAD保护:如果MAD小于等于_Point(最小价格变动单位),说明价格几乎不动,跳过。
  • Z分数:用鲁棒公式将最新中间价转化为标准化偏离度。
  • 反弹过滤+信号:Z分数达到阈值且方向上有微弱反弹,才发出信号。

6.2 参数敏感性提醒

以下参数需要根据具体品种和经纪商环境调整:

参数影响调参方向
WinTicks=301窗口越大信号越稳但越滞后高波动品种可适当缩小
EntryZ=3.2阈值越高信号越少但质量越高需配合品种波动率调整
MaxSpreadPts=12价差上限决定可交易时段不同经纪商差异很大,需实测

七、完整的策略架构总览

我把整个策略从数据到执行的完整链路画出来:

层级组件说明
数据层OnTick() + CopyTicks()唤醒通知 + 拉取完整Tick,维护mid[]环形缓冲区
信号层ShouldEnter()计算鲁棒Z分数 + 反弹过滤 + 价差过滤
执行层OrderCheck() + OrderSend()前置校验 + 发单,不保证即时成交
确认层OnTradeTransaction()逐笔追踪实际成交,事件顺序不保证
风控层五重退出规则Z回归 / 时间止损 / MAD止损 / 价差异常 / 强制平仓
运行层Virtual Hosting7×24托管,选近broker服务器减少延迟

整个链路看起来并不复杂,但每一层都有容易忽略的细节。策略的可靠性不是来自某个”绝妙指标”,而是来自每个环节都用最务实的方式处理

⑧ 我的真实看法:散户的优势到底在哪?

我仍然喜欢做这种”零售级类高频”策略,但原因不是觉得能跑赢机构,而是因为散户的边界本身就是优势。你可以用一个账户、一个品种、一个鲁棒信号,安安静静地捕捉少数几次均值回归的小波动,然后离场。一个大型机构没法把整个业务模型建立在这种”小打小闹”上——他们的资金规模不允许,合规成本不允许,绩效考核也不允许。

但你可以。

这就是”聪明的那部分”:不是技术多牛,而是你选择了一条和机构完全不重叠的赛道。机构比速度,你比选择性;机构比规模,你比精简;机构比信号数量,你比信号质量。

老余的观点:
我做量化这些年,越来越觉得"知道自己的边界"比"突破边界"重要。在MetaTrader上做Tick级别策略,你的边界就是:经纪商的延迟、报价的精度、执行的不确定性。你越早承认这些边界,就越能把精力花在真正有效的地方——比如选一个更鲁棒的统计量,比如加一个反弹过滤器,比如设计更严格的退出规则。

话说回来,这个策略框架也有它的局限性,我补充几个潜在问题:

  • 滑点不可控:MetaTrader的执行是经纪商报价制,不是交易所撮合制。你看到的报价和你实际成交的价格之间可能有差距,特别是在波动剧烈时。这个差距在回测里是体现不出来的。
  • 窗口参数的过拟合风险:301个Tick、3.2的Z阈值、18秒的时间止损——这些参数在某个时间段可能很漂亮,换一个时段可能完全失效。参数的鲁棒性比参数的最优性更重要。
  • 单品种集中风险:只做EURUSD意味着你的策略表现和这一个品种的微结构特征高度绑定。如果EURUSD的流动性结构发生变化(比如央行干预、重大数据发布),你的信号基础可能瞬间失效。
  • 经纪商依赖:不同经纪商的报价源、执行速度、点差政策差异很大。在A经纪商上跑通的策略,在B经纪商上可能完全不同。这不是策略的问题,是基础设施的问题。

⑨ 从MQL5到国内环境:本地化思考

国内做程序化交易的朋友,大多数用的是CTP接口而不是MetaTrader,所以我把几个关键差异点列出来,方便大家对照:

对比维度MetaTrader 5 / MQL5国内CTP
行情粒度Tick级(Bid/Ask逐笔)Tick级(成交逐笔,500ms切片)
执行模式经纪商报价制(做市商)交易所撮合制(竞价)
价差来源Bid-Ask Spread,由经纪商决定买卖盘口,由市场供需决定
7×24运行Virtual Hosting内置需自建服务器/云主机
MAD策略适配适合外汇等连续报价品种适合期货主力合约(高流动性时段)

核心差异在于执行模式。CTP是交易所撮合,你的单子是和全市场的参与者竞争,成交价格更透明但竞争也更激烈;MetaTrader是经纪商报价,执行更”温顺”但存在利益冲突(你的对手方就是经纪商)。两种环境下,同样的MAD Z分数信号,实际表现可能差异很大。

重要提醒:
无论你用的是MQL5还是CTP,Tick级别策略的回测结果都必须打一个大大的问号。回测环境的Tick生成机制和真实市场差距很大:MetaQuotes自己也承认,测试器中的Tick生成是模拟的。真实执行的滑点、延迟、丢Tick,回测里是模拟不出来的。策略上线前,务必用小仓位实盘验证。

⑩ 手把手:从零搭建的策略开发清单

如果你也想在MQL5上搭一个类似的策略桌面,我给你梳理一个开发清单,按照这个顺序来,不容易踩坑:

  • 第一步:搭建Tick数据管道用OnTick()作为唤醒信号,用CopyTicks()拉取完整Tick,维护一个环形缓冲区存储中间价。这是整个策略的地基,数据不准,后面全白搭。
  • 第二步:实现MAD计算函数对滚动窗口内的中间价,分别计算中位数和MAD。注意数组排序的性能开销,窗口大小不要设太大,301是个合理的起点。
  • 第三步:构建信号函数把MAD Z分数、价差过滤、反弹过滤器整合成一个ShouldEnter()函数。先在历史Tick数据上做离线验证,看信号分布是否合理。
  • 第四步:设计退出规则Z分数回归、时间止损、MAD止损、价差异常退出,四个条件缺一不可。先用模拟账户测试,确认退出逻辑在极端行情下不会卡住。
  • 第五步:执行层封装用OrderCheck()做前置校验,用OrderSend()发单,用OnTradeTransaction()追踪成交。这一层的关键是容错——网络断线、拒绝成交、部分成交都要处理。
  • 第六步:小仓位实盘验证最少跑2~4周,观察实际滑点、信号频率、持仓时长是否和预期一致。如果偏差超过20%,说明回测模型有系统性误差,需要重新审视。
  • 第七步:部署到虚拟托管实盘验证通过后,迁移到MetaTrader Virtual Hosting。只保留必要的图表,关闭所有无关的策略和指标,保持环境干净。

写在最后

这篇文章拆解的策略,不是什么”万能信号”,也不是什么”圣杯系统”。它就是一个朴素的框架:用一个对噪声更不敏感的统计量,在一个品种上,用最克制的方式,捕捉短暂的定价偏差,然后用多重规则确保自己能快速离场。

它真正的价值不在于具体参数,而在于三个设计理念:

  • MAD替代标准差:在脏数据环境下,鲁棒性比精确性重要。
  • 反弹过滤器:不追着最极端的Tick跑,等它先喘口气。
  • 多重退出:活得比赚得多,这是Tick级别策略的第一原则。

如果你也在MQL5或者其他平台上测试过MAD相关的Tick级别均值回归策略,欢迎留言交流。我们比的不是谁赚得多,而是谁犯的错少。这,才是散户做量化的正确姿势。

参考资料

  1. SEC, High Frequency Trading literature review — sec.gov
  2. MetaQuotes, OnTick and CopyTicks documentation — mql5.com
  3. NIST, Measures of Scale — itl.nist.gov
  4. MetaQuotes, Generating ticks in tester — mql5.com
  5. MetaQuotes, OrderSend and OnTradeTransaction — mql5.com
  6. MetaTrader 5 Help, Virtual Hosting for 24/7 OperationRegister a Server, and Migration — metatrader5.com
  7. 知乎, 数据预处理——绝对中位差离群值处理 — zhuanlan.zhihu.com
  8. CSDN, 中位数绝对偏差(MAD)法处理离群值 — blog.csdn.net

免责声明: 本文内容仅供学习和交流,不构成任何投资建议或收益承诺。文中提及的策略、代码和参数仅供技术讨论之用,实际交易结果受市场环境、经纪商条件、网络延迟等多重因素影响,过往表现不代表未来。任何基于本文内容的实际操作,风险自担。如有需要,请咨询持牌专业人士。

风险提示:本文仅供参考,不构成投资建议。投资有风险,入市需谨慎。

版权声明:本文为原创内容,转载请注明出处。


#量化交易 #MQL5 #MAD #鲁棒统计 #均值回归 #Tick数据 #MetaTrader5 #策略开发 #Z分数 #微结构 #程序化交易 #信号引擎

Published inAI&Invest专栏

Be First to Comment

    发表回复