Envoyer des ordres à MT4 depuis Java via IP - page 3

 
jjc:

... en disant l'évidence, si vous vouliez faire ce qui précède dans une EA plutôt que dans un script [...].

Version révisée qui peut être utilisée pour créer un EA plutôt qu'un script simplement en décommentant le #define COMPILE_AS_EA. Le code a alors OnTimer (et OnTick) au lieu de OnStart.

Le seul autre changement est que le code met maintenant les sockets en mode non-bloquant. Il interroge toujours la disponibilité de nouveaux événements de socket en utilisant select(), mais il évite maintenant le danger d'entrer dans un état bloquant si select() est erroné.

#property strict 

// ---------------------------------------------------------------------
// This code can either create an EA or a script. To create an EA,
// remove the commenting-out of the line below. The code will then
// have OnTimer() and OnTick() instead of OnStart()
// ---------------------------------------------------------------------

// #define COMPILE_AS_EA


// ---------------------------------------------------------------------
// If we are compiling as a script, then we want the script
// to display the inputs page
// ---------------------------------------------------------------------

#ifndef  COMPILE_AS_EA
#property  show_inputs
#endif


// ---------------------------------------------------------------------
// User-configurable parameters
// ---------------------------------------------------------------------

input int PortNumber = 51234; // TCP/IP port number

// ---------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------

// Size of temporary buffer used to read from client sockets
#define  SOCKET_READ_BUFFER_SIZE  10000

// Defines the frequency of the main-loop processing, in milliseconds.
// This is either the duration of the sleep in OnStart(), if compiled
// as a script, or the frequency which is given to EventSetMillisecondTimer()
// if compiled as an EA
#define  SLEEP_MILLISECONDS       10


// ---------------------------------------------------------------------
// Forward definitions of classes
// ---------------------------------------------------------------------

// Wrapper around a connected client socket
class Connection;

// ---------------------------------------------------------------------
// Winsock structure definitions and DLL imports
// ---------------------------------------------------------------------

struct sockaddr_in {
   short af_family;
   short port;
   int addr;
   int dummy1;
   int dummy2;
};

struct timeval {
   int secs;
   int usecs;
};

struct fd_set {
   int count;
   int single_socket;
   int dummy[63];
};

#import "Ws2_32.dll"
   int socket(int, int, int);   
   int bind(int, sockaddr_in&, int);
   int htons(int);
   int listen(int, int);
   int accept(int, int, int);
   int closesocket(int);
   int select(int, fd_set&, int, int, timeval&);
   int recv(int, uchar&[], int, int);
   int ioctlsocket(int, uint, uint&);
   int WSAGetLastError();
#import   


// ---------------------------------------------------------------------
// Global variables
// ---------------------------------------------------------------------

// Flags whether OnInit was successful
bool SuccessfulInit = false;

// Handle of main listening server socket
int ServerSocket;

// List of currently connected clients
Connection * Clients[];

// For EA compilation only, we track whether we have 
// successfully created a timer
#ifdef  COMPILE_AS_EA
bool CreatedTimer = false;   
#endif

// ---------------------------------------------------------------------
// Initialisation - create listening socket
// ---------------------------------------------------------------------

void OnInit()
{
   SuccessfulInit = false;
   
   if (!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED)) {Print("Requires \'Allow DLL imports\'");return;}

   // (Don't need to call WSAStartup because MT4 must have done this)

   // Create the main server socket
   ServerSocket = socket(2 /* AF_INET */, 1 /* SOCK_STREAM */, 6 /* IPPROTO_TCP */);
   if (ServerSocket == -1) {Print("ERROR " , WSAGetLastError() , " in socket creation");return;}
   
   // Put the socket into non-blocking mode
   uint nbmode = 1;
   if (ioctlsocket(ServerSocket, 0x8004667E /* FIONBIO */, nbmode) != 0) {Print("ERROR in setting non-blocking mode on server socket");return;}
   
   // Bind the socket to the specified port number. In this example,
   // we only accept connections from localhost
   sockaddr_in service;
   service.af_family = 2 /* AF_INET */;
   service.addr = 0x100007F; // equivalent to inet_addr("127.0.0.1")
   service.port = (short)htons(PortNumber);
   if (bind(ServerSocket, service, 16 /* sizeof(service) */) == -1) {Print("ERROR " , WSAGetLastError() , " in socket bind");return;}

   // Put the socket into listening mode
   if (listen(ServerSocket, 10) == -1) {Print("ERROR " , WSAGetLastError() , " in socket listen");return;}

   // Flag that we've successfully initialised
   SuccessfulInit = true;

   // If we're operating as an EA rather than a script, then try setting up a timer
   #ifdef  COMPILE_AS_EA
   CreateTimer();
   #endif   
}


// ---------------------------------------------------------------------
// Variation between EA and script, depending on COMPILE_AS_EA above.
// Both versions simply call MainLoop() to handle the socket activity.
// The script version simply does so from a loop in OnStart().
// The EA version sets up a timer, and then calls MainLoop from OnTimer()
// ---------------------------------------------------------------------

#ifdef  COMPILE_AS_EA
   // .........................................................
   // EA version. Simply calls MainLoop() from a timer.
   // The timer has previously been created in OnInit(), 
   // though we also check this in OnTick()...
   void OnTimer()
   {
      MainLoop();
   }
   
   // The function which creates the timer if it hasn't yet been set up
   void CreateTimer()
   {
      if (!CreatedTimer) CreatedTimer = EventSetMillisecondTimer(SLEEP_MILLISECONDS);
   }

   // Timer creation can sometimes fail in OnInit(), at least
   // up until MT4 build 970, if MT4 is starting up with the EA already
   // attached to a chart. Therefore, as a fallback, we also 
   // handle OnTick(), and thus eventually set up the timer in
   // the EA's first tick if we failed to set it up in OnInit()
   void OnTick()
   {
      CreateTimer();
   }

#else

   // .........................................................
   // Script version. Simply calls MainLoop() repeatedly
   // until the script is removed, with a small sleep between calls
   void OnStart()
   {
      while (!IsStopped()) {
         MainLoop();
         Sleep(SLEEP_MILLISECONDS);
      }
   }
#endif


// ---------------------------------------------------------------------
// Main processing loop which handles new incoming connections, and reads
// data from existing connections.
// Can either be called from OnStart() in a script, on a continuous loop 
// until IsStopped() is true; or from OnTimer() in an EA.
// ---------------------------------------------------------------------

void MainLoop()
{
   // Do nothing if init was unsuccessful
   if (!SuccessfulInit) return;
   
   // .........................................................
   // Do we have a new pending connection on the server socket?
   timeval waitfor;
   waitfor.secs = 0;
   waitfor.usecs = 0;
   
   fd_set PollServerSocket;
   PollServerSocket.count = 1;
   PollServerSocket.single_socket = ServerSocket;

   int selres = select(0, PollServerSocket, 0, 0, waitfor);
   if (selres > 0) {
   
      Print("New incoming connection...");
      int NewClientSocket = accept(ServerSocket, 0, 0);
      if (NewClientSocket == -1) {
         if (WSAGetLastError() == 10035 /* WSAEWOULDBLOCK */) {
            // Blocking warning; ignore
         } else {
            Print("ERROR " , WSAGetLastError() , " in socket accept");
         }

      } else {
         Print("...accepted");

         int ctarr = ArraySize(Clients);
         ArrayResize(Clients, ctarr + 1);
         Clients[ctarr] = new Connection(NewClientSocket);               
         Print("Got connection to client #", Clients[ctarr].GetID());
      }
   }

   // .........................................................
   // Process any incoming data from client connections
   // (including any which have just been accepted, above)
   int ctarr = ArraySize(Clients);
   for (int i = ctarr - 1; i >= 0; i--) {
      // Return value from ReadAnyPendingData() is true
      // if the socket still seems to be alive; false if 
      // the connection seems to have been closed, and should be discarded
      if (Clients[i].ReadAnyPendingData()) {
         // Socket still seems to be alive
         
      } else {
         // Socket appears to be dead. Delete, and remove from list
         Print("Lost connection to client #", Clients[i].GetID());
         
         delete Clients[i];
         for (int j = i + 1; j < ctarr; j++) {
            Clients[j - 1] = Clients[j];
         }
         ctarr--;
         ArrayResize(Clients, ctarr);           
      }
   }
}

// ---------------------------------------------------------------------
// Termination - clean up sockets
// ---------------------------------------------------------------------

void OnDeinit(const int reason)
{
   closesocket(ServerSocket);
   
   for (int i = 0; i < ArraySize(Clients); i++) {
      delete Clients[i];
   }
   ArrayResize(Clients, 0);
   
   #ifdef  COMPILE_AS_EA
   EventKillTimer();
   CreatedTimer = false;
   #endif
}


// ---------------------------------------------------------------------
// Simple wrapper around each connected client socket
// ---------------------------------------------------------------------

class Connection {
private:
   // Client socket handle
   int mSocket;

   // Temporary buffer used to handle incoming data
   uchar mTempBuffer[SOCKET_READ_BUFFER_SIZE];
   
   // Stored-up data, waiting for a \r character 
   string mPendingData;
   
public:
   Connection(int ClientSocket);
   ~Connection();
   string GetID() {return IntegerToString(mSocket);}
   
   bool ReadAnyPendingData();
   void ProcessIncomingMessage(string strMessage);
};

// Constructor, called with the handle of a newly accepted socket
Connection::Connection(int ClientSocket)
{
   mPendingData = "";
   mSocket = ClientSocket; 
   
   // Put the client socket into non-blocking mode
   uint nbmode = 1;
   if (ioctlsocket(mSocket, 0x8004667E /* FIONBIO */, nbmode) != 0) {Print("ERROR in setting non-blocking mode on client socket");}
}

// Destructor. Simply close the client socket
Connection::~Connection()
{
   closesocket(mSocket);
}

// Called repeatedly on a timer, to check whether any
// data is available on this client connection. Returns true if the 
// client still seems to be connected (*not* if there's new data); 
// returns false if the connection seems to be dead. 
bool Connection::ReadAnyPendingData()
{
   // Check the client socket for data-readability
   timeval waitfor;
   waitfor.secs = 0;
   waitfor.usecs = 0;

   fd_set PollClientSocket;
   PollClientSocket.count = 1;
   PollClientSocket.single_socket = mSocket;

   int selres = select(0, PollClientSocket, 0, 0, waitfor);
   if (selres > 0) {
      
      // Winsock says that there is data waiting to be read on this socket
      int res = recv(mSocket, mTempBuffer, SOCKET_READ_BUFFER_SIZE, 0);
      if (res > 0) {
         // Convert the buffer to a string, and add it to any pending
         // data which we already have on this connection
         string strIncoming = CharArrayToString(mTempBuffer, 0, res);
         mPendingData += strIncoming;
         
         // Do we have a complete message (or more than one) ending in \r?
         int idxTerm = StringFind(mPendingData, "\r");
         while (idxTerm >= 0) {
            if (idxTerm > 0) {
               string strMsg = StringSubstr(mPendingData, 0, idxTerm);         
               
               // Strip out any \n characters lingering from \r\n combos
               StringReplace(strMsg, "\n", "");
               
               // Print the \r-terminated message in the log
               ProcessIncomingMessage(strMsg);
            }               
         
            // Keep looping until we have extracted all the \r delimited 
            // messages, and leave any residue in the pending data 
            mPendingData = StringSubstr(mPendingData, idxTerm + 1);
            idxTerm = StringFind(mPendingData, "\r");
         }
         
         return true;
      
      } else if (WSAGetLastError() == 10035 /* WSAEWOULDBLOCK */) {
         // Would block; not an error
         return true;
         
      } else {
         // recv() failed. Assume socket is dead
         return false;
      }
   
   } else if (selres == -1) {
      // Assume socket is dead
      return false;
      
   } else {
      // No pending data
      return true;
   }
}

// Can override this with whatever you want to do with incoming messages:
// handling trading commands, send data back down the socket etc etc etc
void Connection::ProcessIncomingMessage(string strMessage)
{
   Print("#" , GetID() , ": " , strMessage);
}
 
jjc:

Version révisée qui [...]

Je suis sûr que tout le monde est impatient d'avoir une mise à jour sur ce sujet....

Dans le contexte d'une EA - qui est maintenant la valeur par défaut dans le code - il s'avère qu'il est possible d'utiliser WSAAsyncSelect() pour faire de la gestion événementielle de l'activité socket. Cela peut souvent faire passer la latence entre l'envoi et la réception du message sous le niveau de la milliseconde. La vitesse exacte varie en fonction de ce que MT4 fait d'autre, mais elle est en moyenne beaucoup plus rapide, et jamais pire, que la simple utilisation du timer.

Il n'est pas possible de faire en sorte que WSAAsyncSelect() déclenche OnTimer() ou OnTick() dans l'EA. Par exemple, on peut demander à WSAAsyncSelect() d'envoyer des messages WM_TIMER, mais MT4 les ignore car l'ID du timer wparam de WSAAsyncSelect() ne correspond pas à l'ID du timer qu'il attend de EventSetMillisecondTimer().

Cependant, dire à WSAAsyncSelect() d'envoyer WM_KEYDOWN déclenche avec succès OnChartEvent() dans l'EA. Voir les commentaires dans le code pour plus de détails sur le fonctionnement de cet ajout.

#property strict 

// ---------------------------------------------------------------------
// This code can either create an EA or a script. To create a script,
// comment out the line below. The code will then have OnTimer() 
// and OnTick() instead of OnStart()
// ---------------------------------------------------------------------

#define  COMPILE_AS_EA


// ---------------------------------------------------------------------
// If we are compiling as a script, then we want the script
// to display the inputs page
// ---------------------------------------------------------------------

#ifndef  COMPILE_AS_EA
#property  show_inputs
#endif


// ---------------------------------------------------------------------
// User-configurable parameters
// ---------------------------------------------------------------------

input int PortNumber = 51234; // TCP/IP port number

// ---------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------

// Size of temporary buffer used to read from client sockets
#define  SOCKET_READ_BUFFER_SIZE  10000

// Defines the frequency of the main-loop processing, in milliseconds.
// This is either the duration of the sleep in OnStart(), if compiled
// as a script, or the frequency which is given to EventSetMillisecondTimer()
// if compiled as an EA
#define  SLEEP_MILLISECONDS       10


// ---------------------------------------------------------------------
// Forward definitions of classes
// ---------------------------------------------------------------------

// Wrapper around a connected client socket
class Connection;

// ---------------------------------------------------------------------
// Winsock structure definitions and DLL imports
// ---------------------------------------------------------------------

struct sockaddr_in {
   short af_family;
   short port;
   int addr;
   int dummy1;
   int dummy2;
};

struct timeval {
   int secs;
   int usecs;
};

struct fd_set {
   int count;
   int single_socket;
   int dummy[63];
};

#import "Ws2_32.dll"
   int socket(int, int, int);   
   int bind(int, sockaddr_in&, int);
   int htons(int);
   int listen(int, int);
   int accept(int, int, int);
   int closesocket(int);
   int select(int, fd_set&, int, int, timeval&);
   int recv(int, uchar&[], int, int);
   int ioctlsocket(int, uint, uint&);
   int WSAGetLastError();
   int WSAAsyncSelect(int, int, uint, int);
#import   


// ---------------------------------------------------------------------
// Global variables
// ---------------------------------------------------------------------

// Flags whether OnInit was successful
bool SuccessfulInit = false;

// Handle of main listening server socket
int ServerSocket;

// List of currently connected clients
Connection * Clients[];

// For EA compilation only, we track whether we have 
// successfully created a timer
#ifdef  COMPILE_AS_EA
bool CreatedTimer = false;   
#endif

// ---------------------------------------------------------------------
// Initialisation - create listening socket
// ---------------------------------------------------------------------

void OnInit()
{
   SuccessfulInit = false;
   
   if (!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED)) {Print("Requires \'Allow DLL imports\'");return;}
   
   // (Don't need to call WSAStartup because MT4 must have done this)

   // Create the main server socket
   ServerSocket = socket(2 /* AF_INET */, 1 /* SOCK_STREAM */, 6 /* IPPROTO_TCP */);
   if (ServerSocket == -1) {Print("ERROR " , WSAGetLastError() , " in socket creation");return;}
   
   // Put the socket into non-blocking mode
   uint nbmode = 1;
   if (ioctlsocket(ServerSocket, 0x8004667E /* FIONBIO */, nbmode) != 0) {Print("ERROR in setting non-blocking mode on server socket");return;}
   
   // Bind the socket to the specified port number. In this example,
   // we only accept connections from localhost
   sockaddr_in service;
   service.af_family = 2 /* AF_INET */;
   service.addr = 0x100007F; // equivalent to inet_addr("127.0.0.1")
   service.port = (short)htons(PortNumber);
   if (bind(ServerSocket, service, 16 /* sizeof(service) */) == -1) {Print("ERROR " , WSAGetLastError() , " in socket bind");return;}

   // Put the socket into listening mode
   if (listen(ServerSocket, 10) == -1) {Print("ERROR " , WSAGetLastError() , " in socket listen");return;}

   #ifdef  COMPILE_AS_EA
   // In the context of an EA, we can improve the latency even further by making the 
   // processing event-driven, rather than just on a timer. If we use
   // WSAAsyncSelect() to tell Windows to send a WM_KEYDOWN whenever there's socket
   // activity, then this will fire the EA's OnChartEvent. We can thus collect
   // socket events even faster than via the timed check, which becomes a back-up.
   // Note that WSAAsyncSelect() on a server socket also automatically applies 
   // to all client sockets created from it by accept(), and also that
   // WSAAsyncSelect() puts the socket into non-blocking mode, duplicating what
   // we have already done above using ioctlsocket()
   if (WSAAsyncSelect(ServerSocket, (int)ChartGetInteger(0, CHART_WINDOW_HANDLE), 0x100 /* WM_KEYDOWN */, 0xFF /* All events */) != 0) {
      Print("ERROR " , WSAGetLastError() , " in async select");
   }
   #endif

   // Flag that we've successfully initialised
   SuccessfulInit = true;

   // If we're operating as an EA rather than a script, then try setting up a timer
   #ifdef  COMPILE_AS_EA
   CreateTimer();
   #endif   
}


// ---------------------------------------------------------------------
// Variation between EA and script, depending on COMPILE_AS_EA above.
// Both versions simply call MainLoop() to handle the socket activity.
// The script version simply does so from a loop in OnStart().
// The EA version sets up a timer, and then calls MainLoop from OnTimer().
// It also has event-driven handling of socket activity by getting
// Windows to simulate keystrokes whenever a socket event happens.
// This is even faster than relying on the timer.
// ---------------------------------------------------------------------

#ifdef  COMPILE_AS_EA
   // .........................................................
   // EA version. Simply calls MainLoop() from a timer.
   // The timer has previously been created in OnInit(), 
   // though we also check this in OnTick()...
   void OnTimer()
   {
      MainLoop();
   }
   
   // The function which creates the timer if it hasn't yet been set up
   void CreateTimer()
   {
      if (!CreatedTimer) CreatedTimer = EventSetMillisecondTimer(SLEEP_MILLISECONDS);
   }

   // Timer creation can sometimes fail in OnInit(), at least
   // up until MT4 build 970, if MT4 is starting up with the EA already
   // attached to a chart. Therefore, as a fallback, we also 
   // handle OnTick(), and thus eventually set up the timer in
   // the EA's first tick if we failed to set it up in OnInit()
   void OnTick()
   {
      CreateTimer();
   }

   // In an EA, the use of WSAAsyncSelect() above means that Windows will fire a key-down 
   // message whenever there is socket activity. We can then respond to KEYDOWN events
   // in MQL4 as a way of knowing that there is socket activity to be dealt with.
   void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
   {
      if (id == CHARTEVENT_KEYDOWN) {
         // The lparam will be the socket handle, and the dparam will be the type
         // of socket event (e.g. dparam = 1 = FD_READ). The fastest option would be
         // to use the socket handle in lparam to identify the specific socket
         // which requires action, but we'll take the slightly less efficient but simpler
         // route where we just do all our full socket handling in response to an event.
         // Similarly, we won't bother to distinguish between real keypresses and 
         // the pseudo-keypresses from WSAAyncSelect().
         MainLoop();   
      }
   }
  
#else

   // .........................................................
   // Script version. Simply calls MainLoop() repeatedly
   // until the script is removed, with a small sleep between calls
   void OnStart()
   {
      while (!IsStopped()) {
         MainLoop();
         Sleep(SLEEP_MILLISECONDS);
      }
   }
#endif


// ---------------------------------------------------------------------
// Main processing loop which handles new incoming connections, and reads
// data from existing connections.
// Can either be called from OnStart() in a script, on a continuous loop 
// until IsStopped() is true; or from OnTimer() in an EA.
// ---------------------------------------------------------------------

void MainLoop()
{
   // Do nothing if init was unsuccessful
   if (!SuccessfulInit) return;
   
   // .........................................................
   // Do we have a new pending connection on the server socket?
   timeval waitfor;
   waitfor.secs = 0;
   waitfor.usecs = 0;
   
   fd_set PollServerSocket;
   PollServerSocket.count = 1;
   PollServerSocket.single_socket = ServerSocket;

   int selres = select(0, PollServerSocket, 0, 0, waitfor);
   if (selres > 0) {
   
      Print("New incoming connection...");
      int NewClientSocket = accept(ServerSocket, 0, 0);
      if (NewClientSocket == -1) {
         if (WSAGetLastError() == 10035 /* WSAEWOULDBLOCK */) {
            // Blocking warning; ignore
         } else {
            Print("ERROR " , WSAGetLastError() , " in socket accept");
         }

      } else {
         Print("...accepted");

         int ctarr = ArraySize(Clients);
         ArrayResize(Clients, ctarr + 1);
         Clients[ctarr] = new Connection(NewClientSocket);               
         Print("Got connection to client #", Clients[ctarr].GetID());
      }
   }

   // .........................................................
   // Process any incoming data from client connections
   // (including any which have just been accepted, above)
   int ctarr = ArraySize(Clients);
   for (int i = ctarr - 1; i >= 0; i--) {
      // Return value from ReadAnyPendingData() is true
      // if the socket still seems to be alive; false if 
      // the connection seems to have been closed, and should be discarded
      if (Clients[i].ReadAnyPendingData()) {
         // Socket still seems to be alive
         
      } else {
         // Socket appears to be dead. Delete, and remove from list
         Print("Lost connection to client #", Clients[i].GetID());
         
         delete Clients[i];
         for (int j = i + 1; j < ctarr; j++) {
            Clients[j - 1] = Clients[j];
         }
         ctarr--;
         ArrayResize(Clients, ctarr);           
      }
   }
}

// ---------------------------------------------------------------------
// Termination - clean up sockets
// ---------------------------------------------------------------------

void OnDeinit(const int reason)
{
   closesocket(ServerSocket);
   
   for (int i = 0; i < ArraySize(Clients); i++) {
      delete Clients[i];
   }
   ArrayResize(Clients, 0);
   
   #ifdef  COMPILE_AS_EA
   EventKillTimer();
   CreatedTimer = false;
   #endif
}


// ---------------------------------------------------------------------
// Simple wrapper around each connected client socket
// ---------------------------------------------------------------------

class Connection {
private:
   // Client socket handle
   int mSocket;

   // Temporary buffer used to handle incoming data
   uchar mTempBuffer[SOCKET_READ_BUFFER_SIZE];
   
   // Stored-up data, waiting for a \r character 
   string mPendingData;
   
public:
   Connection(int ClientSocket);
   ~Connection();
   string GetID() {return IntegerToString(mSocket);}
   
   bool ReadAnyPendingData();
   void ProcessIncomingMessage(string strMessage);
};

// Constructor, called with the handle of a newly accepted socket
Connection::Connection(int ClientSocket)
{
   mPendingData = "";
   mSocket = ClientSocket; 
   
   // Put the client socket into non-blocking mode
   uint nbmode = 1;
   if (ioctlsocket(mSocket, 0x8004667E /* FIONBIO */, nbmode) != 0) {Print("ERROR in setting non-blocking mode on client socket");}
}

// Destructor. Simply close the client socket
Connection::~Connection()
{
   closesocket(mSocket);
}

// Called repeatedly on a timer, to check whether any
// data is available on this client connection. Returns true if the 
// client still seems to be connected (*not* if there's new data); 
// returns false if the connection seems to be dead. 
bool Connection::ReadAnyPendingData()
{
   // Check the client socket for data-readability
   timeval waitfor;
   waitfor.secs = 0;
   waitfor.usecs = 0;

   fd_set PollClientSocket;
   PollClientSocket.count = 1;
   PollClientSocket.single_socket = mSocket;

   int selres = select(0, PollClientSocket, 0, 0, waitfor);
   if (selres > 0) {
      
      // Winsock says that there is data waiting to be read on this socket
      int res = recv(mSocket, mTempBuffer, SOCKET_READ_BUFFER_SIZE, 0);
      if (res > 0) {
         // Convert the buffer to a string, and add it to any pending
         // data which we already have on this connection
         string strIncoming = CharArrayToString(mTempBuffer, 0, res);
         mPendingData += strIncoming;
         
         // Do we have a complete message (or more than one) ending in \r?
         int idxTerm = StringFind(mPendingData, "\r");
         while (idxTerm >= 0) {
            if (idxTerm > 0) {
               string strMsg = StringSubstr(mPendingData, 0, idxTerm);         
               
               // Strip out any \n characters lingering from \r\n combos
               StringReplace(strMsg, "\n", "");
               
               // Print the \r-terminated message in the log
               ProcessIncomingMessage(strMsg);
            }               
         
            // Keep looping until we have extracted all the \r delimited 
            // messages, and leave any residue in the pending data 
            mPendingData = StringSubstr(mPendingData, idxTerm + 1);
            idxTerm = StringFind(mPendingData, "\r");
         }
         
         return true;
      
      } else if (WSAGetLastError() == 10035 /* WSAEWOULDBLOCK */) {
         // Would block; not an error
         return true;
         
      } else {
         // recv() failed. Assume socket is dead
         return false;
      }
   
   } else if (selres == -1) {
      // Assume socket is dead
      return false;
      
   } else {
      // No pending data
      return true;
   }
}

// Can override this with whatever you want to do with incoming messages:
// handling trading commands, send data back down the socket etc etc etc
void Connection::ProcessIncomingMessage(string strMessage)
{
   Print("#" , GetID() , ": " , strMessage);
}
 
jjc:

Pour s'amuser un peu...


Lol. Incroyable. Félicitations. Je ne pensais pas qu'il était possible de coder ces fonctionnalités sur MQL.

 
Mariop:

Lol. Incroyable. Félicitations. Je ne pensais pas qu'il était possible de coder ces fonctionnalités sur MQL.

Mon instinct est toujours de faire des sockets de serveur en utilisant le multi-threading, mais je ne peux pas penser à une raison pour laquelle ce code ne serait pas viable et sûr pour une utilisation dans la vie réelle.

Il y a une chose dans la version la plus récente, ci-dessus, qui n'est pas idéale. Plutôt que de poster un autre gros bloc de code, je vais le décrire à la place :

  • L'utilisation de WSASyncSelect() pour faire le traitement plus rapide basé sur les événements dépend de la présence d'un handle de fenêtre graphique.
  • Mais essayer d'utiliser CHART_WINDOW_HANDLE échouera dans OnInit() si MT4 démarre avec un EA déjà et précédemment attaché à un graphique (comme le fait, déjà traité, que EventSetMillisecondTimer peut échouer dans OnInit).
  • Par conséquent, si vous redémarrez MT4 avec l'EA déjà attaché et en cours d'exécution, l'appel à WSAAsyncSelect échouera, et vous n'obtiendrez qu'une gestion de l'activité de la socket basée sur un timer, et non la gestion plus rapide basée sur les événements.

Il s'agit d'une petite modification du code, mais je ne veux pas poster un autre gros bloc dans ce forum alors que vous seul êtes intéressé.

 
jjc :

Plutôt que de poster un autre gros bloc de code [...]

Après réflexion...

Encore une autre version, avec les modifications suivantes :

  • Fonctionne sur MT5 (32 bits et 64 bits) ainsi que sur MT4
  • Gère le fait que CHART_WINDOW_HANDLE peut échouer dans OnInit
  • Effectue un traitement plus rapide des notifications événementielles via OnChartEvent, en ne vérifiant que le socket spécifique qui a généré l'événement plutôt que d'exécuter l'intégralité de MainLoop()
  • Autorise éventuellement les connexions à partir de n'importe quel ordinateur plutôt qu'un simple hôte local, commutable via un paramètre dans les entrées

 // *********************************************************************
// Example of non-blocking server sockets, allowing connections
// from multiple concurrent clients. This example waits for 
// each \r-terminated line from a client (e.g. Telnet), and prints it 
// to the Experts log. The behaviour can be extended into something
// more realistic by changing Connection::ProcessIncomingMessage().
//
// Works on both MT4 and MT5 (32-bit and 64-bit). But see the 
// notes to socketselect3264() about the cheat which is used
// on 64-bit MT5.
// *********************************************************************

#property strict 


// ---------------------------------------------------------------------
// This code can either create an EA or a script. To create a script,
// comment out the line below. The code will then have OnTimer() 
// and OnTick() instead of OnStart()
// ---------------------------------------------------------------------

#define COMPILE_AS_EA


// ---------------------------------------------------------------------
// If we are compiling as a script, then we want the script
// to display the inputs page
// ---------------------------------------------------------------------

#ifndef COMPILE_AS_EA
#property show_inputs
#endif


// ---------------------------------------------------------------------
// User-configurable parameters
// ---------------------------------------------------------------------

// Purely cosmetic; causes MT4 to display the comments below
// as the options for the input, instead of using a boolean true/false
enum AllowedAddressesEnum {
   aaeLocal = 0 ,   // localhost only (127.0.0.1)
   aaeAny = 1        // All IP addresses
};

// User inputs
input int                      PortNumber = 51234 ;     // TCP/IP port number
input AllowedAddressesEnum    AcceptFrom = aaeLocal;   // Accept connections from


// ---------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------

// Size of temporary buffer used to read from client sockets
#define SOCKET_READ_BUFFER_SIZE   10000

// Defines the frequency of the main-loop processing, in milliseconds.
// This is either the duration of the sleep in OnStart(), if compiled
// as a script, or the frequency which is given to EventSetMillisecondTimer()
// if compiled as an EA. (In the context of an EA, the use of additional
// event-driven handling means that the timer is only a fallback, and
// this value could be set to a larger value, just acting as a sweep-up)
#define SLEEP_MILLISECONDS       10


// ---------------------------------------------------------------------
// Forward definitions of classes
// ---------------------------------------------------------------------

// Wrapper around a connected client socket
class Connection;

// ---------------------------------------------------------------------
// Winsock structure definitions and DLL imports
// ---------------------------------------------------------------------

struct sockaddr_in {
   short af_family;
   short port;
   int addr;
   int dummy1;
   int dummy2;
};

struct timeval {
   int secs;
   int usecs;
};

struct fd_set {
   int count;
   int single_socket;
   int dummy[ 63 ];
};

struct fd_set64 {
   long count;
   long single_socket;
   long dummy[ 63 ];
};

#import "Ws2_32.dll"
   int socket( int , int , int );   
   int bind( int , sockaddr_in&, int );
   int htons( int );
   int listen( int , int );
   int accept( int , int , int );
   int closesocket( int );
   int select( int , fd_set&, int , int , timeval&);
   int select( int , fd_set64&, int , int , timeval&);   // See notes to socketselect3264() below
   int recv( int , uchar &[], int , int );
   int ioctlsocket( int , uint , uint &);
   int WSAGetLastError();
   int WSAAsyncSelect( int , int , uint , int );
#import   


// ---------------------------------------------------------------------
// Global variables
// ---------------------------------------------------------------------

// Flags whether OnInit was successful
bool SuccessfulInit = false ;

// Handle of main listening server socket
int ServerSocket;

// List of currently connected clients
Connection * Clients[];

#ifdef COMPILE_AS_EA
// For EA compilation only, we track whether we have 
// successfully created a timer
bool CreatedTimer = false ;   

// For EA compilation only, we track whether we have 
// done WSAAsyncSelect(), which can't reliably done in OnInit()
// because a chart handle isn't necessarily available
bool DoneAsyncSelect = false ;
#endif

// ---------------------------------------------------------------------
// Initialisation - create listening socket
// ---------------------------------------------------------------------

void OnInit ()
{
   SuccessfulInit = false ;
   
   if (! TerminalInfoInteger ( TERMINAL_DLLS_ALLOWED )) { Print ( "Requires \'Allow DLL imports\'" ); return ;}
   
   // (Don't need to call WSAStartup because MT4 must have done this)

   // Create the main server socket
   ServerSocket = socket( 2 /* AF_INET */ , 1 /* SOCK_STREAM */ , 6 /* IPPROTO_TCP */ );
   if (ServerSocket == - 1 ) { Print ( "ERROR " , WSAGetLastError() , " in socket creation" ); return ;}
   
   // Put the socket into non-blocking mode
   uint nbmode = 1 ;
   if (ioctlsocket(ServerSocket, 0x8004667E /* FIONBIO */ , nbmode) != 0 ) { Print ( "ERROR in setting non-blocking mode on server socket" ); return ;}
   
   // Bind the socket to the specified port number. In this example,
   // we only accept connections from localhost
   sockaddr_in service;
   service.af_family = 2 /* AF_INET */ ;
   service.addr = (AcceptFrom == aaeAny ? 0 /* INADDR_ANY */ : 0x100007F /* 127.0.0.1 */ );
   service.port = ( short )htons(PortNumber);
   if (bind(ServerSocket, service, 16 /* sizeof(service) */ ) == - 1 ) { Print ( "ERROR " , WSAGetLastError() , " in socket bind" ); return ;}

   // Put the socket into listening mode
   if (listen(ServerSocket, 10 ) == - 1 ) { Print ( "ERROR " , WSAGetLastError() , " in socket listen" ); return ;}

   #ifdef COMPILE_AS_EA
   // If we're operating as an EA, set up event-driven handling of the sockets
   // as described below. (Also called from OnTick() because this requires a 
   // window handle, and the set-up will fail here if MT4 is starting up
   // with the EA already attached to a chart.)
   SetupAsyncSelect();
   #endif

   // Flag that we've successfully initialised
   SuccessfulInit = true ;

   // If we're operating as an EA rather than a script, then try setting up a timer
   #ifdef COMPILE_AS_EA
   CreateTimer();
   #endif   
}


// ---------------------------------------------------------------------
// Variation between EA and script, depending on COMPILE_AS_EA above.
// The script version simply calls MainLoop() from a loop in OnStart().
// The EA version sets up a timer, and then calls MainLoop from OnTimer().
// It also has event-driven handling of socket activity by getting
// Windows to simulate keystrokes whenever a socket event happens.
// This is even faster than relying on the timer.
// ---------------------------------------------------------------------

#ifdef COMPILE_AS_EA
   // .........................................................
   // EA version. Simply calls MainLoop() from a timer.
   // The timer has previously been created in OnInit(), 
   // though we also check this in OnTick()...
   void OnTimer ()
   {
      MainLoop();
   }
   
   // The function which creates the timer if it hasn't yet been set up
   void CreateTimer()
   {
       if (!CreatedTimer) CreatedTimer = EventSetMillisecondTimer (SLEEP_MILLISECONDS);
   }

   // Timer creation can sometimes fail in OnInit(), at least
   // up until MT4 build 970, if MT4 is starting up with the EA already
   // attached to a chart. Therefore, as a fallback, we also 
   // handle OnTick(), and thus eventually set up the timer in
   // the EA's first tick if we failed to set it up in OnInit().
   // Similarly, the call to WSAAsyncSelect() may not be possible
   // in OnInit(), because it requires a chart handle, and that does
   // not exist in OnInit() during MT4 start-up.
   void OnTick ()
   {
      CreateTimer();
      SetupAsyncSelect();
   }

   // In the context of an EA, we can improve the latency even further by making the 
   // processing event-driven, rather than just on a timer. If we use
   // WSAAsyncSelect() to tell Windows to send a WM_KEYDOWN whenever there's socket
   // activity, then this will fire the EA's OnChartEvent below. We can thus collect
   // socket events even faster than via the timed check, which becomes a back-up.
   // Note that WSAAsyncSelect() on a server socket also automatically applies 
   // to all client sockets created from it by accept(), and also that
   // WSAAsyncSelect() puts the socket into non-blocking mode, duplicating what
   // we have already above using ioctlsocket().
   // The further complication is that WSAAsyncSelect() requires a window handle,
   // and this is not available in OnInit() during MT4 start-up. Therefore,
   // like the timer though for different reasons, we repeat the call to
   // this function during OnTick()
   void SetupAsyncSelect()
   {
       if (DoneAsyncSelect) return ;
       if (WSAAsyncSelect(ServerSocket, ( int ) ChartGetInteger ( 0 , CHART_WINDOW_HANDLE ), 0x100 /* WM_KEYDOWN */ , 0xFF /* All events */ ) == 0 ) {
         DoneAsyncSelect = true ;
      }
   }

   // In an EA, the use of WSAAsyncSelect() above means that Windows will fire a key-down 
   // message whenever there is socket activity. We can then respond to KEYDOWN events
   // in MQL4 as a way of knowing that there is socket activity to be dealt with.
   void OnChartEvent ( const int id, const long & lparam, const double & dparam, const string & sparam)
   {
       if (id == CHARTEVENT_KEYDOWN ) {
         // For a pseudo-keypress from WSAAsyncSelect(), the lparam will be the socket handle
         // (and the dparam will be the type of socket event, e.g. dparam = 1 = FD_READ).
         // Of course, this event can also be fired for real keypresses on the chart...
         
         // What is the lparam?
         if (lparam == ServerSocket) {
             // New pending connection on the listening server socket
            AcceptNewConnections();
         
         } else {
             // See if lparam is one of the client sockets, by looping 
             // through the Clients[] array
             int ctarr = ArraySize (Clients);
             for ( int i = ctarr - 1 ; i >= 0 ; i--) {
               if (Clients[i].SocketHandle() == lparam) {

                   // Yes, we have found a socket matching this "keyboard" event.
                   // Return value from ReadAnyPendingData() is true
                   // if the socket still seems to be alive; false if 
                   // the connection seems to have been closed, and should be discarded
                   if (Clients[i].ReadAnyPendingData()) {
                     // Socket still seems to be alive
                     
                  } else {
                     // Socket appears to be dead
                     ReleaseClientSocket(i, ctarr);
                  }
                   return ; //Early exit!
               }
            }
            
             // If we get here, then the lparam does not match any
             // of the sockets, and the event must be a real keypress,
             // not a pseudo-keypress from WSAAsyncSelect()
         }
      }
   }
    
#else

   // .........................................................
   // Script version. Simply calls MainLoop() repeatedly
   // until the script is removed, with a small sleep between calls
   void OnStart ()
   {
       while (! IsStopped ()) {
         MainLoop();
         Sleep (SLEEP_MILLISECONDS);
      }
   }
#endif


// ---------------------------------------------------------------------
// Main processing loop which handles new incoming connections, and reads
// data from existing connections.
// Can either be called from OnTimer() in an EA; or from OnStart() in a script, 
// on a continuous loop until IsStopped() is true. In an EA, socket 
// activity should almost always get handled by the event-driven stuff 
// above, and this timer loop is just a fallback.
// ---------------------------------------------------------------------

void MainLoop()
{
   // Do nothing if init was unsuccessful
   if (!SuccessfulInit) return ;

   // This main timer loop does two things: accepts new incoming 
   // connections, and reads pending data on all sockets (including
   // new ones which have just been accepted)
   AcceptNewConnections();
   ReadSocketMessages();   
}

// Accept any new pending connections on the main listening server socket,
// wrapping the new caller in a Connection object and putting it
// into the Clients[] array
void AcceptNewConnections()
{
   int selres = socketselect3264(ServerSocket);
   if (selres > 0 ) {
   
       Print ( "New incoming connection..." );
       int NewClientSocket = accept(ServerSocket, 0 , 0 );
       if (NewClientSocket == - 1 ) {
         if (WSAGetLastError() == 10035 /* WSAEWOULDBLOCK */ ) {
             // Blocking warning; ignore
             Print ( "... would block; ignore" );
         } else {
             Print ( "ERROR " , WSAGetLastError() , " in socket accept" );
         }

      } else {
         Print ( "...accepted" );
         
         // Create a new Connection object to wrap the client socket,
         // and add it to the Clients[] array
         int ctarr = ArraySize (Clients);
         ArrayResize (Clients, ctarr + 1 );
         Clients[ctarr] = new Connection(NewClientSocket);               
         Print ( "Got connection to client #" , Clients[ctarr].GetID());
      }
   }
}


// Process any incoming data from all client connections
// (including any which have just been accepted, above)
void ReadSocketMessages()
{
   int ctarr = ArraySize (Clients);
   for ( int i = ctarr - 1 ; i >= 0 ; i--) {
       // Return value from ReadAnyPendingData() is true
       // if the socket still seems to be alive; false if 
       // the connection seems to have been closed, and should be discarded
       if (Clients[i].ReadAnyPendingData()) {
         // Socket still seems to be alive
         
      } else {
         // Socket appears to be dead. Delete, and remove from list
         ReleaseClientSocket(i, ctarr);
      }
   }
}

// Discards a client socket which appears to have died, deleting the Connection class
// and removing it from the Clients[] array. Takes two parameters: the index of 
// the connection within the Clients[] array, and the current size of that array (which
// is passed by reference, with the function sending back the decremented size)
void ReleaseClientSocket( int idxReleaseAt, int & SizeOfArray)
{
   Print ( "Lost connection to client #" , Clients[idxReleaseAt].GetID());
   
   delete Clients[idxReleaseAt];
   for ( int j = idxReleaseAt + 1 ; j < SizeOfArray; j++) {
      Clients[j - 1 ] = Clients[j];
   }
   SizeOfArray--;
   ArrayResize (Clients, SizeOfArray);           
}

// Wraps the Winsock select() function, doing an immediate non-blocking
// check of a single socket. This is the one area where we must
// vary things between 32-bit and 64-bit. On 64-bit MT5, we can get away
// with using Winsock calls where everything is defined as a 32-bit int;
// we can rely on the fact that socket and window handles are only ever going
// to be 4 bytes rather than 8 bytes - naughty, but works in practice.
// The one exception is the fd_set structure. On 64-bit MT5, 
// we must pass a version of the fd_set where everything is defined
// as 8-byte long rather than 4-byte int. Note that this applies to
// the MT5 version, not the O/S. On 32-bit MT5, even on a 64-bit computer,
// we use the same structure as MT4: 4-byte int rather than 8-byte long
int socketselect3264( int TestSocket)
{
   timeval waitfor;
   waitfor.secs = 0 ;
   waitfor.usecs = 0 ;

   // The following, incidentally, is permitted on MT4, but obviously returns false...
   if ( TerminalInfoInteger ( TERMINAL_X64 )) {
      fd_set64 PollSocket64;
      PollSocket64.count = 1 ;
      PollSocket64.single_socket = TestSocket;

       return select( 0 , PollSocket64, 0 , 0 , waitfor);

   } else {
      fd_set PollSocket32;
      PollSocket32.count = 1 ;
      PollSocket32.single_socket = TestSocket;
   
       return select( 0 , PollSocket32, 0 , 0 , waitfor);
   }
}


// ---------------------------------------------------------------------
// Termination - clean up sockets
// ---------------------------------------------------------------------

void OnDeinit ( const int reason)
{
   closesocket(ServerSocket);
   
   for ( int i = 0 ; i < ArraySize (Clients); i++) {
       delete Clients[i];
   }
   ArrayResize (Clients, 0 );
   
   #ifdef COMPILE_AS_EA
   EventKillTimer ();
   CreatedTimer = false ;
   DoneAsyncSelect = false ;
   #endif
}


// ---------------------------------------------------------------------
// Simple wrapper around each connected client socket
// ---------------------------------------------------------------------

class Connection {
private :
   // Client socket handle
   int mSocket;

   // Temporary buffer used to handle incoming data
   uchar mTempBuffer[SOCKET_READ_BUFFER_SIZE];
   
   // Stored-up data, waiting for a \r character 
   string mPendingData;
   
public :
   Connection( int ClientSocket);
   ~Connection();
   string GetID() { return IntegerToString (mSocket);}
   int SocketHandle() { return mSocket;}
      
   bool ReadAnyPendingData();
   void ProcessIncomingMessage( string strMessage);
};

// Constructor, called with the handle of a newly accepted socket
Connection::Connection( int ClientSocket)
{
   mPendingData = "" ;
   mSocket = ClientSocket; 
   
   // Put the client socket into non-blocking mode
   uint nbmode = 1 ;
   if (ioctlsocket(mSocket, 0x8004667E /* FIONBIO */ , nbmode) != 0 ) { Print ( "ERROR in setting non-blocking mode on client socket" );}
}

// Destructor. Simply close the client socket
Connection::~Connection()
{
   closesocket(mSocket);
}

// Called repeatedly on a timer, to check whether any
// data is available on this client connection. Returns true if the 
// client still seems to be connected (*not* if there's new data); 
// returns false if the connection seems to be dead. 
bool Connection::ReadAnyPendingData()
{
   // Check the client socket for data-readability
   int selres = socketselect3264(mSocket);
   if (selres > 0 ) {
      
       // Winsock says that there is data waiting to be read on this socket
       int szData = recv(mSocket, mTempBuffer, SOCKET_READ_BUFFER_SIZE, 0 );
       if (szData > 0 ) {
         // Convert the buffer to a string, and add it to any pending
         // data which we already have on this connection
         string strIncoming = CharArrayToString (mTempBuffer, 0 , szData);
         mPendingData += strIncoming;
         
         // Do we have a complete message (or more than one) ending in \r?
         int idxTerm = StringFind (mPendingData, "\r" );
         while (idxTerm >= 0 ) {
             if (idxTerm > 0 ) {
               string strMsg = StringSubstr (mPendingData, 0 , idxTerm);         
               
               // Strip out any \n characters lingering from \r\n combos
               StringReplace (strMsg, "\n" , "" );
               
               // Print the \r-terminated message in the log
               ProcessIncomingMessage(strMsg);
            }               
         
             // Keep looping until we have extracted all the \r delimited 
             // messages, and leave any residue in the pending data 
            mPendingData = StringSubstr (mPendingData, idxTerm + 1 );
            idxTerm = StringFind (mPendingData, "\r" );
         }
         
         return true ;
      
      } else if (WSAGetLastError() == 10035 /* WSAEWOULDBLOCK */ ) {
         // Would block; not an error
         return true ;
         
      } else {
         // recv() failed. Assume socket is dead
         return false ;
      }
   
   } else if (selres == - 1 ) {
       // Assume socket is dead
       return false ;
      
   } else {
       // No pending data
       return true ;
   }
}

// Can override this with whatever you want to do with incoming messages:
// handling trading commands, send data back down the socket etc etc etc
void Connection::ProcessIncomingMessage( string strMessage)
{
   Print ( "#" , GetID() , ": " , strMessage);
}
 
jjc: En y réfléchissant bien...

Au lieu de poster des contributions SRC aussi longues, envisagez de simplement joindre le fichier lui-même à la place. Je vous suggère de MODIFIER vos longs messages en supprimant les sections SRC et en joignant simplement le fichier source.

 
FMIC:

Au lieu de poster des contributions SRC aussi longues, envisagez de simplement joindre le fichier lui-même à la place. Je vous suggère de MODIFIER vos longs messages en supprimant les sections SRC et en joignant simplement le fichier source à la place.

Merci beaucoup pour votre contribution.

Mais, parmi les nombreuses raisons pour lesquelles je ne vais pas le faire, il y a le fait que le code ci-dessus fonctionne à la fois sur MT4 et MT5. Si je le téléchargeais en tant que pièce jointe, il devrait être marqué de manière trompeuse comme étant soit .mq4, soit .mq5, alors qu'en fait il fonctionne sur les deux.

 
jjc:

Mais, parmi les nombreuses raisons [...]

Une autre des principales raisons est le scénario suivant, qui aurait pu s'appliquer au PO :

" Je m'y connais assez bien en programmation de socket dans un langage tel que Java. Je me demande si quelqu'un a écrit un serveur de socket pour MT4 ? Si oui, cela doit impliquer l'utilisation de fonctions telles que listen(), bind(), htons(), etc. Je vais donc essayer de trouver un tel article en faisant une recherche Google pour 'mql4 bind listen htons' ".

Pour autant que je sache, ni les moteurs de recherche ni la recherche sur ce site ne peuvent regarder à l'intérieur des pièces jointes. Donc, le fait de poster le code en pièce jointe le rendrait invisible à une recherche telle que "mql4 bind listen htons".

Vous dites régulièrement aux gens qu'ils auraient dû faire une recherche ou consulter la documentation ; c'est exactement ce que j'essaie de faire.

(Il se trouve que "mql4 bind listen htons" ne trouve pas ce sujet parce que Google n'a pas exploré la page depuis le 14 septembre. Mais elle devrait commencer à apparaître sur Google prochainement).

 
jjc:

Merci beaucoup pour votre contribution.

Mais, parmi les nombreuses raisons pour lesquelles je ne vais pas le faire, il y a le fait que le code ci-dessus fonctionne à la fois sur MT4 et MT5. Si je le téléchargeais en tant que pièce jointe, il devrait être marqué de manière trompeuse comme étant soit .mq4, soit .mq5, alors qu'en fait il fonctionne sur les deux.

Dans ce cas, transformez-le en un fichier d'en-tête d'inclusion (".mqh"). De cette façon, il sera réutilisable à la fois dans MQL4 et MQL5. Assurez-vous simplement de ne pas intégrer le code directement dans les gestionnaires d'événements, OnInit(), OnDeinit(), etc. - Donnez à ces fonctions des noms distincts et uniques, et l'utilisateur n'aura qu'à placer un appel à ces fonctions dans son propre code à partir de ces gestionnaires.
 
jjc:

En y réfléchissant bien...

Encore une autre version, avec les changements suivants :

  • Fonctionne sur MT5 (à la fois 32-bit et 64-bit) ainsi que sur MT4
  • Gère le fait que CHART_WINDOW_HANDLE peut échouer dans OnInit.
  • Traitement plus rapide des notifications événementielles via OnChartEvent, en vérifiant uniquement le socket spécifique qui a généré l'événement plutôt que d'exécuter la totalité de MainLoop().
  • Permet de se connecter à partir de n'importe quel ordinateur plutôt que de se limiter à localhost, ce qui peut être changé par un paramètre dans Inputs.


MERCI encore jjc,

Pour l'instant je suis pressé et je n'ai pas le temps de le tester, mais je le ferai la semaine prochaine et je vous en parlerai. Je pense que je n'aurai aucun problème à le faire fonctionner, mais je vous poserai peut-être quelques questions. BTW : S'il vous plaît, ne modifiez aucun message ; ils sont tous parfaits tels quels (je me sens comme dans StackOverflow =)).