//+------------------------------------------------------------------+ //| CopyMaster_MT4.mq4 | //| Fast Local Copy Trade - MASTER (MT4 -> MT5) v5.00 | //| File based | No DLL | Validated snapshots | VPS ready | //+------------------------------------------------------------------+ #property copyright "Fast Copy Trade System" #property version "5.00" #property strict #property description "MASTER EA - local copy MT4 -> MT5 via shared file (no DLL)" #property description "Attach to ONE chart only. This EA never trades; AutoTrading is not required." input string S1 = "============== FILE =============="; // * FILE input string InpFileName = "CopyTrade_MT4_to_MT5.csv"; // Shared file name (same on Slave) input string S2 = "============== SYMBOL MAPPING =============="; // * SYMBOL MAPPING input string InpStripTrailing = "."; // Strip from END of symbol (e.g. .) input string InpSymbolPrefix = ""; // Then add PREFIX input string InpSymbolSuffix = ""; // Then add SUFFIX input string S3 = "============== FILTER =============="; // * FILTER input int InpMagicFilter = 0; // Magic filter (0 = all trades) input string S4 = "============== COPY OPTIONS =============="; // * COPY OPTIONS input bool InpSendSLTP = true; // Send SL & TP (also carries trailing) input string S5 = "============== SPEED / SAFETY =============="; // * SPEED / SAFETY input int InpTimerMs = 1; // Timer ms (1 = fastest, real res ~1-16ms) input int InpHeartbeatMs = 200; // Heartbeat write when idle (ms) input int InpWarmupMs = 5000; // Wait after (re)connect before publishing (ms) input string S6 = "============== PANEL =============="; // * PANEL input bool InpShowPanel = true; // Show panel (turn OFF on VPS if you like) input int InpPanelMs = 500; // Panel refresh ms #define P "CTM5_" #define PROTO "5" string g_lastBody = ""; bool g_lastReady = false; uint g_seq = 0; uint g_ver = 0; uint g_lastWrite = 0; ulong g_writes = 0; // successful snapshot writes since EA start (diagnostic counter only) int g_fsize = 0; // current size of the shared file in bytes (always stays tiny: file is overwritten) uint g_lastPanel = 0; uint g_connSince = 0; bool g_conn = false; bool g_ok = false; bool g_ready = false; bool g_stopping = false; int g_openCnt = 0; string g_lastSym = ""; string g_err = ""; uint g_errLog = 0; //+------------------------------------------------------------------+ int OnInit() { if(StringLen(InpFileName) == 0) { Print("FATAL: file name is empty"); return(INIT_PARAMETERS_INCORRECT); } g_conn = IsConnected(); g_connSince = GetTickCount(); g_stopping = false; g_ver = GetTickCount(); // seed: a Master restart must never reuse a version the Slave already saw g_seq = GetTickCount(); int ms = MathMax(1, InpTimerMs); if(!EventSetMillisecondTimer(ms)) { Print("FATAL: EventSetMillisecondTimer failed, err=", GetLastError()); return(INIT_FAILED); } Scan(true); if(InpShowPanel) CreatePanel(); Print("======================================================"); Print("CopyMaster_MT4 v5.00 (File, validated snapshots)"); Print("Account : ", AccountNumber()); Print("File : Common\\Files\\", InpFileName); Print("Strip : [", InpStripTrailing, "] Warm-up: ", InpWarmupMs, " ms"); Print("Timer : ", ms, " ms Heartbeat: ", InpHeartbeatMs, " ms"); Print("======================================================"); return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { EventKillTimer(); g_stopping = true; // publish "not ready" so the Slave never acts on a dead Master Scan(true); ObjectsDeleteAll(0, P); } void OnTimer() { Scan(false); if(InpShowPanel) UpdatePanel(); } void OnTick() { Scan(false); } //+------------------------------------------------------------------+ string MapSymbol(string raw) { string s = raw; int sl = StringLen(InpStripTrailing); if(sl > 0) { while(StringLen(s) >= sl && StringSubstr(s, StringLen(s) - sl) == InpStripTrailing) s = StringSubstr(s, 0, StringLen(s) - sl); } return InpSymbolPrefix + s + InpSymbolSuffix; } //+------------------------------------------------------------------+ //| Build snapshot of open market orders and publish it if changed | //| (or when the heartbeat is due). A snapshot is NEVER published | //| if the order list could not be read completely. | //+------------------------------------------------------------------+ void Scan(bool force) { uint now = GetTickCount(); bool conn = IsConnected(); if(conn && !g_conn) g_connSince = now; g_conn = conn; bool ready = conn && !g_stopping && ((uint)(now - g_connSince) >= (uint)MathMax(0, InpWarmupMs)); g_ready = ready; int total = OrdersTotal(); string body = ""; int cnt = 0; string lastSym = ""; for(int i = 0; i < total; i++) { if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) return; // incomplete read -> do not publish int type = OrderType(); if(type > OP_SELL) continue; // market orders only if(InpMagicFilter != 0 && OrderMagicNumber() != InpMagicFilter) continue; string raw = OrderSymbol(); string sym = MapSymbol(raw); lastSym = sym; int dg = (int)MarketInfo(raw, MODE_DIGITS); if(dg < 0 || dg > 8) dg = 5; double sl = 0.0, tp = 0.0; if(InpSendSLTP) { sl = OrderStopLoss(); tp = OrderTakeProfit(); } // partial close in MT4 => new ticket with comment "from #" long parent = 0; string cm = OrderComment(); if(StringFind(cm, "from #") == 0) parent = StringToInteger(StringSubstr(cm, 6)); body += "T," + IntegerToString(OrderTicket()) + "," + sym + "," + IntegerToString(type) + "," + DoubleToString(OrderLots(), 3) + "," + DoubleToString(OrderOpenPrice(), dg) + "," + DoubleToString(sl, dg) + "," + DoubleToString(tp, dg) + "," + IntegerToString(OrderMagicNumber()) + "," + IntegerToString((long)OrderOpenTime()) + "," + IntegerToString(parent) + "\n"; cnt++; } if(OrdersTotal() != total) return; // list changed while reading -> retry next call bool changed = (body != g_lastBody) || (ready != g_lastReady); uint hb = (uint)MathMax(20, InpHeartbeatMs); if(!force && !changed && g_ok && (uint)(now - g_lastWrite) < hb) return; uint seq = g_seq + 1; uint ver = g_ver + (changed ? 1 : 0); string full = "H," + PROTO + "," + IntegerToString(AccountNumber()) + "," + IntegerToString(seq) + "," + IntegerToString(ver) + "," + IntegerToString(GetTickCount()) + "," + IntegerToString(cnt) + "," + (ready ? "1" : "0") + "," + IntegerToString((long)TimeCurrent()) + "\n" + body + "E," + IntegerToString(seq) + "," + IntegerToString(cnt) + "\n"; ResetLastError(); int h = FileOpen(InpFileName, FILE_WRITE|FILE_TXT|FILE_COMMON|FILE_ANSI|FILE_SHARE_READ); if(h == INVALID_HANDLE) { g_ok = false; g_err = "FileOpen err " + IntegerToString(GetLastError()); if((uint)(now - g_errLog) > 5000) { Print("WRITE ERROR: ", g_err); g_errLog = now; } return; } uint written = FileWriteString(h, full); int fsz = (int)FileGetInteger(h, FILE_SIZE); FileClose(h); if(written == 0) { g_ok = false; g_err = "FileWriteString = 0"; if((uint)(now - g_errLog) > 5000) { Print("WRITE ERROR: ", g_err); g_errLog = now; } return; } g_seq = seq; g_ver = ver; g_lastBody = body; g_lastReady = ready; g_lastWrite = now; g_openCnt = cnt; g_lastSym = lastSym; g_writes++; g_fsize = fsz; g_ok = true; g_err = ""; } //+------------------------------------------------------------------+ 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, 270); ObjectSetInteger(0, P+"bg", OBJPROP_YSIZE, 138); 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, "MASTER v5.00", 10, clrDodgerBlue); Lbl(P+"s", 16, 46, "Status: ...", 9, clrOrange); Lbl(P+"a", 16, 64, "Account: " + IntegerToString(AccountNumber()), 9, clrWhite); Lbl(P+"o", 16, 82, "Open: 0", 9, clrWhite); Lbl(P+"y", 16, 100, "Sym: --", 9, clrSilver); Lbl(P+"f", 16, 118, "File: -- bytes", 9, clrSilver); } 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((uint)(n - g_lastPanel) < (uint)InpPanelMs) return; g_lastPanel = n; string st; color sc; if(!g_ok) { st = "Status: WRITE ERROR"; sc = clrRed; } else if(!g_conn) { st = "Status: BROKER DISCONNECTED"; sc = clrRed; } else if(!g_ready) { st = "Status: WARM-UP (not publishing)"; sc = clrOrange; } else { st = "Status: ACTIVE"; sc = clrLime; } ObjectSetString (0, P+"s", OBJPROP_TEXT, st); ObjectSetInteger(0, P+"s", OBJPROP_COLOR, sc); ObjectSetString (0, P+"o", OBJPROP_TEXT, "Open: " + IntegerToString(g_openCnt) + " Writes: " + IntegerToString(g_writes)); ObjectSetString (0, P+"f", OBJPROP_TEXT, "File: " + IntegerToString(g_fsize) + " bytes (overwritten)"); ObjectSetString (0, P+"y", OBJPROP_TEXT, "Sym: " + (StringLen(g_lastSym) > 0 ? g_lastSym : "--") + (StringLen(g_err) > 0 ? " " + g_err : "")); ChartRedraw(0); } //+------------------------------------------------------------------+