//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+