1197 lines
37 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| 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);
| |||
}
| |||
//+------------------------------------------------------------------+
|