diff --git a/MtApi/Monitors/MtMonitorBase.cs b/MtApi/Monitors/MtMonitorBase.cs new file mode 100644 index 00000000..8f5c0407 --- /dev/null +++ b/MtApi/Monitors/MtMonitorBase.cs @@ -0,0 +1,113 @@ +using System; +using MtApi.Monitors.Triggers; + +namespace MtApi.Monitors +{ + public abstract class MtMonitorBase + { + #region Fields + private volatile bool _isStarted = false; + private bool _syncTrigger; + #endregion + + #region Properties + /// + /// ApiClient + /// + protected MtApiClient ApiClient { get; } + /// + /// Returns true if the is connected. + /// + public bool IsMtConnected => ApiClient.ConnectionState == MtConnectionState.Connected; + /// + /// Returns the trigger which will be used to raise the monitoring call. + /// + public IMonitorTrigger MonitorTrigger { get; } + /// + /// Returns true if the Monitor is started. + /// + public bool IsStarted { get => _isStarted; } + /// + /// If true, the will be stopped or started automatically when or will be called. + /// CAUTION: If you use the MonitorTrigger for different Monitors, this will stop all monitors if you call stop and is true. + /// + public bool SyncTrigger { get => _syncTrigger; set => _syncTrigger = value; } + #endregion + + #region ctor + /// + /// Default constructor for Monitors + /// + /// The apiClient which will be used to work with. + /// The trigger which lead this Monitor to do his work. + /// See property . + public MtMonitorBase(MtApiClient apiClient, IMonitorTrigger monitorTrigger, bool syncTrigger = false) + { + ApiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient)); + MonitorTrigger = monitorTrigger ?? throw new ArgumentNullException(nameof(monitorTrigger)); + SyncTrigger = syncTrigger; + } + #endregion + + #region Methods + /// + /// Let the monitor listen to the . + /// + public virtual void Start() + { + if (!_isStarted) + { + ApiClient.ConnectionStateChanged += ApiClientConnectionStateChanged; + MonitorTrigger.Raised += MonitorTriggerRaised; + _isStarted = true; + OnStart(); + if (SyncTrigger) + MonitorTrigger.Start(); + } + } + /// + /// Let the monitor stop listening to the . + /// + public virtual void Stop() + { + if (_isStarted) + { + MonitorTrigger.Raised -= MonitorTriggerRaised; + ApiClient.ConnectionStateChanged -= ApiClientConnectionStateChanged; + _isStarted = false; + OnStop(); + if (SyncTrigger) + MonitorTrigger.Stop(); + } + } + private void MonitorTriggerRaised(object sender, EventArgs e) => OnTriggerRaised(); + private void ApiClientConnectionStateChanged(object sender, MtConnectionEventArgs e) + { + if (e.Status == MtConnectionState.Connected) + OnMtConnected(); + else if (e.Status == MtConnectionState.Failed || e.Status == MtConnectionState.Disconnected) + OnMtDisconnected(); + } + /// + /// Will be called when will be called. + /// + protected virtual void OnStart() { } + /// + /// Will be called when will be called. + /// + protected virtual void OnStop() { } + /// + /// Will be called when the is successfully connected. + /// + protected virtual void OnMtConnected() { } + /// + /// Will be called when is disconnected. + /// + protected virtual void OnMtDisconnected() { } + /// + /// Will be called when the raised. + /// + protected abstract void OnTriggerRaised(); + #endregion + } +} \ No newline at end of file diff --git a/MtApi/Monitors/TimeframeTradeMonitor.cs b/MtApi/Monitors/TimeframeTradeMonitor.cs index 41663663..e76306d8 100644 --- a/MtApi/Monitors/TimeframeTradeMonitor.cs +++ b/MtApi/Monitors/TimeframeTradeMonitor.cs @@ -1,56 +1,13 @@ - +using MtApi.Monitors.Triggers; + namespace MtApi.Monitors { public class TimeframeTradeMonitor : TradeMonitor { - #region Fields - private volatile bool _isStarted; - #endregion - - #region ctor - public TimeframeTradeMonitor(MtApiClient apiClient) - : base(apiClient) + public TimeframeTradeMonitor(MtApiClient apiClient) + : base(apiClient, new NewBarTrigger(apiClient)) { - apiClient.OnLastTimeBar += ApiClient_OnLastTimeBar; + SyncTrigger = true; //Sync-Trigger set to true, to have the same behavior as before } - #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 => _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 } -} +} \ No newline at end of file diff --git a/MtApi/Monitors/TimerTradeMonitor.cs b/MtApi/Monitors/TimerTradeMonitor.cs index c8b430f2..4d52c746 100644 --- a/MtApi/Monitors/TimerTradeMonitor.cs +++ b/MtApi/Monitors/TimerTradeMonitor.cs @@ -1,90 +1,27 @@ -using System.Timers; +using System; +using MtApi.Monitors.Triggers; 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. + private readonly TimeElapsedTrigger _timeElapsedTrigger; public double Interval { - get { return _timer.Interval; } - set { _timer.Interval = value; } + get => _timeElapsedTrigger.Interval.TotalMilliseconds; + set => _timeElapsedTrigger.Interval = TimeSpan.FromMilliseconds(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 + public TimerTradeMonitor(MtApiClient apiClient) + : this(apiClient, new TimeElapsedTrigger(TimeSpan.FromSeconds(10))) { - get { return _timer.Enabled; } - } - #endregion - #region Protected Methods - protected override void OnStart() + } + public TimerTradeMonitor(MtApiClient apiClient, TimeElapsedTrigger timeElapsedTrigger) + : base(apiClient, timeElapsedTrigger) { - if (IsMtConnected) - { - _timer.Start(); - } + SyncTrigger = true; //Sync-Trigger set to true, to have the same behavior as before + _timeElapsedTrigger = timeElapsedTrigger; } - - 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 } -} +} \ No newline at end of file diff --git a/MtApi/Monitors/TradeMonitor.cs b/MtApi/Monitors/TradeMonitor.cs index fc746339..d1e498ca 100644 --- a/MtApi/Monitors/TradeMonitor.cs +++ b/MtApi/Monitors/TradeMonitor.cs @@ -1,63 +1,18 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using MtApi.Monitors.Triggers; namespace MtApi.Monitors { - public abstract class TradeMonitor + public class TradeMonitor : MtMonitorBase { #region Fields - private readonly MtApiClient _apiClient; private List _prevOrders; private readonly object _locker = new object(); #endregion - #region ctor - protected 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: @@ -65,17 +20,23 @@ namespace MtApi.Monitors public event EventHandler AvailabilityOrdersChanged; #endregion - #region Protected Methods + #region ctor + public TradeMonitor(MtApiClient apiClient, IMonitorTrigger monitorTrigger) : base(apiClient, monitorTrigger) { } + #endregion - protected abstract void OnStart(); - protected abstract void OnStop(); - - protected abstract void OnMtConnected(); - protected abstract void OnMtDisconnected(); - - public bool IsMtConnected => _apiClient.ConnectionState == MtConnectionState.Connected; - - protected void Check() + protected override void OnMtConnected() + { + InitialCheck(); + base.OnMtConnected(); + } + protected override void OnStart() + { + if (IsMtConnected) + InitialCheck(); + base.OnStart(); + } + protected override void OnTriggerRaised() => Check(); + private void Check() { try { @@ -90,92 +51,48 @@ namespace MtApi.Monitors //TODO: write error to log } } - #endregion + private void InitialCheck() + { + lock (_locker) + _prevOrders = null; - #region Private Methods + Task.Factory.StartNew(Check); + } private void CheckOrders() { var openedOrders = new List(); var closedOrders = new List(); + List prevOrders; // get current orders from MetaTrader - var tradesOrders = _apiClient.GetOrders(OrderSelectSource.MODE_TRADES); + var tradesOrders = ApiClient.GetOrders(OrderSelectSource.MODE_TRADES); - List prevOrders; - lock(_locker) - { + 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); - } - } + openedOrders = tradesOrders.Where(to => prevOrders.Find(a => a.Ticket == to.Ticket) == null).ToList(); //check closed orders - var closeOrdersTemp = new List(); - foreach (var order in prevOrders) - { - if (tradesOrders.Find(a => a.Ticket == order.Ticket) == null) - { - closeOrdersTemp.Add(order); - } - } + var closeOrdersTemp = prevOrders.Where(po => tradesOrders.Find(a => a.Ticket == po.Ticket) == null).ToList(); 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); - } - } + var historyOrders = ApiClient.GetOrders(OrderSelectSource.MODE_HISTORY); + closedOrders = closeOrdersTemp.Where(cot => historyOrders.Find(a => a.Ticket == cot.Ticket) != null).ToList(); } } - lock(_locker) - { + 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/Monitors/Triggers/IMonitorTrigger.cs b/MtApi/Monitors/Triggers/IMonitorTrigger.cs new file mode 100644 index 00000000..54e268d8 --- /dev/null +++ b/MtApi/Monitors/Triggers/IMonitorTrigger.cs @@ -0,0 +1,27 @@ +using System; + +namespace MtApi.Monitors.Triggers +{ + /// + /// Interface for triggers which can be used to trigger a . + /// + public interface IMonitorTrigger + { + /// + /// Event will be called if the trigger raised. + /// + event EventHandler Raised; + /// + /// Returns whether the trigger is started + /// + bool IsStarted { get; } + /// + /// Stops the trigger and prevents further calls of the event. + /// + void Stop(); + /// + /// Starts the trigger. + /// + void Start(); + } +} diff --git a/MtApi/Monitors/Triggers/NewBarTrigger.cs b/MtApi/Monitors/Triggers/NewBarTrigger.cs new file mode 100644 index 00000000..2c4cdc4e --- /dev/null +++ b/MtApi/Monitors/Triggers/NewBarTrigger.cs @@ -0,0 +1,43 @@ +using System; + +namespace MtApi.Monitors.Triggers +{ + /// + /// Raises the event if a bar is closed and a new one started. + /// + public class NewBarTrigger : IMonitorTrigger + { + #region Fields + private volatile bool _isStarted; + private readonly MtApiClient _apiClient; + #endregion + + public bool IsStarted => _isStarted; + public event EventHandler Raised; + public NewBarTrigger(MtApiClient apiClient) + { + _apiClient = apiClient; + _apiClient.OnLastTimeBar += _apiClient_OnLastTimeBar; + } + + private void _apiClient_OnLastTimeBar(object sender, TimeBarArgs e) + { + if (_isStarted) + Raised?.Invoke(this, EventArgs.Empty); + } + + public void Start() => SetIsStarted(true); + public void Stop() => SetIsStarted(false); + private void SetIsStarted(bool value) + { + if (value != _isStarted) + { + _isStarted = value; + if (value) + _apiClient.OnLastTimeBar += _apiClient_OnLastTimeBar; + else + _apiClient.OnLastTimeBar -= _apiClient_OnLastTimeBar; + } + } + } +} diff --git a/MtApi/Monitors/Triggers/TimeElapsedTrigger.cs b/MtApi/Monitors/Triggers/TimeElapsedTrigger.cs new file mode 100644 index 00000000..ba085fd1 --- /dev/null +++ b/MtApi/Monitors/Triggers/TimeElapsedTrigger.cs @@ -0,0 +1,40 @@ +using System; +using System.Timers; + +namespace MtApi.Monitors.Triggers +{ + public class TimeElapsedTrigger : IMonitorTrigger + { + readonly Timer _timer; + + public event EventHandler Raised; + /// + /// Interval for raising the trigger + /// + public TimeSpan Interval + { + get => TimeSpan.FromMilliseconds(_timer.Interval); + set => _timer.Interval = value.TotalMilliseconds; + } + public bool IsStarted => _timer.Enabled; + + public bool AutoReset { get => _timer.AutoReset; set => _timer.AutoReset = value; } + + public TimeElapsedTrigger(TimeSpan time, bool autoReset = true) + { + _timer = new Timer(time.TotalMilliseconds); + _timer.Elapsed += _timer_Elapsed; + AutoReset = autoReset; + } + + private void _timer_Elapsed(object sender, ElapsedEventArgs e) + { + _timer.Elapsed -= _timer_Elapsed; + Raised?.Invoke(this, EventArgs.Empty); + _timer.Elapsed += _timer_Elapsed; + } + public void Stop() => _timer.Stop(); + + public void Start() => _timer.Start(); + } +} diff --git a/MtApi/MtApi.csproj b/MtApi/MtApi.csproj index 3a7b9699..92296139 100755 --- a/MtApi/MtApi.csproj +++ b/MtApi/MtApi.csproj @@ -76,6 +76,10 @@ + + + + diff --git a/TestClients/TestApiClientUI/Form1.cs b/TestClients/TestApiClientUI/Form1.cs index dcf7cef9..ccb12183 100644 --- a/TestClients/TestApiClientUI/Form1.cs +++ b/TestClients/TestApiClientUI/Form1.cs @@ -212,8 +212,8 @@ namespace TestApiClientUI foreach (var quote in quotes) { AddNewQuote(quote); - } - } + } + } } private void OnDisconnected() @@ -456,7 +456,7 @@ namespace TestApiClientUI ticket = (int)listBoxSendedOrders.SelectedItems[0]; else if (listBoxClosedOrders.SelectedItems.Count > 0) ticket = (int)listBoxClosedOrders.SelectedItems[0]; - + if (ticket >= 0) { var result = _apiClient.OrderSelect(ticket, OrderSelectMode.SELECT_BY_POS);