| //+------------------------------------------------------------------+
|
| //| CopySlave_MT5.mq5 |
|
| //| Fast Local Copy Trade - SLAVE (MT4 -> MT5) v5.00 |
|
| //| File based | No DLL | Hedging account required |
|
| //+------------------------------------------------------------------+
|
| #property copyright "Fast Copy Trade System"
|
| #property version "5.00"
|
| #property description "SLAVE EA - fast local copy from MT4 Master (file, no DLL)"
|
| #property description "Hedging account + AutoTrading required. Attach to ONE chart."
|
|
|
| input group "============== FILE & CONNECTION =============="
|
| input string InpFileName = "CopyTrade_MT4_to_MT5.csv"; // Shared file name (same as Master)
|
| input long InpMasterAccount = 0; // Master account (0 = any) - SET on real
|
| input int InpStaleMs = 3000; // Master lost if no heartbeat for (ms)
|
|
|
| input group "============== SYMBOL MAPPING =============="
|
| input string InpReplaceFrom = ""; // Replace this in symbol
|
| input string InpReplaceTo = ""; // With this
|
| input string InpSymbolPrefix = ""; // Then PREFIX
|
| input string InpSymbolSuffix = "+"; // Then SUFFIX (your broker: +)
|
| input string InpPreloadSymbols = ""; // Preload (master names, comma sep) e.g. EURUSD,XAUUSD
|
|
|
| input group "============== LOT SETTINGS =============="
|
| input double InpLotMultiplier = 1.0; // Lot multiplier
|
| input double InpFixedLot = 0.0; // Fixed lot (0 = use multiplier)
|
| input double InpMaxLot = 50.0; // Max lot (0 = off)
|
| input double InpMinLot = 0.01; // Min lot (0 = off)
|
|
|
| input group "============== TRADE SETTINGS =============="
|
| input int InpSlippage = 50; // Slippage (points)
|
| input int InpMagicNumber = 20241014; // Magic for copied trades
|
| input bool InpReverseTrade = false; // Reverse Buy/Sell (SL<->TP swapped)
|
| input bool InpCopySL = true; // Copy SL (also follows trailing)
|
| input bool InpCopyTP = true; // Copy TP
|
| input bool InpOnlyNewTrades = false; // Ignore trades already open at EA start
|
| input int InpMaxOpenAgeSec = 0; // Skip trades older than N sec (0 = off)
|
| input bool InpAsyncMode = true; // Async orders (fastest)
|
| input int InpPendingMs = 3000; // Async: wait before re-sending (ms)
|
|
|
| input group "============== ACCOUNT PROTECTION =============="
|
| input int InpMaxPositions = 0; // Max copied positions (0 = off)
|
| input double InpMinMarginLevel = 0.0; // Min margin level % after open (0 = off)
|
| input int InpMaxRetries = 6; // Max open retries per trade
|
| input int InpHistoryDays = 14; // History scan to avoid re-opening (days)
|
|
|
| input group "============== SPEED =============="
|
| input int InpTimerMs = 1; // Timer ms (1 = fastest)
|
| input int InpFullSyncMs = 250; // Periodic full re-check (ms)
|
|
|
| input group "============== PANEL / LOG =============="
|
| input bool InpShowPanel = true; // Show panel
|
| input int InpPanelMs = 500; // Panel refresh ms
|
| input bool InpShowLogs = true; // Log copies
|
|
|
| #define P "CTS5_"
|
| #define CMT "CT#"
|
| #define K_OPEN 1
|
| #define K_MOD 2
|
| #define K_CLOSE 3
|
| #define K_WARN 9
|
|
|
| struct MTrade
|
| {
|
| ulong ticket;
|
| ulong parent;
|
| string symbol;
|
| int type;
|
| double lots;
|
| double sl;
|
| double tp;
|
| datetime openTime;
|
| };
|
|
|
| struct SPos
|
| {
|
| ulong ticket;
|
| ulong ref;
|
| double vol;
|
| double sl;
|
| double tp;
|
| string sym;
|
| int ptype;
|
| };
|
|
|
| struct RState
|
| {
|
| int kind;
|
| ulong key;
|
| uint nextMs;
|
| int fails;
|
| bool dead;
|
| };
|
|
|
| struct SymC
|
| {
|
| string name;
|
| int digits;
|
| double point;
|
| double tick;
|
| double vmin;
|
| double vmax;
|
| double vstep;
|
| int vdig;
|
| int fillOpen;
|
| int fillClose;
|
| };
|
|
|
| struct AliasT
|
| {
|
| ulong o;
|
| ulong n;
|
| };
|
|
|
| MTrade g_list[];
|
| int g_mCount = 0;
|
| SPos g_sp[];
|
| int g_spN = 0;
|
| RState g_rs[];
|
| SymC g_sc[];
|
| AliasT g_al[];
|
| ulong g_done[];
|
|
|
| ulong g_lastVer = 0;
|
| bool g_haveList = false;
|
| bool g_verChanged = false;
|
| bool g_baselineDone = false;
|
| bool g_mReady = false;
|
| long g_masterAcc = 0;
|
| datetime g_mst = 0;
|
| uint g_changeTick = 0;
|
| uint g_lastGoodMs = 0;
|
| bool g_everGood = false;
|
| bool g_connected = false;
|
| int g_lat = -1;
|
| double g_latSum = 0;
|
| int g_latN = 0;
|
| int g_latMax = 0;
|
| ulong g_copied = 0;
|
| string g_lastError = "";
|
| bool g_tradeBlocked = false;
|
| uint g_lastFullMs = 0;
|
| uint g_lastSyncMs = 0;
|
| bool g_needSync = false;
|
| uint g_lastPanel = 0;
|
|
|
| //+------------------------------------------------------------------+
|
| int OnInit()
|
| {
|
| if(AccountInfoInteger(ACCOUNT_MARGIN_MODE) != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
|
| {
|
| Print("FATAL: this EA needs a HEDGING account (netting would merge copied trades).");
|
| return(INIT_FAILED);
|
| }
|
| if(InpLotMultiplier <= 0 && InpFixedLot <= 0)
|
| {
|
| Print("FATAL: lot multiplier / fixed lot invalid");
|
| return(INIT_PARAMETERS_INCORRECT);
|
| }
|
| if(!EventSetMillisecondTimer(MathMax(1, InpTimerMs)))
|
| {
|
| Print("FATAL timer: ", GetLastError());
|
| return(INIT_FAILED);
|
| }
|
|
|
| LoadHistoryDone();
|
| PreloadSymbols();
|
| if(InpShowPanel) CreatePanel();
|
|
|
| Print("======================================================");
|
| Print("CopySlave_MT5 v5.00 (File, comment-mapped, ", (InpAsyncMode ? "ASYNC" : "SYNC"), ")");
|
| Print("File : Common\\Files\\", InpFileName);
|
| Print("Suffix : [", InpSymbolSuffix, "] Magic: ", InpMagicNumber);
|
| Print("Timer : ", MathMax(1, InpTimerMs), " ms done-set from history: ", ArraySize(g_done));
|
| Print("======================================================");
|
| return(INIT_SUCCEEDED);
|
| }
|
|
|
| void OnDeinit(const int reason)
|
| {
|
| EventKillTimer();
|
| ObjectsDeleteAll(0, P);
|
| Print("Slave stopped | Copied: ", g_copied);
|
| }
|
|
|
| void OnTimer()
|
| {
|
| Poll();
|
| if(InpShowPanel) UpdatePanel();
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Failures of async requests + detection of executed entries |
|
| //+------------------------------------------------------------------+
|
| void OnTradeTransaction(const MqlTradeTransaction &trans,
|
| const MqlTradeRequest &request,
|
| const MqlTradeResult &result)
|
| {
|
| if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
|
| {
|
| if(!HistoryDealSelect(trans.deal)) return;
|
| if(HistoryDealGetInteger(trans.deal, DEAL_MAGIC) != InpMagicNumber) return;
|
| if(HistoryDealGetInteger(trans.deal, DEAL_ENTRY) != DEAL_ENTRY_IN) return;
|
| ulong ref = ParseRef(HistoryDealGetString(trans.deal, DEAL_COMMENT));
|
| if(ref > 0)
|
| {
|
| AddDone(ref);
|
| RClear(K_OPEN, ref);
|
| g_copied++;
|
| g_needSync = true;
|
| }
|
| return;
|
| }
|
|
|
| if(!InpAsyncMode) return;
|
| if(trans.type != TRADE_TRANSACTION_REQUEST) return;
|
| if(request.magic != (ulong)InpMagicNumber) return;
|
|
|
| uint rc = result.retcode;
|
| if(RcOK(rc)) return;
|
|
|
| uint now = GetTickCount();
|
| if(request.action == TRADE_ACTION_DEAL && request.position == 0)
|
| {
|
| ulong ref = ParseRef(request.comment);
|
| if(ref > 0)
|
| {
|
| if(rc == TRADE_RETCODE_INVALID_FILL) RotateFill(request.symbol);
|
| RFail(K_OPEN, ref, rc, now);
|
| Print("ASYNC OPEN FAIL [", request.symbol, "] #", ref, " rc=", rc, " ", RcText(rc));
|
| g_lastError = RcText(rc);
|
| g_needSync = true;
|
| }
|
| }
|
| else if(request.action == TRADE_ACTION_DEAL)
|
| {
|
| if(rc == TRADE_RETCODE_POSITION_CLOSED) return;
|
| if(rc == TRADE_RETCODE_INVALID_FILL) RotateFill(request.symbol);
|
| RFail(K_CLOSE, request.position, rc, now);
|
| Print("ASYNC CLOSE FAIL #", request.position, " rc=", rc, " ", RcText(rc));
|
| g_lastError = RcText(rc);
|
| }
|
| else if(request.action == TRADE_ACTION_SLTP)
|
| {
|
| if(rc == TRADE_RETCODE_NO_CHANGES) return;
|
| RFail(K_MOD, request.position, rc, now);
|
| }
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Helpers |
|
| //+------------------------------------------------------------------+
|
| void Lg(string s) { if(InpShowLogs) Print(s); }
|
|
|
| bool RcOK(uint rc)
|
| {
|
| return (rc == TRADE_RETCODE_DONE || rc == TRADE_RETCODE_DONE_PARTIAL || rc == TRADE_RETCODE_PLACED);
|
| }
|
|
|
| string RcText(uint rc)
|
| {
|
| switch(rc)
|
| {
|
| case TRADE_RETCODE_REQUOTE: return "Requote";
|
| case TRADE_RETCODE_REJECT: return "Rejected";
|
| case TRADE_RETCODE_TIMEOUT: return "Timeout";
|
| case TRADE_RETCODE_INVALID_VOLUME: return "Invalid volume";
|
| case TRADE_RETCODE_INVALID_PRICE: return "Invalid price";
|
| case TRADE_RETCODE_INVALID_STOPS: return "Invalid stops";
|
| case TRADE_RETCODE_TRADE_DISABLED: return "Trade disabled";
|
| case TRADE_RETCODE_MARKET_CLOSED: return "Market closed";
|
| case TRADE_RETCODE_NO_MONEY: return "No money";
|
| case TRADE_RETCODE_PRICE_CHANGED: return "Price changed";
|
| case TRADE_RETCODE_PRICE_OFF: return "No quotes";
|
| case TRADE_RETCODE_TOO_MANY_REQUESTS: return "Too many requests";
|
| case TRADE_RETCODE_CONNECTION: return "No connection";
|
| case TRADE_RETCODE_INVALID_FILL: return "Invalid fill mode";
|
| case TRADE_RETCODE_LIMIT_VOLUME: return "Volume limit";
|
| case TRADE_RETCODE_LIMIT_POSITIONS: return "Positions limit";
|
| case TRADE_RETCODE_HEDGE_PROHIBITED: return "Hedge prohibited";
|
| case TRADE_RETCODE_FIFO_CLOSE: return "FIFO rule";
|
| case TRADE_RETCODE_CLIENT_DISABLES_AT: return "AutoTrading off (client)";
|
| case TRADE_RETCODE_SERVER_DISABLES_AT: return "AutoTrading off (server)";
|
| }
|
| return "retcode " + IntegerToString((long)rc);
|
| }
|
|
|
| string MapSymbol(string src)
|
| {
|
| string s = src;
|
| if(StringLen(InpReplaceFrom) > 0)
|
| StringReplace(s, InpReplaceFrom, InpReplaceTo);
|
| return InpSymbolPrefix + s + InpSymbolSuffix;
|
| }
|
|
|
| ulong ParseRef(const string c)
|
| {
|
| if(StringFind(c, CMT) != 0) return 0;
|
| return (ulong)StringToInteger(StringSubstr(c, StringLen(CMT)));
|
| }
|
|
|
| bool TradingAllowed()
|
| {
|
| if(!TerminalInfoInteger(TERMINAL_CONNECTED)) return false;
|
| if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) return false;
|
| if(!MQLInfoInteger(MQL_TRADE_ALLOWED)) return false;
|
| if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED)) return false;
|
| if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT)) return false;
|
| return true;
|
| }
|
|
|
| bool SendReq(MqlTradeRequest &rq, MqlTradeResult &rs)
|
| {
|
| if(InpAsyncMode) return OrderSendAsync(rq, rs);
|
| return OrderSend(rq, rs);
|
| }
|
|
|
| //--- alias table (master partial-close chain: old ticket -> new ticket)
|
| ulong AliasGet(ulong o)
|
| {
|
| for(int i = ArraySize(g_al) - 1; i >= 0; i--)
|
| if(g_al[i].o == o) return g_al[i].n;
|
| return 0;
|
| }
|
|
|
| void AliasSet(ulong o, ulong n)
|
| {
|
| int k = ArraySize(g_al);
|
| for(int i = 0; i < k; i++)
|
| if(g_al[i].o == o) { g_al[i].n = n; return; }
|
| if(k >= 256)
|
| {
|
| for(int j = 1; j < k; j++) g_al[j-1] = g_al[j];
|
| k--;
|
| }
|
| else
|
| ArrayResize(g_al, k + 1);
|
| g_al[k].o = o;
|
| g_al[k].n = n;
|
| }
|
|
|
| //--- "done" set: master tickets already copied / deliberately skipped
|
| bool DoneHas(ulong t)
|
| {
|
| for(int i = ArraySize(g_done) - 1; i >= 0; i--)
|
| if(g_done[i] == t) return true;
|
| return false;
|
| }
|
|
|
| void AddDone(ulong t)
|
| {
|
| if(t == 0 || DoneHas(t)) return;
|
| int n = ArraySize(g_done);
|
| ArrayResize(g_done, n + 1);
|
| g_done[n] = t;
|
| }
|
|
|
| bool IsDone(const MTrade &t)
|
| {
|
| if(DoneHas(t.ticket)) return true;
|
| if(t.parent > 0 && DoneHas(t.parent)) { AddDone(t.ticket); return true; }
|
| return false;
|
| }
|
|
|
| void PruneDone()
|
| {
|
| int n = ArraySize(g_done), k = 0;
|
| for(int i = 0; i < n; i++)
|
| if(FindMasterIdx(g_done[i]) >= 0) g_done[k++] = g_done[i];
|
| ArrayResize(g_done, k);
|
| }
|
|
|
| //--- retry / pending state
|
| int RFind(int kind, ulong key)
|
| {
|
| for(int i = ArraySize(g_rs) - 1; i >= 0; i--)
|
| if(g_rs[i].kind == kind && g_rs[i].key == key) return i;
|
| return -1;
|
| }
|
|
|
| int RGet(int kind, ulong key, uint now)
|
| {
|
| int i = RFind(kind, key);
|
| if(i >= 0) return i;
|
| i = ArraySize(g_rs);
|
| ArrayResize(g_rs, i + 1);
|
| g_rs[i].kind = kind;
|
| g_rs[i].key = key;
|
| g_rs[i].nextMs = now;
|
| g_rs[i].fails = 0;
|
| g_rs[i].dead = false;
|
| return i;
|
| }
|
|
|
| bool RReady(int kind, ulong key, uint now)
|
| {
|
| int i = RFind(kind, key);
|
| if(i < 0) return true;
|
| if(g_rs[i].dead) return false;
|
| return ((int)(now - g_rs[i].nextMs) >= 0);
|
| }
|
|
|
| void RHold(int kind, ulong key, uint now, uint ms)
|
| {
|
| int i = RGet(kind, key, now);
|
| g_rs[i].nextMs = now + ms;
|
| }
|
|
|
| void RSuccess(int kind, ulong key, uint now, uint holdMs)
|
| {
|
| int i = RGet(kind, key, now);
|
| g_rs[i].fails = 0;
|
| g_rs[i].dead = false;
|
| g_rs[i].nextMs = now + holdMs;
|
| }
|
|
|
| void RClear(int kind, ulong key)
|
| {
|
| int i = RFind(kind, key);
|
| if(i < 0) return;
|
| int n = ArraySize(g_rs);
|
| g_rs[i] = g_rs[n - 1];
|
| ArrayResize(g_rs, n - 1);
|
| }
|
|
|
| uint RetryDelay(uint rc, int fails, int kind)
|
| {
|
| uint d;
|
| switch(rc)
|
| {
|
| case TRADE_RETCODE_REQUOTE:
|
| case TRADE_RETCODE_PRICE_CHANGED:
|
| case TRADE_RETCODE_PRICE_OFF:
|
| case TRADE_RETCODE_REJECT:
|
| case TRADE_RETCODE_INVALID_FILL:
|
| d = (uint)MathMin(20 * fails, 300);
|
| break;
|
| case TRADE_RETCODE_TIMEOUT:
|
| case TRADE_RETCODE_CONNECTION:
|
| d = 1500;
|
| break;
|
| case TRADE_RETCODE_TOO_MANY_REQUESTS:
|
| d = 1000;
|
| break;
|
| default:
|
| d = (uint)MathMin(1000 * fails, 10000);
|
| break;
|
| }
|
| if(kind != K_OPEN && d > 2000) d = 2000;
|
| return d;
|
| }
|
|
|
| void RFail(int kind, ulong key, uint rc, uint now)
|
| {
|
| int i = RGet(kind, key, now);
|
| g_rs[i].fails++;
|
| g_rs[i].nextMs = now + RetryDelay(rc, g_rs[i].fails, kind);
|
| if(kind == K_OPEN && g_rs[i].fails >= InpMaxRetries)
|
| {
|
| g_rs[i].dead = true;
|
| Print("GIVE UP opening master #", key, " after ", g_rs[i].fails, " tries: ", RcText(rc));
|
| }
|
| }
|
|
|
| bool SlaveHas(ulong tk)
|
| {
|
| for(int i = 0; i < g_spN; i++)
|
| if(g_sp[i].ticket == tk) return true;
|
| return false;
|
| }
|
|
|
| void PruneRS(uint now)
|
| {
|
| for(int i = ArraySize(g_rs) - 1; i >= 0; i--)
|
| {
|
| bool rm = false;
|
| int kd = g_rs[i].kind;
|
| ulong key = g_rs[i].key;
|
| bool expired = ((int)(now - g_rs[i].nextMs) >= 0);
|
|
|
| if(kd == K_OPEN)
|
| {
|
| if(FindMasterIdx(key) < 0) rm = true;
|
| else if(g_rs[i].fails == 0 && !g_rs[i].dead && expired) rm = true;
|
| }
|
| else if(kd == K_WARN)
|
| {
|
| if(!SlaveHas(key)) rm = true;
|
| }
|
| else
|
| {
|
| if(!SlaveHas(key)) rm = true;
|
| else if(g_rs[i].fails == 0 && expired) rm = true;
|
| }
|
| if(rm)
|
| {
|
| int n = ArraySize(g_rs);
|
| g_rs[i] = g_rs[n - 1];
|
| ArrayResize(g_rs, n - 1);
|
| }
|
| }
|
| }
|
|
|
| bool HasActiveRS()
|
| {
|
| for(int i = ArraySize(g_rs) - 1; i >= 0; i--)
|
| if(!g_rs[i].dead) return true;
|
| return false;
|
| }
|
|
|
| //--- symbol cache
|
| int VolDigits(double step)
|
| {
|
| int d = 0;
|
| double s = step;
|
| while(d < 8 && MathAbs(s - MathRound(s)) > 1e-9) { s *= 10.0; d++; }
|
| return d;
|
| }
|
|
|
| void DetectFills(int i)
|
| {
|
| long fm = SymbolInfoInteger(g_sc[i].name, SYMBOL_FILLING_MODE);
|
| if((fm & SYMBOL_FILLING_FOK) != 0) g_sc[i].fillOpen = ORDER_FILLING_FOK;
|
| else if((fm & SYMBOL_FILLING_IOC) != 0) g_sc[i].fillOpen = ORDER_FILLING_IOC;
|
| else g_sc[i].fillOpen = ORDER_FILLING_RETURN;
|
|
|
| if((fm & SYMBOL_FILLING_IOC) != 0) g_sc[i].fillClose = ORDER_FILLING_IOC;
|
| else if((fm & SYMBOL_FILLING_FOK) != 0) g_sc[i].fillClose = ORDER_FILLING_FOK;
|
| else g_sc[i].fillClose = ORDER_FILLING_RETURN;
|
| }
|
|
|
| int NextFill(int f)
|
| {
|
| if(f == ORDER_FILLING_FOK) return ORDER_FILLING_IOC;
|
| if(f == ORDER_FILLING_IOC) return ORDER_FILLING_RETURN;
|
| return ORDER_FILLING_FOK;
|
| }
|
|
|
| void RotateFill(string sym)
|
| {
|
| for(int i = 0; i < ArraySize(g_sc); i++)
|
| if(g_sc[i].name == sym)
|
| {
|
| g_sc[i].fillOpen = NextFill(g_sc[i].fillOpen);
|
| g_sc[i].fillClose = NextFill(g_sc[i].fillClose);
|
| return;
|
| }
|
| }
|
|
|
| int SymIdx(const string name)
|
| {
|
| for(int i = ArraySize(g_sc) - 1; i >= 0; i--)
|
| if(g_sc[i].name == name) return i;
|
|
|
| if(!SymbolSelect(name, true)) return -1;
|
|
|
| // first use: give the terminal a moment to receive the first quote
|
| for(int w = 0; w < 60; w++)
|
| {
|
| MqlTick x;
|
| if(SymbolInfoTick(name, x) && x.bid > 0) break;
|
| Sleep(5);
|
| }
|
|
|
| double pt = SymbolInfoDouble(name, SYMBOL_POINT);
|
| if(pt <= 0) return -1;
|
|
|
| int n = ArraySize(g_sc);
|
| ArrayResize(g_sc, n + 1);
|
| g_sc[n].name = name;
|
| g_sc[n].digits = (int)SymbolInfoInteger(name, SYMBOL_DIGITS);
|
| g_sc[n].point = pt;
|
| double ts = SymbolInfoDouble(name, SYMBOL_TRADE_TICK_SIZE);
|
| g_sc[n].tick = (ts > 0) ? ts : pt;
|
| g_sc[n].vmin = SymbolInfoDouble(name, SYMBOL_VOLUME_MIN);
|
| g_sc[n].vmax = SymbolInfoDouble(name, SYMBOL_VOLUME_MAX);
|
| g_sc[n].vstep = SymbolInfoDouble(name, SYMBOL_VOLUME_STEP);
|
| if(g_sc[n].vstep <= 0) g_sc[n].vstep = 0.01;
|
| if(g_sc[n].vmin <= 0) g_sc[n].vmin = g_sc[n].vstep;
|
| if(g_sc[n].vmax <= 0) g_sc[n].vmax = 1000.0;
|
| g_sc[n].vdig = VolDigits(g_sc[n].vstep);
|
| DetectFills(n);
|
| return n;
|
| }
|
|
|
| void PreloadSymbols()
|
| {
|
| if(StringLen(InpPreloadSymbols) == 0) return;
|
| string arr[];
|
| int c = StringSplit(InpPreloadSymbols, ',', arr);
|
| for(int i = 0; i < c; i++)
|
| {
|
| string s = arr[i];
|
| StringTrimLeft(s);
|
| StringTrimRight(s);
|
| if(StringLen(s) == 0) continue;
|
| string m = MapSymbol(s);
|
| if(SymIdx(m) < 0) Print("Preload: symbol not found [", m, "]");
|
| }
|
| }
|
|
|
| double Snap(double p, int si)
|
| {
|
| if(p <= 0) return 0;
|
| double ts = g_sc[si].tick;
|
| if(ts > 0) p = MathRound(p / ts) * ts;
|
| return NormalizeDouble(p, g_sc[si].digits);
|
| }
|
|
|
| double CalcLots(double mLots, int si)
|
| {
|
| double lots = (InpFixedLot > 0) ? InpFixedLot : mLots * InpLotMultiplier;
|
| if(InpMinLot > 0 && lots < InpMinLot) lots = InpMinLot;
|
| if(InpMaxLot > 0 && lots > InpMaxLot) lots = InpMaxLot;
|
| double step = g_sc[si].vstep;
|
| lots = MathFloor(lots / step + 1e-9) * step;
|
| if(lots < g_sc[si].vmin) lots = g_sc[si].vmin;
|
| if(lots > g_sc[si].vmax) lots = g_sc[si].vmax;
|
| return NormalizeDouble(lots, g_sc[si].vdig);
|
| }
|
|
|
| void LoadHistoryDone()
|
| {
|
| if(InpHistoryDays <= 0) return;
|
| datetime to = TimeCurrent() + 86400;
|
| datetime from = to - (datetime)(InpHistoryDays * 86400);
|
| if(!HistorySelect(from, to)) return;
|
| int n = HistoryDealsTotal();
|
| for(int i = 0; i < n; i++)
|
| {
|
| ulong d = HistoryDealGetTicket(i);
|
| if(d == 0) continue;
|
| if(HistoryDealGetInteger(d, DEAL_MAGIC) != InpMagicNumber) continue;
|
| if(HistoryDealGetInteger(d, DEAL_ENTRY) != DEAL_ENTRY_IN) continue;
|
| ulong ref = ParseRef(HistoryDealGetString(d, DEAL_COMMENT));
|
| if(ref > 0) AddDone(ref);
|
| }
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Snapshot reading (validated: header + count + end marker) |
|
| //+------------------------------------------------------------------+
|
| int FindMasterIdx(ulong tk)
|
| {
|
| for(int i = 0; i < g_mCount; i++)
|
| if(g_list[i].ticket == tk) return i;
|
| return -1;
|
| }
|
|
|
| int FindChildIdx(ulong parent)
|
| {
|
| for(int i = 0; i < g_mCount; i++)
|
| if(g_list[i].parent == parent) return i;
|
| return -1;
|
| }
|
|
|
| int ResolveIdx(ulong ref)
|
| {
|
| ulong cur = ref;
|
| for(int hop = 0; hop < 12; hop++)
|
| {
|
| int idx = FindMasterIdx(cur);
|
| if(idx >= 0)
|
| {
|
| if(cur != ref) AliasSet(ref, cur);
|
| return idx;
|
| }
|
| ulong a = AliasGet(cur);
|
| if(a > 0 && a != cur) { cur = a; continue; }
|
| int c = FindChildIdx(cur);
|
| if(c >= 0)
|
| {
|
| AliasSet(cur, g_list[c].ticket);
|
| cur = g_list[c].ticket;
|
| continue;
|
| }
|
| return -1;
|
| }
|
| return -1;
|
| }
|
|
|
| bool ReadSnapshot(uint now)
|
| {
|
| g_verChanged = false;
|
|
|
| int h = FileOpen(InpFileName, FILE_READ|FILE_TXT|FILE_ANSI|FILE_COMMON|FILE_SHARE_READ|FILE_SHARE_WRITE);
|
| if(h == INVALID_HANDLE) return false;
|
|
|
| string hp[];
|
| int hn = StringSplit(FileReadString(h), ',', hp);
|
| if(hn < 9 || hp[0] != "H" || hp[1] != "5") { FileClose(h); return false; }
|
|
|
| long acc = StringToInteger(hp[2]);
|
| if(InpMasterAccount != 0 && acc != InpMasterAccount)
|
| {
|
| FileClose(h);
|
| g_lastError = "Master account mismatch";
|
| return false;
|
| }
|
|
|
| ulong seq = (ulong)StringToInteger(hp[3]);
|
| ulong ver = (ulong)StringToInteger(hp[4]);
|
| uint mTick = (uint)StringToInteger(hp[5]);
|
| int hcnt = (int)StringToInteger(hp[6]);
|
| bool mrdy = (hp[7] == "1");
|
| datetime mst = (datetime)StringToInteger(hp[8]);
|
|
|
| uint d = now - mTick;
|
| int lat = (d <= 0x7FFFFFFF) ? (int)d : 0;
|
| g_lat = lat;
|
| if(lat > InpStaleMs) { FileClose(h); return false; } // stale file = master gone
|
|
|
| if(hcnt < 0 || hcnt > 5000) { FileClose(h); return false; }
|
|
|
| g_masterAcc = acc;
|
| g_mReady = mrdy;
|
| g_mst = mst;
|
| g_lastGoodMs = now;
|
| g_everGood = true;
|
|
|
| if(g_haveList && ver == g_lastVer) { FileClose(h); return true; } // nothing changed: cheap exit
|
|
|
| MTrade tmp[];
|
| ArrayResize(tmp, hcnt);
|
| int n = 0;
|
| bool tailOK = false;
|
|
|
| while(!FileIsEnding(h))
|
| {
|
| string line = FileReadString(h);
|
| if(StringLen(line) < 3) continue;
|
| string p[];
|
| int c = StringSplit(line, ',', p);
|
| if(c < 1) continue;
|
|
|
| if(p[0] == "T")
|
| {
|
| if(c < 11 || n >= hcnt) { n = -1; break; }
|
| tmp[n].ticket = (ulong)StringToInteger(p[1]);
|
| tmp[n].symbol = MapSymbol(p[2]);
|
| tmp[n].type = (int)StringToInteger(p[3]);
|
| tmp[n].lots = StringToDouble(p[4]);
|
| tmp[n].sl = StringToDouble(p[6]);
|
| tmp[n].tp = StringToDouble(p[7]);
|
| tmp[n].openTime = (datetime)StringToInteger(p[9]);
|
| tmp[n].parent = (ulong)StringToInteger(p[10]);
|
| n++;
|
| }
|
| else if(p[0] == "E")
|
| {
|
| tailOK = (c >= 3 && StringToInteger(p[1]) == (long)seq && StringToInteger(p[2]) == hcnt);
|
| break;
|
| }
|
| }
|
| FileClose(h);
|
|
|
| if(n != hcnt || !tailOK) return false; // torn / partial read -> ignore
|
|
|
| ArrayResize(g_list, n);
|
| for(int i = 0; i < n; i++) g_list[i] = tmp[i];
|
| g_mCount = n;
|
| g_lastVer = ver;
|
| g_haveList = true;
|
| g_verChanged = true;
|
| g_changeTick = mTick;
|
|
|
| if(g_baselineDone)
|
| {
|
| g_latSum += lat;
|
| g_latN++;
|
| if(lat > g_latMax) g_latMax = lat;
|
| }
|
| return true;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| void Poll()
|
| {
|
| uint now = GetTickCount();
|
| bool ok = ReadSnapshot(now);
|
|
|
| g_connected = g_everGood && ((int)(now - g_lastGoodMs) < InpStaleMs);
|
| if(!ok) return;
|
| if(!g_mReady) return;
|
|
|
| bool due = g_verChanged
|
| || (g_needSync && (int)(now - g_lastSyncMs) >= 5)
|
| || ((int)(now - g_lastFullMs) >= MathMax(50, InpFullSyncMs));
|
| if(!due) return;
|
|
|
| Sync(now);
|
| g_lastFullMs = now;
|
| }
|
|
|
| void ScanSlavePositions()
|
| {
|
| int pt = PositionsTotal();
|
| ArrayResize(g_sp, pt);
|
| g_spN = 0;
|
| for(int i = 0; i < pt; i++)
|
| {
|
| ulong tk = PositionGetTicket(i);
|
| if(tk == 0) continue;
|
| if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
|
| int k = g_spN++;
|
| g_sp[k].ticket = tk;
|
| g_sp[k].ref = ParseRef(PositionGetString(POSITION_COMMENT));
|
| g_sp[k].vol = PositionGetDouble(POSITION_VOLUME);
|
| g_sp[k].sl = PositionGetDouble(POSITION_SL);
|
| g_sp[k].tp = PositionGetDouble(POSITION_TP);
|
| g_sp[k].sym = PositionGetString(POSITION_SYMBOL);
|
| g_sp[k].ptype = (int)PositionGetInteger(POSITION_TYPE);
|
| }
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| void Sync(uint now)
|
| {
|
| g_lastSyncMs = now;
|
|
|
| if(!TradingAllowed()) { g_tradeBlocked = true; return; }
|
| g_tradeBlocked = false;
|
|
|
| if(!g_baselineDone)
|
| {
|
| if(InpOnlyNewTrades)
|
| for(int i = 0; i < g_mCount; i++) AddDone(g_list[i].ticket);
|
| g_baselineDone = true;
|
| }
|
|
|
| ScanSlavePositions();
|
|
|
| bool covered[];
|
| ArrayResize(covered, g_mCount);
|
| ArrayInitialize(covered, false);
|
|
|
| //--- 1) existing slave positions: close / partial / stops
|
| for(int k = 0; k < g_spN; k++)
|
| {
|
| if(g_sp[k].ref == 0)
|
| {
|
| if(RFind(K_WARN, g_sp[k].ticket) < 0)
|
| {
|
| int w = RGet(K_WARN, g_sp[k].ticket, now);
|
| g_rs[w].dead = true;
|
| Print("WARNING: position #", g_sp[k].ticket, " has our magic but no CT# comment -> ignored");
|
| }
|
| continue;
|
| }
|
|
|
| AddDone(g_sp[k].ref);
|
| int mi = ResolveIdx(g_sp[k].ref);
|
| if(mi < 0)
|
| {
|
| if(RReady(K_CLOSE, g_sp[k].ticket, now))
|
| CloseVol(g_sp[k].ticket, g_sp[k].sym, g_sp[k].ptype, g_sp[k].vol, now, false);
|
| continue;
|
| }
|
|
|
| covered[mi] = true;
|
| AddDone(g_list[mi].ticket);
|
| if(g_list[mi].ticket != g_sp[k].ref) PartialSync(k, mi, now);
|
| SyncStops(k, mi, now);
|
| }
|
|
|
| //--- 2) new master trades
|
| int openCnt = g_spN;
|
| for(int i = 0; i < g_mCount; i++)
|
| {
|
| if(covered[i]) continue;
|
| if(IsDone(g_list[i])) continue;
|
| if(!RReady(K_OPEN, g_list[i].ticket, now)) continue;
|
|
|
| if(InpMaxOpenAgeSec > 0 && g_mst > 0 && g_list[i].openTime > 0 &&
|
| (long)(g_mst - g_list[i].openTime) > InpMaxOpenAgeSec)
|
| {
|
| AddDone(g_list[i].ticket);
|
| Lg("SKIP old master trade #" + IntegerToString((long)g_list[i].ticket));
|
| continue;
|
| }
|
| if(InpMaxPositions > 0 && openCnt >= InpMaxPositions) continue;
|
| if(OpenTrade(g_list[i], now)) openCnt++;
|
| }
|
|
|
| //--- 3) housekeeping
|
| if(g_verChanged) PruneDone();
|
| PruneRS(now);
|
| g_needSync = HasActiveRS();
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| void SyncStops(int k, int mi, uint now)
|
| {
|
| if(!InpCopySL && !InpCopyTP) return;
|
| if(!RReady(K_MOD, g_sp[k].ticket, now)) return;
|
|
|
| string sym = g_sp[k].sym;
|
| int si = SymIdx(sym);
|
| if(si < 0) return;
|
|
|
| double wsl = InpReverseTrade ? g_list[mi].tp : g_list[mi].sl;
|
| double wtp = InpReverseTrade ? g_list[mi].sl : g_list[mi].tp;
|
| double curSL = g_sp[k].sl, curTP = g_sp[k].tp;
|
| double nsl = InpCopySL ? Snap(wsl, si) : curSL;
|
| double ntp = InpCopyTP ? Snap(wtp, si) : curTP;
|
| double half = g_sc[si].tick * 0.5;
|
|
|
| bool dSL = (MathAbs(nsl - curSL) > half);
|
| bool dTP = (MathAbs(ntp - curTP) > half);
|
| if(!dSL && !dTP) return;
|
|
|
| MqlTick tk;
|
| if(!SymbolInfoTick(sym, tk)) return;
|
| double lvl = (double)SymbolInfoInteger(sym, SYMBOL_TRADE_STOPS_LEVEL) * g_sc[si].point;
|
| bool buy = (g_sp[k].ptype == POSITION_TYPE_BUY);
|
|
|
| if(dSL && nsl > 0)
|
| {
|
| bool okS = buy ? (nsl < tk.bid - lvl) : (nsl > tk.ask + lvl);
|
| if(!okS) { nsl = curSL; dSL = false; }
|
| }
|
| if(dTP && ntp > 0)
|
| {
|
| bool okT = buy ? (ntp > tk.bid + lvl) : (ntp < tk.ask - lvl);
|
| if(!okT) { ntp = curTP; dTP = false; }
|
| }
|
| if(!dSL && !dTP) return; // will be re-checked on next full sync
|
|
|
| MqlTradeRequest rq;
|
| MqlTradeResult rs;
|
| ZeroMemory(rq);
|
| ZeroMemory(rs);
|
| rq.action = TRADE_ACTION_SLTP;
|
| rq.position = g_sp[k].ticket;
|
| rq.symbol = sym;
|
| rq.sl = nsl;
|
| rq.tp = ntp;
|
| rq.magic = InpMagicNumber;
|
|
|
| bool sent = SendReq(rq, rs);
|
| if(sent && (RcOK(rs.retcode) || rs.retcode == TRADE_RETCODE_NO_CHANGES))
|
| RSuccess(K_MOD, g_sp[k].ticket, now, InpAsyncMode ? 120 : 30);
|
| else
|
| {
|
| uint rc = (rs.retcode == 0) ? TRADE_RETCODE_REJECT : rs.retcode;
|
| RFail(K_MOD, g_sp[k].ticket, rc, now);
|
| }
|
| }
|
|
|
| void PartialSync(int k, int mi, uint now)
|
| {
|
| if(InpFixedLot > 0) return;
|
| if(!RReady(K_CLOSE, g_sp[k].ticket, now)) return;
|
| int si = SymIdx(g_sp[k].sym);
|
| if(si < 0) return;
|
|
|
| double target = CalcLots(g_list[mi].lots, si);
|
| double step = g_sc[si].vstep;
|
| double diff = g_sp[k].vol - target;
|
| if(diff < step * 0.999) return;
|
| diff = MathFloor(diff / step + 1e-9) * step;
|
| if(diff < g_sc[si].vmin) return;
|
| if(g_sp[k].vol - diff < g_sc[si].vmin - 1e-9) return;
|
|
|
| CloseVol(g_sp[k].ticket, g_sp[k].sym, g_sp[k].ptype, NormalizeDouble(diff, g_sc[si].vdig), now, true);
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| void CloseVol(ulong ticket, string sym, int ptype, double vol, uint now, bool partial)
|
| {
|
| int si = SymIdx(sym);
|
| if(si < 0) return;
|
|
|
| MqlTick tk;
|
| if(!SymbolInfoTick(sym, tk) || tk.bid <= 0 || tk.ask <= 0)
|
| {
|
| RHold(K_CLOSE, ticket, now, 20);
|
| return;
|
| }
|
|
|
| MqlTradeRequest rq;
|
| MqlTradeResult rs;
|
| ZeroMemory(rq);
|
| ZeroMemory(rs);
|
| bool buy = (ptype == POSITION_TYPE_BUY);
|
| rq.action = TRADE_ACTION_DEAL;
|
| rq.position = ticket;
|
| rq.symbol = sym;
|
| rq.volume = vol;
|
| rq.type = buy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
|
| rq.price = buy ? tk.bid : tk.ask;
|
| rq.deviation = InpSlippage;
|
| rq.magic = InpMagicNumber;
|
| rq.type_filling = (ENUM_ORDER_TYPE_FILLING)g_sc[si].fillClose;
|
| rq.comment = partial ? "CT-part" : "CT-close";
|
|
|
| uint t0 = GetTickCount();
|
| int detect = (int)(t0 - g_changeTick);
|
| bool sent = SendReq(rq, rs);
|
| uint t1 = GetTickCount();
|
|
|
| if(sent && (RcOK(rs.retcode) || rs.retcode == TRADE_RETCODE_POSITION_CLOSED))
|
| {
|
| RSuccess(K_CLOSE, ticket, now, InpAsyncMode ? 400 : 150);
|
| Lg((partial ? "PARTIAL " : "CLOSE ") + sym + " #" + IntegerToString((long)ticket) +
|
| " vol=" + DoubleToString(vol, g_sc[si].vdig) +
|
| " master->send=" + IntegerToString(detect) + "ms call=" + IntegerToString((int)(t1 - t0)) + "ms");
|
| return;
|
| }
|
|
|
| uint rc = (rs.retcode == 0) ? TRADE_RETCODE_REJECT : rs.retcode;
|
| if(rc == TRADE_RETCODE_INVALID_FILL) RotateFill(sym);
|
| RFail(K_CLOSE, ticket, rc, now);
|
| g_lastError = RcText(rc);
|
| Print("CLOSE FAIL [", sym, "] #", ticket, " ", RcText(rc));
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| bool OpenTrade(const MTrade &t, uint now)
|
| {
|
| string sym = t.symbol;
|
| int si = SymIdx(sym);
|
| if(si < 0)
|
| {
|
| g_lastError = "Not found: " + sym;
|
| RFail(K_OPEN, t.ticket, 0, now);
|
| Print("Symbol not found: [", sym, "] - check symbol mapping");
|
| return false;
|
| }
|
|
|
| bool wantBuy = ((t.type == 0) != InpReverseTrade);
|
|
|
| long tm = SymbolInfoInteger(sym, SYMBOL_TRADE_MODE);
|
| if(tm == SYMBOL_TRADE_MODE_DISABLED || tm == SYMBOL_TRADE_MODE_CLOSEONLY ||
|
| (wantBuy && tm == SYMBOL_TRADE_MODE_SHORTONLY) || (!wantBuy && tm == SYMBOL_TRADE_MODE_LONGONLY))
|
| {
|
| g_lastError = "Trade mode: " + sym;
|
| RFail(K_OPEN, t.ticket, TRADE_RETCODE_TRADE_DISABLED, now);
|
| return false;
|
| }
|
|
|
| MqlTick tk;
|
| if(!SymbolInfoTick(sym, tk) || tk.ask <= 0 || tk.bid <= 0)
|
| {
|
| RFail(K_OPEN, t.ticket, TRADE_RETCODE_PRICE_OFF, now);
|
| return false;
|
| }
|
|
|
| double lots = CalcLots(t.lots, si);
|
| if(lots <= 0) return false;
|
|
|
| ENUM_ORDER_TYPE otype = wantBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
|
| double price = wantBuy ? tk.ask : tk.bid;
|
|
|
| double margin = 0;
|
| if(OrderCalcMargin(otype, sym, lots, price, margin))
|
| {
|
| bool bad = (margin > AccountInfoDouble(ACCOUNT_MARGIN_FREE));
|
| if(!bad && InpMinMarginLevel > 0)
|
| {
|
| double used = AccountInfoDouble(ACCOUNT_MARGIN) + margin;
|
| if(used > 0 && AccountInfoDouble(ACCOUNT_EQUITY) / used * 100.0 < InpMinMarginLevel) bad = true;
|
| }
|
| if(bad)
|
| {
|
| g_lastError = "Margin too low";
|
| RFail(K_OPEN, t.ticket, TRADE_RETCODE_NO_MONEY, now);
|
| Print("OPEN SKIP [", sym, "] margin too low (need ", DoubleToString(margin, 2), ")");
|
| return false;
|
| }
|
| }
|
|
|
| double sl = 0, tp = 0;
|
| if(InpCopySL || InpCopyTP)
|
| {
|
| double msl = InpReverseTrade ? t.tp : t.sl;
|
| double mtp = InpReverseTrade ? t.sl : t.tp;
|
| double lvl = (double)SymbolInfoInteger(sym, SYMBOL_TRADE_STOPS_LEVEL) * g_sc[si].point;
|
| if(InpCopySL && msl > 0)
|
| {
|
| double v = Snap(msl, si);
|
| if(wantBuy ? (v < tk.bid - lvl) : (v > tk.ask + lvl)) sl = v;
|
| }
|
| if(InpCopyTP && mtp > 0)
|
| {
|
| double v = Snap(mtp, si);
|
| if(wantBuy ? (v > tk.bid + lvl) : (v < tk.ask - lvl)) tp = v;
|
| }
|
| }
|
|
|
| MqlTradeRequest rq;
|
| MqlTradeResult rs;
|
| ZeroMemory(rq);
|
| ZeroMemory(rs);
|
| rq.action = TRADE_ACTION_DEAL;
|
| rq.symbol = sym;
|
| rq.volume = lots;
|
| rq.type = otype;
|
| rq.price = price;
|
| rq.sl = sl;
|
| rq.tp = tp;
|
| rq.deviation = InpSlippage;
|
| rq.magic = InpMagicNumber;
|
| rq.comment = CMT + IntegerToString((long)t.ticket);
|
| rq.type_filling = (ENUM_ORDER_TYPE_FILLING)g_sc[si].fillOpen;
|
|
|
| uint t0 = GetTickCount();
|
| int detect = (int)(t0 - g_changeTick);
|
| bool sent = SendReq(rq, rs);
|
| uint t1 = GetTickCount();
|
|
|
| if(sent && RcOK(rs.retcode))
|
| {
|
| RSuccess(K_OPEN, t.ticket, now, InpAsyncMode ? (uint)MathMax(500, InpPendingMs) : 500);
|
| if(!InpAsyncMode) AddDone(t.ticket);
|
| g_lastError = "";
|
| Lg("OPEN " + sym + " " + EnumToString(otype) + " lot=" + DoubleToString(lots, g_sc[si].vdig) +
|
| " #" + IntegerToString((long)t.ticket) +
|
| " ipc=" + IntegerToString(g_lat) + "ms master->send=" + IntegerToString(detect) +
|
| "ms call=" + IntegerToString((int)(t1 - t0)) + "ms");
|
| return true;
|
| }
|
|
|
| uint rc = (rs.retcode == 0) ? TRADE_RETCODE_REJECT : rs.retcode;
|
| if(rc == TRADE_RETCODE_INVALID_FILL) RotateFill(sym);
|
| RFail(K_OPEN, t.ticket, rc, now);
|
| g_lastError = RcText(rc);
|
| Print("OPEN FAIL [", sym, "] #", t.ticket, " ", RcText(rc));
|
| return false;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Panel |
|
| //+------------------------------------------------------------------+
|
| void CreatePanel()
|
| {
|
| ObjectsDeleteAll(0, P);
|
| ObjectCreate(0, P+"bg", OBJ_RECTANGLE_LABEL, 0, 0, 0);
|
| ObjectSetInteger(0, P+"bg", OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
| ObjectSetInteger(0, P+"bg", OBJPROP_XDISTANCE, 8);
|
| ObjectSetInteger(0, P+"bg", OBJPROP_YDISTANCE, 18);
|
| ObjectSetInteger(0, P+"bg", OBJPROP_XSIZE, 300);
|
| ObjectSetInteger(0, P+"bg", OBJPROP_YSIZE, 150);
|
| ObjectSetInteger(0, P+"bg", OBJPROP_BGCOLOR, C'12,12,18');
|
| ObjectSetInteger(0, P+"bg", OBJPROP_BORDER_TYPE, BORDER_FLAT);
|
| ObjectSetInteger(0, P+"bg", OBJPROP_COLOR, clrDodgerBlue);
|
| ObjectSetInteger(0, P+"bg", OBJPROP_WIDTH, 1);
|
| ObjectSetInteger(0, P+"bg", OBJPROP_BACK, false);
|
| ObjectSetInteger(0, P+"bg", OBJPROP_SELECTABLE, false);
|
|
|
| Lbl(P+"t", 16, 26, "SLAVE v5.00", 10, clrDodgerBlue);
|
| Lbl(P+"s", 16, 48, "Status: ...", 9, clrOrange);
|
| Lbl(P+"a", 16, 66, "Master: --", 9, clrWhite);
|
| Lbl(P+"l", 16, 86, "IPC: -- ms", 9, clrAqua);
|
| Lbl(P+"m", 16, 106, "M:0 S:0", 9, clrWhite);
|
| Lbl(P+"c", 16, 124, "Copied: 0", 9, clrSilver);
|
| Lbl(P+"e", 16, 140, "", 8, clrOrange);
|
| }
|
|
|
| void Lbl(string n, int x, int y, string t, int sz, color c)
|
| {
|
| ObjectCreate(0, n, OBJ_LABEL, 0, 0, 0);
|
| ObjectSetInteger(0, n, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
| ObjectSetInteger(0, n, OBJPROP_XDISTANCE, x);
|
| ObjectSetInteger(0, n, OBJPROP_YDISTANCE, y);
|
| ObjectSetString(0, n, OBJPROP_TEXT, t);
|
| ObjectSetString(0, n, OBJPROP_FONT, "Arial");
|
| ObjectSetInteger(0, n, OBJPROP_FONTSIZE, sz);
|
| ObjectSetInteger(0, n, OBJPROP_COLOR, c);
|
| ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
|
| }
|
|
|
| void UpdatePanel()
|
| {
|
| uint n = GetTickCount();
|
| if((int)(n - g_lastPanel) < InpPanelMs) return;
|
| g_lastPanel = n;
|
|
|
| string st; color sc;
|
| if(!g_connected) { st = "Status: MASTER LOST"; sc = clrRed; }
|
| else if(!g_mReady) { st = "Status: MASTER WARM-UP (idle)"; sc = clrOrange; }
|
| else if(g_tradeBlocked) { st = "Status: AUTOTRADING OFF"; sc = clrRed; }
|
| else { st = "Status: CONNECTED"; sc = clrLime; }
|
| ObjectSetString (0, P+"s", OBJPROP_TEXT, st);
|
| ObjectSetInteger(0, P+"s", OBJPROP_COLOR, sc);
|
| ObjectSetString (0, P+"a", OBJPROP_TEXT, "Master: " + (g_masterAcc > 0 ? IntegerToString(g_masterAcc) : "--"));
|
|
|
| string ls = "IPC: " + (g_lat >= 0 ? IntegerToString(g_lat) : "--") + " ms";
|
| if(g_latN > 0) ls += " avg " + DoubleToString(g_latSum / g_latN, 1) + " max " + IntegerToString(g_latMax);
|
| ObjectSetString(0, P+"l", OBJPROP_TEXT, ls);
|
|
|
| ObjectSetString(0, P+"m", OBJPROP_TEXT, "M:" + IntegerToString(g_mCount) + " S:" + IntegerToString(g_spN) +
|
| (InpAsyncMode ? " ASYNC" : " SYNC"));
|
| ObjectSetString(0, P+"c", OBJPROP_TEXT, "Copied: " + IntegerToString((long)g_copied));
|
| ObjectSetString(0, P+"e", OBJPROP_TEXT, g_lastError);
|
| ChartRedraw(0);
|
| }
|
| //+------------------------------------------------------------------+
|