| //+------------------------------------------------------------------+
|
| //| 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 #<old ticket>"
|
| 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);
|
| }
|
| //+------------------------------------------------------------------+
|