| |
|
| |
|
| |
|
| |
|
| |
|
| | #property copyright "Antigravity"
|
| | #property link ""
|
| | #property version "2.00"
|
| |
|
| |
|
| | #include <Zmq/Zmq.mqh>
|
| |
|
| |
|
| | #include <Trade/Trade.mqh>
|
| |
|
| |
|
| | input string InpPubAddress = "tcp://0.0.0.0:5555";
|
| | input string InpRepAddress = "tcp://0.0.0.0:5556";
|
| | input double InpDefaultSlippage = 10;
|
| |
|
| | CZmq *g_publisher;
|
| | CZmq *g_responder;
|
| | CTrade g_trade;
|
| |
|
| |
|
| |
|
| |
|
| | int OnInit()
|
| | {
|
| | Print("Initializing ZmqPublisher v2.0 with Order Support...");
|
| |
|
| |
|
| | g_publisher = new CZmq();
|
| | if(!g_publisher.Init(ZMQ_PUB)) {
|
| | Print("Failed to initialize ZMQ Publisher");
|
| | return(INIT_FAILED);
|
| | }
|
| | if(!g_publisher.Bind(InpPubAddress)) {
|
| | Print("Failed to bind publisher to ", InpPubAddress);
|
| | return(INIT_FAILED);
|
| | }
|
| | Print("Tick Publisher bound to ", InpPubAddress);
|
| |
|
| |
|
| | g_responder = new CZmq();
|
| | if(!g_responder.Init(ZMQ_REP)) {
|
| | Print("Failed to initialize ZMQ Responder");
|
| | return(INIT_FAILED);
|
| | }
|
| | if(!g_responder.Bind(InpRepAddress)) {
|
| | Print("Failed to bind responder to ", InpRepAddress);
|
| | return(INIT_FAILED);
|
| | }
|
| | Print("Order Responder bound to ", InpRepAddress);
|
| |
|
| |
|
| | g_trade.SetDeviationInPoints((ulong)InpDefaultSlippage);
|
| | g_trade.SetTypeFilling(ORDER_FILLING_IOC);
|
| |
|
| | return(INIT_SUCCEEDED);
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| | void OnDeinit(const int reason)
|
| | {
|
| | Print("Deinitializing ZmqPublisher...");
|
| | if(g_publisher != NULL) {
|
| | g_publisher.Shutdown();
|
| | delete g_publisher;
|
| | g_publisher = NULL;
|
| | }
|
| | if(g_responder != NULL) {
|
| | g_responder.Shutdown();
|
| | delete g_responder;
|
| | g_responder = NULL;
|
| | }
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | string ProcessOrderRequest(string request)
|
| | {
|
| |
|
| |
|
| |
|
| |
|
| | string orderType = ExtractJsonString(request, "type");
|
| | string symbol = ExtractJsonString(request, "symbol");
|
| | double volume = ExtractJsonDouble(request, "volume");
|
| | double price = ExtractJsonDouble(request, "price");
|
| | ulong ticket = (ulong)ExtractJsonDouble(request, "ticket");
|
| |
|
| | if(symbol == "") symbol = _Symbol;
|
| | if(volume <= 0) volume = 0.01;
|
| |
|
| | Print("Order request: type=", orderType, " symbol=", symbol, " vol=", volume, " price=", price, " ticket=", ticket);
|
| |
|
| | bool success = false;
|
| | ulong resultTicket = 0;
|
| | string errorMsg = "";
|
| |
|
| |
|
| | if(orderType == "market_buy") {
|
| | double askPrice = SymbolInfoDouble(symbol, SYMBOL_ASK);
|
| | success = g_trade.Buy(volume, symbol, askPrice, 0, 0, "Rust GUI Order");
|
| | if(success) resultTicket = g_trade.ResultOrder();
|
| | else errorMsg = GetLastErrorDescription();
|
| | }
|
| | else if(orderType == "market_sell") {
|
| | double bidPrice = SymbolInfoDouble(symbol, SYMBOL_BID);
|
| | success = g_trade.Sell(volume, symbol, bidPrice, 0, 0, "Rust GUI Order");
|
| | if(success) resultTicket = g_trade.ResultOrder();
|
| | else errorMsg = GetLastErrorDescription();
|
| | }
|
| | else if(orderType == "limit_buy") {
|
| | success = g_trade.BuyLimit(volume, price, symbol, 0, 0, ORDER_TIME_GTC, 0, "Rust GUI Limit");
|
| | if(success) resultTicket = g_trade.ResultOrder();
|
| | else errorMsg = GetLastErrorDescription();
|
| | }
|
| | else if(orderType == "limit_sell") {
|
| | success = g_trade.SellLimit(volume, price, symbol, 0, 0, ORDER_TIME_GTC, 0, "Rust GUI Limit");
|
| | if(success) resultTicket = g_trade.ResultOrder();
|
| | else errorMsg = GetLastErrorDescription();
|
| | }
|
| | else if(orderType == "stop_buy") {
|
| | success = g_trade.BuyStop(volume, price, symbol, 0, 0, ORDER_TIME_GTC, 0, "Rust GUI Stop");
|
| | if(success) resultTicket = g_trade.ResultOrder();
|
| | else errorMsg = GetLastErrorDescription();
|
| | }
|
| | else if(orderType == "stop_sell") {
|
| | success = g_trade.SellStop(volume, price, symbol, 0, 0, ORDER_TIME_GTC, 0, "Rust GUI Stop");
|
| | if(success) resultTicket = g_trade.ResultOrder();
|
| | else errorMsg = GetLastErrorDescription();
|
| | }
|
| | else if(orderType == "close_position") {
|
| | if(ticket > 0) {
|
| | success = g_trade.PositionClose(ticket);
|
| | if(success) errorMsg = "Position closed";
|
| | else errorMsg = GetLastErrorDescription();
|
| | } else {
|
| | errorMsg = "Invalid ticket for close_position";
|
| | }
|
| | }
|
| | else if(orderType == "cancel_order") {
|
| | if(ticket > 0) {
|
| | success = g_trade.OrderDelete(ticket);
|
| | if(success) errorMsg = "Order deleted";
|
| | else errorMsg = GetLastErrorDescription();
|
| | } else {
|
| | errorMsg = "Invalid ticket for cancel_order";
|
| | }
|
| | }
|
| | else if(orderType == "download_history") {
|
| |
|
| | string tfStr = ExtractJsonString(request, "timeframe");
|
| | string startStr = ExtractJsonString(request, "start");
|
| | string endStr = ExtractJsonString(request, "end");
|
| | string mode = ExtractJsonString(request, "mode");
|
| |
|
| | if(mode == "") mode = "OHLC";
|
| |
|
| | success = DownloadHistory(symbol, tfStr, startStr, endStr, mode, errorMsg);
|
| | }
|
| | else {
|
| | errorMsg = "Unknown order type: " + orderType;
|
| | }
|
| |
|
| |
|
| | string response;
|
| | if(success) {
|
| | if(orderType == "download_history") {
|
| |
|
| | StringConcatenate(response, "{\"success\":true,\"message\":\"", errorMsg, "\"}");
|
| | } else {
|
| | StringConcatenate(response, "{\"success\":true,\"ticket\":", IntegerToString(resultTicket), "}");
|
| | }
|
| | } else {
|
| | StringConcatenate(response, "{\"success\":false,\"error\":\"", errorMsg, "\"}");
|
| | }
|
| |
|
| | return response;
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| | bool DownloadHistory(string symbol, string tfStr, string startStr, string endStr, string mode, string &resultMsg)
|
| | {
|
| | datetime start = StringToTime(startStr);
|
| | datetime end = StringToTime(endStr);
|
| | if(start == 0) start = D'2024.01.01 00:00';
|
| | if(end == 0) end = TimeCurrent();
|
| |
|
| | ENUM_TIMEFRAMES tf = PERIOD_M1;
|
| | if(tfStr == "M5") tf = PERIOD_M5;
|
| | else if(tfStr == "M15") tf = PERIOD_M15;
|
| | else if(tfStr == "H1") tf = PERIOD_H1;
|
| | else if(tfStr == "H4") tf = PERIOD_H4;
|
| | else if(tfStr == "D1") tf = PERIOD_D1;
|
| |
|
| | string csvContent = "";
|
| | int count = 0;
|
| |
|
| |
|
| | string NL = "|NL|";
|
| |
|
| | if(mode == "TICKS") {
|
| | MqlTick ticks[];
|
| | int received = CopyTicksRange(symbol, ticks, COPY_TICKS_ALL, start * 1000, end * 1000);
|
| |
|
| | if(received > 0) {
|
| | csvContent = "Time,Bid,Ask,Volume" + NL;
|
| | for(int i=0; i<received && i<50000; i++) {
|
| | csvContent += TimeToString(ticks[i].time, TIME_DATE|TIME_SECONDS) + "," +
|
| | DoubleToString(ticks[i].bid, _Digits) + "," +
|
| | DoubleToString(ticks[i].ask, _Digits) + "," +
|
| | IntegerToString(ticks[i].volume) + NL;
|
| | }
|
| | count = MathMin(received, 50000);
|
| | }
|
| | }
|
| | else {
|
| |
|
| | MqlRates rates[];
|
| | ArraySetAsSeries(rates, false);
|
| | int received = CopyRates(symbol, tf, start, end, rates);
|
| |
|
| | if(received > 0) {
|
| | csvContent = "Time,Open,High,Low,Close,TickVol,Spread" + NL;
|
| | for(int i=0; i<received && i<100000; i++) {
|
| | csvContent += TimeToString(rates[i].time, TIME_DATE|TIME_MINUTES) + "," +
|
| | DoubleToString(rates[i].open, _Digits) + "," +
|
| | DoubleToString(rates[i].high, _Digits) + "," +
|
| | DoubleToString(rates[i].low, _Digits) + "," +
|
| | DoubleToString(rates[i].close, _Digits) + "," +
|
| | IntegerToString(rates[i].tick_volume) + "," +
|
| | IntegerToString(rates[i].spread) + NL;
|
| | }
|
| | count = MathMin(received, 100000);
|
| | }
|
| | }
|
| |
|
| | if(count > 0) {
|
| |
|
| |
|
| | resultMsg = IntegerToString(count) + " records||CSV_DATA||" + csvContent;
|
| | return true;
|
| | } else {
|
| | resultMsg = "No data found for period";
|
| | return false;
|
| | }
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| | string ExtractJsonString(string json, string key)
|
| | {
|
| | string searchKey = "\"" + key + "\":\"";
|
| | int startPos = StringFind(json, searchKey);
|
| | if(startPos < 0) return "";
|
| |
|
| | startPos += StringLen(searchKey);
|
| | int endPos = StringFind(json, "\"", startPos);
|
| | if(endPos < 0) return "";
|
| |
|
| | return StringSubstr(json, startPos, endPos - startPos);
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| | double ExtractJsonDouble(string json, string key)
|
| | {
|
| | string searchKey = "\"" + key + "\":";
|
| | int startPos = StringFind(json, searchKey);
|
| | if(startPos < 0) return 0.0;
|
| |
|
| | startPos += StringLen(searchKey);
|
| |
|
| |
|
| | int endPos = startPos;
|
| | int len = StringLen(json);
|
| | while(endPos < len) {
|
| | ushort ch = StringGetCharacter(json, endPos);
|
| | if(ch == ',' || ch == '}' || ch == ' ') break;
|
| | endPos++;
|
| | }
|
| |
|
| | string valueStr = StringSubstr(json, startPos, endPos - startPos);
|
| | return StringToDouble(valueStr);
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| | string GetLastErrorDescription()
|
| | {
|
| | int err = GetLastError();
|
| | return "Error " + IntegerToString(err) + ": " + ErrorDescription(err);
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| | string ErrorDescription(int error)
|
| | {
|
| | switch(error) {
|
| | case 0: return "No error";
|
| | case 10004: return "Requote";
|
| | case 10006: return "Request rejected";
|
| | case 10007: return "Request canceled by trader";
|
| | case 10010: return "Request rejected - only part of the request was fulfilled";
|
| | case 10011: return "Request error";
|
| | case 10012: return "Request canceled due to timeout";
|
| | case 10013: return "Invalid request";
|
| | case 10014: return "Invalid volume";
|
| | case 10015: return "Invalid price";
|
| | case 10016: return "Invalid stops";
|
| | case 10017: return "Trade disabled";
|
| | case 10018: return "Market is closed";
|
| | case 10019: return "Not enough money";
|
| | case 10020: return "Prices changed";
|
| | case 10021: return "No quotes to process request";
|
| | case 10022: return "Invalid order expiration date";
|
| | case 10023: return "Order state changed";
|
| | case 10024: return "Too many requests";
|
| | case 10025: return "No changes in request";
|
| | case 10026: return "Autotrading disabled by server";
|
| | case 10027: return "Autotrading disabled by client terminal";
|
| | case 10028: return "Request locked for processing";
|
| | case 10029: return "Long positions only allowed";
|
| | case 10030: return "Maximum position volume exceeded";
|
| | default: return "Unknown error";
|
| | }
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| | void OnTick()
|
| | {
|
| |
|
| | if(g_responder != NULL) {
|
| | string request = g_responder.Receive(true);
|
| | if(request != "") {
|
| | Print("Received order request: ", request);
|
| | string response = ProcessOrderRequest(request);
|
| | g_responder.Send(response, false);
|
| | Print("Sent response: ", response);
|
| | }
|
| | }
|
| |
|
| |
|
| | if(g_publisher == NULL) return;
|
| |
|
| | MqlTick tick;
|
| | if(SymbolInfoTick(_Symbol, tick)) {
|
| |
|
| | double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
| | double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
| | double margin = AccountInfoDouble(ACCOUNT_MARGIN);
|
| | double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
|
| |
|
| |
|
| | double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
| | double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
| | double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
| |
|
| |
|
| | string positionsJson = "[";
|
| | int posCount = PositionsTotal();
|
| | bool firstPos = true;
|
| | for(int i = 0; i < posCount; i++) {
|
| | ulong ticket = PositionGetTicket(i);
|
| | if(PositionSelectByTicket(ticket)) {
|
| | if(PositionGetString(POSITION_SYMBOL) == _Symbol) {
|
| | if(!firstPos) StringAdd(positionsJson, ",");
|
| |
|
| | string posType = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? "BUY" : "SELL";
|
| | StringAdd(positionsJson, "{\"ticket\":" + IntegerToString(ticket) +
|
| | ",\"type\":\"" + posType + "\"" +
|
| | ",\"volume\":" + DoubleToString(PositionGetDouble(POSITION_VOLUME), 2) +
|
| | ",\"price\":" + DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), _Digits) +
|
| | ",\"profit\":" + DoubleToString(PositionGetDouble(POSITION_PROFIT), 2) +
|
| | "}");
|
| | firstPos = false;
|
| | }
|
| | }
|
| | }
|
| | StringAdd(positionsJson, "]");
|
| |
|
| |
|
| | string ordersJson = "[";
|
| | int orderCount = OrdersTotal();
|
| | bool firstOrder = true;
|
| | for(int i = 0; i < orderCount; i++) {
|
| | ulong ticket = OrderGetTicket(i);
|
| | if(OrderSelect(ticket)) {
|
| | if(OrderGetString(ORDER_SYMBOL) == _Symbol) {
|
| | if(!firstOrder) StringAdd(ordersJson, ",");
|
| |
|
| | ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
|
| | string orderTypeStr = "UNKNOWN";
|
| | if(type == ORDER_TYPE_BUY_LIMIT) orderTypeStr = "BUY LIMIT";
|
| | else if(type == ORDER_TYPE_SELL_LIMIT) orderTypeStr = "SELL LIMIT";
|
| | else if(type == ORDER_TYPE_BUY_STOP) orderTypeStr = "BUY STOP";
|
| | else if(type == ORDER_TYPE_SELL_STOP) orderTypeStr = "SELL STOP";
|
| |
|
| | StringAdd(ordersJson, "{\"ticket\":" + IntegerToString(ticket) +
|
| | ",\"type\":\"" + orderTypeStr + "\"" +
|
| | ",\"volume\":" + DoubleToString(OrderGetDouble(ORDER_VOLUME_INITIAL), 2) +
|
| | ",\"price\":" + DoubleToString(OrderGetDouble(ORDER_PRICE_OPEN), _Digits) +
|
| | "}");
|
| | firstOrder = false;
|
| | }
|
| | }
|
| | }
|
| | StringAdd(ordersJson, "]");
|
| |
|
| |
|
| | string json;
|
| | StringConcatenate(json, "{\"symbol\":\"", _Symbol,
|
| | "\",\"bid\":", DoubleToString(tick.bid, _Digits),
|
| | ",\"ask\":", DoubleToString(tick.ask, _Digits),
|
| | ",\"time\":", IntegerToString(tick.time),
|
| | ",\"volume\":", IntegerToString(tick.volume),
|
| | ",\"balance\":", DoubleToString(balance, 2),
|
| | ",\"equity\":", DoubleToString(equity, 2),
|
| | ",\"margin\":", DoubleToString(margin, 2),
|
| | ",\"free_margin\":", DoubleToString(freeMargin, 2),
|
| | ",\"min_lot\":", DoubleToString(minLot, 2),
|
| | ",\"max_lot\":", DoubleToString(maxLot, 2),
|
| | ",\"lot_step\":", DoubleToString(lotStep, 2),
|
| | ",\"positions\":", positionsJson,
|
| | ",\"orders\":", ordersJson,
|
| | "}");
|
| |
|
| | g_publisher.Send(json);
|
| |
|
| | }
|
| | }
|
| |
|
| |
|
| |
|