Centaur_Quant_Architecture/MQL5/Include/Data/ContextPackager.mqh

255 lines
11 KiB
MQL5
Raw Permalink Normal View History

//+------------------------------------------------------------------+
//| ContextPackager.mqh |
//| Centaur Quant Architecture — Data Module |
//| Historical Context & Swing Mapper (SDP-compatible) |
//+------------------------------------------------------------------+
//| PURPOSE |
//| Scans recent bars (any timeframe) for significant swing highs |
//| and swing lows. Fractal pivots are filtered by a dynamic ATR |
//| threshold supplied by CSymbolNormalizer, so noise rejection |
//| adapts to each instrument's volatility. Outputs parallel arrays |
//| (swing_high, swing_low, swing_time) directly consumable by |
//| CSDPEncoder::BuildHistoricalContext(). |
//| NOTE: the ATR threshold is sampled on the chart's current period |
//| (CSymbolNormalizer); when scanning a different timeframe, tune |
//| atr_multiplier to keep the threshold dimensionally consistent. |
//+------------------------------------------------------------------+
#property strict
#ifndef CONTEXTPACKAGER_MQH
#define CONTEXTPACKAGER_MQH
#include "../Core/CSymbolNormalizer.mqh"
#include "../Network/CSDPEncoder.mqh"
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class CContextPackager
{
private:
string m_symbol; // instrument whose structure is scanned
ENUM_TIMEFRAMES m_scan_period; // timeframe the swing scan runs on
string m_tf_name; // SDP wire name of m_scan_period
CSymbolNormalizer m_normalizer; // dynamic ATR threshold source
CSDPEncoder m_encoder; // SDP serializer
//--- detected swing bars (parallel arrays, oldest -> newest) ---
double m_swing_high[];
double m_swing_low[];
datetime m_swing_time[];
string PeriodName(const ENUM_TIMEFRAMES period);
void ResetSwings();
public:
CContextPackager(const string symbol, const ENUM_TIMEFRAMES scan_period = PERIOD_CURRENT);
~CContextPackager();
//--- scan recent bars and rebuild the swing arrays ---
bool Scan(const int bars_to_scan,
const int fractal_radius = 2,
const double atr_multiplier = 1.0,
const int atr_period = 14);
//--- serialize detected swings to the SDP historical_context array ---
string ContextJSON();
//--- read access ---
int SwingCount() const { return ArraySize(m_swing_time); }
bool GetSwing(const int index,
double &swing_high, double &swing_low,
datetime &swing_time) const;
string Symbol() const { return m_symbol; }
string Timeframe() const { return m_tf_name; }
};
//+------------------------------------------------------------------+
//| Constructor — bind symbol and resolve the scan period. |
//+------------------------------------------------------------------+
CContextPackager::CContextPackager(const string symbol, const ENUM_TIMEFRAMES scan_period)
: m_symbol(symbol),
m_scan_period(scan_period),
m_tf_name(""),
m_normalizer(symbol),
m_encoder()
{
ENUM_TIMEFRAMES p = m_scan_period;
if(p == PERIOD_CURRENT)
p = (ENUM_TIMEFRAMES)_Period;
m_scan_period = p;
m_tf_name = PeriodName(p);
if(StringLen(m_symbol) == 0 || !m_normalizer.IsReady())
PrintFormat("[CContextPackager] ERROR: invalid symbol '%s' or normalizer not ready.", m_symbol);
else
PrintFormat("[CContextPackager] INFO: ready on %s %s.", m_symbol, m_tf_name);
}
//+------------------------------------------------------------------+
//| Destructor — nothing to release; present for symmetry. |
//+------------------------------------------------------------------+
CContextPackager::~CContextPackager()
{
}
//+------------------------------------------------------------------+
//| Scan — detect significant swings over the recent window. |
//| 1) Dynamic noise threshold = ATR(period) * multiplier (normalizer).|
//| 2) Fractal pivot scan: a bar is a pivot when its extreme strictly |
//| dominates the +/-fractal_radius window. |
//| 3) Significance filter: pivots must ALTERNATE (high/low) and move |
//| at least one threshold from the previous accepted swing. |
//| Output arrays are chronological (oldest bar first). |
//+------------------------------------------------------------------+
bool CContextPackager::Scan(const int bars_to_scan,
const int fractal_radius,
const double atr_multiplier,
const int atr_period)
{
ResetSwings();
//--- parameter validation ---
if(StringLen(m_symbol) == 0)
{
PrintFormat("[CContextPackager] ERROR: no symbol configured.");
return false;
}
if(fractal_radius < 1)
{
PrintFormat("[CContextPackager] ERROR: fractal_radius must be >= 1 (got %d).", fractal_radius);
return false;
}
if(atr_multiplier <= 0.0)
{
PrintFormat("[CContextPackager] ERROR: atr_multiplier must be > 0 (got %G).", atr_multiplier);
return false;
}
int bars = bars_to_scan;
if(bars > 10000)
{
PrintFormat("[CContextPackager] WARNING: bars_to_scan %d clamped to 10000.", bars);
bars = 10000;
}
const int min_bars = 2 * fractal_radius + 2;
if(bars < min_bars)
{
PrintFormat("[CContextPackager] ERROR: bars_to_scan %d < minimum %d.", bars, min_bars);
return false;
}
//--- dynamic noise threshold from the normalizer ---
const double threshold = m_normalizer.GetATRBuffer(atr_period, atr_multiplier);
if(threshold <= 0.0)
{
PrintFormat("[CContextPackager] ERROR: ATR threshold unavailable for %s (warm-up?). Scan aborted.",
m_symbol);
return false;
}
//--- copy the scan window; index 0 is the most recent bar ---
double high[], low[];
datetime time[];
const int got_high = CopyHigh(m_symbol, m_scan_period, 0, bars, high);
const int got_low = CopyLow(m_symbol, m_scan_period, 0, bars, low);
const int got_time = CopyTime(m_symbol, m_scan_period, 0, bars, time);
if(got_high < min_bars || got_low < min_bars || got_time < min_bars)
{
PrintFormat("[CContextPackager] ERROR: insufficient history for %s %s (need %d bars, got %d/%d/%d).",
m_symbol, m_tf_name, min_bars, got_high, got_low, got_time);
return false;
}
const int n = got_time; // all three arrays hold the same window in practice
//--- fractal pivot scan, oldest -> newest (chronological output) ---
bool have_last = false;
bool last_is_high = false;
double last_price = 0.0;
for(int i = n - 1 - fractal_radius; i >= fractal_radius; i--)
{
// pivot high: strict max over the surrounding window
bool is_high = true;
for(int k = i - fractal_radius; k <= i + fractal_radius && is_high; k++)
{
if(k != i && high[k] >= high[i])
is_high = false;
}
// pivot low: strict min over the surrounding window
bool is_low = true;
for(int k = i - fractal_radius; k <= i + fractal_radius && is_low; k++)
{
if(k != i && low[k] <= low[i])
is_low = false;
}
// significance: alternation + at least one threshold of travel
bool accept_high = is_high && (!have_last || !last_is_high);
if(accept_high && have_last && (high[i] - last_price) < threshold)
accept_high = false;
bool accept_low = is_low && (!have_last || last_is_high);
if(accept_low && have_last && (last_price - low[i]) < threshold)
accept_low = false;
if(accept_high || accept_low)
{
const int s = ArraySize(m_swing_time);
ArrayResize(m_swing_high, s + 1);
ArrayResize(m_swing_low, s + 1);
ArrayResize(m_swing_time, s + 1);
m_swing_high[s] = high[i];
m_swing_low[s] = low[i];
m_swing_time[s] = time[i];
if(accept_high)
{
last_is_high = true;
last_price = high[i];
}
else
{
last_is_high = false;
last_price = low[i];
}
have_last = true;
}
}
PrintFormat("[CContextPackager] INFO: %s %s scan complete — %d significant swing(s), threshold %.5f.",
m_symbol, m_tf_name, ArraySize(m_swing_time), threshold);
return true;
}
//+------------------------------------------------------------------+
//| ContextJSON — SDP historical_context JSON array of the detected |
//| swings; "" when nothing was detected. |
//+------------------------------------------------------------------+
string CContextPackager::ContextJSON()
{
if(ArraySize(m_swing_time) == 0)
return "";
return m_encoder.BuildHistoricalContext(m_symbol, m_swing_high, m_swing_low, m_swing_time);
}
//+------------------------------------------------------------------+
//| GetSwing — copy one detected swing bar; false on bad index. |
//+------------------------------------------------------------------+
bool CContextPackager::GetSwing(const int index,
double &swing_high, double &swing_low,
datetime &swing_time) const
{
if(index < 0 || index >= ArraySize(m_swing_time))
return false;
swing_high = m_swing_high[index];
swing_low = m_swing_low[index];
swing_time = m_swing_time[index];
return true;
}
//+------------------------------------------------------------------+
//| ResetSwings — clear all detected swing arrays. |
//+------------------------------------------------------------------+
void CContextPackager::ResetSwings()
{
ArrayResize(m_swing_high, 0);
ArrayResize(m_swing_low, 0);
ArrayResize(m_swing_time, 0);
}
//+------------------------------------------------------------------+
//| PeriodName — wire name of an ENUM_TIMEFRAMES value. |
//+------------------------------------------------------------------+
string CContextPackager::PeriodName(const ENUM_TIMEFRAMES period)
{
ENUM_TIMEFRAMES p = period;
if(p == PERIOD_CURRENT)
p = (ENUM_TIMEFRAMES)_Period;
string name = EnumToString(p); // e.g. "PERIOD_H1"
StringReplace(name, "PERIOD_", ""); // "H1"
return name;
}
#endif // CONTEXTPACKAGER_MQH