// MT5BotBridgeEA.mq5 // Polls the VPS bot for pending orders and executes them in MT5. // Attach to a chart on a DEMO account first. #property strict #include input string BotBaseUrl = "https://mt5.shegerpay.com"; input string BridgeToken = ""; input double DefaultLots = 0.01; input int PollSeconds = 3; input int MaxSlippagePoints = 30; input bool DemoOnly = true; CTrade trade; int OnInit() { EventSetTimer(PollSeconds); trade.SetDeviationInPoints(MaxSlippagePoints); Print("MT5BotBridgeEA started. Add BotBaseUrl to Tools > Options > Expert Advisors > WebRequest allowed URLs."); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { EventKillTimer(); } void OnTimer() { if(DemoOnly && AccountInfoInteger(ACCOUNT_TRADE_MODE) != ACCOUNT_TRADE_MODE_DEMO) { Print("DemoOnly is true, but this account is not demo. Refusing to trade."); return; } HttpPost(BotBaseUrl + "/bridge/account/snapshot", BuildAccountSnapshot()); string body = HttpGet(BotBaseUrl + "/bridge/orders/next"); if(body == "" || StringFind(body, "\"id\":null") >= 0) return; string orderId = JsonString(body, "id"); string symbol = JsonString(body, "symbol"); string side = JsonString(body, "side"); double lots = JsonNumber(body, "volume"); double sl = JsonNumber(body, "stop_loss"); double tp = JsonNumber(body, "take_profit"); if(lots <= 0) lots = DefaultLots; bool ok = false; string error = ""; if(symbol == "") symbol = _Symbol; if(!SymbolSelect(symbol, true)) { error = "symbol not available"; } else if(side == "buy") { ok = trade.Buy(lots, symbol, 0.0, sl, tp, "mt5-bot"); } else if(side == "sell") { ok = trade.Sell(lots, symbol, 0.0, sl, tp, "mt5-bot"); } else { error = "invalid side"; } if(!ok && error == "") error = trade.ResultRetcodeDescription(); string result = StringFormat("{\"ok\":%s,\"ticket\":%I64u,\"retcode\":%u,\"message\":\"%s\"}", ok ? "true" : "false", trade.ResultOrder(), trade.ResultRetcode(), EscapeJson(error)); HttpPost(BotBaseUrl + "/bridge/orders/" + orderId + "/result", result); } string BuildAccountSnapshot() { string positions = "["; int positionCount = PositionsTotal(); for(int i = 0; i < positionCount; i++) { ulong ticket = PositionGetTicket(i); if(ticket == 0 || !PositionSelectByTicket(ticket)) continue; if(StringLen(positions) > 1) positions += ","; string side = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY ? "buy" : "sell"; positions += StringFormat( "{\"ticket\":\"%I64u\",\"symbol\":\"%s\",\"side\":\"%s\",\"volume\":%.8f," "\"open_price\":%.8f,\"current_price\":%.8f,\"stop_loss\":%.8f," "\"take_profit\":%.8f,\"profit\":%.2f}", ticket, EscapeJson(PositionGetString(POSITION_SYMBOL)), side, PositionGetDouble(POSITION_VOLUME), PositionGetDouble(POSITION_PRICE_OPEN), PositionGetDouble(POSITION_PRICE_CURRENT), PositionGetDouble(POSITION_SL), PositionGetDouble(POSITION_TP), PositionGetDouble(POSITION_PROFIT) ); } positions += "]"; datetime dayStart = StringToTime(TimeToString(TimeCurrent(), TIME_DATE)); HistorySelect(dayStart, TimeCurrent()); string closedTrades = "["; double realized = 0.0; int deals = HistoryDealsTotal(); for(int i = 0; i < deals; i++) { ulong dealTicket = HistoryDealGetTicket(i); long entry = HistoryDealGetInteger(dealTicket, DEAL_ENTRY); if(entry != DEAL_ENTRY_OUT && entry != DEAL_ENTRY_OUT_BY) continue; long dealType = HistoryDealGetInteger(dealTicket, DEAL_TYPE); if(dealType != DEAL_TYPE_BUY && dealType != DEAL_TYPE_SELL) continue; double profit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT) + HistoryDealGetDouble(dealTicket, DEAL_SWAP) + HistoryDealGetDouble(dealTicket, DEAL_COMMISSION); realized += profit; if(StringLen(closedTrades) > 1) closedTrades += ","; string originalSide = dealType == DEAL_TYPE_SELL ? "buy" : "sell"; ulong positionId = (ulong)HistoryDealGetInteger(dealTicket, DEAL_POSITION_ID); closedTrades += StringFormat( "{\"ticket\":\"%I64u\",\"symbol\":\"%s\",\"side\":\"%s\",\"volume\":%.8f," "\"open_price\":%.8f,\"close_price\":%.8f,\"profit\":%.2f,\"closed_at\":\"%s\"}", dealTicket, EscapeJson(HistoryDealGetString(dealTicket, DEAL_SYMBOL)), originalSide, HistoryDealGetDouble(dealTicket, DEAL_VOLUME), FindPositionOpenPrice(positionId), HistoryDealGetDouble(dealTicket, DEAL_PRICE), profit, IsoTime((datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME)) ); } closedTrades += "]"; double floating = AccountInfoDouble(ACCOUNT_EQUITY) - AccountInfoDouble(ACCOUNT_BALANCE); return StringFormat( "{\"connected\":true,\"broker\":\"%s\",\"server\":\"%s\",\"login\":\"%I64d\"," "\"currency\":\"%s\",\"balance\":%.2f,\"equity\":%.2f,\"margin\":%.2f," "\"free_margin\":%.2f,\"floating_pnl\":%.2f,\"realized_pnl_today\":%.2f," "\"positions\":%s,\"closed_trades\":%s}", EscapeJson(AccountInfoString(ACCOUNT_COMPANY)), EscapeJson(AccountInfoString(ACCOUNT_SERVER)), AccountInfoInteger(ACCOUNT_LOGIN), EscapeJson(AccountInfoString(ACCOUNT_CURRENCY)), AccountInfoDouble(ACCOUNT_BALANCE), AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_MARGIN), AccountInfoDouble(ACCOUNT_MARGIN_FREE), floating, realized, positions, closedTrades ); } string IsoTime(datetime value) { MqlDateTime parts; TimeToStruct(value, parts); return StringFormat("%04d-%02d-%02dT%02d:%02d:%02dZ", parts.year, parts.mon, parts.day, parts.hour, parts.min, parts.sec); } double FindPositionOpenPrice(ulong positionId) { if(!HistorySelectByPosition(positionId)) return 0.0; int count = HistoryDealsTotal(); for(int i = 0; i < count; i++) { ulong ticket = HistoryDealGetTicket(i); long entry = HistoryDealGetInteger(ticket, DEAL_ENTRY); if(entry == DEAL_ENTRY_IN || entry == DEAL_ENTRY_INOUT) return HistoryDealGetDouble(ticket, DEAL_PRICE); } return 0.0; } string HttpGet(string url) { char data[]; char result[]; string headers = "Authorization: Bearer " + BridgeToken + "\r\n"; string resultHeaders; int status = WebRequest("GET", url, headers, 15000, data, result, resultHeaders); if(status < 200 || status >= 300) { Print("GET failed: ", status, " ", GetLastError()); return ""; } return CharArrayToString(result); } string HttpPost(string url, string payload) { char data[]; char result[]; StringToCharArray(payload, data, 0, WHOLE_ARRAY, CP_UTF8); string headers = "Content-Type: application/json\r\nAuthorization: Bearer " + BridgeToken + "\r\n"; string resultHeaders; int status = WebRequest("POST", url, headers, 15000, data, result, resultHeaders); if(status < 200 || status >= 300) Print("POST failed: ", status, " ", GetLastError()); return CharArrayToString(result); } string JsonString(string json, string key) { string pattern = "\"" + key + "\":\""; int start = StringFind(json, pattern); if(start < 0) return ""; start += StringLen(pattern); int end = StringFind(json, "\"", start); if(end < 0) return ""; return StringSubstr(json, start, end - start); } double JsonNumber(string json, string key) { string pattern = "\"" + key + "\":"; int start = StringFind(json, pattern); if(start < 0) return 0.0; start += StringLen(pattern); int end = start; while(end < StringLen(json)) { ushort c = StringGetCharacter(json, end); if(!((c >= '0' && c <= '9') || c == '.' || c == '-')) break; end++; } return StringToDouble(StringSubstr(json, start, end - start)); } string EscapeJson(string value) { StringReplace(value, "\\", "\\\\"); StringReplace(value, "\"", "\\\""); return value; }