devKit/signalGate.mqh

305 lines
12 KiB
MQL5
Raw Permalink Normal View History

2026-07-12 21:21:20 +03:00
//+------------------------------------------------------------------+
//| 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
//+------------------------------------------------------------------+