//+------------------------------------------------------------------+ //| signalGate.mqh | //| Once-Per-Signal Gate (devKit) | //| | //| PURPOSE | //| ------- | //| Eliminates the "repeated execution on every tick" problem for | //| any logic that should fire ONCE per signal: | //| • position opening | //| • alert / push notification sending | //| • drawing objects, logging, webhooks, etc. | //| | //| CORE CONCEPT | //| ----------- | //| A "signal key" identifies one unique signal event. | //| Typically this is the opening time of the triggering candle | //| (iTime(Symbol(), tf, 1)). As long as OnTick() keeps seeing | //| the same key the gate stays locked. The moment the key changes | //| the gate automatically re-arms for the next execution. | //| | //| Each named "slot" is an independent gate. One slot per | //| action type (e.g. "buy_open", "buy_alert", "sell_open", …). | //| | //| QUICK-START | //| ----------- | //| #include "../devKit/signalGate.mqh" | //| | //| CSignalGate gate; | //| | //| void OpenBuy() { msafwa.Buy(...); } | //| bool TryAlert() { return SendNotification("Signal!"); } | //| | //| void OnTick() { | //| datetime key = iTime(Symbol(), PERIOD_H1, 1); | //| if(buySignal) { | //| gate.TryOnce("buy_open", key, OpenBuy); | //| gate.TryOnceResult("buy_alert", key, TryAlert, ok); | //| } | //| } | //+------------------------------------------------------------------+ #pragma once //------------------------------------------------------------------- // Action function pointer types //------------------------------------------------------------------- typedef void (*GateAction)(); // fire-and-forget (e.g. open position) typedef bool (*GateBoolAction)(); // action with success report (e.g. alert/send) //------------------------------------------------------------------- // GateSlot — internal state for one named gate //------------------------------------------------------------------- struct GateSlot { string name; // developer-assigned identifier datetime last_key; // signal key that last triggered this slot bool last_result; // result of last bool action (false for void) int exec_count; // total lifetime executions through this slot bool locked; // manual override: lock regardless of key }; //------------------------------------------------------------------- // CSignalGate — the main gate manager //------------------------------------------------------------------- class CSignalGate { private: GateSlot m_slots[]; int m_count; //--- Find slot index by name, return -1 if not found int FindSlot(const string name) const { for(int i = 0; i < m_count; i++) if(m_slots[i].name == name) return i; return -1; } //--- Return existing slot index or create a new one int EnsureSlot(const string name) { int idx = FindSlot(name); if(idx != -1) return idx; ArrayResize(m_slots, m_count + 1); m_slots[m_count].name = name; m_slots[m_count].last_key = 0; m_slots[m_count].last_result = false; m_slots[m_count].exec_count = 0; m_slots[m_count].locked = false; return m_count++; } //--- Core gate check: returns true when the slot should fire bool ShouldFire(int idx, datetime signal_key) const { if(m_slots[idx].locked) return false; // manual lock return (m_slots[idx].last_key != signal_key); } public: //--- ctor CSignalGate() : m_count(0) { ArrayResize(m_slots, 0); } ~CSignalGate() {} //================================================================ // PRE-REGISTER SLOTS (optional, improves clarity) //================================================================ //--- Explicitly register a named slot before use. // Not required — EnsureSlot handles lazy creation — but useful // for documenting which slots an EA uses at a glance. void Register(const string name) { EnsureSlot(name); } //================================================================ // FIRE-AND-FORGET (void actions: position open, draw object…) //================================================================ //--- Execute `action` once per unique `signal_key`. // Returns true if the action was executed this call, // false if the gate was still locked (already fired for this key). bool TryOnce(const string slot, datetime signal_key, GateAction action) { if(action == NULL) { Print("CSignalGate::TryOnce — null action for slot '", slot, "'."); return false; } int idx = EnsureSlot(slot); if(!ShouldFire(idx, signal_key)) return false; action(); m_slots[idx].last_key = signal_key; m_slots[idx].last_result = false; m_slots[idx].exec_count++; return true; } //================================================================ // BOOL ACTION (actions that report success: alerts, sends…) //================================================================ //--- Like TryOnce but captures the action's bool return value. // `fired` — set to true if the gate allowed execution. // `result` — the action's return value (only meaningful when fired=true). // Returns the same value as `fired`. bool TryOnceResult(const string slot, datetime signal_key, GateBoolAction action, bool &result) { result = false; if(action == NULL) { Print("CSignalGate::TryOnceResult — null action for slot '", slot, "'."); return false; } int idx = EnsureSlot(slot); if(!ShouldFire(idx, signal_key)) return false; result = action(); m_slots[idx].last_key = signal_key; m_slots[idx].last_result = result; m_slots[idx].exec_count++; return true; } //================================================================ // INLINE / MACRO-STYLE — execute without a function pointer //================================================================ //--- Returns true ONCE per signal_key, then locks until key changes. // Use this when you want to write the action inline: // // if(gate.Once("buy_open", key)) { // msafwa.Buy(...); // only runs once per candle // } // bool Once(const string slot, datetime signal_key) { int idx = EnsureSlot(slot); if(!ShouldFire(idx, signal_key)) return false; m_slots[idx].last_key = signal_key; m_slots[idx].exec_count++; return true; } //================================================================ // INSPECTION //================================================================ //--- Would this slot fire right now without executing? bool WouldFire(const string slot, datetime signal_key) const { int idx = FindSlot(slot); if(idx == -1) return true; // unknown slot → would fire (first time) return ShouldFire(idx, signal_key); } //--- Get the signal key that last triggered a slot (0 = never fired). datetime LastKey(const string slot) const { int idx = FindSlot(slot); return (idx == -1) ? 0 : m_slots[idx].last_key; } //--- Get the last bool-action result for a slot. bool LastResult(const string slot) const { int idx = FindSlot(slot); return (idx == -1) ? false : m_slots[idx].last_result; } //--- How many times has this slot executed in total? int ExecCount(const string slot) const { int idx = FindSlot(slot); return (idx == -1) ? 0 : m_slots[idx].exec_count; } //================================================================ // RESET //================================================================ //--- Re-arm a single slot so it will fire on the very next call, // regardless of whether the signal key has changed. void Reset(const string slot) { int idx = FindSlot(slot); if(idx != -1) { m_slots[idx].last_key = 0; m_slots[idx].locked = false; } } //--- Re-arm ALL registered slots. void ResetAll() { for(int i = 0; i < m_count; i++) { m_slots[i].last_key = 0; m_slots[i].locked = false; } } //================================================================ // MANUAL LOCK / UNLOCK //================================================================ //--- Permanently lock a slot until you explicitly Unlock() it. // Useful when an external condition should suppress a gate // (e.g. max daily trades reached, news filter active). void Lock(const string slot) { int idx = EnsureSlot(slot); m_slots[idx].locked = true; } void Unlock(const string slot) { int idx = EnsureSlot(slot); m_slots[idx].locked = false; } bool IsLocked(const string slot) const { int idx = FindSlot(slot); return (idx == -1) ? false : m_slots[idx].locked; } //================================================================ // DIAGNOSTICS //================================================================ //--- Dump all slot states to the Experts log. void PrintStatus() const { Print("--- CSignalGate status (", m_count, " slots) ---"); for(int i = 0; i < m_count; i++) { PrintFormat(" [%d] %-20s | last_key=%s | execs=%d | locked=%s | last_result=%s", i, m_slots[i].name, (m_slots[i].last_key == 0 ? "never" : TimeToString(m_slots[i].last_key, TIME_DATE | TIME_SECONDS)), m_slots[i].exec_count, (m_slots[i].locked ? "YES" : "no"), (m_slots[i].last_result ? "true" : "false")); } Print("-------------------------------------------"); } }; //+------------------------------------------------------------------+ // END OF FRAMEWORK //+------------------------------------------------------------------+