305 lines
12 KiB
MQL5
305 lines
12 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| 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
|
||
|
|
//+------------------------------------------------------------------+
|