From a12ddba765ce8917dc27af451895f593fac5d961 Mon Sep 17 00:00:00 2001 From: "m.bochmann" Date: Tue, 13 Oct 2020 15:10:26 +0200 Subject: [PATCH 1/5] Changed design of monitors for better extensibility --- MtApi/Monitors/MtMonitorBase.cs | 113 +++++++++++++ MtApi/Monitors/TimeframeTradeMonitor.cs | 55 +------ MtApi/Monitors/TimerTradeMonitor.cs | 89 ++--------- MtApi/Monitors/TradeMonitor.cs | 151 ++++-------------- MtApi/Monitors/Triggers/IMonitorTrigger.cs | 27 ++++ MtApi/Monitors/Triggers/NewBarTrigger.cs | 43 +++++ MtApi/Monitors/Triggers/TimeElapsedTrigger.cs | 40 +++++ MtApi/MtApi.csproj | 4 + TestClients/TestApiClientUI/Form1.cs | 6 +- 9 files changed, 283 insertions(+), 245 deletions(-) create mode 100644 MtApi/Monitors/MtMonitorBase.cs create mode 100644 MtApi/Monitors/Triggers/IMonitorTrigger.cs create mode 100644 MtApi/Monitors/Triggers/NewBarTrigger.cs create mode 100644 MtApi/Monitors/Triggers/TimeElapsedTrigger.cs 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); From 739567d8715622e413b2a8f538f74b7e0a6edd3a Mon Sep 17 00:00:00 2001 From: "m.bochmann" Date: Tue, 13 Oct 2020 15:36:59 +0200 Subject: [PATCH 2/5] Bugfix in TimerTradeMonitor SyncTrigger should only be set to true by default when the trigger will be initialized in the constructor. --- MtApi/Monitors/TimerTradeMonitor.cs | 3 +-- MtApi/MtApi.csproj | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/MtApi/Monitors/TimerTradeMonitor.cs b/MtApi/Monitors/TimerTradeMonitor.cs index 4d52c746..ccf249a4 100644 --- a/MtApi/Monitors/TimerTradeMonitor.cs +++ b/MtApi/Monitors/TimerTradeMonitor.cs @@ -15,12 +15,11 @@ namespace MtApi.Monitors public TimerTradeMonitor(MtApiClient apiClient) : this(apiClient, new TimeElapsedTrigger(TimeSpan.FromSeconds(10))) { - + SyncTrigger = true; //Sync-Trigger set to true, to have the same behavior as before } public TimerTradeMonitor(MtApiClient apiClient, TimeElapsedTrigger timeElapsedTrigger) : base(apiClient, timeElapsedTrigger) { - SyncTrigger = true; //Sync-Trigger set to true, to have the same behavior as before _timeElapsedTrigger = timeElapsedTrigger; } } diff --git a/MtApi/MtApi.csproj b/MtApi/MtApi.csproj index 92296139..21130cc6 100755 --- a/MtApi/MtApi.csproj +++ b/MtApi/MtApi.csproj @@ -77,7 +77,7 @@ - + From 724162f29d5b099639dc4c08770cc3a4d12dd59f Mon Sep 17 00:00:00 2001 From: "m.bochmann" Date: Tue, 13 Oct 2020 15:52:55 +0200 Subject: [PATCH 3/5] Updated documentation of newly added classes --- MtApi/Monitors/AvailabilityOrdersEventArgs.cs | 7 ++- MtApi/Monitors/TimeframeTradeMonitor.cs | 5 +++ MtApi/Monitors/TimerTradeMonitor.cs | 21 +++++++++ MtApi/Monitors/TradeMonitor.cs | 5 +++ MtApi/Monitors/Triggers/NewBarTrigger.cs | 29 +++++++++++- MtApi/Monitors/Triggers/TimeElapsedTrigger.cs | 44 ++++++++++++++++--- 6 files changed, 103 insertions(+), 8 deletions(-) diff --git a/MtApi/Monitors/AvailabilityOrdersEventArgs.cs b/MtApi/Monitors/AvailabilityOrdersEventArgs.cs index f6bea3c8..3f569321 100644 --- a/MtApi/Monitors/AvailabilityOrdersEventArgs.cs +++ b/MtApi/Monitors/AvailabilityOrdersEventArgs.cs @@ -10,8 +10,13 @@ namespace MtApi.Monitors Opened = opened; Closed = closed; } - + /// + /// Contains all newly opened orders since the last time the monitor checked the open orders. + /// public List Opened { get; private set; } + /// + /// Contains all newly closed orders since the last time the monitor checked the open orders. + /// public List Closed { get; private set; } } } diff --git a/MtApi/Monitors/TimeframeTradeMonitor.cs b/MtApi/Monitors/TimeframeTradeMonitor.cs index e76306d8..8d08a0b2 100644 --- a/MtApi/Monitors/TimeframeTradeMonitor.cs +++ b/MtApi/Monitors/TimeframeTradeMonitor.cs @@ -4,6 +4,11 @@ namespace MtApi.Monitors { public class TimeframeTradeMonitor : TradeMonitor { + /// + /// Constructor for initializing a new instance with a trigger instance of . + /// SyncTrigger is set to true by default + /// + /// The which will be used to communicate with MetaTrader. public TimeframeTradeMonitor(MtApiClient apiClient) : base(apiClient, new NewBarTrigger(apiClient)) { diff --git a/MtApi/Monitors/TimerTradeMonitor.cs b/MtApi/Monitors/TimerTradeMonitor.cs index ccf249a4..769ada89 100644 --- a/MtApi/Monitors/TimerTradeMonitor.cs +++ b/MtApi/Monitors/TimerTradeMonitor.cs @@ -5,22 +5,43 @@ namespace MtApi.Monitors { public class TimerTradeMonitor : TradeMonitor { + #region Fields private readonly TimeElapsedTrigger _timeElapsedTrigger; + #endregion + + #region Properties + /// + /// Interval for raising the trigger + /// public double Interval { get => _timeElapsedTrigger.Interval.TotalMilliseconds; set => _timeElapsedTrigger.Interval = TimeSpan.FromMilliseconds(value); } + #endregion + #region ctors + /// + /// Constructor for initializing a new instance with a default of 10 seconds. + /// SyncTrigger is set to true by default + /// + /// The which will be used to communicate with MetaTrader. public TimerTradeMonitor(MtApiClient apiClient) : this(apiClient, new TimeElapsedTrigger(TimeSpan.FromSeconds(10))) { SyncTrigger = true; //Sync-Trigger set to true, to have the same behavior as before } + /// + /// Constructor for initializing a new instance with a custom instance of . + /// SyncTrigger is set to false by default + /// + /// The which will be used to communicate with MetaTrader. + /// The custom instance of which will be used to trigger this instance of . public TimerTradeMonitor(MtApiClient apiClient, TimeElapsedTrigger timeElapsedTrigger) : base(apiClient, timeElapsedTrigger) { _timeElapsedTrigger = timeElapsedTrigger; } + #endregion } } \ No newline at end of file diff --git a/MtApi/Monitors/TradeMonitor.cs b/MtApi/Monitors/TradeMonitor.cs index d1e498ca..a80361a0 100644 --- a/MtApi/Monitors/TradeMonitor.cs +++ b/MtApi/Monitors/TradeMonitor.cs @@ -21,6 +21,11 @@ namespace MtApi.Monitors #endregion #region ctor + /// + /// Constructor for initializing an instance of . + /// + /// The which will be used to communicate with MetaTrader. + /// The custom instance of which will be used to trigger this instance of . public TradeMonitor(MtApiClient apiClient, IMonitorTrigger monitorTrigger) : base(apiClient, monitorTrigger) { } #endregion diff --git a/MtApi/Monitors/Triggers/NewBarTrigger.cs b/MtApi/Monitors/Triggers/NewBarTrigger.cs index 2c4cdc4e..4c39109b 100644 --- a/MtApi/Monitors/Triggers/NewBarTrigger.cs +++ b/MtApi/Monitors/Triggers/NewBarTrigger.cs @@ -12,22 +12,46 @@ namespace MtApi.Monitors.Triggers private readonly MtApiClient _apiClient; #endregion + #region Properties + /// + /// Returns true if the trigger is started, otherwise false + /// public bool IsStarted => _isStarted; + #endregion + + #region Events + /// + /// Event will be called if the trigger raised. + /// public event EventHandler Raised; + #endregion + + #region ctor public NewBarTrigger(MtApiClient apiClient) { _apiClient = apiClient; _apiClient.OnLastTimeBar += _apiClient_OnLastTimeBar; } + #endregion + #region Public methods + /// + /// Starts the trigger + /// + public void Start() => SetIsStarted(true); + /// + /// Stops the trigger + /// + public void Stop() => SetIsStarted(false); + #endregion + + #region Private methods 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) @@ -39,5 +63,6 @@ namespace MtApi.Monitors.Triggers _apiClient.OnLastTimeBar -= _apiClient_OnLastTimeBar; } } + #endregion } } diff --git a/MtApi/Monitors/Triggers/TimeElapsedTrigger.cs b/MtApi/Monitors/Triggers/TimeElapsedTrigger.cs index ba085fd1..65d0b59d 100644 --- a/MtApi/Monitors/Triggers/TimeElapsedTrigger.cs +++ b/MtApi/Monitors/Triggers/TimeElapsedTrigger.cs @@ -5,9 +5,11 @@ namespace MtApi.Monitors.Triggers { public class TimeElapsedTrigger : IMonitorTrigger { + #region Fields readonly Timer _timer; + #endregion - public event EventHandler Raised; + #region Properties /// /// Interval for raising the trigger /// @@ -16,25 +18,57 @@ namespace MtApi.Monitors.Triggers get => TimeSpan.FromMilliseconds(_timer.Interval); set => _timer.Interval = value.TotalMilliseconds; } + + /// + /// Returns true if the trigger is started, otherwise false + /// public bool IsStarted => _timer.Enabled; + /// + /// If true, the trigger will raise continuosly after elapsed , otherwise the trigger will raise only once after elapsed . + /// public bool AutoReset { get => _timer.AutoReset; set => _timer.AutoReset = value; } + #endregion + #region Events + /// + /// Returns true if the trigger is started, otherwise false + /// + public event EventHandler Raised; + #endregion + + #region ctor + /// + /// Constructor for initializing TimeElapsedTrigger + /// + /// Defines the interval for raising the event. + /// If true, the trigger will raise continuosly after elapsed , otherwise the trigger will raise only once after elapsed . public TimeElapsedTrigger(TimeSpan time, bool autoReset = true) { _timer = new Timer(time.TotalMilliseconds); _timer.Elapsed += _timer_Elapsed; AutoReset = autoReset; } + #endregion + #region Public methods + /// + /// Starts the trigger + /// + public void Start() => _timer.Start(); + /// + /// Stops the trigger + /// + public void Stop() => _timer.Stop(); + #endregion + + #region private methods 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(); + #endregion } -} +} \ No newline at end of file From 833ce1f19216d24c654bbc3630c47161ed3a708a Mon Sep 17 00:00:00 2001 From: Mitja Date: Sun, 18 Oct 2020 11:05:18 +0200 Subject: [PATCH 4/5] Added ModifiedOrdersMonitor, added some more doc --- .../ModifiedOrdersEventArgs.cs | 20 ++++++ .../ModifiedOrdersMonitor.cs | 68 +++++++++++++++++++ .../OrderModification/MtModifiedOrder.cs | 50 ++++++++++++++ .../OrderModification/OrderModifiedTypes.cs | 18 +++++ MtApi/Monitors/readme.md | 66 ++++++++++++++++++ MtApi/MtApi.csproj | 4 ++ 6 files changed, 226 insertions(+) create mode 100644 MtApi/Monitors/OrderModification/ModifiedOrdersEventArgs.cs create mode 100644 MtApi/Monitors/OrderModification/ModifiedOrdersMonitor.cs create mode 100644 MtApi/Monitors/OrderModification/MtModifiedOrder.cs create mode 100644 MtApi/Monitors/OrderModification/OrderModifiedTypes.cs create mode 100644 MtApi/Monitors/readme.md diff --git a/MtApi/Monitors/OrderModification/ModifiedOrdersEventArgs.cs b/MtApi/Monitors/OrderModification/ModifiedOrdersEventArgs.cs new file mode 100644 index 00000000..d803ea8d --- /dev/null +++ b/MtApi/Monitors/OrderModification/ModifiedOrdersEventArgs.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MtApi.Monitors +{ + public class ModifiedOrdersEventArgs : EventArgs + { + /// + /// Returns a list of all modified orders + /// + public List ModifiedOrders { get; } + public ModifiedOrdersEventArgs(List modifiedOrders) + { + ModifiedOrders = modifiedOrders; + } + } +} diff --git a/MtApi/Monitors/OrderModification/ModifiedOrdersMonitor.cs b/MtApi/Monitors/OrderModification/ModifiedOrdersMonitor.cs new file mode 100644 index 00000000..5a4618f6 --- /dev/null +++ b/MtApi/Monitors/OrderModification/ModifiedOrdersMonitor.cs @@ -0,0 +1,68 @@ +using MtApi.Monitors.Triggers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MtApi.Monitors +{ + public class ModifiedOrdersMonitor : MtMonitorBase + { + #region Fields + private List _lastOrders = null; + + #endregion + + #region Properties + /// + /// Define on which types of modification this monitor should raise + /// + public OrderModifiedTypes OrderModifiedTypes { get; set; } + #endregion + + #region Events + /// + /// Will be raised when this monitor detects changes on open orders + /// + public event EventHandler OrdersModified; + #endregion + + #region ctor + public ModifiedOrdersMonitor(MtApiClient apiClient, IMonitorTrigger monitorTrigger, OrderModifiedTypes orderModifiedTypes = OrderModifiedTypes.All, bool syncTrigger = false) + : base(apiClient, monitorTrigger, syncTrigger) + { + _lastOrders = GetOrders(); + OrderModifiedTypes = orderModifiedTypes; + } + #endregion + + /// + /// Requests all current open orders + /// + /// + private List GetOrders() => IsMtConnected ? ApiClient.GetOrders(OrderSelectSource.MODE_TRADES) : null; + protected override void OnTriggerRaised() + { + if(_lastOrders == null) + { + _lastOrders = GetOrders(); + return; + } + List currentOrders = GetOrders(); + OrderModifiedTypes omt = OrderModifiedTypes; + var mtModifiedOrders = currentOrders + .Select(co => new MtModifiedOrder(_lastOrders.FirstOrDefault(x => x.Ticket == co.Ticket), co)) + .ToList(); + List modifiedOrders = new List(); + modifiedOrders.AddRange(GetMtModifiedOrdersWithModType(mtModifiedOrders, omt, OrderModifiedTypes.TakeProfit)); //If the takeprofit were changed between both calls + modifiedOrders.AddRange(GetMtModifiedOrdersWithModType(mtModifiedOrders, omt, OrderModifiedTypes.StopLoss)); //If the stoploss were changed between both calls + modifiedOrders.AddRange(GetMtModifiedOrdersWithModType(mtModifiedOrders, omt, OrderModifiedTypes.Operation)); //If an order changed from limit / stop order to an open order + if (modifiedOrders.Count > 0) + OrdersModified?.Invoke(this, new ModifiedOrdersEventArgs(modifiedOrders)); + _lastOrders = currentOrders; + } + private static IEnumerable GetMtModifiedOrdersWithModType(IEnumerable orders, OrderModifiedTypes globalSearchFlag, OrderModifiedTypes modifiedType) + => globalSearchFlag.HasFlag(modifiedType) ? orders.Where(o => o.ModifyType.HasFlag(modifiedType)) : new List(); + } +} \ No newline at end of file diff --git a/MtApi/Monitors/OrderModification/MtModifiedOrder.cs b/MtApi/Monitors/OrderModification/MtModifiedOrder.cs new file mode 100644 index 00000000..7bb82d0c --- /dev/null +++ b/MtApi/Monitors/OrderModification/MtModifiedOrder.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MtApi.Monitors +{ + public class MtModifiedOrder + { + /// + /// The order in its old state (before the changes) + /// + public MtOrder OldOrder { get; } + /// + /// The order in its new state (after the changes) + /// + public MtOrder NewOrder { get; } + /// + /// The changes found by this instance + /// + public OrderModifiedTypes ModifyType { get; private set; } + /// + /// Initializes an instance and compare the order in its old and new state + /// + /// The order in its old state (before the changes) + /// The order in its new state (after the changes) + public MtModifiedOrder(MtOrder oldOrder, MtOrder newOrder) + { + if (oldOrder != null && newOrder != null && oldOrder.Ticket != newOrder.Ticket) + throw new ArgumentException(nameof(oldOrder) + " and " + nameof(newOrder) + " need to have the same ticket id"); + OldOrder = oldOrder; + NewOrder = newOrder; + ModifyType = OrderModifiedTypes.None; + Compare(); + } + private void Compare() + { + if(NewOrder != null && OldOrder != null) + { + if (OldOrder.StopLoss != NewOrder.StopLoss) + ModifyType |= OrderModifiedTypes.StopLoss; + if (OldOrder.TakeProfit != NewOrder.TakeProfit) + ModifyType |= OrderModifiedTypes.TakeProfit; + if (OldOrder.Operation != NewOrder.Operation) + ModifyType |= OrderModifiedTypes.Operation; + } + } + } +} diff --git a/MtApi/Monitors/OrderModification/OrderModifiedTypes.cs b/MtApi/Monitors/OrderModification/OrderModifiedTypes.cs new file mode 100644 index 00000000..74c351b8 --- /dev/null +++ b/MtApi/Monitors/OrderModification/OrderModifiedTypes.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MtApi.Monitors +{ + [Flags] + public enum OrderModifiedTypes + { + None = 0x0, + TakeProfit = 1 << 0, + StopLoss = 1 << 1, + Operation = 1 << 2, + All = 7 + } +} diff --git a/MtApi/Monitors/readme.md b/MtApi/Monitors/readme.md new file mode 100644 index 00000000..e351d883 --- /dev/null +++ b/MtApi/Monitors/readme.md @@ -0,0 +1,66 @@ +## Monitors + +Monitors can be used to monitor different changes on Mt4 structs. + +## MtMonitorBase +This is the base class for monitoring extensions. If inherited `MtMonitorBase` needs an instance of `MtApiClient` and `IMonitorTrigger`. + +`SyncTrigger` (which can be set in the constructor or as Property) can be used to define whether the trigger should be started and stopped as well if `Start()` or `Stop()` will be called on an instance of a child class of `MtMonitorBase`. +Keep in mind: If an `IMonitorTrigger` will be used for several monitors, `SyncTrigger` set to `true` would cause that all monitors related to this trigger would stop: + +``` +var fooTrigger = new FooTrigger(); +var fooMonitor = new FooMonitor(apiClient, fooTrigger, true); +var barMonitor = new FooMonitor(apiClient, fooTrigger, false); + +fooTrigger.Start(); +barMonitor.Start(); +fooMonitor.Start(); + +fooMonitor.Stop(); //Because of SyncTrigger = true in the constructor of FooMonitor, fooTrigger.Stop() were triggered as well. Therefore barMonitor will not get any further triggers. +``` + +## IMonitorTrigger +An `IMonitorTrigger` is for defining when the monitor should check whether his conditions are met for invoking his event. + +There are already two `IMonitorTrigger`s which can be used: +### NewBarTrigger +Triggers when a new bar starts +### TimeElapsedTrigger +Triggers when a defined time elapsed + +## Default monitors: +There are already two different monitors defined. You can extend them or define new ones by inheriting from `MtMonitorBase`. + +### TradeMonitor +Can be used to get updates on new opened trades or closed trades. + +## ModifiedOrdersMonitor +Can be used to get updates on modified trades (takeprofit, stoploss, operation). + +`OrderModifiedTypes` defines which modifications should be monitored: + +1. `None` would cause no monitoring. But please use `Start()` and `Stop()` instead. +2. `TakeProfit` would cause observing whether the TakeProfit were changed. +3. `StopLoss` would cause observing whether the StopLoss were changed. +`Operation` would cause observing trades which changed from a stop / limit order to an open order. +4. `All` would cause observing all above defined. + +Because `OrderModifiedTypes` is defined with the Flag-Attribute, you can combine the above monitoring types with a pipe: `OrderModifiedTypes.TakeProfit | OrderModifiedTypes.StopLoss`. + +### Example +``` +var orderModifyMonitor = new ModifiedOrdersMonitor( + _apiClient, + new MtApi.Monitors.Triggers.TimeElapsedTrigger(TimeSpan.FromSeconds(1)), + OrderModifiedTypes.All, + true +); +orderModifyMonitor.OrdersModified += OrderModifyMonitor_OrdersModified; +orderModifyMonitor.Start(); + +private void OrderModifyMonitor_OrdersModified(object sender, ModifiedOrdersEventArgs e) +{ + //Receives the event +} +``` diff --git a/MtApi/MtApi.csproj b/MtApi/MtApi.csproj index 21130cc6..92bc2804 100755 --- a/MtApi/MtApi.csproj +++ b/MtApi/MtApi.csproj @@ -76,7 +76,11 @@ + + + + From 5715e7e2b887f983ef1b28413878daaab41ce07a Mon Sep 17 00:00:00 2001 From: Mitja Bochmann <49299762+mbochmann@users.noreply.github.com> Date: Sun, 18 Oct 2020 11:12:28 +0200 Subject: [PATCH 5/5] Update readme.md --- MtApi/Monitors/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MtApi/Monitors/readme.md b/MtApi/Monitors/readme.md index e351d883..d50dae28 100644 --- a/MtApi/Monitors/readme.md +++ b/MtApi/Monitors/readme.md @@ -43,8 +43,8 @@ Can be used to get updates on modified trades (takeprofit, stoploss, operation). 1. `None` would cause no monitoring. But please use `Start()` and `Stop()` instead. 2. `TakeProfit` would cause observing whether the TakeProfit were changed. 3. `StopLoss` would cause observing whether the StopLoss were changed. -`Operation` would cause observing trades which changed from a stop / limit order to an open order. -4. `All` would cause observing all above defined. +4. `Operation` would cause observing trades which changed from a stop / limit order to an open order. +5. `All` would cause observing all above defined. Because `OrderModifiedTypes` is defined with the Flag-Attribute, you can combine the above monitoring types with a pipe: `OrderModifiedTypes.TakeProfit | OrderModifiedTypes.StopLoss`.