English Русский Español Deutsch 日本語 Português
MetaTrader 4 Expert Advisor 与外部世界交换信息

MetaTrader 4 Expert Advisor 与外部世界交换信息

MetaTrader 4示例 | 11 四月 2016, 14:38
6 197 0
more
[删除]

简介

这是一个软件工具,允许 MetaTrader 4 Expert Advisor 创建服务器和客户端。 客户端可以建立与其自己的服务器以及任何其他可提供点对点协议连接的服务器类型的连接。 提供的软件工具包括两个组件:

  • NetEventsProc.exe - 是一个以后台模式(无控制台)运行的 Windows 进程,并在第二个组件 NetEventsProcDLL.dll 需要时执行。 根据应用中的请求,它可为以下两者创建服务器和客户端:为其自己的服务器和任何其他可提供点对点协议连接的服务器类型。 例如,你可创建与网站交换信息的客户端。 当然,首先要能够支持 HTTP 协议。

  • NetEventsProcDLL.dll - 是需要 NetEventsProc.exe 进程服务和 NetEventsProc.exe 进程自身的应用之间的接口。 凭借 DLL 接口,任何以编程语言编写的程序都可使用此软件工具进行双向信息交换。 MetaTrader 4 Expert Advisor 只是此软件工具的一个可能用例。

本文的结构如下:

  • 快速启动:所提供的软件工具的实际运用最初在简单示例中进行证明,然后在更复杂的示例中进行证明。

  • DLL 接口规格:NetEventsProc.exe 进程在处理应用中的 DLL 函数时提供的这些函数和服务的详细说明。

  • 项目实施:阐明此项目实施详情(如果可能)。

随附的 NetServerClient.zip 存档包含两个 Microsoft Visual Studio 2010 Ultimate 项目: NetEventsProc - 用于构建 NetEventsProc.exe, NetEventsProcDLL - 构建 NetEventsProcDLL.dll 源代码有详细注释。 你可调查实施详情并根据你的特定需求定制项目(如果愿意)。

NetEventsProc.exe 使用异步套接字实施服务器和客户端。 为了将套接字切换为异步模式,使用在异步模式下操作的可能方式之一:将套接字绑定至 WSAEventSelect(h_Socket, h_Event, FD_ALL_EVENTS) 网络事件。

此项目基于伟大的 Master Elmue 的基础工作。



1. 快速启动

1.1. exe.zip 存档

提取此存档。 我们将找到以下组件:

  • NetEventsProcDLL.dll - 将其放入“C:\Windows\System32\”文件夹中。

  • NetEventsProc.exe - 创建“C:\NetEventsProc\”文件夹并将此组件放入此文件夹中。 NetEventsProcDLL.dll 就在此文件夹中搜索 NetEventsProc.exe 模块!

此存档的以下组件提供将 DLL 函数从 NetEventsProcDLL.dll 导入到应用中的机制:

  • ImportNetEventsProcDLL.mqh - 从 NetEventsProcDLL.dll 导入到 MetaTrader 4 Expert Advisor 的函数原型。 将此文件放到终端数据文件夹“MetaTrader 4\experts\include\”中。

  • cNetEventsProcDLL.h - C++ 类定义包含 NetEventsProcDLL.dll 中的所有 DLL 函数原型。 在 C++ 程序中包括此类,可允许导入 NetEventsProcDLL.dll 中的所有 DLL 函数。 对于用其他编程语言编写的程序,你应单独导入各个函数或重新编写此类的定义。

  • NetEventsProcDLL.lib - 程序中包括的模块,可在加载时动态链接模式下导入 NetEventsProcDLL.dll 中的 DLL 函数(编译工具: /EHsc/link NetEventsProcDLL.lib)。

这实际上完成了配置流程。 现在可以用任何编程语言编写 MetaTrader 4 Expert Advisor 和应用程序,使用 DLL 函数创建服务器和客户端。

为避免跑题,我们在这里提供 ImportNetEventsProcDLL.mqh 和 cNetEventsProcDLL.h 的源代码。 ImportNetEventsProcDLL.mqh 头文件包含 NetEventsProcDLL.dll 程序的已导入 DLL 函数和其他两个服务函数的原型:

string GetErrMsg(int s32_Error);
string FormatIP(int IP);

GetErrMsg 函数 将 DLL 函数的返回码转换为文本。 FormatIP 函数 将 IP 地址的二进制表示法转换为标准文本格式,例如“93.127.110.161”。 应将 ImportNetEventsProcDLL.mqh 文件放到终端数据文件夹“MetaTrader 4\experts\include\”中。

以下是: ImportNetEventsProcDLL.mqh 的源代码(仅提供文件的一部分,此部分直接对应已导入 DLL 函数的原型定义):

// ImportNetEventsProcDLL.mqh

#import "NetEventsProcDLL.dll"
// Only for Clients:
int ConnectTo(string  ps8_ServerIP,             // in - string ps8_ServerIP = "0123456789123456"
              int     s32_Port,                 // in 
              int&    h_Client[]);              // out - int h_Client[1]
                 
int ConnectClose(int h_Client);                 // in
//
// Only for Server:
int ServerOpen(int  s32_Port);                  // in

int GetAllConnections(int& ph_Client[],         // out - int ph_Client[62]
                      int& ps32_ClientIP[],     // out - int ps32_ClientIP[62]
                      int& ps32_ClientCount[]); // out - int ps32_ClientCount[1]
                       
int DisconnectClient(int h_Client);             // in

int ServerClose();
//
// For both: Clients and Server
int SendToInt   (int  h_Client,             // in
                 int& ps32_SendBuf[],       // in
                 int  s32_SendBufLen);      // in - SendBuf[] array size in int element 
                 
int SendToDouble(int     h_Client,          // in
                 double& pd_SendBuf[],      // in
                 int     s32_SendBufLen);   // in - SendBuf[] array size in double element 
                 
int SendToString(int    h_Client,           // in
                 string ps8_SendBuf,        // in
                 int    s32_SendBufLen);    // in - SendBuf string size in char element
                 

int ReadFromInt   (int h_Client,            // in
                   int& ps32_ReadBuf[],     // in 
                   int  s32_ReadBufLen,     // in  - ReadBuf[] array size in int element
                   int& ps32_ReadLen[]);    // out - int ps32_ReadLen[1] - count of actually read data in int element
                  
int ReadFromDouble(int     h_Client,        // in
                   double& pd_ReadBuf[],    // in
                   int     s32_ReadBufLen,  // in  - ReadBuf[] array size in double element
                   int&    ps32_ReadLen[]); // out - int ps32_ReadLen[1] - count of actually read data in double element
                   
int ReadFromString(int     h_Client,        // in
                   string  ps8_ReadBuf,     // in
                   int     s32_ReadBufLen,  // in  - ReadBuf   string size in char element
                   int&    ps32_ReadLen[]); // out - int ps32_ReadLen[1] - count of actually read data in char element
//                   
#import
//***************************************************************************************
...
...
...
// Get a human readable error message for an API error code
string GetErrMsg(int s32_Error)
{
   ...
   .. 
}

// Convert DWORD IP to string IP
string FormatIP(int IP)
{
   ...
   ...
   ...
}       

cNetEventsProcDLL.h 文件 具有 C++ 类定义和从 NetEventsProcDLL.dll 导入的所有 DLL 函数。 以下是此文件的源代码:

//+---------------------------------------------------------------------------+
//|                                            cNetEventsProcDLL.h            |
//|                      Copyright © 2012, https://www.mql4.com/ en/users/more  |

//|                                       tradertobe@gmail.com                |
//+---------------------------------------------------------------------------+

// cNetEventsProcDLL.h

#pragma once

#define EXPFUNC __declspec(dllexport)
        
class cNetEventsProcDLL
{
public:
    static BOOL MessageDLL_PROCESS_ATTACH(void);
    static BOOL MessageDLL_PROCESS_DETACH(void);
//*******************************************************************************************************************
    static EXPFUNC int __stdcall ConnectTo(char* ps8_ServerIP, //in - ps8_ServerIP = "0123456789123456"
                                           int   s32_Port,     //in 
                                           int*  ph_Client);   //out - int ph_Client[1]

    static EXPFUNC int __stdcall ConnectClose(int h_Client);   //in 

    static EXPFUNC int __stdcall ServerOpen(int s32_Port);     //in

    static EXPFUNC int __stdcall GetAllConnections(int* ph_Client,         // out - int ph_Client[62]
                                                   int* ps32_ClientIP,     // out - int ps32_ClientIP[62]
                                                   int* ps32_ClientCount); // out - int ps32_ClientCount[1]
    
    static EXPFUNC int __stdcall DisconnectClient(SOCKET h_Client);        // in 

    static EXPFUNC int __stdcall ServerClose();

    static EXPFUNC int __stdcall SendToInt(SOCKET h_Client,             // in
                                           int*   ps32_SendBuf,         // in
                                           int    s32_SendBufLen);      // in - SendBuf[] array size in int element

    static EXPFUNC int __stdcall SendToDouble(SOCKET  h_Client,         // in
                                              double* pd_SendBuf,       // in
                                              int     s32_SendBufLen);  // in - SendBuf[] array size in double element

    static EXPFUNC int __stdcall SendToString(SOCKET h_Client,          // in
                                              char*  ps8_SendBuf,       // in
                                              int    s32_SendBufLen);   // SendBuf string size in char element

    static EXPFUNC int __stdcall ReadFromInt(SOCKET h_Client,           // in
                                             int*   ps32_ReadBuf,       // in
                                             int    s32_ReadBufLen,     // ReadBuf[] array size in int element
                                             int*   ps32_ReadLen);      // out - int ps32_ReadLen[1] - actual count of read data in int element

    static EXPFUNC int __stdcall ReadFromDouble(SOCKET  h_Client,       // in
                                                double* pd_ReadBuf,     // in
                                                int     s32_ReadBufLen, // ReadBuf[] array size in double element
                                                int*    ps32_ReadLen);  // out - int ps32_ReadLen[1] - actual count of read data in double element

    static EXPFUNC int __stdcall ReadFromString(SOCKET h_Client,        // in
                                                char*  ps8_ReadBuf,     // in
                                                int    s32_ReadBufLen,  // ReadBuf[] array size in char element
                                                int*   ps32_ReadLen);   // out - int ps32_ReadLen[1] - actual count of read data in char element
//*******************************************************************************************************************
protected:
    static DWORD SendTo(SOCKET h_Client,   char* ps8_SendBuf, INT s32_SendBufLen);
    static DWORD ReadFrom(SOCKET h_Client, char* ps8_ReadBuf, INT s32_ReadBufLen, INT& s32_ReadLen);
};

1.2. FastStart.zip 存档

此存档包含演示示例中使用的所有程序的源代码。 C++ 程序表现为 Microsoft Visual Studio 2010 Ultimate 项目 客户端回显服务器。 MQL4 程序的源代码以及用于将 DLL 函数导入到 MQL4 程序中的 ImportNetEventsProcDLL.mqh 文件也包括在此存档中。 将此文件放到文件夹“MetaTrader 4\experts\include\”中。

在进一步的讨论中,所有这些程序的源代码都列示在文本中。 我们将考虑 3 个示例,它们演示如何使用以 MQL4 和 C++ 编程语言编写的所有 DLL 函数:

  • 1.2.1. 节 展示了 МetaТrader 4 Expert Advisor 服务器与 C++ 客户端之间的信息交换。

  • 1.2.2. 节 展示了 C++ 服务器与 МetaТrader 4 Expert Advisor 客户端之间的信息交换。

  • 1.2.3. 节 展示了 МetaТrader 4 Expert Advisor 之间的信息交换。 这些 EA 之一作为服务器向其他 МetaТrader 4 Expert Advisor 指标(Expert Advisor 客户端)提供指标值。 换句话说,我们已将“安全”指标的值分配给客户端。

1.2.1. МetaТrader 4 Expert Advisor 服务器与 C++ 程序客户端

探讨下 МetaТrader 4 Expert Advisor 与 C++ 程序之间信息交换这一额外任务:

  • EchoServer.mq4 - 用作回显服务器的 Expert Advisor。

  • Client.cpp - C++ 程序,用作此 Expert Advisor 服务器的客户端。

C++ 客户端读取用户在控制台中输入的消息,并将它们发送到 Expert Advisor。 Expert Advisor 接收这些消息,在终端窗口中显示它们,再将它们发送回接收者。 下图说明了这个思路:

图 1 МetaТrader 4 Expert Advisor 服务器与 C++ 程序客户端

以下是作为回显服务器的 MetaTrader 4 Expert Advisor EchoServer.mq4 的源代码:

//+---------------------------------------------------------------------------+
//|                                            EchoServer.mq4                 |
//|                      Copyright © 2012, https://www.mql4.com/ en/users/more  |
//|                                       tradertobe@gmail.com                |
//+---------------------------------------------------------------------------+
#property copyright "Copyright © 2012, https://www.mql4.com/ ru/users/more"
#property link      "https://www.mql4.com/ ru/users/more"
#include <ImportNetEventsProcDLL.mqh>
/*int ServerOpen(int  s32_Port); // in
*/
/*int ServerClose();
*/
/*int GetAllConnections(int& ph_Client[],         // out - int ph_Client[62]
                        int& ps32_ClientIP[],     // out - int ps32_ClientIP[62]
                        int& ps32_ClientCount[]); // out - int ps32_ClientCount[1]
*/                      
/*int SendToString(int    h_Client,               // in
                   string ps8_SendBuf,            // in
                   int    s32_SendBufLen);        // in - SendBuf string size in char element
*/ 
/*int ReadFromString(int     h_Client,            // in
                   string    ps8_ReadBuf,         // in
                   int       s32_ReadBufLen,      // in  - ReadBuf   string size in char element
                   int&      ps32_ReadLen[]);     // out - int ps32_ReadLen[1] - count of actually read data in char element     
*/                              
// Globals variables
int s32_Error;
int i;

int s32_Port = 2000;
bool b_ServerOpened = false;

// for GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount)
int ph_Client       [62];
int ps32_ClientIP   [62];
int ps32_ClientCount[1 ];


// for int ReadFromString(h_Client, ps8_Buf, s32_BufLen, ps32_ReadLen)
// for int SendToString  (h_Client, ps8_Buf, s32_BufLen)
string ps8_Buf = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
int    s32_BufLen;
int    ps32_ReadLen[1];
//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
{
   //----
   s32_BufLen = StringLen(ps8_Buf);
   
   if (!b_ServerOpened)
   {
      s32_Error = ServerOpen(s32_Port);
      Print("ServerOpen() return is: ",GetErrMsg(s32_Error));
      
      if (s32_Error == OK)
      {
         b_ServerOpened = true;
         Print("Server is Opened and Waiting fo Client connection requests...");
      }
   }
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
  {
//----
   if (b_ServerOpened)
   {
      s32_Error = ServerClose();
      Print("ServerClose() return is: ",GetErrMsg(s32_Error));
      
      if (s32_Error == OK)
         b_ServerOpened = false;
   }
//----
   return(0);
  }  

int start()
{
//----
   if (!b_ServerOpened)
      return(0);
      
   s32_Error = GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount);
   
   if (s32_Error != 0)
   {
      Print("GetAllConnections(...) return is: ",GetErrMsg(s32_Error));
      return(1);
   }
   

   Print("ClientCount = ", ps32_ClientCount[0]);

   
   for (i = 0; i<ps32_ClientCount[0]; i++)
   {
      Print("h_Client = ", ph_Client[i], "      Client IP =  ", FormatIP(ps32_ClientIP[i])); 
       
      s32_Error = ReadFromString(ph_Client[i], ps8_Buf, s32_BufLen, ps32_ReadLen);
      
      Print("ReadFromString(",ph_Client[i],",...) return is: ", GetErrMsg(s32_Error));
      
      if (s32_Error == 0)
      {
         Print("ReadFromString(",ph_Client[i],",...) ps32_ReadLen = ",ps32_ReadLen[0]);
         
         if (ps32_ReadLen[0] > 0)
         {
            Print("ReadFromString(",ph_Client[i],",...) Read data is: ", StringSubstr(ps8_Buf,0,ps32_ReadLen[0]));
            
            s32_Error = SendToString(ph_Client[i], ps8_Buf, StringLen(StringSubstr(ps8_Buf,0,ps32_ReadLen[0])));
            
            Print("SendToString(", ph_Client[i],",...) return is: ",GetErrMsg(s32_Error));
         }
      }
   }
//----
   return(0);
  }
//+------------------------------------------------------------------+

以下是作为 Expert Advisor 服务器客户端的 Client.cpp - C++ 程序的源代码:

//+---------------------------------------------------------------------------+
//|                                            Client.cpp                     |
//|                      Copyright © 2012, https://www.mql4.com/ en/users/more  |
//|                                       tradertobe@gmail.com                |
//+---------------------------------------------------------------------------+

// Client.cpp

#include <winsock2.h>
#pragma comment(lib, "ws2_32")
#include <iostream>
#pragma comment(lib, "NetEventsProcDLL") // NetEventsProcDLL.lib placed in ...\FastStart\Client\
#include  "cNetEventsProcDLL.h"

// LocalIp = 0x6401a8c0 -> 192.168.1.100
// Returns a list of all local IP's on this computer (multiple IP's if multiple network adapters)
DWORD GetLocalIPs(char s8_IpList[][20], int &s32_IpCount);

/* This is the simple Client.
   It connects to Echo Server, send your input to Echo Server and read Echo from Server.

   ATTENTION !!! If your want to connect to local Server do not use  "127.0.0.1" !!!ATTENTION !!! 
   You may get all your local IP's by means of GetLocalIPs(...) function, here is call example:
   // Local IP's list
        char s8_IpList[10][20];

        int s32_IpCount;
        DWORD u32_Err = GetLocalIPs(s8_IpList, s32_IpCount);

        if (u32_Err)
        {
                printf("Get local IP's error !, no local IP means no network available...");
                return 1;
        }

        printf("\nLocal IP's list:\n");

        for (int i = 0; i<s32_IpCount; i++)
                printf("\n%s\n",s8_IpList[i]);
        //
*/

// This is the Server we want to connect to...
#define SERVER_IP   "192.168.1.5"  //this is mine local IP's get by means of GetLocalIPs(...) function,
                                   // do not use so called loopback  "127.0.0.1" address !!! 
#define PORT         2000

int main()
{
        // Local IP's list
        char s8_IpList[10][20];

        int s32_IpCount;
        DWORD u32_Err = GetLocalIPs(s8_IpList, s32_IpCount);

        if (u32_Err)
        {
                printf("Get local IP's error !, no local IP means no network available...");
                return 1;
        }

        printf("\nLocal IP's list:\n");

        for (int i = 0; i<s32_IpCount; i++)
                printf("\n%s\n",s8_IpList[i]);
        //

        char s8_ServerIP[] = SERVER_IP;
        int  s32_Port      = PORT;
        int  ph_Client[1];

        int  h_Client;

        DWORD u32_Error = cNetEventsProcDLL::ConnectTo(SERVER_IP, PORT, ph_Client);

        if (u32_Error)
        {
                printf("\nConnectTo(...) failed with error: %d\n", u32_Error);
                return 1;
        }
        else
                printf("\nConnectTo(...) OK, ph_Client[0] = : %d\n", ph_Client[0]);

        h_Client = ph_Client[0];

        // Connection is established successfully ! Let's  have some SendTo(...) data on this connection...
        char  ps8_SendData[200];
        int   s32_SendDataLen;;

        char   ps8_ReadBuf[1025];
        DWORD  s32_ReadBufLen = 1025;
        int    ps32_ReadLen[1];

        while(true)
        {
                std::cout << "\nEnter something to send to Server or Exit 'q':\n" << std::endl;

                std::cin.getline(ps8_SendData, 200);

                s32_SendDataLen = strlen(ps8_SendData);

                OemToCharBuff(ps8_SendData, ps8_SendData, s32_SendDataLen);

                if (ps8_SendData[0] == 'q')
                {
                        u32_Error = cNetEventsProcDLL::ConnectClose(h_Client);
                        if (u32_Error)
                        {
                                printf("\nConnectClose(...) failed with error: %d\n", u32_Error);
                                break;
                        }
                        else
                        {
                                printf("\nConnectClose(...) OK.\n");
                                break;
                        }
                }

                u32_Error = cNetEventsProcDLL::SendToString(h_Client, ps8_SendData, s32_SendDataLen);   

                switch (u32_Error)
                {
                case 0:
                        printf("\nSendTo(...) OK");
                        printf("\nSendTo(%d...) sent %d bytes\n", h_Client, s32_SendDataLen);
                        CharToOemBuff(ps8_SendData, ps8_SendData, s32_SendDataLen);
                        printf("\nSendTo(%d...) Sent Data: %s\n",h_Client, ps8_SendData);
                        printf("Waiting now for Echo....");
                        break;
                case ERROR_INVALID_PARAMETER:
                        printf("\nSendTo(%d...) return is: ERROR_INVALID_PARAMETER(%d)\n",h_Client, u32_Error);
                        printf("\nERROR_INVALID_PARAMETER -> One of this parms or more: h_Client, ps8_SendData, u32_SendDataLen is invalid...\n");
                        break;
                case WSAEWOULDBLOCK:
                        printf("\nSendTo(%d...) return is: WSAEWOULDBLOCK(%d)\n",h_Client, u32_Error);
                        printf("\nWSAEWOULDBLOCK -> The data will be send after the next FD_WRITE event, do nouthing\n");
                        break;

                case WSA_IO_PENDING:
                        printf("\nSendTo(%d...) return is: WSA_IO_PENDING(%d)\n",h_Client, u32_Error);
                        printf("\nWSA_IO_PENDING -> Error: A previous Send operation is still pending. This data will not be sent, try latter\n");
                        break;

                default:
                        printf("\nSendTo(%d...)failed with severe error: %d\n",h_Client, u32_Error);
                        // Severe error -> abort event loop
                        printf("\nConnection was closed !\n");
                        break;
                };
                
                if (u32_Error == 0 || u32_Error == WSAEWOULDBLOCK)
                {
                        int ReadLen = 0;

                        while(!ReadLen)
                        {
                                u32_Error = cNetEventsProcDLL::ReadFromString(h_Client, ps8_ReadBuf, s32_ReadBufLen, ps32_ReadLen);

                                if (u32_Error)
                                {
                                        printf("\nReadFromString(%d...) failed with error: %d\n", h_Client, u32_Error);
                                        break;
                                }

                                ReadLen = ps32_ReadLen[0];
                        }
                        if (u32_Error)
                        {
                                printf("\nReadFromString(%d...) failed with error: %d\n", h_Client, u32_Error);
                        }
                        else
                        {
                                printf("\nReadFromString(%d...) OK, read %d  bytes\n", h_Client, ReadLen);
                        }
                                
                        if (ReadLen > 0)
                        {
                                CharToOemBuff(ps8_ReadBuf, ps8_ReadBuf, s32_SendDataLen);
                                ps8_ReadBuf[ReadLen] = 0;
                                printf("\nReadFromString(%d...) Read Data: %s\n", h_Client, ps8_ReadBuf);
                        }

                }

        }

        return 0;
}

// LocalIp = 0x6401a8c0 -> 192.168.1.100
// Returns a list of all local IP's on this computer (multiple IP's if multiple network adapters)
DWORD GetLocalIPs(char s8_IpList[][20], int &s32_IpCount)
{
        // Winsock version 2.0 is available on ALL Windows operating systems 
        // except Windows 95 which comes with Winsock 1.1
        WSADATA k_Data;
        DWORD u32_Error = WSAStartup(MAKEWORD(2,0), &k_Data);
        if (u32_Error)
                return u32_Error;

        int ps32_IpList[20];

        char s8_Host[500];
        if (gethostname(s8_Host, sizeof(s8_Host)) == SOCKET_ERROR)
                return WSAGetLastError();
        
        struct hostent* pk_Host = gethostbyname(s8_Host);
        if (!pk_Host)
                return WSAGetLastError();

        s32_IpCount = 0;

        for (DWORD i=0; TRUE; i++)
        {
                if (!pk_Host->h_addr_list[i])
                        break; // The IP list is zero terminated

                ps32_IpList[i] = *((DWORD*)pk_Host->h_addr_list[i]);

                s32_IpCount++;
        }

        if (!s32_IpCount)
                return WSAENETDOWN; // no local IP means no network available

        for (int i = 0; i<s32_IpCount; i++)
        {
                BYTE* pu8_Addr = (BYTE*)&ps32_IpList[i];
        
                sprintf(s8_IpList[i],"%d.%d.%d.%d",pu8_Addr[0], pu8_Addr[1], pu8_Addr[2], pu8_Addr[3]);
        }
        return 0;
}

要运行你需要的这个演示示例:

  1. EchoServer.mq4 文件放在终端数据文件夹“МetaТrader 4\experts\”中,并编译此文件。

  2. 在 Microsoft Visual Studio 2010 Ultimate 中打开 客户端 项目,并使用 发布 配置构建它。 如要在另一个 IDE 中构建此项目,别忘了指定 NetEventsProcDLL.lib 模块作为编辑器的额外条目(编译工具: /EHsc/link NetEventsProcDLL.lib)。

  3. 运行 Client.exe 可执行模块。 代码 10057 和你计算机的本地 IP 列表会出错。 在 Client.cpp 源代码中纠正以下字符串

    #define SERVER_IP   "192.168.1.5"

    ,方法是将“192.168.1.5”替换为第一个(或唯一的)本地 IP,并重新编译项目。

  4. 在 MetaTrader 4 终端中的 任何图表上运行 EchoServer Expert Advisor。 如果一切顺利,终端窗口将显示以下消息: "ServerOpen() return is: OK"、"Server is Opened and Waiting for Client connection requests..."。 然后每发生一次变动,Expert Advisor 就会检查一次连接请求,连接,从每个连接读取收到的消息,并将消息副本发送回接收者。 Expert Advisor 在终端窗口中显示所有当前信息。

  5. 现在你可运行步骤 3 中构建的 Client.exe 程序。

  6. 可运行此程序的多个副本,并查看 Expert Advisor 如何与所有副本交换信息。

  7. 如果在运行 Expert Advisor 服务器的计算机上设置了全局 IP 而非步骤 3 中的本地 IP,则在这种情况下,你可运行 Client.exe 并与互联网上的任何其他计算机上的 Expert Advisor 通信。 当然,别忘了禁用防火墙当前的所有防护功能。

1.2.2. C++ 程序服务器与 MetaTrader 4 Expert Advisor 客户端

现在让我们考虑 МetaТrader 4 Expert Advisor 与 C++ 程序之间信息交换这一额外任务:

  • EchoServer.cpp - 用作回显服务器的 C++ 程序。

  • Client.mq4 - 用作此 C++ 服务器的客户端的 Expert Advisor。

Expert Advisor 客户端读取相关交易品种的报价,并将这些报价发送到 C++ 服务器。 C++ 服务器将报价返回给接收者,收到后在终端窗口中显示它们。 下图说明了这个思路:

图 2 C++ 程序服务器与 MetaTrader 4 Expert Advisor 客户端

以下是 作为回显服务器的 MetaTrader 4 Expert Advisor

//+---------------------------------------------------------------------------+
//|                                            Client.mq4                     |
//|                      Copyright © 2012, https://www.mql4.com/ en/users/more  |
//|                                       tradertobe@gmail.com                |
//+---------------------------------------------------------------------------+
#property copyright "Copyright © 2012, https://www.mql4.com/ ru/users/more"
#property link      "https://www.mql4.com/ ru/users/more"
#include <ImportNetEventsProcDLL.mqh>
/*int ConnectTo(string  ps8_ServerIP,         // in - string ps8_ServerIP = "0123456789123456"
                int     s32_Port,             // in 
                int&    ph_Client[]);         // out - int ph_Client[1]
*/
/*int SendToDouble(int     h_Client,          // in
                   double& pd_SendBuf[],      // in
                   int     s32_SendBufLen);   // in - SendBuf[] array size in double element 
*/
/*int ReadFromDouble(int     h_Client,        // in
                     double& pd_ReadBuf[],    // in
                     int     s32_ReadBufLen,  // in  - ReadBuf[] array size in double element
                     int&    ps32_ReadLen[]); // out - int ps32_ReadLen[1] - count of actually read data in double element
*/                      
/*int ConnectClose(int h_Client);             // in
*/ 
       
// Globals variables
int s32_Error;
int i;
// for int ConnectTo(ps8_ServerIP, s32_Port, ph_Client); // out - int h_Client[1]
string ps8_ServerIP = "192.168.1.5";                     // mine local IP
int    s32_Port = 2000;
int    ph_Client[1];

bool b_ConnectTo = false;

// for int SendToDouble(ph_Client[0], pd_Buf, s32_BufLen);  
// for int ReadFromDouble(ph_Client[0], pd_Buf, s32_BufLen, ps32_ReadLen);
double pd_Buf[1];
int    s32_BufLen = 1;
int    ps32_ReadLen[1];
//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
{
//----
   if (!b_ConnectTo)
   {
      s32_Error = ConnectTo(ps8_ServerIP, s32_Port, ph_Client);
      Print("ConnectTo(...) return is: ",GetErrMsg(s32_Error));
      Print("ConnectTo(...) handle is: ",ph_Client[0]);
      
      if (s32_Error == OK)
      {
         b_ConnectTo = true;
         Print("Client now is ConnectTo the Server: ",ps8_ServerIP);
      }
   }
//----
   return(0);
}
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
{
//----
   if (b_ConnectTo)
   {
      s32_Error = ConnectClose(ph_Client[0]);
      Print("ConnectClose(...) return is: ",GetErrMsg(s32_Error));
      
      if (s32_Error == OK)
         b_ConnectTo = false;
   }
//----
   return(0);
}  

int start()
{
//----
   if (!b_ConnectTo)
      return(0);
   
   RefreshRates();
   
   double pd_Value[1];
   
   pd_Value[0] = NormalizeDouble(Bid,Digits);
      
   s32_Error = SendToDouble(ph_Client[0], pd_Value, s32_BufLen);
   
   
   if (s32_Error != 0)
   {
      Print("SendToDouble(",ph_Client[0],"...) return is: ",GetErrMsg(s32_Error));
      return(1);
   }
   else
      Print("SendToDouble(",ph_Client[0],"...) return is: OK");
      
   s32_Error = ReadFromDouble(ph_Client[0], pd_Buf, s32_BufLen, ps32_ReadLen);      
   
   if (s32_Error != 0)
   {
      Print("ReadFromDouble(",ph_Client[0],"...) return is: ", GetErrMsg(s32_Error));
      return(1);
   }
   else
      Print("ReadFromDouble(",ph_Client[0],"...) return is: OK"); 
   
   pd_Buf[0] = NormalizeDouble(pd_Buf[0],Digits);
   
   if (ps32_ReadLen[0] > 0)
      Print("Read doble value is: ", pd_Buf[0]);
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

以下是作为 Expert Advisor 客户端的回显服务器的 EchoServer.cpp - C++ 程序的源代码:

//+---------------------------------------------------------------------------+
//|                                            EchoServer.cpp                 |
//|                      Copyright © 2012, https://www.mql4.com/ en/users/more  |
//|                                       tradertobe@gmail.com                |
//+---------------------------------------------------------------------------+

// EchoServer.cpp

#include <winsock2.h>
#pragma comment(lib, "NetEventsProcDLL") // NetEventsProcDLL.lib placed in ...\FastStart\EchoServer\ 
#include <iostream>
#include <conio.h>

#include  "cNetEventsProcDLL.h"

BOOL FormatIP(DWORD u32_IP, char* s8_IP);

int main()
{
        int s32_Port = 2000;
        
        // Try to create server listening on port 2000
        // You may change port.
        DWORD u32_Error = cNetEventsProcDLL::ServerOpen(s32_Port);

        if (u32_Error)
        {
                printf("\nServerOpen() failed with error: %d\n", u32_Error);
                return 1;
        }
        else
                printf("\nServerOpen() fine, we now are waiting for connections...\n");
        
        DWORD u32_Count = 0;
        DWORD u32_CountOld = 0;
        
        double pd_Buf[1025];
        DWORD  u32_BufLen = 1025;
        int    ps32_ReadLen[1];

        pd_Buf[0] = 0;

        int ph_Client[62];
        int ps32_ClientIP[62];
        int ps32_ClientCount[1];

        while(!kbhit())
        {
                u32_Error = cNetEventsProcDLL::GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount);
                
                if (u32_Error)
                {
                        printf("\nGetAllConnections(...) failed with error: %d\n", u32_Error);
                        break;
                }
                else
                        u32_Count = ps32_ClientCount[0];

                if (u32_Count != u32_CountOld)
                {
                        u32_CountOld = u32_Count;

                        printf("\nNumber of connections now = %d\n", u32_Count);
                        printf("#     h_Connect   (peer IP)\n");
                        
                        for (DWORD i = 0; i<u32_Count; i++)
                        {
                                char  s8_IP[20];
                                sprintf(s8_IP, "%s","123456789012345");

                                FormatIP(ps32_ClientIP[i], s8_IP);

                                printf("%d      %d       (%s)\n", i, ph_Client[i], s8_IP);
                        }
                }

                for (DWORD i = 0; i<u32_Count; i++)
                {
                        u32_Error = cNetEventsProcDLL::ReadFromDouble(ph_Client[i], pd_Buf, u32_BufLen, ps32_ReadLen);

                        if (u32_Error)
                        {
                                printf("ReadFromDouble(%d...) failed with error: %d\n", ph_Client[i], u32_Error);
                        }

                        if (ps32_ReadLen[0])
                        {
                                printf("ReadFromDouble(%d...) read %d double values\n", ph_Client[i], ps32_ReadLen[0]);
                                printf("\nReadFromDouble(%d...) Read Data: %9.5f\n", ph_Client[i], pd_Buf[0]);
                        }
                        
                        if (ps32_ReadLen[0])
                        {
                                u32_Error = cNetEventsProcDLL::SendToDouble(ph_Client[i], pd_Buf, ps32_ReadLen[0]);

                                if (u32_Error)
                                {
                                        printf("SendToDouble(%d...) failed with error: %d\n", ph_Client[i], u32_Error);
                                }
                                else
                                {
                                        printf("SendToDouble(%d...) sent %d double values\n", ph_Client[i], ps32_ReadLen[0]);
                                        printf("SendToDouble(%d...) sent Data: %9.5f\n",ph_Client[i], pd_Buf[0]);
                                }
                        }

                }
                
        }

        u32_Error = cNetEventsProcDLL::ServerClose();

        if (u32_Error)
        {
                printf("\nServerClose() failed with error: %d\n", u32_Error);
                return 1;
        }
        else
                printf("\nServerClose() fine...\n");

        Sleep(10000);
        return 0;
}

BOOL FormatIP(DWORD u32_IP, char* s8_IP)
{
        DWORD u32_Len = strlen(s8_IP);

        if ( u32_Len < 15)
                return FALSE;
        
        BYTE* pu8_Addr = (BYTE*)&u32_IP;
        sprintf(s8_IP,"%d.%d.%d.%d",pu8_Addr[0], pu8_Addr[1], pu8_Addr[2], pu8_Addr[3]);

        return TRUE;
}

要运行你需要的这个演示示例:

  1. Client.mq4 将 Client.mq4 文件放到终端数据文件夹“MetaTrader 4\experts\”中,并将本地 IP(在上一例 1.2.1. 中获取)分配给字符串

    string ps8_ServerIP = "192.168.1.5";

    并编译它。 如果 C++ 服务器将在另一个计算机上运行,则将此计算机的全局 IP 粘贴到此处。 别忘了禁用防火墙的所有现有防护功能。

  2. 在 Microsoft Visual Studio 2010 Ultimate 中打开 客户端 项目,并使用 发布 配置构建它。 如要在另一个 IDE 中构建此项目,别忘了指定 NetEventsProcDLL.lib 模块作为编辑器的额外条目(编译工具: /EHsc/link NetEventsProcDLL.lib)。

  3. 运行 EchoServer.exe C++ 服务器。 如果一切顺利,控制台将显示以下消息: "ServerOpen() fine, we now are waiting for connections.....". 按任意键关闭服务器并终止程序。

  4. 在 MetaTrader 4 终端中的任何图表上运行 Client.mq4 客户端。

  5. 可在一个或多个计算机、一个或多个终端上的多个图表上同时运行此客户端。

  6. 观察 C++ 服务器和 МetaТrader 4 客户端的工作情况。 按 C++ 服务器控制台中的任意键,则 C++ 程序将关闭服务器并终止。

1.2.3. МetaТrader 4 Expert Advisor 指标(Expert Advisor 服务器)与 МetaТrader 4 客户端指标

Expert Advisor 指标(Expert Advisor 服务器)为客户端指标提供指标值。 在这种情况下,这些值是标准 iEnvelops(...) 指标的值。 此例的实用价值在于可将“安全”指标的值分配到所有用户客户端。

下图说明了这个思路:

图 3 МetaТrader 4 Expert Advisor 指标(Expert Advisor 服务器)与 МetaТrader 4 客户端指标

以下是作为 iEnvelops(...) 指标值提供者的 MetaTrader 4 Expert Advisor ServerSendInd.mq4 的源代码:

//+---------------------------------------------------------------------------+
//|                                            ServerSendInd.mq4              |
//|                      Copyright © 2012, https://www.mql4.com/ en/users/more  |
//|                                       tradertobe@gmail.com                |
//+---------------------------------------------------------------------------+
#property copyright "Copyright © 2012, https://www.mql4.com/ ru/users/more"
#property link      "https://www.mql4.com/ ru/users/more"
#include <ImportNetEventsProcDLL.mqh>
/*int ServerOpen(int  s32_Port);                  // in
*/
/*int ServerClose();
*/
/*int GetAllConnections(int& ph_Client[],         // out - int ph_Client[62]
                        int& ps32_ClientIP[],     // out - int ps32_ClientIP[62]
                        int& ps32_ClientCount[]); // out - int ps32_ClientCount[1]
*/                      
/*int ReadFromString(int     h_Client,            // in
                   string    ps8_ReadBuf,         // in
                   int       s32_ReadBufLen,      // in  - ReadBuf   string size in char element
                   int&      ps32_ReadLen[]);     // out - int ps32_ReadLen[1] - count of actually read data in char element     
*/ 
/*int SendToDouble(int     h_Client,              // in
                   double& pd_SendBuf[],          // in
                   int     s32_SendBufLen);       // in - SendBuf[] array size in double element   
*/                                            
// Globals variables
int s32_Error;
int i;

int s32_Port = 2000;
bool b_ServerOpened = false;

// for GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount)
int ph_Client       [62];
int ps32_ClientIP   [62];
int ps32_ClientCount[1 ];

// for int ReadFromString(h_Client, ps8_ReadBuf, s32_ReadBufLen, ps32_ReadLen)
string ps8_ReadBuf = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
int    s32_ReadBufLen;
int    ps32_ReadLen[1];

// for int SendToDouble(ph_Client[0], pd_SendBuf, s32_SendBufLen);  
#define  BARS_COUNT  200
double pd_SendBuf      [BARS_COUNT];  //BARS_COUNT/2 Bars for each of 2 line 
int    s32_SendBufLen = BARS_COUNT;

//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
{
   //----
   s32_ReadBufLen = StringLen(ps8_ReadBuf);
   
   if (!b_ServerOpened)
   {
      s32_Error = ServerOpen(s32_Port);
      Print("ServerOpen() return is: ",GetErrMsg(s32_Error));
      
      if (s32_Error == OK)
      {
         b_ServerOpened = true;
         Print("Server is Opened and Waiting for Clients connection requests...");
      }
   }
//----
   return(0);
}
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
{
//----
   if (b_ServerOpened)
   {
      s32_Error = ServerClose();
      Print("ServerClose() return is: ",GetErrMsg(s32_Error));
      
      if (s32_Error == OK)
         b_ServerOpened = false;
   }
//----
   return(0);
}  

int start()
{
//----
   if (!b_ServerOpened)
      return(0);
      
   s32_Error = GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount);
   
   if (s32_Error != 0)
   {
      Print("GetAllConnections(...) failed with error: ",GetErrMsg(s32_Error));
      return(1);
   }
   
   Print("ClientCount = ", ps32_ClientCount[0]);
   
   for (i = 0; i<ps32_ClientCount[0]; i++)
   {
      Print("h_Client = ", ph_Client[i], "      Client IP =  ", FormatIP(ps32_ClientIP[i]));
      
      s32_Error = ReadFromString(ph_Client[i], ps8_ReadBuf, s32_ReadBufLen, ps32_ReadLen); 
      
      if (s32_Error != 0)
      {
         Print("ReadFromString(",ph_Client[i],") failed with error: ", GetErrMsg(s32_Error));
         continue;
      }

      if (ps32_ReadLen[0] > 0)
      {
         // ps8_ReadBuf = "EURUSDMinuts"   i.e. "Symbol+Timeframe"
         string Sym       = StringSubstr(ps8_ReadBuf,0,6);
         int    TimeFrame = StrToInteger(StringSubstr(ps8_ReadBuf,6,ps32_ReadLen[0]-6));
      

         int k;                
         for (k = 0; k<BARS_COUNT/2; k++)
         {
            while(true)
            {
               double UpperLine_k = iEnvelopes(Sym, TimeFrame, 14, MODE_SMA, 0, PRICE_CLOSE, 0.1, MODE_UPPER, k);
               if (GetLastError() != 0)
                  continue;
               else
                  break;   
            }    
            while(true)
            {           
               double LowerLine_k = iEnvelopes(Sym, TimeFrame, 14, MODE_SMA, 0, PRICE_CLOSE, 0.1, MODE_LOWER, k);
               if (GetLastError() != 0)
                  continue;
               else
                  break;
            }
             
            pd_SendBuf[k]              = UpperLine_k;
            pd_SendBuf[k+BARS_COUNT/2] = LowerLine_k;
         }
         
         s32_Error = SendToDouble(ph_Client[i], pd_SendBuf, s32_SendBufLen); 
         if (s32_Error != 0)
         {
            Print("SendToDouble(",ph_Client[i],") failed with error: ", GetErrMsg(s32_Error));

            continue;
         }
      }  
   }
//----
   return(0);
}
//+------------------------------------------------------------------+

以下是用于从 ServerSendInd.mq4 Expert Advisor 中获取 iEnvelops(...) 指标值的 客户端指标 ClientIndicator.mq4 的源代码: Expert advisor:

//+---------------------------------------------------------------------------+
//|                                            ClientIndicator.mq4            |
//|                      Copyright © 2012, https://www.mql4.com/ en/users/more  |
//|                                       tradertobe@gmail.com                |
//+---------------------------------------------------------------------------+
#property copyright "Copyright © 2012, https://www.mql4.com/ en/users/more"
#property link      "https://www.mql4.com/ ru/users/more"
#include <ImportNetEventsProcDLL.mqh>

/*int ConnectTo(string  ps8_ServerIP,         // in - string ps8_ServerIP = "0123456789123456"
                int     s32_Port,             // in 
                int&    ph_Client[]);         // out - int ph_Client[1]
*/
/*                      
/*int ConnectClose(int h_Client);             // in
*/
/*int SendToString(int    h_Client,           // in
                   string ps8_SendBuf,        // in
                   int    s32_SendBufLen);    // in - SendBuf string size in char element
*/
/*int ReadFromDouble(int     h_Client,        // in
                     double& pd_ReadBuf[],    // in
                     int     s32_ReadBufLen,  // in  - ReadBuf[] array size in double element
                     int&    ps32_ReadLen[]); // out - int ps32_ReadLen[1] - count of actually read data in double element
*/
// Globals variables
int s32_Error;
int i;
// for int ConnectTo(ps8_ServerIP, s32_Port, ph_Client);  // out - int h_Client[1]
string ps8_ServerIP = "192.168.1.5";                      // mine local IP
int    s32_Port = 2000;
int    ph_Client[1]; 

bool b_ConnectTo = false;

// for int SendToString  (h_Client, ps8_SendBuf, s32_SendBufLen)
string ps8_SendBuf = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
int    s32_SendBufLen;  
 
// for int ReadFromDouble(ph_Client[0], pd_ReadBuf, s32_ReadBufLen, ps32_ReadLen);
#define BARS_COUNT  200
double  pd_ReadBuf      [BARS_COUNT];
int     s32_ReadBufLen = BARS_COUNT;
int     ps32_ReadLen[1]; 
               
string Indicator_Name = "Envelopes: ";
//----
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_color1 Blue
#property indicator_color2 Red

double UpperLine[];
double LowerLine[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init()
{
//---- indicators
   SetIndexStyle (0,DRAW_LINE);
   SetIndexBuffer(0,UpperLine);
   
   SetIndexStyle (1,DRAW_LINE);
   SetIndexBuffer(1,LowerLine);
   
   s32_SendBufLen = StringLen(ps8_SendBuf);
   
   if (!b_ConnectTo)
   {
      s32_Error = ConnectTo(ps8_ServerIP, s32_Port, ph_Client);
      Print("ConnectTo(...) return is: ",GetErrMsg(s32_Error));
      Print("ConnectTo(...) handle is: ",ph_Client[0]);
      
      if (s32_Error == OK)
      {
         b_ConnectTo = true;
         Print("Client now is ConnectTo the Server: ",ps8_ServerIP);
      }
   }
//----   
   return(0);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
int deinit()
{
    //----
   if (b_ConnectTo)
   {
      s32_Error = ConnectClose(ph_Client[0]);
      Print("ConnectClose(...) return is: ",GetErrMsg(s32_Error));
      
      if (s32_Error == OK)
         b_ConnectTo = false;
   }
//----
   return(0);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()                          
{
//----
   if (!b_ConnectTo)
      return(0);
      
    string Sym       = Symbol();
    int    TimeFrame = Period();
    ps8_SendBuf = Symbol() + DoubleToStr(Period(),0);
    
    s32_Error = SendToString(ph_Client[0], ps8_SendBuf, StringLen(ps8_SendBuf));
    
    if (s32_Error != 0) 
    {       
      Print("SendToString(", ph_Client[0],",...) failed with error: ",GetErrMsg(s32_Error));
      return (1);
    }
    
    s32_Error = ReadFromDouble(ph_Client[0], pd_ReadBuf, s32_ReadBufLen, ps32_ReadLen);      
   
   if (s32_Error != 0)
   {
      Print("ReadFromDouble(",ph_Client[0],"...) return is: ", GetErrMsg(s32_Error));
      return(1);
   }
   
   if (ps32_ReadLen[0] == 0)
      return (0);
 
//--------------------------------------------------------------------
    int Counted_bars = IndicatorCounted();       // Number of calculated bars 
    i = Bars - Counted_bars - 1;                 // Index of first not-calculated
    if (i > BARS_COUNT/2-1)  i = BARS_COUNT/2-1; // Calculate specified count if there are many bars
//-----------------------------------------------------------------------  
    for (i = BARS_COUNT/2-1; i >= 0; i--)
    {
         UpperLine  [i] = pd_ReadBuf[i];
         LowerLine  [i] = pd_ReadBuf[i+BARS_COUNT/2];
    }  
    
    return;                          
} // end of int start()
//--------------------------------------------------------------------

要运行你需要的这个演示示例:

  1. ServerSendInd.mq4 文件放在终端数据文件夹“МetaТrader 4\experts\”中,并编译此文件。

  2. ClientIndicator.mq4 文件放到终端数据文件夹“MetaTrader 4\experts\indicators\”中,并将本地 IP(在上一例 1.2.1. 中获取)分配给字符串:

    string ps8_ServerIP = "192.168.1.5";

    如果 ServerSendInd 将在另一个计算机上运行,则将此计算机的全局 IP 粘贴到此处。 别忘了禁用防火墙的所有现有防护功能。 编译它。

  3. 运行 ServerSendInd。 如果一切顺利,控制台将显示以下消息: "ServerOpen() fine, we now are waiting for connections.....".

  4. 在 МetaТrader 4 终端的任意图表上运行 ClientIndicator.mq4 指标。 图表上将显示两条指标线。 如果在同一图表上运行标准 Envelops 指标,确保我们的两行指标线与标准指标线一致。

  5. 可在一个或多个计算机、一个或多个终端上的多个图表上同时运行 ClientIndicator 指标。

  6. 观察 ServerSendInd 服务器和 ClientIndicator 的工作情况。 试着用 ClientIndicator 指标更改图表的时间范围。 将立即设置 ServerSendInd 服务器以发送此时间范围的指标值。


2. DLL 接口规格

在本节中,我们将详细说明所有 DLL 函数和调用这些函数所需的参数。 所有函数在成功执行后都返回 0。 否则,函数将返回 winsock2 API 错误代码。 C++ 类 cNetEventsProcDLL.h 声明中给出了所有已导出的 DLL 函数,因此我们将提供此文件的源代码:

//+---------------------------------------------------------------------------+
//|                                            cNetEventsProcDLL.h            |
//|                      Copyright © 2012, https://www.mql4.com/ en/users/more  |
//|                                       tradertobe@gmail.com                |
//+---------------------------------------------------------------------------+
//--- cNetEventsProcDLL.h
#pragma once
#define EXPFUNC __declspec(dllexport)
//---   
class cNetEventsProcDLL
  {
public:
   static BOOL MessageDLL_PROCESS_ATTACH(void);
   static BOOL MessageDLL_PROCESS_DETACH(void);
//---
   static EXPFUNC int __stdcall ConnectTo(char *ps8_ServerIP,             // in - ps8_ServerIP = "0123456789123456"
                                          int   s32_Port,                 // in 
                                          int*  ph_Client);               // out - int ph_Client[1]
//---
   static EXPFUNC int __stdcall ConnectClose(int h_Client);               // in 
//---
   static EXPFUNC int __stdcall ServerOpen(int s32_Port);                 // in
//---
   static EXPFUNC int __stdcall GetAllConnections(int* ph_Client,         // out - int ph_Client[62]
                                                  int* ps32_ClientIP,     // out - int ps32_ClientIP[62]
                                                  int* ps32_ClientCount); // out - int ps32_ClientCount[1]
//---
   static EXPFUNC int __stdcall DisconnectClient(SOCKET h_Client);        // in 
//---
   static EXPFUNC int __stdcall ServerClose();
//---
   static EXPFUNC int __stdcall SendToInt(SOCKET h_Client,             // in
                                          int   *ps32_SendBuf,         // in
                                          int    s32_SendBufLen);      // in -  SendBuf[] array size in int element
//---
   static EXPFUNC int __stdcall SendToDouble(SOCKET  h_Client,         // in
                                             double* pd_SendBuf,       // in
                                             int     s32_SendBufLen);  // in -  SendBuf[] array size in double element
//---
   static EXPFUNC int __stdcall SendToString(SOCKET h_Client,          // in
                                             char*  ps8_SendBuf,       // in
                                             INT   s32_SendBufLen);    // SendBuf string size in char element
//---
   static EXPFUNC int __stdcall ReadFromInt(SOCKET h_Client,           // in
                                            int   *ps32_ReadBuf,       // in
                                            int    s32_ReadBufLen,     // ReadBuf[] array size in int element
                                            int   *ps32_ReadLen);      // out - int ps32_ReadLen[1] - actual count of read data in int element
//---
   static EXPFUNC int __stdcall ReadFromDouble(SOCKET  h_Client,       // in
                                               double *pd_ReadBuf,     // in
                                               int     s32_ReadBufLen, // ReadBuf[] array size in double element
                                               int    *ps32_ReadLen);  // out - int ps32_ReadLen[1] - actual count of read data in double element
//---
   static EXPFUNC int __stdcall ReadFromString(SOCKET h_Client,        // in
                                               char  *ps8_ReadBuf,     // in
                                               int    s32_ReadBufLen,  // ReadBuf[] array size in char element
                                               int*   ps32_ReadLen);   // out - int ps32_ReadLen[1] - actual count of read data in char element
//---
protected:
   static DWORD SendTo(SOCKET h_Client,char *ps8_SendBuf,INT s32_SendBufLen);
   static DWORD ReadFrom(SOCKET h_Client,char *ps8_ReadBuf,INT s32_ReadBufLen,INT &s32_ReadLen);
  };

现在让我们按这些 DLL 函数在此文件中的出现顺序一一进行说明:

  1. ConnectTo - 向服务器请求创建连接:

    static EXPFUNC int __stdcall ConnectTo(char* ps8_ServerIP, // in - ps8_ServerIP = "0123456789123456"
                                           int   s32_Port,     // in 
                                           int*  ph_Client);   // out - int ph_Client[1]

    函数参数:

    • char* ps8_ServerIP - 要连接到的服务器的 IP 地址(例如“93.127.110.162”)。 如果此服务器是本地服务器,则需指定 IP,此 IP 不是“127.0.0.1”,而是按 1.2.1. 示例中所述方式获取的 IP。

    • int s32_Port - 服务器“监听”的端口号。

    • int* ph_Client - 如果函数成功终止,则连接句柄放在此变量中。 此句柄必须用于此连接的所有后续操作。

    如果成功,函数返回 true,否则返回 winsock2 API 错误代码。 C++ 示例:

    DWORD u32_Error = cNetEventsProcDLL::ConnectTo(SERVER_IP, PORT, ph_Client);
    
    if (u32_Error)
    {
            printf("\nConnectTo(...) failed with error: %d\n", u32_Error);
            return 1;
    }
    else
            printf("\nConnectTo(...) OK, ph_Client[0] = : %d\n", ph_Client[0]);
    
    int h_Client = ph_Client[0];
  2. ConnectClose - 向服务器请求关闭连接。

    static EXPFUNC int __stdcall ConnectClose(int h_Client); // in 

    函数参数:

    • int h_Client - 必须要关闭的连接句柄。 如果成功,函数返回 true,否则返回 winsock2 API 错误代码。

    C++ 示例:

    int u32_Error = cNetEventsProcDLL::ConnectClose(h_Client);
    
        if (u32_Error)
                printf("\nConnectClose(...) failed with error: %d\n", u32_Error);
        else
                printf("\nConnectClose(...) OK.\n");
  3. ServerOpen - 请求创建服务器。

    static EXPFUNC int __stdcall ServerOpen(int s32_Port); //in

    函数参数:

    • int s32_Port - 服务器等待客户端请求时将“监听”的端口号。 如果成功,函数返回 true,否则返回 winsock2 API 错误代码。

    C++ 示例:

    int u32_Error = cNetEventsProcDLL::ServerOpen(s32_Port);
    
    if (u32_Error)
    {
            printf("\nServerOpen() failed with error: %d\n", u32_Error);
            return 1;
    }
    else
            printf("\nServerOpen() fine, we now are waiting for connections...\n");
  4. GetAllConnections - 向服务器请求获取所有当前连接的相关信息。

    static EXPFUNC int __stdcall GetAllConnections(int* ph_Client,         // out - int ph_Client[62]
                                                   int* ps32_ClientIP,     // out - int ps32_ClientIP[62]
                                                   int* ps32_ClientCount); // out - int ps32_ClientCount[1]

    函数参数:

    • int ph_Client[62] - 输出数组,供服务器放置所有当前连接的句柄。

    • int ps32_ClientIP[62] - 输出数组,供服务器放置所有当前连接的 IP 地址。 要将这些地址转换为标准格式(例如“92.127.110.161”),使用 МetaТrader 4 Expert Advisor 的 string FormatIP(int IP) 函数或 C++ 程序示例中给出的任何类似函数。 数组大小中的数字 62 并不是随意指定的:它指明每个服务器的可能连接(客户端)的数量限制。

    • int* ps32_ClientCount - 服务器将当前连接数量放置到此变量中,即上述数组中的元素数量。

    如果成功,函数返回 true,否则返回 winsock2 API 错误代码。 C++ 示例:

    int ph_Client[62];
    int ps32_ClientIP[62];
    int ps32_ClientCount[1];
    
    int u32_Error = cNetEventsProcDLL::GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount);
                    
    if (u32_Error)
    {
        printf("\nGetAllConnections(...) failed with error: %d\n", u32_Error);
    }
    else
       int u32_Count = ps32_ClientCount[0];
  5. DisconnectClient - 向服务器请求关闭与其某个客户端的连接。

    static EXPFUNC int __stdcall DisconnectClient(int h_Client); // in 

    函数参数:

    • int h_Client - 必须要关闭的连接句柄。 如果成功,函数返回 true,否则返回 winsock2 API 错误代码。

    C++ 示例:

    int u32_Error = cNetEventsProcDLL::DisconnectClient(h_Client);
    
    if (u32_Error)
            printf("\nDisconnectClient(...) failed with error: %d\n", u32_Error);
    else
            printf("\nDisconnectClient(...) OK.\n");
  6. ServerClose - 请求关闭服务器。

    static EXPFUNC int __stdcall ServerClose(); >

    关闭服务器时,将关闭所有当前连接,这样,所有客户端的任何操作获得的响应都将是“无连接”返回码。 如果成功,函数返回 true,否则返回 winsock2 API 错误代码。 C++ 示例:

    int u32_Error = cNetEventsProcDLL::ServerClose();
    
    if (u32_Error)
            printf("\nServerClose() failed with error: %d\n", u32_Error);
    else
            printf("\nServerClose() OK.\n");
  7. 下一组函数直接对应通过当前连接进行的数据交换。 在异步模式下接收各个当前连接的数据,即没有接收者的响应。

    所有数据都采用独立交换单位(即块)的形式进行发送和接收。 各个接收者的所有块都累积在 FIFO 堆栈中。 接收者可以随时从此堆栈中检索这些块。 各个交换函数都使用单个块操作。

    对于数据发送操作,操作可能会成功,但返回码不是 0,可能有以下几个值:

    • WSAEWOULDBLOCK - 操作成功,数据尚未发送到接收者,但会在合适的时间发送。 无需任何用户操作。

    • WSA_IO_PENDING - 之前的数据传输尚未完成,用户必须尝试稍后发送数据。 这是正常情况,因此会被视为函数成功执行。

    任何其他返回码都表示用户错误。

  8. SendToInt - 请求通过当前连接发送数据块(int 类型数组)。

    static EXPFUNC int __stdcall SendToInt(SOCKET h_Client,        // in
                                           int*   ps32_SendBuf,    // in
                                           int    s32_SendBufLen); // in - SendBuf[] array size in int element

    函数参数:

    • SOCKET h_Client - 当前连接的句柄。

    • int ps32_SendBuf[s32_SendBufLen] - 需要发送到客户端的单个块(int 类型数组)。

    • int s32_SendBufLen - 数组大小。

    如果成功,函数返回 true,否则返回 winsock2 API 错误代码。 C++ 示例:

    int ps32_SendBuf[200];
    int s32_SendBufLen=200;
    
    int u32_Error = cNetEventsProcDLL::SendToInt(h_Client, ps32_SendBuf, s32_SendBufLen);   
    
    switch (u32_Error)
    {
       case 0:
           printf("\nSendTo(...) OK");
           break;
       case WSAEWOULDBLOCK:
           printf("\nSendTo(%d...) return is: WSAEWOULDBLOCK(%d)\n",h_Client, u32_Error);
           printf("\nWSAEWOULDBLOCK -> The data will be send after the next FD_WRITE event, do nouthing\n");
           break;
       case WSA_IO_PENDING:
           printf("\nSendTo(%d...) return is: WSA_IO_PENDING(%d)\n",h_Client, u32_Error);
           printf("\nWSA_IO_PENDING -> Error: A previous Send operation is still pending. This data will not be sent, try latter\n");
           break;
    
       default:
           printf("\nSendTo(%d...)failed with severe error: %d\n",h_Client, u32_Error);
           break;
    };
  9. SendToDouble - 请求通过当前连接发送数据块(double 类型数组)。

    static EXPFUNC int __stdcall SendToDouble(SOCKET h_Client,          // in
                                              double*  pd_SendBuf,      // in
                                              int      s32_SendBufLen); // in - SendBuf[] array size in int element

    函数参数:

    • SOCKET h_Client - 当前连接的句柄。

    • double pd_SendBuf[s32_SendBufLen] - 需要发送到客户端的单个块(double 类型数组)。

    • int s32_SendBufLen - 数组大小。

    如果成功,函数返回 true,否则返回 winsock2 API 错误代码。 C++ 示例:

    double pd_SendBuf[200];
    int    s32_SendBufLen=200;
    
    int u32_Error = cNetEventsProcDLL::SendToDouble(h_Client, pd_SendBuf, s32_SendBufLen);   
    
    switch (u32_Error)
    {
       case 0:
           printf("\nSendTo(...) OK");
           break;
       case WSAEWOULDBLOCK:
           printf("\nSendTo(%d...) return is: WSAEWOULDBLOCK(%d)\n",h_Client, u32_Error);
           printf("\nWSAEWOULDBLOCK -> The data will be send after the next FD_WRITE event, do nouthing\n");
           break;
       case WSA_IO_PENDING:
           printf("\nSendTo(%d...) return is: WSA_IO_PENDING(%d)\n",h_Client, u32_Error);
           printf("\nWSA_IO_PENDING -> Error: A previous Send operation is still pending. This data will not be sent, try latter\n");
           break;
    
       default:
           printf("\nSendTo(%d...)failed with severe error: %d\n",h_Client, u32_Error);
           break;
    };
  10. SendToString - 请求通过当前连接发送数据块(char 类型数组)。

    static EXPFUNC int __stdcall SendToString(SOCKET h_Client,        // in
                                              char*  ps8_SendBuf,     // in
                                              int    s32_SendBufLen); // in -  SendBuf[] array size in int element

    函数参数:

    • SOCKET h_Client - 当前连接的句柄。

    • char ps8_SendBuf[s32_SendBufLen] - 需要发送到客户端的单个块(char 类型数组)。

    • int s32_SendBufLen - 数组大小。

    如果成功,函数返回 true,否则返回 winsock2 API 错误代码。 C++ 示例:

    char ps8_SendBuf[200];
    int  s32_SendBufLen=200;
    
    int u32_Error = cNetEventsProcDLL::SendToString(h_Client, ps8_SendBuf, s32_SendBufLen);   
    
    switch (u32_Error)
    {
       case 0:
           printf("\nSendTo(...) OK");
           break;
       case WSAEWOULDBLOCK:
           printf("\nSendTo(%d...) return is: WSAEWOULDBLOCK(%d)\n",h_Client, u32_Error);
           printf("\nWSAEWOULDBLOCK -> The data will be send after the next FD_WRITE event, do nouthing\n");
           break;
       case WSA_IO_PENDING:
           printf("\nSendTo(%d...) return is: WSA_IO_PENDING(%d)\n",h_Client, u32_Error);
           printf("\nWSA_IO_PENDING -> Error: A previous Send operation is still pending. This data will not be sent, try latter\n");
           break;
    
       default:
           printf("\nSendTo(%d...)failed with severe error: %d\n",h_Client, u32_Error);
           break;
    };
  11. ReadFromInt - 请求通过当前连接接收数据块(int 类型数组)。

    static EXPFUNC int __stdcall ReadFromInt(SOCKET h_Client,       // in
                                             int*   ps32_ReadBuf,   // in
                                             int    s32_ReadBufLen, // ReadBuf[] array size in int element
                                             int*   ps32_ReadLen);  // out - int ps32_ReadLen[1] - actual count of read data in int element

    函数参数:

    • SOCKET h_Client - 当前连接的句柄。

    • int ps32_ReadBuf[s32_ReadBufLen] - 用于接收数据块的 int 类型数组。

    • int s32_ReadBufLen - 接收数组的大小。

    • int* ps32ReadLen - 此变量包含接收并放置到 ps32_ReadBuf[] 数组中的实际数据块大小。 如果接收数组的大小不足以接收数据块,则此变量将包含接收数据块所需的大小,外加一个减号。 此块仍留在堆栈中,且返回码将等于 0。 如果客户端堆栈中没有带指定句柄的数据,此变量将等于 0,且返回码也将等于 0。

    如果成功,函数返回 true,否则返回 winsock2 API 错误代码。 C++ 示例:

    int ps32_ReadBuf[2000];
    int s32_ReadBufLen=2000;
    int ps32_ReadLen[1];
    int u32_Error = cNetEventsProcDLL::ReadFromInt(h_Client, ps32_ReadBuf, s32_ReadBufLen, ps32_ReadLen);   
            
    if(u32_Error)
        printf("ReadFromInt(%d...) failed with error: %d", h_Client, u32_Error);
    else
        if(ps32_ReadLen[0] >= 0) 
            printf("ReadFromInt(%d...) fine, %d int number was read", h_Client, ps32_ReadLen[0]);
        else 
            printf("ReadFromInt(%d...) fine, but ReadBuf must be at least %d int number size", h_Client, -ps32_ReadLen[0]);
  12. ReadFromDouble - 请求通过当前连接接收数据块(double 类型数组)。

    static EXPFUNC int __stdcall ReadFromDouble(SOCKET h_Client,        // in
                                                double* pd_ReadBuf,     // in
                                                int     s32_ReadBufLen, // ReadBuf[] array size in double element
                                                int*    ps32_ReadLen);  // out - int ps32_ReadLen[1] - actual count of read data in double element

    函数参数:

    • SOCKET h_Client - 当前连接的句柄。

    • double pd_ReadBuf[s32_ReadBufLen] - 用于接收数据块的 double 类型数组。

    • int s32_ReadBufLen - 接收数组的大小。

    • int* ps32ReadLen - 此变量包含接收并放置到 ps32_ReadBuf[] 数组中的实际数据块大小。 如果接收数组的大小不足以接收数据块,则此变量将包含接收数据块所需的大小,外加一个减号。 此块仍留在堆栈中,且返回码将等于 0。 如果客户端堆栈中没有带指定句柄的数据,此变量将等于 0,且返回码也将等于 0。

    如果成功,函数返回 true,否则返回 winsock2 API 错误代码。 C++ 示例:

    double ps32_ReadBuf[2000];
    int    s32_ReadBufLen = 2000;
    int    ps32_ReadLen[1];
    int    u32_Error = cNetEventsProcDLL::ReadFromDouble(h_Client, pd_ReadBuf, s32_ReadBufLen, ps32_ReadLen);   
            
    if(u32_Error)
        printf("ReadFromDouble(%d...) failed with error: %d", h_Client, u32_Error);
    else
        if(ps32_ReadLen[0] >= 0) 
            printf("ReadFromDouble(%d...) fine, %d double number was read", h_Client, ps32_ReadLen[0]);
        else 
            printf("ReadFromDouble(%d...) fine, but ReadBuf must be at least %d double number size", h_Client, -ps32_ReadLen[0]);
  13. ReadFromString - 请求通过当前连接接收数据块(char 类型数组)。

    static EXPFUNC int __stdcall ReadFromString(SOCKET h_Client,       // in
                                                char*  ps8_ReadBuf,    // in
                                                int    s32_ReadBufLen, // ReadBuf[] array size in char element
                                                int*   ps32_ReadLen);  // out - int ps32_ReadLen[1] - actual count of read data in char element

    函数参数:

    • SOCKET h_Client - 当前连接的句柄。

    • char ps8_ReadBuf[s32_ReadBufLen] - 用于接收数据块的 char 类型数组。

    • int s32_ReadBufLen - 接收数组的大小。

    • int* ps32ReadLen - 此变量包含接收并放置到 ps32_ReadBuf[] 数组中的实际数据块大小。 如果接收数组的大小不足以接收数据块,则此变量将包含接收数据块所需的大小,外加一个减号。 此块仍留在堆栈中,且返回码将等于 0。 如果客户端堆栈中没有带指定句柄的数据,此变量将等于 0,且返回码也将等于 0。

    如果成功,函数返回 true,否则返回 winsock2 API 错误代码。 C++ 示例:

    char ps8_ReadBuf[2000];
    int  s32_ReadBufLen = 2000;
    int  ps32_ReadLen[1];
    int  u32_Error = cNetEventsProcDLL::ReadFromString(h_Client, ps8_ReadBuf, s32_ReadBufLen, ps32_ReadLen);   
            
    if(u32_Error)
        printf("ReadFromStrung(%d...) failed with error: %d", h_Client, u32_Error);
    else
        if(ps32_ReadLen[0] >= 0)
            printf("ReadFromString(%d...) fine, %d char was read", h_Client, ps32_ReadLen[0]);
        else 
            printf("ReadFromString(%d...) fine, but ReadBuf must be at least %d char size", h_Client, -ps32_ReadLen[0]);


3. 项目实施

随附的 NetServerClient.zip 存档包含两个 Microsoft Visual Studio 2010 Ultimate 项目:

  • NetEventsProc - 用于构建 NetEventsProc.exe
  • NetEventsProcDLL - 构建 NetEventsProcDLL.dll

源代码有详细注释。 你可调查实施详情并根据你的特定需求定制项目(如果愿意)。

NetEventsProc.exe 使用异步套接字实施服务器和客户端。 为了将套接字切换为异步模式,使用在异步模式下操作的可能方式之一:将套接字绑定至 WSAEventSelect(h_Socket, h_Event, FD_ALL_EVENTS) 网络事件。

如果本文能引起读者的兴趣,我们将在本文的下一个版本中讨论所有实施详情。 但目前我们就说这么多吧。 请再次注意,此项目基于伟大的Elmue 大师的基础工作。



4. 总结

我希望本文能解决 МetaТrader 4 终端与第三方应用程序(无论它们在终端所安装的本地计算机上还是远程计算机上)之间的信息交换问题。 我希望本文没有大量面条式代码,并且 DLL 函数的使用流程相当简单明了。 至少我努力去这么做了。

还有一个要点需要注意:

几乎所有计算机都是局域网 (LAN) 的节点。 即使你只有一台计算机,它仍非常可能是只包含一台计算机的 LAN 的节点。

广域网 (WAN) 的连接是通过额外的硬件设备(可称为路由器、调制解调器或其他一些技术术语)执行的。 为简单起见,我们称其为路由器。 路由器提供商分配的全局 IP 地址就用于此处。

路由器和 WAN 配合使用时可以解决某些安全问题,允许使用备用全局 IP 地址,帮助安排来自局域网的 WAN 连接。 但同时,它们实际上扭曲了 WAN 的初始意义,即任意两台计算机都能实现直接点对点连接。

产生此类效果的原因是任何路由器实际上都能执行所谓的网络地址转换 (NAT) 。 网络地址是用 <protocol, IP, port> 元组表示的。 此元组的任何元素都可被路由器更改,这完全取决于特定的路由器型号。

在这种情况下,从 LAN 访问 WAN 的计算机并不具有纯粹的 WAN 所能提供的全部优势。 绝大部分路由器都有 出站 功能,它允许路由器记住通过某个请求从 LAN 中对全局 WAN 网络进行寻址的 LAN 客户端的网络地址。

依靠这一功能,路由器可将其接受到的所有信息作为对客户端请求的响应发送给客户端。 因此,LAN 客户端可以连接到 WAN 服务器。 但是,情况并非始终如此,出于安全原因和工作纪律,一些网络地址可能会被硬件封锁。

因此,为了在 LAN 计算机上组织服务器,你必须设置 所谓的端口转发。 至于本文中的示例,在单个 LAN 计算机的最简单情况下,你需要转发端口号 2000。 为此,你可以自己动手在浏览器中连接到路由器迷你站点,或求助于专业人士。 这个迷你站点的网址很可能是 192.168.1.1。

如果你想通过 WAN 交换信息,这些东西都需要考虑。

在本文的下一个版本中,我们将探讨 点对点 (p2p) 类型的连接。

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/1361

附加的文件 |
EXE.zip (21.35 KB)
FastStart.zip (28.26 KB)
NetServerClient.zip (56.32 KB)
手动交易自动化的三个方面。 第 1 部分: 交易 手动交易自动化的三个方面。 第 1 部分: 交易
本文是介绍 МetaТrader 4 交易平台中手动交易自动化的系列文章的第一篇。 本系列文章的每一篇都专门针对以下方面之一:手动交易的自动化,交易显示自动化的当前状态,和交易结果报告的自动化。 本文中,我将介绍一个有趣的由交易者手动控制的 EA 创建方法。
随机沙盒 随机沙盒
本文包括用作为 Excel 文件的交互式“沙盒”,用于模拟随机的 Expert Advisor 回溯测试数据。 读者可以使用它,有助于探索和深入了解 MetaTrader 默认提供的 EA 性能指标。 本文旨在引导读者获得这种体验。
Chuvashov 的三角形机械交易系统 Chuvashov 的三角形机械交易系统
我将对基于 Stanislav Chuvashov 理念的机械交易系统进行概述并提供程序代码。 三角形建基于上分形和下分形产生的两条趋势线的交叉。
在Linux上运行MetaTrader 4 在Linux上运行MetaTrader 4
在本文中,我们演示了一种在流行的Linux版本(Ubuntu和Debian)上安装MetaTrader 4的简单方法。这些系统广泛用于服务器硬件以及交易者的个人计算机上。