169 lines
5 KiB
MQL5
169 lines
5 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| 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;
| |||
}
| |||
//+------------------------------------------------------------------+
|