威廉·甘恩(William Gann)方法(第一部分):创建甘恩角度指标·进阶篇
(2/3)· 接上篇概念铺垫,这一篇把 45 度平衡线变成 MT5 里能跑的自定义指标
用角度线判趋势和卡止损
甘恩角度最直白的用法是从一个显著高低点拉出 1x1 线:价格站在线之上是上升趋势倾向,跌破则转空概率升高。想看趋势强不强,叠 2x1 或 3x1 这类更陡的线,角度越陡通常代表推动力量越集中。 实操里,入场常等价格沿趋势碰线反弹或有效突破。例如多头中价格回踩 1x1 不破再起,往往是一个可验证的试多位置,打开 MT5 的 Gann 工具就能手动复现这种触碰。 止损放置也顺着角度走:多单放在最近角度线下方、空单放在上方,用空间换容错。长线层面换 1x2、1x4 等平角看大方向,避免在噪音里频繁挪仓。外汇与贵金属杠杆高,角度只是概率参考,破线不代表反转必现。
◍ 甘恩角度线的四种实战切入法
甘恩角度线不是画完就完事,它真正有用是在价格碰线那一刻怎么反应。1x1 和 2x1 是最常被当成支撑阻力的两条,价格蹭到后收出反转 K 线再进场,比提前挂单更不容易吃假突破。 突破玩法看量能配合。价格向上捅穿关键角度线且成交量放大,多头机会的概率更高;向下破位则偏空。孤立突破没有量确认,很容易沦为震荡里的噪音。 扇形结构从一点散出多条线,天然把空间切成支撑阻力带。价格在第 3 条和第 5 条线之间反复,往往比单线更能暴露反转区域。 把角度和时间周期叠起来用,是进阶做法。甘恩的重要时间线与角度线交叉点,常出现变盘窗口,这时候入场或离场的胜率倾向优于随意挑的点位。 多周期对照也实用:日线 1x1 定大方向,切到小时图用更陡角度找具体入场。一套下来,你能直接开 MT5 拉 Gann Fan 验证日线方向和小时入场是否冲突。
「MT5里搭甘恩角度指标的关键工序」
在MT5写甘恩角度指标,先得把指标骨架定义清楚:指标名、角度输入参数、要引用的库都在最外层声明,这一步决定后面能不能灵活调角度值。 核心画图逻辑全压在OnCalculate()里。你得先指定角度起点(通常是某个显著高低点),再按甘恩几何算出各条线的末端坐标,最后用OBJ_TREND之类的对象往图表上画线。 坐标算得准不准,直接受图表比例和时间区间影响。同一组角度参数,在1小时图和5分钟图里因坐标轴压缩比不同,视觉斜率会差出明显一截,所以数学映射必须按价格/时间的实际像素比来推。 写完别急着用,挑至少3个交易品种、切4个以上时间框架去跑。外汇和贵金属波动属性差异大,高风险环境下角度错位会直接误导进场,验证线条贴合度是唯一靠谱的debug手段。
甘恩角度指标的骨架怎么搭
在 MT5 里写甘恩角度类指标,先要把分析窗口、输入参数和全局极值变量定清楚。下面这段基础代码把回测区间锁在 2023 全年(StartDate=D'2023.01.01 00:00' 到 EndDate=D'2023.12.31 23:59'),用 clrBlue 画扇线,读者可以直接把起止日改成自己想验证的行情段。 代码里的全局变量 extremumPrice / extremumTime 存趋势端点的价与时间,oppositeExtremumPrice / oppositeExtremumTime 存对侧端点,isTrendUp 标记方向——这几个量是后续画 1×1、2×1 角度线的坐标基准,缺一个都跑不起来。 OnInit 只返回 INIT_SUCCEEDED,真正干活在 OnCalculate;注意 prev_calculated==0 时会先 ObjectsDeleteAll 清掉带 GannFan_ 和 OppositeGannFan_ 前缀的旧对象,避免重复加载时图形叠层。外汇与贵金属波动高,用这类固定时间窗的扇线前,建议先在模拟盘验证角度与实际阻力的吻合度,再上实盘。
class="macro">#class="kw">property copyright "Copyright class="num">2024, Evgeniy Shtenco" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_chart_window class=class="str">"cmt">// Input parameters input class="type">class="kw">datetime StartDate = D&class="macro">#x27;class="num">2023.01.class="num">01 class="num">00:class="num">00&class="macro">#x27;; class=class="str">"cmt">// Start date for analysis input class="type">class="kw">datetime EndDate = D&class="macro">#x27;class="num">2023.12.class="num">31 class="num">23:class="num">59&class="macro">#x27;; class=class="str">"cmt">// End date for analysis input class="type">class="kw">color GannFanColor = clrBlue; class=class="str">"cmt">// Color for Gann Fan lines class=class="str">"cmt">// Global variables class="type">class="kw">double extremumPrice; class="type">class="kw">datetime extremumTime; class="type">class="kw">double oppositeExtremumPrice; class="type">class="kw">datetime oppositeExtremumTime; class="type">bool isTrendUp; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { ObjectsDeleteAll(class="num">0, "GannFan_"); ObjectsDeleteAll(class="num">0, "OppositeGannFan_"); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator iteration function | class=class="str">"cmt">//+------------------------------------------------------------------+ 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 < class="num">1) class="kw">return(class="num">0); class=class="str">"cmt">// Clear previous objects if (prev_calculated == class="num">0) { ObjectsDeleteAll(class="num">0, "GannFan_"); ObjectsDeleteAll(class="num">0, "OppositeGannFan_"); }
◍ 在指定区间抓取极值并落甘氏扇
把甘氏扇画对,第一步不是画线,而是在你给定的时间窗里先把最高、最低两根棒找出来。下面这段逻辑用 StartDate / EndDate 两个外部变量框定范围,从 0 遍历到 rates_total,记下首个 ≥ StartDate 的索引和最后一个 ≤ EndDate 的索引。若 startIndex 或 endIndex 为 -1,或起始晚于结束,直接 Print 报错并 return,避免在空区间里硬算。 拿到合法区间后,ArrayMaximum 取 high 数组在窗口内的最大下标,ArrayMinimum 取 low 数组的最小下标。注意第三个参数是长度:endIndex - startIndex + 1,漏掉 +1 会少算一根棒。随后比较 highestIndex 与 lowestIndex 的时间先后——谁更晚,谁就是“最近极值”,另一头自动成为 opposite 极值,并据此把 isTrendUp 置反(高点更新则趋势向下)。 DrawGannFan 里 angles 数组写死了 9 条角度:82.5、75、71.25、63.75、45、26.25、18.75、15、7.5,对应 1x8 到 8x1 的名称。endTime 用 extremumTime + PeriodSeconds() * 300 推算,也就是从极值时刻往后推 300 根当前周期棒作为扇骨终点。外汇与贵金属波动跳空频繁,这套固定 300 棒跨度在 H1 以上周期可能跨度过窄,实盘前建议在 MT5 里改参数跑一遍看扇面是否铺满你的观察窗。
class=class="str">"cmt">// Find extremums within the specified date range FindExtremums(rates_total, high, low, time); class=class="str">"cmt">// Draw Gann Fans DrawGannFan(extremumPrice, extremumTime); DrawOppositeGannFan(oppositeExtremumPrice, oppositeExtremumTime); class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Find both extremums within the specified date range | class=class="str">"cmt">//+------------------------------------------------------------------+ 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 = -class="num">1; class="type">int endIndex = -class="num">1; for (class="type">int i = class="num">0; i < rates_total; i++) { if (time[i] >= StartDate && startIndex == -class="num">1) { startIndex = i; } if (time[i] <= EndDate) { endIndex = i; } } if (startIndex == -class="num">1 || endIndex == -class="num">1 || startIndex > endIndex) { Print("Error: Invalid date range or no data available in the specified range"); class="kw">return; } class="type">int highestIndex = ArrayMaximum(high, startIndex, endIndex - startIndex + class="num">1); class="type">int lowestIndex = ArrayMinimum(low, startIndex, endIndex - startIndex + class="num">1); class=class="str">"cmt">// Determine the most recent extremum if (time[highestIndex] > time[lowestIndex]) { extremumPrice = high[highestIndex]; extremumTime = time[highestIndex]; oppositeExtremumPrice = low[lowestIndex]; oppositeExtremumTime = time[lowestIndex]; isTrendUp = false; } else { extremumPrice = low[lowestIndex]; extremumTime = time[lowestIndex]; oppositeExtremumPrice = high[highestIndex]; oppositeExtremumTime = time[highestIndex]; isTrendUp = true; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Draw Gann Fan | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void DrawGannFan(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 = extremumTime + PeriodSeconds() * class="num">300;
「反向甘氏扇的绘制收口」
在主趋势甘氏扇之外,再画一套反向扇面,能帮你在极值反转时快速比对斜率冲突。上面这段把角度数组原样复用,只是终点价格沿相反方向推算,向下趋势里 angle 取负让射线自然朝右下延伸。 endTime 用 extremumTime + PeriodSeconds()*300 固定向右推 300 根当前周期 K 线,EURUSD 这类品种在 M15 下约覆盖 75 小时,足够暴露后续价格与扇线的接触关系。外汇与贵金属杠杆高,扇面仅作概率参考,实际突破常伴随滑点放大风险。 循环里 ObjectCreate 失败会走 Print 打 GetLastError(),实盘前建议把失败分支接上图表注释,免得静默丢线还以为画全了。
for(class="type">int i = class="num">0; i < ArraySize(angles); i++) { class="type">class="kw">string label = "GannFan_" + angleNames[i]; class="type">class="kw">double angle = angles[i]; 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) { 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, "Gann Fan " + angleNames[i]); } else { Print("Failed to create Gann Fan line: ", GetLastError()); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Draw Opposite Gann Fan | class=class="str">"cmt">//+------------------------------------------------------------------+ 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 = extremumTime + PeriodSeconds() * class="num">300; 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];