You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何让MT4分形指标从指定K线索引显示至最新K线?

MT4指标:从自定义交叉索引开始显示Swing High/Low的修复方案

核心问题梳理

你的指标无法正常运行,主要是以下关键错误导致:

  1. 数组索引混乱:OnCalculate传入的原生数组(high[]/low[]等)默认是非序列模式(索引0为最早K线),但你自行Copy的数组设置了ArraySetAsSeries(true)(索引0为最新K线),两者索引对应K线完全错位。
  2. 循环逻辑完全错误:原代码for(int i = start; i <= InpRangeBars ; i--)的条件矛盾,导致循环根本无法执行。
  3. 无限循环风险FindMostRecentCross里的for(int i = 0; i >= 0; i++)是无限循环,会导致指标卡死。
  4. 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;
}

修复说明

  1. 统一序列模式:所有数组都设置为序列模式(索引0为最新K线),避免索引错位。
  2. 循环逻辑修正:正确设置遍历范围,从交叉点到最新K线,确保分形检查的有效性。
  3. 交叉函数优化:去掉无限循环,直接使用传入的数组,提高效率并避免索引错误。
  4. 资源优化:创建标签前先检查是否存在,减少不必要的对象操作。

内容的提问来源于stack exchange,提问作者Blackout

火山引擎 最新活动