diff --git a/MtClient/MtClient.csproj b/MtClient/MtClient.csproj
new file mode 100755
index 00000000..bb23fb7d
--- /dev/null
+++ b/MtClient/MtClient.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/MtClient/MtMessage.cs b/MtClient/MtMessage.cs
new file mode 100755
index 00000000..77b09b9a
--- /dev/null
+++ b/MtClient/MtMessage.cs
@@ -0,0 +1,180 @@
+namespace MtClient
+{
+ public enum MessageType
+ {
+ Command = 0,
+ Response = 1,
+ Event = 2,
+ ExpertList = 3,
+ ExpertAdded = 4,
+ ExpertRemoved = 5,
+ }
+
+ public abstract class MtMessage
+ {
+ public abstract MessageType MsgType { get; }
+
+ public string Serialize()
+ {
+ return $"{(int)MsgType};{GetMessageBody()}";
+ }
+
+ protected abstract string GetMessageBody();
+ }
+
+ public class MtCommand(int expertHandle, int commandType, int commandId, string payload) : MtMessage
+ {
+ public override MessageType MsgType => MessageType.Command;
+
+ public int ExpertHandle { private set; get; } = expertHandle;
+ public int CommandType { private set; get; } = commandType;
+ public int CommandId { private set; get; } = commandId;
+ public string Payload { private set; get; } = payload;
+
+ protected override string GetMessageBody()
+ {
+ return $"{ExpertHandle};{CommandId};{CommandType};{Payload}";
+ }
+ }
+
+ public class MtEvent(int expertHandle, int eventType, string payload) : MtMessage
+ {
+ public override MessageType MsgType => MessageType.Event;
+
+ public int ExpertHandle { private set; get; } = expertHandle;
+ public int EventType { private set; get; } = eventType;
+ public string Payload { private set; get; } = payload;
+
+ protected override string GetMessageBody()
+ {
+ throw new NotImplementedException();
+ }
+
+ public static MtMessage? Parse(string payload)
+ {
+ var pieces = payload.Split(";", 3);
+ if (pieces.Length == 3
+ && int.TryParse(pieces[0], out int expertHandle)
+ && int.TryParse(pieces[1], out int eventType))
+ return new MtEvent(expertHandle, eventType, payload);
+ return null;
+ }
+ }
+
+ public class MtExpertAddedMsg(int expertHandle) : MtMessage
+ {
+ public override MessageType MsgType => MessageType.ExpertAdded;
+
+ public int ExpertHandle { private set; get; } = expertHandle;
+
+ protected override string GetMessageBody()
+ {
+ throw new NotImplementedException();
+ }
+
+ public static MtMessage? Parse(string payload)
+ {
+ if (int.TryParse(payload, out int expertHandle) == false)
+ return null;
+
+ return new MtExpertAddedMsg(expertHandle);
+ }
+ }
+
+ public class MtExpertRemovedMsg(int expertHandle) : MtMessage
+ {
+ public override MessageType MsgType => MessageType.ExpertRemoved;
+
+ public int ExpertHandle { private set; get; } = expertHandle;
+
+ protected override string GetMessageBody()
+ {
+ throw new NotImplementedException();
+ }
+
+ public static MtMessage? Parse(string payload)
+ {
+ if (int.TryParse(payload, out int expertHandle) == false)
+ return null;
+
+ return new MtExpertRemovedMsg(expertHandle);
+ }
+ }
+
+ public class MtExpertListMsg(List experts) : MtMessage
+ {
+ public override MessageType MsgType => MessageType.ExpertList;
+
+ public List Experts { private set; get; } = experts;
+
+ protected override string GetMessageBody()
+ {
+ throw new NotImplementedException();
+ }
+
+ public static MtMessage? Parse(string payload)
+ {
+ var pieces = payload.Split(",");
+ List handles = [];
+ foreach (var p in pieces)
+ {
+ if (int.TryParse(p, out int expertHandle) == false)
+ return null;
+ handles.Add(expertHandle);
+ }
+
+ return new MtExpertListMsg(handles);
+ }
+ }
+
+ public class MtResponse(int expertHandle, int commandId, string payload) : MtMessage
+ {
+ public override MessageType MsgType => MessageType.Response;
+
+ public int ExpertHandle { private set; get; } = expertHandle;
+ public int CommandId { private set; get; } = commandId;
+ public string Payload { private set; get; } = payload;
+
+ protected override string GetMessageBody()
+ {
+ throw new NotImplementedException();
+ }
+
+ public static MtMessage? Parse(string payload)
+ {
+ var pieces = payload.Split(";", 3);
+ if (pieces.Length != 3
+ || string.IsNullOrEmpty(pieces[0])
+ || string.IsNullOrEmpty(pieces[1])
+ || string.IsNullOrEmpty(pieces[2]))
+ return null;
+
+ if (int.TryParse(pieces[0], out int expertHandle) == false)
+ return null;
+
+ if (int.TryParse(pieces[1], out int commandId) == false)
+ return null;
+
+ return new MtResponse(expertHandle, commandId, pieces[2]);
+ }
+ }
+
+ public static class MtMessageParser
+ {
+ static MtMessageParser()
+ {
+ msgHandlers_[MessageType.Event] = MtEvent.Parse;
+ msgHandlers_[MessageType.Response] = MtResponse.Parse;
+ msgHandlers_[MessageType.ExpertList] = MtExpertListMsg.Parse;
+ msgHandlers_[MessageType.ExpertAdded] = MtExpertAddedMsg.Parse;
+ msgHandlers_[MessageType.ExpertRemoved] = MtExpertRemovedMsg.Parse;
+ }
+
+ public static MtMessage? Parse(MessageType msgType, string payload)
+ {
+ return msgHandlers_[msgType](payload);
+ }
+
+ private static readonly Dictionary> msgHandlers_ = new();
+ }
+}
diff --git a/MtClient/MtRpcClient.cs b/MtClient/MtRpcClient.cs
new file mode 100755
index 00000000..a2bbac7e
--- /dev/null
+++ b/MtClient/MtRpcClient.cs
@@ -0,0 +1,184 @@
+using System.Net.WebSockets;
+using System.Text;
+
+namespace MtClient
+{
+ public class MtRpcClient
+ {
+ public MtRpcClient(string host, int port)
+ {
+ host_ = host;
+ port_ = port;
+
+ receiveThread_ = new Thread(new ThreadStart(DoReceive));
+ sendThread_ = new Thread(new ThreadStart(DoWrite));
+ }
+
+ public async Task Connect()
+ {
+ Log($"Connect: started to {host_}:{port_}");
+
+ try
+ {
+ await ws_.ConnectAsync(new Uri($"ws://{host_}:{port_}/ws"), CancellationToken.None);
+ }
+ catch (Exception ex)
+ {
+ Log($"Connect failed: {ex.Message}");
+ throw new Exception($"Failed connection to {host_}:{port_}");
+ }
+
+ receiveThread_.Start();
+ sendThread_.Start();
+
+ Log($"Connect: success.");
+ }
+
+ public async void Disconnect()
+ {
+ Log($"Disconnect: {host_}:{port_}");
+
+ try
+ {
+ await ws_.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
+ ws_.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Log($"Disconnect: {ex.Message}");
+ }
+
+ sendWaiter_.Set();
+ sendThread_.Join();
+ receiveThread_.Join();
+
+ Log($"Disconnect: success");
+ }
+
+ public void Send(MtMessage message)
+ {
+ pendingMessages_.Enqueue(message.Serialize());
+ sendWaiter_.Set();
+ }
+
+ private async void DoWrite()
+ {
+ while(ws_.State == WebSocketState.Open)
+ {
+ string? message = null;
+ lock(pendingMessages_)
+ {
+ if (pendingMessages_.Count > 0)
+ message = pendingMessages_.Dequeue();
+ }
+
+ if (message == null)
+ {
+ sendWaiter_.WaitOne();
+ continue;
+ }
+
+ try
+ {
+ Log($"DoWrite: sending message: {message}");
+ byte[] bytes = Encoding.ASCII.GetBytes(message);
+ await ws_.SendAsync(bytes, WebSocketMessageType.Text, true, CancellationToken.None);
+ }
+ catch (Exception e)
+ {
+ Log($"DoWrite: {e.Message}");
+ }
+ }
+ }
+
+ private async void DoReceive()
+ {
+ try
+ {
+ byte[] recvBuffer = new byte[64 * 1024];
+ while (ws_.State == WebSocketState.Open)
+ {
+ var result = await ws_.ReceiveAsync(new ArraySegment(recvBuffer), CancellationToken.None);
+ if (result.MessageType == WebSocketMessageType.Close)
+ {
+ Log($"DoReceive: close signal {result.CloseStatusDescription}");
+ await ws_.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
+ break;
+ }
+ else
+ {
+ var msg = Encoding.ASCII.GetString(recvBuffer, 0, result.Count);
+ OnReceive(msg);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Log($"Exception in receive - {ex.Message}");
+ ConnectionFailed?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ private void OnReceive(string msg)
+ {
+ Log($"OnReceive: {msg}");
+
+ if (string.IsNullOrEmpty(msg))
+ {
+ Log("OnReceive: Invalid message (null or empty)");
+ return;
+ }
+
+ var pieces = msg.Split(";", 2);
+
+ if (pieces.Length != 2
+ || string.IsNullOrEmpty(pieces[0])
+ || string.IsNullOrEmpty(pieces[1]))
+ {
+ Log("OnReceive: Invalid message format.");
+ return;
+ }
+
+ MessageType msgType;
+ try
+ {
+ var msgTypeValue = int.Parse(pieces[0]);
+ msgType = (MessageType)Enum.ToObject(typeof(MessageType), msgTypeValue);
+
+ }
+ catch (Exception e)
+ {
+ Log($"OnReceive: Parse MessageType failed. {e.Message}");
+ return;
+ }
+
+ var message = MtMessageParser.Parse(msgType, (pieces[1]));
+ if (message == null)
+ {
+ Log("OnReceive: Failed parse message payload");
+ return;
+ }
+
+ MessageReceived?.Invoke(this, message);
+ }
+
+ private void Log(string msg)
+ {
+ Console.WriteLine($"[{Environment.CurrentManagedThreadId}] {msg}");
+ }
+
+ public event EventHandler? MessageReceived;
+ public event EventHandler? ConnectionFailed;
+
+ private readonly ClientWebSocket ws_ = new();
+ private readonly string host_;
+ private readonly int port_;
+ private readonly byte[] buf_ = new byte[10000];
+ private readonly Dictionary> msgHandlers_ = new();
+ private readonly Queue pendingMessages_ = [];
+
+ private readonly Thread receiveThread_;
+ private readonly Thread sendThread_;
+ private readonly EventWaitHandle sendWaiter_ = new AutoResetEvent(false);
+ }
+}
\ No newline at end of file
diff --git a/MtService/CommandTask.h b/MtService/CommandTask.h
new file mode 100755
index 00000000..14b812ab
--- /dev/null
+++ b/MtService/CommandTask.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include
+#include "MtMessage.h"
+
+typedef std::function TaskCallback;
+
+class CommandTask
+{
+public:
+ CommandTask(std::unique_ptr command, TaskCallback callback)
+ : command_(std::move(command))
+ , callback_(std::move(callback))
+ {}
+
+ MtCommand& getCommand() const { return *command_; }
+
+ void ProcessResponse(int handle, const std::string& payload)
+ {
+ MtResponse respone(handle, command_->getCommandId(), payload);
+ callback_(respone);
+ }
+
+private:
+ std::unique_ptr command_;
+ TaskCallback callback_;
+};
\ No newline at end of file
diff --git a/MtService/LogConfigurator.cpp b/MtService/LogConfigurator.cpp
new file mode 100755
index 00000000..6b3f8b77
--- /dev/null
+++ b/MtService/LogConfigurator.cpp
@@ -0,0 +1,90 @@
+#include "pch.h"
+#include "LogConfigurator.h"
+
+#include
+#include
+#include
+
+static const std::string DEFAULT_FILE_EXTENSION = ".log";
+
+using namespace boost::log;
+
+namespace
+{
+trivial::severity_level ToSeverityLevel(LogLevel level)
+{
+ switch (level)
+ {
+ case LogLevel::Fatal:
+ return trivial::fatal;
+ case LogLevel::Error:
+ return trivial::error;
+ case LogLevel::Warning:
+ return trivial::warning;
+ case LogLevel::Info:
+ return trivial::info;
+ case LogLevel::Debug:
+ return trivial::debug;
+ case LogLevel::Trace:
+ return trivial::trace;
+ default:
+ assert(false);
+ }
+ throw;
+}
+
+std::string GetTempDirectoryPathImpl()
+{
+ std::string tmp_prefix;
+ char char_path[MAX_PATH];
+ if (auto s = GetTempPathA(MAX_PATH, char_path))
+ {
+ tmp_prefix = std::string(char_path, std::size_t(s - 1));
+ }
+ std::replace(tmp_prefix.begin(), tmp_prefix.end(), '\\', '/');
+ return tmp_prefix;
+}
+} // namespace
+
+void LogConfigurator::Setup(LogLevel level, OutputType output_type,
+ const std::string& profile_name)
+{
+ if (profile_name.empty())
+ throw std::runtime_error("Invalid profile name");
+
+ static const std::string COMMON_FMT("[%TimeStamp%] [%ThreadID%] [%Severity%] [%ClassName%]: %Message%");
+
+ register_simple_formatter_factory("Severity");
+
+ if ((output_type & OutputType::Console) == OutputType::Console)
+ {
+ // Output message to console
+ add_console_log(
+ std::cout,
+ keywords::format = COMMON_FMT,
+ keywords::auto_flush = true);
+ }
+
+ if ((output_type & OutputType::File) == OutputType::File)
+ {
+ std::string file_name = profile_name;
+ file_name += "_%Y%m%d_%2N";
+ file_name += DEFAULT_FILE_EXTENSION;
+
+ std::string file_path = GetTempDirectoryPathImpl() + "\\" + profile_name + "\\" + file_name;
+
+ // Output message to file, rotates when file reached 1mb or at midnight every day. Each log file
+ // is capped at 1mb and total is 20mb
+ add_file_log(
+ keywords::file_name = file_path,
+ keywords::rotation_size = 1 * 1024 * 1024,
+ keywords::max_size = 20 * 1024 * 1024,
+ keywords::time_based_rotation = sinks::file::rotation_at_time_point(0, 0, 0),
+ keywords::format = COMMON_FMT,
+ keywords::auto_flush = true);
+ }
+
+ add_common_attributes();
+
+ core::get()->set_filter(trivial::severity >= ToSeverityLevel(level));
+}
diff --git a/MtService/LogConfigurator.h b/MtService/LogConfigurator.h
new file mode 100755
index 00000000..1616c800
--- /dev/null
+++ b/MtService/LogConfigurator.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "LogLevel.h"
+#include
+#include
+
+enum class OutputType : int
+{
+ Console = 0x1,
+ File = 0x2
+};
+
+inline OutputType operator|(OutputType lhs, OutputType rhs)
+{
+ using T = std::underlying_type_t;
+ return static_cast(static_cast(lhs) | static_cast(rhs));
+}
+
+inline OutputType& operator|=(OutputType& lhs, OutputType rhs)
+{
+ lhs = lhs | rhs;
+ return lhs;
+}
+
+inline OutputType operator&(OutputType lhs, OutputType rhs)
+{
+ using T = std::underlying_type_t;
+ return static_cast(static_cast(lhs) & static_cast(rhs));
+}
+
+
+class LogConfigurator
+{
+public:
+ static void Setup(LogLevel level, OutputType output_type,
+ const std::string& profile_name);
+};
\ No newline at end of file
diff --git a/MtService/LogLevel.h b/MtService/LogLevel.h
new file mode 100755
index 00000000..d08266c3
--- /dev/null
+++ b/MtService/LogLevel.h
@@ -0,0 +1,11 @@
+#pragma once
+
+enum class LogLevel
+{
+ Fatal,
+ Error,
+ Warning,
+ Info,
+ Debug,
+ Trace
+};
\ No newline at end of file
diff --git a/MtService/Logger.cpp b/MtService/Logger.cpp
new file mode 100755
index 00000000..0cf1102d
--- /dev/null
+++ b/MtService/Logger.cpp
@@ -0,0 +1,90 @@
+#include "pch.h"
+#include "Logger.h"
+#include "LogLevel.h"
+#include "boost/log/common.hpp"
+#include "boost/log/trivial.hpp"
+#include "boost/log/sources/severity_logger.hpp"
+#include
+
+using namespace boost::log;
+
+class Logger::LoggerImpl
+{
+public:
+ LoggerImpl(const char* class_name)
+ {
+ boost_logger_.add_attribute("ClassName", attributes::constant(class_name));
+ }
+
+ void LogSev(trivial::severity_level level, const char* msg)
+ {
+ BOOST_LOG_SEV(boost_logger_, level) << msg;
+ }
+
+ void LogSev(trivial::severity_level level, const char* fmt, va_list va)
+ {
+ char buf[8192];
+ vsnprintf(buf, sizeof(buf), fmt, va);
+ LogSev(level, buf);
+ }
+
+private:
+ sources::severity_logger boost_logger_;
+};
+
+Logger::Logger(const char* class_name)
+ : logger_impl_(new Logger::LoggerImpl(class_name))
+{
+}
+
+Logger::~Logger()
+{
+}
+
+void Logger::Fatal(const char* fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ logger_impl_->LogSev(trivial::fatal, fmt, va);
+ va_end(va);
+}
+
+void Logger::Error(const char* fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ logger_impl_->LogSev(trivial::error, fmt, va);
+ va_end(va);
+}
+
+void Logger::Warning(const char* fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ logger_impl_->LogSev(trivial::warning, fmt, va);
+ va_end(va);
+}
+
+void Logger::Info(const char* fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ logger_impl_->LogSev(trivial::info, fmt, va);
+ va_end(va);
+}
+
+void Logger::Debug(const char* fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ logger_impl_->LogSev(trivial::debug, fmt, va);
+ va_end(va);
+}
+
+void Logger::Trace(const char* fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ logger_impl_->LogSev(trivial::trace, fmt, va);
+ va_end(va);
+}
diff --git a/MtService/Logger.h b/MtService/Logger.h
new file mode 100755
index 00000000..ab682b74
--- /dev/null
+++ b/MtService/Logger.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include
+#include
+
+class Logger
+{
+public:
+ Logger(const char* class_name);
+ ~Logger();
+
+ void Fatal(const char* fmt, ...);
+ void Error(const char* fmt, ...);
+ void Warning(const char* fmt, ...);
+ void Info(const char* fmt, ...);
+ void Debug(const char* fmt, ...);
+ void Trace(const char* fmt, ...);
+
+private:
+ class LoggerImpl;
+ std::unique_ptr logger_impl_;
+};
diff --git a/MtService/MetaTraderHandler.h b/MtService/MetaTraderHandler.h
new file mode 100755
index 00000000..ed0635af
--- /dev/null
+++ b/MtService/MetaTraderHandler.h
@@ -0,0 +1,8 @@
+#pragma once
+
+class MetaTraderHandler
+{
+public:
+ virtual ~MetaTraderHandler() = default;
+ virtual void SendTickToMetaTrader() = 0;
+};
\ No newline at end of file
diff --git a/MtService/MtConnection.cpp b/MtService/MtConnection.cpp
new file mode 100755
index 00000000..14dbeb8c
--- /dev/null
+++ b/MtService/MtConnection.cpp
@@ -0,0 +1,164 @@
+#include "pch.h"
+#include "MtConnection.h"
+
+#include
+
+MtConnection::MtConnection(WebsocketStream&& ws)
+ : ws_(std::move(ws))
+ , resolver_(ws_.get_executor())
+{
+ log_.Debug("%s: connection %p created.", __FUNCTION__, this);
+}
+
+MtConnection::~MtConnection()
+{
+ log_.Debug("%s: connection %p destroyed.", __FUNCTION__, this);
+}
+
+void MtConnection::Accept()
+{
+ // Set suggested timeout settings for the websocket
+ ws_.set_option(
+ boost::beast::websocket::stream_base::timeout::suggested(
+ boost::beast::role_type::server));
+
+ // Set a decorator to change the Server of the handshake
+ ws_.set_option(boost::beast::websocket::stream_base::decorator(
+ [](boost::beast::websocket::response_type& res)
+ {
+ res.set(boost::beast::http::field::server,
+ std::string(BOOST_BEAST_VERSION_STRING) +
+ " websocket-server-async");
+ }));
+ // Accept the websocket handshake
+ ws_.async_accept(
+ std::bind(
+ &MtConnection::OnAccept,
+ shared_from_this(),
+ std::placeholders::_1));
+}
+
+void MtConnection::Close()
+{
+ log_.Debug("%s: entry.", __FUNCTION__);
+
+ ws_.async_close(boost::beast::websocket::close_code::normal,
+ [](boost::beast::error_code const ec){ });
+}
+
+void MtConnection::Send(const std::string& msg)
+{
+ log_.Trace("%s: msg = %s", __FUNCTION__, msg.c_str());
+
+ send_queue_.push(msg);
+ if (send_queue_.size() == 1)
+ DoWrite();
+}
+
+void MtConnection::OnAccept(boost::beast::error_code ec)
+{
+ log_.Trace("%s: entry.", __FUNCTION__);
+
+ if (ec)
+ {
+ log_.Error("%s: %s", __FUNCTION__, ec.what().c_str());
+ OnConnectionFailed(ec.message());
+ }
+ else
+ DoRead();
+
+ OnConnected();
+}
+
+void MtConnection::DoRead()
+{
+ log_.Trace("%s: enter.", __FUNCTION__);
+
+ // Clear the buffer
+ read_text_.clear();
+
+ // Read a message into our buffer
+ ws_.async_read(
+ read_buffer_,
+ std::bind(
+ &MtConnection::OnRead,
+ shared_from_this(),
+ std::placeholders::_1,
+ std::placeholders::_2));
+}
+
+void MtConnection::OnRead(
+ boost::beast::error_code ec,
+ std::size_t bytes_transferred)
+{
+ boost::ignore_unused(bytes_transferred);
+
+ // This indicates that the session was closed
+ if (ec == boost::beast::websocket::error::closed)
+ {
+ log_.Info("%s: session was closed.", __FUNCTION__);
+ OnDisconnected();
+ return;
+ }
+
+ if (ec == boost::asio::error::operation_aborted)
+ {
+ log_.Info("%s: session was aborted.", __FUNCTION__);
+ return;
+ }
+
+ if (ec)
+ {
+ log_.Error("%s: %s", __FUNCTION__, ec.message().c_str());
+ OnConnectionFailed(ec.message());
+ }
+ else
+ {
+ auto msg = boost::beast::buffers_to_string(read_buffer_.data());
+ log_.Trace("%s: %s", __FUNCTION__, msg.c_str());
+
+ OnMessageReceived(msg);
+
+ // Clear the buffer
+ read_buffer_.consume(read_buffer_.size());
+
+ boost::asio::dispatch(ws_.get_executor(),
+ std::bind(
+ &MtConnection::DoRead,
+ shared_from_this()));
+ }
+}
+
+void MtConnection::DoWrite()
+{
+ if (send_queue_.empty())
+ return;
+
+ log_.Trace("%s: msg = %s", __FUNCTION__, send_queue_.front().c_str());
+
+ ws_.text(ws_.got_text());
+ ws_.async_write(
+ boost::asio::buffer(send_queue_.front()),
+ std::bind(
+ &MtConnection::OnWrite,
+ shared_from_this(),
+ std::placeholders::_1,
+ std::placeholders::_2));
+}
+
+void MtConnection::OnWrite(
+ boost::beast::error_code ec,
+ std::size_t bytes_transferred)
+{
+ boost::ignore_unused(bytes_transferred);
+
+ if (ec)
+ {
+ log_.Error("%s, %s", __FUNCTION__, ec.what().c_str());
+ return;
+ }
+
+ send_queue_.pop();
+ DoWrite();
+}
+
diff --git a/MtService/MtConnection.h b/MtService/MtConnection.h
new file mode 100755
index 00000000..e27e0dbd
--- /dev/null
+++ b/MtService/MtConnection.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "Logger.h"
+#include "MtMessage.h"
+
+typedef boost::beast::websocket::stream WebsocketStream;
+
+class MtConnection : public std::enable_shared_from_this
+{
+public:
+ MtConnection(WebsocketStream&& ws);
+ ~MtConnection();
+
+ void Accept();
+ void Close();
+ void Send(const std::string& msg);
+
+ boost::signals2::signal OnConnected;
+ boost::signals2::signal OnConnectionFailed;
+ boost::signals2::signal OnDisconnected;
+ boost::signals2::signal OnMessageReceived;
+
+private:
+ void OnAccept(boost::beast::error_code ec);
+ void DoRead();
+ void OnRead(
+ boost::beast::error_code ec,
+ std::size_t bytes_transferred);
+ void DoWrite();
+ void OnWrite(
+ boost::beast::error_code ec,
+ std::size_t bytes_transferred);
+
+ Logger log_{ "MtConnection" };
+ WebsocketStream ws_;
+ boost::asio::ip::tcp::resolver resolver_;
+ std::string host_;
+ std::string read_text_;
+ boost::beast::flat_buffer read_buffer_;
+ //std::string send_text_;
+ std::queue send_queue_;
+};
diff --git a/MtService/MtExpert.cpp b/MtService/MtExpert.cpp
new file mode 100755
index 00000000..37b0918d
--- /dev/null
+++ b/MtService/MtExpert.cpp
@@ -0,0 +1,73 @@
+#include "pch.h"
+
+#include "MtExpert.h"
+#include
+
+namespace
+{
+constexpr int MT_COMMAND_TYPE_EMPTY = 0;
+}
+
+MtExpert::MtExpert(int handle, std::unique_ptr mt_handler)
+ : handle_(handle)
+ , mt_handler_(std::move(mt_handler))
+{
+ log_.Debug("%s: expert %d created.", __FUNCTION__, handle_);
+}
+
+MtExpert::~MtExpert()
+{
+ log_.Debug("%s: expert %d destroyed.", __FUNCTION__, handle_);
+}
+
+void MtExpert::Deinit()
+{
+ log_.Debug("%s: handle = %d", __FUNCTION__, handle_);
+
+ OnDeinit(handle_);
+}
+
+void MtExpert::SendEvent(int event_type, const std::string& payload)
+{
+ log_.Trace("%s: handle = %d, event_type = %d, payload = %s", __FUNCTION__, handle_, event_type, payload.c_str());
+
+ OnEvent(MtEvent(handle_, event_type, payload));
+}
+
+void MtExpert::SendResponse(const std::string& payload)
+{
+ log_.Trace("%s: handle = %d, payload = %s", __FUNCTION__, handle_, payload.c_str());
+
+ if (current_task_)
+ {
+ current_task_->ProcessResponse(handle_, payload);
+ current_task_.reset();
+ }
+}
+
+void MtExpert::Process(std::unique_ptr command, TaskCallback callback)
+{
+ log_.Debug("%s: handle = %d, command type = %d, command id = %d", __FUNCTION__, handle_,
+ command->getCommandType(), command->getCommandId());
+
+ auto task = std::make_unique(std::move(command), std::move(callback));
+ tasks_.push(std::move(task));
+
+ mt_handler_->SendTickToMetaTrader();
+}
+
+int MtExpert::GetCommandType()
+{
+ if (tasks_.size() == 0)
+ return MT_COMMAND_TYPE_EMPTY;
+
+ current_task_ = std::move(tasks_.front());
+ tasks_.pop();
+
+ return current_task_->getCommand().getCommandType();
+}
+
+std::string MtExpert::GetCommandPayload()
+{
+ return current_task_ ? current_task_->getCommand().getPayload() : "";
+}
\ No newline at end of file
diff --git a/MtService/MtExpert.h b/MtService/MtExpert.h
new file mode 100755
index 00000000..697a119e
--- /dev/null
+++ b/MtService/MtExpert.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#include "CommandTask.h"
+#include "Logger.h"
+#include "MetaTraderHandler.h"
+#include "MtMessage.h"
+
+class MtExpert
+{
+public:
+ MtExpert(int handle, std::unique_ptr mt_handler);
+ ~MtExpert();
+
+ void Deinit();
+
+ void SendEvent(int event_type, const std::string& payload);
+ void SendResponse(const std::string& payload);
+
+ void Process(std::unique_ptr command, TaskCallback callback);
+
+ int GetCommandType();
+ std::string GetCommandPayload();
+
+ boost::signals2::signal OnEvent;
+ boost::signals2::signal OnDeinit;
+
+private:
+ Logger log_{ "MtExpert" };
+ int handle_;
+ std::unique_ptr mt_handler_;
+ std::queue> tasks_;
+ std::unique_ptr current_task_;
+};
\ No newline at end of file
diff --git a/MtService/MtMessage.h b/MtService/MtMessage.h
new file mode 100755
index 00000000..e4154783
--- /dev/null
+++ b/MtService/MtMessage.h
@@ -0,0 +1,204 @@
+#pragma once
+
+#include
+#include
+
+const std::string MT_MESSAGE_DELIMETER{ ";" };
+
+enum MessageType
+{
+ COMMAND = 0,
+ RESPONSE = 1,
+ EVENT = 2,
+ EXPERT_LIST = 3,
+ EXPERT_ADDED = 4,
+ EXPERT_REMOVED = 5
+};
+
+class MtMessage
+{
+public:
+ std::string Serialize() const
+ {
+ return std::to_string(GetType()) + MT_MESSAGE_DELIMETER + GetBody();
+ }
+
+protected:
+ virtual MessageType GetType() const = 0;
+ virtual std::string GetBody() const = 0;
+};
+
+class MtResponse : public MtMessage
+{
+public:
+ MtResponse(int expert_handle, int id, const std::string& payload)
+ : expert_handle_(expert_handle)
+ , id_(id)
+ , payload_(payload)
+ {
+ }
+
+ int getExpertHandle() const { return expert_handle_; }
+
+ int getCommandId() const { return id_; }
+
+ std::string getPayload() const { return payload_; }
+
+private:
+ MessageType GetType() const override
+ {
+ return MessageType::RESPONSE;
+ }
+
+ std::string GetBody() const override
+ {
+ return std::to_string(expert_handle_)
+ + MT_MESSAGE_DELIMETER + std::to_string(id_)
+ + MT_MESSAGE_DELIMETER + payload_;
+ }
+
+ int expert_handle_;
+ int id_;
+ std::string payload_;
+};
+
+class MtCommand : public MtMessage
+{
+public:
+ MtCommand(int expert_handle, int command_id, int command_type, const std::string& payload)
+ : expert_handle_(expert_handle)
+ , command_id_(command_id)
+ , command_type_(command_type)
+ , payload_(payload)
+ {
+ }
+
+ int getExpertHandle() const { return expert_handle_; }
+
+ int getCommandId() const { return command_id_; }
+
+ int getCommandType() const { return command_type_; }
+
+ std::string getPayload() const { return payload_; }
+
+private:
+ MessageType GetType() const override
+ {
+ return MessageType::COMMAND;
+ }
+
+ std::string GetBody() const override
+ {
+ return std::to_string(expert_handle_)
+ + MT_MESSAGE_DELIMETER + std::to_string(command_id_)
+ + MT_MESSAGE_DELIMETER + std::to_string(command_type_)
+ + MT_MESSAGE_DELIMETER + payload_;
+ }
+
+ int expert_handle_;
+ int command_id_;
+ int command_type_;
+ std::string payload_;
+};
+
+class MtEvent : public MtMessage
+{
+public:
+ MtEvent(int expert_handle, int event_type, const std::string& payload)
+ : expert_handle_(expert_handle)
+ , event_type_(event_type)
+ , payload_(payload)
+ {
+ }
+
+private:
+ MessageType GetType() const override
+ {
+ return MessageType::EVENT;
+ }
+
+ std::string GetBody() const override
+ {
+ return std::to_string(expert_handle_)
+ + MT_MESSAGE_DELIMETER + std::to_string(event_type_)
+ + MT_MESSAGE_DELIMETER + payload_;
+ }
+
+ int expert_handle_;
+ int event_type_;
+ std::string payload_;
+};
+
+class MtExpertListMsg : public MtMessage
+{
+public:
+ MtExpertListMsg(std::vector experts)
+ : experts_(std::move(experts))
+ {
+ }
+
+private:
+ MessageType GetType() const override
+ {
+ return MessageType::EXPERT_LIST;
+ }
+
+ std::string GetBody() const override
+ {
+ std::stringstream ss;
+ for (size_t i = 0; i < experts_.size(); ++i)
+ {
+ if (i != 0)
+ ss << ",";
+ ss << experts_[i];
+ }
+ return ss.str();
+ }
+
+ int expert_handle_;
+ std::vector experts_;
+};
+
+class MtExpertAddedMsg : public MtMessage
+{
+public:
+ MtExpertAddedMsg(int handle)
+ : handle_(handle)
+ {
+ }
+
+private:
+ MessageType GetType() const override
+ {
+ return MessageType::EXPERT_ADDED;
+ }
+
+ std::string GetBody() const override
+ {
+ return std::to_string(handle_);
+ }
+
+ int handle_;
+};
+
+class MtExpertRemovedMsg : public MtMessage
+{
+public:
+ MtExpertRemovedMsg(int handle)
+ : handle_(handle)
+ {
+ }
+
+private:
+ MessageType GetType() const override
+ {
+ return MessageType::EXPERT_REMOVED;
+ }
+
+ std::string GetBody() const override
+ {
+ return std::to_string(handle_);
+ }
+
+ int handle_;
+};
\ No newline at end of file
diff --git a/MtService/MtServer.cpp b/MtService/MtServer.cpp
new file mode 100755
index 00000000..b67c6fca
--- /dev/null
+++ b/MtService/MtServer.cpp
@@ -0,0 +1,295 @@
+#include "pch.h"
+#include "MtServer.h"
+
+#include
+#include
+#include
+#include
+
+#include "MtConnection.h"
+#include "MtExpert.h"
+
+const std::string ADDRESS = "0.0.0.0";
+constexpr int STOP_EXPERT_INTERVAL = 1; // 1 sec
+
+MtServer::MtServer(
+ boost::asio::io_context& context,
+ unsigned short port)
+ : context_(context)
+ , port_(port)
+ , acceptor_(context)
+ , stop_timer_(context)
+{
+ boost::beast::error_code ec;
+
+ boost::asio::ip::tcp::endpoint endpoint{boost::asio::ip::make_address(ADDRESS), port};
+
+ // Open the acceptor
+ acceptor_.open(endpoint.protocol(), ec);
+ if (ec)
+ {
+ log_.Error("%s: open failed! %s", __FUNCTION__, ec.what().c_str());
+ return;
+ }
+
+ // Allow address reuse
+ acceptor_.set_option(boost::asio::socket_base::reuse_address(true), ec);
+ if (ec)
+ {
+ log_.Error("%s: set_option failed! %s", __FUNCTION__, ec.what().c_str());
+ return;
+ }
+
+ // Bind to the server address
+ acceptor_.bind(endpoint, ec);
+ if (ec)
+ {
+ log_.Error("%s: bind failed! %s", __FUNCTION__, ec.what().c_str());
+ return;
+ }
+
+ // Start listening for connections
+ acceptor_.listen(
+ boost::asio::socket_base::max_listen_connections, ec);
+ if (ec)
+ {
+ log_.Error("%s: listen failed! %s", __FUNCTION__, ec.what().c_str());
+ return;
+ }
+}
+
+MtServer::~MtServer()
+{
+ log_.Debug("%s: destructor called.", __FUNCTION__);
+}
+
+void MtServer::AddExpert(int handle, MtExpert* expert)
+{
+ log_.Debug("%s: handle = %d", __FUNCTION__, handle);
+
+ if (experts_.count(handle) == 0)
+ {
+ if (experts_.size() == 0)
+ {
+ Start();
+ }
+
+ auto slot = std::make_unique();
+ slot->expert = expert;
+ slot->event_connection = expert->OnEvent.connect(boost::bind(&MtServer::OnEvent, this, std::placeholders::_1));
+ slot->deinit_connection = expert->OnDeinit.connect(boost::bind(&MtServer::OnDeinitExpert, this, std::placeholders::_1));
+ experts_[handle] = std::move(slot);
+
+ Send(MtExpertAddedMsg(handle));
+ }
+}
+
+void MtServer::DoAccept()
+{
+ // The new connection gets its own strand
+ acceptor_.async_accept(
+ boost::asio::make_strand(context_),
+ boost::beast::bind_front_handler(
+ &MtServer::OnAccept,
+ shared_from_this()));
+}
+
+void MtServer::OnAccept(boost::beast::error_code ec,
+ boost::asio::ip::tcp::socket socket)
+{
+ log_.Trace("%s: enter", __FUNCTION__);
+
+ if (ec == boost::asio::error::operation_aborted)
+ {
+ log_.Info("%s: session was aborted.", __FUNCTION__);
+ return;
+ }
+
+ if (ec)
+ {
+ log_.Error("%s: %s", __FUNCTION__, ec.what().c_str());
+ }
+ else
+ {
+ WebsocketStream ws(std::move(socket));
+ auto connection = std::make_shared(std::move(ws));
+ connection->OnConnected.connect([con = connection->weak_from_this(), this]() {
+ auto connection = con.lock();
+ if (connection)
+ {
+ auto it = std::find(pending_connections_.begin(), pending_connections_.end(), connection);
+ if (it != pending_connections_.end())
+ {
+ pending_connections_.erase(it);
+ connections_.push_back(connection);
+ log_.Trace("%s: Pushed connection %p to collection", __FUNCTION__, connection.get());
+
+ std::vector expert_list;
+ for (const auto& e : experts_)
+ expert_list.push_back(e.first);
+
+ MtExpertListMsg msg(std::move(expert_list));
+ connection->Send(msg.Serialize());
+ }
+ }
+ });
+ connection->OnConnectionFailed.connect([con = connection->weak_from_this(), this](const std::string& msg) {
+ log_.Warning("MtServer::OnConnectionFailed: %s", msg.c_str());
+ auto connection = con.lock();
+ if (connection)
+ {
+ if (pending_connections_.size() > 0)
+ {
+ auto it = std::find(pending_connections_.begin(), pending_connections_.end(), connection);
+ if (it != pending_connections_.end())
+ {
+ pending_connections_.erase(it);
+ log_.Trace("MtServer::OnConnectionFailed: Removed connection %p from pending collection", connection.get());
+ }
+ }
+ if (connections_.size() > 0)
+ {
+ auto it = std::find(connections_.begin(), connections_.end(), connection);
+ if (it != connections_.end())
+ {
+ connections_.erase(it);
+ log_.Trace("MtServer::OnConnectionFailed: Removed connection %p from collection", connection.get());
+ }
+ }
+ }
+ });
+ connection->OnDisconnected.connect([con = connection->weak_from_this(), this]() {
+ auto connection = con.lock();
+ if (connection)
+ {
+ auto it = std::find(connections_.begin(), connections_.end(), connection);
+ if (it != connections_.end())
+ {
+ connections_.erase(it);
+ log_.Trace("MtServer::OnDisconnected: Removed connection %p from collection", connection.get());
+ }
+ }
+ });
+ connection->OnMessageReceived.connect([con = connection->weak_from_this(), this](const std::string& msg) {
+ log_.Trace("MtServer::OnMessageReceived: %s", msg.c_str());
+ auto command = ParseMessage(msg);
+ if (command)
+ {
+ if (experts_.count(command->getExpertHandle()) > 0)
+ {
+ experts_[command->getExpertHandle()]->expert->Process(std::move(command), [&](const MtResponse& response) {
+ auto connection = con.lock();
+ if (connection)
+ connection->Send(response.Serialize());
+ else
+ log_.Warning("MtServer::OnMessageReceived: connection is not valid (null)");
+ });
+ }
+ else
+ {
+ log_.Warning("MtServer::OnMessageReceived: Expert is not found with handle %d", command->getExpertHandle());
+ }
+ }
+ else
+ {
+ log_.Warning("MtServer::OnMessageReceived: Failed to parse command from message: %s", msg.c_str());
+ }
+ });
+ pending_connections_.push_back(connection);
+ connection->Accept();
+
+ // Accept another connection
+ DoAccept();
+ }
+}
+
+std::unique_ptr MtServer::ParseMessage(const std::string& msg)
+{
+ std::unique_ptr command;
+
+ using namespace boost::spirit;
+ using rule_s = qi::rule;
+ using rule = qi::rule;
+
+ std::string message_type;
+ std::string expert_handle;
+ std::string command_id;
+ std::string command_type;
+ std::string payload;
+
+ rule_s word_p = qi::as_string[+(qi::char_ - qi::char_(';'))];
+ rule message_type_p = word_p[boost::phoenix::ref(message_type) = qi::_1];
+ rule expert_handle_p = +qi::char_(';') >> word_p[boost::phoenix::ref(expert_handle) = qi::_1];
+ rule command_id_p = +qi::char_(';') >> word_p[boost::phoenix::ref(command_id) = qi::_1];
+ rule command_type_p = +qi::char_(';') >> word_p[boost::phoenix::ref(command_type) = qi::_1];
+ rule payload_p = +qi::char_(';') >> word_p[boost::phoenix::ref(payload) = qi::_1];
+
+ bool ok = qi::parse(msg.begin(), msg.end(), message_type_p >> -expert_handle_p >> -command_id_p >> -command_type_p >> -payload_p);
+ if (ok)
+ {
+ command = std::make_unique(std::stoi(expert_handle),
+ std::stoi(command_id), std::stoi(command_type), std::move(payload));
+ }
+
+ return command;
+}
+
+// Start accepting incoming connections
+void MtServer::Start()
+{
+ log_.Debug("%s: entry. port = %d", __FUNCTION__, port_);
+
+ DoAccept();
+}
+
+void MtServer::Stop()
+{
+ log_.Debug("%s: entry. port = %d", __FUNCTION__, port_);
+
+ for(auto& c : connections_)
+ c->Close();
+
+ connections_.clear();
+ pending_connections_.clear();
+
+ acceptor_.cancel();
+
+ OnStopped(port_);
+}
+
+void MtServer::OnEvent(const MtEvent& event)
+{
+ log_.Trace("%s: entry.", __FUNCTION__);
+
+ Send(event);
+}
+
+void MtServer::OnDeinitExpert(int handle)
+{
+ log_.Debug("%s: entry. handle = %d", __FUNCTION__, handle);
+
+ if (experts_.count(handle) > 0)
+ {
+ log_.Debug("%s: remove expert %d from collection", __FUNCTION__, handle);
+ experts_.erase(handle);
+ Send(MtExpertRemovedMsg(handle));
+ }
+
+ if (experts_.size() == 0)
+ {
+ log_.Debug("%s: expert count = 0. Starting stop timer (1 sec) ...", __FUNCTION__, handle);
+ stop_timer_.expires_from_now(boost::asio::chrono::seconds(STOP_EXPERT_INTERVAL));
+ stop_timer_.async_wait([this](const boost::system::error_code& /*ec*/) {
+ if (experts_.size() == 0)
+ Stop();
+ });
+ }
+}
+
+void MtServer::Send(const MtMessage& message)
+{
+ auto msg = message.Serialize();
+ log_.Trace("%s: %s", __FUNCTION__, msg.c_str());
+ for (auto& c : connections_)
+ c->Send(msg);
+}
\ No newline at end of file
diff --git a/MtService/MtServer.h b/MtService/MtServer.h
new file mode 100755
index 00000000..eebb3422
--- /dev/null
+++ b/MtService/MtServer.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "Logger.h"
+#include "MtMessage.h"
+
+class MtConnection;
+class MtExpert;
+
+struct MtExpertSlot
+{
+ MtExpert* expert;
+ boost::signals2::connection event_connection;
+ boost::signals2::connection deinit_connection;
+
+ ~MtExpertSlot()
+ {
+ event_connection.disconnect();
+ deinit_connection.disconnect();
+ }
+};
+
+class MtServer : public std::enable_shared_from_this
+{
+public:
+ MtServer(
+ boost::asio::io_context& context,
+ unsigned short port);
+ ~MtServer();
+
+ void AddExpert(int handle, MtExpert* expert);
+
+ boost::signals2::signal OnStopped;
+
+private:
+ void Start();
+ void Stop();
+
+ void DoAccept();
+ void OnAccept(boost::beast::error_code ec,
+ boost::asio::ip::tcp::socket socket);
+
+ std::unique_ptr ParseMessage(const std::string& msg);
+
+ void OnEvent(const MtEvent& event);
+ void OnDeinitExpert(int handle);
+
+ void Send(const MtMessage& message);
+
+ Logger log_{ "MtServer" };
+ boost::asio::io_context& context_;
+ unsigned short port_;
+ boost::asio::ip::tcp::acceptor acceptor_;
+ std::vector> pending_connections_;
+ std::vector> connections_;
+ std::unordered_map> experts_;
+ boost::asio::steady_timer stop_timer_;
+};
\ No newline at end of file
diff --git a/MtService/MtService.cpp b/MtService/MtService.cpp
new file mode 100755
index 00000000..acec9d60
--- /dev/null
+++ b/MtService/MtService.cpp
@@ -0,0 +1,224 @@
+// MtService.cpp : Defines the functions for the static library.
+//
+
+#include "pch.h"
+#include "framework.h"
+#include "MtService.h"
+
+#include "MtExpert.h"
+#include "LogConfigurator.h"
+#include "MtServer.h"
+
+#include
+#include
+#include
+
+//--------------------------------------------------------------------------------
+// MtService
+//--------------------------------------------------------------------------------
+
+class MtServiceImpl
+{
+public:
+ MtServiceImpl();
+ ~MtServiceImpl();
+
+ void InitExpert(int port, int handle, std::unique_ptr mt_handler);
+ void DeinitExpert(int handle);
+
+ void SendEvent(int handle, int event_type, const std::string& payload);
+ void SendResponse(int handle, const std::string& payload);
+
+ int GetCommandType(int handle);
+ std::string GetCommandPayload(int handle);
+
+ void LogError(const std::string& error);
+
+private:
+ void OnServerStopped(unsigned short port);
+ void ThreadProc();
+
+ Logger log_{ "MtService" };
+ boost::asio::io_context context_;
+ std::unique_ptr work_;
+ std::thread thread_;
+
+ std::unordered_map> experts_;
+ std::unordered_map> servers_;
+};
+
+MtServiceImpl::MtServiceImpl()
+ : work_(new boost::asio::io_context::work(context_))
+ , thread_(&MtServiceImpl::ThreadProc, this)
+{
+ log_.Debug("%s: service created", __FUNCTION__);
+}
+
+MtServiceImpl::~MtServiceImpl()
+{
+ log_.Trace("%s: destructor started", __FUNCTION__);
+
+ boost::asio::post(context_, [this]() {
+ if (experts_.size() > 0)
+ {
+ for (auto& e : experts_)
+ e.second->Deinit();
+ experts_.clear();
+ }
+ });
+ work_.reset();
+ if (thread_.joinable())
+ thread_.join();
+
+ log_.Debug("%s: service destroyed", __FUNCTION__);
+}
+
+void MtServiceImpl::InitExpert(int port, int handle, std::unique_ptr mt_handler)
+{
+ log_.Debug("%s: port = %d, handle = %d", __FUNCTION__, port, handle);
+
+ boost::asio::post(context_, [port, handle, mt_h = std::move(mt_handler), this]() mutable {
+ auto expert = std::make_unique(handle, std::move(mt_h));
+ if (servers_.count(port) == 0)
+ {
+ auto server = std::make_shared(context_, port);
+ server->OnStopped.connect(boost::bind(&MtServiceImpl::OnServerStopped, this, std::placeholders::_1));
+ servers_[port] = server;
+ }
+
+ servers_[port]->AddExpert(handle, expert.get());
+ experts_[handle] = std::move(expert);
+ });
+}
+
+void MtServiceImpl::DeinitExpert(int handle)
+{
+ log_.Debug("%s: handle = %d", __FUNCTION__, handle);
+
+ boost::asio::post(context_, [handle, this]() {
+ if (experts_.count(handle) > 0)
+ {
+ experts_[handle]->Deinit();
+ experts_.erase(handle);
+ }
+ });
+}
+
+void MtServiceImpl::SendEvent(int handle, int event_type, const std::string& payload)
+{
+ log_.Trace("%s: handle = %d, event_type = %d, payload = %s", __FUNCTION__, handle, event_type, payload.c_str());
+
+ boost::asio::post(context_, [handle, event_type, payload = payload, this]() {
+ if (experts_.count(handle) > 0)
+ experts_[handle]->SendEvent(event_type, payload);
+ });
+}
+
+void MtServiceImpl::SendResponse(int handle, const std::string& payload)
+{
+ log_.Trace("%s: handle = %d, payload = %s", __FUNCTION__, handle, payload.c_str());
+
+ boost::asio::post(context_, [handle, payload = payload, this]() {
+ if (experts_.count(handle) > 0)
+ experts_[handle]->SendResponse(payload);
+ });
+}
+
+int MtServiceImpl::GetCommandType(int handle)
+{
+ log_.Trace("%s: handle = %d", __FUNCTION__, handle);
+
+ std::packaged_task task([handle, this]() {
+ return (experts_.count(handle) > 0) ? experts_[handle]->GetCommandType() : 0;
+ });
+ auto f = task.get_future();
+ boost::asio::post(context_, std::bind(std::move(task)));
+ return f.get();
+}
+
+std::string MtServiceImpl::GetCommandPayload(int handle)
+{
+ log_.Trace("%s: handle = %d", __FUNCTION__, handle);
+
+ std::packaged_task task([handle, this]() {
+ return (experts_.count(handle) > 0) ? experts_[handle]->GetCommandPayload() : 0;
+ });
+ auto f = task.get_future();
+ boost::asio::post(context_, std::bind(std::move(task)));
+ return f.get();
+}
+
+void MtServiceImpl::LogError(const std::string& error)
+{
+ log_.Error("%s: %s", __FUNCTION__, error.c_str());
+}
+
+void MtServiceImpl::OnServerStopped(unsigned short port)
+{
+ log_.Trace("%s: port = %d", __FUNCTION__, port);
+
+ if (servers_.count(port) > 0)
+ servers_.erase(port);
+}
+
+void MtServiceImpl::ThreadProc()
+{
+ log_.Debug("%s: started", __FUNCTION__);
+ context_.run();
+ log_.Debug("%s: stopped", __FUNCTION__);
+}
+
+//--------------------------------------------------------------------------------
+// MtService
+//--------------------------------------------------------------------------------
+
+MtService::MtService()
+{
+ LogConfigurator::Setup(LogLevel::Trace, OutputType::Console | OutputType::File, "MtApiService");
+ impl_ = std::make_unique();
+}
+
+MtService::~MtService()
+{
+}
+
+MtService& MtService::GetInstance()
+{
+ static MtService instance;
+ return instance;
+}
+
+void MtService::InitExpert(int port, int handle, std::unique_ptr mt_handler)
+{
+ impl_->InitExpert(port, handle, std::move(mt_handler));
+}
+
+void MtService::DeinitExpert(int handle)
+{
+ impl_->DeinitExpert(handle);
+}
+
+void MtService::SendEvent(int handle, int event_type, const std::string& payload)
+{
+ impl_->SendEvent(handle, event_type, payload);
+}
+
+void MtService::SendResponse(int handle, const std::string& payload)
+{
+ impl_->SendResponse(handle, payload);
+}
+
+int MtService::GetCommandType(int handle)
+{
+ return impl_->GetCommandType(handle);
+}
+
+std::string MtService::GetCommandPayload(int handle)
+{
+ return impl_->GetCommandPayload(handle);
+}
+
+void MtService::LogError(const std::string& error)
+{
+ impl_->LogError(error);
+}
\ No newline at end of file
diff --git a/MtService/MtService.h b/MtService/MtService.h
new file mode 100755
index 00000000..c3ba2fdb
--- /dev/null
+++ b/MtService/MtService.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include
+#include
+
+#include "MetaTraderHandler.h"
+
+class MtServiceImpl;
+
+class MtService
+{
+private:
+ MtService();
+ ~MtService();
+
+public:
+ static MtService& GetInstance();
+
+ void InitExpert(int port, int handle, std::unique_ptr mt_handler);
+ void DeinitExpert(int handle);
+
+ void SendEvent(int handle, int event_type, const std::string& payload);
+ void SendResponse(int handle, const std::string& payload);
+
+ int GetCommandType(int handle);
+ std::string GetCommandPayload(int handle);
+
+ void LogError(const std::string& error);
+
+private:
+ std::unique_ptr impl_;
+};
\ No newline at end of file
diff --git a/MtService/MtService.vcxproj b/MtService/MtService.vcxproj
new file mode 100755
index 00000000..d50e4e44
--- /dev/null
+++ b/MtService/MtService.vcxproj
@@ -0,0 +1,175 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 17.0
+ Win32Proj
+ {3effff03-2a1d-48aa-a49a-9f90889f31e0}
+ MtService
+ 10.0
+
+
+
+ StaticLibrary
+ true
+ v143
+ Unicode
+
+
+ StaticLibrary
+ false
+ v143
+ true
+ Unicode
+
+
+ StaticLibrary
+ true
+ v143
+ Unicode
+
+
+ StaticLibrary
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ Use
+ pch.h
+
+
+
+
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ Use
+ pch.h
+
+
+
+
+ true
+ true
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ Use
+ pch.h
+ C:\Users\vdemy\source\repos\MtService\MtService;C:\Users\vdemy\source\repos\MtService\thirdparty\include;%(AdditionalIncludeDirectories)
+ Default
+
+
+
+
+ true
+
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ Use
+ pch.h
+
+
+
+
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create
+ Create
+ Create
+ Create
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MtService/MtService.vcxproj.filters b/MtService/MtService.vcxproj.filters
new file mode 100755
index 00000000..bada8a25
--- /dev/null
+++ b/MtService/MtService.vcxproj.filters
@@ -0,0 +1,78 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
\ No newline at end of file
diff --git a/MtService/framework.h b/MtService/framework.h
new file mode 100755
index 00000000..880eb720
--- /dev/null
+++ b/MtService/framework.h
@@ -0,0 +1,3 @@
+#pragma once
+
+#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
diff --git a/MtService/pch.cpp b/MtService/pch.cpp
new file mode 100755
index 00000000..91c22df2
--- /dev/null
+++ b/MtService/pch.cpp
@@ -0,0 +1,5 @@
+// pch.cpp: source file corresponding to the pre-compiled header
+
+#include "pch.h"
+
+// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.
diff --git a/MtService/pch.h b/MtService/pch.h
new file mode 100755
index 00000000..37f3b744
--- /dev/null
+++ b/MtService/pch.h
@@ -0,0 +1,14 @@
+// pch.h: This is a precompiled header file.
+// Files listed below are compiled only once, improving build performance for future builds.
+// This also affects IntelliSense performance, including code completion and many code browsing features.
+// However, files listed here are ALL re-compiled if any one of them is updated between builds.
+// Do not add files here that you will be updating frequently as this negates the performance advantage.
+
+#ifndef PCH_H
+#define PCH_H
+
+// add headers that you want to pre-compile here
+#include "framework.h"
+#include
+
+#endif //PCH_H