Suggestion: Allow runtime choice of indicator window (chart / separate) and dynamic buffers/plots

Hello everyone,

I was studying the open-source TradingView script Z-Score Range Boxes Breakout [BigBeluga].

Both script versions for TV and ProRealTime can create indicators/draw buffers in both windows using only one script. This is extremely convenient.


In MQL5, as far as I know, you can archive this by creating 2 separate indicator scripts then main chart script communicate with the separate window script, or use #resource to compile them together. Both solutions work, but they feel heavier than necessary.


Would it be a long-shot to suggest an improvement so that an indicator no longer has to declare its window type, number of buffers and number of plots at compile time? In other words, could these three directives become optional or overridable at runtime?

#property indicator_chart_window
#property indicator_plots 6
#property indicator_buffers 7

As someone who approached MQL5 from a C++ background, i think this would be a big improvement, here is an example of a simple RSI script and some screenshot using c++, indicator buffers are drawn at any window at runtime.


#pragma once

class CRSI : public CIndicatorBase {
public:
    CRSI(
        const std::string& symbol,
        const std::string& timeframe)
        : CIndicatorBase(
              symbol,
              timeframe) {}

protected:
    InitRetCode OnInit() noexcept override;
    int OnCalculate(
        int rates_total,
        int prev_calculated,
        const std::vector<long>& time,
        const std::vector<double>& open,
        const std::vector<double>& high,
        const std::vector<double>& low,
        const std::vector<double>& close,
        const std::vector<int>& volume) noexcept override;

private:
    // Output buffer
    std::vector<double>* m_rsiBuf{nullptr};

    // Internal calculation buffers
    std::vector<double>* m_posBuf{nullptr};
    std::vector<double>* m_negBuf{nullptr};

    // Parameters
    int m_period{14};
};

inline InitRetCode CRSI::OnInit() noexcept {
    auto& adapter = CMqlSdlAdapter::GetInstance();
    auto seriesManager = adapter.GetMarketModule()->GetSeriesManager();
    if (!seriesManager) {
        Print("Series manager is null");
        return InitRetCode::InitFailed;
    }

    CInputBuilder(*this)
        .Int("period", "Period", m_period);

    //--- Buffer[0]
    m_rsiBuf = &seriesManager->GetIndicatorBuffer(m_symbol, m_timeframe, MakeBufferName("Main"));

    //--- Buffer[1]
    m_posBuf = &seriesManager->GetIndicatorBuffer(m_symbol, m_timeframe, MakeBufferName("Pos"));

    //--- Buffer[2]
    m_negBuf = &seriesManager->GetIndicatorBuffer(m_symbol, m_timeframe, MakeBufferName("Neg"));

    return InitRetCode::InitSucceeded;
}

inline int CRSI::OnCalculate(
    int rates_total,
    int prev_calculated,
    const std::vector<long>& time,
    const std::vector<double>& open,
    const std::vector<double>& high,
    const std::vector<double>& low,
    const std::vector<double>& close,
    const std::vector<int>& volume) noexcept {
    if (rates_total <= m_period)
        return 0;

    //--- preliminary calculations
    int pos = prev_calculated - 1;
    if (pos <= m_period) {
        double sum_pos = 0.0;
        double sum_neg = 0.0;
        //--- first period values of the indicator are not calculated
        (*m_rsiBuf)[0] = 0.0;
        (*m_posBuf)[0] = 0.0;
        (*m_negBuf)[0] = 0.0;
        for (int i = 1; i <= m_period; i++) {
            (*m_rsiBuf)[i] = 0.0;
            (*m_posBuf)[i] = 0.0;
            (*m_negBuf)[i] = 0.0;
            double diff = close[i] - close[i - 1];
            sum_pos += (diff > 0 ? diff : 0);
            sum_neg += (diff < 0 ? -diff : 0);
        }
        //--- calculate first visible value
        (*m_posBuf)[m_period] = sum_pos / m_period;
        (*m_negBuf)[m_period] = sum_neg / m_period;
        if ((*m_negBuf)[m_period] != 0.0)
            (*m_rsiBuf)[m_period] = 100.0 - (100.0 / (1.0 + (*m_posBuf)[m_period] / (*m_negBuf)[m_period]));
        else {
            if ((*m_posBuf)[m_period] != 0.0)
                (*m_rsiBuf)[m_period] = 100.0;
            else
                (*m_rsiBuf)[m_period] = 50.0;
        }
        //--- prepare the position value for main calculation
        pos = m_period + 1;
    }
    //--- the main loop of calculations
    for (int i = pos; i < rates_total && !IsStopped(); i++) {
        double diff = close[i] - close[i - 1];
        (*m_posBuf)[i] = ((*m_posBuf)[i - 1] * (m_period - 1) + (diff > 0.0 ? diff : 0.0)) / m_period;
        (*m_negBuf)[i] = ((*m_negBuf)[i - 1] * (m_period - 1) + (diff < 0.0 ? -diff : 0.0)) / m_period;
        if ((*m_negBuf)[i] != 0.0)
            (*m_rsiBuf)[i] = 100.0 - 100.0 / (1 + (*m_posBuf)[i] / (*m_negBuf)[i]);
        else {
            if ((*m_posBuf)[i] != 0.0)
                (*m_rsiBuf)[i] = 100.0;
            else
                (*m_rsiBuf)[i] = 50.0;
        }
    }

    //--- OnCalculate done. Return new prev_calculated.
    return rates_total;
}


Tshidiso Ephraim Mpakanyane:
In MQL5, as far as I know, you can archive this by creating 2 separate indicator scripts

one thing you can already do today: an indicator that lives in a seperate window can still draw on the main chart using objects. if you create them in sub window 0, the boxes, lines or arrows go on the price chart while the buffers stay below.

the buffers are fixed to one window when you compile, so for plotted lines you still need two files, but for range boxes like that script, objects are enough. good suggestion anyway, it would save alot of double work.