//+------------------------------------------------------------------+ //| 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 //+------------------------------------------------------------------+