从新手到专家:支撑与阻力强度指标(SRSI)·进阶篇
指标骨架:先让 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 新建指标把这段丢进去,能编译零报错才算过关。
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,强阻力列表可能直接翻倍,这是可调的真旋钮。
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。外汇与贵金属波动剧烈,这类区域仅作概率参考,实盘须自担高风险。
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 里验证帧率。
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 }