255 lines
11 KiB
MQL5
255 lines
11 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| 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
|