diff --git a/MtApi/ExtensionMethods.cs b/MtApi/ExtensionMethods.cs index fa33115c..f96146b6 100755 --- a/MtApi/ExtensionMethods.cs +++ b/MtApi/ExtensionMethods.cs @@ -1,7 +1,39 @@ -namespace MtApi +using System; +using System.Threading.Tasks; + +namespace MtApi { static class ExtensionMethods { + #region Event Methods + + public static Task FireEventAsync(this MtApiQuoteHandler evenHandler, object sender, string symbol, double bid, double ask) + { + return Task.Factory.StartNew(() => + { + evenHandler?.Invoke(sender, symbol, bid, ask); + }); + } + + public static Task FireEventAsync(this EventHandler eventHandler, object sender) + { + return Task.Factory.StartNew(() => + { + eventHandler?.Invoke(sender, EventArgs.Empty); + }); + } + + public static Task FireEventAsync(this EventHandler eventHandler, object sender, T e) + where T : EventArgs + { + return Task.Factory.StartNew(() => + { + eventHandler?.Invoke(sender, e); + }); + } + + #endregion + public static MtQuote Convert(this MTApiService.MtQuote quote) { return (quote != null) ? new MtQuote(quote.Instrument, quote.Bid, quote.Ask) : null; diff --git a/MtApi/Monitors/AvailabilityOrdersEventArgs.cs b/MtApi/Monitors/AvailabilityOrdersEventArgs.cs new file mode 100644 index 00000000..f6bea3c8 --- /dev/null +++ b/MtApi/Monitors/AvailabilityOrdersEventArgs.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace MtApi.Monitors +{ + public class AvailabilityOrdersEventArgs : EventArgs + { + public AvailabilityOrdersEventArgs(List opened, List closed) + { + Opened = opened; + Closed = closed; + } + + public List Opened { get; private set; } + public List Closed { get; private set; } + } +} diff --git a/MtApi/Monitors/TimeframeTradeMonitor.cs b/MtApi/Monitors/TimeframeTradeMonitor.cs new file mode 100644 index 00000000..6a172f9a --- /dev/null +++ b/MtApi/Monitors/TimeframeTradeMonitor.cs @@ -0,0 +1,61 @@ + +namespace MtApi.Monitors +{ + public class TimeframeTradeMonitor : TradeMonitor + { + #region Fields + private volatile bool _isStarted = false; + #endregion + + #region ctor + public TimeframeTradeMonitor(MtApiClient apiClient) + : base(apiClient) + { + apiClient.OnLastTimeBar += ApiClient_OnLastTimeBar; + } + #endregion + + #region Public Methods + // + // Summary: + // Gets a value indicating whether the TimeframeTradeMonitor should raise checking orders + // + // Returns: + // true if PositionMonitor should check orders + // otherwise, false. + public override bool IsStarted + { + get + { + return _isStarted; + } + } + #endregion + + #region Protected Methods + protected override void OnMtConnected() {} + + protected override void OnMtDisconnected() {} + + protected override void OnStart() + { + _isStarted = true; + } + + protected override void OnStop() + { + _isStarted = false; + } + #endregion + + #region Private Methods + private void ApiClient_OnLastTimeBar(object sender, TimeBarArgs e) + { + if (_isStarted) + { + Check(); + } + } + #endregion + } +} diff --git a/MtApi/Monitors/TimerTradeMonitor.cs b/MtApi/Monitors/TimerTradeMonitor.cs new file mode 100644 index 00000000..c8b430f2 --- /dev/null +++ b/MtApi/Monitors/TimerTradeMonitor.cs @@ -0,0 +1,90 @@ +using System.Timers; + +namespace MtApi.Monitors +{ + public class TimerTradeMonitor : TradeMonitor + { + #region Fields + private readonly Timer _timer = new Timer(); + #endregion + + #region ctor + public TimerTradeMonitor(MtApiClient apiClient) + : base(apiClient) + { + _timer.Interval = 10000; //default interval 10 sec + _timer.Elapsed += _timer_Elapsed; + + } + #endregion + + #region Public Methods + // + // Summary: + // Gets or sets the interval, expressed in milliseconds, at which to check orders + // + // Returns: + // The time, in milliseconds, between checking events. The value + // must be greater than zero, and less than or equal to System.Int32.MaxValue. + // The default is 10000 milliseconds. + // + // Exceptions: + // T:System.ArgumentException: + // The interval is less than or equal to zero.-or-The interval is greater than System.Int32.MaxValue, + // and the PositionMonitor is currently started. + public double Interval + { + get { return _timer.Interval; } + set { _timer.Interval = value; } + } + + // + // Summary: + // Gets a value indicating whether the PositionMonitor should raise checking orders + // + // Returns: + // true if TimerTradeMonitor should check orders + // otherwise, false. + public override bool IsStarted + { + get { return _timer.Enabled; } + } + #endregion + + #region Protected Methods + protected override void OnStart() + { + if (IsMtConnected) + { + _timer.Start(); + } + } + + protected override void OnStop() + { + _timer.Stop(); + } + + protected override void OnMtConnected() + { + _timer.Start(); + } + + protected override void OnMtDisconnected() + { + _timer.Stop(); + } + #endregion + + #region Private Methods + private void _timer_Elapsed(object sender, ElapsedEventArgs e) + { + _timer.Elapsed -= _timer_Elapsed; //unregister from events to prevent rise condition during work with orders + + Check(); + + _timer.Elapsed += _timer_Elapsed; //register again + } + #endregion + } +} diff --git a/MtApi/Monitors/TradeMonitor.cs b/MtApi/Monitors/TradeMonitor.cs new file mode 100644 index 00000000..d6fe0007 --- /dev/null +++ b/MtApi/Monitors/TradeMonitor.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MtApi.Monitors +{ + public abstract class TradeMonitor + { + #region Fields + private readonly MtApiClient _apiClient; + private List _prevOrders; + private readonly object _locker = new object(); + #endregion + + #region ctor + public TradeMonitor(MtApiClient apiClient) + { + if (apiClient == null) + throw new ArgumentNullException(nameof(apiClient)); + + _apiClient = apiClient; + } + #endregion + + #region Public Methods + // + // Summary: + // Gets a value indicating whether the TradeMonitor should raise checking orders + // + // Returns: + // true if TradeMonitor should check orders + // otherwise, false. + public abstract bool IsStarted { get; } + + // + // Summary: + // Start checking orders. + // + public void Start() + { + _apiClient.ConnectionStateChanged += _apiClient_ConnectionStateChanged; + if (IsMtConnected) + { + InitialCheck(); + } + + OnStart(); + } + + // + // Summary: + // Stop checking orders. + // + public void Stop() + { + _apiClient.ConnectionStateChanged -= _apiClient_ConnectionStateChanged; + OnStop(); + } + #endregion + + #region Events + // + // Summary: + // Occurs when orders are opened or closed. + public event EventHandler AvailabilityOrdersChanged; + #endregion + + #region Protected Methods + + protected abstract void OnStart(); + protected abstract void OnStop(); + + protected abstract void OnMtConnected(); + protected abstract void OnMtDisconnected(); + + public bool IsMtConnected + { + get + { + return _apiClient.ConnectionState == MtConnectionState.Connected; + } + } + + protected void Check() + { + try + { + CheckOrders(); + } + catch (MtConnectionException) + { + //TODO: write error to log + } + catch (MtExecutionException) + { + //TODO: write error to log + } + } + #endregion + + #region Private Methods + private void CheckOrders() + { + var openedOrders = new List(); + var closedOrders = new List(); + + // get current orders from MetaTrader + var tradesOrders = _apiClient.GetOrders(OrderSelectSource.MODE_TRADES); + + List prevOrders; + lock(_locker) + { + prevOrders = _prevOrders; + } + + if (_prevOrders != null) //skip checking on first load orders + { + //check open orders + foreach (var order in tradesOrders) + { + if (_prevOrders.Find(a => a.Ticket == order.Ticket) == null) + { + openedOrders.Add(order); + } + } + + //check closed orders + var closeOrdersTemp = new List(); + foreach (var order in _prevOrders) + { + if (tradesOrders.Find(a => a.Ticket == order.Ticket) == null) + { + closeOrdersTemp.Add(order); + } + } + + if (closeOrdersTemp.Count > 0) + { + //get closed orders from history with actual values + var historyOrders = _apiClient.GetOrders(OrderSelectSource.MODE_HISTORY); + foreach (var order in closeOrdersTemp) + { + var closedOrder = historyOrders.Find(a => a.Ticket == order.Ticket); + if (closedOrder != null) + { + closedOrders.Add(closedOrder); + } + } + } + } + + lock(_locker) + { + _prevOrders = tradesOrders; + } + + if (openedOrders.Count > 0 || closedOrders.Count > 0) + { + AvailabilityOrdersChanged?.Invoke(this, new AvailabilityOrdersEventArgs(openedOrders, closedOrders)); + } + } + + private void _apiClient_ConnectionStateChanged(object sender, MtConnectionEventArgs e) + { + if (e.Status == MtConnectionState.Connected) + { + InitialCheck(); + OnMtConnected(); + } + else if (e.Status == MtConnectionState.Failed || e.Status == MtConnectionState.Disconnected) + { + OnMtDisconnected(); + } + } + + private void InitialCheck() + { + lock (_locker) + { + _prevOrders = null; + } + + Task.Factory.StartNew(() => + { + Check(); + }); + } + #endregion + } +} \ No newline at end of file diff --git a/MtApi/MtApi.csproj b/MtApi/MtApi.csproj index 0d0ef21b..1276ed05 100755 --- a/MtApi/MtApi.csproj +++ b/MtApi/MtApi.csproj @@ -60,6 +60,7 @@ + @@ -73,6 +74,7 @@ + @@ -98,6 +100,8 @@ + + diff --git a/MtApi/MtApiClient.cs b/MtApi/MtApiClient.cs index 04b06838..bb1b947a 100755 --- a/MtApi/MtApiClient.cs +++ b/MtApi/MtApiClient.cs @@ -485,10 +485,10 @@ namespace MtApi return response != null ? response.Order : null; } - public IEnumerable GetOrders(OrderSelectSource pool) + public List GetOrders(OrderSelectSource pool) { var response = SendRequest(new GetOrdersRequest { Pool = (int)pool }); - return response != null ? response.Orders : null; + return response != null ? response.Orders : new List(); } #endregion @@ -1490,8 +1490,7 @@ namespace MtApi if (changed) { - var handler = ConnectionStateChanged; - handler?.BeginInvoke(this, new MtConnectionEventArgs(state, message), (a) => handler.EndInvoke(a), null); + ConnectionStateChanged.FireEventAsync(this, new MtConnectionEventArgs(state, message)); } } @@ -1573,14 +1572,13 @@ namespace MtApi { if (quote != null) { - var handler = QuoteUpdated; if (IsBacktestingMode) { - handler?.Invoke(this, quote.Instrument, quote.Bid, quote.Ask); + QuoteUpdated?.Invoke(this, quote.Instrument, quote.Bid, quote.Ask); } else { - handler?.BeginInvoke(this, quote.Instrument, quote.Bid, quote.Ask, (a) => handler.EndInvoke(a), null); + QuoteUpdated.FireEventAsync(this, quote.Instrument, quote.Bid, quote.Ask); } } } @@ -1597,14 +1595,12 @@ namespace MtApi private void mClient_QuoteRemoved(MTApiService.MtQuote quote) { - var handler = QuoteRemoved; - handler?.BeginInvoke(this, new MtQuoteEventArgs(quote.Convert()), (a) => handler.EndInvoke(a), null); + QuoteRemoved.FireEventAsync(this, new MtQuoteEventArgs(quote.Convert())); } private void mClient_QuoteAdded(MTApiService.MtQuote quote) { - var handler = QuoteAdded; - handler?.BeginInvoke(this, new MtQuoteEventArgs(quote.Convert()), (a) => handler.EndInvoke(a), null); + QuoteAdded.FireEventAsync(this, new MtQuoteEventArgs(quote.Convert())); } private void _client_MtEventReceived(object sender, MtEventArgs e) @@ -1623,8 +1619,7 @@ namespace MtApi private void FireOnLastTimeBar(MtTimeBar timeBar) { - var handler = OnLastTimeBar; - handler?.BeginInvoke(this, new TimeBarArgs(timeBar), (a) => handler.EndInvoke(a), null); + OnLastTimeBar.FireEventAsync(this, new TimeBarArgs(timeBar)); } #endregion diff --git a/TestClients/TestApiClientUI/Form1.cs b/TestClients/TestApiClientUI/Form1.cs index 9c8f4d50..86f7183f 100644 --- a/TestClients/TestApiClientUI/Form1.cs +++ b/TestClients/TestApiClientUI/Form1.cs @@ -5,6 +5,7 @@ using System.Windows.Forms; using MtApi; using System.Threading.Tasks; using System.Linq; +using MtApi.Monitors; namespace TestApiClientUI { @@ -12,6 +13,8 @@ namespace TestApiClientUI { private readonly List _groupOrderCommands = new List(); private readonly MtApiClient _apiClient = new MtApiClient(); + private readonly TimerTradeMonitor _timerTradeMonitor; + private readonly TimeframeTradeMonitor _timeframeTradeMonitor; public Form1() { @@ -29,6 +32,13 @@ namespace TestApiClientUI comboBox1.SelectedIndex = 0; comboBox2.SelectedIndex = 0; + + _timerTradeMonitor = new TimerTradeMonitor(_apiClient); + _timerTradeMonitor.Interval = 1000; // 1 sec + _timerTradeMonitor.AvailabilityOrdersChanged += _tradeMonitor_AvailabilityOrdersChanged; + + _timeframeTradeMonitor = new TimeframeTradeMonitor(_apiClient); + _timeframeTradeMonitor.AvailabilityOrdersChanged += _tradeMonitor_AvailabilityOrdersChanged; } private void initOrderCommandsGroup() @@ -200,16 +210,20 @@ namespace TestApiClientUI int port; int.TryParse(textBoxPort.Text, out port); + _timerTradeMonitor.Start(); + _timeframeTradeMonitor.Start(); + if (string.IsNullOrEmpty(serverName)) _apiClient.BeginConnect(port); else _apiClient.BeginConnect(serverName, port); - - OnConnected(); } private void buttonDisconnect_Click(object sender, EventArgs e) { + _timerTradeMonitor.Stop(); + _timeframeTradeMonitor.Stop(); + _apiClient.BeginDisconnect(); } @@ -1052,6 +1066,7 @@ namespace TestApiClientUI AddToLog(string.Format("ICustom result: {0}", retVal)); } + //CopyRates private async void button24_Click(object sender, EventArgs e) { string symbol = textBoxSelectedSymbol.Text; @@ -1078,6 +1093,7 @@ namespace TestApiClientUI } } + //CopyRates private async void button25_Click(object sender, EventArgs e) { string symbol = textBoxSelectedSymbol.Text; @@ -1104,6 +1120,7 @@ namespace TestApiClientUI } } + //CopyRates private async void button26_Click(object sender, EventArgs e) { string symbol = textBoxSelectedSymbol.Text; @@ -1130,6 +1147,7 @@ namespace TestApiClientUI } } + //Print private void button27_Click(object sender, EventArgs e) { var msg = textBoxPrint.Text; @@ -1139,5 +1157,19 @@ namespace TestApiClientUI AddToLog(string.Format("Print executed")); } } + + private void _tradeMonitor_AvailabilityOrdersChanged(object sender, AvailabilityOrdersEventArgs e) + { + if (e.Opened != null) + { + AddToLog($"{sender.GetType()}: Opened orders - {string.Join(", ", e.Opened.Select(o => o.Ticket).ToList())}"); + } + + if (e.Closed != null) + { + AddToLog($"{sender.GetType()}: Closed orders - {string.Join(", ", e.Closed.Select(o => o.Ticket).ToList())}"); + } + } + } }