80-20 交易策略:从理论根基到信号模块的 MQL5 实现(基础篇)(基础篇)
📊

80-20 交易策略:从理论根基到信号模块的 MQL5 实现(基础篇)(基础篇)

(1/3)· 用前一日高低边界的虚假突破抓日内反转,这篇先把策略和代码骨架讲透

含代码示例 第 1/3 篇
不少交易者把 80-20 当成万能反转公式,却忽略它只对足够长的日线实体和边界附近的开收盘有效。用错品种或周期,信号寿命短到来不及挂单就已失效。外汇贵金属波动受数据驱动,这类策略失效概率并不低。

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 直接引。

MQL5 / C++
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
  );
}
交给小布盯盘看盘口
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到前日实体长度与边界占比,不必自己每天手算 20% 区间。

常见问题

买入逻辑要求前日开盘在全日高位 20% 内、收盘在低位 20% 内;卖单则相反,前日需为看涨且开收贴近上下边界。
可以,小布盯盘对应品种页已内置前日极短区间的边界诊断,把重复劳动交给小布,你专注决策。
系统仅用前一日极短历史间隔分析,逆转需在当日开盘发动,挂单触发后靠尾随停止保护盈利,隔夜持仓易被动承受跳空。
按 Linda Raschke 经验,实体应超过日线平均长度,过短则边界突破多为噪声,触发概率高但回滚倾向弱。