//+------------------------------------------------------------------+ //| Candles.mqh | //| Copyright 2026, Concrete Goonies | //| https://concrete-goonies.web.app | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Concrete Goonies" #property link "https://concrete-goonies.web.app" #property version "1.00" class Candles { private: string m_symbol; ENUM_TIMEFRAMES m_tf; MqlRates m_bars[]; int m_barsCount; datetime m_lastBarOpen; bool m_initialized; bool m_asSeries; // true = index 0 is the most recent bar public: Candles() : m_symbol(""), m_tf(PERIOD_CURRENT), m_barsCount(0), m_lastBarOpen(0), m_initialized(false), m_asSeries(true) {} ~Candles() {} bool Initialize(string symbol, ENUM_TIMEFRAMES tf, int initialBars, bool asSeries = true); bool Update(int barsCount = 3); double Close(int pShift); double High(int pShift); double Low(int pShift); double Open(int pShift); datetime Time(int pShift); long TickVolume(int pShift); long Volume(int pShift); const MqlRates operator[](int index) const { return m_bars[index]; } int Size() const { return m_barsCount; } datetime LastBarOpen() const { return m_lastBarOpen; } }; //+------------------------------------------------------------------+ //| Initialize | //+------------------------------------------------------------------+ bool Candles::Initialize(string symbol, ENUM_TIMEFRAMES tf, int initialBars, bool asSeries) { m_symbol = symbol; m_tf = tf; m_asSeries = asSeries; // Reset state m_initialized = false; m_barsCount = 0; m_lastBarOpen = 0; ArrayFree(m_bars); if(initialBars <= 0) return false; // Resize and set the series flag BEFORE copying so that CopyRates // fills the array in the desired direction. ArrayResize(m_bars, initialBars); ArraySetAsSeries(m_bars, m_asSeries); int copied = CopyRates(m_symbol, m_tf, 0, initialBars, m_bars); if(copied <= 0) { ArrayFree(m_bars); return false; } m_barsCount = copied; // With the series flag set, the latest bar is always at index 0 // if m_asSeries == true, otherwise at index copied-1. m_lastBarOpen = (m_asSeries) ? m_bars[0].time : m_bars[copied-1].time; m_initialized = true; return true; } //+------------------------------------------------------------------+ //| Update – refresh with the most recent barsCount candles | //+------------------------------------------------------------------+ bool Candles::Update(int barsCount = 3) { if(!m_initialized) return false; if(barsCount <= 0) return false; // Temporary array with the same series direction as m_bars MqlRates rates[]; ArrayResize(rates, barsCount); ArraySetAsSeries(rates, m_asSeries); int copied = CopyRates(m_symbol, m_tf, 0, barsCount, rates); if(copied != barsCount) return false; // Identify the latest bar time in the freshly copied data datetime newLastBarOpen = (m_asSeries) ? rates[0].time : rates[copied-1].time; // If the latest bar hasn't changed, no new bar has formed if(newLastBarOpen == m_lastBarOpen) return false; // Replace internal buffer, preserving series flag ArrayFree(m_bars); ArrayResize(m_bars, copied); ArraySetAsSeries(m_bars, m_asSeries); // Copy element by element – safest with series flags for(int i = 0; i < copied; i++) m_bars[i] = rates[i]; m_barsCount = copied; m_lastBarOpen = newLastBarOpen; return true; } //+------------------------------------------------------------------+ //| Accessor helpers | //+------------------------------------------------------------------+ double Candles::Close(int pShift) { if(pShift < 0 || pShift >= m_barsCount) return 0.0; return m_bars[pShift].close; } double Candles::High(int pShift) { if(pShift < 0 || pShift >= m_barsCount) return 0.0; return m_bars[pShift].high; } double Candles::Low(int pShift) { if(pShift < 0 || pShift >= m_barsCount) return 0.0; return m_bars[pShift].low; } double Candles::Open(int pShift) { if(pShift < 0 || pShift >= m_barsCount) return 0.0; return m_bars[pShift].open; } datetime Candles::Time(int pShift) { if(pShift < 0 || pShift >= m_barsCount) return 0; return m_bars[pShift].time; } long Candles::TickVolume(int pShift) { if(pShift < 0 || pShift >= m_barsCount) return 0; return m_bars[pShift].tick_volume; } long Candles::Volume(int pShift) { if(pShift < 0 || pShift >= m_barsCount) return 0; return m_bars[pShift].real_volume; } //+------------------------------------------------------------------+