devKit/multiTF.mqh
2026-07-12 21:21:20 +03:00

266 lines
9.6 KiB
MQL5

//+------------------------------------------------------------------+
//| multiTF.mqh |
//| Multi-Timeframe Framework (devKit) |
//| |
//| PURPOSE |
//| ------- |
//| Provides a reusable framework for evaluating conditions across |
//| multiple timeframes in a strict HTF → LTF sequence. |
//| |
//| USAGE (quick-start) |
//| ------------------- |
//| 1. Define a bool function that accepts an ENUM_TIMEFRAMES arg. |
//| bool MyCondition(ENUM_TIMEFRAMES tf) { ... return true; } |
//| |
//| 2. Instantiate CMultiTF and register your timeframe stack. |
//| CMultiTF mtf; |
//| mtf.AddTF(PERIOD_D1); // HTF |
//| mtf.AddTF(PERIOD_H4); |
//| mtf.AddTF(PERIOD_H1); |
//| mtf.AddTF(PERIOD_M15); // LTF |
//| |
//| 3. Run the condition — returns true only when ALL TFs pass. |
//| if(mtf.Run(MyCondition)) { ... } |
//| |
//| 4. Or collect per-TF results without short-circuiting. |
//| bool results[]; |
//| int passed = mtf.RunAll(MyCondition, results); |
//| |
//| 5. Or run a *different* function per TF using a function map. |
//| TFConditionFunc map[]; |
//| ArrayResize(map, 4); |
//| map[0] = D1Cond; map[1] = H4Cond; ... |
//| if(mtf.RunMapped(map)) { ... } |
//+------------------------------------------------------------------+
#pragma once
//-------------------------------------------------------------------
// Function pointer type
// Any bool function(ENUM_TIMEFRAMES tf) matches this signature.
//-------------------------------------------------------------------
typedef bool (*TFConditionFunc)(ENUM_TIMEFRAMES tf);
//-------------------------------------------------------------------
// MTF_Result — per-timeframe evaluation snapshot
//-------------------------------------------------------------------
struct MTF_Result
{
ENUM_TIMEFRAMES timeframe; // which TF
bool passed; // did the condition pass?
datetime checked_at; // wall-clock time of evaluation
};
//-------------------------------------------------------------------
// CMultiTF — the main framework class
//-------------------------------------------------------------------
class CMultiTF
{
private:
ENUM_TIMEFRAMES m_tfs[]; // ordered HTF→LTF stack
int m_count; // number of registered TFs
MTF_Result m_last[]; // results from the last run
//--- internal helper: resize and reset results buffer
void PrepareResults()
{
ArrayResize(m_last, m_count);
for(int i = 0; i < m_count; i++)
{
m_last[i].timeframe = m_tfs[i];
m_last[i].passed = false;
m_last[i].checked_at = 0;
}
}
public:
//--- ctor / dtor
CMultiTF() : m_count(0) { ArrayResize(m_tfs, 0); ArrayResize(m_last, 0); }
~CMultiTF() {}
//================================================================
// STACK MANAGEMENT
//================================================================
//--- Append a timeframe to the HTF→LTF stack.
// Call in descending order (largest period first).
void AddTF(ENUM_TIMEFRAMES tf)
{
ArrayResize(m_tfs, m_count + 1);
m_tfs[m_count] = tf;
m_count++;
}
//--- Replace the entire stack at once (array must be HTF→LTF).
void SetTFs(const ENUM_TIMEFRAMES &tfs[])
{
m_count = ArraySize(tfs);
ArrayResize(m_tfs, m_count);
ArrayCopy(m_tfs, tfs, 0, 0, m_count);
}
//--- Remove all registered timeframes.
void Reset()
{
m_count = 0;
ArrayResize(m_tfs, 0);
ArrayResize(m_last, 0);
}
//--- How many TFs are registered?
int Count() const { return m_count; }
//--- Read the TF at position i (0 = HTF end).
ENUM_TIMEFRAMES GetTF(int i) const
{
if(i < 0 || i >= m_count) return PERIOD_CURRENT;
return m_tfs[i];
}
//================================================================
// SINGLE-FUNCTION RUNNERS
//================================================================
//--- Run one bool function across all TFs in HTF→LTF order.
// Returns TRUE only when EVERY timeframe's condition is TRUE.
// Short-circuits on the first FALSE (like a logical AND chain).
bool Run(TFConditionFunc func)
{
if(m_count == 0 || func == NULL)
{
Print("CMultiTF::Run — no timeframes registered or null function.");
return false;
}
PrepareResults();
for(int i = 0; i < m_count; i++)
{
m_last[i].checked_at = TimeCurrent();
m_last[i].passed = func(m_tfs[i]);
if(!m_last[i].passed)
return false; // short-circuit — HTF/LTF alignment broken
}
return true;
}
//--- Like Run() but evaluates ALL timeframes even after a failure.
// Results are written into `out[]`.
// Returns the number of timeframes that passed.
int RunAll(TFConditionFunc func, bool &out[])
{
if(m_count == 0 || func == NULL)
{
Print("CMultiTF::RunAll — no timeframes registered or null function.");
return 0;
}
PrepareResults();
ArrayResize(out, m_count);
int passed_count = 0;
for(int i = 0; i < m_count; i++)
{
m_last[i].checked_at = TimeCurrent();
m_last[i].passed = func(m_tfs[i]);
out[i] = m_last[i].passed;
if(out[i]) passed_count++;
}
return passed_count;
}
//================================================================
// MAPPED RUNNER — one function per TF
//================================================================
//--- Assign a *different* function to each TF slot.
// `funcs[]` must have the same length as the registered TF stack.
// Returns TRUE only when every slot passes.
// Short-circuits on first FALSE.
bool RunMapped(TFConditionFunc &funcs[])
{
if(m_count == 0)
{
Print("CMultiTF::RunMapped — no timeframes registered.");
return false;
}
if(ArraySize(funcs) != m_count)
{
PrintFormat("CMultiTF::RunMapped — function map size (%d) != TF count (%d).",
ArraySize(funcs), m_count);
return false;
}
PrepareResults();
for(int i = 0; i < m_count; i++)
{
if(funcs[i] == NULL)
{
PrintFormat("CMultiTF::RunMapped — null function at slot %d (TF=%s). Skipping.",
i, EnumToString(m_tfs[i]));
m_last[i].passed = false;
return false;
}
m_last[i].checked_at = TimeCurrent();
m_last[i].passed = funcs[i](m_tfs[i]);
if(!m_last[i].passed)
return false;
}
return true;
}
//================================================================
// CONDITIONAL RUNNER WITH MINIMUM PASS THRESHOLD
//================================================================
//--- Returns TRUE when at least `minPass` timeframes pass.
// Useful for "3-of-4 TF confluence" rules.
bool RunAtLeast(TFConditionFunc func, int minPass)
{
bool results[];
int n = RunAll(func, results);
return (n >= minPass);
}
//================================================================
// DIAGNOSTICS
//================================================================
//--- Print the last run's per-TF results to the Experts log.
void PrintLastResults() const
{
for(int i = 0; i < m_count; i++)
{
PrintFormat(" [%d] TF=%-10s | passed=%s | at=%s",
i,
EnumToString(m_last[i].timeframe),
(m_last[i].passed ? "YES" : "NO "),
TimeToString(m_last[i].checked_at, TIME_DATE | TIME_SECONDS));
}
}
//--- Copy the last results snapshot into `out[]`.
void GetLastResults(MTF_Result &out[]) const
{
ArrayResize(out, m_count);
ArrayCopy(out, m_last, 0, 0, m_count);
}
//--- Was TF at index i passing in the last run?
bool LastPassed(int i) const
{
if(i < 0 || i >= m_count) return false;
return m_last[i].passed;
}
};
//+------------------------------------------------------------------+
// END OF FRAMEWORK
//+------------------------------------------------------------------+