威廉·甘恩(William Gann)方法(第一部分):创建甘恩角度指标·综合运用
📘

威廉·甘恩(William Gann)方法(第一部分):创建甘恩角度指标·综合运用

第 3/3 篇

「反向甘氏线的端点推算与重绘钩子」

画反向甘氏线时,端点价不是简单镜像,而是用角度正切乘 300 倍点值再叠加极值。代码中 priceShift = MathTan(angle * M_PI / 180.0) * 300 * _Point,把角度转弧度后乘 300 个点的跨度,这是 MT5 里手绘 Gann Fan 反向射线的常见尺度。 若 isTrendUp 为假(即主趋势向下但画反向线),endPrice = extremum + priceShift;否则走 else 分支,endPrice = extremum - priceShift 且把 angle 取负,保证 tooltip 显示的角度名与几何方向一致。 ObjectCreate 建 OBJ_TREND 后立刻设色、线型、宽度 1、RAY_RIGHT 为真,让射线向右无限延伸;失败则 Print 出 GetLastError() 码,方便在 MT5 专家日志里定位。 图表变动时 OnChartEvent 捕获 CHARTEVENT_CHART_CHANGE,重取 Bars(_Symbol, PERIOD_CURRENT) 总量并 CopyHigh 到降序数组,为下一轮极值重算做准备。外汇与贵金属杠杆高,射线仅作概率参考,实盘请先开 MT5 用脚本验证角度与 300 点系数是否匹配你的品种波动。

MQL5 / C++
class="type">class="kw">double priceShift = MathTan(angle * M_PI / class="num">180.0) * class="num">300 * _Point;
class="type">class="kw">double endPrice;

if(!isTrendUp) class=class="str">"cmt">// Opposite trend
{
   endPrice = extremum + priceShift;
}
else
{
   endPrice = extremum - priceShift;
   angle = -angle; class=class="str">"cmt">// Invert the angle for a downtrend
}
if(ObjectCreate(class="num">0, label, OBJ_TREND, class="num">0, extremumTime, extremum, endTime, endPrice))
{
   ObjectSetInteger(class="num">0, label, OBJPROP_COLOR, GannFanColor);
   ObjectSetInteger(class="num">0, label, OBJPROP_STYLE, STYLE_SOLID);
   ObjectSetInteger(class="num">0, label, OBJPROP_WIDTH, class="num">1);
   ObjectSetInteger(class="num">0, label, OBJPROP_RAY_RIGHT, true);
   ObjectSetString(class="num">0, label, OBJPROP_TOOLTIP, "Opposite Gann Fan " + angleNames[i]);
}
else
{
   Print("Failed to create Opposite Gann Fan line: ", GetLastError());
}
}
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)
{
   class=class="str">"cmt">// Redraw objects when chart changes
   if(id == CHARTEVENT_CHART_CHANGE)
   {
      class=class="str">"cmt">// Find extremums and redraw Gann Fans
      class="type">int rates_total = Bars(_Symbol, PERIOD_CURRENT);
      class="type">class="kw">double high[], low[];
      class="type">class="kw">datetime time[];
      ArraySetAsSeries(high, true);
      ArraySetAsSeries(low, true);
      ArraySetAsSeries(time, true);
      CopyHigh(_Symbol, PERIOD_CURRENT, class="num">0, rates_total, high);

◍ 极值定位后如何落笔甘氏扇

这段调用链出现在指标 OnCalculate 的尾部,先通过 CopyLow 与 CopyTime 把当前图表(_Symbol、PERIOD_CURRENT)从 0 到 rates_total 的根数全部拉进数组,rates_total 即 Bars 总数,在 EURUSD H1 常态下常落在 5000~20000 之间。 拿到完整 low 与 time 序列后,FindExtremums 负责扫描波峰波谷并写出 extremumPrice / extremumTime 以及反向极点,随后 DrawGannFan 与 DrawOppositeGannFan 各自以极点价格为起点画正向与反向扇。外汇与贵金属杠杆高、滑点大,极点识别只是概率性参考,实盘须自行验证。 想确认你的环境是否一致,直接把这段粘进 MT5 自定义指标尾部,编译后切到 XAUUSD M15 看扇线是否随新极点重绘即可。

MQL5 / C++
  CopyLow(_Symbol, PERIOD_CURRENT, class="num">0, rates_total, low);
  CopyTime(_Symbol, PERIOD_CURRENT, class="num">0, rates_total, time);
  
  FindExtremums(rates_total, high, low, time);
  DrawGannFan(extremumPrice, extremumTime);
  DrawOppositeGannFan(oppositeExtremumPrice, oppositeExtremumTime);
}

甘恩扇形指标的五个函数怎么分工

这套 MT5 指标专门在图表上画甘恩扇形角度,核心逻辑拆成五个自定义函数加三个事件钩子。OnInit 只做初始化返回成功,OnDeinit 在指标被移除时清掉所有自建对象,避免残留图形。 OnCalculate 是主循环,每个 tick 触发一次:先清旧对象,再在设定的日期区间里找极值,最后重绘扇形。FindExtremums 负责在区间内抓最高点和最低点,并判断哪个极值离当前时间更近,以此定趋势方向。 DrawGannFan 从一个极值点拉出九条线,角度分别是 82.5°、75°、71.25°、63.75°、45°、26.25°、18.75°、15° 和 7.5°;DrawOppositeGannFan 从另一头画反向的同样九条线。OnChartEvent 监听图表变动,变动即重绘,所以拖动时间轴也不会跑偏。 输入参数只有 StartDate、EndDate 和 GannFanColor 三个,分别管分析区间和线色。极值查找直接用 ArrayMaximum 和 ArrayMinimum,数据处理效率不赖。外汇和贵金属波动大、跳空频繁,这类动态重绘画线工具仅作结构参考,实盘仍需自行验证角度压制是否有效。

「甘恩扇形是怎么在图上画出来的」

甘恩角度不是随便拉一根趋势线,而是从极值点做几何构造。指标先在给定周期里抓最高价和最低价,较晚出现的那个极值点当主扇形起点,较早的用于反方向扇形,这样两侧支撑阻力都能铺开。 角度集合是固定的九档:82.5°、75°、71.25°、63.75°、45°、26.25°、18.75°、15°、7.5°,分别对应 1x8 到 8x1 的甘恩比例。45° 即 1x1,是中性趋势参考线,其余按倍数偏离。 终点公式里 300 是向前延伸的 K 线数量常量。下降趋势时角度符号反转,线条从极值点往下走;每个角度生成 OBJ_TREND 对象并向右延伸覆盖未来数据。 动态更新是这套算法的实用点:新 tick 或新 K 线进来,极值点重算、扇形重建,分析不会停在旧位置。外汇与贵金属波动剧烈,甘恩线仅作概率参考,实际击穿风险高。 [CODE] 里的终点价格计算,逐行看:极值点取自主/次极值;MathTan(角度×M_PI/180.0) 把角度转弧度再取正切,得到每根 K 线的价格斜率;乘 300 是延伸 300 根 K 线的总偏移;最后乘 Point 把斜率换算成当前品种的最小报价单位。复制进 MT5 脚本改 300 这个值,就能控制扇形长短。

MQL5 / C++
终点价格 = 极值点 + MathTan(角度 × M_PI/class="num">180.0) × class="num">300× Point

◍ 甘恩角度能赚钱吗

用甘恩角度做盈利交易,没有标准答案,但方法对了确实有可能。关键不在单看一条角度线,而是把它和价格结构、时间窗口综合起来用,孤立使用胜率会明显打折。 外汇与贵金属杠杆高、波动突兀,哪怕分析再细也不能忘了风险管理。没有任何工具能保 100% 命中,合理仓位和止损才是长期活下来的基石。 小布盯盘里跑甘恩角度时,建议先固定 1:1 基准线,再叠加波动率过滤,把假突破剔除后再看信号,这样样本里的噪音会少一截。

把甘恩角度交给EA跑起来

想在MT5里做一套基于甘恩角度的EA,最省力的起点是直接复用已实现的甘恩角度指标,而不是从零画射线。EA逻辑可以定为:扫描价格与甘恩线的接触和交叉,把交叉点当成候选入场/出场,再叠加成交量与波动率过滤,避免在主趋势外的毛刺上成交。 这类系统的硬价值在于剔除了恐惧和贪婪对下单的干扰——策略写成代码后,信号来了就执行,不会临场手软或加仓上头。对外汇和贵金属这种高杠杆品种,机械化执行本身就能降低人为爆仓的概率。 但甘恩理论先天带主观性,角度线的周期和起点选法人人不同,很难完整翻译成确定规则。EA成败基本卡在开发者对甘恩原意吃透了几成,以及能不能把这套老方法与当前市场波动特征、仓位风控绑在一起,而不是单纯依赖线叉信号。

「甘恩角度在MT5里最头疼的比例失真」

把甘恩角度写进指标或EA,最致命的不是算不准角度,而是图表比例尺一变,几何意义就崩了。甘恩的1x1线本质是一条45度对角线,前提是「时间单位=价格单位」的方格坐标;可MT5里你拉大价格轴、压缩时间轴,原本完美的45度视觉上可能歪成30度甚至60度,交易信号直接错位。 实际写代码时,平台不会替你锁定比例。iChartGetInteger(0,CHART_SCALE)能拿到当前缩放档位,但价格每跳多少像素、时间每根K多少像素,都得自己用可见区域算。一套在EURUSD日线调好的角度算法,切到XAUUSD一小时图,因点值和点距完全不同,线就飘了——外汇和贵金属杠杆高、波动结构差异大,这种跨品种迁移失败率很高。 常见妥协有两种:要么写死比例,工具只能绑死一个品种一个周期;要么每帧重算角度,CPU占用往上飙,EA tick级响应变卡。想验证很简单,开MT5挂个自画甘恩线的指标,手动滚轮缩放图表,看线是否还压在原来价格轨迹上。

◍ 甘恩角度自动化落地的真实门槛

在 MT5 里手搓甘恩角度指标并不复杂,给出的 GannAngleIndicator.mq5(16.11 KB)已经能直接编译跑起来,当作进一步开发的底座没问题。 真正卡住自动化交易的不是画线本身,而是坐标缩放:不同品种的点值、不同周期的价格波动幅度差异,会让同一套角度线在 EURUSD 和 XAUUSD 上判定的突破意义完全错位,通用 EA 很难靠固定参数稳住。 外汇与贵金属自带高杠杆高风险,甘恩角度若只当孤立信号用,胜率倾向随品种漂移;把它和均线结构、ATR 止损叠在一起,才更可能成为工具箱里有效的补充而不是噪音源。 有读者用通达信代码反推,指出原 MQL5 里 priceShift 的斜率算法不如「周期数作分母、极值高度作分子」的范式严谨,下方片段即其修正思路的参考实现,开 MT5 对照改写即可验证画线偏差。

MQL5 / C++
涨周期数:= IF(低点k位置 > 高点k位置, 低点k位置 - 高点k位置 + class="num">1, 低点k位置), NODRAW;
跌周期数:= IF(高点k位置 > 低点k位置, 高点k位置 - 低点k位置 + class="num">1, 高点k位置), NODRAW;
天线:= CONST(IF(高点k位置 = class="num">1, H, REF(H, 高点k位置 - class="num">1))), NODRAW;
地线:= CONST(IF(低点k位置 = class="num">1, L, REF(L, 低点k位置 - class="num">1))), NODRAW;
上涨天数:= IF(ISVALID(地线), BARSLAST(L = 地线), DRAWNULL), NODRAW;
下跌天数:= IF(ISVALID(天线), BARSLAST(H = 天线), DRAWNULL), NODRAW;
上涨高度:= IF(低点k位置 > 高点k位置, 天线 - 地线, HHV(H, 上涨天数) - 地线), NODRAW;
下跌高度:= IF(高点k位置 > 低点k位置, 天线 - 地线, 天线 - LLV(L, 下跌天数)), NODRAW;
上涨斜率:= IF(上涨天数 > class="num">0, ROUND2(上涨高度 / 涨周期数, class="num">4), class="num">0), NODRAW;
下跌斜率:= IF(下跌天数 > class="num">0, ROUND2(下跌高度 / 跌周期数, class="num">4), class="num">0), NODRAW;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 绘制江恩扇形线                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawGannFan(class="type">class="kw">double extremum, class="type">class="kw">datetime extremumTime)
{
    class=class="str">"cmt">// 定义江恩角度和对应的名称
    class="type">class="kw">double angles[] = {class="num">82.5, class="num">75, class="num">71.25, class="num">63.75, class="num">45, class="num">26.25, class="num">18.75, class="num">15, class="num">7.5};
    class="type">class="kw">string angleNames[] = {"1x8", "1x4", "1x3", "1x2", "1x1", "2x1", "3x1", "4x1", "8x1"};
    class=class="str">"cmt">// 获取极值点到当前K线的周期数
    class="type">int extremumBar = iBarShift(_Symbol, PERIOD_CURRENT, extremumTime);
    class="type">int currentBar = class="num">0; class=class="str">"cmt">// 当前K线是第0根
    class="type">int barDiff = currentBar - extremumBar; class=class="str">"cmt">// 周期数差
    
    class=class="str">"cmt">// 计算价格高度差
    class="type">class="kw">double priceDiff;
    if(isTrendUp)

江恩扇形线的斜率与绘制落点

这段逻辑解决的是江恩扇形里每条射线的实际坡度怎么算。基础斜率 baseSlope 用价格差 priceDiff 除以柱数差 barDiff 得到,若 barDiff 为 0 则强制斜率为 0,避免除零崩溃。 角度倍数 slopeMultiplier 拿目标角度的正切值去比 45 度正切(MathTan(45*M_PI/180.0) 恒为 1),所以 1x1 线倍数就是 1,其他角度如 2x1 对应约 26.565 度、倍数约 0.5。价格偏移 priceShift 再乘上 _Point,把点数换算成报价单位,确保黄金和欧美的点值差异不会画歪。 上升趋势里结束价 endPrice = extremum + priceShift,下降则 extremum - priceShift 且 angle 取负,让射线自然朝右下延展。ObjectCreate 用 OBJ_TREND 从极值时间拉到 TimeCurrent(),开右射线;若返回失败直接 Print(GetLastError()),在 MT5 终端能立刻看到具体错误码,方便排查对象重名或权限问题。外汇与贵金属波动剧烈,扇形仅作概率参考,实际击穿倾向需结合实时成交量判断。

MQL5 / C++
{
      class=class="str">"cmt">// 上升趋势:计算从极值低点到当前最高价的高度
      class="type">class="kw">double currentHigh = iHigh(_Symbol, PERIOD_CURRENT, class="num">0);
      priceDiff = currentHigh - extremum;
}
else
{
      class=class="str">"cmt">// 下降趋势:计算从极值高点到当前最低价的高度
      class="type">class="kw">double currentLow = iLow(_Symbol, PERIOD_CURRENT, class="num">0);
      priceDiff = extremum - currentLow;
}

class=class="str">"cmt">// 计算基础斜率(1x1线的斜率)
class="type">class="kw">double baseSlope = (barDiff > class="num">0) ? priceDiff / barDiff : class="num">0;

class=class="str">"cmt">// 为每个角度绘制扇形线
for(class="type">int i = class="num">0; i < ArraySize(angles); i++)
{
      class="type">class="kw">string label = "GannFan_" + angleNames[i];   class=class="str">"cmt">// 对象标签
      class="type">class="kw">double angle = angles[i];

      class=class="str">"cmt">// 根据角度计算斜率倍数
      class="type">class="kw">double slopeMultiplier = MathTan(angle * M_PI / class="num">180.0) / MathTan(class="num">45 * M_PI / class="num">180.0);

      class=class="str">"cmt">// 计算价格偏移量(基于基础斜率和角度倍数)
      class="type">class="kw">double priceShift = baseSlope * barDiff * slopeMultiplier * _Point;
      class="type">class="kw">double endPrice;

      class=class="str">"cmt">// 根据趋势方向计算结束价格
      if(isTrendUp)
      {
            endPrice = extremum + priceShift;
      }
      else
      {
            endPrice = extremum - priceShift;
            angle = -angle; class=class="str">"cmt">// 下降趋势时反转角度
      }
      class=class="str">"cmt">// 创建趋势线对象
      if(ObjectCreate(class="num">0, label, OBJ_TREND, class="num">0, extremumTime, extremum, TimeCurrent(), endPrice))
      {
            class=class="str">"cmt">// 设置趋势线属性
            ObjectSetInteger(class="num">0, label, OBJPROP_COLOR, GannFanColor);
            ObjectSetInteger(class="num">0, label, OBJPROP_STYLE, STYLE_SOLID);
            ObjectSetInteger(class="num">0, label, OBJPROP_WIDTH, class="num">1);
            ObjectSetInteger(class="num">0, label, OBJPROP_RAY_RIGHT, true);
            ObjectSetString(class="num">0, label, OBJPROP_TOOLTIP, "Gann Fan " + angleNames[i]);
      }
      else
      {
            Print("Failed to create Gann Fan line: ", GetLastError());
      }

「反向江恩扇形的斜率换算」

画反向江恩扇形,核心是把极值点到当前 K 线的「周期差」和「价格差」先算出来,再按角度表换算每条射线的斜率。代码里 angles 数组写死了 9 个角度:82.5、75、71.25、63.75、45、26.25、18.75、15、7.5,对应 1x8 到 8x1 的名称,45 度就是基准的 1x1 线。 基础斜率 baseSlope 用 priceDiff / barDiff 得出,若 barDiff 为 0 则强制为 0 避免除零。每条线的斜率倍数靠 MathTan(角度) / MathTan(45) 算,再乘上 _Point 缩到报价精度,最后按趋势方向加减到极值上得到 endPrice。 注意 isTrendUp 标志决定反向:原趋势向下时反向为上升,用极值低点加 priceShift;原趋势向上时反向为下降,用极值高点减 priceShift。外汇和贵金属波动大,这种扇形仅作概率参考,实盘前务必在 MT5 用历史品种跑一遍验证角度贴合度。

MQL5 / C++
class="type">void DrawOppositeGannFan(class="type">class="kw">double extremum, class="type">class="kw">datetime extremumTime)
{
   class=class="str">"cmt">// 定义江恩角度和对应的名称
   class="type">class="kw">double angles[] = {class="num">82.5, class="num">75, class="num">71.25, class="num">63.75, class="num">45, class="num">26.25, class="num">18.75, class="num">15, class="num">7.5};
   class="type">class="kw">string angleNames[] = {"1x8", "1x4", "1x3", "1x2", "1x1", "2x1", "3x1", "4x1", "8x1"};
   class=class="str">"cmt">// 获取极值点到当前K线的周期数
   class="type">int extremumBar = iBarShift(_Symbol, PERIOD_CURRENT, extremumTime);
   class="type">int currentBar = class="num">0; class=class="str">"cmt">// 当前K线是第0根
   class="type">int barDiff = currentBar - extremumBar; class=class="str">"cmt">// 周期数差
   
   class=class="str">"cmt">// 计算价格高度差
   class="type">class="kw">double priceDiff;
   if(!isTrendUp)
   {
      class=class="str">"cmt">// 反向趋势(上升):计算从极值低点到当前最高价的高度
      class="type">class="kw">double currentHigh = iHigh(_Symbol, PERIOD_CURRENT, class="num">0);
      priceDiff = currentHigh - extremum;
   }
   else
   {
      class=class="str">"cmt">// 反向趋势(下降):计算从极值高点到当前最低价的高度
      class="type">class="kw">double currentLow = iLow(_Symbol, PERIOD_CURRENT, class="num">0);
      priceDiff = extremum - currentLow;
   }
   
   class=class="str">"cmt">// 计算基础斜率(1x1线的斜率)
   class="type">class="kw">double baseSlope = (barDiff > class="num">0) ? priceDiff / barDiff : class="num">0;
   
   class=class="str">"cmt">// 为每个角度绘制扇形线
   for(class="type">int i = class="num">0; i < ArraySize(angles); i++)
   {
      class="type">class="kw">string label = "OppositeGannFan_" + angleNames[i];  class=class="str">"cmt">// 对象标签
      class="type">class="kw">double angle = angles[i];
      
      class=class="str">"cmt">// 根据角度计算斜率倍数
      class="type">class="kw">double slopeMultiplier = MathTan(angle * M_PI / class="num">180.0) / MathTan(class="num">45 * M_PI / class="num">180.0);
      
      class=class="str">"cmt">// 计算价格偏移量(基于基础斜率和角度倍数)
      class="type">class="kw">double priceShift = baseSlope * barDiff * slopeMultiplier * _Point;
      class="type">class="kw">double endPrice;
      
      class=class="str">"cmt">// 根据反向趋势方向计算结束价格
      if(!isTrendUp)
      {
         endPrice = extremum + priceShift;
      }
      else
      {
         endPrice = extremum - priceShift;

◍ 反向江恩线的绘制与对象清理

在趋势判定后,若处于下降走势,代码会把角度取反(angle = -angle),让扇形线朝另一侧延伸。随后用 ObjectCreate 以 OBJ_TREND 类型在极值点与时间轴当前点之间拉出趋势线,并开启向右射线(OBJPROP_RAY_RIGHT=true),这样线会一直画到图表最右端。 创建成功后,依次设置颜色、实线样式、宽度为1,并在 tooltip 里标注 'Opposite Gann Fan ' 加角度名,方便鼠标悬停识别。若 ObjectCreate 返回失败,则用 Print 输出 'Failed to create Opposite Gann Fan line: ' 加 GetLastError() 错误码,便于在 MT5 Experts 日志里定位。 指标头部用 #property indicator_chart_window 声明在主图运行,输入参数 LookBackBars=300 控制回溯K线数,GannFanColor 默认 clrBlue。全局变量里 extremumTime/extremumPrice 存极点,opposite 开头的一对存反向极点,isTrendUp 标记方向。 反初始化时 OnDeinit 调用 ObjectsDeleteAll(0,"GannFan_"),把前缀为 GannFan_ 的所有对象一次清掉。外汇与贵金属波动剧烈、杠杆高风险大,这类自动画线仅作结构参考,信号失效概率不低,实盘前请在策略测试器用历史数据验证。

MQL5 / C++
angle = -angle; class=class="str">"cmt">// 下降趋势时反转角度
}
class=class="str">"cmt">// 创建趋势线对象
if(ObjectCreate(class="num">0, label, OBJ_TREND, class="num">0, extremumTime, extremum, TimeCurrent(), endPrice))
{
class=class="str">"cmt">// 设置趋势线属性
ObjectSetInteger(class="num">0, label, OBJPROP_COLOR, GannFanColor);
ObjectSetInteger(class="num">0, label, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(class="num">0, label, OBJPROP_WIDTH, class="num">1);
ObjectSetInteger(class="num">0, label, OBJPROP_RAY_RIGHT, true);
ObjectSetString(class="num">0, label, OBJPROP_TOOLTIP, "Opposite Gann Fan " + angleNames[i]);
}
else
{
Print("Failed to create Opposite Gann Fan line: ", GetLastError());
}
}
}
class="macro">#class="kw">property copyright "Copyright class="num">2024, Evgeniy Shtenco" class=class="str">"cmt">// 版权信息
class="macro">#class="kw">property link "[MQL5官方文档] class=class="str">"cmt">// 作者链接
class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">// 指标版本号
class="macro">#class="kw">property indicator_chart_window class=class="str">"cmt">// 指标显示在图表窗口
class=class="str">"cmt">// 输入参数
input class="type">int LookBackBars = class="num">300; class=class="str">"cmt">// 分析回溯的K线数量
input class="type">class="kw">color GannFanColor = clrBlue; class=class="str">"cmt">// 江恩扇形线颜色
class=class="str">"cmt">// 全局变量
class="type">class="kw">double extremumPrice; class=class="str">"cmt">// 极值点价格
class="type">class="kw">datetime extremumTime; class=class="str">"cmt">// 极值点时间
class="type">class="kw">double oppositeExtremumPrice; class=class="str">"cmt">// 反向极值点价格
class="type">class="kw">datetime oppositeExtremumTime; class=class="str">"cmt">// 反向极值点时间
class="type">bool isTrendUp; class=class="str">"cmt">// 趋势方向标志(true为上升趋势)
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标初始化函数 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
class="kw">return (INIT_SUCCEEDED); class=class="str">"cmt">// 初始化成功
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标反初始化函数 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
{
class=class="str">"cmt">// 删除所有江恩扇形线对象
ObjectsDeleteAll(class="num">0, "GannFan_");

指标主循环里的极值捕捉与扇形重绘

江恩扇形指标的核心计算落在 OnCalculate 里。函数先用 rates_total < LookBackBars 做护栏,数据不足 300 根就直接 return(0),避免数组越界和误画。 首次加载时(prev_calculated == 0)会调两次 ObjectsDeleteAll,分别清掉前缀为 GannFan_ 和 OppositeGannFan_ 的旧图形对象,防止历史残留叠在当前图上。 随后在最近 300 根 K 线窗口内跑 FindExtremums,用 ArrayMaximum(high, startIndex, LookBackBars) 定位最高价索引,再交给 DrawGannFan 与 DrawOppositeGannFan 以极值点和对应时间为锚画双向扇形。外汇与贵金属波动剧烈,这种重绘逻辑在跳空时可能偏移,开 MT5 把 LookBackBars 从 300 改成 150 可观察扇形密度变化。

MQL5 / C++
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
                  class="kw">const class="type">int prev_calculated,
                  class="kw">const class="type">class="kw">datetime & time[],
                  class="kw">const class="type">class="kw">double & open[],
                  class="kw">const class="type">class="kw">double & high[],
                  class="kw">const class="type">class="kw">double & low[],
                  class="kw">const class="type">class="kw">double & close[],
                  class="kw">const class="type">long & tick_volume[],
                  class="kw">const class="type">long & volume[],
                  class="kw">const class="type">int & spread[])
{
   if(rates_total < LookBackBars) class="kw">return(class="num">0);
   if(prev_calculated == class="num">0) {
      ObjectsDeleteAll(class="num">0, "GannFan_");
      ObjectsDeleteAll(class="num">0, "OppositeGannFan_");
   }
   FindExtremums(rates_total, high, low, time);
   DrawGannFan(extremumPrice, extremumTime);
   DrawOppositeGannFan(oppositeExtremumPrice, oppositeExtremumTime);
   class="kw">return(rates_total);
}

class="type">void FindExtremums(class="kw">const class="type">int rates_total, class="kw">const class="type">class="kw">double & high[], class="kw">const class="type">class="kw">double & low[], class="kw">const class="type">class="kw">datetime & time[])
{
   class="type">int startIndex = rates_total - LookBackBars;
   class="type">int endIndex = rates_total - class="num">1;
   class="type">int highestIndex = ArrayMaximum(high, startIndex, LookBackBars);

「用极值时序判定趋势再铺江恩扇」

判定一段回看窗口内的趋势方向,核心不是比高低点价格,而是比它们发生的时间。代码里先用 ArrayMinimum 在 low 数组里捞到最低价索引,再用 time[highestIndex] 与 time[lowestIndex] 做先后比较:最高点时间靠后,倾向定义为下降趋势,反之则为上升。 趋势方向定了,才轮到画扇。DrawGannFan 接收极值价格和极值时间,内置 9 个江恩角度:82.5°(1x8)、75°(1x4)、71.25°(1x3)、63.75°(1x2)、45°(1x1)、26.25°(2x1)、18.75°(3x1)、15°(4x1)、7.5°(8x1)。结束时间直接取 TimeCurrent(),也就是实时下笔。 价格偏移量靠正切算:priceShift = tan(角度×π/180) × (endTime−extremumTime) / PeriodSeconds()。上升时 endPrice = 极值 + 偏移,下降时极值 − 偏移且角度取负。OBJ_TREND 对象以极值时空为起点、当前时空为终点拉出,外汇与贵金属波动剧烈,扇面仅作概率参考,实盘须自担高风险。 开 MT5 把 angles 数组改成你惯用的子集,比如只留 45° 和 1x2,能明显减轻图表杂乱,也方便肉眼核验价格触碰 1x1 后的停顿倾向。

MQL5 / C++
class="type">int lowestIndex = ArrayMinimum(low, startIndex, LookBackBars);
class=class="str">"cmt">// 确定趋势方向(比较最高点和最低点的时间)
if (time[highestIndex] > time[lowestIndex]) {
  class=class="str">"cmt">// 如果最高点在最低点之后,则为下降趋势
  extremumPrice = high[highestIndex];
  extremumTime = time[highestIndex];
  oppositeExtremumPrice = low[lowestIndex];
  oppositeExtremumTime = time[lowestIndex];
  isTrendUp = false;
}
else {
  class=class="str">"cmt">// 否则为上升趋势
  extremumPrice = low[lowestIndex];
  extremumTime = time[lowestIndex];
  oppositeExtremumPrice = high[highestIndex];
  oppositeExtremumTime = time[highestIndex];
  isTrendUp = true;
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 绘制江恩扇形线                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawGannFan(class="type">class="kw">double extremum, class="type">class="kw">datetime extremumTime)
{
  class=class="str">"cmt">// 定义江恩角度和对应的名称
  class="type">class="kw">double angles[] = { class="num">82.5, class="num">75, class="num">71.25, class="num">63.75, class="num">45, class="num">26.25, class="num">18.75, class="num">15, class="num">7.5};
  class="type">class="kw">string angleNames[] = { "1x8", "1x4", "1x3", "1x2", "1x1", "2x1", "3x1", "4x1", "8x1"};
  class=class="str">"cmt">// 计算扇形线的结束时间(当前时间+300个周期)
  class="type">class="kw">datetime endTime = TimeCurrent();  class=class="str">"cmt">// 使用当前时间作为结束时间
  class=class="str">"cmt">// 为每个角度绘制扇形线
  for (class="type">int i = class="num">0; i < ArraySize(angles); i++)
  {
    class="type">class="kw">string label = "GannFan_" + angleNames[i];  class=class="str">"cmt">// 对象标签
    class="type">class="kw">double angle = angles[i];                   class=class="str">"cmt">// 当前角度
    class=class="str">"cmt">// 计算价格偏移量(基于时间差)
    class="type">class="kw">double secondsDiff = endTime - extremumTime;
    class="type">class="kw">double priceShift = MathTan(angle * M_PI / class="num">180.0) * secondsDiff / PeriodSeconds();
    class="type">class="kw">double endPrice;
    class=class="str">"cmt">// 根据趋势方向计算结束价格
    if (isTrendUp) {
      endPrice = extremum + priceShift;
    }
    else {
      endPrice = extremum - priceShift;
      angle = -angle; class=class="str">"cmt">// 下降趋势时反转角度
    }
    class=class="str">"cmt">// 创建趋势线对象
    if (ObjectCreate(class="num">0, label, OBJ_TREND, class="num">0, extremumTime, extremum, endTime, endPrice)) {
      class=class="str">"cmt">// 设置趋势线属性
      ObjectSetInteger(class="num">0, label, OBJPROP_COLOR, GannFanColor);

◍ 反向江恩扇形的绘制逻辑

在 MT5 里画反向江恩扇形,核心是先定义 9 条角度线:82.5°、75°、71.25°、63.75°、45°、26.25°、18.75°、15°、7.5°,分别对应 1x8 到 8x1 的名称。这些角度不是随便写的,是江恩体系中价格与时间比值的常见划分,45° 的 1x1 线通常被视为多空均衡参考。 代码用 TimeCurrent() 取当前时间作为扇尾,再拿 endTime - extremumTime 的秒差除以 PeriodSeconds() 得到 K 线根数差,乘上 MathTan(angle*M_PI/180) 算出价格偏移。注意这里把角度转弧度再取正切,直接把「时间跨度」映射成「价格跨度」,外汇和贵金属波动大,这种映射在小时图以上周期才不容易被点差吃掉。 方向判断靠外部变量 isTrendUp:若原趋势不是向上,反向扇形就从极值加 priceShift 向右上延展;若原趋势向上,则 endPrice = extremum - priceShift 且 angle 取负,线往右下走。对象名拼成 OppositeGannFan_1x1 这种,方便后续用 ObjectsDeleteAll 按前缀清理。 创建用 ObjectCreate 画 OBJ_TREND,成功后才配色。失败就 Print 出 GetLastError 码,常见 4200(对象已存在)或 4201(参数非法),开 MT5 跑一遍改个 GannFanColor 就能直观看到反向扇压回原趋势的概率分布。

MQL5 / C++
class="type">void DrawOppositeGannFan(class="type">class="kw">double extremum, class="type">class="kw">datetime extremumTime)
{
   class="type">class="kw">double angles[] = { class="num">82.5, class="num">75, class="num">71.25, class="num">63.75, class="num">45, class="num">26.25, class="num">18.75, class="num">15, class="num">7.5};
   class="type">class="kw">string angleNames[] = { "1x8", "1x4", "1x3", "1x2", "1x1", "2x1", "3x1", "4x1", "8x1"};
   class="type">class="kw">datetime endTime = TimeCurrent();
   for (class="type">int i = class="num">0; i < ArraySize(angles); i++)
   {
      class="type">class="kw">string label = "OppositeGannFan_" + angleNames[i];
      class="type">class="kw">double angle = angles[i];
      class="type">class="kw">double secondsDiff = endTime - extremumTime;
      class="type">class="kw">double priceShift = MathTan(angle * M_PI / class="num">180.0) * secondsDiff / PeriodSeconds();
      class="type">class="kw">double endPrice;
      if (!isTrendUp)
      {
         endPrice = extremum + priceShift;
      }
      else {
         endPrice = extremum - priceShift;
         angle = -angle;
      }
      if (ObjectCreate(class="num">0, label, OBJ_TREND, class="num">0, extremumTime, extremum, endTime, endPrice)) {
         ObjectSetInteger(class="num">0, label, OBJPROP_COLOR, GannFanColor);

最后说句实在话

上面这段 OnChartEvent 是整套反向江恩扇形脚本的收口:图表被拖动、缩放或鼠标点击时,CHARTEVENT_CHART_CHANGE 与 CHARTEVENT_CLICK 会触发重算,Bars(_Symbol, PERIOD_CURRENT) 拿到的 K 线总数可能随品种切换从几百跳到几万,极值重搜和两侧扇形重绘随之全自动跑一遍。 实盘里黄金和欧美这类高杠杆品种波动极快,图表事件频繁触发意味着对象反复销毁重建,MT5 终端对象句柄占用可能瞬时堆到 400+ 个,老机器容易卡顿甚至丢事件。 把这套逻辑接进「小布盯盘」的 AIGC 标注流,让模型只判扇形突破概率而非手动拉线,你大概率能省掉一半盯屏时间——但外汇贵金属自带高风险,任何扇形信号都只是倾向,不是确定性出口。

MQL5 / C++
ObjectSetInteger(class="num">0, label, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(class="num">0, label, OBJPROP_WIDTH, class="num">1);
ObjectSetInteger(class="num">0, label, OBJPROP_RAY_RIGHT, true);
ObjectSetString(class="num">0, label, OBJPROP_TOOLTIP, "Opposite Gann Fan " + angleNames[i]);
      }
      else {
         Print("Failed to create Opposite Gann Fan line: ", GetLastError());
      }
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 图表事件处理函数                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(class="kw">const class="type">int id,                class=class="str">"cmt">// 事件ID
                  class="kw">const class="type">long& lparam,          class=class="str">"cmt">// 长整型参数
                  class="kw">const class="type">class="kw">double& dparam,        class=class="str">"cmt">// 双精度参数
                  class="kw">const class="type">class="kw">string& sparam)        class=class="str">"cmt">// 字符串参数
{
   class=class="str">"cmt">// 当图表变化时重绘对象
   if(id == CHARTEVENT_CHART_CHANGE || id == CHARTEVENT_CLICK) {
      class=class="str">"cmt">// 重新寻找极值点并绘制江恩扇形线
      class="type">int rates_total = Bars(_Symbol, PERIOD_CURRENT);
      class="type">class="kw">double high[], low[];
      class="type">class="kw">datetime time[];
      ArraySetAsSeries(high, true);
      ArraySetAsSeries(low, true);
      ArraySetAsSeries(time, true);
      CopyHigh(_Symbol, PERIOD_CURRENT, class="num">0, rates_total, high);
      CopyLow(_Symbol, PERIOD_CURRENT, class="num">0, rates_total, low);
      CopyTime(_Symbol, PERIOD_CURRENT, class="num">0, rates_total, time);
      FindExtremums(rates_total, high, low, time);
      DrawGannFan(extremumPrice, extremumTime);
      DrawOppositeGannFan(oppositeExtremumPrice, oppositeExtremumTime);
   }
}

常见问题

以极值点为基准,按 1:1 角度从端点向相反方向延伸;重绘钩子放在新极值出现时触发,避免手动反复拖线。
从低点出发先画 1×1 主线,再按 1×2、2×1 等比例铺开;扇面覆盖后续回踩区才有参考意义。
可以,小布盯盘的 AIGC 已内置甘恩扇诊断,打开对应品种页即可看到自动角度与破线提醒,不用自己手绘。
五个函数分工为极值捕捉、角度计算、线条绘制、重绘控制、标签输出;各管一段,参数不冲突就不会重叠乱画。
外汇贵金属高风险,甘恩角度只提供概率倾斜的支撑阻力区;实盘可能提高过滤效率,但无稳赚结论,需回测验证。