darwinex / dwx-zeromq-connector

Wrapper library for algorithmic trading in Python 3, providing DMA/STP access to Darwinex liquidity via a ZeroMQ-enabled MetaTrader Bridge EA.
https://blog.darwinex.com/zeromq-interface-python-r-metatrader4/
BSD 3-Clause "New" or "Revised" License
341 stars 227 forks source link

dwx zeromq on MT5 #83

Open geekaia opened 4 years ago

geekaia commented 4 years ago

I made some modifications and It's working on Ubuntu 20.04 and MT5.

Download the dependencies and remove funcions that are duplicated. Be happy!!!

//+--------------------------------------------------------------+
//|     DWX_ZeroMQ_Server_v2.0.2_RC8.mq4
//|     @author: Darwinex Labs (www.darwinex.com)
//|    
//|     Copyright (c) 2017-2019, Darwinex. All rights reserved.
//|    
//|     Licensed under the BSD 3-Clause License, you may not use this file except 
//|     in compliance with the License. 
//|    
//|     You may obtain a copy of the License at:    
//|     https://opensource.org/licenses/BSD-3-Clause
//+--------------------------------------------------------------+
#property copyright "Copyright 2017-2019, Darwinex Labs."
#property link      "https://www.darwinex.com/"
#property version   "2.0.2"
#property strict

// Aqui tem algumas constantes que são equivalentes

#include <MT4Orders.mqh> // если есть #include <Trade/Trade.mqh>, вставить эту строчку ПОСЛЕ
#include <MQL4_to_MQL5.mqh> // ТОЛЬКО для данного примера
//MT5|Expert!0
#include <mql4compat.mqh>

//+------------------------------------------------------------------+
//|                                                     InitMQL4.mqh |
//|                                                 Copyright DC2008 |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "keiji"
#property copyright "DC2008"
#property link      "https://www.mql5.com"
//--- Declaration of constants
#define OP_BUY 0           //Buy 
#define OP_SELL 1          //Sell 
#define OP_BUYLIMIT 2      //Pending order of BUY LIMIT type 
#define OP_SELLLIMIT 3     //Pending order of SELL LIMIT type 
#define OP_BUYSTOP 4       //Pending order of BUY STOP type 
#define OP_SELLSTOP 5      //Pending order of SELL STOP type 
//---
#define MODE_OPEN 0
#define MODE_CLOSE 3
#define MODE_VOLUME 4 
#define MODE_REAL_VOLUME 5
#define MODE_TRADES 0
#define MODE_HISTORY 1
#define SELECT_BY_POS 0
#define SELECT_BY_TICKET 1
//---
#define DOUBLE_VALUE 0
#define FLOAT_VALUE 1
#define LONG_VALUE INT_VALUE
//---
#define CHART_BAR 0
#define CHART_CANDLE 1
//---
#define MODE_ASCEND 0
#define MODE_DESCEND 1
//---
#define MODE_LOW 1
#define MODE_HIGH 2
#define MODE_TIME 5
#define MODE_BID 9
#define MODE_ASK 10
#define MODE_POINT 11
#define MODE_DIGITS 12
#define MODE_SPREAD 13
#define MODE_STOPLEVEL 14
#define MODE_LOTSIZE 15
#define MODE_TICKVALUE 16
#define MODE_TICKSIZE 17
#define MODE_SWAPLONG 18
#define MODE_SWAPSHORT 19
#define MODE_STARTING 20
#define MODE_EXPIRATION 21
#define MODE_TRADEALLOWED 22
#define MODE_MINLOT 23
#define MODE_LOTSTEP 24
#define MODE_MAXLOT 25
#define MODE_SWAPTYPE 26
#define MODE_PROFITCALCMODE 27
#define MODE_MARGINCALCMODE 28
#define MODE_MARGININIT 29
#define MODE_MARGINMAINTENANCE 30
#define MODE_MARGINHEDGED 31
#define MODE_MARGINREQUIRED 32
#define MODE_FREEZELEVEL 33
//---
#define EMPTY -1

// Required: MQL-ZMQ from https://github.com/dingmaotu/mql-zmq

#include <Zmq/Zmq.mqh>

extern string PROJECT_NAME = "DWX_ZeroMQ_MT4_Server";
extern string ZEROMQ_PROTOCOL = "tcp";
extern string HOSTNAME = "*";
extern int PUSH_PORT = 32768;
extern int PULL_PORT = 32769;
extern int PUB_PORT = 32770;
extern int MILLISECOND_TIMER = 1;

extern string t0 = "--- Trading Parameters ---";

extern int MagicNumber = 123456;
extern int MaximumOrders = 1;
extern double MaximumLotSize = 0.01;
extern int MaximumSlippage = 3;
extern bool DMA_MODE = true;

//extern string t1 = "--- ZeroMQ Configuration ---";
bool Publish_MarketData  = false;
bool Publish_MarketRates = false;

// Dynamic array initialized at OnInit(). Can be updated by TRACK_PRICES requests from client peers
string Publish_Symbols[];

// CREATE ZeroMQ Context
Context context(PROJECT_NAME);

// CREATE ZMQ_PUSH SOCKET
Socket pushSocket(context, ZMQ_PUSH);

// CREATE ZMQ_PULL SOCKET
Socket pullSocket(context, ZMQ_PULL);

// CREATE ZMQ_PUB SOCKETMT5|Expert!0
Socket pubSocket(context, ZMQ_PUB);

// VARIABLES FOR LATER
uchar _data[];
ZmqMsg request;

/**
 * Class definition for an specific instrument: the tuple (symbol,timeframe)
 */
class Instrument{
public:  

    //--------------------------------------------------------------
    /** Instrument constructor */
    Instrument(){ _symbol = ""; _name = ""; _timeframe = PERIOD_CURRENT; _last_pub_rate =0;}    

    //--------------------------------------------------------------
    /** Getters */
    string          symbol()    { return _symbol; }
    ENUM_TIMEFRAMES timeframe() { return _timeframe; }
    string          name()      { return _name; }
    datetime        getLastPublishTimestamp() { return _last_pub_rate; }
    /** Setters */
    void            setLastPublishTimestamp(datetime tmstmp) { _last_pub_rate = tmstmp; }

   //+------------------------------------------------------------------+
// Get Timeframe from text
string GetTimeframeText(ENUM_TIMEFRAMES tf){
    // Standard timeframes
    switch(tf){
        case PERIOD_M1:    return "M1";
        case PERIOD_M5:    return "M5";
        case PERIOD_M15:   return "M15";
        case PERIOD_M30:   return "M30";
        case PERIOD_H1:    return "H1";
        case PERIOD_H4:    return "H4";
        case PERIOD_D1:    return "D1";
        case PERIOD_W1:    return "W1";
        case PERIOD_MN1:   return "MN1";
        default:           return "UNKNOWN";
    }
}

   //--------------------------------------------------------------
    /** Setup instrument with symbol and timeframe descriptions
     *  @param arg_symbol Symbol
     *  @param arg_timeframe Timeframe
     */
    void setup(string arg_symbol, ENUM_TIMEFRAMES arg_timeframe){
        _symbol = arg_symbol;
        _timeframe = arg_timeframe;
        _name  = _symbol + "_" + GetTimeframeText(_timeframe);
        _last_pub_rate = 0;
    }

    //--------------------------------------------------------------
    /** Get last N MqlRates from this instrument (symbol-timeframe)
     *  @param rates Receives last 'count' rates
     *  @param count Number of requested rates
     *  @return Number of returned rates
     */
    int GetRates(MqlRates& rates[], int count){
        // ensures that symbol is setup
        if(StringLen(_symbol) > 0){
            return CopyRates(_symbol, _timeframe, 0, count, rates);
        }
        return 0;
    }

protected:
    string _name;                //!< Instrument descriptive name
    string _symbol;              //!< Symbol
    ENUM_TIMEFRAMES _timeframe;  //!< Timeframe
    datetime _last_pub_rate;     //!< Timestamp of the last published OHLC rate. Default = 0 (1 Jan 1970)

};

// Array of instruments whose rates will be published if Publish_MarketRates = True. It is initialized at OnInit() and
// can be updated through TRACK_RATES request from client peers.
Instrument Publish_Instruments[];

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
    //---

//    // Default symbol list. Can be modified through TRACK_PRICES request from client side.
//    ArrayResize(Publish_Symbols, 1);
//    Publish_Symbols[0] = "EURAUD";
//    
//    // Default instrument list. Can be modified through TRACK_RATES request from client side.
//    ArrayResize(Publish_Instruments, 1);
//    Publish_Instruments[0].setup("EURAUD", PERIOD_M1);

    EventSetMillisecondTimer(MILLISECOND_TIMER);     // Set Millisecond Timer to get client socket input

    context.setBlocky(false);

    // Send responses to PULL_PORT that client is listening on.   
    if(!pushSocket.bind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PULL_PORT))){
        Print("[PUSH] ####ERROR#### Binding MT4 Server to Socket on Port " + IntegerToString(PULL_PORT) + "..");
        return(INIT_FAILED);
    }
    else{
        Print("[PUSH] Binding MT4 Server to Socket on Port " + IntegerToString(PULL_PORT) + "..");
        pushSocket.setSendHighWaterMark(1);
        pushSocket.setLinger(0);
    }

    // Receive commands from PUSH_PORT that client is sending to.     
    if(!pullSocket.bind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PUSH_PORT))){
        Print("[PULL] ####ERROR#### Binding MT4 Server to Socket on Port " + IntegerToString(PUSH_PORT) + "..");
        return(INIT_FAILED);
    }
    else{
        Print("[PULL] Binding MT4 Server to Socket on Port " + IntegerToString(PUSH_PORT) + "..");
        pullSocket.setReceiveHighWaterMark(1);   
        pullSocket.setLinger(0); 
    }

    // Send new market data to PUB_PORT that client is subscribed to.      
    if(!pubSocket.bind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PUB_PORT))){
        Print("[PUB] ####ERROR#### Binding MT4 Server to Socket on Port " + IntegerToString(PUB_PORT) + "..");
        return(INIT_FAILED);
    }
    else{
        Print("[PUB] Binding MT4 Server to Socket on Port " + IntegerToString(PUB_PORT) + "..");
        pubSocket.setSendHighWaterMark(1);
        pubSocket.setLinger(0);
    }

    return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//---

   Print("[PUSH] Unbinding MT4 Server from Socket on Port " + IntegerToString(PULL_PORT) + "..");
   pushSocket.unbind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PULL_PORT));

   Print("[PULL] Unbinding MT4 Server from Socket on Port " + IntegerToString(PUSH_PORT) + "..");
   pullSocket.unbind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PUSH_PORT));

   if (Publish_MarketData == true || Publish_MarketRates == true)
   {
      Print("[PUB] Unbinding MT4 Server from Socket on Port " + IntegerToString(PUB_PORT) + "..");
      pubSocket.unbind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PUB_PORT));
   }

   // Shutdown ZeroMQ Context
   context.shutdown();
   context.destroy(0);

   EventKillTimer();
}

//+------------------------------------------------------------------+
//| Expert tick function                                            |
//+------------------------------------------------------------------+
void OnTick()
{
   /*
      Use this OnTick() function to send market data to subscribed client.
   */
   if(!IsStopped())
   {
      // Python clients can subscribe to a price feed for each tracked symbol
      if(Publish_MarketData == true) {
        for(int s = 0; s < ArraySize(Publish_Symbols); s++) {
          string _tick = GetBidAsk(Publish_Symbols[s]);            
          // publish: topic=symbol msg=tick_data    
          ZmqMsg reply(StringFormat("%s %s", Publish_Symbols[s], _tick));
          Print("Sending PRICE [" + reply.getData() + "] to PUB Socket");
          if(!pubSocket.send(reply, true)){
            Print("###ERROR### Sending price");
          }
        }
      }

      // Python clients can also subscribe to a rates feed for each tracked instrument
      if(Publish_MarketRates == true){
        for(int s = 0; s < ArraySize(Publish_Instruments); s++) {
            MqlRates curr_rate[];
            int count = Publish_Instruments[s].GetRates(curr_rate, 1);
            // if last rate is returned and its timestamp is greater than the last published...
            if(count > 0 && Publish_Instruments[s].getLastPublishTimestamp() < curr_rate[0].time){
                // then send a new pub message with this new rate
                string _rates = StringFormat("%u;%f;%f;%f;%f;%d;%d;%d",
                                    curr_rate[0].time,
                                    curr_rate[0].open, 
                                    curr_rate[0].high, 
                                    curr_rate[0].low, 
                                    curr_rate[0].close, 
                                    curr_rate[0].tick_volume, 
                                    curr_rate[0].spread, 
                                    curr_rate[0].real_volume);                
                ZmqMsg reply(StringFormat("%s %s", Publish_Instruments[s].name(), _rates));
                Print("Sending Rates @"+TimeToStr(curr_rate[0].time) + " [" + reply.getData() + "] to PUB Socket");
                if(!pubSocket.send(reply, true)){
                    Print("###ERROR### Sending rate");            
                }
                // updates the timestamp
                Publish_Instruments[s].setLastPublishTimestamp(curr_rate[0].time);

          }
        }
      }
   }
}

//+------------------------------------------------------------------+
//| Expert timer function                                            |
//+------------------------------------------------------------------+
void OnTimer()
{
//---

   /*
      Use this OnTimer() function to get and respond to commands
   */

   // Get client's response, but don't block.
   pullSocket.recv(request, true);

   if (request.size() > 0)
   {
      // Wait 
      // pullSocket.recv(request,false);

      // MessageHandler() should go here.   
      ZmqMsg reply = MessageHandler(request);

      // Send response, and block
      // pushSocket.send(reply);

      // Send response, but don't block
      if(!pushSocket.send(reply, true)){
        Print("###ERROR### Sending message");
      }
   }
}

//+------------------------------------------------------------------+
ZmqMsg MessageHandler(ZmqMsg &_request) {

   // Output object
   ZmqMsg reply;

   // Message components for later.
   string components[11];

   if(_request.size() > 0) {

      // Get data from request   
      ArrayResize(_data, _request.size());
      _request.getData(_data);
      string dataStr = CharArrayToString(_data);

      // Process data
      ParseZmqMessage(dataStr, components);

      // Interpret data
      InterpretZmqMessage(pushSocket, components);

   }
   else {
      // NO DATA RECEIVED
   }

   return(reply);
}

// Interpret Zmq Message and perform actions
void InterpretZmqMessage(Socket &pSocket, string &compArray[]) {

   // Message Structures:

   // 1) Trading
   // TRADE|ACTION|TYPE|SYMBOL|PRICE|SL|TP|COMMENT|TICKET
   // e.g. TRADE|OPEN|1|EURUSD|0|50|50|R-to-MetaTrader4|12345678

   // The 12345678 at the end is the ticket ID, for MODIFY and CLOSE.

   // 2) Data Requests

   // 2.1) RATES|SYMBOL   -> Returns Current Bid/Ask

   // 2.2) DATA|SYMBOL|TIMEFRAME|START_DATETIME|END_DATETIME

   // NOTE: datetime has format: D'2015.01.01 00:00'

   /*
      compArray[0] = TRADE or RATES
      If RATES -> compArray[1] = Symbol

      If TRADE ->
         compArray[0] = TRADE
         compArray[1] = ACTION (e.g. OPEN, MODIFY, CLOSE)
         compArray[2] = TYPE (e.g. OP_BUY, OP_SELL, etc - only used when ACTION=OPEN)

         // ORDER TYPES: 
         // https://docs.mql4.com/constants/tradingconstants/orderproperties

         // OP_BUY = 0
         // OP_SELL = 1
         // OP_BUYLIMIT = 2
         // OP_SELLLIMIT = 3
         // OP_BUYSTOP = 4
         // OP_SELLSTOP = 5

         compArray[3] = Symbol (e.g. EURUSD, etc.)
         compArray[4] = Open/Close Price (ignored if ACTION = MODIFY)
         compArray[5] = SL
         compArray[6] = TP
         compArray[7] = Trade Comment
         compArray[8] = Lots
         compArray[9] = Magic Number
         compArray[10] = Ticket Number (MODIFY/CLOSE)
   */

   int switch_action = 0;

   /* 02-08-2019 10:41 CEST - HEARTBEAT */
   if(compArray[0] == "HEARTBEAT")
      InformPullClient(pSocket, "{'_action': 'heartbeat', '_response': 'loud and clear!'}");

   /* Process Messages */
   if(compArray[0] == "TRADE" && compArray[1] == "OPEN")
      switch_action = 1;
   if(compArray[0] == "TRADE" && compArray[1] == "MODIFY")
      switch_action = 2;
   if(compArray[0] == "TRADE" && compArray[1] == "CLOSE")
      switch_action = 3;
   if(compArray[0] == "TRADE" && compArray[1] == "CLOSE_PARTIAL")
      switch_action = 4;
   if(compArray[0] == "TRADE" && compArray[1] == "CLOSE_MAGIC")
      switch_action = 5;
   if(compArray[0] == "TRADE" && compArray[1] == "CLOSE_ALL")
      switch_action = 6;
   if(compArray[0] == "TRADE" && compArray[1] == "GET_OPEN_TRADES")
      switch_action = 7;
   if(compArray[0] == "DATA")
      switch_action = 8;

   /* Setup processing variables */
   string zmq_ret = "";
   string ret = "";
   int ticket = -1;
   bool ans = false;

   /****************************
    * PERFORM SOME CHECKS HERE *
    ****************************/
   if (CheckOpsStatus(pSocket, switch_action) == true)
   {
      switch(switch_action) 
      {
         case 1: // OPEN TRADE

            zmq_ret = "{";

            // Function definition:
            ticket = DWX_OpenOrder(compArray[3], StringToInteger(compArray[2]), StringToDouble(compArray[8]), 
                                    StringToDouble(compArray[4]), StringToInteger(compArray[5]), StringToInteger(compArray[6]), 
                                    compArray[7], StringToInteger(compArray[9]), zmq_ret);

            // Send TICKET back as JSON
            InformPullClient(pSocket, zmq_ret + "}");

            break;

         case 2: // MODIFY SL/TP

            zmq_ret = "{'_action': 'MODIFY'";

            // Function definition:
            ans = DWX_SetSLTP(StringToInteger(compArray[10]), StringToDouble(compArray[5]), StringToDouble(compArray[6]), 
                              StringToInteger(compArray[9]), StringToInteger(compArray[2]), StringToDouble(compArray[4]), 
                              compArray[3], 3, zmq_ret);

            InformPullClient(pSocket, zmq_ret + "}");

            break;

         case 3: // CLOSE TRADE

            zmq_ret = "{";

            // IMPLEMENT CLOSE TRADE LOGIC HERE
            DWX_CloseOrder_Ticket(StrToInteger(compArray[10]), zmq_ret);

            InformPullClient(pSocket, zmq_ret + "}");

            break;

         case 4: // CLOSE PARTIAL

            zmq_ret = "{";

            ans = DWX_ClosePartial(StrToDouble(compArray[8]), zmq_ret, StrToInteger(compArray[10]));

            InformPullClient(pSocket, zmq_ret + "}");

            break;

         case 5: // CLOSE MAGIC

            zmq_ret = "{";

            DWX_CloseOrder_Magic(StrToInteger(compArray[9]), zmq_ret);

            InformPullClient(pSocket, zmq_ret + "}");

            break;

         case 6: // CLOSE ALL ORDERS

            zmq_ret = "{";

            DWX_CloseAllOrders(zmq_ret);

            InformPullClient(pSocket, zmq_ret + "}");

            break;

         case 7: // GET OPEN ORDERS

            zmq_ret = "{";

            DWX_GetOpenOrders(zmq_ret);

            InformPullClient(pSocket, zmq_ret + "}");

            break;

         case 8: // DATA REQUEST

            zmq_ret = "{";

            DWX_GetData(compArray, zmq_ret);

            InformPullClient(pSocket, zmq_ret + "}");

            break;

         default: 
            break;
      }
   }
}

// CLOSE ORDER (by Ticket)
void DWX_CloseOrder_Ticket(int _ticket, string &zmq_ret) {

   bool found = false;

   zmq_ret = zmq_ret + "'_action': 'CLOSE', '_ticket': " + IntegerToString(_ticket);

   for(int i=0; i<OrdersTotal(); i++) {
      if (OrderSelect(i,SELECT_BY_POS)==true && OrderTicket() == _ticket) {
         found = true;

         if(OrderType() == OP_BUY || OrderType() == OP_SELL) {
            DWX_CloseAtMarket(-1, zmq_ret);
            zmq_ret = zmq_ret + ", '_response': 'CLOSE_MARKET'";
         } else {
            zmq_ret = zmq_ret + ", '_response': 'CLOSE_PENDING'";
            int tmpRet = OrderDelete(OrderTicket());
         }
      }
   }

   if(found == false) {
      zmq_ret = zmq_ret + ", '_response': 'NOT_FOUND'";
   }
   else {
      zmq_ret = zmq_ret + ", '_response_value': 'SUCCESS'";
   }

}

// Check if operations are permitted
bool CheckOpsStatus(Socket &pSocket, int sFlag) {

   if (sFlag <= 6) {

      if (!IsTradeAllowed()) {
         InformPullClient(pSocket, "{'_response': 'TRADING_IS_NOT_ALLOWED__ABORTED_COMMAND'}");
         return(false);
      }
      else if (!IsExpertEnabled()) {
         InformPullClient(pSocket, "{'_response': 'EA_IS_DISABLED__ABORTED_COMMAND'}");
         return(false);
      }
      else if (IsTradeContextBusy()) {
         InformPullClient(pSocket, "{'_response': 'TRADE_CONTEXT_BUSY__ABORTED_COMMAND'}");
         return(false);
      }
      else if (!IsDllsAllowed()) {
         InformPullClient(pSocket, "{'_response': 'DLLS_DISABLED__ABORTED_COMMAND'}");
         return(false);
      }
      else if (!IsLibrariesAllowed()) {
         InformPullClient(pSocket, "{'_response': 'LIBS_DISABLED__ABORTED_COMMAND'}"); 
         return(false);
      }
      else if (!IsConnected()) {
         InformPullClient(pSocket, "{'_response': 'NO_BROKER_CONNECTION__ABORTED_COMMAND'}");
         return(false);
      }
   }

   return(true);
}

// OPEN NEW ORDER
int DWX_OpenOrder(string _symbol, int _type, double _lots, double _price, double _SL, double _TP, string _comment, int _magic, string &zmq_ret) {

   int ticket, error;

   zmq_ret = zmq_ret + "'_action': 'EXECUTION'";

   if(_lots > MaximumLotSize) {
      zmq_ret = zmq_ret + ", " + "'_response': 'LOT_SIZE_ERROR', 'response_value': 'MAX_LOT_SIZE_EXCEEDED'";
      return(-1);
   }

   double sl = _SL;
   double tp = _TP;

   // Else
   if(DMA_MODE) {
      sl = 0.0;
      tp = 0.0;
   } 

   if(_symbol == "NULL") {
      ticket = OrderSend(Symbol(), _type, _lots, _price, MaximumSlippage, sl, tp, _comment, _magic);
   } else {
      ticket = OrderSend(_symbol, _type, _lots, _price, MaximumSlippage, sl, tp, _comment, _magic);
   }
   if(ticket < 0) {
      // Failure
      error = GetLastError();
      zmq_ret = zmq_ret + ", " + "'_response': '" + IntegerToString(error) + "', 'response_value': '" + ErrorDescription(error) + "'";
      return(-1*error);
   }

   int tmpRet = OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES);

   zmq_ret = zmq_ret + ", " + "'_magic': " + IntegerToString(_magic) + ", '_ticket': " + IntegerToString(OrderTicket()) + ", '_open_time': '" + TimeToStr(OrderOpenTime(),TIME_DATE|TIME_SECONDS) + "', '_open_price': " + DoubleToString(OrderOpenPrice());

   if(DMA_MODE) {

      int retries = 3;
      while(true) {
         retries--;
         if(retries < 0) return(0);

         if((_SL == 0 && _TP == 0) || (OrderStopLoss() == _SL && OrderTakeProfit() == _TP)) {
            return(ticket);
         }

         if(DWX_IsTradeAllowed(30, zmq_ret) == 1) {
            if(DWX_SetSLTP(ticket, _SL, _TP, _magic, _type, _price, _symbol, retries, zmq_ret)) {
               return(ticket);
            }
            if(retries == 0) {
               zmq_ret = zmq_ret + ", '_response': 'ERROR_SETTING_SL_TP'";
               return(-11111);
            }
         }

         Sleep(MILLISECOND_TIMER);
      }

      zmq_ret = zmq_ret + ", '_response': 'ERROR_SETTING_SL_TP'";
      zmq_ret = zmq_ret + "}";
      return(-1);
   }

    // Send zmq_ret to Python Client
    zmq_ret = zmq_ret + "}";

   return(ticket);
}

//+------------------------------------------------------------------+
// SET SL/TP
bool DWX_SetSLTP(int ticket, double _SL, double _TP, int _magic, int _type, double _price, string _symbol, int retries, string &zmq_ret) {

   if (OrderSelect(ticket, SELECT_BY_TICKET) == true)
   {
      int dir_flag = -1;

      if (OrderType() == 0 || OrderType() == 2 || OrderType() == 4)
         dir_flag = 1;

      double vpoint  = MarketInfo(OrderSymbol(), MODE_POINT);
      int    vdigits = (int)MarketInfo(OrderSymbol(), MODE_DIGITS);

      if(OrderModify(ticket, OrderOpenPrice(), NormalizeDouble(OrderOpenPrice()-_SL*dir_flag*vpoint,vdigits), NormalizeDouble(OrderOpenPrice()+_TP*dir_flag*vpoint,vdigits), 0, 0)) {
         zmq_ret = zmq_ret + ", '_sl': " + DoubleToString(_SL) + ", '_tp': " + DoubleToString(_TP);
         return(true);
      } else {
         int error = GetLastError();
         zmq_ret = zmq_ret + ", '_response': '" + IntegerToString(error) + "', '_response_value': '" + ErrorDescription(error) + "', '_sl_attempted': " + NormalizeDouble(OrderOpenPrice()-_SL*dir_flag*vpoint,vdigits) + ", '_tp_attempted': " + NormalizeDouble(OrderOpenPrice()+_TP*dir_flag*vpoint,vdigits);

         if(retries == 0) {
            RefreshRates();
            DWX_CloseAtMarket(-1, zmq_ret);
            // int lastOrderErrorCloseTime = TimeCurrent();
         }

         return(false);
      }
   }    

   // return(true);
   return(false);
}

//+------------------------------------------------------------------+
// Set list of symbols to get real-time price data
void DWX_SetSymbolList(string& compArray[], string& zmq_ret) {

    zmq_ret = zmq_ret + "'_action': 'TRACK_PRICES'";

    // Format: TRACK_PRICES|SYMBOL_1|SYMBOL_2|...|SYMBOL_N
    string result = "Tracking PRICES from";
    int _num_symbols = ArraySize(compArray) - 1;
    if(_num_symbols > 0){
        ArrayResize(Publish_Symbols, _num_symbols);
        for(int s=0; s<_num_symbols; s++){
            Publish_Symbols[s] = compArray[s+1];
            result += " " + Publish_Symbols[s];
        }
        zmq_ret = zmq_ret + ", '_data': {'symbol_count':" + IntegerToString(_num_symbols) + "}";
        Publish_MarketData = true;
    }
    else {
        Publish_MarketData = false;
        ArrayResize(Publish_Symbols, 1);
        zmq_ret = zmq_ret + ", '_data': {'symbol_count': 0}";
        result += " NONE";
   }         
   Print(result);
}

//+------------------------------------------------------------------+
// Get data for request datetime range
void DWX_GetData(string& compArray[], string& zmq_ret) {

   // Format: DATA|SYMBOL|TIMEFRAME|START_DATETIME|END_DATETIME

   double price_array[];
   datetime time_array[];

   // Get prices
   int price_count = CopyClose(compArray[1], 
                  TFMigrate(StringToInteger(compArray[2])), 
                  StringToTime(compArray[3]),
                  StringToTime(compArray[4]), 
                  price_array);

   // Get timestamps
   int time_count = CopyTime(compArray[1], 
                 TFMigrate(StringToInteger(compArray[2])),  // 
                  StringToTime(compArray[3]), // datetime 
                  StringToTime(compArray[4]),  // datetime 
                  time_array); // Array datetime

//      int  CopyTime(
 //  string           symbol_name,     // symbol name
 //  ENUM_TIMEFRAMES  timeframe,       // period
  // datetime         start_time,      // start date and time
  // datetime         stop_time,       // stop date and time
  // datetime         time_array[]     // target array to copy open times
   //);

   zmq_ret = zmq_ret + "'_action': 'DATA'";

   if (price_count > 0) {

      zmq_ret = zmq_ret + ", '_data': {";

      // Construct string of price|price|price|.. etc and send to PULL client.
      for(int i = 0; i < price_count; i++ ) {

         if(i == 0)
            zmq_ret = zmq_ret + "'" + TimeToString(time_array[i]) + "': " + DoubleToString(price_array[i]);
         else
            zmq_ret = zmq_ret + ", '" + TimeToString(time_array[i]) + "': " + DoubleToString(price_array[i]);

      }

      zmq_ret = zmq_ret + "}";

   }
   else {
      zmq_ret = zmq_ret + ", " + "'_response': 'NOT_AVAILABLE'";
   }

}

//+------------------------------------------------------------------+
// Get historic for request datetime range
void DWX_GetHist(string& compArray[], string& zmq_ret) {

   // Format: HIST|SYMBOL|TIMEFRAME|START_DATETIME|END_DATETIME

   MqlRates rates_array[];

   // Get prices
   int rates_count = CopyRates(compArray[1], 
                  TFMigrate(StringToInteger(compArray[2])), 
                  StringToTime(compArray[3]),
                  StringToTime(compArray[4]), rates_array);

   zmq_ret = zmq_ret + "'_action': 'HIST'";

   // if data then forms response as json:
   // {'_action: 'HIST', 
   //  '_data':[{'time': 'YYYY:MM:DD,HH:MM:SS', 'open':0.0, 'high':0.0, 'low':0.0, 'close':0.0, 'tick_volume:0, 'spread':0, 'real_volume':0},
   //           {...},
   //           ...  
   //          ]
   // }
   if (rates_count > 0) {

      zmq_ret = zmq_ret + ", '_data': [";

      // Construct string of rates and send to PULL client.
      for(int i = 0; i < rates_count; i++ ) {

         if(i == 0)
            zmq_ret = zmq_ret + "{'time':'" + TimeToString(rates_array[i].time) + "', 'open':" + DoubleToString(rates_array[i].open) + ", 'high':" + DoubleToString(rates_array[i].high) + ", 'low':" + DoubleToString(rates_array[i].low) + ", 'close':" + DoubleToString(rates_array[i].close) + ", 'tick_volume':" + IntegerToString(rates_array[i].tick_volume) + ", 'spread':" + IntegerToString(rates_array[i].spread)  + ", 'real_volume':" + IntegerToString(rates_array[i].real_volume) + "}";
         else
            zmq_ret = zmq_ret + ", {'time':'" + TimeToString(rates_array[i].time) + "', 'open':" + DoubleToString(rates_array[i].open) + ", 'high':" + DoubleToString(rates_array[i].high) + ", 'low':" + DoubleToString(rates_array[i].low) + ", 'close':" + DoubleToString(rates_array[i].close) + ", 'tick_volume':" + IntegerToString(rates_array[i].tick_volume) + ", 'spread':" + IntegerToString(rates_array[i].spread)  + ", 'real_volume':" + IntegerToString(rates_array[i].real_volume) + "}";

      }

      zmq_ret = zmq_ret + "]";

   }
   // if NO data then forms response as json:
   // {'_action: 'HIST', 
   //  '_response': 'NOT_AVAILABLE'
   // }
   else {
      zmq_ret = zmq_ret + ", " + "'_response': 'NOT_AVAILABLE'";
   }         
}

//+------------------------------------------------------------------+
// CLOSE ORDER (by Magic Number)
void DWX_CloseOrder_Magic(int _magic, string &zmq_ret) {

   bool found = false;

   zmq_ret = zmq_ret + "'_action': 'CLOSE_ALL_MAGIC'";
   zmq_ret = zmq_ret + ", '_magic': " + IntegerToString(_magic);

   zmq_ret = zmq_ret + ", '_responses': {";

   for(int i=OrdersTotal()-1; i >= 0; i--) {
      if (OrderSelect(i,SELECT_BY_POS)==true && OrderMagicNumber() == _magic) {
         found = true;

         zmq_ret = zmq_ret + IntegerToString(OrderTicket()) + ": {'_symbol':'" + OrderSymbol() + "'";

         if(OrderType() == OP_BUY || OrderType() == OP_SELL) {
            DWX_CloseAtMarket(-1, zmq_ret);
            zmq_ret = zmq_ret + ", '_response': 'CLOSE_MARKET'";

            if (i != 0)
               zmq_ret = zmq_ret + "}, ";
            else
               zmq_ret = zmq_ret + "}";

         } else {
            zmq_ret = zmq_ret + ", '_response': 'CLOSE_PENDING'";

            if (i != 0)
               zmq_ret = zmq_ret + "}, ";
            else
               zmq_ret = zmq_ret + "}";

            int tmpRet = OrderDelete(OrderTicket());
         }
      }
   }

   zmq_ret = zmq_ret + "}";

   if(found == false) {
      zmq_ret = zmq_ret + ", '_response': 'NOT_FOUND'";
   }
   else {
      zmq_ret = zmq_ret + ", '_response_value': 'SUCCESS'";
   }

}

//+------------------------------------------------------------------+
// Set list of instruments to get OHLC rates
void DWX_SetInstrumentList(string& compArray[], string& zmq_ret) {

    zmq_ret = zmq_ret + "'_action': 'TRACK_RATES'";

    // Format: TRACK_RATES|SYMBOL_1|TIMEFRAME_1|SYMBOL_2|TIMEFRAME_2|...|SYMBOL_N|TIMEFRAME_N
    string result = "Tracking RATES from";      
    int _num_instruments = (ArraySize(compArray) - 1)/2;
    if(_num_instruments > 0){
        ArrayResize(Publish_Instruments, _num_instruments);        
        for(int s=0; s<_num_instruments; s++){ 
            Publish_Instruments[s].setup(compArray[(2*s)+1], (ENUM_TIMEFRAMES)StringToInteger(compArray[(2*s)+2]));
            result += " " + Publish_Instruments[s].name();
         }
        zmq_ret = zmq_ret + ", '_data': {'instrument_count':" + IntegerToString(_num_instruments) + "}";
        Publish_MarketRates = true;
    }
    else {
        Publish_MarketRates = false;    
        ArrayResize(Publish_Instruments, 1);           
        zmq_ret = zmq_ret + ", '_data': {'instrument_count': 0}";
        result += " NONE";
   }         
   Print(result);
}

//+------------------------------------------------------------------+
// Inform Client
void InformPullClient(Socket& pSocket, string message) {

   ZmqMsg pushReply(StringFormat("%s", message));

   pSocket.send(pushReply,true); // NON-BLOCKING

}

// Parse Zmq Message
void ParseZmqMessage(string& message, string& retArray[]) {

   //Print("Parsing: " + message);

   string sep = ";";
   ushort u_sep = StringGetCharacter(sep,0);

   int splits = StringSplit(message, u_sep, retArray);

   /*
   for(int i = 0; i < splits; i++) {
      Print(IntegerToString(i) + ") " + retArray[i]);
   }
   */
}

//+------------------------------------------------------------------+
// Generate string for Bid/Ask by symbol
string GetBidAsk(string symbol) {

   MqlTick last_tick;

   if(SymbolInfoTick(symbol,last_tick))
   {
       return(StringFormat("%f;%f", last_tick.bid, last_tick.ask));
   }

   // Default
   return "";
}

//+------------------------------------------------------------------+
// CLOSE AT MARKET
bool DWX_CloseAtMarket(double size, string &zmq_ret) {

   int error;

   int retries = 3;
   while(true) {
      retries--;
      if(retries < 0) return(false);

      if(DWX_IsTradeAllowed(30, zmq_ret) == 1) {
         if(DWX_ClosePartial(size, zmq_ret)) {
            // trade successfuly closed
            return(true);
         } else {
            error = GetLastError();
            zmq_ret = zmq_ret + ", '_response': '" + IntegerToString(error) + "', '_response_value': '" + ErrorDescription(error) + "'";
         }
      }

   }

   return(false);
}

//+------------------------------------------------------------------+
// CLOSE PARTIAL SIZE
bool DWX_ClosePartial(double size, string &zmq_ret, int ticket = 0) {

   RefreshRates();
   double priceCP;

   bool close_ret = False;

   if(OrderType() != OP_BUY && OrderType() != OP_SELL) {
     return(true);
   }

   if(OrderType() == OP_BUY) {
      priceCP = DWX_GetBid(OrderSymbol());
   } else {
      priceCP = DWX_GetAsk(OrderSymbol());
   }

   // If the function is called directly, setup init() JSON here.
   if(ticket != 0) {
      zmq_ret = zmq_ret + "'_action': 'CLOSE', '_ticket': " + IntegerToString(ticket);
      zmq_ret = zmq_ret + ", '_response': 'CLOSE_PARTIAL'";
   }

   int local_ticket = 0;

   if (ticket != 0)
      local_ticket = ticket;
   else
      local_ticket = OrderTicket();

   if(size < 0.01 || size > OrderLots()) {
      close_ret = OrderClose(local_ticket, OrderLots(), priceCP, MaximumSlippage);
      zmq_ret = zmq_ret + ", '_close_price': " + DoubleToString(priceCP) + ", '_close_lots': " + DoubleToString(OrderLots());
      return(close_ret);
   } else {
      close_ret = OrderClose(local_ticket, size, priceCP, MaximumSlippage);
      zmq_ret = zmq_ret + ", '_close_price': " + DoubleToString(priceCP) + ", '_close_lots': " + DoubleToString(size);
      return(close_ret);
   }   
}

//+------------------------------------------------------------------+
// CLOSE ALL ORDERS
void DWX_CloseAllOrders(string &zmq_ret) {

   bool found = false;

   zmq_ret = zmq_ret + "'_action': 'CLOSE_ALL'";

   zmq_ret = zmq_ret + ", '_responses': {";

   for(int i=OrdersTotal()-1; i >= 0; i--) {
      if (OrderSelect(i,SELECT_BY_POS)==true) {

         found = true;

         zmq_ret = zmq_ret + IntegerToString(OrderTicket()) + ": {'_symbol':'" + OrderSymbol() + "', '_magic': " + IntegerToString(OrderMagicNumber());

         if(OrderType() == OP_BUY || OrderType() == OP_SELL) {
            DWX_CloseAtMarket(-1, zmq_ret);
            zmq_ret = zmq_ret + ", '_response': 'CLOSE_MARKET'";

            if (i != 0)
               zmq_ret = zmq_ret + "}, ";
            else
               zmq_ret = zmq_ret + "}";

         } else {
            zmq_ret = zmq_ret + ", '_response': 'CLOSE_PENDING'";

            if (i != 0)
               zmq_ret = zmq_ret + "}, ";
            else
               zmq_ret = zmq_ret + "}";

            int tmpRet = OrderDelete(OrderTicket());
         }
      }
   }

   zmq_ret = zmq_ret + "}";

   if(found == false) {
      zmq_ret = zmq_ret + ", '_response': 'NOT_FOUND'";
   }
   else {
      zmq_ret = zmq_ret + ", '_response_value': 'SUCCESS'";
   }

}

//+------------------------------------------------------------------+
// GET OPEN ORDERS
void DWX_GetOpenOrders(string &zmq_ret) {

   bool found = false;

   zmq_ret = zmq_ret + "'_action': 'OPEN_TRADES'";
   zmq_ret = zmq_ret + ", '_trades': {";

   for(int i=OrdersTotal()-1; i>=0; i--) {
      found = true;

      if (OrderSelect(i,SELECT_BY_POS)==true) {

         zmq_ret = zmq_ret + IntegerToString(OrderTicket()) + ": {";

         zmq_ret = zmq_ret + "'_magic': " + IntegerToString(OrderMagicNumber()) + ", '_symbol': '" + OrderSymbol() + "', '_lots': " + DoubleToString(OrderLots()) + ", '_type': " + IntegerToString(OrderType()) + ", '_open_price': " + DoubleToString(OrderOpenPrice()) + ", '_open_time': '" + TimeToStr(OrderOpenTime(),TIME_DATE|TIME_SECONDS) + "', '_SL': " + DoubleToString(OrderStopLoss()) + ", '_TP': " + DoubleToString(OrderTakeProfit()) + ", '_pnl': " + DoubleToString(OrderProfit()) + ", '_comment': '" + OrderComment() + "'";

         if (i != 0)
            zmq_ret = zmq_ret + "}, ";
         else
            zmq_ret = zmq_ret + "}";
      }
   }
   zmq_ret = zmq_ret + "}";
}

//+------------------------------------------------------------------+
// CHECK IF TRADE IS ALLOWED
int DWX_IsTradeAllowed(int MaxWaiting_sec, string &zmq_ret) {

    if(!IsTradeAllowed()) {

        int StartWaitingTime = (int)GetTickCount();
        zmq_ret = zmq_ret + ", " + "'_response': 'TRADE_CONTEXT_BUSY'";

        while(true) {

            if(IsStopped()) {
                zmq_ret = zmq_ret + ", " + "'_response_value': 'EA_STOPPED_BY_USER'";
                return(-1);
            }

            int diff = (int)(GetTickCount() - StartWaitingTime);
            if(diff > MaxWaiting_sec * 1000) {
                zmq_ret = zmq_ret + ", '_response': 'WAIT_LIMIT_EXCEEDED', '_response_value': " + IntegerToString(MaxWaiting_sec);
                return(-2);
            }
            // if the trade context has become free,
            if(IsTradeAllowed()) {
                zmq_ret = zmq_ret + ", '_response': 'TRADE_CONTEXT_NOW_FREE'";
                RefreshRates();
                return(1);
            }

          }
    } else {
        return(1);
    }

    return(1);
}

//+------------------------------------------------------------------+
string ErrorDescription(int error_code)
  {
   string error_string;
//----
   switch(error_code)
     {
      //---- codes returned from trade server
      case 0:
      case 1:   error_string="no error";                                                  break;
      case 2:   error_string="common error";                                              break;
      case 3:   error_string="invalid trade parameters";                                  break;
      case 4:   error_string="trade server is busy";                                      break;
      case 5:   error_string="old version of the client terminal";                        break;
      case 6:   error_string="no connection with trade server";                           break;
      case 7:   error_string="not enough rights";                                         break;
      case 8:   error_string="too frequent requests";                                     break;
      case 9:   error_string="malfunctional trade operation (never returned error)";      break;
      case 64:  error_string="account disabled";                                          break;
      case 65:  error_string="invalid account";                                           break;
      case 128: error_string="trade timeout";                                             break;
      case 129: error_string="invalid price";                                             break;
      case 130: error_string="invalid stops";                                             break;
      case 131: error_string="invalid trade volume";                                      break;
      case 132: error_string="market is closed";                                          break;
      case 133: error_string="trade is disabled";                                         break;
      case 134: error_string="not enough money";                                          break;
      case 135: error_string="price changed";                                             break;
      case 136: error_string="off quotes";                                                break;
      case 137: error_string="broker is busy (never returned error)";                     break;
      case 138: error_string="requote";                                                   break;
      case 139: error_string="order is locked";                                           break;
      case 140: error_string="long positions only allowed";                               break;
      case 141: error_string="too many requests";                                         break;
      case 145: error_string="modification denied because order too close to market";     break;
      case 146: error_string="trade context is busy";                                     break;
      case 147: error_string="expirations are denied by broker";                          break;
      case 148: error_string="amount of open and pending orders has reached the limit";   break;
      case 149: error_string="hedging is prohibited";                                     break;
      case 150: error_string="prohibited by FIFO rules";                                  break;
      //---- mql4 errors
      case 4000: error_string="no error (never generated code)";                          break;
      case 4001: error_string="wrong function pointer";                                   break;
      case 4002: error_string="array index is out of range";                              break;
      case 4003: error_string="no memory for function call stack";                        break;
      case 4004: error_string="recursive stack overflow";                                 break;
      case 4005: error_string="not enough stack for parameter";                           break;
      case 4006: error_string="no memory for parameter string";                           break;
      case 4007: error_string="no memory for temp string";                                break;
      case 4008: error_string="not initialized string";                                   break;
      case 4009: error_string="not initialized string in array";                          break;
      case 4010: error_string="no memory for array\' string";                             break;
      case 4011: error_string="too long string";                                          break;
      case 4012: error_string="remainder from zero divide";                               break;
      case 4013: error_string="zero divide";                                              break;
      case 4014: error_string="unknown command";                                          break;
      case 4015: error_string="wrong jump (never generated error)";                       break;
      case 4016: error_string="not initialized array";                                    break;
      case 4017: error_string="dll calls are not allowed";                                break;
      case 4018: error_string="cannot load library";                                      break;
      case 4019: error_string="cannot call function";                                     break;
      case 4020: error_string="expert function calls are not allowed";                    break;
      case 4021: error_string="not enough memory for temp string returned from function"; break;
      case 4022: error_string="system is busy (never generated error)";                   break;
      case 4050: error_string="invalid function parameters count";                        break;
      case 4051: error_string="invalid function parameter value";                         break;
      case 4052: error_string="string function internal error";                           break;
      case 4053: error_string="some array error";                                         break;
      case 4054: error_string="incorrect series array using";                             break;
      case 4055: error_string="custom indicator error";                                   break;
      case 4056: error_string="arrays are incompatible";                                  break;
      case 4057: error_string="global variables processing error";                        break;
      case 4058: error_string="global variable not found";                                break;
      case 4059: error_string="function is not allowed in testing mode";                  break;
      case 4060: error_string="function is not confirmed";                                break;
      case 4061: error_string="send mail error";                                          break;
      case 4062: error_string="string parameter expected";                                break;
      case 4063: error_string="integer parameter expected";                               break;
      case 4064: error_string="double parameter expected";                                break;
      case 4065: error_string="array as parameter expected";                              break;
      case 4066: error_string="requested history data in update state";                   break;
      case 4099: error_string="end of file";                                              break;
      case 4100: error_string="some file error";                                          break;
      case 4101: error_string="wrong file name";                                          break;
      case 4102: error_string="too many opened files";                                    break;
      case 4103: error_string="cannot open file";                                         break;
      case 4104: error_string="incompatible access to a file";                            break;
      case 4105: error_string="no order selected";                                        break;
      case 4106: error_string="unknown symbol";                                           break;
      case 4107: error_string="invalid price parameter for trade function";               break;
      case 4108: error_string="invalid ticket";                                           break;
      case 4109: error_string="trade is not allowed in the expert properties";            break;
      case 4110: error_string="longs are not allowed in the expert properties";           break;
      case 4111: error_string="shorts are not allowed in the expert properties";          break;
      case 4200: error_string="object is already exist";                                  break;
      case 4201: error_string="unknown object property";                                  break;
      case 4202: error_string="object is not exist";                                      break;
      case 4203: error_string="unknown object type";                                      break;
      case 4204: error_string="no object name";                                           break;
      case 4205: error_string="object coordinates error";                                 break;
      case 4206: error_string="no specified subwindow";                                   break;
      default:   error_string="unknown error";
     }
//----
   return(error_string);
  }

double DWX_GetAsk(string symbol) {
   if(symbol == "NULL") {
      return(Ask);
   } else {
      return(SymbolInfoDouble(symbol,SYMBOL_ASK));
   }
}

//+------------------------------------------------------------------+
double DWX_GetBid(string symbol) {
   if(symbol == "NULL") {
      return(Bid);
   } else {
      return(SymbolInfoDouble(symbol,SYMBOL_BID));
   }
}

///////////////////// Até aqui está ok 
glikoz commented 4 years ago

Darwinex could you add MT5 fork of this great project? thx.

eabase commented 3 years ago

@geekaia Please edit your post and use triple-back-ticks for code.

integracore2 commented 3 years ago

Thank you for this great contribution @geekaia - much appreciated! 👏

We'll test it and credit you with the work once released on GitHub - many thanks for your invaluable contributions guys, this really makes our day! 🙏

p.s. for now, I've updated your post so the code is shown appropriately.

diogopublio commented 3 years ago

Great contribution!

geekaia commented 3 years ago

It's here. https://github.com/geekaia/mt5teste

vargaspazdaniel commented 2 years ago

@integracore2 is this okay to use it in MT5?

Folarinosuolale commented 2 years ago

It's here. https://github.com/geekaia/mt5teste

I downloaded these files and I moved the dwx mq5 file into the Expert/Advisor folder in my metatrader5, yet...I can't see the EA after refreshing

I use a Mac, and I was thinking if it makes any difference.

Although I had tried using the mq4 for metatrader4, it worked successfully but the mq5 file wouldn't work on metatrader5

And which folders would the remaining 3 "mqh" files be moved to?

vivek-ram commented 2 years ago

@Folarinosuolale i am using mac too i tried dwx-zeromq-connector for mt4 i got error. it says cannot open the file.

Folarinosuolale commented 2 years ago

Try this https://github.com/darwinex/dwxconnect

I think there's an update here