CreateNamedPipeW, ReadFile, WriteFile) через импорт kernel32.dll позволяет организовать сверхбыстрый (sub-millisecond) локальный обмен данными между несколькими терминалами MT5 без использования сторонних DLL и сетевых сокетов.Зачем нужны Named Pipes в алготрейдинге?
При построении сложных распределенных торговых систем часто возникает необходимость мгновенной передачи сигналов между терминалами. Например, когда ведущий советник, использующий панель управления советником с кнопками Buy/Sell, должен мгновенно дублировать сделки на ведомые терминалы (копирование сделок).
В отличие от сетевых сокетов или отправки уведомлений через внешние API (как описано в статье про критические PUSH-уведомления через WebRequest), именованные каналы (Named Pipes) работают локально на уровне ядра Windows. Это обеспечивает субмиллисекундную задержку (latency), что критически важно для высокочастотного трейдинга и синхронизации сложных стратегий, таких как сетка ордеров (Grid) с мартингейлом.
Исходный код
Ниже представлен готовый к использованию советник (Expert Advisor), который может работать как в режиме Сервера (Master), так и в режиме Клиента (Slave). Код написан с использованием неблокирующего режима чтения, что предотвращает зависание интерфейса MetaTrader 5.
//+------------------------------------------------------------------+
//| MT5_Pipes.mq5 |
//| Copyright 2024, Finfluct |
//| https://finfluct.com |
//+------------------------------------------------------------------+
#property property_strict
#property description "Named Pipes IPC for MT5 Terminals"
// Импорт функций Windows API из kernel32.dll
#import "kernel32.dll"
int CreateNamedPipeW(string lpName, uint dwOpenMode, uint dwPipeMode, uint nMaxInstances, uint nOutBufferSize, uint nInBufferSize, uint nDefaultTimeOut, ulong lpSecurityAttributes);
int ConnectNamedPipe(int hNamedPipe, ulong lpOverlapped);
int DisconnectNamedPipe(int hNamedPipe);
int CreateFileW(string lpFileName, uint dwDesiredAccess, uint dwShareMode, ulong lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, int hTemplateFile);
int ReadFile(int hFile, uchar &lpBuffer[], uint nNumberOfBytesToRead, uint &lpNumberOfBytesRead, ulong lpOverlapped);
int WriteFile(int hFile, uchar &lpBuffer[], uint nNumberOfBytesToWrite, uint &lpNumberOfBytesWritten, ulong lpOverlapped);
int CloseHandle(int hObject);
int PeekNamedPipe(int hNamedPipe, uchar &lpBuffer[], uint nBufferSize, uint &lpBytesRead, uint &lpTotalBytesAvail, uint &lpBytesLeftThisMessage);
#import
// Константы WinAPI
#define INVALID_HANDLE_VALUE -1
#define PIPE_ACCESS_DUPLEX 0x00000003
#define PIPE_TYPE_BYTE 0x00000000
#define PIPE_READMODE_BYTE 0x00000000
#define PIPE_NOWAIT 0x00000001
#define OPEN_EXISTING 3
#define GENERIC_READ 0x80000000
#define GENERIC_WRITE 0x40000000
#define FILE_ATTRIBUTE_NORMAL 0x00000080
enum ENUM_ROLE {
ROLE_SERVER, // Master (Сервер)
ROLE_CLIENT // Slave (Клиент)
};
input ENUM_ROLE InputRole = ROLE_SERVER;
input string InputPipeName = "MT5_Easy_Pipe";
int hPipe = INVALID_HANDLE_VALUE;
string fullPipePath = "";
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
fullPipePath = "\\\\.\\pipe\\" + InputPipeName;
EventSetMillisecondTimer(100);
if(InputRole == ROLE_SERVER)
{
// Создаем именованный канал в неблокирующем режиме (PIPE_NOWAIT)
hPipe = CreateNamedPipeW(fullPipePath,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_NOWAIT,
1, 1024, 1024, 0, 0);
if(hPipe == INVALID_HANDLE_VALUE)
{
Print("Ошибка создания Named Pipe. Код ошибки: ", GetLastError());
return(INIT_FAILED);
}
Print("Сервер Named Pipe запущен: ", fullPipePath);
}
else
{
Print("Клиент инициализирован. Ожидание подключения к: ", fullPipePath);
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
EventKillTimer();
if(hPipe != INVALID_HANDLE_VALUE)
{
if(InputRole == ROLE_SERVER)
{
DisconnectNamedPipe(hPipe);
}
CloseHandle(hPipe);
hPipe = INVALID_HANDLE_VALUE;
}
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Пример: Сервер транслирует изменение цены Bid в канал
if(InputRole == ROLE_SERVER && hPipe != INVALID_HANDLE_VALUE)
{
static double lastBid = 0;
double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(currentBid != lastBid)
{
lastBid = currentBid;
string message = _Symbol + "|" + DoubleToString(lastBid, _Digits);
SendMessage(message);
}
}
}
//+------------------------------------------------------------------+
//| Timer function |
//+------------------------------------------------------------------+
void OnTimer()
{
if(InputRole == ROLE_CLIENT)
{
// Попытка подключения, если канал еще не открыт
if(hPipe == INVALID_HANDLE_VALUE)
{
hPipe = CreateFileW(fullPipePath, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if(hPipe != INVALID_HANDLE_VALUE)
{
Print("Успешное подключение к Серверу Pipe!");
}
}
else
{
// Чтение входящих сообщений
string msg = ReceiveMessage();
if(msg != "")
{
Print("Получено сообщение: ", msg);
}
}
}
else if(InputRole == ROLE_SERVER)
{
// Сервер проверяет входящие подключения в неблокирующем режиме
ConnectNamedPipe(hPipe, 0);
}
}
//+------------------------------------------------------------------+
//| Отправка сообщения в канал |
//+------------------------------------------------------------------+
void SendMessage(string text)
{
if(hPipe == INVALID_HANDLE_VALUE) return;
uchar buffer[];
StringToCharArray(text, buffer, 0, WHOLE_ARRAY, CP_UTF8);
uint bytesWritten = 0;
int res = WriteFile(hPipe, buffer, ArraySize(buffer), bytesWritten, 0);
if(res == 0)
{
// Если запись не удалась, возможно клиент отключился
Print("Ошибка записи в Pipe. Клиент отключен?");
}
}
//+------------------------------------------------------------------+
//| Безопасное неблокирующее чтение из канала |
//+------------------------------------------------------------------+
string ReceiveMessage()
{
if(hPipe == INVALID_HANDLE_VALUE) return "";
uint bytesAvail = 0;
uint bytesRead = 0;
uint lpLeft = 0;
uchar peekBuf[1];
// Проверяем наличие данных в канале без блокировки потока
int peekRes = PeekNamedPipe(hPipe, peekBuf, 1, bytesRead, bytesAvail, lpLeft);
if(peekRes == 0 || bytesAvail == 0)
{
if(peekRes == 0)
{
CloseHandle(hPipe);
hPipe = INVALID_HANDLE_VALUE;
Print("Соединение с Pipe потеряно.");
}
return "";
}
uchar buffer[];
ArrayResize(buffer, bytesAvail);
int readRes = ReadFile(hPipe, buffer, bytesAvail, bytesRead, 0);
if(readRes != 0 && bytesRead > 0)
{
return CharArrayToString(buffer, 0, bytesRead, CP_UTF8);
}
return "";
} Разбор параметров
InputRole: Определяет режим работы советника.ROLE_SERVERсоздает именованный канал и выступает в роли источника сигналов (Master).ROLE_CLIENTподключается к существующему каналу для приема данных (Slave).InputPipeName: Имя создаваемого канала. Должно быть абсолютно одинаковым на стороне Сервера и Клиента. Полный системный путь формируется автоматически как\\.\pipe\имя_канала.PIPE_NOWAIT: Важнейший флаг WinAPI, переводящий канал в неблокирующий режим. Без него вызовы функций чтения/записи заморозят поток терминала MT5 до тех пор, пока на другом конце не появится активность.PeekNamedPipe: Функция предварительного анализа буфера. Позволяет узнать, сколько байт готово к чтению, предотвращая зависание функцииReadFile.
Как запустить
Для демонстрации работы межпроцессного взаимодействия выполните следующие шаги:
1. Откройте первый терминал MetaTrader 5. Скомпилируйте код выше и запустите советник на любом графике с параметром InputRole = ROLE_SERVER. В журнале появится запись о запуске сервера.
2. Откройте второй терминал MetaTrader 5 на этом же компьютере. Запустите этот же советник, но с параметром InputRole = ROLE_CLIENT.
3. В журнале клиентского терминала появится сообщение Успешное подключение к Серверу Pipe!. При каждом изменении цены Bid на сервере, клиент будет мгновенно получать и выводить в лог сообщения вида EURUSD|1.08450.




