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