Copy_Trade_MT4_to_MT5/CopyMaster_MT4.mq4
2026-10-27 07:36:57 +00:00
303 lines
11 KiB
MQL4
//+------------------------------------------------------------------+
//| 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);
}
//+------------------------------------------------------------------+