forked from MrBaro75/Warrior_EA
118 lines
5.8 KiB
MQL5
118 lines
5.8 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| Warrior_EA |
| |||
//| AnimateDread |
| |||
//| |
| |||
//| THE PER-CONFIG CHART LOCK. STATELESS-except-one-field, same |
| |||
//| doctrine as BarrierHorizon\BarrierHorizon.mqh: m_configLockName |
| |||
//| is genuinely exclusive (grep-verified against the rest of |
| |||
//| Expert\ - nothing outside Lifecycle.mqh ever touched it), so this |
| |||
//| collaborator owns it as a real member instead of reaching it |
| |||
//| through the view. Every method below is a pure relocation of |
| |||
//| Expert\AIBase\Lifecycle.mqh's original AcquireConfigLock/ |
| |||
//| ReleaseConfigLock bodies - same order, same conditionals, no |
| |||
//| logic changes (the repeated m_activeFileName reads are cached to |
| |||
//| one local, since the view call replaces a direct field read that |
| |||
//| was itself invariant for the duration of the call). AcquireConfigLock |
| |||
//| has exactly one external caller (Topology.mqh, grep-verified), |
| |||
//| which keeps calling it unqualified - nothing to rewire. |
| |||
//+------------------------------------------------------------------+
| |||
#ifndef WARRIOR_CONFIGLOCK_CONFIGLOCK_MQH
| |||
#define WARRIOR_CONFIGLOCK_CONFIGLOCK_MQH
| |||
//--- Set when a start is refused for a reason no retry can change. OnInit's retry loop (Warrior_EA.mq5)
| |||
//--- reads it so the operator's LAST log line names the cause instead of "Failed to initialize Indicators".
| |||
string g_initFatalReason = "";
| |||
class CConfigLock
| |||
{
| |||
private:
| |||
CConfigLockView *m_view; // BORROWED - the signal owns the adapter, not the reverse
| |||
string m_configLockName;
| |||
public:
| |||
CConfigLock(void) : m_view(NULL), m_configLockName("") { }
| |||
void Bind(CConfigLockView *view) { m_view = view; }
| |||
| |||
bool Acquire(void);
| |||
void Release(void);
| |||
};
| |||
//+------------------------------------------------------------------+
| |||
//| Claim the resolved model filename for this chart, terminal-wide. |
| |||
//+------------------------------------------------------------------+
| |||
bool CConfigLock::Acquire(void)
| |||
{
| |||
string activeFileName = m_view.ActiveFileName();
| |||
//--- FNV-1a over the resolved filename: every retrain-affecting input is already folded into that
| |||
//--- name, so equal names mean genuinely equal configs and nothing else has to be compared. Hashed
| |||
//--- because MQL5 caps global-variable names at 63 characters and the path alone exceeds that.
| |||
uint h = 2166136261;
| |||
int len = StringLen(activeFileName);
| |||
for(int i = 0; i < len; i++)
| |||
{
| |||
h ^= (uint)StringGetCharacter(activeFileName, i);
| |||
h *= 16777619;
| |||
}
| |||
string name = "WarriorAI_" + m_view.ShortId() + "_" + StringFormat("%08x", h);
| |||
long self = ChartID();
| |||
//--- Atomic: true means it did not exist and is now ours.
| |||
if(GlobalVariableTemp(name))
| |||
{
| |||
GlobalVariableSet(name, (double)self);
| |||
m_configLockName = name;
| |||
return true;
| |||
}
| |||
long owner = (long)GlobalVariableGet(name);
| |||
//--- Our own entry: this chart is re-initializing after a parameter change or a recompile whose
| |||
//--- OnDeinit never reached Release(). Reclaim it instead of refusing to start.
| |||
if(owner == self)
| |||
{
| |||
m_configLockName = name;
| |||
return true;
| |||
}
| |||
//--- Owner recorded but its chart no longer runs an expert - take the claim over. owner == 0 is
| |||
//--- deliberately NOT treated as stale: it means another instance created the variable microseconds
| |||
//--- ago and has not stamped its id yet, which is a live claim, not a dead one.
| |||
bool ownerAlive = false;
| |||
if(owner != 0)
| |||
{
| |||
long id = ChartFirst();
| |||
while(id >= 0)
| |||
{
| |||
if(id == owner)
| |||
{
| |||
ownerAlive = (StringLen(ChartGetString(id, CHART_EXPERT_NAME)) > 0);
| |||
break;
| |||
}
| |||
id = ChartNext(id);
| |||
}
| |||
}
| |||
if(owner != 0 && !ownerAlive)
| |||
{
| |||
GlobalVariableSet(name, (double)self);
| |||
m_configLockName = name;
| |||
return true;
| |||
}
| |||
Print(m_view.Id() + ": REFUSED to start - another chart is already training this exact configuration. Both" +
| |||
" would save into the same files (" + activeFileName + ".nnw plus its .cfg/.stats/checkpoints)" +
| |||
" and overwrite each other's progress with no error reported anywhere. Owner: " +
| |||
(owner != 0 ? "chart " + IntegerToString(owner) + " (" + ChartSymbol(owner) + " " +
| |||
EnumToString((ENUM_TIMEFRAMES)ChartPeriod(owner)) + ")" : "another chart, still initializing") +
| |||
". Change the enabled NN set (Use_MLP/Use_CONV/Use_LSTM/Use_CONVLSTM) or any retrain-affecting" +
| |||
" input on THIS chart so it trains its own model, or remove one of the two charts. Note the" +
| |||
" private build defaults ALL FOUR direction NNs on - two same-symbol charts left at their" +
| |||
" defaults land here.");
| |||
g_initFatalReason = "another chart already owns this configuration (" + activeFileName + ")";
| |||
return false;
| |||
}
| |||
//+------------------------------------------------------------------+
| |||
//| Drop this instance's claim (see Acquire). |
| |||
//+------------------------------------------------------------------+
| |||
void CConfigLock::Release(void)
| |||
{
| |||
if(StringLen(m_configLockName) == 0)
| |||
return;
| |||
//--- Only delete a claim we still hold: if a later instance took this entry over via the stale-owner
| |||
//--- path above, deleting it here would silently hand the config to a third chart.
| |||
if((long)GlobalVariableGet(m_configLockName) == ChartID())
| |||
GlobalVariableDel(m_configLockName);
| |||
m_configLockName = "";
| |||
}
| |||
#endif // WARRIOR_CONFIGLOCK_CONFIGLOCK_MQH
| |||
//+------------------------------------------------------------------+
|