如何让MT4分形指标从指定K线索引显示至最新K线?
MT4指标:从自定义交叉索引开始显示Swing High/Low的修复方案
核心问题梳理
你的指标无法正常运行,主要是以下关键错误导致:
- 数组索引混乱:OnCalculate传入的原生数组(
high[]/low[]等)默认是非序列模式(索引0为最早K线),但你自行Copy的数组设置了ArraySetAsSeries(true)(索引0为最新K线),两者索引对应K线完全错位。 - 循环逻辑完全错误:原代码
for(int i = start; i <= InpRangeBars ; i--)的条件矛盾,导致循环根本无法执行。 - 无限循环风险:
FindMostRecentCross里的for(int i = 0; i >= 0; i++)是无限循环,会导致指标卡死。 - prev_calculated处理错误:更新时设置
start = rates_total完全不符合MT4指标更新逻辑,无法处理新增K线。
具体修复步骤
1. 统一数组序列模式
在OnInit中设置指标缓冲区为序列模式,和Copy的数组保持一致,方便处理最新K线:
SetIndexBuffer(0, SwingHighBuffer, INDICATOR_DATA); SetIndexBuffer(1, SwingLowBuffer, INDICATOR_DATA); ArraySetAsSeries(SwingHighBuffer, true); ArraySetAsSeries(SwingLowBuffer, true);
2. 修复FindMostRecentCross函数
- 去掉无限循环,遍历有效K线范围
- 直接使用OnCalculate传入的
close[]和已Copy的MA数组,避免索引混乱 - 正确返回交叉点的序列索引(0为最新K线)
3. 修正循环范围与逻辑
- 首次计算时,从交叉点索引开始,遍历到最新K线(索引0)
- 更新时,只计算新增的K线,同时确保不早于交叉点
- 调整分形检查的索引逻辑,适配序列模式
4. 优化对象创建逻辑
避免重复创建标签,先检查对象是否存在再创建,减少资源占用
完整修复代码
#property indicator_chart_window #property indicator_buffers 2 #property indicator_plots 2 //--- plot Swing High #property indicator_label1 "Swing High" #property indicator_type1 DRAW_ARROW #property indicator_color1 clrRed #property indicator_width1 2 //--- plot Swing Low #property indicator_label2 "Swing Low" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrBlue #property indicator_width2 2 //--- input parameters input int InpRangeBars = 2; // Number of bars to check on each side //--- indicator buffers double SwingHighBuffer[]; double SwingLowBuffer[]; datetime time_alert; //used when sending alert //--- variables to store swing points double H1 = 0; double L1 = 0; datetime H1Time = 0; datetime L1Time = 0; double MA[]; int MA_handle; int crossoverIndex = -1; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0, SwingHighBuffer, INDICATOR_DATA); SetIndexBuffer(1, SwingLowBuffer, INDICATOR_DATA); // 设置缓冲区为序列模式(0为最新K线) ArraySetAsSeries(SwingHighBuffer, true); ArraySetAsSeries(SwingLowBuffer, true); //--- setting a code from the Wingdings charset as the property of PLOT_ARROW PlotIndexSetInteger(0, PLOT_ARROW, 234); PlotIndexSetInteger(1, PLOT_ARROW, 233); //--- setting indicator digits IndicatorSetInteger(INDICATOR_DIGITS, _Digits); //--- name for DataWindow and indicator subwindow label string short_name = "Improved Swing High/Low Identifier"; IndicatorSetString(INDICATOR_SHORTNAME, short_name); MA_handle = iMA(NULL, PERIOD_CURRENT, 50, 0, MODE_SMA, PRICE_CLOSE); if(MA_handle == INVALID_HANDLE) { Print("iMA创建失败: ", GetLastError()); return(INIT_FAILED); } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { // 转换为序列模式数组 ArraySetAsSeries(time, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(close, true); // 复制MA数据 if(BarsCalculated(MA_handle) < rates_total) return(0); if(CopyBuffer(MA_handle, 0, 0, rates_total, MA) <= 0) { Print("CopyBuffer失败: ", GetLastError()); return(rates_total); } ArraySetAsSeries(MA, true); // 查找最新交叉点 crossoverIndex = FindMostRecentCross(rates_total, close, MA); Print("交叉索引 = ", crossoverIndex); int start; if(prev_calculated == 0) { // 首次计算:从交叉点开始,最少需要保留InpRangeBars根K线用于分形检查 start = MathMax(crossoverIndex, InpRangeBars); } else { // 更新计算:从上次计算的位置开始,不早于交叉点 start = MathMax(rates_total - prev_calculated, InpRangeBars); start = MathMax(start, crossoverIndex); } // 重置缓冲区与标签 for(int i = 0; i < start; i++) { SwingHighBuffer[i] = EMPTY_VALUE; SwingLowBuffer[i] = EMPTY_VALUE; ObjectDelete(0, "SwingHigh_" + IntegerToString(i)); ObjectDelete(0, "SwingLow_" + IntegerToString(i)); } // 遍历K线寻找分形 for(int i = start; i < rates_total; i++) { SwingHighBuffer[i] = EMPTY_VALUE; SwingLowBuffer[i] = EMPTY_VALUE; bool isSwingHigh = true; bool isSwingLow = true; // 检查Swing High(序列模式下,i+j是更早的K线) for(int j = 1; j <= InpRangeBars; j++) { if(i + j >= rates_total) { isSwingHigh = false; break; } if(high[i] <= high[i + j]) { isSwingHigh = false; break; } } for(int j = 1; j <= InpRangeBars; j++) { if(i - j < 0) { isSwingHigh = false; break; } if(high[i] <= high[i - j]) { isSwingHigh = false; break; } } // 检查Swing Low for(int j = 1; j <= InpRangeBars; j++) { if(i + j >= rates_total) { isSwingLow = false; break; } if(low[i] >= low[i + j]) { isSwingLow = false; break; } } for(int j = 1; j <= InpRangeBars; j++) { if(i - j < 0) { isSwingLow = false; break; } if(low[i] >= low[i - j]) { isSwingLow = false; break; } } // 标记Swing High if(isSwingHigh) { SwingHighBuffer[i] = high[i]; string labelName = "SwingHigh_" + IntegerToString(i); if(!ObjectFind(0, labelName)) CreateLabel(labelName, time[i], high[i], "H", clrRed); if(high[i] > H1 || H1 == 0) { H1 = high[i]; H1Time = time[i]; } } // 标记Swing Low if(isSwingLow) { SwingLowBuffer[i] = low[i]; string labelName = "SwingLow_" + IntegerToString(i); if(!ObjectFind(0, labelName)) CreateLabel(labelName, time[i], low[i], "L", clrBlue); if(low[i] < L1 || L1 == 0) { L1 = low[i]; L1Time = time[i]; } } } return(rates_total); } //+------------------------------------------------------------------+ //| Create a text label on the chart | //+------------------------------------------------------------------+ void CreateLabel(const string name, const datetime time, const double price, const string text, const color clr) { ObjectCreate(0, name, OBJ_TEXT, 0, time, price); ObjectSetString(0, name, OBJPROP_TEXT, text); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 8); ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(0, "SwingHigh_"); ObjectsDeleteAll(0, "SwingLow_"); } //+------------------------------------------------------------------+ //| 查找最新的价格与MA50交叉点 | //+------------------------------------------------------------------+ int FindMostRecentCross(const int rates_total, const double &close[], const double &MA[]) { // 遍历从最新K线开始,最少需要3根K线判断交叉 for(int i = 0; i < rates_total - 3; i++) { double currClose = close[i]; double prevClose = close[i+1]; double prevPrevClose = close[i+2]; double currMA = MA[i]; double prevMA = MA[i+1]; double prevPrevMA = MA[i+2]; // 向上交叉:前前收盘价在MA下,前收盘价在MA上,当前收盘价也在MA上 if(prevPrevClose < prevPrevMA && prevClose > prevMA && currClose > currMA) { return i+1; // 返回交叉发生的K线索引 } // 向下交叉:前前收盘价在MA上,前收盘价在MA下,当前收盘价也在MA下 if(prevPrevClose > prevPrevMA && prevClose < prevMA && currClose < currMA) { return i+1; // 返回交叉发生的K线索引 } } // 未找到交叉点,返回InpRangeBars确保能计算分形 return InpRangeBars; }
修复说明
- 统一序列模式:所有数组都设置为序列模式(索引0为最新K线),避免索引错位。
- 循环逻辑修正:正确设置遍历范围,从交叉点到最新K线,确保分形检查的有效性。
- 交叉函数优化:去掉无限循环,直接使用传入的数组,提高效率并避免索引错误。
- 资源优化:创建标签前先检查是否存在,减少不必要的对象操作。
内容的提问来源于stack exchange,提问作者Blackout




