//+------------------------------------------------------------------+ //| IndicatorSeries.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://mql5.com" #property version "1.10" class IndicatorSeries { private: string m_symbol; string m_name; ENUM_TIMEFRAMES m_tf; int m_handle; int m_buffersCount; int m_barsCount; bool m_asSeries; // true = shift 0 is the most recent bar MqlParam m_params[]; double m_buffers[]; // flat: buffersCount * barsCount bool RecreateHandle(); public: IndicatorSeries(); ~IndicatorSeries(); // Initialisation overloads bool Initialize(string symbol, ENUM_TIMEFRAMES tf, string name, int handle, int buffersCount, int initialBars, bool asSeries = true); bool Initialize(string symbol, ENUM_TIMEFRAMES tf, string name, int buffersCount, int initialBars, bool asSeries = true); bool Initialize(string symbol, ENUM_TIMEFRAMES tf, string name, int buffersCount, int initialBars, const MqlParam ¶ms[], bool asSeries = true); bool Update(int barsCount = 3); double Value(int bufferIndex, int pShift); int Handle() const { return m_handle; } int BuffersCount() const { return m_buffersCount; } int Bars() const { return m_barsCount; } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ IndicatorSeries::IndicatorSeries() : m_handle(INVALID_HANDLE), m_buffersCount(0), m_barsCount(0), m_asSeries(true), m_symbol(""), m_name(""), m_tf(PERIOD_CURRENT) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ IndicatorSeries::~IndicatorSeries() { if(m_handle != INVALID_HANDLE) { IndicatorRelease(m_handle); m_handle = INVALID_HANDLE; } } //+------------------------------------------------------------------+ //| Recreate the indicator handle | //+------------------------------------------------------------------+ bool IndicatorSeries::RecreateHandle() { if(ArraySize(m_params) > 0) m_handle = IndicatorCreate(m_symbol, m_tf, IND_CUSTOM, ArraySize(m_params), m_params); else m_handle = iCustom(m_symbol, m_tf, m_name); return (m_handle != INVALID_HANDLE); } //+------------------------------------------------------------------+ //| Overload 1: Initialization using an existing handle | //+------------------------------------------------------------------+ bool IndicatorSeries::Initialize(string symbol, ENUM_TIMEFRAMES tf, string name, int handle, int buffersCount, int initialBars, bool asSeries) { m_symbol = symbol; m_tf = tf; m_name = name; m_handle = handle; m_buffersCount = buffersCount; m_asSeries = asSeries; ArrayFree(m_params); if(m_handle == INVALID_HANDLE) { PrintFormat("IndicatorSeries::Initialize failed: invalid handle | %s %s | %s", m_symbol, EnumToString(m_tf), m_name); return false; } return Update(initialBars); } //+------------------------------------------------------------------+ //| Overload 2: iCustom without extra parameters | //+------------------------------------------------------------------+ bool IndicatorSeries::Initialize(string symbol, ENUM_TIMEFRAMES tf, string name, int buffersCount, int initialBars, bool asSeries) { int handle = iCustom(symbol, tf, name); if(handle == INVALID_HANDLE) { PrintFormat("IndicatorSeries::Initialize failed: iCustom | %s %s | %s | Error=%d", symbol, EnumToString(tf), name, GetLastError()); return false; } return Initialize(symbol, tf, name, handle, buffersCount, initialBars, asSeries); } //+------------------------------------------------------------------+ //| Overload 3: Initialization with MqlParam array (custom indicator)| //+------------------------------------------------------------------+ bool IndicatorSeries::Initialize(string symbol, ENUM_TIMEFRAMES tf, string name, int buffersCount, int initialBars, const MqlParam ¶ms[], bool asSeries) { m_symbol = symbol; m_tf = tf; m_name = name; m_buffersCount = buffersCount; m_asSeries = asSeries; ArrayFree(m_params); int srcSize = ArraySize(params); if(ArrayResize(m_params, srcSize + 1) != srcSize + 1) return false; // First parameter = custom indicator name (required for IndicatorCreate) m_params[0].type = TYPE_STRING; m_params[0].string_value = m_name; // Copy the remaining parameters for(int i = 0; i < srcSize; i++) m_params[i + 1] = params[i]; if(!RecreateHandle()) { PrintFormat("IndicatorSeries::Initialize failed: RecreateHandle | %s %s | %s", m_symbol, EnumToString(m_tf), m_name); return false; } return Update(initialBars); } //+------------------------------------------------------------------+ //| Update internal buffers | //+------------------------------------------------------------------+ bool IndicatorSeries::Update(int barsCount = 3) { if(barsCount <= 0 || m_buffersCount <= 0) return false; if(m_handle == INVALID_HANDLE && !RecreateHandle()) return false; m_barsCount = barsCount; ArrayResize(m_buffers, m_buffersCount * m_barsCount); // Temporary buffer – its series flag is set to match m_asSeries double tempBuffer[]; ArraySetAsSeries(tempBuffer, m_asSeries); for(int i = 0; i < m_buffersCount; i++) { // CopyBuffer respects the series flag of tempBuffer. // If m_asSeries == true → tempBuffer[0] = latest bar. // If m_asSeries == false → tempBuffer[0] = oldest bar. int copied = CopyBuffer(m_handle, i, 0, m_barsCount, tempBuffer); if(copied != m_barsCount) return false; // Append the segment into the flat m_buffers array. // We must copy element by element to preserve the series ordering. int offset = i * m_barsCount; for(int j = 0; j < m_barsCount; j++) m_buffers[offset + j] = tempBuffer[j]; } return true; } //+------------------------------------------------------------------+ //| Get a value at a specific buffer and shift | //+------------------------------------------------------------------+ double IndicatorSeries::Value(int bufferIndex, int pShift) { if(bufferIndex < 0 || bufferIndex >= m_buffersCount || pShift < 0 || pShift >= m_barsCount) return 0.0; // Because we stored the buffer segment exactly as it was copied // (with tempBuffer's series flag already applied), pShift works uniformly: // asSeries = true → pShift 0 = latest bar // asSeries = false → pShift 0 = oldest bar return m_buffers[(bufferIndex * m_barsCount) + pShift]; } //+------------------------------------------------------------------+