80-20 交易策略:从理论根基到信号模块的 MQL5 实现(基础篇)(基础篇)
(1/3)· 用前一日高低边界的虚假突破抓日内反转,这篇先把策略和代码骨架讲透
80-20 系统的信号骨架
80-20 交易系统是一套基于价格行为反转概率的手动策略,核心逻辑是:当一段行情中约 80% 的时间朝一个方向运行后,剩余 20% 的时间出现反向修正的概率偏高。它最早在 MT5 社区以文章形式发布于 2016 年 12 月 29 日,原文显示浏览量 15 166、评论 6 条,说明该思路在外汇与贵金属交易者中有一定传播。 系统不依赖传统均线交叉,而是用自定义指标标出'极端单边段',交易者在指标提示的潜在反转区 manual 介入。需注意:外汇与贵金属属高杠杆品种,这种统计倾向不等于必然反转,实盘前应在 MT5 策略测试器用历史数据跑一遍。 要验证它,最直接的做法是打开 MT5 附带的策略测试器,加载对应 EA 做回测——原文作者提供了智能交易系统测试章节,说明该逻辑已被写成可回测代码,而非纯主观画线。
◍ 80-20 策略与本次工程落点
‘80-20’ 这个名字来自 Linda Raschke 与 Laurence Connors 的短线交易著作,核心动作是抓前一天极短线区间边界的虚假突破与回滚,信号寿命短,定位日内。和作者前作里那些‘测试范围边界’的思路同源,但这次只盯前一天的极短历史窗口。 本篇工程上要干三件事:用 MQL5 写出 80-20 的信号模块;把它挂到前篇文章那个基础 EA 的微调版上;再同一模块衍生出手动交易指标。代码面向刚从过程式往面向对象挪的新进程序员,所以不堆类封装,全用更好懂的结构体式写法。 另一个硬指标是验证策略活不活:Raschke 和 Connors 当年用的是上世纪末的市况。文末会贴几组基于近期历史数据的 EA 回测,外汇与贵金属波动剧烈、杠杆高风险,结论只代表概率层面‘至今仍可能可行’,不是稳赚依据。
「80-20 逆向挂单的触发逻辑」
这套系统核心假设很简单:若前一日开盘收在全日区间高位 20%、收盘收在低位 20%,且实体够长,那么当日开盘逆向穿透前日边界的概率偏高。George Taylor 的泰勒技术、Steve Moore 的期市计算机分析、Derek Gipson 实盘经验共同支撑这一形态。Linda Raschke 补充过,前日日线实体要超过平均日线长度才有效,但她没给出计算平均长度的历史根数,读者在 MT5 里可自取 20~60 根做敏感性测试。 具体买入流程:先确认前日开盘在顶部 20% 区、收盘在底部 20% 区;等当日最低价被实时报价至少穿透前日最低价 5 次;挂 buy limit 在前日区间下边界;成交后初始止损放当日最低价,再用尾随止损保盈。卖出对称处理,前日需为看涨柱,挂单在上边界,止损放当日最高价。外汇与贵金属波动剧烈,这类日内突破失败率不低,仅适合用迷你仓在 M15 及以下周期验证。 原文示例用 M15 布局、M5 判形态、M1 找价位,这种多周期下沉能避开 M15 噪音。你在 MT5 开三个窗口同步滚动,就能复现“前日 80-20 柱 + 当日 5 次穿透”的预警,不用等指标脚本也能人工盯出来。
把止盈塞进双蜡烛信号里
在只靠尾随止损平仓的旧逻辑上,加一个依赖最小突破级别 TS_8020_Extremum_Break 的止盈位,再乘自定义比率 TS_8020_Take_Profit_Ratio,就能让策略在 D1 双蜡烛形态触发时自带离场目标。外汇与贵金属波动剧烈,这类挂单信号仅代表概率倾向,实盘前务必在 MT5 用历史数据校验。 信号主函数 fe_Get_Entry_Signal 通过引用变量回传出场价:入场位、止损、止盈,以及首根柱线的高低边界。返回状态沿用 ENTRY_BUY / ENTRY_SELL / ENTRY_NONE / ENTRY_UNKNOWN 四态枚举,调用方按状态决定是否下挂单。 首根 D1 柱线只判一次,用静态变量缓存。两条硬标准:柱线极差必须超过近 TS_8020_D1_Average_Period(默认 20)日均值;开盘与收盘要落在柱线反向 20% 区间内。满足后按阴阳线定方向,并把高低点存进 sd_Range_High / sd_Range_Low 供次根柱线比对。 次根柱线唯一门槛是突破幅度不小于 TS_8020_Extremum_Break(默认 50 点)。买入形态看收盘价是否低于首根低点多出 50 点,卖出则反之;触发即回补止盈价 = 入场 ± _Point * 50 * 比率。把两个函数及 input 参数收进 Signal_80-20.mqh,丢到 MQL5\Include\Expert\Signal 就能被 EA 直接引。
enum ENUM_ENTRY_SIGNAL { class=class="str">"cmt">// 入场信号列表 ENTRY_BUY, class=class="str">"cmt">// 买入信号 ENTRY_SELL, class=class="str">"cmt">// 卖出信号 ENTRY_NONE, class=class="str">"cmt">// 无信号 ENTRY_UNKNOWN class=class="str">"cmt">// 未定义状态 }; ENUM_ENTRY_SIGNAL fe_Get_Entry_Signal( class=class="str">"cmt">// D1 双蜡烛形态分析 class="type">class="kw">datetime t_Time, class=class="str">"cmt">// 当前时间 class="type">class="kw">double & d_Entry_Level, class=class="str">"cmt">// 入场价位 (链接到变量) class="type">class="kw">double & d_SL, class=class="str">"cmt">// 止损位 (链接到变量) class="type">class="kw">double & d_TP, class=class="str">"cmt">// 止盈位 (链接到变量) class="type">class="kw">double & d_Range_High, class=class="str">"cmt">// 形态第一根柱线的最高价 (链接到变量) class="type">class="kw">double & d_Range_Low class=class="str">"cmt">// 形态第一根柱线的最低价 (链接到变量) ) {} class=class="str">"cmt">// 自定义设置 class="kw">input class="type">uint TS_8020_D1_Average_Period = class="num">20; class=class="str">"cmt">// class="num">80-class="num">20: 计算日线均值的天数 class="kw">input class="type">uint TS_8020_Extremum_Break = class="num">50; class=class="str">"cmt">// class="num">80-class="num">20: 昨日极值突破的最小点数 class="kw">static ENUM_ENTRY_SIGNAL se_Possible_Signal = ENTRY_UNKNOWN; class=class="str">"cmt">// 形态的首根柱线信号方向 class="kw">static class="type">class="kw">double class=class="str">"cmt">// 在即时报价之间存储计算价位的变量 sd_Entry_Level = class="num">0, sd_SL = class="num">0, sd_TP = class="num">0, sd_Range_High = class="num">0, sd_Range_Low = class="num">0; class=class="str">"cmt">// 检查形态 D1 图表上的首根柱线: if (se_Possible_Signal == ENTRY_UNKNOWN) { class=class="str">"cmt">// 未知 st_Last_D1_Bar = t_Curr_D1_Bar; class=class="str">"cmt">// 首根柱线当日无变化 class=class="str">"cmt">// 日线范围均值 class="type">class="kw">double d_Average_Bar_Range = fd_Average_Bar_Range(TS_8020_D1_Average_Period, PERIOD_D1, t_Time); if (ma_Rates[class="num">0].high - ma_Rates[class="num">0].low <= d_Average_Bar_Range) { class=class="str">"cmt">// 首根柱线长度不够 se_Possible_Signal = ENTRY_NONE; class=class="str">"cmt">// 意味着当日无信号 class="kw">return (se_Possible_Signal); } class="type">class="kw">double d_20_Percents = class="num">0.2 * (ma_Rates[class="num">0].high - ma_Rates[class="num">0].low); class=class="str">"cmt">// 昨日范围的 class="num">20% if ( ( class=class="str">"cmt">// 阴线: ma_Rates[class="num">0].open > ma_Rates[class="num">0].high - d_20_Percents class=class="str">"cmt">// 柱线开盘价位于上部 class="num">20% 之内 && ma_Rates[class="num">0].close < ma_Rates[class="num">0].low + d_20_Percents class=class="str">"cmt">// 收盘价在柱线下部 class="num">20% 之内 ) || ( class=class="str">"cmt">// 阳线: ma_Rates[class="num">0].close > ma_Rates[class="num">0].high - d_20_Percents class=class="str">"cmt">// 柱线收盘价在上部 class="num">20% 之内 && ma_Rates[class="num">0].open < ma_Rates[class="num">0].low + d_20_Percents class=class="str">"cmt">// 开盘价在下部 class="num">20% 之内 ) ) { class=class="str">"cmt">// 首根柱线对应条件 class=class="str">"cmt">// 为形态的首根柱线定义当日交易方向: se_Possible_Signal = ma_Rates[class="num">0].open > ma_Rates[class="num">0].close ? ENTRY_BUY : ENTRY_SELL; class=class="str">"cmt">// 入场价位: sd_Entry_Level = d_Entry_Level = se_Possible_Signal == ENTRY_BUY ? ma_Rates[class="num">0].low : ma_Rates[class="num">0].high; class=class="str">"cmt">// 形态首根柱线的范围边界: sd_Range_High = d_Range_High = ma_Rates[class="num">0].high; sd_Range_Low = d_Range_Low = ma_Rates[class="num">0].low; } else { class=class="str">"cmt">// 首根柱线的开盘价/收盘价与条件不匹配 se_Possible_Signal = ENTRY_NONE; class=class="str">"cmt">// 意即当日无信号 class="kw">return (se_Possible_Signal); } } class="type">class="kw">double fd_Average_Bar_Range( class=class="str">"cmt">// 计算平局柱线大小 class="type">int i_Bars_Limit, class=class="str">"cmt">// 要考虑的柱线数量 ENUM_TIMEFRAMES e_TF = PERIOD_CURRENT, class=class="str">"cmt">// 柱线时间帧 class="type">class="kw">datetime t_Time = WRONG_VALUE class=class="str">"cmt">// 当开始计算 ) { class="type">class="kw">double d_Average_Range = class="num">0; class=class="str">"cmt">// 数值汇总的变量 if (i_Bars_Limit < class="num">1) class="kw">return (d_Average_Range); class="type">MqlRates ma_Rates[]; class=class="str">"cmt">// 柱线信息数组 class=class="str">"cmt">// 从指定历史间隔获取柱线信息: if (t_Time == WRONG_VALUE) t_Time = TimeCurrent(); class="type">int i_Price_Bars = CopyRates(_Symbol, e_TF, t_Time, i_Bars_Limit, ma_Rates); if (i_Price_Bars == WRONG_VALUE) { class=class="str">"cmt">// 处理 CopyRates 函数错误 if (Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyRates: error #%u", __FUNCTION__, _LastError); class="kw">return (d_Average_Range); } if (i_Price_Bars < i_Bars_Limit) { class=class="str">"cmt">// CopyRates 函数未能获取请求的数据量 if (Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyRates: 拷贝 %u 根柱线 %u", __FUNCTION__, i_Price_Bars, i_Bars_Limit); } class=class="str">"cmt">// 范围之和: class="type">int i_Bar = i_Price_Bars; class="kw">while (i_Bar-- > class="num">0) d_Average_Range += ma_Rates[i_Bar].high - ma_Rates[i_Bar].low; class=class="str">"cmt">// 均值: class="kw">return (d_Average_Range / class="type">class="kw">double(i_Price_Bars)); } class=class="str">"cmt">// 检查在 D1 时间帧内形态的第二根 (当前) 柱线: if (se_Possible_Signal == ENTRY_BUY) { sd_SL = d_SL = ma_Rates[class="num">1].low; class=class="str">"cmt">// 止损 — 当日最高价 if (TS_8020_Take_Profit_Ratio > class="num">0) sd_TP = d_TP = d_Entry_Level + _Point * TS_8020_Extremum_Break * TS_8020_Take_Profit_Ratio; class=class="str">"cmt">// 止盈 class="kw">return ( class=class="str">"cmt">// 是否向下突破清晰可见? ma_Rates[class="num">1].close < ma_Rates[class="num">0].low - _Point * TS_8020_Extremum_Break ? ENTRY_BUY : ENTRY_NONE ); } if (se_Possible_Signal == ENTRY_SELL) { sd_SL = d_SL = ma_Rates[class="num">1].high; class=class="str">"cmt">// 止损 — 当日最低价 if (TS_8020_Take_Profit_Ratio > class="num">0) sd_TP = d_TP = d_Entry_Level - _Point * TS_8020_Extremum_Break * TS_8020_Take_Profit_Ratio; class=class="str">"cmt">// 止盈 class="kw">return ( class=class="str">"cmt">// 是否向上突破清晰可见? ma_Rates[class="num">1].close > ma_Rates[class="num">0].high + _Point * TS_8020_Extremum_Break ? ENTRY_SELL : ENTRY_NONE ); }