StopFirst-Core/Include/StopFirst/Atr.mqh
RUSLAN SLOBODIANIUK 6ba79702fd Make the header comments self-contained
They referred to project documents that are not part of this repository.
Comments only; PriceToPips is now documented as not rounding, which is what
it does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 21:48:01 +03:00

217 lines
9 KiB
MQL5

//+------------------------------------------------------------------+
//| Atr.mqh|
//| Copyright 2026, bandersoft |
//| https://www.mql5.com/en/users/fats |
//+------------------------------------------------------------------+
//| |
//| Average True Range helper. |
//| |
//+------------------------------------------------------------------+
#ifndef STOPFIRST_ATR_MQH
#define STOPFIRST_ATR_MQH
//--- GetATRPips reports its result via PriceToPips
#include "Symbol.mqh"
//+------------------------------------------------------------------+
//| Average True Range on one symbol, timeframe and period |
//| |
//| Calling iATR, CopyBuffer and IndicatorRelease on every request |
//| is expensive once per tick, and a handle created moments earlier |
//| usually has no calculated data yet, so CopyBuffer fails and the |
//| result is a silent 0 the caller cannot tell from a real reading. |
//| |
//| The handle is created once and held for the object's lifetime. |
//| |
//| CAtr atr; |
//| int OnInit() { return atr.Init(_Symbol, PERIOD_H1, 14) ? 0 : 1; }|
//| void OnTick() { double d = atr.Pips(); } |
//+------------------------------------------------------------------+
class CAtr
{
private:
string m_symbol;
ENUM_TIMEFRAMES m_timeframe;
int m_period;
int m_handle;
public:
CAtr(void);
~CAtr(void);
bool Init(const string symbol = "",
const ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT,
const int period = 14);
double Value(const int shift = 0);
double Pips(const int shift = 0);
void Release(void);
bool IsReady(void) const { return m_handle != INVALID_HANDLE; }
int Handle(void) const { return m_handle; }
};
CAtr::CAtr(void) : m_symbol(_Symbol),
m_timeframe(PERIOD_CURRENT),
m_period(14),
m_handle(INVALID_HANDLE)
{
}
CAtr::~CAtr(void)
{
Release();
}
//+------------------------------------------------------------------+
//| Release the handle, if one is held |
//| Returns: nothing. |
//+------------------------------------------------------------------+
void CAtr::Release(void)
{
if(m_handle != INVALID_HANDLE)
{
IndicatorRelease(m_handle);
m_handle = INVALID_HANDLE;
}
}
//+------------------------------------------------------------------+
//| Create the indicator handle |
//| Parameters: |
//| symbol - empty for the current chart symbol |
//| timeframe - PERIOD_CURRENT for the chart timeframe |
//| period - ATR averaging period, must be positive |
//| |
//| Returns: true if the handle was created. Safe to call again to |
//| repoint the object; the previous handle is released. |
//+------------------------------------------------------------------+
bool CAtr::Init(const string symbol, const ENUM_TIMEFRAMES timeframe, const int period)
{
Release();
if(period <= 0) return false;
m_symbol = (symbol == "") ? _Symbol : symbol;
m_timeframe = timeframe;
m_period = period;
m_handle = iATR(m_symbol, m_timeframe, m_period);
return m_handle != INVALID_HANDLE;
}
//+------------------------------------------------------------------+
//| ATR in price units |
//| Parameters: |
//| shift - bar to read, 0 being the forming bar |
//| |
//| Returns: the ATR, or 0 if Init was never called, the handle |
//| failed, or the indicator has not calculated yet. A |
//| freshly created handle needs a few ticks; retry rather |
//| than treating the 0 as a reading. |
//+------------------------------------------------------------------+
double CAtr::Value(const int shift)
{
if(m_handle == INVALID_HANDLE || shift < 0) return 0.0;
double buffer[];
ArraySetAsSeries(buffer, true);
if(CopyBuffer(m_handle, 0, shift, 1, buffer) <= 0) return 0.0;
return buffer[0];
}
//+------------------------------------------------------------------+
//| ATR expressed in pips |
//| Returns: the ATR in pips, or 0 - see Value(). |
//+------------------------------------------------------------------+
double CAtr::Pips(const int shift)
{
double value = Value(shift);
if(value <= 0) return 0.0;
return PriceToPips(value, m_symbol);
}
//+------------------------------------------------------------------+
//| Cache backing the GetATRPips wrapper |
//| |
//| Parallel primitive arrays rather than an array of CAtr: MQL5 |
//| reports undeleted dynamic objects when a program unloads, and a |
//| file-scope cache has no destructor to clean up in. Handles are |
//| released by the terminal at unload, or explicitly by |
//| ReleaseAtrCache(). |
//+------------------------------------------------------------------+
string g_atrKeys[];
int g_atrHandles[];
//+------------------------------------------------------------------+
//| Release every handle the GetATRPips cache holds |
//| Returns: nothing. Call from OnDeinit if you want them gone early. |
//+------------------------------------------------------------------+
void ReleaseAtrCache(void)
{
int total = ArraySize(g_atrHandles);
for(int i = 0; i < total; i++)
if(g_atrHandles[i] != INVALID_HANDLE)
IndicatorRelease(g_atrHandles[i]);
ArrayResize(g_atrKeys, 0);
ArrayResize(g_atrHandles, 0);
}
//+------------------------------------------------------------------+
//| Cached iATR handle for one symbol, timeframe and period |
//| Returns: the handle, or INVALID_HANDLE if it could not be made. |
//+------------------------------------------------------------------+
int CachedAtrHandle(const string symbol, const ENUM_TIMEFRAMES timeframe, const int period)
{
string key = symbol + "|" + IntegerToString(timeframe) + "|" + IntegerToString(period);
int total = ArraySize(g_atrKeys);
for(int i = 0; i < total; i++)
if(g_atrKeys[i] == key)
return g_atrHandles[i];
int handle = iATR(symbol, timeframe, period);
if(handle == INVALID_HANDLE) return INVALID_HANDLE;
ArrayResize(g_atrKeys, total + 1);
ArrayResize(g_atrHandles, total + 1);
g_atrKeys[total] = key;
g_atrHandles[total] = handle;
return handle;
}
//+------------------------------------------------------------------+
//| Get ATR value in pips |
//| Parameters: |
//| period - ATR averaging period |
//| timeframe - PERIOD_CURRENT for the chart timeframe |
//| symbol - empty for the current chart symbol |
//| |
//| Kept as a wrapper over the cache so existing callers do not have |
//| to hold a CAtr. Prefer CAtr in new code - it makes the handle's |
//| lifetime visible. |
//| |
//| Returns: the ATR in pips, or 0 if the handle could not be made |
//| or the indicator has not calculated yet. |
//+------------------------------------------------------------------+
double GetATRPips(int period = 14, ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT,
string symbol = "")
{
if(symbol == "") symbol = _Symbol;
if(period <= 0) return 0;
int handle = CachedAtrHandle(symbol, timeframe, period);
if(handle == INVALID_HANDLE) return 0;
double atr[];
ArraySetAsSeries(atr, true);
if(CopyBuffer(handle, 0, 0, 1, atr) <= 0) return 0;
return PriceToPips(atr[0], symbol);
}
#endif // STOPFIRST_ATR_MQH
//+------------------------------------------------------------------+