266 lines
9.6 KiB
MQL5
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
|
|
//+------------------------------------------------------------------+
|