MtApi/MtApi5: updated logic of connection with requesting expert list and quotes

This commit is contained in:
Viacheslav Demydiuk
2024-12-21 23:12:59 +02:00
parent b89f00d310
commit ba1754d59b
3 changed files with 128 additions and 143 deletions
+44 -50
View File
@@ -27,7 +27,6 @@ namespace MtApi
private readonly Dictionary<MtEventTypes, Action<int, string>> _mtEventHandlers = []; private readonly Dictionary<MtEventTypes, Action<int, string>> _mtEventHandlers = [];
private HashSet<int> _experts = []; private HashSet<int> _experts = [];
private Dictionary<int, MtQuote> _quotes = []; private Dictionary<int, MtQuote> _quotes = [];
private readonly EventWaitHandle _quotesWaiter = new AutoResetEvent(false);
private MtConnectionState _connectionState = MtConnectionState.Disconnected; private MtConnectionState _connectionState = MtConnectionState.Disconnected;
private int _executorHandle; private int _executorHandle;
@@ -83,7 +82,6 @@ namespace MtApi
///</summary> ///</summary>
public List<MtQuote> GetQuotes() public List<MtQuote> GetQuotes()
{ {
_quotesWaiter.WaitOne(10000); // wait 10 sec for loading all quotes from MetaTrader
lock (_locker) lock (_locker)
{ {
return _quotes.Values.ToList(); return _quotes.Values.ToList();
@@ -3041,7 +3039,6 @@ namespace MtApi
ConnectionStateChanged?.Invoke(this, new MtConnectionEventArgs(MtConnectionState.Connecting, message)); ConnectionStateChanged?.Invoke(this, new MtConnectionEventArgs(MtConnectionState.Connecting, message));
var client = new MtRpcClient(host, port, new RpcClientLogger(Log)); var client = new MtRpcClient(host, port, new RpcClientLogger(Log));
client.ExpertList += Client_ExpertList;
client.ExpertAdded += Client_ExpertAdded; client.ExpertAdded += Client_ExpertAdded;
client.ExpertRemoved += Client_ExpertRemoved; client.ExpertRemoved += Client_ExpertRemoved;
client.MtEventReceived += Client_MtEventReceived; client.MtEventReceived += Client_MtEventReceived;
@@ -3052,13 +3049,44 @@ namespace MtApi
{ {
await client.Connect(); await client.Connect();
Log.Info($"Connected to {host}:{port}"); Log.Info($"Connected to {host}:{port}");
var experts = client.RequestExpertsList();
if (experts == null || experts.Count == 0)
{
var errorMessage = "Failed to load expert list";
Log.Error(errorMessage);
client.Disconnect();
ConnectionStateChanged?.Invoke(this, new MtConnectionEventArgs(MtConnectionState.Failed, errorMessage));
throw new Exception($"Connection to {host}:{port} failed. Error: {errorMessage}");
}
// Load quotes
Dictionary<int, MtQuote> quotes = [];
foreach (var handle in experts)
{
var quote = GetQuote(client, handle);
if (quote != null)
quotes[handle] = quote;
}
lock (_locker) lock (_locker)
{ {
_client = client; _client = client;
_experts = experts;
_quotes = quotes;
if (_executorHandle == 0)
_executorHandle = (_experts.Count > 0) ? _experts.ElementAt(0) : 0;
_connectionState = MtConnectionState.Connected; _connectionState = MtConnectionState.Connected;
} }
client.NotifyClientReady();
if (IsTesting())
{
BacktestingReady();
}
ConnectionStateChanged?.Invoke(this, new MtConnectionEventArgs(MtConnectionState.Connected, $"Connected to {host}:{port}")); ConnectionStateChanged?.Invoke(this, new MtConnectionEventArgs(MtConnectionState.Connected, $"Connected to {host}:{port}"));
QuoteList?.Invoke(this, new(quotes.Values.ToList()));
} }
catch (Exception e) catch (Exception e)
{ {
@@ -3102,7 +3130,11 @@ namespace MtApi
private T? SendCommand<T>(int expertHandle, MtCommandType commandType, object? payload = null) private T? SendCommand<T>(int expertHandle, MtCommandType commandType, object? payload = null)
{ {
var client = Client; return SendCommand<T>(Client, expertHandle, commandType, payload);
}
private T? SendCommand<T>(MtRpcClient? client, int expertHandle, MtCommandType commandType, object? payload = null)
{
if (client == null) if (client == null)
{ {
Log.Warn("SendCommand: No connection"); Log.Warn("SendCommand: No connection");
@@ -3143,11 +3175,6 @@ namespace MtApi
Task.Run(() => _mtEventHandlers[(MtEventTypes)e.EventType](e.ExpertHandle, e.Payload)); Task.Run(() => _mtEventHandlers[(MtEventTypes)e.EventType](e.ExpertHandle, e.Payload));
} }
private void Client_ExpertList(object? sender, MtExpertListEventArgs e)
{
Task.Run(() => ProcessExpertList(e.Experts));
}
private void Client_ExpertAdded(object? sender, MtExpertEventArgs e) private void Client_ExpertAdded(object? sender, MtExpertEventArgs e)
{ {
Task.Run(() => ProcessExpertAdded(e.Expert)); Task.Run(() => ProcessExpertAdded(e.Expert));
@@ -3213,39 +3240,6 @@ namespace MtApi
OnLockTicks?.Invoke(this, new MtLockTicksEventArgs(expertHandle, e.Instrument)); OnLockTicks?.Invoke(this, new MtLockTicksEventArgs(expertHandle, e.Instrument));
} }
private void ProcessExpertList(HashSet<int> experts)
{
if (experts == null || experts.Count == 0)
{
Log.Warn("ProcessExpertList: expert list invalid or empty");
return;
}
Dictionary<int, MtQuote> quotes = [];
foreach (var handle in experts)
{
var quote = GetQuote(handle);
if (quote != null)
quotes[handle] = quote;
}
lock (_locker)
{
_experts = experts;
_quotes = quotes;
if (_executorHandle == 0)
_executorHandle = (_experts.Count > 0) ? _experts.ElementAt(0) : 0;
}
_quotesWaiter.Set();
QuoteList?.Invoke(this, new(quotes.Values.ToList()));
if (IsTesting())
{
BacktestingReady();
}
}
private void ProcessExpertAdded(int handle) private void ProcessExpertAdded(int handle)
{ {
Log.Debug($"ProcessExpertAdded: {handle}"); Log.Debug($"ProcessExpertAdded: {handle}");
@@ -3260,7 +3254,7 @@ namespace MtApi
if (added) if (added)
{ {
var quote = GetQuote(handle); var quote = GetQuote(Client, handle);
if (quote != null) if (quote != null)
{ {
lock (_locker) lock (_locker)
@@ -3295,19 +3289,19 @@ namespace MtApi
QuoteRemoved?.Invoke(this, new MtQuoteEventArgs(quote)); QuoteRemoved?.Invoke(this, new MtQuoteEventArgs(quote));
} }
private MtQuote? GetQuote(int expertHandle) private MtQuote? GetQuote(MtRpcClient? client, int expertHandle)
{ {
Log.Debug($"GetQuote: expertHandle = {expertHandle}"); Log.Debug($"GetQuote: expertHandle = {expertHandle}");
var e = SendCommand<MtRpcQuote>(expertHandle, MtCommandType.GetQuote); var q = SendCommand<MtRpcQuote>(client, expertHandle, MtCommandType.GetQuote);
if (e == null || string.IsNullOrEmpty(e.Instrument) || e.Tick == null) if (q == null || string.IsNullOrEmpty(q.Instrument) || q.Tick == null)
return null; return null;
MtQuote quote = new() MtQuote quote = new()
{ {
Instrument = e.Instrument, Instrument = q.Instrument,
Bid = e.Tick.Bid, Bid = q.Tick.Bid,
Ask = e.Tick.Ask, Ask = q.Tick.Ask,
ExpertHandle = expertHandle, ExpertHandle = expertHandle,
}; };
+46 -60
View File
@@ -31,7 +31,6 @@ namespace MtApi5
private HashSet<int> _experts = []; private HashSet<int> _experts = [];
private Dictionary<int, Mt5Quote> _quotes = []; private Dictionary<int, Mt5Quote> _quotes = [];
private readonly EventWaitHandle _quotesWaiter = new AutoResetEvent(false);
#endregion #endregion
#region Public Methods #region Public Methods
@@ -94,17 +93,13 @@ namespace MtApi5
{ {
if (_connectionState == Mt5ConnectionState.Connected if (_connectionState == Mt5ConnectionState.Connected
|| _connectionState == Mt5ConnectionState.Connecting) || _connectionState == Mt5ConnectionState.Connecting)
{
return; return;
}
_connectionState = Mt5ConnectionState.Connecting; _connectionState = Mt5ConnectionState.Connecting;
} }
ConnectionStateChanged?.Invoke(this, new Mt5ConnectionEventArgs(Mt5ConnectionState.Connecting, $"Connecting to {host}:{port}")); ConnectionStateChanged?.Invoke(this, new Mt5ConnectionEventArgs(Mt5ConnectionState.Connecting, $"Connecting to {host}:{port}"));
var client = new MtRpcClient(host, port, new RpcClientLogger(Log)); var client = new MtRpcClient(host, port, new RpcClientLogger(Log));
client.ExpertList += Client_ExpertList;
client.ExpertAdded += Client_ExpertAdded; client.ExpertAdded += Client_ExpertAdded;
client.ExpertRemoved += Client_ExpertRemoved; client.ExpertRemoved += Client_ExpertRemoved;
client.MtEventReceived += Client_MtEventReceived; client.MtEventReceived += Client_MtEventReceived;
@@ -115,13 +110,42 @@ namespace MtApi5
{ {
await client.Connect(); await client.Connect();
Log.Info($"Connected to {host}:{port}"); Log.Info($"Connected to {host}:{port}");
var experts = client.RequestExpertsList();
if (experts == null || experts.Count == 0)
{
var errorMessage = "Failed to load expert list";
Log.Error(errorMessage);
client.Disconnect();
ConnectionStateChanged?.Invoke(this, new Mt5ConnectionEventArgs(Mt5ConnectionState.Failed, errorMessage));
throw new Exception($"Connection to {host}:{port} failed. Error: {errorMessage}");
}
// Load quotes
Dictionary<int, Mt5Quote> quotes = [];
foreach (var handle in experts)
{
var quote = GetQuote(client, handle);
if (quote != null)
quotes[handle] = quote;
}
lock (_locker) lock (_locker)
{ {
_client = client; _client = client;
_experts = experts;
_quotes = quotes;
if (_executorHandle == 0)
_executorHandle = (_experts.Count > 0) ? _experts.ElementAt(0) : 0;
_connectionState = Mt5ConnectionState.Connected; _connectionState = Mt5ConnectionState.Connected;
} }
client.NotifyClientReady();
if (IsTesting())
BacktestingReady();
ConnectionStateChanged?.Invoke(this, new Mt5ConnectionEventArgs(Mt5ConnectionState.Connected, $"Connected to {host}:{port}")); ConnectionStateChanged?.Invoke(this, new Mt5ConnectionEventArgs(Mt5ConnectionState.Connected, $"Connected to {host}:{port}"));
QuoteList?.Invoke(this, new(quotes.Values.ToList()));
} }
catch (Exception e) catch (Exception e)
{ {
@@ -154,14 +178,10 @@ namespace MtApi5
} }
///<summary> ///<summary>
///Load quotes connected into MetaTrader API (deprecated) ///Load quotes connected into MetaTrader API
///</summary> ///</summary>
public IEnumerable<Mt5Quote> GetQuotes() public IEnumerable<Mt5Quote> GetQuotes()
{ {
// this function is deprecated.
// should be used event OnQuoteList
_quotesWaiter.WaitOne(10000); // wait 10 sec for loading all quotes from MetaTrader
lock (_locker) lock (_locker)
{ {
return _quotes.Values.ToList(); return _quotes.Values.ToList();
@@ -3345,11 +3365,6 @@ namespace MtApi5
Task.Run(() => _mtEventHandlers[(Mt5EventTypes)e.EventType](e.ExpertHandle, e.Payload)); Task.Run(() => _mtEventHandlers[(Mt5EventTypes)e.EventType](e.ExpertHandle, e.Payload));
} }
private void Client_ExpertList(object? sender, MtExpertListEventArgs e)
{
Task.Run(()=>ProcessExpertList(e.Experts));
}
private void Client_ExpertAdded(object? sender, MtExpertEventArgs e) private void Client_ExpertAdded(object? sender, MtExpertEventArgs e)
{ {
Task.Run(() => ProcessExpertAdded(e.Expert)); Task.Run(() => ProcessExpertAdded(e.Expert));
@@ -3360,39 +3375,6 @@ namespace MtApi5
Task.Run(() => ProcessExpertRemoved(e.Expert)); Task.Run(() => ProcessExpertRemoved(e.Expert));
} }
private void ProcessExpertList(HashSet<int> experts)
{
if (experts == null || experts.Count == 0)
{
Log.Warn("ProcessExpertList: expert list invalid or empty");
return;
}
Dictionary<int, Mt5Quote> quotes = [];
foreach (var handle in experts)
{
var quote = GetQuote(handle);
if (quote != null)
quotes[handle] = quote;
}
lock (_locker)
{
_experts = experts;
_quotes = quotes;
if (_executorHandle == 0)
_executorHandle = (_experts.Count > 0) ? _experts.ElementAt(0) : 0;
}
_quotesWaiter.Set();
QuoteList?.Invoke(this, new(quotes.Values.ToList()));
if (IsTesting())
{
BacktestingReady();
}
}
private void ProcessExpertAdded(int handle) private void ProcessExpertAdded(int handle)
{ {
Log.Debug($"ProcessExpertAdded: {handle}"); Log.Debug($"ProcessExpertAdded: {handle}");
@@ -3407,7 +3389,7 @@ namespace MtApi5
if (added) if (added)
{ {
var quote = GetQuote(handle); var quote = GetQuote(Client, handle);
if (quote != null) if (quote != null)
{ {
lock (_locker) lock (_locker)
@@ -3444,23 +3426,23 @@ namespace MtApi5
QuoteRemoved?.Invoke(this, new Mt5QuoteEventArgs(quote)); QuoteRemoved?.Invoke(this, new Mt5QuoteEventArgs(quote));
} }
private Mt5Quote? GetQuote(int expertHandle) private Mt5Quote? GetQuote(MtRpcClient? client, int expertHandle)
{ {
Log.Debug($"GetQuote: expertHandle = {expertHandle}"); Log.Debug($"GetQuote: expertHandle = {expertHandle}");
var e = SendCommand<MtQuote>(expertHandle, Mt5CommandType.GetQuote); var q = SendCommand<MtQuote>(client, expertHandle, Mt5CommandType.GetQuote);
if (e == null || string.IsNullOrEmpty(e.Instrument) || e.Tick == null) if (q == null || string.IsNullOrEmpty(q.Instrument) || q.Tick == null)
return null; return null;
Mt5Quote quote = new() Mt5Quote quote = new()
{ {
Instrument = e.Instrument, Instrument = q.Instrument,
Bid = e.Tick.Bid, Bid = q.Tick.Bid,
Ask = e.Tick.Ask, Ask = q.Tick.Ask,
ExpertHandle = expertHandle, ExpertHandle = expertHandle,
Volume = e.Tick.Volume, Volume = q.Tick.Volume,
Time = Mt5TimeConverter.ConvertFromMtTime(e.Tick.Time), Time = Mt5TimeConverter.ConvertFromMtTime(q.Tick.Time),
Last = e.Tick.Last Last = q.Tick.Last
}; };
return quote; return quote;
@@ -3572,7 +3554,11 @@ namespace MtApi5
private T? SendCommand<T>(int expertHandle, Mt5CommandType commandType, object? payload = null) private T? SendCommand<T>(int expertHandle, Mt5CommandType commandType, object? payload = null)
{ {
var client = Client; return SendCommand<T>(Client, expertHandle, commandType, payload);
}
private T? SendCommand<T>(MtRpcClient? client, int expertHandle, Mt5CommandType commandType, object? payload = null)
{
if (client == null) if (client == null)
{ {
Log.Warn("SendCommand: No connection"); Log.Warn("SendCommand: No connection");
+38 -33
View File
@@ -1,5 +1,4 @@
using System.Diagnostics; using System.Net.WebSockets;
using System.Net.WebSockets;
using System.Text; using System.Text;
namespace MtClient namespace MtClient
@@ -68,7 +67,7 @@ namespace MtClient
public string? SendCommand(int expertHandle, int commandType, string payload) public string? SendCommand(int expertHandle, int commandType, string payload)
{ {
CommandTask commandTask = new(); CommandTask<string> commandTask = new();
int commandId; int commandId;
lock (tasks_) lock (tasks_)
{ {
@@ -88,10 +87,24 @@ namespace MtClient
return response; return response;
} }
public void NotifyClientReady() public HashSet<int>? RequestExpertsList()
{ {
CommandTask<object> notificationTask = new();
lock (notification_tasks_)
{
notification_tasks_[MtNotificationType.ClientReady] = notificationTask;
}
MtNotification notification = new(MtNotificationType.ClientReady); MtNotification notification = new(MtNotificationType.ClientReady);
Send(notification); Send(notification);
var response = notificationTask.WaitResponse(10000); // 10 sec
lock (notification_tasks_)
{
notification_tasks_.Remove(MtNotificationType.ClientReady);
}
return response as HashSet<int>;
} }
private void Send(MtMessage message) private void Send(MtMessage message)
@@ -118,7 +131,7 @@ namespace MtClient
{ {
sendWaiter_.WaitOne(); sendWaiter_.WaitOne();
continue; continue;
} }
try try
{ {
@@ -166,30 +179,12 @@ namespace MtClient
else if (result.MessageType == WebSocketMessageType.Text) else if (result.MessageType == WebSocketMessageType.Text)
{ {
var msg = Encoding.UTF8.GetString(ms.ToArray(), 0, recvCount); var msg = Encoding.UTF8.GetString(ms.ToArray(), 0, recvCount);
//var msg = Encoding.ASCII.GetString(recvBuffer, 0, result.Count);
OnReceive(msg); OnReceive(msg);
} }
ms.Seek(0, SeekOrigin.Begin); ms.Seek(0, SeekOrigin.Begin);
ms.Position = 0; ms.Position = 0;
} }
} }
//byte[] recvBuffer = new byte[64 * 1024];
//while (ws_.State == WebSocketState.Open)
//{
// var result = await ws_.ReceiveAsync(new ArraySegment<byte>(recvBuffer), CancellationToken.None);
// if (result.MessageType == WebSocketMessageType.Close)
// {
// logger_.Info($"MtRpcClient.DoReceive: close signal {result.CloseStatusDescription}");
// Disconnected?.Invoke(this, EventArgs.Empty);
// break;
// }
// else
// {
// var msg = Encoding.ASCII.GetString(recvBuffer, 0, result.Count);
// OnReceive(msg);
// }
//}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -216,7 +211,7 @@ namespace MtClient
{ {
logger_.Warn("MtRpcClient.OnReceive: Invalid message format."); logger_.Warn("MtRpcClient.OnReceive: Invalid message format.");
return; return;
} }
MessageType msgType; MessageType msgType;
try try
@@ -250,7 +245,7 @@ namespace MtClient
break; break;
case MessageType.ExpertList: case MessageType.ExpertList:
if (message is MtExpertListMsg expertListMsg) if (message is MtExpertListMsg expertListMsg)
ExpertList?.Invoke(this, new(expertListMsg.Experts)); ProcessExpertList(expertListMsg.Experts);
break; break;
case MessageType.ExpertAdded: case MessageType.ExpertAdded:
if (message is MtExpertAddedMsg expertAddedMsg) if (message is MtExpertAddedMsg expertAddedMsg)
@@ -273,14 +268,24 @@ namespace MtClient
lock (tasks_) lock (tasks_)
{ {
if (tasks_.TryGetValue(commandId, out CommandTask? value)) if (tasks_.TryGetValue(commandId, out CommandTask<string>? value))
value.SetResponse(payload); value.SetResponse(payload);
} }
} }
private void ProcessExpertList(HashSet<int> experts)
{
logger_.Debug($"MtRpcClient.ProcessExpertList: experts count - {experts.Count}");
lock (notification_tasks_)
{
if (notification_tasks_.TryGetValue(MtNotificationType.ClientReady, out CommandTask<object>? value))
value.SetResponse(experts);
}
}
public event EventHandler<EventArgs>? ConnectionFailed; public event EventHandler<EventArgs>? ConnectionFailed;
public event EventHandler<EventArgs>? Disconnected; public event EventHandler<EventArgs>? Disconnected;
public event EventHandler<MtExpertListEventArgs>? ExpertList;
public event EventHandler<MtExpertEventArgs>? ExpertAdded; public event EventHandler<MtExpertEventArgs>? ExpertAdded;
public event EventHandler<MtExpertEventArgs>? ExpertRemoved; public event EventHandler<MtExpertEventArgs>? ExpertRemoved;
public event EventHandler<MtEventArgs>? MtEventReceived; public event EventHandler<MtEventArgs>? MtEventReceived;
@@ -288,7 +293,6 @@ namespace MtClient
private readonly ClientWebSocket ws_ = new(); private readonly ClientWebSocket ws_ = new();
private readonly string host_; private readonly string host_;
private readonly int port_; private readonly int port_;
private readonly byte[] buf_ = new byte[10000];
private readonly Queue<MtMessage> pendingMessages_ = []; private readonly Queue<MtMessage> pendingMessages_ = [];
private readonly Thread receiveThread_; private readonly Thread receiveThread_;
@@ -296,18 +300,19 @@ namespace MtClient
private readonly EventWaitHandle sendWaiter_ = new AutoResetEvent(false); private readonly EventWaitHandle sendWaiter_ = new AutoResetEvent(false);
private int nextCommandId = 0; private int nextCommandId = 0;
private readonly Dictionary<int, CommandTask> tasks_ = []; private readonly Dictionary<int, CommandTask<string>> tasks_ = [];
private readonly Dictionary<MtNotificationType, CommandTask<object>> notification_tasks_ = [];
private readonly IRpcLogger logger_; private readonly IRpcLogger logger_;
} }
internal class CommandTask internal class CommandTask<T>
{ {
private readonly EventWaitHandle responseWaiter_ = new AutoResetEvent(false); private readonly EventWaitHandle responseWaiter_ = new AutoResetEvent(false);
private string? response_; private T? response_;
private readonly object locker_ = new(); private readonly object locker_ = new();
public string? WaitResponse(int time) public T? WaitResponse(int time)
{ {
responseWaiter_.WaitOne(time); responseWaiter_.WaitOne(time);
lock (locker_) lock (locker_)
@@ -316,7 +321,7 @@ namespace MtClient
} }
} }
public void SetResponse(string result) public void SetResponse(T result)
{ {
lock (locker_) lock (locker_)
{ {