-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHCPropsController.mq5
More file actions
602 lines (527 loc) · 25.4 KB
/
Copy pathHCPropsController.mq5
File metadata and controls
602 lines (527 loc) · 25.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
//+------------------------------------------------------------------+
//| HCPropsController.mq5 |
//| Copy-trading (Master/Slave) + prop-firm guardian + news filter |
//| Single EA, file-based sync on the same VPS. No backend/license. |
//+------------------------------------------------------------------+
#property strict
#property version "2.51"
#property description "HCPropsController: Master/Slave copy trading, prop-firm limits and news filter in a single EA."
#property description "v2.51: total (cumulative) trades limit that never resets daily."
#property description "v2.50: daily net W/L tally limit (wins +1 / losses -1, stop at an upper/lower bound)."
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\OrderInfo.mqh>
#include <Trade\DealInfo.mqh>
#include <Trade\AccountInfo.mqh>
#include <Trade\SymbolInfo.mqh>
//===================================================================
// ENUMS
//===================================================================
enum HCMode
{
MODE_MASTER = 0, // Master (executes trades and replicates to Slaves)
MODE_SLAVE = 1, // Slave (replicates trades from a Master)
MODE_NONE = 2 // None (risk management only; no replication, no shared files)
};
enum HCCopyMode
{
COPY_NORMAL = 0, // NORMAL (also replicates SL/TP modifications)
COPY_INCOGNITO = 1 // INCOGNITO (SL/TP only on open; ignores changes)
};
enum HCNewsMode
{
NEWS_OPERATE = 0, // OPERATE (do nothing)
NEWS_PAUSE_OPEN = 1, // PAUSE_OPEN (block new entries; keep positions)
NEWS_CLOSE_ALL = 2 // CLOSE_ALL (close all + block)
};
// Values aligned with ENUM_CALENDAR_EVENT_IMPORTANCE (LOW=1, MODERATE=2, HIGH=3)
enum HCNewsImpact
{
NEWS_IMP_LOW = 1, // Low or higher
NEWS_IMP_MODERATE = 2, // Moderate or higher
NEWS_IMP_HIGH = 3 // High impact only
};
//===================================================================
// INPUT PARAMETERS
//===================================================================
input group "=== GENERAL SETTINGS ==="
input HCMode Mode = MODE_MASTER; // Operation mode
input bool PropFirmMode = true; // Enable limits guardian (all modes)
input double ForceInitialBalance = 0.0; // Force initial balance (0 = auto-detect)
input datetime HistoryFromDate = 0; // Ignore history before this time (account reset); 0 = full history
input bool ResetCountersOnInit = false; // Reset counters and locks on init
input group "=== SYNC FILE ==="
input string FileName = "master_00001.csv"; // Shared file name (Master and Slave must match; bump 00001 for more)
input string CustomFilePath = ""; // Custom path inside Common\Files (overrides FileName)
input string Symbols = ""; // (MASTER) Symbols to replicate, comma-sep (empty = all)
input group "=== SLAVE SETTINGS (SLAVE mode only) ==="
input string SymbolMapping = ""; // Mapping MAST:SLAV;MAST2:SLAV2 (optional)
input HCCopyMode CopyMode = COPY_NORMAL;// Copy mode
input bool InverseMode = true; // Invert Master trades (reverse direction; mirror SL/TP)
input double RiskMultiplier = 1.0; // Lot multiplier (Slave lot = Master lot x mult)
input bool AutoLotScaling = true; // Auto-scale lots by point value (contract size differences)
input int Slippage = 10; // Allowed slippage (points)
input long MagicNumber = 987654; // Magic Number of the Slave orders
input group "=== PROPAGATION (set the same on Master and Slaves) ==="
input bool PropagateSlaveClose = true; // Slave closes AND trading locks propagate to the Master
input group "=== MULTI-ACCOUNT SAFETY (MASTER mode only) ==="
input int ExpectedSlaves = 0; // Fresh Slave heartbeats required for new entries; 0 = off
input int SlaveHeartbeatTimeoutSec = 15; // Slave heartbeat considered stale after (seconds)
input group "=== LOGGING ==="
input bool TradePairLog = true; // Per-account trade log (<syncfile>.trades.<login>.csv) for hedge reconciliation
// The guardian below runs in BOTH modes. On the Master it gates the order-placing
// EA (global variable + deletes pendings) and closes positions on breach. On a
// Slave it does the same on its own account and, with PropagateSlaveClose=true,
// reports its lock to the Master: the Master only accepts new orders while EVERY
// account (Master and all Slaves) has trading enabled. Slave breaches that close
// positions also close the originals on the Master (and so on every other Slave).
input group "=== EQUITY LIMITS ==="
input double DailyProfitLimitPercent = 4.6; // Daily profit limit (%); 0 = no limit
input double DailyLossLimitPercent = 4.6; // Daily loss limit (%); 0 = no limit
input double TotalProfitLimitPercent = 8.1; // Total profit limit (%); 0 = no limit
input double TotalLossLimitPercent = 8.1; // Total loss limit (%); 0 = no limit
input group "=== TRADING LIMITS ==="
input int MaxParallelTrades = 1; // Parallel trades limit; 0 = no limit
input int MaxTradesPerDay = 1; // Trades per day limit; 0 = no limit
input int MaxTradesTotal = 0; // Total trades limit (never resets daily); 0 = no limit
input int MaxConsecLossesPerDay = 0; // Consecutive losses per day limit; 0 = no limit
input int MaxConsecWinsPerDay = 0; // Consecutive wins per day limit; 0 = no limit
input int MaxDailyNetWins = 0; // Daily net W/L tally: stop at wins-losses >= +this; 0 = no limit
input int MaxDailyNetLosses = 0; // Daily net W/L tally: stop at wins-losses <= -this; 0 = no limit
input group "=== DAILY RESET ==="
input int DailyResetHour = 0; // Daily reset hour (0-23)
input int DailyResetMinute = 0; // Daily reset minute (0-59)
input group "=== TRADING HOURS ==="
input bool LimitTradingHours = true; // Limit new entries to the specified hours
input int TradingStartHour = 6; // Trading start hour (0-23)
input int TradingStartMinute = 0; // Trading start minute (0-59)
input int TradingEndHour = 20; // Trading end hour (0-23)
input int TradingEndMinute = 0; // Trading end minute (0-59)
input group "=== FORCED CLOSE ==="
input bool ForceExitEnabled = true; // Force close at the specified time
input int TradingExitHour = 22; // Forced close hour (0-23)
input int TradingExitMinute = 0; // Forced close minute (0-59)
input group "=== NEWS PROTECTION ==="
input HCNewsMode NewsMode = NEWS_OPERATE; // News handling mode
input int NewsDuration = 120; // Protection before and after (seconds)
input string NewsCurrencies = ""; // Currencies to watch (e.g. EUR,USD,GBP); empty = chart symbol
input HCNewsImpact NewsMinImpact = NEWS_IMP_HIGH; // Minimum impact to consider
//===================================================================
// GLOBAL VARIABLES (keys)
//===================================================================
string HCPROPS_KEY = "HCPropsController";
string GV_DISABLE = "HCPropsControllerDisableTrading"; // signal respected by patched SQX EAs
string GV_TOTAL_LOCK = "HCPropsController_TotalLocked";
string GV_DAILY_LOCK = "HCPropsController_DailyLocked";
string GV_INIT_BAL = "HCPropsController_InitBalance";
string GV_INIT_EQD = "HCPropsController_InitEquityDaily";
string GV_NEXT_RESET = "HCPropsController_NextReset";
//===================================================================
// TRADING HELPERS (global lock signal)
//===================================================================
void DisableTrading() { GlobalVariableSet(GV_DISABLE, 1.0); }
void EnableTrading() { GlobalVariableDel(GV_DISABLE); }
bool TradingIsDisabled() { return(GlobalVariableCheck(GV_DISABLE) && GlobalVariableGet(GV_DISABLE) == 1.0); }
string ModeName() { return (Mode == MODE_MASTER ? "MASTER" : (Mode == MODE_SLAVE ? "SLAVE" : "NONE")); }
//===================================================================
// RUNTIME STATE
//===================================================================
double AccountDepositsAndWithdrawals = 0.0; // initial balance (reference for total %)
double InitialEquityDaily = 0.0;
datetime NextDailyResetTime = 0;
datetime NextForceExitTime = 0;
double DailyUpperLimitEquity = 0.0;
double DailyLowerLimitEquity = 0.0;
double TotalUpperLimitEquity = 0.0;
double TotalLowerLimitEquity = 0.0;
int TradesOpenedToday = 0;
int TradesOpenedTotal = 0; // cumulative since account start / HistoryFromDate (no daily reset)
int CurrentTradesCount = 0;
int ConsecutiveWinsToday = 0;
int ConsecutiveLossesToday = 0;
int NetTradesToday = 0; // wins (+1) minus losses (-1) since the daily reset
// Lock flags
bool IsGlobalTradingDisabled = false; // total limit (sticky until ResetCountersOnInit)
bool IsDailyLimitTradingDisabled= false; // daily equity limit (sticky until daily reset)
bool IsDailyNumberTradingDisabled = false;
bool IsTotalNumberTradingDisabled = false; // cumulative trades cap (never resets daily)
bool IsParallelTradesDisabled = false;
bool IsTradingHoursDisabled = false;
bool IsConsecWinsDisabled = false;
bool IsConsecLossesDisabled = false;
bool IsNetWinsDisabled = false; // daily net W/L tally hit upper bound (latched until reset)
bool IsNetLossesDisabled = false; // daily net W/L tally hit lower bound (latched until reset)
bool IsNewsBlocked = false;
bool TotalLocked = false; // persistent state of the total lock
bool DidCloseOrders = false;
bool DidClosePositions = false;
// Lock + heartbeat propagation (Slave guardian -> Master)
bool IsSlaveLockTradingDisabled = false; // MASTER: some Slave reports trading disabled
bool IsSlaveDownTradingDisabled = false; // MASTER: fewer fresh Slave heartbeats than ExpectedSlaves
string g_slaveLockInfo = ""; // MASTER: which Slave(s)/reason(s), for log + panel
string g_slaveDownInfo = ""; // MASTER: heartbeat summary, for log + panel
int g_slvFreshCount = 0; // MASTER: Slaves with a fresh heartbeat
// MASTER: cached per-Slave status (survives torn reads of a status file)
string g_slvLogin[];
datetime g_slvHb[]; // heartbeat stamp (TimeLocal of the Slave = same machine clock)
bool g_slvLocked[];
string g_slvReason[];
// SLAVE: status file state
bool g_statusDirty = true; // a write is pending (new close request / failed write)
bool g_lastLockState = false; // last lock state written (for transition logging)
// Dashboard
string LastDashboardValues[];
bool DashboardNeedsUpdate = true;
// Synchronization
string LastPositionsHash = "";
bool SyncFileInitialized = false;
bool MasterFileExists = false;
bool SlaveWarningShown = false;
ulong g_syncSeq = 0; // Master: write sequence (monotonic across EA restarts)
int g_timerTick = 0; // 200 ms timer tick counter (every 5th = ~1 s work)
// News (cache)
datetime g_newsTimes[];
string g_newsCurr[];
string g_newsName[];
datetime g_lastNewsFetch = 0;
string g_activeNews = "";
datetime g_activeNewsEnd = 0; // end of the currently active protection window
//===================================================================
// MODULES
//===================================================================
#include "HCProps_Util.mqh"
#include "HCProps_Guardian.mqh"
#include "HCProps_News.mqh"
#include "HCProps_Master.mqh"
#include "HCProps_Slave.mqh"
#include "HCProps_Panel.mqh"
//===================================================================
// INIT
//===================================================================
int OnInit()
{
Print("HCPropsController v2 initialized. Mode: ", ModeName());
// Time-range validations (the guardian runs in BOTH modes)
if(DailyResetHour < 0 || DailyResetHour > 23 || DailyResetMinute < 0 || DailyResetMinute > 59)
{ Print("ERROR: Daily reset out of range"); return INIT_PARAMETERS_INCORRECT; }
if(LimitTradingHours &&
(TradingStartHour < 0 || TradingStartHour > 23 || TradingStartMinute < 0 || TradingStartMinute > 59 ||
TradingEndHour < 0 || TradingEndHour > 23 || TradingEndMinute < 0 || TradingEndMinute > 59))
{ Print("ERROR: Trading hours out of range"); return INIT_PARAMETERS_INCORRECT; }
if(ForceExitEnabled &&
(TradingExitHour < 0 || TradingExitHour > 23 || TradingExitMinute < 0 || TradingExitMinute > 59))
{ Print("ERROR: Forced close out of range"); return INIT_PARAMETERS_INCORRECT; }
if(NewsMode != NEWS_OPERATE && NewsDuration < 0)
{ Print("ERROR: NewsDuration must be >= 0"); return INIT_PARAMETERS_INCORRECT; }
if(ExpectedSlaves < 0)
{ Print("ERROR: ExpectedSlaves must be >= 0"); return INIT_PARAMETERS_INCORRECT; }
if(ExpectedSlaves > 0 && SlaveHeartbeatTimeoutSec < 5)
{ Print("ERROR: SlaveHeartbeatTimeoutSec must be >= 5"); return INIT_PARAMETERS_INCORRECT; }
if(HistoryFromDate > 0)
{
if(HistoryFromDate > TimeCurrent())
{ Print("ERROR: HistoryFromDate is in the future"); return INIT_PARAMETERS_INCORRECT; }
Print("HistoryFromDate active: ignoring account history before ",
TimeToString(HistoryFromDate, TIME_DATE | TIME_SECONDS),
" (initial balance = balance as of that moment)");
}
// SLAVE validation: needs a shared file name to read from
if(Mode == MODE_SLAVE)
{
if(FileName == "" && CustomFilePath == "")
{
Print("ERROR: In SLAVE mode set FileName (must match the Master's FileName), or CustomFilePath");
return INIT_PARAMETERS_INCORRECT;
}
}
// The per-ticket replication model needs a hedging account.
if((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE) != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
{
if(Mode == MODE_SLAVE)
{
Print("ERROR: SLAVE mode requires a HEDGING account (per-ticket mapping breaks on netting accounts).");
return INIT_PARAMETERS_INCORRECT;
}
Print("WARNING: this account is not HEDGING - per-ticket replication may misbehave on netting accounts.");
}
// MASTER: refuse a second Master instance on the same FileName in this terminal.
if(Mode == MODE_MASTER)
{
long other = GlobalVariableCheck(MasterMutexGVName()) ? (long)GlobalVariableGet(MasterMutexGVName()) : 0;
if(other != 0 && other != ChartID() && ChartPeriod(other) > 0)
{
Print("ERROR: another MASTER (chart ", other, ") already serves '", SyncFileLabel(),
"' in this terminal. Remove one of them (or use a different FileName).");
return INIT_PARAMETERS_INCORRECT;
}
GlobalVariableSet(MasterMutexGVName(), (double)ChartID());
}
if(Mode != MODE_NONE)
CleanupLegacySideFiles(); // v2.20 .close.*/.lock.* files are replaced by .slave.<login>
CalculateAccountDepositsAndWithdrawals();
// ---- Guardian init (both modes; on the Slave it protects its own account) ----
// Reset persistent state if requested
if(ResetCountersOnInit)
{
GlobalVariableDel(GV_TOTAL_LOCK);
GlobalVariableDel(GV_DAILY_LOCK);
GlobalVariableDel(GV_INIT_BAL);
GlobalVariableDel(GV_INIT_EQD);
GlobalVariableDel(GV_NEXT_RESET);
// An inputs-change reinit keeps the program's globals alive: clear the
// in-memory latches too, or PersistState() below would write the stale
// lock straight back into the GlobalVariables.
TotalLocked = false;
IsGlobalTradingDisabled = false;
IsDailyLimitTradingDisabled = false;
DidCloseOrders = false;
DidClosePositions = false;
EnableTrading();
Print("ResetCountersOnInit: state cleared");
}
// Restore baseline + locks from GlobalVariables (survives restarts/VPS crashes)
bool restored = false;
if(PropFirmMode && !ResetCountersOnInit && GlobalVariableCheck(GV_INIT_BAL))
{
if(GlobalVariableGet(GV_INIT_BAL) > 0)
AccountDepositsAndWithdrawals = GlobalVariableGet(GV_INIT_BAL);
InitialEquityDaily = GlobalVariableGet(GV_INIT_EQD);
NextDailyResetTime = (datetime)GlobalVariableGet(GV_NEXT_RESET);
TotalLocked = (GlobalVariableCheck(GV_TOTAL_LOCK) && GlobalVariableGet(GV_TOTAL_LOCK) == 1.0);
IsDailyLimitTradingDisabled = (GlobalVariableCheck(GV_DAILY_LOCK) && GlobalVariableGet(GV_DAILY_LOCK) == 1.0);
restored = (InitialEquityDaily > 0 && NextDailyResetTime > 0);
if(restored)
Print("State restored from GlobalVariables. TotalLocked=", TotalLocked, " InitEquityDaily=", InitialEquityDaily);
}
if(!restored)
{
CalculateInitialEquityDaily();
CalculateNextDailyResetTime();
}
CalculateTotalLimits();
CalculateDailyLimits();
if(ForceExitEnabled)
CalculateNextForceExitTime();
Sleep(100);
CountTradesOpenedToday();
CountCurrentTrades();
CountConsecutiveWinsLosses();
// If the reset time passed while the EA was off, reset now
if(restored && TimeCurrent() >= NextDailyResetTime)
PerformDailyReset();
PersistState();
if(PropFirmMode)
CheckGuardRules();
CheckNews();
Print(ModeName(), " OnInit: PropFirmMode=", PropFirmMode,
" TradesToday=", TradesOpenedToday, "/", MaxTradesPerDay);
// 200 ms timer: fast Slave reaction / close-request processing.
// Heavy 1-second work runs on every 5th tick (see OnTimer).
EventSetMillisecondTimer(200);
ArrayResize(LastDashboardValues, 64);
for(int i = 0; i < 64; i++)
LastDashboardValues[i] = "";
DashboardNeedsUpdate = true;
CreateDashboard();
if(Mode == MODE_MASTER)
{
Sleep(50);
LastPositionsHash = "";
SyncFileInitialized = false;
Print("OnInit: syncing initial positions. Positions: ", PositionsTotal());
SyncPositionsToFile();
}
else if(Mode == MODE_SLAVE)
{
string rel = GetSyncFilePath();
MasterFileExists = FileIsExist(rel, FILE_COMMON);
if(MasterFileExists)
Print("SLAVE: Master file found: ", rel);
else
{
Print("SLAVE: Master file NOT found on start: ", rel);
Print("SLAVE: check that FileName matches the Master's FileName exactly.");
SlaveWarningShown = true;
}
WriteSlaveStatusFile(); // publish heartbeat + lock state right away
}
// MODE_NONE: guardian only - no shared files of any kind
return INIT_SUCCEEDED;
}
//===================================================================
// TIMER
//===================================================================
void OnTimer()
{
g_timerTick++;
bool fullTick = (g_timerTick % 5 == 0); // timer runs at 200 ms; ~1 s cadence for heavy work
// ---- every 200 ms: the fast paths ----
if(Mode == MODE_MASTER)
ProcessSlaveStatusFiles(); // close requests + lock flags + heartbeats
else if(Mode == MODE_SLAVE)
{
SlaveSync(); // replicate / reconcile
if(g_statusDirty)
WriteSlaveStatusFile(); // pending close requests / failed write retry
}
// Equity limits react to fast spikes: check at the full 200 ms cadence.
if(PropFirmMode)
{
CheckEquityLimits();
CheckAndUpdateTradingStatus();
}
if(!fullTick)
return;
// ---- ~1 s: guardian, news, status propagation, dashboard (both modes) ----
if(PropFirmMode)
{
if(TimeCurrent() >= NextDailyResetTime)
PerformDailyReset();
if(ForceExitEnabled && NextForceExitTime > 0 && TimeCurrent() >= NextForceExitTime)
{
if(Mode == MODE_SLAVE && PropagateSlaveClose)
{
EnqueueAllReplicatedCloses("FORCE_EXIT"); // flatten the Master legs too
WriteSlaveStatusFile();
}
CloseAllPositions(true);
CalculateNextForceExitTime();
Print("Forced close executed. Next: ", TimeToString(NextForceExitTime));
}
CheckGuardRules();
}
CheckNews(); // also manages trading state when PropFirmMode=false
if(Mode == MODE_MASTER)
SyncPositionsToFile();
else if(Mode == MODE_SLAVE)
WriteSlaveStatusFile(); // heartbeat (and lock/close lines) refresh
// Housekeeping: keep the per-ticket throttle/warn arrays bounded.
PruneThrottle(g_openTryTicket, g_openTryWhenMs, 600000);
PruneThrottle(g_closeTryTicket, g_closeTryWhenMs, 600000);
PruneThrottle(g_ackLogTicket, g_ackLogWhenMs, 600000);
if(Mode == MODE_SLAVE)
PruneMapGVs();
else if(Mode == MODE_MASTER)
PruneWarnedExcluded();
UpdateDashboard();
}
//===================================================================
// TRADE DETECTION ON MASTER
//===================================================================
void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request,
const MqlTradeResult &result)
{
if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
{
HistorySelect(0, TimeCurrent());
bool ok = HistoryDealSelect(trans.deal);
int retry = 0;
while(!ok && retry < 3) { Sleep(10); HistorySelect(0, TimeCurrent()); ok = HistoryDealSelect(trans.deal); retry++; }
// Counters feed the guardian in BOTH modes
if(ok)
{
CDealInfo deal; deal.Ticket(trans.deal);
if(deal.Entry() == DEAL_ENTRY_IN)
CountTradesOpenedToday();
else if(deal.Entry() == DEAL_ENTRY_OUT)
{
Sleep(10);
CountConsecutiveWinsLosses();
DashboardNeedsUpdate = true;
}
}
else
CountTradesOpenedToday();
CountCurrentTrades();
if(PropFirmMode)
CheckGuardRules();
if(ok && Mode != MODE_NONE)
LogClosedDeal(trans.deal); // hedge-reconciliation trade log (no shared files in NONE)
if(Mode == MODE_MASTER)
SyncPositionsToFile();
else if(Mode == MODE_SLAVE && ok)
SlavePropagateCloseFromDeal(trans.deal);
}
if(trans.type == TRADE_TRANSACTION_POSITION && Mode == MODE_MASTER)
SyncPositionsToFile();
}
// SLAVE: when our own SL/TP (or a manual close / stop out) closes a mirrored
// position while the Master still holds it, ask the Master to close it too,
// so the Master and every other Slave flatten immediately.
void SlavePropagateCloseFromDeal(ulong dealTicket)
{
if(!PropagateSlaveClose)
return;
if(HistoryDealGetInteger(dealTicket, DEAL_MAGIC) != MagicNumber)
return;
ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
if(entry != DEAL_ENTRY_OUT && entry != DEAL_ENTRY_OUT_BY)
return;
ENUM_DEAL_REASON reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(dealTicket, DEAL_REASON);
// DEAL_REASON_EXPERT = our own sync/guardian close (already propagated or Master-initiated).
bool propagate = (reason == DEAL_REASON_SL || reason == DEAL_REASON_TP || reason == DEAL_REASON_SO ||
reason == DEAL_REASON_CLIENT || reason == DEAL_REASON_MOBILE || reason == DEAL_REASON_WEB);
if(!propagate)
return;
ulong posId = (ulong)HistoryDealGetInteger(dealTicket, DEAL_POSITION_ID);
if(posId == 0)
return;
if(PositionSelectByTicket(posId))
return; // partial close: the position is still open, keep mirroring
ulong mTicket = MasterTicketForSlavePosition(posId);
if(mTicket == 0)
return;
string rs = (reason == DEAL_REASON_SL ? "SL" :
reason == DEAL_REASON_TP ? "TP" :
reason == DEAL_REASON_SO ? "STOPOUT" : "MANUAL");
Print("SLAVE: mirrored position closed by ", rs, " -> requesting Master close of #", mTicket);
EnqueueMasterClose(mTicket, rs);
WriteSlaveStatusFile(); // get the request on disk immediately
}
//===================================================================
// DEINIT
//===================================================================
void OnDeinit(const int reason)
{
EventKillTimer();
DeleteDashboard();
if(Mode == MODE_MASTER)
{
// We do not force EnableTrading here to avoid lifting a limit lock on a mere recompile.
// Delete the sync file so Slaves detect the disconnection.
string rel = GetSyncFilePath();
if(FileIsExist(rel, FILE_COMMON))
FileDelete(rel, FILE_COMMON);
// If the EA is removed for good (not a recompile), release the lock signal
// and the one-Master-per-file mutex.
if(reason == REASON_REMOVE || reason == REASON_CHARTCLOSE)
{
EnableTrading();
if(GlobalVariableCheck(MasterMutexGVName()) &&
(long)GlobalVariableGet(MasterMutexGVName()) == ChartID())
GlobalVariableDel(MasterMutexGVName());
}
}
else if(Mode == MODE_SLAVE)
{
// Removed for good: take this Slave out of the system (its status file is
// the Master's heartbeat/lock source). On a mere recompile/restart the file
// stays put; the heartbeat goes briefly stale and refreshes on re-init.
// NOTE: with ExpectedSlaves > 0 on the Master, removing a Slave makes the
// Master block new entries until ExpectedSlaves is adjusted - intended.
if(reason == REASON_REMOVE || reason == REASON_CHARTCLOSE)
{
string st = SlaveStatusPath();
if(FileIsExist(st, FILE_COMMON))
FileDelete(st, FILE_COMMON);
EnableTrading();
}
}
else // NONE: nothing on disk to clean up; just release the lock signal on removal
{
if(reason == REASON_REMOVE || reason == REASON_CHARTCLOSE)
EnableTrading();
}
}
//+------------------------------------------------------------------+