从新手到专家:支撑与阻力强度指标(SRSI)·进阶篇
📘

从新手到专家:支撑与阻力强度指标(SRSI)·进阶篇

第 2/3 篇

指标骨架:先让 MT5 编译通过

写价格行为指标,第一步不是画线,而是把 MT5 强制要求的壳子搭起来。平台规定 #property indicator_buffers 至少 1 个、indicator_plots 至少 1 个,哪怕你只想用矩形标支撑阻力区,也得先绑一个隐身缓冲区。 下面这段就是把 DummyBuffer 绑到索引 0,再用 PlotIndexSetInteger 把绘制类型设成 DRAW_NONE,图表上不显示但编译器满意。 参数层给了四个可调入口:InpLookBack=1000 看 1000 根棒、`InpTestProximity=0.0007 约 7 点容差、InpMinTests=3 最少触碰 3 次才算强级、InpShowRectangles=true 控制是否画区。外汇和贵金属波动属性不同,0.0007 对 XAUUSD 可能太窄,实盘前建议按品种调。 级别和结构体已经定义好:ENUM_LEVEL_TYPE 分强/弱支撑阻力四种,PriceLevel` 存价格、触碰次数、类型和时间。开 MT5 新建指标把这段丢进去,能编译零报错才算过关。

MQL5 / C++
class="macro">#class="kw">property copyright "Clemence Benjamin"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property strict
class="macro">#class="kw">property indicator_chart_window
class="macro">#class="kw">property indicator_buffers class="num">1
class="macro">#class="kw">property indicator_plots   class="num">1
class="type">class="kw">double DummyBuffer[];
class="type">int OnInit()
{
   SetIndexBuffer(class="num">0, DummyBuffer, INDICATOR_DATA);
   PlotIndexSetInteger(class="num">0, PLOT_DRAW_TYPE, DRAW_NONE);
   class="kw">return(INIT_SUCCEEDED);
}
input class="type">int InpLookBack = class="num">1000;
input class="type">class="kw">double InpTestProximity = class="num">0.0007;
input class="type">int InpMinTests = class="num">3;
input class="type">bool InpShowRectangles = true;
enum ENUM_LEVEL_TYPE {
   LEVEL_STRONG_SUPPORT,
   LEVEL_STRONG_RESISTANCE,
   LEVEL_WEAK_SUPPORT,
   LEVEL_WEAK_RESISTANCE
};
class="kw">struct PriceLevel {
   class="type">class="kw">double price;
   class="type">int test_count;
   ENUM_LEVEL_TYPE type;
   class="type">class="kw">datetime time;
};

◍ 摆动点判定与价位测试计数怎么写

识别关键价位的第一步,是在 K 线序列里确认摆动高点与摆动低点。下面这套逻辑用左右各 5 根 K 线做窗口:若目标 bar 的最高价不严格高于两侧各 5 根,就不算摆动高点;最低价不严格低于两侧各 5 根,就不算摆动低点。 测试次数决定强弱分级。CountLevelTests 从摆动点之后逐根扫描,只要某根 high 或 low 与参考价之差的绝对值不超过 InpTestProximity,就计一次测试。外汇与贵金属波动快,这个容差参数设太小会漏掉刺透测试,设太大会把无关波动算进来,建议先在 M1~H1 上用 2~3 点容差回看 EURUSD 验证。 ProcessLevel 把扫描结果落库:test_count 达到 InpMinTests 的归入 StrongLevels 并生成 Zone,否则进 WeakLevels。强弱不是拍脑袋,是「被触碰次数 ≥ 阈值」这一硬条件触发的,MT5 里把 InpMinTests 从 3 改成 2,强阻力列表可能直接翻倍,这是可调的真旋钮。

MQL5 / C++
class="type">class="kw">double top;                 class=class="str">"cmt">// Top price of the zone
class="type">class="kw">double bottom;               class=class="str">"cmt">// Bottom price of the zone
class="type">class="kw">datetime start_time;         class=class="str">"cmt">// When the zone starts
class="type">class="kw">datetime end_time;           class=class="str">"cmt">// When the zone ends(current time)
};
PriceLevel StrongLevels[];     class=class="str">"cmt">// Array for strong levels
PriceLevel WeakLevels[];       class=class="str">"cmt">// Array for weak levels
Zone Zones[];                  class=class="str">"cmt">// Array for strong level zones
class="type">class="kw">datetime LastAlertTime = class="num">0;    class=class="str">"cmt">// Tracks the last alert time
class="type">bool IsSwingHigh(class="type">int index, class="kw">const class="type">class="kw">double &high[])
{
   class="type">int window = class="num">5;   class=class="str">"cmt">// Check class="num">5 bars on each side
   for (class="type">int i = class="num">1; i <= window; i++) {
      if (index - i < class="num">0 || index + i >= ArraySize(high)) class="kw">return class="kw">false; class=class="str">"cmt">// Out of bounds
      if (high[index] <= high[index - i] || high[index] <= high[index + i]) class="kw">return class="kw">false; class=class="str">"cmt">// Not a peak
   }
   class="kw">return true;
}
class="type">bool IsSwingLow(class="type">int index, class="kw">const class="type">class="kw">double &low[])
{
   class="type">int window = class="num">5;   class=class="str">"cmt">// Check class="num">5 bars on each side
   for (class="type">int i = class="num">1; i <= window; i++) {
      if (index - i < class="num">0 || index + i >= ArraySize(low)) class="kw">return class="kw">false; class=class="str">"cmt">// Out of bounds
      if (low[index] >= low[index - i] || low[index] >= low[index + i]) class="kw">return class="kw">false; class=class="str">"cmt">// Not a trough
   }
   class="kw">return true;
}
class="type">int CountLevelTests(class="type">class="kw">double price, class="type">int start_index, class="kw">const class="type">class="kw">double &high[], class="kw">const class="type">class="kw">double &low[])
{
   class="type">int tests = class="num">0;
   for (class="type">int i = start_index + class="num">1; i < ArraySize(high); i++) {
      if (MathAbs(high[i] - price) <= InpTestProximity || MathAbs(low[i] - price) <= InpTestProximity) {
         tests++;
      }
   }
   class="kw">return tests;
}
class="type">void ProcessLevel(class="type">int index, class="type">class="kw">double price, class="type">bool is_high, 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[])
{
   PriceLevel level;
   level.price = price;
   level.test_count = CountLevelTests(price, index, high, low);
   level.time = time[index];

   if (is_high) {
      level.type = (level.test_count >= InpMinTests) ? LEVEL_STRONG_RESISTANCE : LEVEL_WEAK_RESISTANCE;
   } else {
      level.type = (level.test_count >= InpMinTests) ? LEVEL_STRONG_SUPPORT : LEVEL_WEAK_SUPPORT;
   }

   if (level.test_count >= InpMinTests) {
      ArrayResize(StrongLevels, ArraySize(StrongLevels) + class="num">1);
      StrongLevels[ArraySize(StrongLevels) - class="num">1] = level;
      Zone zone;
      zone.start_time = time[index];

「把供需区与强弱线画上图表」

这段逻辑负责把算法算出的 zone 与 price level 落到 MT5 画面上,并控制报警节奏。zone 用矩形框标识,上下边界由 price±InpTestProximity 决定,弱级别则只画虚线水平线。 RenderZone 里先拼出 "Zone_"+时间戳的名字,若 InpShowRectangles 为真就用 OBJ_RECTANGLE 拉出填充浅灰矩形;随后画一条 OBJ_HLINE,强支撑染蓝标 SS,强阻力染红标 SR。RenderWeakLine 对弱支撑/弱阻力用 STYLE_DASH 虚线,浅蓝 WS、粉红 WR,肉眼区分强度。 SendPeriodicAlert 卡了两个门槛:距上次报警不足 3600 秒(1 小时)直接 return;强级别数组为空也不报。通过后取最新强级别,用 DoubleToString(...,5) 拼出 5 位报价提示,当前价同精度,Alert 弹窗并刷新 LastAlertTime。外汇与贵金属波动剧烈,这类区域仅作概率参考,实盘须自担高风险。

MQL5 / C++
zone.end_time = TimeCurrent();
zone.top = price + InpTestProximity;
zone.bottom = price - InpTestProximity;
ArrayResize(Zones, ArraySize(Zones) + class="num">1);
Zones[ArraySize(Zones) - class="num">1] = zone;
 } else {
   ArrayResize(WeakLevels, ArraySize(WeakLevels) + class="num">1);
   WeakLevels[ArraySize(WeakLevels) - class="num">1] = level;
 }
}
class="type">void RenderZone(class="kw">const Zone &zone, class="kw">const PriceLevel &level)
{
   class="type">class="kw">string name = "Zone_" + TimeToString(zone.start_time);
   if (InpShowRectangles) {
      ObjectCreate(class="num">0, name, OBJ_RECTANGLE, class="num">0, zone.start_time, zone.top, zone.end_time, zone.bottom);
      ObjectSetInteger(class="num">0, name, OBJPROP_COLOR, clrLightGray);
      ObjectSetInteger(class="num">0, name, OBJPROP_FILL, true);
   }

   class="type">class="kw">string line_name = "Line_" + TimeToString(level.time);
   ObjectCreate(class="num">0, line_name, OBJ_HLINE, class="num">0, class="num">0, level.price);
   ObjectSetInteger(class="num">0, line_name, OBJPROP_COLOR, (level.type == LEVEL_STRONG_SUPPORT) ? clrBlue : clrRed);
   ObjectSetString(class="num">0, line_name, OBJPROP_TEXT, (level.type == LEVEL_STRONG_SUPPORT) ? "SS" : "SR");
}
class="type">void RenderWeakLine(class="kw">const PriceLevel &level)
{
   class="type">class="kw">string name = "WeakLine_" + TimeToString(level.time);
   ObjectCreate(class="num">0, name, OBJ_HLINE, class="num">0, class="num">0, level.price);
   ObjectSetInteger(class="num">0, name, OBJPROP_STYLE, STYLE_DASH);
   ObjectSetInteger(class="num">0, name, OBJPROP_COLOR, (level.type == LEVEL_WEAK_SUPPORT) ? clrLightBlue : clrPink);
   ObjectSetString(class="num">0, name, OBJPROP_TEXT, (level.type == LEVEL_WEAK_SUPPORT) ? "WS" : "WR");
}
class="type">void SendPeriodicAlert(class="type">class="kw">double current_price)
{
   if (TimeCurrent() - LastAlertTime < class="num">3600) class="kw">return; class=class="str">"cmt">// Wait class="num">1 hour between alerts
   if (ArraySize(StrongLevels) == class="num">0) class="kw">return;          class=class="str">"cmt">// No strong levels, no alert

   PriceLevel latest = StrongLevels[ArraySize(StrongLevels) - class="num">1];
   class="type">class="kw">string message = "SRZones Alert: Strong " +
                        ((latest.type == LEVEL_STRONG_SUPPORT) ? "Support" : "Resistance") +
                        " at " + DoubleToString(latest.price, class="num">5) +
                        ", Current Price: " + DoubleToString(current_price, class="num">5);
   Alert(message);
   LastAlertTime = TimeCurrent();
}
class="type">void ClearDisplayObjects()
{

重算时如何干净地重建供需区

指标在 OnCalculate 里做了一次「全清全建」的逻辑:当 prev_calculated 为 0(刚加载)或 rates_total 大于已计算柱数(来了新 K 线)时,先调用 ClearDisplayObjects 把图上旧对象清掉,再把三个动态数组 StrongLevels、WeakLevels、Zones 用 ArrayResize 缩到 0,避免历史残留影响本次绘制。 清场之后,回看窗口用 MathMax(5, rates_total - InpLookBack) 确定起点,循环到 rates_total - 5 为止——前后各留 5 根是为了摆动的左右比对不被边界截断。对每个 i 调用 IsSwingHigh / IsSwingLow 判定,命中就走 ProcessLevel 写进对应数组。 数组填完,先按 Zones[i] 和 StrongLevels[i] 渲染供需区,再单独把 WeakLevels 画成弱线;最后用最新收盘价跑一次 SendPeriodicAlert。return(rates_total) 是告诉 MT5「这轮所有柱都处理完了」。外汇与贵金属波动剧烈,这种全量重算在 InpLookBack 设得很大时可能拖慢刷新,建议先用 500 根内回看在 MT5 里验证帧率。

MQL5 / C++
   ObjectsDeleteAll(class="num">0, "Zone_");        class=class="str">"cmt">// Delete all zones
   ObjectsDeleteAll(class="num">0, "Line_");        class=class="str">"cmt">// Delete strong lines
   ObjectsDeleteAll(class="num">0, "WeakLine_");    class=class="str">"cmt">// Delete weak lines
}
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 (prev_calculated == class="num">0 || rates_total > prev_calculated) { class=class="str">"cmt">// Full recalc on start or new bar
      ClearDisplayObjects();
      ArrayResize(StrongLevels, class="num">0);
      ArrayResize(WeakLevels, class="num">0);
      ArrayResize(Zones, class="num">0);
      
      class="type">int start = MathMax(class="num">5, rates_total - InpLookBack); class=class="str">"cmt">// Start within lookback period
      for (class="type">int i = start; i < rates_total - class="num">5; i++) {      class=class="str">"cmt">// Leave room for swing checks
         if (IsSwingHigh(i, high)) ProcessLevel(i, high[i], true, high, low, time);
         if (IsSwingLow(i, low)) ProcessLevel(i, low[i], class="kw">false, high, low, time);
      }
      
      for (class="type">int i = class="num">0; i < ArraySize(StrongLevels); i++) {
         RenderZone(Zones[i], StrongLevels[i]);
      }
      for (class="type">int i = class="num">0; i < ArraySize(WeakLevels); i++) {
         RenderWeakLine(WeakLevels[i]);
      }
      
      SendPeriodicAlert(close[rates_total - class="num">1]);
   }
   class="kw">return(rates_total); class=class="str">"cmt">// Tell MT5 how many bars were processed
}

常见问题

先放一个最简骨架:只包含输入参数、OnCalculate 空函数和缓冲区声明,确认能编译再通过后续逻辑,避免一上来就报几十个错。
用循环比对每根 K 线高低点与候选价位,触及但在容差内未穿透就计数加一,最后用该计数代表此处的试探强度。
小布会自动标注当前品种的供需区和强弱线,并给出哪些价位被反复测试但未破的提示,省去你手动数 K 线的麻烦。
重算前没删除旧对象所致。每次重建前用 ObjectsDeleteAll 或按名字清理对应图形,再画新的,图表才干净。
在 OnInit 或重算入口先清空已有区间数组与图形对象,再重新跑判定逻辑,只保留当次计算结果即可。