diff --git a/MTApiService/MTApiService.csproj b/MTApiService/MTApiService.csproj index 64c83838..b99a1f50 100755 --- a/MTApiService/MTApiService.csproj +++ b/MTApiService/MTApiService.csproj @@ -44,14 +44,15 @@ MtApiKey.snk - - ..\packages\log4net.2.0.5\lib\net40-full\log4net.dll - True + + ..\packages\log4net.2.0.12\lib\net40\log4net.dll + + diff --git a/MTApiService/packages.config b/MTApiService/packages.config index d67e57f9..0066004f 100644 --- a/MTApiService/packages.config +++ b/MTApiService/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/MetaTraderApi_2017.sln b/MetaTraderApi_2017.sln index d8351e85..4010dd14 100644 --- a/MetaTraderApi_2017.sln +++ b/MetaTraderApi_2017.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30413.136 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApiClientUI", "TestClients\TestApiClientUI\TestApiClientUI.csproj", "{663CC515-EAAE-47D4-8933-5008C2DA1160}" EndProject @@ -47,6 +47,8 @@ Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "MtApiBootstrapper", "MtApiB {78B94552-DB17-40EC-B7C6-23D32DB85DC1} = {78B94552-DB17-40EC-B7C6-23D32DB85DC1} EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MtApiServiceNetCore", "MtApiServiceNetCore\MtApiServiceNetCore.csproj", "{7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -232,6 +234,22 @@ Global {8E63046B-56E5-4623-8808-558AD72A8F2B}.Release|x64.ActiveCfg = Release|x86 {8E63046B-56E5-4623-8808-558AD72A8F2B}.Release|x86.ActiveCfg = Release|x86 {8E63046B-56E5-4623-8808-558AD72A8F2B}.Release|x86.Build.0 = Release|x86 + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Debug|Win32.ActiveCfg = Debug|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Debug|Win32.Build.0 = Debug|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Debug|x64.ActiveCfg = Debug|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Debug|x64.Build.0 = Debug|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Debug|x86.ActiveCfg = Debug|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Debug|x86.Build.0 = Debug|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Release|Any CPU.Build.0 = Release|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Release|Win32.ActiveCfg = Release|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Release|Win32.Build.0 = Release|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Release|x64.ActiveCfg = Release|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Release|x64.Build.0 = Release|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Release|x86.ActiveCfg = Release|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -241,4 +259,7 @@ Global {38B9C657-BC2F-44F0-8824-54B31F2A64F5} = {B91FF338-E05D-4EF1-948B-A2376DB37ECA} {EB7C228D-9494-4985-845E-B8312450DF3D} = {B91FF338-E05D-4EF1-948B-A2376DB37ECA} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {23C8878C-16A5-47DF-9A57-73CCF847780D} + EndGlobalSection EndGlobal diff --git a/MtApi5/Mt5CommandType.cs b/MtApi5/Mt5CommandType.cs index c50d5521..0a5691a6 100755 --- a/MtApi5/Mt5CommandType.cs +++ b/MtApi5/Mt5CommandType.cs @@ -255,6 +255,8 @@ internal enum Mt5CommandType UnlockTicks = 159, PositionCloseAll = 160, - TesterStop = 161 + TesterStop = 161, + TesterDeposit = 162, + TesterWithdrawal = 163 } } diff --git a/MtApi5/MtApi5.csproj b/MtApi5/MtApi5.csproj old mode 100755 new mode 100644 index 8596a9c9..786f59c4 --- a/MtApi5/MtApi5.csproj +++ b/MtApi5/MtApi5.csproj @@ -1,123 +1,23 @@ - - + - Debug - AnyCPU - 8.0.30703 - 2.0 - {AC8B5010-DA75-477E-9CA5-547C649E12D8} + net5.0 Library - Properties - MtApi5 - MtApi5 - v4.0 - 512 + false - true - full - false ..\build\products\Debug\ - DEBUG;TRACE - prompt - 4 false - pdbonly - true ..\build\products\Release\ - TRACE - prompt - 4 - - ..\packages\Newtonsoft.Json.12.0.2\lib\net40\Newtonsoft.Json.dll - True - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - {DE76D5C7-B99C-4467-8408-78173BDD84E0} - MTApiService - + - - - - - \ No newline at end of file diff --git a/MtApi5/MtApi5Client.cs b/MtApi5/MtApi5Client.cs index a28348c0..ea6742a1 100755 --- a/MtApi5/MtApi5Client.cs +++ b/MtApi5/MtApi5Client.cs @@ -2241,6 +2241,28 @@ public void TesterStop() SendCommand(Mt5CommandType.TesterStop, null); } + /// + ///The special function that emulates depositing funds during a test. It can be used in some money management systems. + /// + ///Money to be deposited to an account in the deposit currency. + ///If successful, returns true, otherwise - false. + public bool TesterDeposit(double money) + { + var commandParameters = new ArrayList { money }; + return SendCommand(Mt5CommandType.TesterDeposit, commandParameters); + } + + /// + ///The special function to emulate the operation of money withdrawal in the process of testing. Can be used in some asset management systems. + /// + /// The sum of money that we need to withdraw (in the deposit currency). + ///If successful, returns true, otherwise - false. + public bool TesterWithdrawal(double money) + { + var commandParameters = new ArrayList { money }; + return SendCommand(Mt5CommandType.TesterWithdrawal, commandParameters); + } + #endregion // Common Functions #region Object Functions diff --git a/MtApi5/packages.config b/MtApi5/packages.config index 66b711be..32637c2e 100755 --- a/MtApi5/packages.config +++ b/MtApi5/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/MtApiServiceNetCore/MtApiProxy.cs b/MtApiServiceNetCore/MtApiProxy.cs new file mode 100644 index 00000000..7934fad9 --- /dev/null +++ b/MtApiServiceNetCore/MtApiProxy.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.ServiceModel; +using System.ServiceModel.Channels; + +namespace MTApiService +{ + internal class MtApiProxy : IMtApi, IDisposable + { + private IMtApi InnerChannel; + + public CommunicationState State => ((ICommunicationObject)InnerChannel).State; + + public MtApiProxy(InstanceContext callbackContext, Binding binding, EndpointAddress remoteAddress) + { + var channel = new DuplexChannelFactory(callbackContext, binding, remoteAddress); + channel.Faulted += InnerDuplexChannel_Faulted; + + // configure endpoint programmatically instead via an attribute which will lead to a PlatformNotSupportedException + (channel.Endpoint.EndpointBehaviors.Single(b => b is CallbackBehaviorAttribute) as CallbackBehaviorAttribute).UseSynchronizationContext = false; + + InnerChannel = channel.CreateChannel(); + } + + #region IMtApi Members + + public bool Connect() + { + return InnerChannel.Connect(); + } + + public void Disconnect() + { + InnerChannel.Disconnect(); + } + + public MtResponse SendCommand(MtCommand command) + { + return InnerChannel.SendCommand(command); + } + + public List GetQuotes() + { + return InnerChannel.GetQuotes(); + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + try + { + Disconnect(); + } + catch (Exception) + { + + } + } + + #endregion + + #region Private Methods + private void InnerDuplexChannel_Faulted(object sender, EventArgs e) + { + Faulted?.Invoke(this, e); + } + + #endregion + + #region Events + public event EventHandler Faulted; + #endregion + } +} diff --git a/MtApiServiceNetCore/MtApiServiceNetCore.csproj b/MtApiServiceNetCore/MtApiServiceNetCore.csproj new file mode 100644 index 00000000..96f29b07 --- /dev/null +++ b/MtApiServiceNetCore/MtApiServiceNetCore.csproj @@ -0,0 +1,39 @@ + + + net5.0 + Library + false + + + ..\build\products\Debug\ + + + ..\build\products\Release\ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MtApiServiceNetCore/MtClient.cs b/MtApiServiceNetCore/MtClient.cs new file mode 100644 index 00000000..f5403fec --- /dev/null +++ b/MtApiServiceNetCore/MtClient.cs @@ -0,0 +1,291 @@ +using System; +using System.Collections; +using System.ServiceModel; +using System.Collections.Generic; +using log4net; +using System.Threading.Tasks; + +namespace MTApiService +{ + public sealed class MtClient : IMtApiCallback, IDisposable + { + private const string ServiceName = "MtApiService"; + + public delegate void MtQuoteHandler(MtQuote quote); + public delegate void MtEventHandler(MtEvent e); + + #region Fields + private static readonly ILog Log = LogManager.GetLogger(typeof(MtClient)); + + private readonly MtApiProxy _proxy; + private Task lastQuoteTask; + private Task lastEventTask; + #endregion + + #region ctor + public MtClient(string host, int port) + { + if (string.IsNullOrEmpty(host)) + throw new ArgumentNullException(nameof(host), "host is null or empty"); + + if (port < 0 || port > 65536) + throw new ArgumentOutOfRangeException(nameof(port), "port value is invalid"); + + Host = host; + Port = port; + + var urlService = $"net.tcp://{host}:{port}/{ServiceName}"; + + var bind = new NetTcpBinding(SecurityMode.None) + { + MaxReceivedMessageSize = 2147483647, + MaxBufferSize = 2147483647, + MaxBufferPoolSize = 2147483647, + SendTimeout = new TimeSpan(12, 0, 0), + ReceiveTimeout = new TimeSpan(12, 0, 0), + ReaderQuotas = + { + MaxArrayLength = 2147483647, + MaxBytesPerRead = 2147483647, + MaxDepth = 2147483647, + MaxStringContentLength = 2147483647, + MaxNameTableCharCount = 2147483647 + } + }; + + var quoteScheduler = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.AttachedToParent); + var eventScheduler = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.AttachedToParent); + lastQuoteTask = quoteScheduler.StartNew(() => { }); + lastEventTask = eventScheduler.StartNew(() => { }); + + _proxy = new MtApiProxy(new InstanceContext(this), bind, new EndpointAddress(urlService)); + _proxy.Faulted += ProxyFaulted; + } + + public MtClient(int port) : this("localhost", port) + { } + + #endregion + + #region Public Methods + /// Thrown when connection failed + public void Connect() + { + Log.Debug("Connect: begin."); + + if (_proxy.State != CommunicationState.Created) + { + Log.ErrorFormat("Connected: end. Client has invalid state {0}", _proxy.State); + return; + } + + bool coonected; + + try + { + coonected = _proxy.Connect(); + } + catch (Exception ex) + { + Log.ErrorFormat("Connect: Exception - {0}", ex.Message); + + throw new CommunicationException($"Connection failed to service. {ex.Message}"); + } + + if (coonected == false) + { + Log.Error("Connect: end. Connection failed."); + throw new CommunicationException("Connection failed"); + } + + Log.Debug("Connect: end."); + } + + public void Disconnect() + { + Log.Debug("Disconnect: begin."); + + try + { + _proxy.Disconnect(); + } + catch (Exception ex) + { + Log.ErrorFormat("Disconnect: Exception - {0}", ex.Message); + } + + Log.Debug("Disconnect: end."); + } + + /// Thrown when connection failed + public MtResponse SendCommand(int commandType, ArrayList parameters, Dictionary namedParams, int expertHandle) + { + Log.DebugFormat("SendCommand: begin. commandType = {0}, parameters count = {1}", commandType, parameters?.Count); + + if (IsConnected == false) + { + Log.Error("SendCommand: Client is not connected."); + throw new CommunicationException("Client is not connected."); + } + + MtResponse result; + + try + { + result = _proxy.SendCommand(new MtCommand { + CommandType = commandType, + Parameters = parameters, + NamedParams = namedParams, + ExpertHandle = expertHandle}); + } + catch (Exception ex) + { + Log.ErrorFormat("SendCommand: Exception - {0}", ex.Message); + + throw new CommunicationException("Service connection failed! " + ex.Message); + } + + Log.DebugFormat("SendCommand: end. result = {0}", result); + + return result; + } + + /// Thrown when connection failed + public List GetQuotes() + { + Log.Debug("GetQuotes: begin."); + + if (IsConnected == false) + { + Log.Warn("GetQuotes: end. Client is not connected."); + return null; + } + + List result; + + try + { + result = _proxy.GetQuotes(); + } + catch (Exception ex) + { + Log.ErrorFormat("GetQuotes: Exception - {0}", ex.Message); + + throw new CommunicationException($"Service connection failed! {ex.Message}"); + } + + Log.DebugFormat("GetQuotes: end. quotes count = {0}", result?.Count); + + return result; + } + + #endregion + + #region IMtApiCallback Members + + public void OnQuoteUpdate(MtQuote quote) + { + Log.DebugFormat("OnQuoteUpdate: begin. quote = {0}", quote); + + if (quote == null) return; + + if (QuoteUpdated != null) + { + lastQuoteTask = lastQuoteTask.ContinueWith((t) => QuoteUpdated.Invoke(quote)); + } + + Log.Debug("OnQuoteUpdate: end."); + } + + public void OnQuoteAdded(MtQuote quote) + { + Log.DebugFormat("OnQuoteAdded: begin. quote = {0}", quote); + + if (QuoteAdded != null) + { + lastQuoteTask = lastQuoteTask.ContinueWith((t) => QuoteAdded.Invoke(quote)); + } + + Log.Debug("OnQuoteAdded: end."); + } + + public void OnQuoteRemoved(MtQuote quote) + { + Log.DebugFormat("OnQuoteRemoved: begin. quote = {0}", quote); + + if (QuoteRemoved != null) + { + lastQuoteTask = lastQuoteTask.ContinueWith((t) => QuoteRemoved.Invoke(quote)); + } + + Log.Debug("OnQuoteRemoved: end."); + } + + public void OnServerStopped() + { + Log.Debug("OnServerStopped: begin."); + + ServerDisconnected?.Invoke(this, EventArgs.Empty); + + Log.Debug("OnServerStopped: end."); + } + + + public void OnMtEvent(MtEvent e) + { + Log.DebugFormat("OnMtEvent: begin. event = {0}", e); + + if (MtEventReceived != null) + { + lastEventTask = lastEventTask.ContinueWith((t) => MtEventReceived.Invoke(e)); + } + + Log.Debug("OnMtEvent: end."); + } + + #endregion + + #region Properties + public string Host { get; private set; } + public int Port { get; private set; } + + private bool IsConnected => _proxy.State == CommunicationState.Opened; + + #endregion + + #region Private Methods + + private void ProxyFaulted(object sender, EventArgs e) + { + Log.Debug("ProxyFaulted: begin."); + + ServerFailed?.Invoke(this, EventArgs.Empty); + + Log.Debug("ProxyFaulted: end."); + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + Log.Debug("Dispose: begin."); + + _proxy.Dispose(); + + Log.Debug("Dispose: end."); + } + + #endregion + + #region Events + public event MtQuoteHandler QuoteAdded; + public event MtQuoteHandler QuoteRemoved; + public event MtQuoteHandler QuoteUpdated; + public event EventHandler ServerDisconnected; + public event EventHandler ServerFailed; + public event MtEventHandler MtEventReceived; + #endregion + } +} diff --git a/MtApiServiceNetCore/MtService.cs b/MtApiServiceNetCore/MtService.cs new file mode 100644 index 00000000..506fe9d4 --- /dev/null +++ b/MtApiServiceNetCore/MtService.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.ServiceModel; +using System.Threading; +using log4net; + +namespace MTApiService +{ + [ServiceContract(CallbackContract = typeof(IMtApiCallback), SessionMode = SessionMode.Required)] + public interface IMtApi + { + [OperationContract] + bool Connect(); + + [OperationContract(IsOneWay = true)] + void Disconnect(); + + [OperationContract] + MtResponse SendCommand(MtCommand command); + + [OperationContract] + List GetQuotes(); + } + + [ServiceContract] + public interface IMtApiCallback + { + [OperationContract(IsOneWay = true)] + void OnQuoteUpdate(MtQuote quote); + + [OperationContract(IsOneWay = true)] + void OnServerStopped(); + + [OperationContract(IsOneWay = true)] + void OnQuoteAdded(MtQuote quote); + + [OperationContract(IsOneWay = true)] + void OnQuoteRemoved(MtQuote quote); + + [OperationContract(IsOneWay = true)] + void OnMtEvent(MtEvent ntEvent); + } +} diff --git a/MtApiServiceNetCore/Properties/AssemblyInfo.cs b/MtApiServiceNetCore/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..3dc1eafc --- /dev/null +++ b/MtApiServiceNetCore/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MTApiService")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("DW")] +[assembly: AssemblyProduct("MTApiService")] +[assembly: AssemblyCopyright("Copyright © DW 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f1cc1516-9352-4ddd-811a-c5fc842b12d4")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.32.0")] +[assembly: AssemblyFileVersion("1.0.32.0")] \ No newline at end of file diff --git a/TestClients/MtApi5TestClient/MainWindow.xaml b/TestClients/MtApi5TestClient/MainWindow.xaml index 0a7cdbb4..78729d34 100755 --- a/TestClients/MtApi5TestClient/MainWindow.xaml +++ b/TestClients/MtApi5TestClient/MainWindow.xaml @@ -594,6 +594,8 @@