diff --git a/.gitignore b/.gitignore index a49625e3c..b5d4e16ef 100644 --- a/.gitignore +++ b/.gitignore @@ -57,7 +57,9 @@ _NCrunch_* .vs node_modules **/launchSettings.json +**/appsettings.json **/appsettings.*.json +!**/appsettings.Serilog.json **/docker-compose.dev.yml **/marginTradingSettings.json **/*.ps1 diff --git a/BenchmarkRunner/BenchmarkRunner.csproj b/BenchmarkRunner/BenchmarkRunner.csproj new file mode 100644 index 000000000..4269d8967 --- /dev/null +++ b/BenchmarkRunner/BenchmarkRunner.csproj @@ -0,0 +1,19 @@ + + + + Exe + netcoreapp2.2 + ExternalOrderbooksBenchmark + + + + + + + + + + + + + diff --git a/BenchmarkRunner/Program.cs b/BenchmarkRunner/Program.cs new file mode 100644 index 000000000..7297a1a71 --- /dev/null +++ b/BenchmarkRunner/Program.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using BenchmarkDotNet.Running; +using BenchmarkScenarios; + +namespace ExternalOrderbooksBenchmark +{ + class Program + { + static void Main(string[] args) + { + //var summary = BenchmarkRunner.Run(); + var summary = BenchmarkRunner.Run(); + } + } +} \ No newline at end of file diff --git a/BenchmarkScenarios/BenchmarkScenarios.csproj b/BenchmarkScenarios/BenchmarkScenarios.csproj new file mode 100644 index 000000000..45f794064 --- /dev/null +++ b/BenchmarkScenarios/BenchmarkScenarios.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp2.2 + + + + + + + + + + + + + diff --git a/BenchmarkScenarios/ConcurrentDictionaryVsReadWriteLockedDictionaryBenchmark.cs b/BenchmarkScenarios/ConcurrentDictionaryVsReadWriteLockedDictionaryBenchmark.cs new file mode 100644 index 000000000..b5833f3b0 --- /dev/null +++ b/BenchmarkScenarios/ConcurrentDictionaryVsReadWriteLockedDictionaryBenchmark.cs @@ -0,0 +1,112 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using MarginTrading.Backend.Core.Orderbooks; +using MarginTrading.Common.Helpers; + +namespace BenchmarkScenarios +{ + [CoreJob] + [RPlotExporter, RankColumn] + public class ConcurrentDictionaryVsReadWriteLockedDictionaryBenchmark + { + private static readonly ReadWriteLockedDictionary> RwlDictionary = + new ReadWriteLockedDictionary>(); + + private static readonly ConcurrentDictionary> ConcurDictionary = + new ConcurrentDictionary>(); + + private static readonly ExternalOrderBook OrderBook = new ExternalOrderBook( + "test", + "test", + DateTime.Now, + new [] {new VolumePrice() {Price = 1, Volume = 1}}, + new [] {new VolumePrice() {Price = 1, Volume = 1}} + ); + + private static readonly Action RwlActionAdd = () => + { + RwlDictionary.AddOrUpdate("test", + k => UpdateOrderbooksDictionary(k, new Dictionary()), + UpdateOrderbooksDictionary); + }; + + private static readonly Action RwlActionGet = () => + { + RwlDictionary.TryReadValue("test", (dataExist, assetPair, orderbooks) + => dataExist ? DoSomeJob(orderbooks) : null); + }; + + private static readonly Action ConcurActionAdd = () => + { + ConcurDictionary.AddOrUpdate("test", + k => UpdateOrderbooksDictionary(k, new Dictionary()), + UpdateOrderbooksDictionary); + }; + + private static readonly Action ConcurActionGet = () => + { + if (ConcurDictionary.TryGetValue("test", out var orderbooks)) + { + DoSomeJob(orderbooks); + } + }; + + private readonly List _rwlDictionaryActions = new List + { + RwlActionAdd, + RwlActionGet, + RwlActionGet, + RwlActionGet, + RwlActionGet, + }; + + private readonly List _concurDictionaryActions = new List + { + ConcurActionAdd, + ConcurActionGet, + ConcurActionGet, + ConcurActionGet, + ConcurActionGet, + }; + + private static decimal? DoSomeJob(Dictionary orderbooks) + { + return !orderbooks.TryGetValue("test", out var orderBook) + ? null + : orderBook.Asks.FirstOrDefault()?.Price; + } + + private static Dictionary UpdateOrderbooksDictionary(string assetPairId, + Dictionary dict) + { + dict[OrderBook.ExchangeName] = OrderBook; + + return dict; + } + + [Benchmark] + public void RwlDictionaryTest() + { + foreach (var action in _rwlDictionaryActions) + { + Task.Factory.StartNew(action); + } + } + + [Benchmark] + public void ConcurDictionaryTest() + { + foreach (var action in _concurDictionaryActions) + { + Task.Factory.StartNew(action); + } + } + } +} \ No newline at end of file diff --git a/BenchmarkScenarios/ExternalOrderbookServicesBenchmark.cs b/BenchmarkScenarios/ExternalOrderbookServicesBenchmark.cs new file mode 100644 index 000000000..677de2f61 --- /dev/null +++ b/BenchmarkScenarios/ExternalOrderbookServicesBenchmark.cs @@ -0,0 +1,106 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using BenchmarkDotNet.Attributes; +using Common.Log; +using Lykke.MarginTrading.OrderBookService.Contracts; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orderbooks; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Services.AssetPairs; +using MarginTrading.Backend.Services.Events; +using MarginTrading.Backend.Services.Infrastructure; +using MarginTrading.Backend.Services.Stp; +using MarginTrading.Common.Services; +using Moq; + +namespace BenchmarkScenarios +{ + [CoreJob] + [RPlotExporter, RankColumn] + public class ExternalOrderbookServicesBenchmark + { + private ExternalOrderbookService _service; + + private LightweightExternalOrderbookService _lightweightService; + + public ExternalOrderbookServicesBenchmark() + { + var doMock = new Mock(); + doMock.Setup(a => a.IsDayOff(It.IsAny())).Returns(true); + + _service = new ExternalOrderbookService( + Mock.Of>(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + doMock.Object, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + new MarginTradingSettings() {DefaultExternalExchangeId = "test"}); + + _lightweightService = new LightweightExternalOrderbookService( + Mock.Of>(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + doMock.Object, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + new MarginTradingSettings() {DefaultExternalExchangeId = "test"}); + } + + private static readonly ExternalOrderBook OrderBook = new ExternalOrderBook( + "test", + "test", + DateTime.Now, + new [] + { + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1} + }, + new [] + { + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1}, + new VolumePrice {Price = 1, Volume = 1}, + } + ); + + [Benchmark] + public void Default() + { + _service.SetOrderbook(OrderBook); + } + + [Benchmark] + public void Lightweight() + { + _lightweightService.SetOrderbook(OrderBook); + } + } +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index 8864d4a39..e01d32e6d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,18 +1,13 @@ -MIT License - -Copyright (c) 2017 +Copyright (c) 2019 Lykke Corp. Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal +of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER diff --git a/MarginTrading.sln b/MarginTrading.sln index 28654502a..a1930fe7b 100644 --- a/MarginTrading.sln +++ b/MarginTrading.sln @@ -13,12 +13,11 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution items", "Solution items", "{805BE832-6AA9-4026-8131-309A1C5B570C}" ProjectSection(SolutionItems) = preProject .gitignore = .gitignore + README.md = README.md EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.Common", "src\MarginTrading.Common\MarginTrading.Common.csproj", "{28D8D798-8EF9-48BC-9F10-6E7823343CC9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.Frontend", "src\MarginTrading.Frontend\MarginTrading.Frontend.csproj", "{23988B46-8A81-4324-B1F8-0FD2F5EFDD24}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.Backend", "src\MarginTrading.Backend\MarginTrading.Backend.csproj", "{E6A712C9-78F1-4573-B3A4-318FA4A6BA0D}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.Backend.Services", "src\MarginTrading.Backend.Services\MarginTrading.Backend.Services.csproj", "{5030E6AA-4DDE-41EC-AE8A-609C2D7D5D37}" @@ -29,32 +28,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.Backend.Core" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.AzureRepositories", "src\MarginTrading.AzureRepositories\MarginTrading.AzureRepositories.csproj", "{C78B5B26-4078-47FE-BCD1-CF6F9886A4FC}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5. Wamp test client", "5. Wamp test client", "{18A6F48C-BAC6-4D92-A9AD-764E4F8BEB82}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.Client", "src\MarginTrading.Client\MarginTrading.Client.csproj", "{47A3C5B2-289D-41B9-8B73-F2C5995227A9}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "6. Brokers", "6. Brokers", "{E74A349A-3D48-4345-80E7-FD4C462864F0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.AccountHistoryBroker", "src\MarginTrading.Brokers\MarginTrading.AccountHistoryBroker\MarginTrading.AccountHistoryBroker.csproj", "{08FFD2CD-50C4-4B84-BA9E-27667677E20C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.OrderHistoryBroker", "src\MarginTrading.Brokers\MarginTrading.OrderHistoryBroker\MarginTrading.OrderHistoryBroker.csproj", "{02819188-B172-400E-9DBA-9A7E5C4F6266}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.OrderRejectedBroker", "src\MarginTrading.Brokers\MarginTrading.OrderRejectedBroker\MarginTrading.OrderRejectedBroker.csproj", "{BE0E8F09-2A68-426E-B457-5AA929CD998F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.BrokerBase", "src\MarginTrading.Brokers\MarginTrading.BrokerBase\MarginTrading.BrokerBase.csproj", "{6B4B176C-58E3-4C66-96E2-91330192F238}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.AccountMarginEventsBroker", "src\MarginTrading.Brokers\MarginTrading.AccountMarginEventsBroker\MarginTrading.AccountMarginEventsBroker.csproj", "{2228A223-02B8-472C-98BC-8F71E8F0F604}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.DataReader", "src\MarginTrading.DataReader\MarginTrading.DataReader.csproj", "{88F0B500-1119-4292-A721-15B13BB1A4BA}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarginTrading.Contract", "src\MarginTrading.Contract\MarginTrading.Contract.csproj", "{08346EF4-8525-4CE0-93C4-CCE510D27D88}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarginTrading.Backend.Core.Mappers", "src\MarginTrading.Backend.Core.Mappers\MarginTrading.Backend.Core.Mappers.csproj", "{41948BD6-0CD9-4A2B-BAA3-02AD065AD52B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarginTrading.AccountReportsBroker", "src\MarginTrading.Brokers\MarginTrading.AccountReportsBroker\MarginTrading.AccountReportsBroker.csproj", "{9595DED9-58C9-4732-87B1-891AFC2282B1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarginTrading.Frontend.Tests", "tests\MarginTrading.Frontend.Tests\MarginTrading.Frontend.Tests.csproj", "{FE2B7AD8-4085-410A-8855-75F9FC650EFB}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarginTrading.Backend.Contracts", "src\MarginTrading.Backend.Contracts\MarginTrading.Backend.Contracts.csproj", "{5EC22A63-BA3F-41A2-A70F-216B7E809390}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3. Backend", "3. Backend", "{1EA2FA6D-0FEA-47D9-B8CE-EF31A1C54DCD}" @@ -65,7 +46,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarginTrading.ExternalOrder EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarginTrading.OrderbookBestPricesBroker", "src\MarginTrading.Brokers\MarginTrading.OrderbookBestPricesBroker\MarginTrading.OrderbookBestPricesBroker.csproj", "{B9DD492A-58CC-4716-879F-725AE2218AB7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarginTrading.MigrateApp", "src\MarginTrading.Brokers\MarginTrading.MigrateApp\MarginTrading.MigrateApp.csproj", "{921B4212-E0C8-4422-A689-A19095BD5CDC}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarginTrading.SqlRepositories", "src\MarginTrading.SqlRepositories\MarginTrading.SqlRepositories.csproj", "{115EFE4D-B269-4D62-944D-12C9FC1AE56A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarginTrading.BrokerBase", "src\MarginTrading.Brokers\MarginTrading.BrokerBase\MarginTrading.BrokerBase.csproj", "{970E8EC9-C56E-44A1-AD1D-8DEB653C404E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkRunner", "BenchmarkRunner\BenchmarkRunner.csproj", "{08EC2443-941D-4DA8-A7AE-F92D4BB12E0A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkScenarios", "BenchmarkScenarios\BenchmarkScenarios.csproj", "{A479EF23-D993-45A3-983D-948A4BC8067E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -77,10 +64,6 @@ Global {28D8D798-8EF9-48BC-9F10-6E7823343CC9}.Debug|Any CPU.Build.0 = Debug|Any CPU {28D8D798-8EF9-48BC-9F10-6E7823343CC9}.Release|Any CPU.ActiveCfg = Release|Any CPU {28D8D798-8EF9-48BC-9F10-6E7823343CC9}.Release|Any CPU.Build.0 = Release|Any CPU - {23988B46-8A81-4324-B1F8-0FD2F5EFDD24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23988B46-8A81-4324-B1F8-0FD2F5EFDD24}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23988B46-8A81-4324-B1F8-0FD2F5EFDD24}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23988B46-8A81-4324-B1F8-0FD2F5EFDD24}.Release|Any CPU.Build.0 = Release|Any CPU {E6A712C9-78F1-4573-B3A4-318FA4A6BA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E6A712C9-78F1-4573-B3A4-318FA4A6BA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU {E6A712C9-78F1-4573-B3A4-318FA4A6BA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -101,34 +84,10 @@ Global {C78B5B26-4078-47FE-BCD1-CF6F9886A4FC}.Debug|Any CPU.Build.0 = Debug|Any CPU {C78B5B26-4078-47FE-BCD1-CF6F9886A4FC}.Release|Any CPU.ActiveCfg = Release|Any CPU {C78B5B26-4078-47FE-BCD1-CF6F9886A4FC}.Release|Any CPU.Build.0 = Release|Any CPU - {47A3C5B2-289D-41B9-8B73-F2C5995227A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {47A3C5B2-289D-41B9-8B73-F2C5995227A9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {47A3C5B2-289D-41B9-8B73-F2C5995227A9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {47A3C5B2-289D-41B9-8B73-F2C5995227A9}.Release|Any CPU.Build.0 = Release|Any CPU - {08FFD2CD-50C4-4B84-BA9E-27667677E20C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {08FFD2CD-50C4-4B84-BA9E-27667677E20C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {08FFD2CD-50C4-4B84-BA9E-27667677E20C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {08FFD2CD-50C4-4B84-BA9E-27667677E20C}.Release|Any CPU.Build.0 = Release|Any CPU - {02819188-B172-400E-9DBA-9A7E5C4F6266}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {02819188-B172-400E-9DBA-9A7E5C4F6266}.Debug|Any CPU.Build.0 = Debug|Any CPU - {02819188-B172-400E-9DBA-9A7E5C4F6266}.Release|Any CPU.ActiveCfg = Release|Any CPU - {02819188-B172-400E-9DBA-9A7E5C4F6266}.Release|Any CPU.Build.0 = Release|Any CPU - {BE0E8F09-2A68-426E-B457-5AA929CD998F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BE0E8F09-2A68-426E-B457-5AA929CD998F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BE0E8F09-2A68-426E-B457-5AA929CD998F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BE0E8F09-2A68-426E-B457-5AA929CD998F}.Release|Any CPU.Build.0 = Release|Any CPU - {6B4B176C-58E3-4C66-96E2-91330192F238}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6B4B176C-58E3-4C66-96E2-91330192F238}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6B4B176C-58E3-4C66-96E2-91330192F238}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6B4B176C-58E3-4C66-96E2-91330192F238}.Release|Any CPU.Build.0 = Release|Any CPU {2228A223-02B8-472C-98BC-8F71E8F0F604}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2228A223-02B8-472C-98BC-8F71E8F0F604}.Debug|Any CPU.Build.0 = Debug|Any CPU {2228A223-02B8-472C-98BC-8F71E8F0F604}.Release|Any CPU.ActiveCfg = Release|Any CPU {2228A223-02B8-472C-98BC-8F71E8F0F604}.Release|Any CPU.Build.0 = Release|Any CPU - {88F0B500-1119-4292-A721-15B13BB1A4BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88F0B500-1119-4292-A721-15B13BB1A4BA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88F0B500-1119-4292-A721-15B13BB1A4BA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88F0B500-1119-4292-A721-15B13BB1A4BA}.Release|Any CPU.Build.0 = Release|Any CPU {08346EF4-8525-4CE0-93C4-CCE510D27D88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {08346EF4-8525-4CE0-93C4-CCE510D27D88}.Debug|Any CPU.Build.0 = Debug|Any CPU {08346EF4-8525-4CE0-93C4-CCE510D27D88}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -137,14 +96,6 @@ Global {41948BD6-0CD9-4A2B-BAA3-02AD065AD52B}.Debug|Any CPU.Build.0 = Debug|Any CPU {41948BD6-0CD9-4A2B-BAA3-02AD065AD52B}.Release|Any CPU.ActiveCfg = Release|Any CPU {41948BD6-0CD9-4A2B-BAA3-02AD065AD52B}.Release|Any CPU.Build.0 = Release|Any CPU - {9595DED9-58C9-4732-87B1-891AFC2282B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9595DED9-58C9-4732-87B1-891AFC2282B1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9595DED9-58C9-4732-87B1-891AFC2282B1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9595DED9-58C9-4732-87B1-891AFC2282B1}.Release|Any CPU.Build.0 = Release|Any CPU - {FE2B7AD8-4085-410A-8855-75F9FC650EFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FE2B7AD8-4085-410A-8855-75F9FC650EFB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FE2B7AD8-4085-410A-8855-75F9FC650EFB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FE2B7AD8-4085-410A-8855-75F9FC650EFB}.Release|Any CPU.Build.0 = Release|Any CPU {5EC22A63-BA3F-41A2-A70F-216B7E809390}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5EC22A63-BA3F-41A2-A70F-216B7E809390}.Debug|Any CPU.Build.0 = Debug|Any CPU {5EC22A63-BA3F-41A2-A70F-216B7E809390}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -161,29 +112,32 @@ Global {B9DD492A-58CC-4716-879F-725AE2218AB7}.Debug|Any CPU.Build.0 = Debug|Any CPU {B9DD492A-58CC-4716-879F-725AE2218AB7}.Release|Any CPU.ActiveCfg = Release|Any CPU {B9DD492A-58CC-4716-879F-725AE2218AB7}.Release|Any CPU.Build.0 = Release|Any CPU - {921B4212-E0C8-4422-A689-A19095BD5CDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {921B4212-E0C8-4422-A689-A19095BD5CDC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {921B4212-E0C8-4422-A689-A19095BD5CDC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {921B4212-E0C8-4422-A689-A19095BD5CDC}.Release|Any CPU.Build.0 = Release|Any CPU + {115EFE4D-B269-4D62-944D-12C9FC1AE56A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {115EFE4D-B269-4D62-944D-12C9FC1AE56A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {115EFE4D-B269-4D62-944D-12C9FC1AE56A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {115EFE4D-B269-4D62-944D-12C9FC1AE56A}.Release|Any CPU.Build.0 = Release|Any CPU + {970E8EC9-C56E-44A1-AD1D-8DEB653C404E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {970E8EC9-C56E-44A1-AD1D-8DEB653C404E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {970E8EC9-C56E-44A1-AD1D-8DEB653C404E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {970E8EC9-C56E-44A1-AD1D-8DEB653C404E}.Release|Any CPU.Build.0 = Release|Any CPU + {08EC2443-941D-4DA8-A7AE-F92D4BB12E0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08EC2443-941D-4DA8-A7AE-F92D4BB12E0A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08EC2443-941D-4DA8-A7AE-F92D4BB12E0A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08EC2443-941D-4DA8-A7AE-F92D4BB12E0A}.Release|Any CPU.Build.0 = Release|Any CPU + {A479EF23-D993-45A3-983D-948A4BC8067E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A479EF23-D993-45A3-983D-948A4BC8067E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A479EF23-D993-45A3-983D-948A4BC8067E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A479EF23-D993-45A3-983D-948A4BC8067E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {28D8D798-8EF9-48BC-9F10-6E7823343CC9} = {84F3857F-CE2C-4A87-ACB2-809570092CF6} - {23988B46-8A81-4324-B1F8-0FD2F5EFDD24} = {A1366CA2-DF4A-4E38-BD6C-895A399118B4} {AFC440C0-68F0-447F-9C04-7614E6323B22} = {F9EA3679-8D3F-4DC2-B0A9-08801E02EE70} {C78B5B26-4078-47FE-BCD1-CF6F9886A4FC} = {5AD188EF-3F19-4527-816C-A361746EE9C2} - {47A3C5B2-289D-41B9-8B73-F2C5995227A9} = {18A6F48C-BAC6-4D92-A9AD-764E4F8BEB82} - {08FFD2CD-50C4-4B84-BA9E-27667677E20C} = {E74A349A-3D48-4345-80E7-FD4C462864F0} - {02819188-B172-400E-9DBA-9A7E5C4F6266} = {E74A349A-3D48-4345-80E7-FD4C462864F0} - {BE0E8F09-2A68-426E-B457-5AA929CD998F} = {E74A349A-3D48-4345-80E7-FD4C462864F0} - {6B4B176C-58E3-4C66-96E2-91330192F238} = {E74A349A-3D48-4345-80E7-FD4C462864F0} {2228A223-02B8-472C-98BC-8F71E8F0F604} = {E74A349A-3D48-4345-80E7-FD4C462864F0} - {88F0B500-1119-4292-A721-15B13BB1A4BA} = {A1366CA2-DF4A-4E38-BD6C-895A399118B4} {08346EF4-8525-4CE0-93C4-CCE510D27D88} = {84F3857F-CE2C-4A87-ACB2-809570092CF6} - {9595DED9-58C9-4732-87B1-891AFC2282B1} = {E74A349A-3D48-4345-80E7-FD4C462864F0} - {FE2B7AD8-4085-410A-8855-75F9FC650EFB} = {F9EA3679-8D3F-4DC2-B0A9-08801E02EE70} {5EC22A63-BA3F-41A2-A70F-216B7E809390} = {1EA2FA6D-0FEA-47D9-B8CE-EF31A1C54DCD} {A3E8A724-1ACD-4C92-AC1E-91D4AFFD17FE} = {1EA2FA6D-0FEA-47D9-B8CE-EF31A1C54DCD} {41948BD6-0CD9-4A2B-BAA3-02AD065AD52B} = {1EA2FA6D-0FEA-47D9-B8CE-EF31A1C54DCD} @@ -192,7 +146,10 @@ Global {E6A712C9-78F1-4573-B3A4-318FA4A6BA0D} = {A1366CA2-DF4A-4E38-BD6C-895A399118B4} {AE4DA150-F395-4E80-B1A2-2D0D4FDB89D8} = {E74A349A-3D48-4345-80E7-FD4C462864F0} {B9DD492A-58CC-4716-879F-725AE2218AB7} = {E74A349A-3D48-4345-80E7-FD4C462864F0} - {921B4212-E0C8-4422-A689-A19095BD5CDC} = {E74A349A-3D48-4345-80E7-FD4C462864F0} + {115EFE4D-B269-4D62-944D-12C9FC1AE56A} = {5AD188EF-3F19-4527-816C-A361746EE9C2} + {970E8EC9-C56E-44A1-AD1D-8DEB653C404E} = {E74A349A-3D48-4345-80E7-FD4C462864F0} + {08EC2443-941D-4DA8-A7AE-F92D4BB12E0A} = {F9EA3679-8D3F-4DC2-B0A9-08801E02EE70} + {A479EF23-D993-45A3-983D-948A4BC8067E} = {F9EA3679-8D3F-4DC2-B0A9-08801E02EE70} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {16BCEC97-771D-46F8-BD02-379A8FC34CDD} diff --git a/MarginTrading.sln.DotSettings b/MarginTrading.sln.DotSettings new file mode 100644 index 000000000..5e9904edd --- /dev/null +++ b/MarginTrading.sln.DotSettings @@ -0,0 +1,12 @@ + + True + True + True + True + True + True + True + True + Copyright (c) 2019 Lykke Corp. +See the LICENSE file in the project root for more information. + \ No newline at end of file diff --git a/README.md b/README.md index 39af52c07..df01dc160 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,231 @@ -# README # +# MarginTrading.Backend, MarginTrading.AccountMarginEventsBroker # -This README would normally document whatever steps are necessary to get your application up and running. +Margin trading core API. Broker to pass margin and liquidation events from message queue to storage. +Below is the API description. -### What is this repository for? ### +## How to use in prod env? ## -* Quick summary -* Version -* [Learn Markdown](https://bitbucket.org/tutorials/markdowndemo) +1. Pull "mt-trading-core" docker image with a corresponding tag. +2. Configure environment variables according to "Environment variables" section. +3. Put secrets.json with endpoint data including the certificate: +```json +"Kestrel": { + "EndPoints": { + "HttpsInlineCertFile": { + "Url": "https://*:5130", + "Certificate": { + "Path": "", + "Password": "" + } + } +} +``` +4. Initialize all dependencies. +5. Run. -### How do I get set up? ### +## How to run for debug? ## -* Summary of set up -* Configuration -* Dependencies -* Database configuration -* How to run tests -* Deployment instructions +1. Clone repo to some directory. +2. In MarginTrading.Backend root create a appsettings.dev.json with settings. +3. Add environment variable "SettingsUrl": "appsettings.dev.json". +4. VPN to a corresponding env must be connected and all dependencies must be initialized. +5. Run. -### Contribution guidelines ### +## Startup process ## -* Writing tests -* Code review -* Other guidelines +1. Standard ASP.NET middlewares are initialised. +2. Settings are loaded. +3. Health checks are ran: +- StartupDeduplicationService checks a "TradingEngine:DeduplicationTimestamp" in Redis and +if DeduplicationTimestamp > (now - DeduplicationCheckPeriod) exception is thrown saying: +"Trading Engine failed to start due to deduplication validation failure". +- StartupQueuesCheckerService checks that OrderHistory and PositionHistory broker related queues are empty. +Queue names are set in settings StartupQueuesChecker section with OrderHistoryQueueName and PositionHistoryQueueName. +4. IoC container is built, all caches are warmed up. +5. Scheduled jobs are initialised. -### Who do I talk to? ### +### Dependencies ### -* Repo owner or admin -* Other community or team contact \ No newline at end of file +TBD + +### Configuration ### + +Kestrel configuration may be passed through appsettings.json, secrets or environment. +All variables and value constraints are default. For instance, to set host URL the following env variable may be set: +```json +{ + "Kestrel__EndPoints__Http__Url": "http://*:5030" +} +``` + +### Environment variables ### + +* *RESTART_ATTEMPTS_NUMBER* - number of restart attempts. If not set int.MaxValue is used. +* *RESTART_ATTEMPTS_INTERVAL_MS* - interval between restarts in milliseconds. If not set 10000 is used. +* *SettingsUrl* - defines URL of remote settings or path for local settings. + +### Settings ### + +Settings schema is: + +```json +{ + "AccountsManagementServiceClient": { + "ServiceUrl": "http://mt-account-management.mt.svc.cluster.local" + }, + "Jobs": { + "NotificationsHubName": "", + "NotificationsHubConnectionString": "" + }, + "MtBackend": { + "ApiKey": "MT Core backend api key", + "MtRabbitMqConnString": "amqp://login:password@rabbit-mt.mt.svc.cluster.local:5672", + "Db": { + "StorageMode": "SqlServer", + "LogsConnString": "logs connection string", + "MarginTradingConnString": "date connection string", + "StateConnString": "state connection string", + "SqlConnectionString": "sql connection string", + "OrdersHistorySqlConnectionString": "sql connection string", + "OrdersHistoryTableName": "OrdersHistory", + "PositionsHistorySqlConnectionString": "sql connection string", + "PositionsHistoryTableName": "PositionsHistory", + "QueryTimeouts": + { + "GetLastSnapshotTimeoutS": 120 + } + }, + "RabbitMqQueues": { + "OrderHistory": { + "ExchangeName": "lykke.mt.orderhistory" + }, + "OrderRejected": { + "ExchangeName": "lykke.mt.orderrejected" + }, + "OrderbookPrices": { + "ExchangeName": "lykke.mt.pricefeed" + }, + "AccountStopout": { + "ExchangeName": "lykke.mt.account.stopout" + }, + "AccountMarginEvents": { + "ExchangeName": "lykke.mt.account.marginevents" + }, + "AccountStats": { + "ExchangeName": "lykke.mt.account.stats" + }, + "Trades": { + "ExchangeName": "lykke.mt.trades" + }, + "PositionHistory": { + "ExchangeName": "lykke.mt.position.history" + }, + "ExternalOrder": { + "ExchangeName": "lykke.stpexchangeconnector.trades" + }, + "MarginTradingEnabledChanged": { + "ExchangeName": "lykke.mt.enabled.changed" + }, + "SettingsChanged": { + "ExchangeName": "MtCoreSettingsChanged" + } + }, + "FxRateRabbitMqSettings": { + "ConnectionString": "amqp://login:pwd@rabbit-mt.mt.svc.cluster.local:5672", + "ExchangeName": "lykke.stpexchangeconnector.fxRates" + }, + "StpAggregatorRabbitMqSettings": { + "ConnectionString": "amqp://login:pwd@rabbit-mt.mt.svc.cluster.local:5672", + "ExchangeName": "lykke.exchangeconnector.orderbooks", + "ConsumerCount": 10 + }, + "BlobPersistence": { + "QuotesDumpPeriodMilliseconds": 3400000, + "FxRatesDumpPeriodMilliseconds": 3500000, + "OrderbooksDumpPeriodMilliseconds": 3600000, + "OrdersDumpPeriodMilliseconds": 600000 + }, + "RequestLoggerSettings": { + "Enabled": false, + "MaxPartSize": 2048 + }, + "Telemetry": { + "LockMetricThreshold": 10 + }, + "ReportingEquivalentPricesSettings": [ + { + "LegalEntity": "Default", + "EquivalentAsset": "EUR" + }, + { + "LegalEntity": "UNKNOWN", + "EquivalentAsset": "USD" + } + ], + "UseAzureIdentityGenerator": false, + "WriteOperationLog": true, + "UseSerilog": false, + "ExchangeConnector": "FakeExchangeConnector", + "MaxMarketMakerLimitOrderAge": 3000000, + "Cqrs": { + "ConnectionString": "amqp://login:pwd@rabbit-mt.mt.svc.cluster.local:5672", + "RetryDelay": "00:00:02", + "EnvironmentName": "env name" + }, + "SpecialLiquidation": { + "Enabled": true, + "FakePrice": 5, + "PriceRequestTimeoutSec": 600, + "RetryTimeout": "00:01:00", + "VolumeThreshold": 1000, + "VolumeThresholdCurrency": "EUR" + }, + "ChaosKitty": { + "StateOfChaos": 0 + }, + "Throttling": { + "MarginCallThrottlingPeriodMin": 30, + "StopOutThrottlingPeriodMin": 1 + }, + "OvernightMargin": { + "ScheduleMarketId": "PlatformScheduleMarketId", + "OvernightMarginParameter": 100, + "WarnPeriodMinutes": 10, + "ActivationPeriodMinutes": 10 + }, + "PendingOrderRetriesThreshold": 100, + "RedisSettings": { + "Configuration": "redis conn str" + }, + "DeduplicationTimestampPeriod": "00:00:01", + "DeduplicationCheckPeriod": "00:00:02", + "StartupQueuesChecker": { + "ConnectionString": "amqp://login:pwd@rabbit-mt.mt.svc.cluster.local:5672", + "OrderHistoryQueueName": "lykke.mt.orderhistory.MarginTrading.TradingHistory.OrderHistoryBroker.DefaultEnv", + "PositionHistoryQueueName": "lykke.mt.position.history.MarginTrading.TradingHistory.PositionHistoryBroker.DefaultEnv.PositionsHistory" + } + }, + "MtStpExchangeConnectorClient": { + "ServiceUrl": "http://gavel.mt.svc.cluster.local:5019", + "ApiKey": "key" + }, + "OrderBookServiceClient": { + "ServiceUrl": "http://mt-orderbook-service.mt.svc.cluster.local" + }, + "SettingsServiceClient": { + "ServiceUrl": "http://mt-settings-service.mt.svc.cluster.local" + } +} +``` + +#### Optional sections of MtBackend #### +(with hardcoded default values): + +```json +"OrderbookValidation": { + "ValidateInstrumentStatusForTradingQuotes": false, + "ValidateInstrumentStatusForTradingFx": false, + "ValidateInstrumentStatusForEodQuotes": true, + "ValidateInstrumentStatusForEodFx": true +} +``` diff --git a/src/MarginTrading.AzureRepositories/AccountAssetsPairsRepository.cs b/src/MarginTrading.AzureRepositories/AccountAssetsPairsRepository.cs deleted file mode 100644 index 75e50e1ab..000000000 --- a/src/MarginTrading.AzureRepositories/AccountAssetsPairsRepository.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using AzureStorage; -using MarginTrading.AzureRepositories.Contract; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Settings; -using MarginTrading.Backend.Core.TradingConditions; -using Microsoft.WindowsAzure.Storage.Table; - -namespace MarginTrading.AzureRepositories -{ - public class AccountAssetPairEntity : TableEntity, IAccountAssetPair - { - public string TradingConditionId { get; set; } - public string BaseAssetId { get; set; } - public string Instrument => RowKey; - public int LeverageInit { get; set; } - public int LeverageMaintenance { get; set; } - decimal IAccountAssetPair.SwapLong => (decimal) SwapLong; - public double SwapLong { get; set; } - decimal IAccountAssetPair.SwapShort => (decimal) SwapShort; - public double SwapShort { get; set; } - decimal IAccountAssetPair.OvernightSwapLong => (decimal) OvernightSwapLong; - public double OvernightSwapLong { get; set; } - decimal IAccountAssetPair.OvernightSwapShort => (decimal) OvernightSwapShort; - public double OvernightSwapShort { get; set; } - decimal IAccountAssetPair.CommissionLong => (decimal) CommissionLong; - public double CommissionLong { get; set; } - decimal IAccountAssetPair.CommissionShort => (decimal) CommissionShort; - public double CommissionShort { get; set; } - decimal IAccountAssetPair.CommissionLot => (decimal) CommissionLot; - public double CommissionLot { get; set; } - decimal IAccountAssetPair.DeltaBid => (decimal) DeltaBid; - public double DeltaBid { get; set; } - decimal IAccountAssetPair.DeltaAsk => (decimal) DeltaAsk; - public double DeltaAsk { get; set; } - decimal IAccountAssetPair.DealLimit => (decimal) DealLimit; - public double DealLimit { get; set; } - decimal IAccountAssetPair.PositionLimit => (decimal) PositionLimit; - public double PositionLimit { get; set; } - - public static string GeneratePartitionKey(string tradingConditionId, string baseAssetId) - { - return $"{tradingConditionId}_{baseAssetId}"; - } - - public static string GenerateRowKey(string instrument) - { - return instrument; - } - - public static AccountAssetPairEntity Create(IAccountAssetPair src) - { - return new AccountAssetPairEntity - { - PartitionKey = GeneratePartitionKey(src.TradingConditionId, src.BaseAssetId), - RowKey = GenerateRowKey(src.Instrument), - TradingConditionId = src.TradingConditionId, - BaseAssetId = src.BaseAssetId, - LeverageInit = src.LeverageInit, - LeverageMaintenance = src.LeverageMaintenance, - SwapLong = (double) src.SwapLong, - SwapShort = (double) src.SwapShort, - OvernightSwapLong = (double) src.OvernightSwapLong, - OvernightSwapShort = (double) src.OvernightSwapShort, - CommissionLong = (double) src.CommissionLong, - CommissionShort = (double) src.CommissionShort, - CommissionLot = (double) src.CommissionLot, - DeltaBid = (double) src.DeltaBid, - DeltaAsk = (double) src.DeltaAsk, - DealLimit = (double) src.DealLimit, - PositionLimit = (double) src.PositionLimit - }; - } - } - - public class AccountAssetsPairsRepository : IAccountAssetPairsRepository - { - private readonly INoSQLTableStorage _tableStorage; - - public AccountAssetsPairsRepository(INoSQLTableStorage tableStorage) - { - _tableStorage = tableStorage; - } - - public async Task AddOrReplaceAsync(IAccountAssetPair accountAssetPair) - { - await _tableStorage.InsertOrReplaceAsync(AccountAssetPairEntity.Create(accountAssetPair)); - } - - public async Task GetAsync(string tradingConditionId, string baseAssetId, string assetPairId) - { - return await _tableStorage.GetDataAsync(AccountAssetPairEntity.GeneratePartitionKey(tradingConditionId, baseAssetId), - AccountAssetPairEntity.GenerateRowKey(assetPairId)); - } - - public async Task> GetAllAsync(string tradingConditionId, string baseAssetId) - { - return await _tableStorage.GetDataAsync(AccountAssetPairEntity.GeneratePartitionKey(tradingConditionId, baseAssetId)); - } - - public async Task> GetAllAsync() - { - return await _tableStorage.GetDataAsync(); - } - - public async Task Remove(string tradingConditionId, string baseAssetId, string assetPairId) - { - await _tableStorage.DeleteAsync( - AccountAssetPairEntity.GeneratePartitionKey(tradingConditionId, baseAssetId), - AccountAssetPairEntity.GenerateRowKey(assetPairId)); - } - - public async Task> AddAssetPairs(string tradingConditionId, string baseAssetId, - IEnumerable assetPairsIds, AccountAssetsSettings defaults) - { - var entitiesToAdd = assetPairsIds.Select(x => AccountAssetPairEntity.Create( - new AccountAssetPair - { - BaseAssetId = baseAssetId, - TradingConditionId = tradingConditionId, - Instrument = x, - CommissionLong = defaults.CommissionLong, - CommissionLot = defaults.CommissionLot, - CommissionShort = defaults.CommissionShort, - DealLimit = defaults.DealLimit, - DeltaAsk = defaults.DeltaAsk, - DeltaBid = defaults.DeltaBid, - LeverageInit = defaults.LeverageInit, - LeverageMaintenance = defaults.LeverageMaintenance, - PositionLimit = defaults.PositionLimit, - SwapLong = defaults.SwapLong, - SwapShort = defaults.SwapShort, - OvernightSwapLong = defaults.OvernightSwapLong, - OvernightSwapShort = defaults.OvernightSwapShort - })).ToArray(); - await _tableStorage.InsertAsync(entitiesToAdd); - - return entitiesToAdd; - } - } -} diff --git a/src/MarginTrading.AzureRepositories/AccountGroupRepository.cs b/src/MarginTrading.AzureRepositories/AccountGroupRepository.cs deleted file mode 100644 index 0ab6aae1f..000000000 --- a/src/MarginTrading.AzureRepositories/AccountGroupRepository.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using AzureStorage; -using MarginTrading.AzureRepositories.Contract; -using MarginTrading.Backend.Core.TradingConditions; -using Microsoft.WindowsAzure.Storage.Table; - -namespace MarginTrading.AzureRepositories -{ - public class AccountGroupEntity : TableEntity, IAccountGroup - { - public string TradingConditionId => PartitionKey; - public string BaseAssetId => RowKey; - - decimal IAccountGroup.MarginCall => (decimal) MarginCall; - public double MarginCall { get; set; } - decimal IAccountGroup.StopOut => (decimal) StopOut; - public double StopOut { get; set; } - decimal IAccountGroup.DepositTransferLimit => (decimal) DepositTransferLimit; - public double DepositTransferLimit { get; set; } - decimal IAccountGroup.ProfitWithdrawalLimit => (decimal) ProfitWithdrawalLimit; - public double ProfitWithdrawalLimit { get; set; } - - - public static string GeneratePartitionKey(string tradingConditionId) - { - return tradingConditionId; - } - - public static string GenerateRowKey(string baseAssetid) - { - return baseAssetid; - } - - public static AccountGroupEntity Create(IAccountGroup src) - { - return new AccountGroupEntity - { - PartitionKey = GeneratePartitionKey(src.TradingConditionId), - RowKey = GenerateRowKey(src.BaseAssetId), - MarginCall = (double) src.MarginCall, - StopOut = (double) src.StopOut, - DepositTransferLimit = (double) src.DepositTransferLimit, - ProfitWithdrawalLimit = (double) src.ProfitWithdrawalLimit - }; - } - } - - public class AccountGroupRepository : IAccountGroupRepository - { - private readonly INoSQLTableStorage _tableStorage; - - public AccountGroupRepository(INoSQLTableStorage tableStorage) - { - _tableStorage = tableStorage; - } - - public async Task AddOrReplaceAsync(IAccountGroup group) - { - await _tableStorage.InsertOrReplaceAsync(AccountGroupEntity.Create(group)); - } - - public async Task GetAsync(string tradingConditionId, string baseAssetId) - { - return await _tableStorage.GetDataAsync(AccountGroupEntity.GeneratePartitionKey(tradingConditionId), - AccountGroupEntity.GenerateRowKey(baseAssetId)); - } - - public async Task> GetAllAsync() - { - return await _tableStorage.GetDataAsync(); - } - } -} diff --git a/src/MarginTrading.AzureRepositories/AccountStatRepository.cs b/src/MarginTrading.AzureRepositories/AccountStatRepository.cs new file mode 100644 index 000000000..ab6723f86 --- /dev/null +++ b/src/MarginTrading.AzureRepositories/AccountStatRepository.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AzureStorage; +using AzureStorage.Tables; +using Common.Log; +using Lykke.SettingsReader; +using MarginTrading.AzureRepositories.Entities; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Common.Services; + +namespace MarginTrading.AzureRepositories +{ + public class AccountStatRepository : IAccountStatRepository + { + private readonly INoSQLTableStorage _tableStorage; + private readonly IDateService _dateService; + + public AccountStatRepository(IReloadingManager connectionStringManager, + ILog log, + IDateService dateService) + { + _tableStorage = AzureTableStorage.Create( + connectionStringManager, + "AccountStatDump", + log); + _dateService = dateService; + } + + public async Task Dump(IEnumerable accounts) + { + var reportTime = _dateService.Now(); + var entities = accounts.Select(x => AccountStatEntity.Create(x, reportTime)); + + await _tableStorage.DeleteAsync(); + await _tableStorage.CreateTableIfNotExistsAsync(); + await _tableStorage.InsertAsync(entities); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.AzureRepositories/AssetPairsRepository.cs b/src/MarginTrading.AzureRepositories/AssetPairsRepository.cs deleted file mode 100644 index 965761bcb..000000000 --- a/src/MarginTrading.AzureRepositories/AssetPairsRepository.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using AutoMapper; -using AzureStorage; -using Lykke.AzureStorage.Tables; -using MarginTrading.AzureRepositories.Contract; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.MatchingEngines; -using MarginTrading.Common.Services; - -namespace MarginTrading.AzureRepositories -{ - internal class AssetPairsRepository : IAssetPairsRepository - { - private readonly INoSQLTableStorage _tableStorage; - private readonly IConvertService _convertService; - - public AssetPairsRepository(INoSQLTableStorage tableStorage, - IConvertService convertService) - { - _tableStorage = tableStorage; - _convertService = convertService; - } - - public async Task> GetAsync() - { - return Convert(await _tableStorage.GetDataAsync()); - } - - public Task InsertAsync(IAssetPair settings) - { - return _tableStorage.InsertAsync(Convert(settings)); - } - - public Task ReplaceAsync(IAssetPair settings) - { - return _tableStorage.ReplaceAsync(Convert(settings)); - } - - public async Task DeleteAsync(string assetPairId) - { - return Convert(await _tableStorage.DeleteAsync(AssetPairEntity.GeneratePartitionKey(), - AssetPairEntity.GenerateRowKey(assetPairId))); - } - - public async Task GetAsync(string assetPairId) - { - return Convert(await _tableStorage.GetDataAsync(AssetPairEntity.GeneratePartitionKey(), - AssetPairEntity.GenerateRowKey(assetPairId))); - } - - private static IReadOnlyList Convert( - IEnumerable accountAssetPairEntities) - { - return accountAssetPairEntities.ToList(); - } - - private AssetPairEntity Convert(IAssetPair accountAssetPair) - { - return _convertService.Convert(accountAssetPair, - o => o.ConfigureMap(MemberList.Source).ForMember(e => e.ETag, e => e.UseValue("*"))); - } - - internal class AssetPairEntity : AzureTableEntity, IAssetPair - { - public AssetPairEntity() - { - PartitionKey = GeneratePartitionKey(); - } - - public string Id - { - get => RowKey; - set => RowKey = value; - } - - public string Name { get; set; } - public string BaseAssetId { get; set; } - public string QuoteAssetId { get; set; } - public int Accuracy { get; set; } - - public string LegalEntity { get; set; } - public string BasePairId { get; set; } - public MatchingEngineMode MatchingEngineMode { get; set; } - public decimal StpMultiplierMarkupBid { get; set; } - public decimal StpMultiplierMarkupAsk { get; set; } - - public static string GeneratePartitionKey() - { - return "AssetPairs"; - } - - public static string GenerateRowKey(string assetPairId) - { - return assetPairId; - } - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.AzureRepositories/AzureIdentityGenerator.cs b/src/MarginTrading.AzureRepositories/AzureIdentityGenerator.cs index 7f9706f5a..6b081e6a8 100644 --- a/src/MarginTrading.AzureRepositories/AzureIdentityGenerator.cs +++ b/src/MarginTrading.AzureRepositories/AzureIdentityGenerator.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Threading.Tasks; using AzureStorage; using MarginTrading.AzureRepositories.Entities; @@ -37,5 +40,15 @@ public async Task GenerateIdAsync(string entityType) return id; } + + public string GenerateAlphanumericId() + { + return Guid.NewGuid().ToString("N"); + } + + public string GenerateGuid() + { + return Guid.NewGuid().ToString("N"); + } } } \ No newline at end of file diff --git a/src/MarginTrading.AzureRepositories/AzureRepoFactories.cs b/src/MarginTrading.AzureRepositories/AzureRepoFactories.cs index 3079c259d..39cd54756 100644 --- a/src/MarginTrading.AzureRepositories/AzureRepoFactories.cs +++ b/src/MarginTrading.AzureRepositories/AzureRepoFactories.cs @@ -1,10 +1,10 @@ -using AzureStorage.Tables; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using AzureStorage.Tables; using Common.Log; using Lykke.SettingsReader; -using MarginTrading.AzureRepositories.Contract; -using MarginTrading.AzureRepositories.Entities; using MarginTrading.AzureRepositories.Logs; -using MarginTrading.Backend.Core; using MarginTrading.Common.Services; namespace MarginTrading.AzureRepositories @@ -13,35 +13,6 @@ public class AzureRepoFactories { public static class MarginTrading { - public static TradingConditionsRepository CreateTradingConditionsRepository(IReloadingManager connString, ILog log) - { - return new TradingConditionsRepository(AzureTableStorage.Create(connString, - "MarginTradingConditions", log)); - } - - public static AccountGroupRepository CreateAccountGroupRepository(IReloadingManager connString, ILog log) - { - return new AccountGroupRepository(AzureTableStorage.Create(connString, - "MarginTradingAccountGroups", log)); - } - - public static AccountAssetsPairsRepository CreateAccountAssetsRepository(IReloadingManager connString, ILog log) - { - return new AccountAssetsPairsRepository(AzureTableStorage.Create(connString, - "MarginTradingAccountAssets", log)); - } - - public static MarginTradingOrdersHistoryRepository CreateOrdersHistoryRepository(IReloadingManager connString, ILog log) - { - return new MarginTradingOrdersHistoryRepository(AzureTableStorage.Create(connString, - "MarginTradingOrdersHistory", log)); - } - - public static MarginTradingOrdersRejectedRepository CreateOrdersRejectedRepository(IReloadingManager connString, ILog log) - { - return new MarginTradingOrdersRejectedRepository(AzureTableStorage.Create(connString, - "MarginTradingOrdersRejected", log)); - } public static MarginTradingAccountHistoryRepository CreateAccountHistoryRepository(IReloadingManager connString, ILog log) { @@ -49,12 +20,6 @@ public static MarginTradingAccountHistoryRepository CreateAccountHistoryReposito "AccountsHistory", log)); } - public static MarginTradingAccountsRepository CreateAccountsRepository(IReloadingManager connString, ILog log) - { - return new MarginTradingAccountsRepository(AzureTableStorage.Create(connString, - "MarginTradingAccounts", log)); - } - public static MarginTradingAccountStatsRepository CreateAccountStatsRepository(IReloadingManager connString, ILog log) { return new MarginTradingAccountStatsRepository(AzureTableStorage.Create(connString, @@ -66,42 +31,11 @@ public static MarginTradingBlobRepository CreateBlobRepository(IReloadingManager return new MarginTradingBlobRepository(connString); } - public static MatchingEngineRoutesRepository CreateMatchingEngineRoutesRepository(IReloadingManager connString, ILog log) - { - return new MatchingEngineRoutesRepository(AzureTableStorage.Create(connString, - "MatchingEngineRoutes", log)); - } - public static RiskSystemCommandsLogRepository CreateRiskSystemCommandsLogRepository(IReloadingManager connString, ILog log) { return new RiskSystemCommandsLogRepository(AzureTableStorage.Create(connString, "RiskSystemCommandsLog", log)); } - - public static OvernightSwapStateRepository CreateOvernightSwapStateRepository(IReloadingManager connString, ILog log) - { - return new OvernightSwapStateRepository(AzureTableStorage.Create(connString, - "OvernightSwapState", log)); - } - - public static OvernightSwapHistoryRepository CreateOvernightSwapHistoryRepository(IReloadingManager connString, ILog log) - { - return new OvernightSwapHistoryRepository(AzureTableStorage.Create(connString, - "OvernightSwapHistory", log)); - } - - public static IDayOffSettingsRepository CreateDayOffSettingsRepository(IReloadingManager connString) - { - return new DayOffSettingsRepository(new MarginTradingBlobRepository(connString)); - } - - public static IAssetPairsRepository CreateAssetPairSettingsRepository(IReloadingManager connString, - ILog log, IConvertService convertService) - { - return new AssetPairsRepository( - AzureTableStorage.Create(connString, - "AssetPairs", log), convertService); - } } } } \ No newline at end of file diff --git a/src/MarginTrading.AzureRepositories/Contract/IAccountAssetPairsRepository.cs b/src/MarginTrading.AzureRepositories/Contract/IAccountAssetPairsRepository.cs deleted file mode 100644 index f6364e658..000000000 --- a/src/MarginTrading.AzureRepositories/Contract/IAccountAssetPairsRepository.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Settings; -using MarginTrading.Backend.Core.TradingConditions; - -namespace MarginTrading.AzureRepositories.Contract -{ - public interface IAccountAssetPairsRepository - { - Task AddOrReplaceAsync(IAccountAssetPair accountAssetPair); - Task GetAsync(string tradingConditionId, string baseAssetId, string assetPairId); - Task> GetAllAsync(string tradingConditionId, string baseAssetId); - Task> GetAllAsync(); - Task> AddAssetPairs(string tradingConditionId, string baseAssetId, IEnumerable assetPairsIds, - AccountAssetsSettings defaults); - Task Remove(string tradingConditionId, string baseAssetId, string assetPairId); - } -} \ No newline at end of file diff --git a/src/MarginTrading.AzureRepositories/Contract/IAccountGroupRepository.cs b/src/MarginTrading.AzureRepositories/Contract/IAccountGroupRepository.cs deleted file mode 100644 index 0d298e7ed..000000000 --- a/src/MarginTrading.AzureRepositories/Contract/IAccountGroupRepository.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using MarginTrading.Backend.Core.TradingConditions; - -namespace MarginTrading.AzureRepositories.Contract -{ - public interface IAccountGroupRepository - { - Task AddOrReplaceAsync(IAccountGroup group); - Task GetAsync(string tradingConditionId, string baseAssetId); - Task> GetAllAsync(); - } -} diff --git a/src/MarginTrading.AzureRepositories/Contract/IAssetPairsRepository.cs b/src/MarginTrading.AzureRepositories/Contract/IAssetPairsRepository.cs deleted file mode 100644 index fc8535a55..000000000 --- a/src/MarginTrading.AzureRepositories/Contract/IAssetPairsRepository.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using MarginTrading.Backend.Core; - -namespace MarginTrading.AzureRepositories.Contract -{ - public interface IAssetPairsRepository - { - Task> GetAsync(); - Task InsertAsync(IAssetPair settings); - Task ReplaceAsync(IAssetPair settings); - Task DeleteAsync(string assetPairId); - Task GetAsync(string assetPairId); - } -} \ No newline at end of file diff --git a/src/MarginTrading.AzureRepositories/Contract/ITradingConditionRepository.cs b/src/MarginTrading.AzureRepositories/Contract/ITradingConditionRepository.cs deleted file mode 100644 index a1998603e..000000000 --- a/src/MarginTrading.AzureRepositories/Contract/ITradingConditionRepository.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.TradingConditions; - -namespace MarginTrading.AzureRepositories.Contract -{ - public interface ITradingConditionRepository - { - Task AddOrReplaceAsync(ITradingCondition condition); - Task GetAsync(string tradingConditionId); - Task> GetAllAsync(); - } -} diff --git a/src/MarginTrading.AzureRepositories/DayOffSettingsRepository.cs b/src/MarginTrading.AzureRepositories/DayOffSettingsRepository.cs deleted file mode 100644 index 37cd8c1a9..000000000 --- a/src/MarginTrading.AzureRepositories/DayOffSettingsRepository.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.DayOffSettings; - -namespace MarginTrading.AzureRepositories -{ - internal class DayOffSettingsRepository : IDayOffSettingsRepository - { - private const string BlobContainer = "mt-dayoff-settings"; - private const string Key = "DayOffSettingsRoot"; - private readonly IMarginTradingBlobRepository _blobRepository; - - public DayOffSettingsRepository(IMarginTradingBlobRepository blobRepository) - { - _blobRepository = blobRepository; - } - - public DayOffSettingsRoot Read() - { - return Convert(_blobRepository.Read(BlobContainer, Key)); - } - - public void Write(DayOffSettingsRoot settings) - { - _blobRepository.Write(BlobContainer, Key, Convert(settings)); - } - - private static DayOffSettingsRootStorageModel Convert(DayOffSettingsRoot settingsRoot) - { - return new DayOffSettingsRootStorageModel - { - Exclusions = settingsRoot.Exclusions.ToImmutableDictionary(d => d.Key, d => - new DayOffExclusionStorageModel - { - AssetPairRegex = d.Value.AssetPairRegex, - Id = d.Value.Id, - Start = d.Value.Start, - End = d.Value.End, - IsTradeEnabled = d.Value.IsTradeEnabled, - }), - ScheduleSettings = new ScheduleSettingsStorageModel - { - AssetPairsWithoutDayOff = settingsRoot.ScheduleSettings.AssetPairsWithoutDayOff, - DayOffEndDay = settingsRoot.ScheduleSettings.DayOffEndDay, - DayOffEndTime = settingsRoot.ScheduleSettings.DayOffEndTime, - DayOffStartDay = settingsRoot.ScheduleSettings.DayOffStartDay, - DayOffStartTime = settingsRoot.ScheduleSettings.DayOffStartTime, - PendingOrdersCutOff = settingsRoot.ScheduleSettings.PendingOrdersCutOff, - } - }; - } - - private static DayOffSettingsRoot Convert(DayOffSettingsRootStorageModel settings) - { - if (settings == null) - return null; - - return new DayOffSettingsRoot( - settings.Exclusions.ToImmutableDictionary(s => s.Key, s => - new DayOffExclusion( - id: s.Value.Id, - assetPairRegex: s.Value.AssetPairRegex, - start: s.Value.Start, - end: s.Value.End, - isTradeEnabled: s.Value.IsTradeEnabled)), - new ScheduleSettings( - dayOffStartDay: settings.ScheduleSettings.DayOffStartDay, - dayOffStartTime: settings.ScheduleSettings.DayOffStartTime, - dayOffEndDay: settings.ScheduleSettings.DayOffEndDay, - dayOffEndTime: settings.ScheduleSettings.DayOffEndTime, - assetPairsWithoutDayOff: settings.ScheduleSettings.AssetPairsWithoutDayOff, - pendingOrdersCutOff: settings.ScheduleSettings.PendingOrdersCutOff)); - } - - public class DayOffSettingsRootStorageModel - { - public ImmutableDictionary Exclusions { get; set; } - public ScheduleSettingsStorageModel ScheduleSettings { get; set; } - } - - public class DayOffExclusionStorageModel - { - public Guid Id { get; set; } - public string AssetPairRegex { get; set; } - public DateTime Start { get; set; } - public DateTime End { get; set; } - public bool IsTradeEnabled { get; set; } - } - - public class ScheduleSettingsStorageModel - { - public DayOfWeek DayOffStartDay { get; set; } - public TimeSpan DayOffStartTime { get; set; } - public DayOfWeek DayOffEndDay { get; set; } - public TimeSpan DayOffEndTime { get; set; } - public HashSet AssetPairsWithoutDayOff { get; set; } - public TimeSpan PendingOrdersCutOff { get; set; } - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.AzureRepositories/Entities/AccountStatEntity.cs b/src/MarginTrading.AzureRepositories/Entities/AccountStatEntity.cs new file mode 100644 index 000000000..99531b272 --- /dev/null +++ b/src/MarginTrading.AzureRepositories/Entities/AccountStatEntity.cs @@ -0,0 +1,48 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using Lykke.AzureStorage.Tables; +using MarginTrading.Backend.Core; + +namespace MarginTrading.AzureRepositories.Entities +{ + public class AccountStatEntity : AzureTableEntity + { + public string Id => RowKey; + + public decimal PnL { get; set; } + public decimal UnrealizedDailyPnl { get; set; } + public decimal UsedMargin { get; set; } + public decimal MarginInit { get; set; } + public int OpenPositionsCount { get; set; } + public decimal MarginCall1Level { get; set; } + public decimal MarginCall2Level { get; set; } + public decimal StopoutLevel { get; set; } + + public decimal WithdrawalFrozenMargin { get; set; } + public decimal UnconfirmedMargin { get; set; } + + public DateTime HistoryTimestamp { get; set; } + + public static AccountStatEntity Create(MarginTradingAccount account, DateTime now) + { + return new AccountStatEntity + { + PartitionKey = "AccountStats", + RowKey = account.Id, + PnL = account.GetPnl(), + UnrealizedDailyPnl = account.GetUnrealizedDailyPnl(), + UsedMargin = account.GetUsedMargin(), + MarginInit = account.GetMarginInit(), + OpenPositionsCount = account.GetOpenPositionsCount(), + MarginCall1Level = account.GetMarginCall1Level(), + MarginCall2Level = account.GetMarginCall2Level(), + StopoutLevel = account.GetStopOutLevel(), + WithdrawalFrozenMargin = account.GetFrozenMargin(), + UnconfirmedMargin = account.GetUnconfirmedMargin(), + HistoryTimestamp = now, + }; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.AzureRepositories/Entities/IdentityEntity.cs b/src/MarginTrading.AzureRepositories/Entities/IdentityEntity.cs index f36e8cf75..e073e94b1 100644 --- a/src/MarginTrading.AzureRepositories/Entities/IdentityEntity.cs +++ b/src/MarginTrading.AzureRepositories/Entities/IdentityEntity.cs @@ -1,4 +1,7 @@ -using Microsoft.WindowsAzure.Storage.Table; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Microsoft.WindowsAzure.Storage.Table; namespace MarginTrading.AzureRepositories.Entities { diff --git a/src/MarginTrading.AzureRepositories/Entities/OpenPositionEntity.cs b/src/MarginTrading.AzureRepositories/Entities/OpenPositionEntity.cs new file mode 100644 index 000000000..86c1c6678 --- /dev/null +++ b/src/MarginTrading.AzureRepositories/Entities/OpenPositionEntity.cs @@ -0,0 +1,105 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using AzureStorage.Tables; +using Common; +using Lykke.AzureStorage.Tables; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orders; + +namespace MarginTrading.AzureRepositories.Entities +{ + public class OpenPositionEntity : AzureTableEntity + { + public string Id => RowKey; + public long Code { get; set; } + public string AssetPairId { get; set; } + public string Direction { get; set; } + public decimal Volume { get; set; } + public string AccountId { get; set; } + public string TradingConditionId { get; set; } + public string AccountAssetId { get; set; } + public decimal? ExpectedOpenPrice { get; set; } + public string OpenMatchingEngineId { get; set; } + public DateTime OpenDate { get; set; } + public string OpenTradeId { get; set; } + public decimal OpenPrice { get; set; } + public decimal OpenFxPrice { get; set; } + public string EquivalentAsset { get; set; } + public decimal OpenPriceEquivalent { get; set; } + public string LegalEntity { get; set; } + public string OpenOriginator { get; set; } + public string ExternalProviderId { get; set; } + public decimal SwapCommissionRate { get; set; } + public decimal OpenCommissionRate { get; set; } + public decimal CloseCommissionRate { get; set; } + public decimal CommissionLot { get; set; } + public string CloseMatchingEngineId { get; set; } + public decimal ClosePrice { get; set; } + public decimal CloseFxPrice { get; set; } + public decimal ClosePriceEquivalent { get; set; } + public DateTime? StartClosingDate { get; set; } + public DateTime? CloseDate { get; set; } + public string CloseOriginator { get; set; } + public string CloseReason { get; set; } + public string CloseComment { get; set; } + public string CloseTrades { get; set; } + public DateTime? LastModified { get; set; } + public decimal TotalPnL { get; set; } + public decimal ChargedPnL { get; set; } + public decimal Margin { get; set; } + + public string RelatedOrders { get; set; } + + public DateTime HistoryTimestamp { get; set; } + + public static OpenPositionEntity Create(Position position, DateTime now) + { + return new OpenPositionEntity + { + PartitionKey = "OpenPositions", + RowKey = position.Id, + AccountAssetId = position.AccountAssetId, + AccountId = position.AccountId, + AssetPairId = position.AssetPairId, + CloseComment = position.CloseComment, + CloseCommissionRate = position.CloseCommissionRate, + CloseDate = position.CloseDate, + CloseFxPrice = position.CloseFxPrice, + CloseMatchingEngineId = position.CloseMatchingEngineId, + CloseOriginator = position.CloseOriginator?.ToString(), + ClosePrice = position.ClosePrice, + ClosePriceEquivalent = position.ClosePriceEquivalent, + CloseReason = position.CloseReason.ToString(), + CloseTrades = position.CloseTrades.ToJson(), + Code = position.Code, + CommissionLot = position.CommissionLot, + Direction = position.Direction.ToString(), + EquivalentAsset = position.EquivalentAsset, + ExpectedOpenPrice = position.ExpectedOpenPrice, + ExternalProviderId = position.ExternalProviderId, + LastModified = position.LastModified, + LegalEntity = position.LegalEntity, + OpenCommissionRate = position.OpenCommissionRate, + OpenDate = position.OpenDate, + OpenFxPrice = position.OpenFxPrice, + OpenMatchingEngineId = position.OpenMatchingEngineId, + OpenOriginator = position.OpenOriginator.ToString(), + OpenPrice = position.OpenPrice, + OpenPriceEquivalent = position.OpenPriceEquivalent, + OpenTradeId = position.OpenTradeId, + RelatedOrders = position.RelatedOrders.ToJson(), + StartClosingDate = position.CloseDate, + SwapCommissionRate = position.SwapCommissionRate, + TotalPnL = position.GetFpl(), + ChargedPnL = position.ChargedPnL, + Margin = position.GetMarginMaintenance(), + TradingConditionId = position.TradingConditionId, + Volume = position.Volume, + HistoryTimestamp = now, + }; + } + } +} diff --git a/src/MarginTrading.AzureRepositories/Entities/OperationExecutionInfoEntity.cs b/src/MarginTrading.AzureRepositories/Entities/OperationExecutionInfoEntity.cs new file mode 100644 index 000000000..363ae2877 --- /dev/null +++ b/src/MarginTrading.AzureRepositories/Entities/OperationExecutionInfoEntity.cs @@ -0,0 +1,40 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using Lykke.AzureStorage.Tables; +using MarginTrading.Backend.Core; +using Newtonsoft.Json; + +namespace MarginTrading.AzureRepositories.Entities +{ + public class OperationExecutionInfoEntity : AzureTableEntity, IOperationExecutionInfo + { + public string OperationName + { + get => PartitionKey; + set => PartitionKey = value; + } + + public string Id + { + get => RowKey; + set => RowKey = value; + } + + public DateTime LastModified { get; set; } + + object IOperationExecutionInfo.Data => JsonConvert.DeserializeObject(Data); + public string Data { get; set; } + + public static string GeneratePartitionKey(string operationName) + { + return operationName; + } + + public static string GenerateRowKey(string id) + { + return id; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.AzureRepositories/Entities/OvernightSwapHistoryEntity.cs b/src/MarginTrading.AzureRepositories/Entities/OvernightSwapHistoryEntity.cs deleted file mode 100644 index 7eb8063a1..000000000 --- a/src/MarginTrading.AzureRepositories/Entities/OvernightSwapHistoryEntity.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using MarginTrading.Backend.Core; -using Microsoft.WindowsAzure.Storage.Table; -using Newtonsoft.Json; - -namespace MarginTrading.AzureRepositories.Entities -{ - public class OvernightSwapHistoryEntity : TableEntity, IOvernightSwapHistory - { - public string ClientId { get; set; } - public string AccountId { get; set; } - public string Instrument { get; set; } - public string Direction { get; set; } - OrderDirection? IOvernightSwapState.Direction => - Enum.TryParse(Direction, out var direction) ? direction : (OrderDirection?)null; - public DateTime Time { get; set; } - public double Volume { get; set; } - decimal IOvernightSwapState.Volume => (decimal) Volume; - public string OpenOrderIds { get; set; } - List IOvernightSwapState.OpenOrderIds => JsonConvert.DeserializeObject>(OpenOrderIds); - public double Value { get; set; } - decimal IOvernightSwapState.Value => (decimal) Value; - public double SwapRate { get; set; } - decimal IOvernightSwapState.SwapRate => (decimal) SwapRate; - - public bool IsSuccess { get; set; } - public string Exception { get; set; } - Exception IOvernightSwapHistory.Exception => JsonConvert.DeserializeObject(Exception); - - public static OvernightSwapHistoryEntity Create(IOvernightSwapHistory obj) - { - return new OvernightSwapHistoryEntity - { - PartitionKey = obj.AccountId, - RowKey = $"{obj.Time:O}", - ClientId = obj.ClientId, - AccountId = obj.AccountId, - Instrument = obj.Instrument, - Direction = obj.Direction?.ToString(), - Time = obj.Time, - Volume = (double) obj.Volume, - Value = (double) obj.Value, - SwapRate = (double) obj.SwapRate, - OpenOrderIds = JsonConvert.SerializeObject(obj.OpenOrderIds), - IsSuccess = obj.IsSuccess, - Exception = JsonConvert.SerializeObject(obj.Exception) - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.AzureRepositories/Entities/OvernightSwapStateEntity.cs b/src/MarginTrading.AzureRepositories/Entities/OvernightSwapStateEntity.cs deleted file mode 100644 index 71b84200e..000000000 --- a/src/MarginTrading.AzureRepositories/Entities/OvernightSwapStateEntity.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Collections.Generic; -using MarginTrading.Backend.Core; -using Microsoft.WindowsAzure.Storage.Table; -using Newtonsoft.Json; - -namespace MarginTrading.AzureRepositories.Entities -{ - public class OvernightSwapStateEntity : TableEntity, IOvernightSwapState - { - public string ClientId { get; set; } - public string AccountId { get; set; } - public string Instrument { get; set; } - public string Direction { get; set; } - OrderDirection? IOvernightSwapState.Direction => - Enum.TryParse(Direction, out var direction) ? direction : (OrderDirection?)null; - public DateTime Time { get; set; } - public double Volume { get; set; } - decimal IOvernightSwapState.Volume => (decimal) Volume; - public string OpenOrderIds { get; set; } - List IOvernightSwapState.OpenOrderIds => JsonConvert.DeserializeObject>(OpenOrderIds); - public double Value { get; set; } - decimal IOvernightSwapState.Value => (decimal) Value; - public double SwapRate { get; set; } - decimal IOvernightSwapState.SwapRate => (decimal) SwapRate; - - public static string GetKey(string accountId, string instrument, OrderDirection? direction) => - $"{accountId}_{instrument ?? ""}_{direction?.ToString() ?? ""}"; - - public static OvernightSwapStateEntity Create(IOvernightSwapState obj) - { - return new OvernightSwapStateEntity - { - PartitionKey = obj.AccountId, - RowKey = GetKey(obj.AccountId, obj.Instrument, obj.Direction), - ClientId = obj.ClientId, - AccountId = obj.AccountId, - Instrument = obj.Instrument, - Direction = obj.Direction?.ToString(), - Time = obj.Time, - Volume = (double) obj.Volume, - Value = (double) obj.Value, - SwapRate = (double) obj.SwapRate, - OpenOrderIds = JsonConvert.SerializeObject(obj.OpenOrderIds), - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.AzureRepositories/Helpers/BatchEntityInsertHelper.cs b/src/MarginTrading.AzureRepositories/Helpers/BatchEntityInsertHelper.cs index 4242a2b7d..5e5f8b454 100644 --- a/src/MarginTrading.AzureRepositories/Helpers/BatchEntityInsertHelper.cs +++ b/src/MarginTrading.AzureRepositories/Helpers/BatchEntityInsertHelper.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Linq; using Microsoft.WindowsAzure.Storage.Table; diff --git a/src/MarginTrading.AzureRepositories/Logs/IRiskSystemCommandsLogRepository.cs b/src/MarginTrading.AzureRepositories/Logs/IRiskSystemCommandsLogRepository.cs index 13814a176..a1ff63f75 100644 --- a/src/MarginTrading.AzureRepositories/Logs/IRiskSystemCommandsLogRepository.cs +++ b/src/MarginTrading.AzureRepositories/Logs/IRiskSystemCommandsLogRepository.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; namespace MarginTrading.AzureRepositories.Logs { diff --git a/src/MarginTrading.AzureRepositories/Logs/RiskSystemCommandsLogEntity.cs b/src/MarginTrading.AzureRepositories/Logs/RiskSystemCommandsLogEntity.cs index f95d4d3fe..cde267a4b 100644 --- a/src/MarginTrading.AzureRepositories/Logs/RiskSystemCommandsLogEntity.cs +++ b/src/MarginTrading.AzureRepositories/Logs/RiskSystemCommandsLogEntity.cs @@ -1,4 +1,7 @@ -using Microsoft.WindowsAzure.Storage.Table; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Microsoft.WindowsAzure.Storage.Table; namespace MarginTrading.AzureRepositories.Logs { diff --git a/src/MarginTrading.AzureRepositories/Logs/RiskSystemCommandsLogRepository.cs b/src/MarginTrading.AzureRepositories/Logs/RiskSystemCommandsLogRepository.cs index 25caa2857..12d92a850 100644 --- a/src/MarginTrading.AzureRepositories/Logs/RiskSystemCommandsLogRepository.cs +++ b/src/MarginTrading.AzureRepositories/Logs/RiskSystemCommandsLogRepository.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Threading.Tasks; using AzureStorage; using Common; diff --git a/src/MarginTrading.AzureRepositories/MarginTrading.AzureRepositories.csproj b/src/MarginTrading.AzureRepositories/MarginTrading.AzureRepositories.csproj index e5d4dac9e..b37f230c3 100644 --- a/src/MarginTrading.AzureRepositories/MarginTrading.AzureRepositories.csproj +++ b/src/MarginTrading.AzureRepositories/MarginTrading.AzureRepositories.csproj @@ -1,17 +1,25 @@  - netcoreapp2.0 + netcoreapp2.2 MarginTrading.AzureRepositories MarginTrading.AzureRepositories false false false - 1.0.1 + 1.16.29 + 7.3 + + + 1701;1702;1705;CA2007;0612;0618;1591 + + {08346EF4-8525-4CE0-93C4-CCE510D27D88} + MarginTrading.Contract + - + \ No newline at end of file diff --git a/src/MarginTrading.AzureRepositories/MarginTradingAccountHistoryRepository.cs b/src/MarginTrading.AzureRepositories/MarginTradingAccountHistoryRepository.cs index 6f09e610f..d58033f29 100644 --- a/src/MarginTrading.AzureRepositories/MarginTradingAccountHistoryRepository.cs +++ b/src/MarginTrading.AzureRepositories/MarginTradingAccountHistoryRepository.cs @@ -1,5 +1,7 @@ -using System; -using System.Collections.Concurrent; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; diff --git a/src/MarginTrading.AzureRepositories/MarginTradingAccountStatsRepository.cs b/src/MarginTrading.AzureRepositories/MarginTradingAccountStatsRepository.cs index d723a62d7..ee2b3aea5 100644 --- a/src/MarginTrading.AzureRepositories/MarginTradingAccountStatsRepository.cs +++ b/src/MarginTrading.AzureRepositories/MarginTradingAccountStatsRepository.cs @@ -1,4 +1,6 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; diff --git a/src/MarginTrading.AzureRepositories/MarginTradingAccountsRepository.cs b/src/MarginTrading.AzureRepositories/MarginTradingAccountsRepository.cs deleted file mode 100644 index e4dbac0e3..000000000 --- a/src/MarginTrading.AzureRepositories/MarginTradingAccountsRepository.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using AzureStorage; -using Common; -using MarginTrading.Backend.Core; -using Microsoft.WindowsAzure.Storage.Table; - -namespace MarginTrading.AzureRepositories -{ - public class MarginTradingAccountEntity : TableEntity, IMarginTradingAccount - { - public string Id => RowKey; - public string ClientId => PartitionKey; - public string TradingConditionId { get; set; } - public string BaseAssetId { get; set; } - decimal IMarginTradingAccount.Balance => (decimal) Balance; - public double Balance { get; set; } - decimal IMarginTradingAccount.WithdrawTransferLimit => (decimal) WithdrawTransferLimit; - public AccountFpl AccountFpl => new AccountFpl(); - public string LegalEntity { get; set; } - public double WithdrawTransferLimit { get; set; } - public double MarginCall { get; set; } - public double StopOut { get; set; } - - public static string GeneratePartitionKey(string clientId) - { - return clientId; - } - - public static string GenerateRowKey(string id) - { - return id; - } - - public static MarginTradingAccountEntity Create(IMarginTradingAccount src) - { - return new MarginTradingAccountEntity - { - PartitionKey = GeneratePartitionKey(src.ClientId), - RowKey = GenerateRowKey(src.Id), - TradingConditionId = src.TradingConditionId, - BaseAssetId = src.BaseAssetId, - Balance = (double) src.Balance, - WithdrawTransferLimit = (double) src.WithdrawTransferLimit, - LegalEntity = src.LegalEntity, - }; - } - } - - public class MarginTradingAccountsRepository : IMarginTradingAccountsRepository - { - private readonly INoSQLTableStorage _tableStorage; - - public MarginTradingAccountsRepository(INoSQLTableStorage tableStorage) - { - _tableStorage = tableStorage; - } - - public async Task> GetAllAsync(string clientId = null) - { - return string.IsNullOrEmpty(clientId) - ? await _tableStorage.GetDataAsync() - : await _tableStorage.GetDataAsync(MarginTradingAccountEntity.GeneratePartitionKey(clientId)); - } - - public async Task UpdateBalanceAsync(string clientId, string accountId, decimal amount, bool changeLimit) - { - var account = await _tableStorage.GetDataAsync(MarginTradingAccountEntity.GeneratePartitionKey(clientId), MarginTradingAccountEntity.GenerateRowKey(accountId)); - - if (account != null) - { - account.Balance += (double) amount; - - if (changeLimit) - account.WithdrawTransferLimit += (double) amount; - - await _tableStorage.InsertOrMergeAsync(account); - return MarginTradingAccount.Create(account); - } - - return null; - } - - public async Task UpdateTradingConditionIdAsync(string clientId, string accountId, - string tradingConditionId) - { - return await _tableStorage.MergeAsync(MarginTradingAccountEntity.GeneratePartitionKey(clientId), - MarginTradingAccountEntity.GenerateRowKey(accountId), - a => - { - a.TradingConditionId = tradingConditionId; - return a; - }); - } - - public async Task AddAsync(MarginTradingAccount account) - { - var entity = MarginTradingAccountEntity.Create(account); - await _tableStorage.InsertOrMergeAsync(entity); - } - - public async Task GetAsync(string clientId, string accountId) - { - return await _tableStorage.GetDataAsync(MarginTradingAccountEntity.GeneratePartitionKey(clientId), MarginTradingAccountEntity.GenerateRowKey(accountId)); - } - - public async Task GetAsync(string accountId) - { - return (await _tableStorage.GetDataAsync(entity => entity.Id == accountId)).FirstOrDefault(); - } - - public async Task DeleteAsync(string clientId, string accountId) - { - await _tableStorage.DeleteAsync(MarginTradingAccountEntity.GeneratePartitionKey(clientId), - MarginTradingAccountEntity.GenerateRowKey(accountId)); - } - } -} diff --git a/src/MarginTrading.AzureRepositories/MarginTradingBlobRepository.cs b/src/MarginTrading.AzureRepositories/MarginTradingBlobRepository.cs index 211ab88f3..a4c48c4cf 100644 --- a/src/MarginTrading.AzureRepositories/MarginTradingBlobRepository.cs +++ b/src/MarginTrading.AzureRepositories/MarginTradingBlobRepository.cs @@ -1,4 +1,8 @@ -using System.Text; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Text; using System.Threading.Tasks; using AzureStorage; using AzureStorage.Blob; @@ -45,7 +49,17 @@ public async Task ReadAsync(string blobContainer, string key) return default(T); } - public async Task Write(string blobContainer, string key, T obj) + public (T, DateTime) ReadWithTimestamp(string blobContainer, string key) + { + throw new NotImplementedException(); + } + + public Task<(T, DateTime)> ReadWithTimestampAsync(string blobContainer, string key) + { + throw new NotImplementedException(); + } + + public async Task WriteAsync(string blobContainer, string key, T obj) { var data = JsonConvert.SerializeObject(obj).ToUtf8Bytes(); await _blobStorage.SaveBlobAsync(blobContainer, key, data); diff --git a/src/MarginTrading.AzureRepositories/MarginTradingOperationsLogRepository.cs b/src/MarginTrading.AzureRepositories/MarginTradingOperationsLogRepository.cs index 2fee1e33e..23fdce3c2 100644 --- a/src/MarginTrading.AzureRepositories/MarginTradingOperationsLogRepository.cs +++ b/src/MarginTrading.AzureRepositories/MarginTradingOperationsLogRepository.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using AzureStorage; -using MarginTrading.Backend.Core; using MarginTrading.Common.Services; using Microsoft.WindowsAzure.Storage.Table; @@ -10,35 +9,33 @@ namespace MarginTrading.AzureRepositories public class OperationLogEntity : TableEntity, IOperationLog { public string Name { get; set; } - public string ClientId { get; set; } public string AccountId { get; set; } public string Input { get; set; } public string Data { get; set; } - public static string GeneratePartitionKey(string clientId, string name) + public static string GeneratePartitionKey(string accountId, string name) { - return clientId ?? name; + return accountId ?? name; } public static OperationLogEntity Create(IOperationLog src) { return new OperationLogEntity { - PartitionKey = GeneratePartitionKey(src.ClientId, src.Name), + PartitionKey = GeneratePartitionKey(src.AccountId, src.Name), Name = src.Name, Input = src.Input, Data = src.Data, AccountId = src.AccountId, - ClientId = src.ClientId }; } } - public class MarginTradingOperationsLogRepository : IMarginTradingOperationsLogRepository + public class OperationsLogRepository : IOperationsLogRepository { private readonly INoSQLTableStorage _tableStorage; - public MarginTradingOperationsLogRepository(INoSQLTableStorage tableStorage) + public OperationsLogRepository(INoSQLTableStorage tableStorage) { _tableStorage = tableStorage; } diff --git a/src/MarginTrading.AzureRepositories/MarginTradingOrdersHistoryRepository.cs b/src/MarginTrading.AzureRepositories/MarginTradingOrdersHistoryRepository.cs deleted file mode 100644 index 6eb993d9e..000000000 --- a/src/MarginTrading.AzureRepositories/MarginTradingOrdersHistoryRepository.cs +++ /dev/null @@ -1,196 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using AzureStorage; -using AzureStorage.Tables; -using Common; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.MatchedOrders; -using MarginTrading.Backend.Core.MatchingEngines; -using Microsoft.WindowsAzure.Storage.Table; - -namespace MarginTrading.AzureRepositories -{ - public class MarginTradingOrderHistoryEntity : TableEntity, IOrderHistory - { - public string Id { get; set; } - public long Code { get; set; } - public string ClientId { get; set; } - public string AccountId { get; set; } - public string TradingConditionId { get; set; } - public string AccountAssetId { get; set; } - public string Instrument { get; set; } - public DateTime CreateDate { get; set; } - public DateTime? OpenDate { get; set; } - public DateTime? CloseDate { get; set; } - decimal? IOrderHistory.ExpectedOpenPrice => (decimal?) ExpectedOpenPrice; - public double? ExpectedOpenPrice { get; set; } - decimal IOrderHistory.OpenPrice => (decimal) OpenPrice; - public double OpenPrice { get; set; } - decimal IOrderHistory.ClosePrice => (decimal) ClosePrice; - public double ClosePrice { get; set; } - decimal IOrderHistory.Volume => (decimal) Volume; - public double Volume { get; set; } - decimal IOrderHistory.MatchedVolume => (decimal) MatchedVolume; - public double MatchedVolume { get; set; } - decimal IOrderHistory.MatchedCloseVolume => (decimal) MatchedCloseVolume; - public double MatchedCloseVolume { get; set; } - decimal? IOrderHistory.TakeProfit => (decimal?) TakeProfit; - public double? TakeProfit { get; set; } - decimal? IOrderHistory.StopLoss => (decimal?) StopLoss; - public double? StopLoss { get; set; } - decimal IOrderHistory.Fpl => (decimal) Fpl; - public double Fpl { get; set; } - decimal IOrderHistory.PnL => (decimal) PnL; - public double PnL { get; set; } - decimal IOrderHistory.InterestRateSwap => (decimal) InterestRateSwap; - public double InterestRateSwap { get; set; } - decimal IOrderHistory.CommissionLot => (decimal) CommissionLot; - public double CommissionLot { get; set; } - decimal IOrderHistory.OpenCommission => (decimal) OpenCommission; - public double OpenCommission { get; set; } - decimal IOrderHistory.CloseCommission => (decimal) CloseCommission; - public double CloseCommission { get; set; } - decimal IOrderHistory.QuoteRate => (decimal) QuoteRate; - public double QuoteRate { get; set; } - public int AssetAccuracy { get; set; } - decimal IOrderHistory.MarginInit => (decimal) MarginInit; - public double MarginInit { get; set; } - decimal IOrderHistory.MarginMaintenance => (decimal) MarginMaintenance; - public double MarginMaintenance { get; set; } - public DateTime? StartClosingDate { get; set; } - public string Type { get; set; } - OrderDirection IOrderHistory.Type => Type.ParseEnum(OrderDirection.Buy); - public string Status { get; set; } - OrderStatus IOrderHistory.Status => Status.ParseEnum(OrderStatus.Closed); - public string CloseReason { get; set; } - OrderCloseReason IOrderHistory.CloseReason => CloseReason.ParseEnum(OrderCloseReason.Close); - public string FillType { get; set; } - OrderFillType IOrderHistory.FillType => FillType.ParseEnum(OrderFillType.FillOrKill); - public string RejectReason { get; set; } - OrderRejectReason IOrderHistory.RejectReason => RejectReason.ParseEnum(OrderRejectReason.None); - public string RejectReasonText { get; set; } - public string Comment { get; set; } - public List MatchedOrders { get; set; } = new List(); - public List MatchedCloseOrders { get; set; } = new List(); - decimal IOrderHistory.SwapCommission => (decimal) SwapCommission; - public double SwapCommission { get; set; } - - public string EquivalentAsset { get; set; } - decimal IOrderHistory.OpenPriceEquivalent => (decimal) OpenPriceEquivalent; - public double OpenPriceEquivalent { get; set; } - decimal IOrderHistory.ClosePriceEquivalent => (decimal) ClosePriceEquivalent; - public double ClosePriceEquivalent { get; set; } - - public string Orders { get; set; } - public string ClosedOrders { get; set; } - - OrderUpdateType IOrderHistory.OrderUpdateType => OrderUpdateType.ParseEnum(Backend.Core.OrderUpdateType.Close); - - public string OpenExternalOrderId { get; set; } - public string OpenExternalProviderId { get; set; } - public string CloseExternalOrderId { get; set; } - public string CloseExternalProviderId { get; set; } - public string MatchingEngineMode { get; set; } - public string LegalEntity { get; set; } - - MatchingEngineMode IOrderHistory.MatchingEngineMode => - MatchingEngineMode.ParseEnum(Backend.Core.MatchingEngines.MatchingEngineMode.MarketMaker); - - public string OrderUpdateType { get; set; } - - public static string GeneratePartitionKey(string clientId, string accountIds) - { - return $"{clientId}_{accountIds}"; - } - - public static MarginTradingOrderHistoryEntity Create(IOrderHistory src) - { - return new MarginTradingOrderHistoryEntity - { - PartitionKey = GeneratePartitionKey(src.ClientId, src.AccountId), - Id = src.Id, - Code = src.Code, - ClientId = src.ClientId, - AccountId = src.AccountId, - TradingConditionId = src.TradingConditionId, - AccountAssetId = src.AccountAssetId, - Instrument = src.Instrument, - Type = src.Type.ToString(), - CreateDate = src.CreateDate, - OpenDate = src.OpenDate, - CloseDate = src.CloseDate, - ExpectedOpenPrice = (double?) src.ExpectedOpenPrice, - OpenPrice = (double) src.OpenPrice, - ClosePrice = (double) src.ClosePrice, - TakeProfit = (double?) src.TakeProfit, - StopLoss = (double?) src.StopLoss, - Fpl = (double) src.Fpl, - PnL = (double) src.PnL, - InterestRateSwap = (double) src.InterestRateSwap, - CommissionLot = (double) src.CommissionLot, - OpenCommission = (double) src.OpenCommission, - CloseCommission = (double) src.CloseCommission, - QuoteRate = (double) src.QuoteRate, - AssetAccuracy = src.AssetAccuracy, - MarginInit = (double) src.MarginInit, - MarginMaintenance = (double) src.MarginMaintenance, - StartClosingDate = src.StartClosingDate, - Status = src.Status.ToString(), - CloseReason = src.CloseReason.ToString(), - FillType = src.FillType.ToString(), - Volume = (double) src.Volume, - MatchedVolume = (double) src.MatchedVolume, - MatchedCloseVolume = (double) src.MatchedCloseVolume, - RejectReason = src.RejectReason.ToString(), - RejectReasonText = src.RejectReasonText, - Orders = src.MatchedOrders.SerializeArrayForTableStorage(), - ClosedOrders = src.MatchedCloseOrders.SerializeArrayForTableStorage(), - SwapCommission = (double) src.SwapCommission, - EquivalentAsset = src.EquivalentAsset, - OpenPriceEquivalent = (double) src.OpenPriceEquivalent, - ClosePriceEquivalent = (double) src.ClosePriceEquivalent, - Comment = src.Comment, - OrderUpdateType = src.OrderUpdateType.ToString(), - OpenExternalOrderId = src.OpenExternalOrderId, - OpenExternalProviderId = src.OpenExternalProviderId, - CloseExternalOrderId = src.CloseExternalOrderId, - CloseExternalProviderId = src.CloseExternalProviderId, - MatchingEngineMode = src.MatchingEngineMode.ToString(), - LegalEntity = src.LegalEntity, - }; - } - } - - public class MarginTradingOrdersHistoryRepository : IMarginTradingOrdersHistoryRepository - { - private readonly INoSQLTableStorage _tableStorage; - - public MarginTradingOrdersHistoryRepository(INoSQLTableStorage tableStorage) - { - _tableStorage = tableStorage; - } - - public Task AddAsync(IOrderHistory order) - { - var entity = MarginTradingOrderHistoryEntity.Create(order); - // ReSharper disable once RedundantArgumentDefaultValue - return _tableStorage.InsertAndGenerateRowKeyAsDateTimeAsync(entity, entity.CloseDate ?? entity.OpenDate ?? entity.CreateDate, RowKeyDateTimeFormat.Iso); - } - - public async Task> GetHistoryAsync(string clientId, string[] accountIds, DateTime? from, DateTime? to) - { - return (await _tableStorage.WhereAsync(accountIds.Select(a => clientId + '_' + a), - from ?? DateTime.MinValue, to?.Date.AddDays(1) ?? DateTime.MaxValue, ToIntervalOption.IncludeTo)) - .OrderByDescending(entity => entity.CloseDate ?? entity.OpenDate ?? entity.CreateDate).ToList(); - } - - public async Task> GetHistoryAsync() - { - var entities = (await _tableStorage.GetDataAsync()).OrderByDescending(item => item.Timestamp); - - return entities; - } - } -} diff --git a/src/MarginTrading.AzureRepositories/MarginTradingOrdersRejectedRepository.cs b/src/MarginTrading.AzureRepositories/MarginTradingOrdersRejectedRepository.cs deleted file mode 100644 index f6c118d40..000000000 --- a/src/MarginTrading.AzureRepositories/MarginTradingOrdersRejectedRepository.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using AzureStorage; -using AzureStorage.Tables; -using Common; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.MatchedOrders; -using MarginTrading.Backend.Core.MatchingEngines; -using Microsoft.WindowsAzure.Storage.Table; - -namespace MarginTrading.AzureRepositories -{ - public class MarginTradingOrderRejectedEntity : TableEntity, IOrderHistory - { - public string Id { get; set; } - public long Code { get; set; } - public string ClientId { get; set; } - public string AccountId { get; set; } - public string TradingConditionId { get; set; } - public string AccountAssetId { get; set; } - public string Instrument { get; set; } - public DateTime CreateDate { get; set; } - public DateTime? OpenDate { get; set; } - public DateTime? CloseDate { get; set; } - decimal? IOrderHistory.ExpectedOpenPrice => (decimal?) ExpectedOpenPrice; - public double? ExpectedOpenPrice { get; set; } - decimal IOrderHistory.OpenPrice => (decimal) OpenPrice; - public double OpenPrice { get; set; } - decimal IOrderHistory.ClosePrice => (decimal) ClosePrice; - public double ClosePrice { get; set; } - decimal IOrderHistory.Volume => (decimal) Volume; - public double Volume { get; set; } - decimal IOrderHistory.MatchedVolume => (decimal) MatchedVolume; - public double MatchedVolume { get; set; } - decimal IOrderHistory.MatchedCloseVolume => (decimal) MatchedCloseVolume; - public double MatchedCloseVolume { get; set; } - decimal? IOrderHistory.TakeProfit => (decimal?) TakeProfit; - public double? TakeProfit { get; set; } - decimal? IOrderHistory.StopLoss => (decimal?) StopLoss; - public double? StopLoss { get; set; } - decimal IOrderHistory.Fpl => (decimal) Fpl; - public double Fpl { get; set; } - decimal IOrderHistory.PnL => (decimal) PnL; - public double PnL { get; set; } - decimal IOrderHistory.InterestRateSwap => (decimal) InterestRateSwap; - public double InterestRateSwap { get; set; } - decimal IOrderHistory.CommissionLot => (decimal) CommissionLot; - public double CommissionLot { get; set; } - decimal IOrderHistory.OpenCommission => (decimal) OpenCommission; - public double OpenCommission { get; set; } - decimal IOrderHistory.CloseCommission => (decimal) CloseCommission; - public double CloseCommission { get; set; } - decimal IOrderHistory.QuoteRate => (decimal) QuoteRate; - public double QuoteRate { get; set; } - public int AssetAccuracy { get; set; } - decimal IOrderHistory.MarginInit => (decimal) MarginInit; - public double MarginInit { get; set; } - decimal IOrderHistory.MarginMaintenance => (decimal) MarginMaintenance; - public double MarginMaintenance { get; set; } - public DateTime? StartClosingDate { get; set; } - public string Type { get; set; } - OrderDirection IOrderHistory.Type => Type.ParseEnum(OrderDirection.Buy); - public string Status { get; set; } - OrderStatus IOrderHistory.Status => Status.ParseEnum(OrderStatus.Closed); - public string CloseReason { get; set; } - OrderCloseReason IOrderHistory.CloseReason => CloseReason.ParseEnum(OrderCloseReason.Close); - public string FillType { get; set; } - OrderFillType IOrderHistory.FillType => FillType.ParseEnum(OrderFillType.FillOrKill); - public string RejectReason { get; set; } - OrderRejectReason IOrderHistory.RejectReason => RejectReason.ParseEnum(OrderRejectReason.None); - public string RejectReasonText { get; set; } - public string Comment { get; set; } - public List MatchedOrders { get; set; } = new List(); - public List MatchedCloseOrders { get; set; } = new List(); - decimal IOrderHistory.SwapCommission => (decimal) SwapCommission; - public double SwapCommission { get; set; } - - public string EquivalentAsset { get; set; } - decimal IOrderHistory.OpenPriceEquivalent => (decimal) OpenPriceEquivalent; - public double OpenPriceEquivalent { get; set; } - decimal IOrderHistory.ClosePriceEquivalent => (decimal) ClosePriceEquivalent; - public double ClosePriceEquivalent { get; set; } - - public string Orders { get; set; } - public string ClosedOrders { get; set; } - - OrderUpdateType IOrderHistory.OrderUpdateType => OrderUpdateType.ParseEnum(Backend.Core.OrderUpdateType.Reject); - public string OpenExternalOrderId { get; set; } - public string OpenExternalProviderId { get; set; } - public string CloseExternalOrderId { get; set; } - public string CloseExternalProviderId { get; set; } - public string OrderUpdateType { get; set; } - - public string MatchingEngineMode { get; set; } - public string LegalEntity { get; set; } - - MatchingEngineMode IOrderHistory.MatchingEngineMode => - MatchingEngineMode.ParseEnum(Backend.Core.MatchingEngines.MatchingEngineMode.MarketMaker); - - public static string GeneratePartitionKey(string clientId) - { - return clientId; - } - - public static string GenerateRowKey(string id) - { - return id; - } - - public static MarginTradingOrderRejectedEntity Create(IOrderHistory src) - { - return new MarginTradingOrderRejectedEntity - { - PartitionKey = GeneratePartitionKey(src.ClientId), - RowKey = GenerateRowKey(src.Id), - Id = src.Id, - Code = src.Code, - ClientId = src.ClientId, - AccountId = src.AccountId, - TradingConditionId = src.TradingConditionId, - AccountAssetId = src.AccountAssetId, - Instrument = src.Instrument, - Type = src.Type.ToString(), - CreateDate = src.CreateDate, - OpenDate = src.OpenDate, - CloseDate = src.CloseDate, - ExpectedOpenPrice = (double?) src.ExpectedOpenPrice, - OpenPrice = (double) src.OpenPrice, - ClosePrice = (double) src.ClosePrice, - TakeProfit = (double?) src.TakeProfit, - StopLoss = (double?) src.StopLoss, - Fpl = (double) src.Fpl, - PnL = (double) src.PnL, - InterestRateSwap = (double) src.InterestRateSwap, - CommissionLot = (double) src.CommissionLot, - OpenCommission = (double) src.OpenCommission, - CloseCommission = (double) src.CloseCommission, - QuoteRate = (double) src.QuoteRate, - AssetAccuracy = src.AssetAccuracy, - MarginInit = (double) src.MarginInit, - MarginMaintenance = (double) src.MarginMaintenance, - StartClosingDate = src.StartClosingDate, - Status = src.Status.ToString(), - CloseReason = src.CloseReason.ToString(), - FillType = src.FillType.ToString(), - Volume = (double) src.Volume, - MatchedVolume = (double) src.MatchedVolume, - MatchedCloseVolume = (double) src.MatchedCloseVolume, - RejectReason = src.RejectReason.ToString(), - RejectReasonText = src.RejectReasonText, - Orders = src.MatchedOrders.SerializeArrayForTableStorage(), - ClosedOrders = src.MatchedCloseOrders.SerializeArrayForTableStorage(), - SwapCommission = (double) src.SwapCommission, - EquivalentAsset = src.EquivalentAsset, - OpenPriceEquivalent = (double) src.OpenPriceEquivalent, - ClosePriceEquivalent = (double) src.ClosePriceEquivalent, - Comment = src.Comment, - OrderUpdateType = src.OrderUpdateType.ToString(), - OpenExternalOrderId = src.OpenExternalOrderId, - OpenExternalProviderId = src.OpenExternalProviderId, - CloseExternalOrderId = src.CloseExternalOrderId, - CloseExternalProviderId = src.CloseExternalProviderId, - MatchingEngineMode = src.MatchingEngineMode.ToString(), - LegalEntity = src.LegalEntity, - }; - } - } - - public class MarginTradingOrdersRejectedRepository : IMarginTradingOrdersRejectedRepository - { - private readonly INoSQLTableStorage _tableStorage; - - public MarginTradingOrdersRejectedRepository(INoSQLTableStorage tableStorage) - { - _tableStorage = tableStorage; - } - public async Task AddAsync(IOrderHistory order) - { - var entity = MarginTradingOrderRejectedEntity.Create(order); - await _tableStorage.InsertOrReplaceAsync(entity); - } - - public async Task> GetHisotryAsync(string[] accountIds, DateTime from, DateTime to) - { - var entities = (await _tableStorage.GetDataAsync(entity => accountIds.Contains(entity.AccountId) && entity.CloseDate >= from && entity.CloseDate <= to)) - .OrderByDescending(item => item.Timestamp); - - foreach (var entity in entities.Where(item => item.Id == null)) - { - entity.Id = entity.RowKey; - } - - return entities; - } - } -} diff --git a/src/MarginTrading.AzureRepositories/MatchingEngineRoutesRepository.cs b/src/MarginTrading.AzureRepositories/MatchingEngineRoutesRepository.cs deleted file mode 100644 index 7dc5197d4..000000000 --- a/src/MarginTrading.AzureRepositories/MatchingEngineRoutesRepository.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using AzureStorage; -using Common; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.MatchingEngines; -using Microsoft.WindowsAzure.Storage.Table; - -namespace MarginTrading.AzureRepositories -{ - public class MatchingEngineRouteEntity : TableEntity, IMatchingEngineRoute - { - public string Id { get; set; } - public int Rank { get; set; } - public string TradingConditionId { get; set; } - public string ClientId { get; set; } - public string Instrument { get; set; } - public string Type { get; set; } - OrderDirection? IMatchingEngineRoute.Type => Type?.ParseEnum(OrderDirection.Buy); - public string MatchingEngineId { get; set; } - public string Asset { get; set; } - public string RiskSystemLimitType { get; set; } - public string RiskSystemMetricType { get; set; } - - public static string GeneratePartitionKey() - { - return "Rule"; - } - - public static string GenerateRowKey(string id) - { - return id; - } - - public static MatchingEngineRouteEntity Create(IMatchingEngineRoute route) - { - return new MatchingEngineRouteEntity - { - PartitionKey = GeneratePartitionKey(), - RowKey = GenerateRowKey(route.Id), - Id = route.Id, - Rank = route.Rank, - TradingConditionId = route.TradingConditionId, - Instrument = route.Instrument, - Type = route.Type?.ToString(), - MatchingEngineId = route.MatchingEngineId, - ClientId = route.ClientId, - Asset = route.Asset, - RiskSystemLimitType = route.RiskSystemLimitType, - RiskSystemMetricType = route.RiskSystemMetricType - }; - } - } - - public class MatchingEngineRoutesRepository : IMatchingEngineRoutesRepository - { - private readonly INoSQLTableStorage _tableStorage; - - public MatchingEngineRoutesRepository(INoSQLTableStorage tableStorage) - { - _tableStorage = tableStorage; - } - - public async Task AddOrReplaceRouteAsync(IMatchingEngineRoute route) - { - await _tableStorage.InsertOrReplaceAsync(MatchingEngineRouteEntity.Create(route)); - } - - public async Task DeleteRouteAsync(string id) - { - await _tableStorage.DeleteIfExistAsync(MatchingEngineRouteEntity.GeneratePartitionKey(), MatchingEngineRouteEntity.GenerateRowKey(id)); - } - - public async Task> GetAllRoutesAsync() - { - return await _tableStorage.GetDataAsync(MatchingEngineRouteEntity.GeneratePartitionKey()); - } - - public async Task GetRouteByIdAsync(string id) - { - return await _tableStorage.GetDataAsync(MatchingEngineRouteEntity.GeneratePartitionKey(), MatchingEngineRouteEntity.GenerateRowKey(id)); - } - } -} diff --git a/src/MarginTrading.AzureRepositories/OpenPositionsRepository.cs b/src/MarginTrading.AzureRepositories/OpenPositionsRepository.cs new file mode 100644 index 000000000..bdd9b9b27 --- /dev/null +++ b/src/MarginTrading.AzureRepositories/OpenPositionsRepository.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AzureStorage; +using AzureStorage.Tables; +using Common.Log; +using Lykke.SettingsReader; +using MarginTrading.AzureRepositories.Entities; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Common.Services; + +namespace MarginTrading.AzureRepositories +{ + public class OpenPositionsRepository : IOpenPositionsRepository + { + private readonly INoSQLTableStorage _tableStorage; + private readonly IDateService _dateService; + + public OpenPositionsRepository(IReloadingManager connectionStringManager, + ILog log, + IDateService dateService) + { + _tableStorage = AzureTableStorage.Create( + connectionStringManager, + "OpenPositionsDump", + log); + _dateService = dateService; + } + + public async Task Dump(IEnumerable openPositions) + { + var reportTime = _dateService.Now(); + var entities = openPositions.Select(x => OpenPositionEntity.Create(x, reportTime)); + + await _tableStorage.DeleteAsync(); + await _tableStorage.CreateTableIfNotExistsAsync(); + await _tableStorage.InsertAsync(entities); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.AzureRepositories/OperationExecutionInfoRepository.cs b/src/MarginTrading.AzureRepositories/OperationExecutionInfoRepository.cs new file mode 100644 index 000000000..74ede55d9 --- /dev/null +++ b/src/MarginTrading.AzureRepositories/OperationExecutionInfoRepository.cs @@ -0,0 +1,93 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; +using AzureStorage; +using AzureStorage.Tables; +using Common; +using Common.Log; +using Lykke.SettingsReader; +using MarginTrading.AzureRepositories.Entities; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Common.Services; +using Microsoft.Extensions.Internal; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace MarginTrading.AzureRepositories +{ + public class OperationExecutionInfoRepository : IOperationExecutionInfoRepository + { + private readonly INoSQLTableStorage _tableStorage; + private readonly IDateService _dateService; + + public OperationExecutionInfoRepository(IReloadingManager connectionStringManager, + ILog log, IDateService dateService) + { + _tableStorage = AzureTableStorage.Create( + connectionStringManager, + "MarginTradingExecutionInfo", + log); + _dateService = dateService; + } + + public async Task> GetOrAddAsync( + string operationName, string operationId, Func> factory) where TData : class + { + var entity = await _tableStorage.GetOrInsertAsync( + partitionKey: OperationExecutionInfoEntity.GeneratePartitionKey(operationName), + rowKey: OperationExecutionInfoEntity.GeneratePartitionKey(operationId), + createNew: () => + { + var result = Convert(factory()); + result.LastModified = _dateService.Now(); + return result; + }); + + return Convert(entity); + } + + public async Task> GetAsync(string operationName, string id) + where TData : class + { + var obj = await _tableStorage.GetDataAsync( + OperationExecutionInfoEntity.GeneratePartitionKey(operationName), + OperationExecutionInfoEntity.GenerateRowKey(id)) ?? throw new InvalidOperationException( + $"Operation execution info for {operationName} #{id} not yet exists"); + + return Convert(obj); + } + + public async Task Save(IOperationExecutionInfo executionInfo) where TData : class + { + var entity = Convert(executionInfo); + entity.LastModified = _dateService.Now(); + await _tableStorage.ReplaceAsync(entity); + } + + private static IOperationExecutionInfo Convert(OperationExecutionInfoEntity entity) + where TData : class + { + return new OperationExecutionInfo( + operationName: entity.OperationName, + id: entity.Id, + lastModified: entity.LastModified, + data: entity.Data is string dataStr + ? JsonConvert.DeserializeObject(dataStr) + : ((JToken) entity.Data).ToObject()); + } + + private static OperationExecutionInfoEntity Convert(IOperationExecutionInfo model) + where TData : class + { + return new OperationExecutionInfoEntity + { + Id = model.Id, + OperationName = model.OperationName, + Data = model.Data.ToJson(), + }; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.AzureRepositories/OvernightSwapHistoryRepository.cs b/src/MarginTrading.AzureRepositories/OvernightSwapHistoryRepository.cs deleted file mode 100644 index 87c0e626a..000000000 --- a/src/MarginTrading.AzureRepositories/OvernightSwapHistoryRepository.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using AzureStorage; -using MarginTrading.AzureRepositories.Entities; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Repositories; -using Microsoft.WindowsAzure.Storage.Table; -using MoreLinq; - -namespace MarginTrading.AzureRepositories -{ - public class OvernightSwapHistoryRepository : IOvernightSwapHistoryRepository - { - private readonly INoSQLTableStorage _tableStorage; - - public OvernightSwapHistoryRepository(INoSQLTableStorage tableStorage) - { - _tableStorage = tableStorage; - } - - public async Task AddAsync(IOvernightSwapHistory obj) - { - var entity = OvernightSwapHistoryEntity.Create(obj); - await _tableStorage.InsertAndGenerateRowKeyAsDateTimeAsync(entity, entity.Time); - } - - public async Task> GetAsync() - { - return await _tableStorage.GetDataAsync(); - } - - public async Task> GetAsync(DateTime? @from, DateTime? to) - { - return (await _tableStorage.WhereAsync(AzureStorageUtils.QueryGenerator.RowKeyOnly - .BetweenQuery(from ?? DateTime.MinValue, to ?? DateTime.MaxValue, ToIntervalOption.IncludeTo))) - .OrderByDescending(item => item.Time) - .ToList(); - } - - public async Task> GetAsync(string accountId, DateTime? @from, DateTime? to) - { - return (await _tableStorage.WhereAsync(accountId, from ?? DateTime.MinValue, to ?? DateTime.MaxValue, - ToIntervalOption.IncludeTo)) - .OrderByDescending(item => item.Time).ToList(); - } - - public async Task DeleteAsync(IOvernightSwapHistory obj) - { - await _tableStorage.DeleteAsync(OvernightSwapHistoryEntity.Create(obj)); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.AzureRepositories/OvernightSwapStateRepository.cs b/src/MarginTrading.AzureRepositories/OvernightSwapStateRepository.cs deleted file mode 100644 index 858ab30ac..000000000 --- a/src/MarginTrading.AzureRepositories/OvernightSwapStateRepository.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using AzureStorage; -using MarginTrading.AzureRepositories.Entities; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Repositories; - -namespace MarginTrading.AzureRepositories -{ - public class OvernightSwapStateRepository : IOvernightSwapStateRepository - { - private readonly INoSQLTableStorage _tableStorage; - - public OvernightSwapStateRepository(INoSQLTableStorage tableStorage) - { - _tableStorage = tableStorage; - } - - public async Task AddOrReplaceAsync(IOvernightSwapState obj) - { - await _tableStorage.InsertOrReplaceAsync(OvernightSwapStateEntity.Create(obj)); - } - - public async Task> GetAsync() - { - return await _tableStorage.GetDataAsync(); - } - - public async Task DeleteAsync(IOvernightSwapState obj) - { - var entity = OvernightSwapStateEntity.Create(obj); - await _tableStorage.DeleteIfExistAsync(entity.PartitionKey, entity.RowKey); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.AzureRepositories/Properties/AssemblyInfo.cs b/src/MarginTrading.AzureRepositories/Properties/AssemblyInfo.cs index 59682f647..f213b4625 100644 --- a/src/MarginTrading.AzureRepositories/Properties/AssemblyInfo.cs +++ b/src/MarginTrading.AzureRepositories/Properties/AssemblyInfo.cs @@ -1,4 +1,7 @@ -using System.Reflection; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; diff --git a/src/MarginTrading.AzureRepositories/TradingConditionsRepository.cs b/src/MarginTrading.AzureRepositories/TradingConditionsRepository.cs deleted file mode 100644 index a567ae6bd..000000000 --- a/src/MarginTrading.AzureRepositories/TradingConditionsRepository.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using AzureStorage; -using MarginTrading.AzureRepositories.Contract; -using MarginTrading.Backend.Core.TradingConditions; -using Microsoft.WindowsAzure.Storage.Table; - -namespace MarginTrading.AzureRepositories -{ - public class TradingConditionEntity : TableEntity, ITradingCondition - { - public string Id => RowKey; - public string Name { get; set; } - public bool IsDefault { get; set; } - public string LegalEntity { get; set; } - - public static string GeneratePartitionKey() - { - return "TradingCondition"; - } - - public static string GenerateRowKey(string id) - { - return id; - } - - public static TradingConditionEntity Create(ITradingCondition src) - { - return new TradingConditionEntity - { - PartitionKey = GeneratePartitionKey(), - RowKey = GenerateRowKey(src.Id), - Name = src.Name, - IsDefault = src.IsDefault, - LegalEntity = src.LegalEntity, - }; - } - } - - public class TradingConditionsRepository : ITradingConditionRepository - { - private readonly INoSQLTableStorage _tableStorage; - - public TradingConditionsRepository(INoSQLTableStorage tableStorage) - { - _tableStorage = tableStorage; - } - - public async Task AddOrReplaceAsync(ITradingCondition condition) - { - await _tableStorage.InsertOrReplaceAsync(TradingConditionEntity.Create(condition)); - } - - public async Task GetAsync(string tradingConditionId) - { - return await _tableStorage.GetDataAsync(TradingConditionEntity.GeneratePartitionKey(), TradingConditionEntity.GenerateRowKey(tradingConditionId)); - } - - public async Task> GetAllAsync() - { - return await _tableStorage.GetDataAsync(); - } - } -} diff --git a/src/MarginTrading.AzureRepositories/TradingEngineSnapshotsRepository.cs b/src/MarginTrading.AzureRepositories/TradingEngineSnapshotsRepository.cs new file mode 100644 index 000000000..e75396476 --- /dev/null +++ b/src/MarginTrading.AzureRepositories/TradingEngineSnapshotsRepository.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; +using MarginTrading.Backend.Core.Repositories; + +namespace MarginTrading.AzureRepositories +{ + public class TradingEngineSnapshotsRepository : ITradingEngineSnapshotsRepository + { + public Task Add(DateTime tradingDay, string correlationId, DateTime timestamp, string orders, string positions, + string accounts, + string bestFxPrices, string bestTradingPrices) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Account/AccountStatContract.cs b/src/MarginTrading.Backend.Contracts/Account/AccountStatContract.cs new file mode 100644 index 000000000..f1277dcf0 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Account/AccountStatContract.cs @@ -0,0 +1,113 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Contracts.Account +{ + public class AccountStatContract + { + /// + /// ID + /// + public string AccountId { get; set; } + + /// + /// Base asset ID + /// + public string BaseAssetId { get; set; } + + /// + /// Sum of all cash movements except for unrealized PnL + /// + public decimal Balance { get; set; } + + public DateTime LastBalanceChangeTime { get; set; } + + /// + /// Margin call level + /// + public decimal MarginCallLevel { get; set; } + + /// + /// Stop out level + /// + public decimal StopOutLevel { get; set; } + + /// + /// Balance + UnrealizedPnL + /// + public decimal TotalCapital { get; set; } + + /// + /// TotalCapital - UsedMargin + /// + public decimal FreeMargin { get; set; } + + /// + /// TotalCapital - MarginInit + /// + public decimal MarginAvailable { get; set; } + + /// + /// Margin used for maintenance of positions (considering MCO rule) + /// = Max (CurrentlyUsedMargin, InitiallyUsedMargin/2) + /// + public decimal UsedMargin { get; set; } + + /// + /// Margin used by open positions + /// + public decimal CurrentlyUsedMargin { get; set; } + + /// + /// Margin used for initial open of existing positions + /// + public decimal InitiallyUsedMargin { get; set; } + + /// + /// Margin used for calculations, when open new positions + /// + public decimal MarginInit { get; set; } + + /// + /// Unrealized PnL + /// + public decimal PnL { get; set; } + + /// + /// Unrealized daily PnL + /// + public decimal UnrealizedDailyPnl { get; set; } + + /// + /// Number of opened positions + /// + public int OpenPositionsCount { get; set; } + + /// + /// Number of active orders + /// + public int ActiveOrdersCount { get; set; } + + /// + /// TotalCapital / UsedMargin + /// + public decimal MarginUsageLevel { get; set; } + + /// + /// Legal Entity of account + /// + public string LegalEntity { get; set; } + + /// + /// If account is in liquidation state + /// + public bool IsInLiquidation { get; set; } + + /// + /// Margin warning notification level + /// + public string MarginNotificationLevel { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Account/ActiveAccountsRequest.cs b/src/MarginTrading.Backend.Contracts/Account/ActiveAccountsRequest.cs new file mode 100644 index 000000000..ec5a5ab99 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Account/ActiveAccountsRequest.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace MarginTrading.Backend.Contracts.Account +{ + /// + /// Filter parameters to get accounts with open orders/positions + /// + public class ActiveAccountsRequest + { + /// + /// List of asset pairs to filter accounts with open orders + /// + /// + /// Null -> no filter will be applied + /// Empty list -> Account with ANY open order will be returned + /// + public HashSet ActiveOrderAssetPairIds { get; set; } + + /// + /// List of asset pairs to filter accounts with open positions + /// + /// + /// Null -> no filter will be applied + /// Empty list -> Account with ANY open position will be returned + /// + public HashSet ActivePositionAssetPairIds { get; set; } + + /// + /// Combine filters by orders and positions with AND clause? (by default OR is applied) + /// + public bool? IsAndClauseApplied { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Account/DataReaderAccountBackendContract.cs b/src/MarginTrading.Backend.Contracts/Account/DataReaderAccountBackendContract.cs deleted file mode 100644 index dcffd3d0e..000000000 --- a/src/MarginTrading.Backend.Contracts/Account/DataReaderAccountBackendContract.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MarginTrading.Backend.Contracts.Account -{ - public class DataReaderAccountBackendContract - { - public string Id { get; set; } - public string ClientId { get; set; } - public string TradingConditionId { get; set; } - public string BaseAssetId { get; set; } - public decimal Balance { get; set; } - public decimal WithdrawTransferLimit { get; set; } - public bool IsLive { get; set; } - public string LegalEntity { get; set; } - } -} diff --git a/src/MarginTrading.Backend.Contracts/Account/DataReaderAccountStatsBackendContract.cs b/src/MarginTrading.Backend.Contracts/Account/DataReaderAccountStatsBackendContract.cs deleted file mode 100644 index e39eafae5..000000000 --- a/src/MarginTrading.Backend.Contracts/Account/DataReaderAccountStatsBackendContract.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace MarginTrading.Backend.Contracts.Account -{ - public class DataReaderAccountStatsBackendContract - { - public string AccountId { get; set; } - public string BaseAssetId { get; set; } - public decimal MarginCall { get; set; } - public decimal StopOut { get; set; } - public decimal TotalCapital { get; set; } - public decimal FreeMargin { get; set; } - public decimal MarginAvailable { get; set; } - public decimal UsedMargin { get; set; } - public decimal MarginInit { get; set; } - public decimal PnL { get; set; } - public decimal OpenPositionsCount { get; set; } - public decimal MarginUsageLevel { get; set; } - public string LegalEntity { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AccountAssetPair/AccountAssetPairContract.cs b/src/MarginTrading.Backend.Contracts/AccountAssetPair/AccountAssetPairContract.cs deleted file mode 100644 index c6cf4ca17..000000000 --- a/src/MarginTrading.Backend.Contracts/AccountAssetPair/AccountAssetPairContract.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace MarginTrading.Backend.Contracts.AccountAssetPair -{ - public class AccountAssetPairContract - { - public string TradingConditionId { get; set; } - public string BaseAssetId { get; set; } - public string Instrument { get; set; } - public int LeverageInit { get; set; } - public int LeverageMaintenance { get; set; } - public decimal SwapLong { get; set; } - public decimal SwapShort { get; set; } - public decimal OvernightSwapLong { get; set; } - public decimal OvernightSwapShort { get; set; } - public decimal CommissionLong { get; set; } - public decimal CommissionShort { get; set; } - public decimal CommissionLot { get; set; } - public decimal DeltaBid { get; set; } - public decimal DeltaAsk { get; set; } - public decimal DealLimit { get; set; } - public decimal PositionLimit { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AccountBalance/AccounResetRequest.cs b/src/MarginTrading.Backend.Contracts/AccountBalance/AccounResetRequest.cs deleted file mode 100644 index 0359212ac..000000000 --- a/src/MarginTrading.Backend.Contracts/AccountBalance/AccounResetRequest.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MarginTrading.Backend.Contracts.AccountBalance -{ - public class AccounResetRequest - { - public string ClientId { get; set; } - public string AccountId { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AccountBalance/AccountChargeManuallyRequest.cs b/src/MarginTrading.Backend.Contracts/AccountBalance/AccountChargeManuallyRequest.cs deleted file mode 100644 index 21fe1cd20..000000000 --- a/src/MarginTrading.Backend.Contracts/AccountBalance/AccountChargeManuallyRequest.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MarginTrading.Backend.Contracts.AccountBalance -{ - public class AccountChargeManuallyRequest - { - public string ClientId { get; set; } - public string AccountId { get; set; } - public decimal Amount { get; set; } - public string Reason { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AccountBalance/AccountChargeManuallyResponse.cs b/src/MarginTrading.Backend.Contracts/AccountBalance/AccountChargeManuallyResponse.cs deleted file mode 100644 index 15de6771a..000000000 --- a/src/MarginTrading.Backend.Contracts/AccountBalance/AccountChargeManuallyResponse.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MarginTrading.Backend.Contracts.AccountBalance -{ - public class AccountChargeManuallyResponse - { - public string TransactionId { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AccountBalance/AccountDepositWithdrawRequest.cs b/src/MarginTrading.Backend.Contracts/AccountBalance/AccountDepositWithdrawRequest.cs deleted file mode 100644 index 1b91d4a35..000000000 --- a/src/MarginTrading.Backend.Contracts/AccountBalance/AccountDepositWithdrawRequest.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace MarginTrading.Backend.Contracts.AccountBalance -{ - public class AccountDepositWithdrawRequest - { - public string ClientId { get; set; } - public string AccountId { get; set; } - public PaymentType PaymentType { get; set; } - public decimal Amount { get; set; } - public string TransactionId { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AccountBalance/AccountDepositWithdrawResponse.cs b/src/MarginTrading.Backend.Contracts/AccountBalance/AccountDepositWithdrawResponse.cs deleted file mode 100644 index defe73308..000000000 --- a/src/MarginTrading.Backend.Contracts/AccountBalance/AccountDepositWithdrawResponse.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MarginTrading.Backend.Contracts.AccountBalance -{ - public class AccountDepositWithdrawResponse - { - public string TransactionId { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AccountBalance/AccountResetResponse.cs b/src/MarginTrading.Backend.Contracts/AccountBalance/AccountResetResponse.cs deleted file mode 100644 index f26bd5e5b..000000000 --- a/src/MarginTrading.Backend.Contracts/AccountBalance/AccountResetResponse.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MarginTrading.Backend.Contracts.AccountBalance -{ - public class AccountResetResponse - { - public string TransactionId { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AccountBalance/PaymentType.cs b/src/MarginTrading.Backend.Contracts/AccountBalance/PaymentType.cs deleted file mode 100644 index 1cf591cf7..000000000 --- a/src/MarginTrading.Backend.Contracts/AccountBalance/PaymentType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MarginTrading.Backend.Contracts.AccountBalance -{ - public enum PaymentType - { - Transfer, - Swift, - BankCard - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AccountHistory/AccountHistoryContract.cs b/src/MarginTrading.Backend.Contracts/AccountHistory/AccountHistoryContract.cs deleted file mode 100644 index 513daa5a2..000000000 --- a/src/MarginTrading.Backend.Contracts/AccountHistory/AccountHistoryContract.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace MarginTrading.Backend.Contracts.AccountHistory -{ - public class AccountHistoryContract - { - public string Id { get; set; } - public DateTime Date { get; set; } - public string AccountId { get; set; } - public string ClientId { get; set; } - public decimal Amount { get; set; } - public decimal Balance { get; set; } - public decimal WithdrawTransferLimit { get; set; } - public string Comment { get; set; } - public AccountHistoryTypeContract Type { get; set; } - public string OrderId { get; set; } - public string LegalEntity { get; set; } - public string AuditLog { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AccountHistory/AccountHistoryItem.cs b/src/MarginTrading.Backend.Contracts/AccountHistory/AccountHistoryItem.cs deleted file mode 100644 index d38cba89a..000000000 --- a/src/MarginTrading.Backend.Contracts/AccountHistory/AccountHistoryItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using JetBrains.Annotations; - -namespace MarginTrading.Backend.Contracts.AccountHistory -{ - public class AccountHistoryItem - { - public DateTime Date { get; set; } - [CanBeNull] public AccountHistoryContract Account { get; set; } - [CanBeNull] public OrderHistoryContract Position { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AccountHistory/AccountHistoryRequest.cs b/src/MarginTrading.Backend.Contracts/AccountHistory/AccountHistoryRequest.cs deleted file mode 100644 index bf313f4ab..000000000 --- a/src/MarginTrading.Backend.Contracts/AccountHistory/AccountHistoryRequest.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace MarginTrading.Backend.Contracts.AccountHistory -{ - public class AccountHistoryRequest - { - public string ClientId { get; set; } - public string AccountId { get; set; } - public DateTime? From { get; set; } - public DateTime? To { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AccountHistory/AccountHistoryResponse.cs b/src/MarginTrading.Backend.Contracts/AccountHistory/AccountHistoryResponse.cs deleted file mode 100644 index ce9a2cf8c..000000000 --- a/src/MarginTrading.Backend.Contracts/AccountHistory/AccountHistoryResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MarginTrading.Backend.Contracts.AccountHistory -{ - public class AccountHistoryResponse - { - public AccountHistoryContract[] Account { get; set; } - public OrderHistoryContract[] PositionsHistory { get; set; } - public OrderHistoryContract[] OpenPositions { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AccountHistory/AccountHistoryTypeContract.cs b/src/MarginTrading.Backend.Contracts/AccountHistory/AccountHistoryTypeContract.cs deleted file mode 100644 index 54189156a..000000000 --- a/src/MarginTrading.Backend.Contracts/AccountHistory/AccountHistoryTypeContract.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace MarginTrading.Backend.Contracts.AccountHistory -{ - public enum AccountHistoryTypeContract - { - Deposit, - Withdraw, - OrderClosed, - Reset, - Swap, - Manual - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AccountHistory/AccountNewHistoryResponse.cs b/src/MarginTrading.Backend.Contracts/AccountHistory/AccountNewHistoryResponse.cs deleted file mode 100644 index 2ac450a1d..000000000 --- a/src/MarginTrading.Backend.Contracts/AccountHistory/AccountNewHistoryResponse.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MarginTrading.Backend.Contracts.AccountHistory -{ - public class AccountNewHistoryResponse - { - public AccountHistoryItem[] HistoryItems { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AccountHistory/OrderHistoryContract.cs b/src/MarginTrading.Backend.Contracts/AccountHistory/OrderHistoryContract.cs deleted file mode 100644 index c0a6cacc1..000000000 --- a/src/MarginTrading.Backend.Contracts/AccountHistory/OrderHistoryContract.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using MarginTrading.Backend.Contracts.AssetPairSettings; -using MarginTrading.Backend.Contracts.TradeMonitoring; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; - -namespace MarginTrading.Backend.Contracts.AccountHistory -{ - public class OrderHistoryContract - { - public string Id { get; set; } - public long Code { get; set; } - public string AccountId { get; set; } - public string Instrument { get; set; } - public int AssetAccuracy { get; set; } - public OrderDirectionContract Type { get; set; } - public OrderStatusContract Status { get; set; } - public OrderCloseReasonContract CloseReason { get; set; } - public DateTime? OpenDate { get; set; } - public DateTime? CloseDate { get; set; } - public decimal OpenPrice { get; set; } - public decimal ClosePrice { get; set; } - public decimal Volume { get; set; } - public decimal? TakeProfit { get; set; } - public decimal? StopLoss { get; set; } - public decimal TotalPnl { get; set; } - public decimal Pnl { get; set; } - public decimal InterestRateSwap { get; set; } - public decimal CommissionLot { get; set; } - public decimal OpenCommission { get; set; } - public decimal CloseCommission { get; set; } - public string EquivalentAsset { get; set; } - public decimal OpenPriceEquivalent{ get; set; } - public decimal ClosePriceEquivalent { get; set; } - public string OpenExternalOrderId { get; set; } - public string OpenExternalProviderId { get; set; } - public string CloseExternalOrderId { get; set; } - public string CloseExternalProviderId { get; set; } - - [JsonConverter(typeof(StringEnumConverter))] - public MatchingEngineModeContract MatchingEngineMode { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Activities/OrderCancellationReasonContract.cs b/src/MarginTrading.Backend.Contracts/Activities/OrderCancellationReasonContract.cs new file mode 100644 index 000000000..6a5d1ac55 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Activities/OrderCancellationReasonContract.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace MarginTrading.Backend.Contracts.Activities +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum OrderCancellationReasonContract + { + None, + BaseOrderCancelled, + ParentPositionClosed, + ConnectedOrderExecuted, + CorporateAction, + InstrumentInvalidated, + AccountInactivated, + Expired + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Activities/OrderCancelledMetadata.cs b/src/MarginTrading.Backend.Contracts/Activities/OrderCancelledMetadata.cs new file mode 100644 index 000000000..98ea086f6 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Activities/OrderCancelledMetadata.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.Activities +{ + public class OrderCancelledMetadata + { + public OrderCancellationReasonContract Reason { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Activities/OrderChangedMetadata.cs b/src/MarginTrading.Backend.Contracts/Activities/OrderChangedMetadata.cs new file mode 100644 index 000000000..dd1568a7f --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Activities/OrderChangedMetadata.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.Activities +{ + public class OrderChangedMetadata + { + public OrderChangedProperty UpdatedProperty { get; set; } + + public string OldValue { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Activities/OrderChangedProperty.cs b/src/MarginTrading.Backend.Contracts/Activities/OrderChangedProperty.cs new file mode 100644 index 000000000..71998e6ff --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Activities/OrderChangedProperty.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace MarginTrading.Backend.Contracts.Activities +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum OrderChangedProperty + { + None = 0, + Price = 1, + Volume = 2, + RelatedOrderRemoved = 3, + Validity = 4, + ForceOpen = 5, + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Activities/PositionOpenMetadata.cs b/src/MarginTrading.Backend.Contracts/Activities/PositionOpenMetadata.cs new file mode 100644 index 000000000..9047de1d9 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Activities/PositionOpenMetadata.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.Activities +{ + public class PositionOpenMetadata + { + public bool ExistingPositionIncreased { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AssetPairSettings/AssetPairContract.cs b/src/MarginTrading.Backend.Contracts/AssetPairSettings/AssetPairContract.cs deleted file mode 100644 index 9be1b32e8..000000000 --- a/src/MarginTrading.Backend.Contracts/AssetPairSettings/AssetPairContract.cs +++ /dev/null @@ -1,13 +0,0 @@ -using JetBrains.Annotations; - -namespace MarginTrading.Backend.Contracts.AssetPairSettings -{ - [PublicAPI] - public class AssetPairContract : AssetPairInputContract - { - /// - /// Instrument id - /// - public string Id { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AssetPairSettings/AssetPairInputContract.cs b/src/MarginTrading.Backend.Contracts/AssetPairSettings/AssetPairInputContract.cs deleted file mode 100644 index 8c86d64b6..000000000 --- a/src/MarginTrading.Backend.Contracts/AssetPairSettings/AssetPairInputContract.cs +++ /dev/null @@ -1,63 +0,0 @@ -using JetBrains.Annotations; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; - -namespace MarginTrading.Backend.Contracts.AssetPairSettings -{ - [PublicAPI] - public class AssetPairInputContract - { - /// - /// Instrument display name - /// - public string Name { get; set; } - - /// - /// Base asset id - /// - public string BaseAssetId { get; set; } - - /// - /// Quoting asset id - /// - public string QuoteAssetId { get; set; } - - /// - /// Instrument accuracy in decimal digits count - /// - public int Accuracy { get; set; } - - /// - /// Id of legal entity - /// - public string LegalEntity { get; set; } - - /// - /// Base pair id (ex. BTCUSD for id BTCUSD.cy) - /// - [CanBeNull] - public string BasePairId { get; set; } - - /// - /// How should this asset pair be traded - /// - [JsonConverter(typeof(StringEnumConverter))] - public MatchingEngineModeContract MatchingEngineMode { get; set; } - - /// - /// Markup for bid for stp mode. 1 results in no changes. - /// - /// - /// You cannot specify a value lower or equal to 0 to ensure positive resulting values. - /// - public decimal StpMultiplierMarkupBid { get; set; } - - /// - /// Markup for ask for stp mode. 1 results in no changes. - /// - /// - /// You cannot specify a value lower or equal to 0 to ensure positive resulting values. - /// - public decimal StpMultiplierMarkupAsk { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/AssetPairSettings/MatchingEngineModeContract.cs b/src/MarginTrading.Backend.Contracts/AssetPairSettings/MatchingEngineModeContract.cs index e3f3986e1..3abffeacd 100644 --- a/src/MarginTrading.Backend.Contracts/AssetPairSettings/MatchingEngineModeContract.cs +++ b/src/MarginTrading.Backend.Contracts/AssetPairSettings/MatchingEngineModeContract.cs @@ -1,4 +1,7 @@ -using JetBrains.Annotations; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; namespace MarginTrading.Backend.Contracts.AssetPairSettings { diff --git a/src/MarginTrading.Backend.Contracts/Client/IMtBackendClient.cs b/src/MarginTrading.Backend.Contracts/Client/IMtBackendClient.cs deleted file mode 100644 index 79fdcd15a..000000000 --- a/src/MarginTrading.Backend.Contracts/Client/IMtBackendClient.cs +++ /dev/null @@ -1,33 +0,0 @@ -using JetBrains.Annotations; - -namespace MarginTrading.Backend.Contracts.Client -{ - [PublicAPI] - public interface IMtBackendClient - { - /// - /// Manages day offs schedule and exclusions - /// - IScheduleSettingsApi ScheduleSettings { get; } - - /// - /// Account deposit, withdraw and other operations with balace - /// - IAccountsBalanceApi AccountsBalance { get; } - - /// - /// Manages Asset Pairs - /// - IAssetPairsEditingApi AssetPairsEdit { get; } - - /// - /// Manages Trading Conditions - /// - ITradingConditionsEditingApi TradingConditionsEdit { get; } - - /// - /// Performing trading operations - /// - ITradingApi Trading { get; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Client/IMtBackendClientsPair.cs b/src/MarginTrading.Backend.Contracts/Client/IMtBackendClientsPair.cs deleted file mode 100644 index 287e798de..000000000 --- a/src/MarginTrading.Backend.Contracts/Client/IMtBackendClientsPair.cs +++ /dev/null @@ -1,13 +0,0 @@ -using JetBrains.Annotations; - -namespace MarginTrading.Backend.Contracts.Client -{ - [PublicAPI] - public interface IMtBackendClientsPair - { - IMtBackendClient Demo { get; } - IMtBackendClient Live { get; } - - IMtBackendClient Get(bool isLive); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Client/MtBackendClient.cs b/src/MarginTrading.Backend.Contracts/Client/MtBackendClient.cs deleted file mode 100644 index c0ca9990e..000000000 --- a/src/MarginTrading.Backend.Contracts/Client/MtBackendClient.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Lykke.HttpClientGenerator; - -namespace MarginTrading.Backend.Contracts.Client -{ - internal class MtBackendClient : IMtBackendClient - { - public IScheduleSettingsApi ScheduleSettings { get; } - - public IAccountsBalanceApi AccountsBalance { get; } - - public IAssetPairsEditingApi AssetPairsEdit { get; } - - public ITradingConditionsEditingApi TradingConditionsEdit { get; } - - public ITradingApi Trading { get; } - - public MtBackendClient(IHttpClientGenerator clientProxyGenerator) - { - ScheduleSettings = clientProxyGenerator.Generate(); - AccountsBalance = clientProxyGenerator.Generate(); - AssetPairsEdit = clientProxyGenerator.Generate(); - TradingConditionsEdit = clientProxyGenerator.Generate(); - Trading = clientProxyGenerator.Generate(); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Client/MtBackendClientsPair.cs b/src/MarginTrading.Backend.Contracts/Client/MtBackendClientsPair.cs deleted file mode 100644 index a7973d25b..000000000 --- a/src/MarginTrading.Backend.Contracts/Client/MtBackendClientsPair.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace MarginTrading.Backend.Contracts.Client -{ - internal class MtBackendClientsPair : IMtBackendClientsPair - { - public IMtBackendClient Demo { get; } - public IMtBackendClient Live { get; } - - public IMtBackendClient Get(bool isLive) - { - return isLive ? Live : Demo; - } - - public MtBackendClientsPair(IMtBackendClient demo, IMtBackendClient live) - { - Demo = demo ?? throw new ArgumentNullException(nameof(demo)); - Live = live ?? throw new ArgumentNullException(nameof(live)); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Client/ServiceCollectionExtensions.cs b/src/MarginTrading.Backend.Contracts/Client/ServiceCollectionExtensions.cs deleted file mode 100644 index 18be1c125..000000000 --- a/src/MarginTrading.Backend.Contracts/Client/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using JetBrains.Annotations; -using Lykke.HttpClientGenerator; -using Microsoft.Extensions.DependencyInjection; - -namespace MarginTrading.Backend.Contracts.Client -{ - public static class ServiceCollectionExtensions - { - [PublicAPI] - public static void RegisterMtBackendClientsPair(this IServiceCollection services, IHttpClientGenerator demo, - IHttpClientGenerator live) - { - services.AddSingleton(p => new MtBackendClientsPair( - new MtBackendClient(demo), - new MtBackendClient(live))); - } - - [PublicAPI] - public static void RegisterMtBackendClient(this IServiceCollection services, IHttpClientGenerator clientProxyGenerator) - { - services.AddSingleton(p => new MtBackendClient(clientProxyGenerator)); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/ClientCachingAttribute.cs b/src/MarginTrading.Backend.Contracts/ClientCachingAttribute.cs deleted file mode 100644 index d4486f649..000000000 --- a/src/MarginTrading.Backend.Contracts/ClientCachingAttribute.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; - -namespace MarginTrading.Backend.Contracts -{ - [AttributeUsage(AttributeTargets.Method)] - public class ClientCachingAttribute : Attribute - { - private TimeSpan _cachingTime; - - public TimeSpan CachingTime => _cachingTime; - - public int Hours - { - set => _cachingTime = new TimeSpan(value, _cachingTime.Minutes, _cachingTime.Seconds); - get => _cachingTime.Hours; - } - - public int Minutes - { - set => _cachingTime = new TimeSpan(_cachingTime.Hours, value, _cachingTime.Seconds); - get => _cachingTime.Minutes; - } - - public int Seconds - { - set => _cachingTime = new TimeSpan(_cachingTime.Hours, _cachingTime.Minutes, value); - get => _cachingTime.Seconds; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Common/AccountBalanceMessageBase.cs b/src/MarginTrading.Backend.Contracts/Common/AccountBalanceMessageBase.cs new file mode 100644 index 000000000..92b1f2ca0 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Common/AccountBalanceMessageBase.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using JetBrains.Annotations; +using MessagePack; + +namespace MarginTrading.Backend.Contracts +{ + [MessagePackObject] + public abstract class AccountBalanceMessageBase + { + [Key(0)][NotNull] + public string ClientId { get; } + + [Key(1)][NotNull] + public string AccountId { get; } + + [Key(2)] + public decimal Amount { get; } + + [Key(3)][NotNull] + public string OperationId { get; } + + [Key(4)][NotNull] + public string Reason { get; } + + protected AccountBalanceMessageBase([NotNull] string clientId, [NotNull] string accountId, decimal amount, + [NotNull] string operationId, [NotNull] string reason) + { + ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); + AccountId = accountId ?? throw new ArgumentNullException(nameof(accountId)); + Amount = amount; + OperationId = operationId ?? throw new ArgumentNullException(nameof(operationId)); + Reason = reason ?? throw new ArgumentNullException(nameof(reason)); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Common/BackendResponse.cs b/src/MarginTrading.Backend.Contracts/Common/BackendResponse.cs deleted file mode 100644 index e2377874c..000000000 --- a/src/MarginTrading.Backend.Contracts/Common/BackendResponse.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace MarginTrading.Backend.Contracts.Common -{ - public class BackendResponse - { - private string _errorMessage; - public TResult Result { get; set; } - - public string ErrorMessage - { - get => _errorMessage; - set => _errorMessage = value; - } - - public string Message - { - get => _errorMessage; - set => _errorMessage = value; - } - - public bool IsOk => string.IsNullOrEmpty(ErrorMessage); - - public static BackendResponse Ok(TResult result) - { - return new BackendResponse - { - Result = result - }; - } - - public static BackendResponse Error(string message) - { - return new BackendResponse - { - ErrorMessage = message - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Common/PaginatedResponseContract.cs b/src/MarginTrading.Backend.Contracts/Common/PaginatedResponseContract.cs new file mode 100644 index 000000000..41cd6f515 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Common/PaginatedResponseContract.cs @@ -0,0 +1,45 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace MarginTrading.Backend.Contracts.Common +{ + /// + /// Paginated response wrapper + /// + /// + public class PaginatedResponseContract + { + /// + /// Paginated sorted contents + /// + [NotNull] + public IReadOnlyList Contents { get; } + + /// + /// Start position in total contents + /// + public int Start { get; } + + /// + /// Size of returned contents + /// + public int Size { get; } + + /// + /// Total size of all the contents + /// + public int TotalSize { get; } + + public PaginatedResponseContract([NotNull] IReadOnlyList contents, int start, int size, int totalSize) + { + Contents = contents ?? throw new ArgumentNullException(nameof(contents)); + Start = start; + Size = size; + TotalSize = totalSize; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/DataReaderClient/IMtDatareaderClient.cs b/src/MarginTrading.Backend.Contracts/DataReaderClient/IMtDatareaderClient.cs deleted file mode 100644 index 1872697fc..000000000 --- a/src/MarginTrading.Backend.Contracts/DataReaderClient/IMtDatareaderClient.cs +++ /dev/null @@ -1,19 +0,0 @@ -using JetBrains.Annotations; - -namespace MarginTrading.Backend.Contracts.DataReaderClient -{ - [PublicAPI] - public interface IMtDataReaderClient - { - IAssetPairsReadingApi AssetPairsRead { get; } - IAccountHistoryApi AccountHistory { get; } - IAccountsApi AccountsApi { get; } - IAccountAssetPairsReadingApi AccountAssetPairsRead { get; } - ITradeMonitoringReadingApi TradeMonitoringRead { get; } - ITradingConditionsReadingApi TradingConditionsRead { get; } - IAccountGroupsReadingApi AccountGroups { get; } - IDictionariesReadingApi Dictionaries { get; } - IRoutesReadingApi Routes { get; } - ISettingsReadingApi Settings { get; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/DataReaderClient/IMtDatareaderClientsPair.cs b/src/MarginTrading.Backend.Contracts/DataReaderClient/IMtDatareaderClientsPair.cs deleted file mode 100644 index 9b111e1cd..000000000 --- a/src/MarginTrading.Backend.Contracts/DataReaderClient/IMtDatareaderClientsPair.cs +++ /dev/null @@ -1,12 +0,0 @@ -using JetBrains.Annotations; - -namespace MarginTrading.Backend.Contracts.DataReaderClient -{ - [PublicAPI] - public interface IMtDataReaderClientsPair - { - IMtDataReaderClient Demo { get; } - IMtDataReaderClient Live { get; } - IMtDataReaderClient Get(bool isLive); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/DataReaderClient/MtDatareaderClient.cs b/src/MarginTrading.Backend.Contracts/DataReaderClient/MtDatareaderClient.cs deleted file mode 100644 index 6d04f5489..000000000 --- a/src/MarginTrading.Backend.Contracts/DataReaderClient/MtDatareaderClient.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Lykke.HttpClientGenerator; - -namespace MarginTrading.Backend.Contracts.DataReaderClient -{ - internal class MtDataReaderClient : IMtDataReaderClient - { - public IAssetPairsReadingApi AssetPairsRead { get; } - public IAccountHistoryApi AccountHistory { get; } - public IAccountsApi AccountsApi { get; } - public IAccountAssetPairsReadingApi AccountAssetPairsRead { get; } - public ITradeMonitoringReadingApi TradeMonitoringRead { get; } - public ITradingConditionsReadingApi TradingConditionsRead { get; } - public IAccountGroupsReadingApi AccountGroups { get; } - public IDictionariesReadingApi Dictionaries { get; } - public IRoutesReadingApi Routes { get; } - public ISettingsReadingApi Settings { get; } - - public MtDataReaderClient(IHttpClientGenerator clientGenerator) - { - AssetPairsRead = clientGenerator.Generate(); - AccountHistory = clientGenerator.Generate(); - AccountsApi = clientGenerator.Generate(); - AccountAssetPairsRead = clientGenerator.Generate(); - TradeMonitoringRead = clientGenerator.Generate(); - TradingConditionsRead = clientGenerator.Generate(); - AccountGroups = clientGenerator.Generate(); - Dictionaries = clientGenerator.Generate(); - Routes = clientGenerator.Generate(); - Settings = clientGenerator.Generate(); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/DataReaderClient/MtDatareaderClientsPair.cs b/src/MarginTrading.Backend.Contracts/DataReaderClient/MtDatareaderClientsPair.cs deleted file mode 100644 index 0c1bd6812..000000000 --- a/src/MarginTrading.Backend.Contracts/DataReaderClient/MtDatareaderClientsPair.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace MarginTrading.Backend.Contracts.DataReaderClient -{ - internal class MtDataReaderClientsPair : IMtDataReaderClientsPair - { - public IMtDataReaderClient Demo { get; } - public IMtDataReaderClient Live { get; } - - public IMtDataReaderClient Get(bool isLive) - { - return isLive ? Live : Demo; - } - - public MtDataReaderClientsPair(IMtDataReaderClient demo, IMtDataReaderClient live) - { - Demo = demo ?? throw new ArgumentNullException(nameof(demo)); - Live = live ?? throw new ArgumentNullException(nameof(live)); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/DataReaderClient/ServiceCollectionExtensions.cs b/src/MarginTrading.Backend.Contracts/DataReaderClient/ServiceCollectionExtensions.cs deleted file mode 100644 index c4b1cd5b1..000000000 --- a/src/MarginTrading.Backend.Contracts/DataReaderClient/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using JetBrains.Annotations; -using Lykke.HttpClientGenerator; -using Microsoft.Extensions.DependencyInjection; - -namespace MarginTrading.Backend.Contracts.DataReaderClient -{ - public static class ServiceCollectionExtensions - { - [PublicAPI] - public static void RegisterMtDataReaderClientsPair(this IServiceCollection services, IHttpClientGenerator demo, - IHttpClientGenerator live) - { - services.AddSingleton(p => new MtDataReaderClientsPair( - new MtDataReaderClient(demo), - new MtDataReaderClient(live))); - } - - [PublicAPI] - public static void RegisterMtDataReaderClient(this IServiceCollection services, IHttpClientGenerator clientProxyGenerator) - { - services.AddSingleton(p => new MtDataReaderClient(clientProxyGenerator)); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/DayOffSettings/CompiledExclusionContract.cs b/src/MarginTrading.Backend.Contracts/DayOffSettings/CompiledExclusionContract.cs deleted file mode 100644 index 7c651fbae..000000000 --- a/src/MarginTrading.Backend.Contracts/DayOffSettings/CompiledExclusionContract.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace MarginTrading.Backend.Contracts.DayOffSettings -{ - public class CompiledExclusionContract - { - public Guid Id { get; set; } - public string AssetPairRegex { get; set; } - public string AssetPairId { get; set; } - public DateTime Start { get; set; } - public DateTime End { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/DayOffSettings/CompiledExclusionsContract.cs b/src/MarginTrading.Backend.Contracts/DayOffSettings/CompiledExclusionsContract.cs deleted file mode 100644 index 368659dd8..000000000 --- a/src/MarginTrading.Backend.Contracts/DayOffSettings/CompiledExclusionsContract.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; - -namespace MarginTrading.Backend.Contracts.DayOffSettings -{ - public class CompiledExclusionsListContract - { - public IReadOnlyList TradesEnabled { get; set; } - public IReadOnlyList TradesDisabled { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/DayOffSettings/DayOffExclusionContract.cs b/src/MarginTrading.Backend.Contracts/DayOffSettings/DayOffExclusionContract.cs deleted file mode 100644 index 453c2b8ec..000000000 --- a/src/MarginTrading.Backend.Contracts/DayOffSettings/DayOffExclusionContract.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace MarginTrading.Backend.Contracts.DayOffSettings -{ - public class DayOffExclusionContract : DayOffExclusionInputContract - { - public Guid Id { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/DayOffSettings/DayOffExclusionInputContract.cs b/src/MarginTrading.Backend.Contracts/DayOffSettings/DayOffExclusionInputContract.cs deleted file mode 100644 index d0810bbef..000000000 --- a/src/MarginTrading.Backend.Contracts/DayOffSettings/DayOffExclusionInputContract.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace MarginTrading.Backend.Contracts.DayOffSettings -{ - public class DayOffExclusionInputContract - { - public string AssetPairRegex { get; set; } - public DateTime Start { get; set; } - public DateTime End { get; set; } - public bool IsTradeEnabled { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/DayOffSettings/ScheduleSettingsContract.cs b/src/MarginTrading.Backend.Contracts/DayOffSettings/ScheduleSettingsContract.cs deleted file mode 100644 index a4e64f6db..000000000 --- a/src/MarginTrading.Backend.Contracts/DayOffSettings/ScheduleSettingsContract.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MarginTrading.Backend.Contracts.DayOffSettings -{ - public class ScheduleSettingsContract - { - public DayOfWeek DayOffStartDay { get; set; } - public TimeSpan DayOffStartTime { get; set; } - public DayOfWeek DayOffEndDay { get; set; } - public TimeSpan DayOffEndTime { get; set; } - public HashSet AssetPairsWithoutDayOff { get; set; } - public TimeSpan PendingOrdersCutOff { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Events/MarginEventMessage.cs b/src/MarginTrading.Backend.Contracts/Events/MarginEventMessage.cs new file mode 100644 index 000000000..a66504023 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Events/MarginEventMessage.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Contracts.Events +{ + public class MarginEventMessage + { + public string EventId { get; set; } + public DateTime EventTime { get; set; } + public MarginEventTypeContract EventType { get; set; } + + public string AccountId { get; set; } + public string TradingConditionId { get; set; } + public string BaseAssetId { get; set; } + public decimal Balance { get; set; } + public decimal WithdrawTransferLimit { get; set; } + + public decimal MarginCall1Level { get; set; } + public decimal MarginCall2Level { get; set; } + public decimal StopOutLevel { get; set; } + public decimal TotalCapital { get; set; } + public decimal FreeMargin { get; set; } + public decimal MarginAvailable { get; set; } + public decimal UsedMargin { get; set; } + public decimal MarginInit { get; set; } + public decimal PnL { get; set; } + public decimal OpenPositionsCount { get; set; } + public decimal MarginUsageLevel { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Events/MarginEventTypeContract.cs b/src/MarginTrading.Backend.Contracts/Events/MarginEventTypeContract.cs new file mode 100644 index 000000000..6ed62962b --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Events/MarginEventTypeContract.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace MarginTrading.Backend.Contracts.Events +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum MarginEventTypeContract + { + MarginCall1, + MarginCall2, + OvernightMarginCall, + Stopout, + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Events/OrderHistoryEvent.cs b/src/MarginTrading.Backend.Contracts/Events/OrderHistoryEvent.cs new file mode 100644 index 000000000..3fa77cf66 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Events/OrderHistoryEvent.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MarginTrading.Backend.Contracts.Orders; + +namespace MarginTrading.Backend.Contracts.Events +{ + public class OrderHistoryEvent + { + public OrderContract OrderSnapshot { get; set; } + + public OrderHistoryTypeContract Type { get; set; } + + public DateTime Timestamp { get; set; } + + /// + /// Serialised object with additional information for activities in any format + /// + public string ActivitiesMetadata { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Events/OrderHistoryTypeContract.cs b/src/MarginTrading.Backend.Contracts/Events/OrderHistoryTypeContract.cs new file mode 100644 index 000000000..80fd50cfc --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Events/OrderHistoryTypeContract.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.Events +{ + public enum OrderHistoryTypeContract + { + Place, + Activate, + Change, + Cancel, + Reject, + ExecutionStarted, + Executed + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Events/OrderPlacementRejectedEvent.cs b/src/MarginTrading.Backend.Contracts/Events/OrderPlacementRejectedEvent.cs new file mode 100644 index 000000000..2ba66e087 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Events/OrderPlacementRejectedEvent.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MarginTrading.Backend.Contracts.Orders; +using MarginTrading.Backend.Contracts.TradeMonitoring; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.Events +{ + /// + /// Event of order placement request being rejected without placing an order. + /// + [MessagePackObject] + public class OrderPlacementRejectedEvent + { + /// + /// Id of the process which caused parameter change. + /// + [Key(0)] + public string CorrelationId { get; set; } + + /// + /// Time of event generation. + /// + [Key(1)] + public DateTime EventTimestamp { get; set; } + + /// + /// Order placement request which was rejected. + /// + [Key(2)] + public OrderPlaceRequest OrderPlaceRequest { get; set; } + + /// + /// Order reject reason + /// + [Key(3)] + public OrderRejectReasonContract RejectReason { get; set; } + + /// + /// Order reject reason text + /// + [Key(4)] + public string RejectReasonText { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Events/OvernightMarginParameterChangedEvent.cs b/src/MarginTrading.Backend.Contracts/Events/OvernightMarginParameterChangedEvent.cs new file mode 100644 index 000000000..729511471 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Events/OvernightMarginParameterChangedEvent.cs @@ -0,0 +1,40 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.Events +{ + /// + /// Indicates that persisted value of overnight margin parameter has changed. + /// + [MessagePackObject] + public class OvernightMarginParameterChangedEvent + { + /// + /// Id of the process which caused parameter change. + /// + [Key(0)] + public string CorrelationId { get; set; } + + /// + /// Time of event generation. + /// + [Key(1)] + public DateTime EventTimestamp { get; set; } + + /// + /// Current state of parameter. + /// + [Key(2)] + public bool CurrentState { get; set; } + + /// + /// List all items with parameter value != 1. Format: [(TradingCondition, Instrument), Value]. + /// + [Key(3)] + public Dictionary<(string, string), decimal> ParameterValues { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Events/PositionClosedEvent.cs b/src/MarginTrading.Backend.Contracts/Events/PositionClosedEvent.cs new file mode 100644 index 000000000..52737b51a --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Events/PositionClosedEvent.cs @@ -0,0 +1,60 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using JetBrains.Annotations; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.Events +{ + /// + /// Position closed + /// + [MessagePackObject] + public class PositionClosedEvent + { + /// + /// Account id + /// + [NotNull] + [Key(0)] + public string AccountId { get; } + + /// + /// Client id + /// + [NotNull] + [Key(1)] + public string ClientId { get; } + + /// + /// Closed position id + /// + [NotNull] + [Key(2)] + public string PositionId { get; } + + /// + /// Position asset pair Id + /// + [NotNull] + [Key(3)] + public string AssetPairId { get; } + + /// + /// Profit loss which will affect the balance + /// + [Key(4)] + public decimal BalanceDelta { get; } + + public PositionClosedEvent([NotNull] string accountId, [NotNull] string clientId, [NotNull] string positionId, + [NotNull] string assetPairId, decimal balanceDelta) + { + AccountId = accountId ?? throw new ArgumentNullException(nameof(accountId)); + ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); + PositionId = positionId ?? throw new ArgumentNullException(nameof(positionId)); + AssetPairId = assetPairId ?? throw new ArgumentNullException(nameof(assetPairId)); + BalanceDelta = balanceDelta; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Events/PositionHistoryEvent.cs b/src/MarginTrading.Backend.Contracts/Events/PositionHistoryEvent.cs new file mode 100644 index 000000000..0959affe1 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Events/PositionHistoryEvent.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MarginTrading.Backend.Contracts.Positions; + +namespace MarginTrading.Backend.Contracts.Events +{ + public class PositionHistoryEvent + { + /// + /// Snapshot of position at the moment of event + /// + public PositionContract PositionSnapshot { get; set; } + + /// + /// Created deal (if position was closed or partially closed) + /// + public DealContract Deal { get; set; } + + /// + /// Type of event + /// + public PositionHistoryTypeContract EventType { get; set; } + + /// + /// Timestamp of event + /// + public DateTime Timestamp { get; set; } + + /// + /// Serialised object with additional information for activities in any format + /// + public string ActivitiesMetadata { get; set; } + + public string OrderAdditionalInfo { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Events/PositionHistoryTypeContract.cs b/src/MarginTrading.Backend.Contracts/Events/PositionHistoryTypeContract.cs new file mode 100644 index 000000000..ca76a8b66 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Events/PositionHistoryTypeContract.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.Events +{ + public enum PositionHistoryTypeContract + { + Open, + PartiallyClose, + Close + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/ExchangeConnector/ExecType.cs b/src/MarginTrading.Backend.Contracts/ExchangeConnector/ExecType.cs new file mode 100644 index 000000000..978bd384a --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/ExchangeConnector/ExecType.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.ExchangeConnector +{ + public enum ExecType + { + Unknown, + New, + PartialFill, + Fill, + DoneForDay, + Cancelled, + Replace, + PendingCancel, + Stopped, + Rejected, + Suspended, + PendingNew, + Calculated, + Expired, + Restarted, + PendingReplace, + Trade, + TradeCorrect, + TradeCancel, + OrderStatus + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/ExchangeConnector/ExecutionReport.cs b/src/MarginTrading.Backend.Contracts/ExchangeConnector/ExecutionReport.cs new file mode 100644 index 000000000..6eb7d2a6c --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/ExchangeConnector/ExecutionReport.cs @@ -0,0 +1,179 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using Newtonsoft.Json; + +namespace MarginTrading.Backend.Contracts.ExchangeConnector +{ + public class ExecutionReport + { + /// + /// Initializes a new instance of the ExecutionReport class. + /// + public ExecutionReport() + { + } + + /// + /// Initializes a new instance of the ExecutionReport class. + /// + /// A trade direction. Possible values include: + /// 'Unknown', 'Buy', 'Sell' + /// Transaction time + /// An actual price of the execution or + /// order + /// Trade volume + /// Execution fee + /// Indicates that operation was + /// successful + /// Current status of the order. Possible + /// values include: 'Unknown', 'Fill', 'PartialFill', 'Cancelled', + /// 'Rejected', 'New', 'Pending' + /// Possible values include: 'None', + /// 'Unknown', 'ExchangeError', 'ConnectorError', + /// 'InsufficientFunds' + /// A type of the order. Possible values + /// include: 'Unknown', 'Market', 'Limit' + /// A type of the execution. ExecType = Trade + /// means it is an execution, otherwise it is an order. Possible values + /// include: 'Unknown', 'New', 'PartialFill', 'Fill', 'DoneForDay', + /// 'Cancelled', 'Replace', 'PendingCancel', 'Stopped', 'Rejected', + /// 'Suspended', 'PendingNew', 'Calculated', 'Expired', 'Restarted', + /// 'PendingReplace', 'Trade', 'TradeCorrect', 'TradeCancel', + /// 'OrderStatus' + /// A client assigned ID of the + /// order + /// An exchange assigned ID of the + /// order + /// An instrument description + /// Fee currency + /// An arbitrary message from the exchange + /// related to the execution|order + public ExecutionReport( + TradeType type, + DateTime time, + double price, + double volume, + double fee, + bool success, + OrderExecutionStatus executionStatus, + OrderStatusUpdateFailureType failureType, + OrderType orderType, + ExecType execType, + string clientOrderId = null, + string exchangeOrderId = null, + Instrument instrument = null, + string feeCurrency = null, + string message = null) + { + this.ClientOrderId = clientOrderId; + this.ExchangeOrderId = exchangeOrderId; + this.Instrument = instrument; + this.Type = type; + this.Time = time; + this.Price = price; + this.Volume = volume; + this.Fee = fee; + this.FeeCurrency = feeCurrency; + this.Success = success; + this.ExecutionStatus = executionStatus; + this.FailureType = failureType; + this.Message = message; + this.OrderType = orderType; + this.ExecType = execType; + } + + /// Gets a client assigned ID of the order + [JsonProperty(PropertyName = "clientOrderId")] + public string ClientOrderId { get; private set; } + + /// Gets an exchange assigned ID of the order + [JsonProperty(PropertyName = "exchangeOrderId")] + public string ExchangeOrderId { get; private set; } + + /// Gets an instrument description + [JsonProperty(PropertyName = "instrument")] + public Instrument Instrument { get; private set; } + + /// + /// Gets a trade direction. Possible values include: 'Unknown', 'Buy', + /// 'Sell' + /// + [JsonProperty(PropertyName = "type")] + public TradeType Type { get; private set; } + + /// Gets transaction time + [JsonProperty(PropertyName = "time")] + public DateTime Time { get; private set; } + + /// Gets an actual price of the execution or order + [JsonProperty(PropertyName = "price")] + public double Price { get; private set; } + + /// Gets trade volume + [JsonProperty(PropertyName = "volume")] + public double Volume { get; private set; } + + /// Gets execution fee + [JsonProperty(PropertyName = "fee")] + public double Fee { get; private set; } + + /// Gets fee currency + [JsonProperty(PropertyName = "feeCurrency")] + public string FeeCurrency { get; private set; } + + /// Gets indicates that operation was successful + [JsonProperty(PropertyName = "success")] + public bool Success { get; set; } + + /// + /// Gets current status of the order. Possible values include: + /// 'Unknown', 'Fill', 'PartialFill', 'Cancelled', 'Rejected', 'New', + /// 'Pending' + /// + [JsonProperty(PropertyName = "executionStatus")] + public OrderExecutionStatus ExecutionStatus { get; set; } + + /// + /// Gets possible values include: 'None', 'Unknown', 'ExchangeError', + /// 'ConnectorError', 'InsufficientFunds' + /// + [JsonProperty(PropertyName = "failureType")] + public OrderStatusUpdateFailureType FailureType { get; set; } + + /// + /// Gets an arbitrary message from the exchange related to the + /// execution|order + /// + [JsonProperty(PropertyName = "message")] + public string Message { get; private set; } + + /// + /// Gets a type of the order. Possible values include: 'Unknown', + /// 'Market', 'Limit' + /// + [JsonProperty(PropertyName = "orderType")] + public OrderType OrderType { get; private set; } + + /// + /// Gets a type of the execution. ExecType = Trade means it is an + /// execution, otherwise it is an order. Possible values include: + /// 'Unknown', 'New', 'PartialFill', 'Fill', 'DoneForDay', 'Cancelled', + /// 'Replace', 'PendingCancel', 'Stopped', 'Rejected', 'Suspended', + /// 'PendingNew', 'Calculated', 'Expired', 'Restarted', + /// 'PendingReplace', 'Trade', 'TradeCorrect', 'TradeCancel', + /// 'OrderStatus' + /// + [JsonProperty(PropertyName = "execType")] + public ExecType ExecType { get; private set; } + + /// Validate the object. + /// + /// Thrown if validation fails + /// + public void Validate() + { + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/ExchangeConnector/IExchangeConnectorClient.cs b/src/MarginTrading.Backend.Contracts/ExchangeConnector/IExchangeConnectorClient.cs new file mode 100644 index 000000000..c54b527be --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/ExchangeConnector/IExchangeConnectorClient.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Refit; + +namespace MarginTrading.Backend.Contracts.ExchangeConnector +{ + [PublicAPI] + public interface IExchangeConnectorClient + { + [Post("/api/v1/Orders")] + Task ExecuteOrder(OrderModel orderModel, + CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/ExchangeConnector/Instrument.cs b/src/MarginTrading.Backend.Contracts/ExchangeConnector/Instrument.cs new file mode 100644 index 000000000..72390b479 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/ExchangeConnector/Instrument.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; + +namespace MarginTrading.Backend.Contracts.ExchangeConnector +{ + public class Instrument + { + /// Initializes a new instance of the Instrument class. + public Instrument() + { + } + + /// Initializes a new instance of the Instrument class. + public Instrument(string name = null, string exchange = null, string baseProperty = null, string quote = null) + { + this.Name = name; + this.Exchange = exchange; + this.BaseProperty = baseProperty; + this.Quote = quote; + } + + /// + /// + [JsonProperty(PropertyName = "name")] + public string Name { get; private set; } + + /// + /// + [JsonProperty(PropertyName = "exchange")] + public string Exchange { get; private set; } + + /// + /// + [JsonProperty(PropertyName = "base")] + public string BaseProperty { get; private set; } + + /// + /// + [JsonProperty(PropertyName = "quote")] + public string Quote { get; private set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/ExchangeConnector/OrderExecutionStatus.cs b/src/MarginTrading.Backend.Contracts/ExchangeConnector/OrderExecutionStatus.cs new file mode 100644 index 000000000..83a4c4aca --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/ExchangeConnector/OrderExecutionStatus.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.ExchangeConnector +{ + public enum OrderExecutionStatus + { + Unknown, + Fill, + PartialFill, + Cancelled, + Rejected, + New, + Pending + } +} diff --git a/src/MarginTrading.Backend.Contracts/ExchangeConnector/OrderModel.cs b/src/MarginTrading.Backend.Contracts/ExchangeConnector/OrderModel.cs new file mode 100644 index 000000000..4f54c6beb --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/ExchangeConnector/OrderModel.cs @@ -0,0 +1,100 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.ComponentModel; +using Newtonsoft.Json; + +namespace MarginTrading.Backend.Contracts.ExchangeConnector +{ + public class OrderModel + { + /// Initializes a new instance of the OrderModel class. + public OrderModel() + { + } + + /// Initializes a new instance of the OrderModel class. + /// Possible values include: 'Unknown', 'Buy', + /// 'Sell' + /// Possible values include: 'Unknown', + /// 'Market', 'Limit' + /// Possible values include: + /// 'GoodTillCancel', 'FillOrKill' + /// Date and time must be in 5 minutes threshold + /// from UTC now + public OrderModel(TradeType tradeType, OrderType orderType, TimeInForce timeInForce, double volume, + DateTime dateTime, string exchangeName = null, string instrument = null, double? price = null, + string orderId = null, TradeRequestModality modality = TradeRequestModality.Regular, + bool isCancellationTrade = false, string cancellationTradeExternalId = null) + { + ExchangeName = exchangeName; + Instrument = instrument; + TradeType = tradeType; + OrderType = orderType; + TimeInForce = timeInForce; + Price = price; + Volume = volume; + DateTime = dateTime; + OrderId = orderId; + Modality = modality; + IsCancellationTrade = isCancellationTrade; + CancellationTradeExternalId = cancellationTradeExternalId; + } + + /// + /// + [JsonProperty(PropertyName = "exchangeName")] + public string ExchangeName { get; set; } + + /// + /// + [JsonProperty(PropertyName = "instrument")] + public string Instrument { get; set; } + + /// + /// Gets or sets possible values include: 'Unknown', 'Buy', 'Sell' + /// + [JsonProperty(PropertyName = "tradeType")] + public TradeType TradeType { get; set; } + + /// + /// Gets or sets possible values include: 'Unknown', 'Market', 'Limit' + /// + [JsonProperty(PropertyName = "orderType")] + public OrderType OrderType { get; set; } + + /// + /// Gets or sets possible values include: 'GoodTillCancel', + /// 'FillOrKill' + /// + [JsonProperty(PropertyName = "timeInForce")] + public TimeInForce TimeInForce { get; set; } + + /// + /// + [JsonProperty(PropertyName = "price")] + public double? Price { get; set; } + + /// + /// + [JsonProperty(PropertyName = "volume")] + public double Volume { get; set; } + + /// + /// Gets or sets date and time must be in 5 minutes threshold from UTC + /// now + /// + [JsonProperty(PropertyName = "dateTime")] + public DateTime DateTime { get; set; } + + public string OrderId { get; set; } + + [DefaultValue(TradeRequestModality.Regular)] + public TradeRequestModality Modality { get; set; } + + public bool IsCancellationTrade { get; set; } + + public string CancellationTradeExternalId { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/ExchangeConnector/OrderStatusUpdateFailureType.cs b/src/MarginTrading.Backend.Contracts/ExchangeConnector/OrderStatusUpdateFailureType.cs new file mode 100644 index 000000000..93d4809ed --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/ExchangeConnector/OrderStatusUpdateFailureType.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.ExchangeConnector +{ + public enum OrderStatusUpdateFailureType + { + None, + Unknown, + ExchangeError, + ConnectorError, + InsufficientFunds + } +} diff --git a/src/MarginTrading.Backend.Contracts/ExchangeConnector/OrderType.cs b/src/MarginTrading.Backend.Contracts/ExchangeConnector/OrderType.cs new file mode 100644 index 000000000..1f7e5d0ca --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/ExchangeConnector/OrderType.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.ExchangeConnector +{ + public enum OrderType + { + Unknown, + Market, + Limit + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/ExchangeConnector/TimeInForce.cs b/src/MarginTrading.Backend.Contracts/ExchangeConnector/TimeInForce.cs new file mode 100644 index 000000000..b1df2e4d7 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/ExchangeConnector/TimeInForce.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.ExchangeConnector +{ + public enum TimeInForce + { + GoodTillCancel, + FillOrKill + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/ExchangeConnector/TradeRequestModality.cs b/src/MarginTrading.Backend.Contracts/ExchangeConnector/TradeRequestModality.cs new file mode 100644 index 000000000..070538b0b --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/ExchangeConnector/TradeRequestModality.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.ExchangeConnector +{ + public enum TradeRequestModality + { + Unspecified = 0, + Liquidation = 76, + Regular = 82 + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/ExchangeConnector/TradeType.cs b/src/MarginTrading.Backend.Contracts/ExchangeConnector/TradeType.cs new file mode 100644 index 000000000..b376d9b30 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/ExchangeConnector/TradeType.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.ExchangeConnector +{ + public enum TradeType + { + Unknown, + Buy, + Sell + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/IAccountAssetPairsReadingApi.cs b/src/MarginTrading.Backend.Contracts/IAccountAssetPairsReadingApi.cs deleted file mode 100644 index 5f7d2b90c..000000000 --- a/src/MarginTrading.Backend.Contracts/IAccountAssetPairsReadingApi.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using JetBrains.Annotations; -using MarginTrading.Backend.Contracts.AccountAssetPair; -using Refit; - -namespace MarginTrading.Backend.Contracts -{ - [PublicAPI] - public interface IAccountAssetPairsReadingApi - { - /// - /// Get all asset pairs - /// - [Get("/api/accountAssetPairs/")] - Task> List(); - - /// - /// Get asset by tradingcondition and base asset - /// - [Get("/api/accountAssetPairs/byAsset/{tradingConditionId}/{baseAssetId}")] - Task> Get(string tradingConditionId, string baseAssetId); - - /// - /// - /// - [Get("/api/accountAssetPairs/byAssetPair/{tradingConditionId}/{baseAssetId}/{assetPairId}")] - Task Get(string tradingConditionId, string baseAssetId, string assetPairId); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/IAccountGroupsReadingApi.cs b/src/MarginTrading.Backend.Contracts/IAccountGroupsReadingApi.cs deleted file mode 100644 index 704815544..000000000 --- a/src/MarginTrading.Backend.Contracts/IAccountGroupsReadingApi.cs +++ /dev/null @@ -1,24 +0,0 @@ -using JetBrains.Annotations; -using MarginTrading.Backend.Contracts.TradingConditions; -using Refit; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace MarginTrading.Backend.Contracts -{ - [PublicAPI] - public interface IAccountGroupsReadingApi - { - /// - /// Returns all account groups - /// - [Get("/api/accountGroups/")] - Task> List(); - - /// - /// Returns an account group by tradingConditionId and baseAssetId - /// - [Get("/api/accountGroups/byBaseAsset/{tradingConditionId}/{baseAssetId}")] - Task GetByBaseAsset(string tradingConditionId, string baseAssetId); - } -} diff --git a/src/MarginTrading.Backend.Contracts/IAccountHistoryApi.cs b/src/MarginTrading.Backend.Contracts/IAccountHistoryApi.cs deleted file mode 100644 index e5083de8d..000000000 --- a/src/MarginTrading.Backend.Contracts/IAccountHistoryApi.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using MarginTrading.Backend.Contracts.AccountHistory; -using Refit; - -namespace MarginTrading.Backend.Contracts -{ - public interface IAccountHistoryApi - { - [Get("/api/accountHistory/byTypes")] - Task ByTypes([Query] AccountHistoryRequest request); - - [Get("/api/accountHistory/byAccounts")] - Task> ByAccounts( - [Query] string accountId = null, [Query] DateTime? from = null, [Query] DateTime? to = null); - - [Get("/api/accountHistory/timeline")] - Task Timeline([Query] AccountHistoryRequest request); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/IAccountsApi.cs b/src/MarginTrading.Backend.Contracts/IAccountsApi.cs index e90bf5941..40f2ac9b9 100644 --- a/src/MarginTrading.Backend.Contracts/IAccountsApi.cs +++ b/src/MarginTrading.Backend.Contracts/IAccountsApi.cs @@ -1,22 +1,48 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using System.Threading.Tasks; +using JetBrains.Annotations; using MarginTrading.Backend.Contracts.Account; +using MarginTrading.Backend.Contracts.Common; using Refit; namespace MarginTrading.Backend.Contracts { + [PublicAPI] public interface IAccountsApi { - [Get("/api/accounts/")] - Task> GetAllAccounts(); - + /// + /// Returns all accounts stats + /// [Get("/api/accounts/stats")] - Task> GetAllAccountStats(); + Task> GetAllAccountStats(); + + /// + /// Returns all accounts stats, optionally paginated. Both skip and take must be set or unset. + /// + [Get("/api/accounts/stats/by-pages")] + Task> GetAllAccountStatsByPages( + [Query, CanBeNull] int? skip = null, [Query, CanBeNull] int? take = null); - [Get("/api/accounts/byClient/{clientId}")] - Task> GetAccountsByClientId(string clientId); + /// + /// Get accounts depending on active/open orders and positions for particular assets. + /// + /// List of account ids + [Post("/api/accounts")] + Task> GetAllAccountIdsFiltered([Body] ActiveAccountsRequest request); - [Get("/api/accounts/byId/{id}")] - Task GetAccountById(string id); + /// + /// Returns stats of selected account + /// + [Get("/api/accounts/stats/{accountId}")] + Task GetAccountStats([NotNull] string accountId); + + /// + /// Resumes liquidation of selected account + /// + [Post("/api/accounts/resume-liquidation/{accountId}")] + Task ResumeLiquidation(string accountId, string comment); } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/IAccountsBalanceApi.cs b/src/MarginTrading.Backend.Contracts/IAccountsBalanceApi.cs deleted file mode 100644 index 9a7c38798..000000000 --- a/src/MarginTrading.Backend.Contracts/IAccountsBalanceApi.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Threading.Tasks; -using JetBrains.Annotations; -using MarginTrading.Backend.Contracts.AccountBalance; -using MarginTrading.Backend.Contracts.Common; -using Refit; - -namespace MarginTrading.Backend.Contracts -{ - /// - /// Account deposit, withdraw and other operations with balace - /// - [PublicAPI] - public interface IAccountsBalanceApi - { - /// - /// Add funds to account - /// - [Post("/api/AccountsBalance/deposit")] - Task> AccountDeposit(AccountDepositWithdrawRequest request); - - /// - /// Remove funds from account - /// - [Post("/api/AccountsBalance/withdraw")] - Task> AccountWithdraw(AccountDepositWithdrawRequest request); - - /// - /// Resets DEMO account balance to default - /// - /// - /// Only for DEMO account - /// - [Post("/api/AccountsBalance/reset")] - Task> AccountResetDemo(AccounResetRequest request); - - /// - /// Manually charge client's account. Amount is absolute, i.e. negative value goes for charging. - /// - [Post("/api/AccountsBalance/chargeManually")] - Task> ChargeManually([Body]AccountChargeManuallyRequest request); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/IAssetPairsEditingApi.cs b/src/MarginTrading.Backend.Contracts/IAssetPairsEditingApi.cs deleted file mode 100644 index c43755ed2..000000000 --- a/src/MarginTrading.Backend.Contracts/IAssetPairsEditingApi.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Threading.Tasks; -using JetBrains.Annotations; -using MarginTrading.Backend.Contracts.AssetPairSettings; -using Refit; - -namespace MarginTrading.Backend.Contracts -{ - [PublicAPI] - public interface IAssetPairsEditingApi - { - /// - /// Insert new pair - /// - [Post("/api/AssetPairs/{assetPairId}")] - Task Insert(string assetPairId, [Body] AssetPairInputContract settings); - - /// - /// Update existing pair - /// - [Put("/api/AssetPairs/{assetPairId}")] - Task Update(string assetPairId, [Body] AssetPairInputContract settings); - - /// - /// Delete existing pair - /// - [Delete("/api/AssetPairs/{assetPairId}")] - Task Delete(string assetPairId); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/IAssetPairsReadingApi.cs b/src/MarginTrading.Backend.Contracts/IAssetPairsReadingApi.cs deleted file mode 100644 index 558b0be60..000000000 --- a/src/MarginTrading.Backend.Contracts/IAssetPairsReadingApi.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using JetBrains.Annotations; -using MarginTrading.Backend.Contracts.AssetPairSettings; -using MarginTrading.Backend.Contracts.Client; -using Refit; - -namespace MarginTrading.Backend.Contracts -{ - [PublicAPI] - public interface IAssetPairsReadingApi - { - /// - /// Get all pairs. - /// Cached on client for 3 minutes - /// - [Get("/api/AssetPairs/"), ClientCaching(Minutes = 3)] - Task> List([Query, CanBeNull] string legalEntity = null, - [Query] MatchingEngineModeContract? matchingEngineMode = null); - - /// - /// Get pair by id - /// - [Get("/api/AssetPairs/{assetPairId}")] - Task Get(string assetPairId); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/IDictionariesReadingApi.cs b/src/MarginTrading.Backend.Contracts/IDictionariesReadingApi.cs deleted file mode 100644 index 12c7f0f2c..000000000 --- a/src/MarginTrading.Backend.Contracts/IDictionariesReadingApi.cs +++ /dev/null @@ -1,30 +0,0 @@ -using JetBrains.Annotations; -using MarginTrading.Backend.Contracts.AssetPairSettings; -using Refit; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace MarginTrading.Backend.Contracts -{ - [PublicAPI] - public interface IDictionariesReadingApi - { - /// - /// Returns all asset pairs - /// - [Get("/api/dictionaries/assetPairs/")] - Task> AssetPairs(); - - /// - /// Returns all matching engines - /// - [Get("/api/dictionaries/matchingEngines/")] - Task> MatchingEngines(); - - /// - /// Returns all order types - /// - [Get("/api/dictionaries/orderTypes/")] - Task> OrderTypes(); - } -} diff --git a/src/MarginTrading.Backend.Contracts/IOrdersApi.cs b/src/MarginTrading.Backend.Contracts/IOrdersApi.cs new file mode 100644 index 000000000..165a03731 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/IOrdersApi.cs @@ -0,0 +1,82 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using MarginTrading.Backend.Contracts.Common; +using MarginTrading.Backend.Contracts.Orders; +using Refit; + +namespace MarginTrading.Backend.Contracts +{ + /// + /// API for performing operations with orders and reading current state + /// + [PublicAPI] + public interface IOrdersApi + { + /// + /// Place new order + /// + /// Order model + /// Order Id + [Post("/api/orders")] + Task PlaceAsync([Body] [NotNull] OrderPlaceRequest request); + + /// + /// Change existing order + /// + [Put("/api/orders/{orderId}")] + Task ChangeAsync([NotNull] string orderId, [Body][NotNull] OrderChangeRequest request); + + /// + /// Close existing order + /// + [Delete("/api/orders/{orderId}")] + Task CancelAsync([NotNull] string orderId, [Body] OrderCancelRequest request = null); + + /// + /// Close group of orders by accountId, assetPairId and direction. + /// + /// Mandatory + /// Optional + /// Optional + /// Optional, should orders, linked to positions, to be canceled + /// Optional + /// Dictionary of failed to close orderIds with exception message + /// + /// + [Delete("/api/orders/cancel-group")] + Task> CancelGroupAsync([Query] [NotNull] string accountId, + [Query] [CanBeNull] string assetPairId = null, + [Query] [CanBeNull] OrderDirectionContract? direction = null, + [Query] bool includeLinkedToPositions = false, + [Body] [CanBeNull] OrderCancelRequest request = null); + + /// + /// Get order by id + /// + [Get("/api/orders/{orderId}"), ItemCanBeNull] + Task GetAsync([NotNull] string orderId); + + /// + /// Get open orders with optional filtering + /// + [Get("/api/orders")] + Task> ListAsync([Query, CanBeNull] string accountId = null, + [Query, CanBeNull] string assetPairId = null, [Query, CanBeNull] string parentPositionId = null, + [Query, CanBeNull] string parentOrderId = null); + + /// + /// Get open orders with optional filtering and pagination. Sorted descending by default. + /// + [Get("/api/orders/by-pages")] + Task> ListAsyncByPages([Query] [CanBeNull] string accountId = null, + [Query] [CanBeNull] string assetPairId = null, [Query] [CanBeNull] string parentPositionId = null, + [Query] [CanBeNull] string parentOrderId = null, + [Query] [CanBeNull] int? skip = null, [Query] [CanBeNull] int? take = null, + [Query] string order = "DESC"); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/IPositionsApi.cs b/src/MarginTrading.Backend.Contracts/IPositionsApi.cs new file mode 100644 index 000000000..70b062c11 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/IPositionsApi.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using MarginTrading.Backend.Contracts.Common; +using MarginTrading.Backend.Contracts.Orders; +using MarginTrading.Backend.Contracts.Positions; +using Refit; +using PositionDirectionContract = MarginTrading.Backend.Contracts.Positions.PositionDirectionContract; + +namespace MarginTrading.Backend.Contracts +{ + /// + /// API for performing operations with positions and getting current state + /// + [PublicAPI] + public interface IPositionsApi + { + /// + /// Close a position + /// + [Delete("/api/positions/{positionId}")] + Task CloseAsync([NotNull] string positionId, + [Body] PositionCloseRequest request = null); + + /// + /// Close group of opened positions by accountId, assetPairId and direction. + /// AccountId must be passed. Method signature allow nulls for backward compatibility. + /// + [Delete("/api/positions/close-group")] + Task CloseGroupAsync([Query, CanBeNull] string assetPairId = null, + [Query] string accountId = null, + [Query, CanBeNull] PositionDirectionContract? direction = null, + [Body, CanBeNull] PositionCloseRequest request = null); + + /// + /// Get a position by id + /// + [Get("/api/positions/{positionId}"), ItemCanBeNull] + Task GetAsync([NotNull] string positionId); + + /// + /// Get positions with optional filtering + /// + [Get("/api/positions")] + Task> ListAsync([Query, CanBeNull] string accountId = null, + [Query, CanBeNull] string assetPairId = null); + + /// + /// Get positions with optional filtering and pagination + /// + [Get("/api/positions/by-pages")] + Task> ListAsyncByPages( + [Query] [CanBeNull] string accountId = null, + [Query] [CanBeNull] string assetPairId = null, + [Query] [CanBeNull] int? skip = null, [Query] [CanBeNull] int? take = null); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/IPricesApi.cs b/src/MarginTrading.Backend.Contracts/IPricesApi.cs new file mode 100644 index 000000000..e937cb2cf --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/IPricesApi.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using MarginTrading.Backend.Contracts.Prices; +using MarginTrading.Backend.Contracts.Snow.Prices; +using Refit; + +namespace MarginTrading.Backend.Contracts +{ + /// + /// Provides data about prices + /// + [PublicAPI] + public interface IPricesApi + { + /// + /// Get current best prices + /// + /// + /// Post because the query string will be too long otherwise + /// + [Post("/api/prices/best")] + Task> GetBestAsync([Body][NotNull] InitPricesBackendRequest request); + + /// + /// Get current best fx prices + /// + /// + /// Post because the query string will be too long otherwise + /// + [Post("/api/prices/bestFx")] + Task> GetBestFxAsync([Body][NotNull] InitPricesBackendRequest request); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/IReportApi.cs b/src/MarginTrading.Backend.Contracts/IReportApi.cs new file mode 100644 index 000000000..82782623b --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/IReportApi.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using JetBrains.Annotations; +using Refit; + +namespace MarginTrading.Backend.Contracts +{ + /// + /// Api to prepare data for reports + /// + [PublicAPI] + public interface IReportApi + { + /// + /// Populates the data needed for report building to the storage: open positions and account fpl + /// + /// Returns 200 on success, exception otherwise + [Post("/api/reports/dump-data")] + Task DumpReportData(); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/IRoutesReadingApi.cs b/src/MarginTrading.Backend.Contracts/IRoutesReadingApi.cs deleted file mode 100644 index f7e0c9efd..000000000 --- a/src/MarginTrading.Backend.Contracts/IRoutesReadingApi.cs +++ /dev/null @@ -1,24 +0,0 @@ -using JetBrains.Annotations; -using MarginTrading.Backend.Contracts.Routes; -using Refit; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace MarginTrading.Backend.Contracts -{ - [PublicAPI] - public interface IRoutesReadingApi - { - /// - /// Returns all routes - /// - [Get("/api/routes/")] - Task> List(); - - /// - /// Returns route by Id - /// - [Get("/api/routes/{id}")] - Task GetById(string id); - } -} diff --git a/src/MarginTrading.Backend.Contracts/IScheduleSettingsApi.cs b/src/MarginTrading.Backend.Contracts/IScheduleSettingsApi.cs deleted file mode 100644 index ce6e88040..000000000 --- a/src/MarginTrading.Backend.Contracts/IScheduleSettingsApi.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using JetBrains.Annotations; -using MarginTrading.Backend.Contracts.DayOffSettings; -using Refit; - -namespace MarginTrading.Backend.Contracts -{ - /// - /// Manages day offs schedule and exclusions - /// - [PublicAPI] - public interface IScheduleSettingsApi - { - /// - /// Gets schedule settings - /// - [Get("/api/ScheduleSettings")] - Task GetSchedule(); - - /// - /// Sets schedule settings - /// - [Put("/api/ScheduleSettings")] - Task SetSchedule([Body] ScheduleSettingsContract scheduleSettingsContract); - - /// - /// Get all exclusions - /// - [Get("/api/ScheduleSettings/Exclusions")] - Task> ListExclusions(); - - /// - /// Get exclusion by id - /// - [Get("/api/ScheduleSettings/Exclusions/{id}")] - Task GetExclusion(Guid id); - - /// - /// Get all compiled exclusions - /// - [Get("/api/ScheduleSettings/Exclusions/Compiled")] - Task ListCompiledExclusions(); - - /// - /// Create exclusion - /// - [Post("/api/ScheduleSettings/Exclusions")] - Task CreateExclusion([Body] DayOffExclusionInputContract contract); - - /// - /// Update exclusion - /// - [Put("/api/ScheduleSettings/Exclusions/{id}")] - Task UpdateExclusion(Guid id, [Body] DayOffExclusionInputContract contract); - - /// - /// Delete exclusion - /// - [Delete("/api/ScheduleSettings/Exclusions/{id}")] - Task DeleteExclusion(Guid id); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/IServiceApi.cs b/src/MarginTrading.Backend.Contracts/IServiceApi.cs new file mode 100644 index 000000000..e8825d6d9 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/IServiceApi.cs @@ -0,0 +1,43 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Refit; + +namespace MarginTrading.Backend.Contracts +{ + /// + /// Api to manage service functions + /// + [PublicAPI] + public interface IServiceApi + { + /// + /// Save snapshot of orders, positions, account stats, best fx prices, best trading prices for current moment. + /// Throws an error in case if trading is not stopped. + /// + /// Snapshot statistics. + [Post("/api/service/make-trading-data-snapshot")] + Task MakeTradingDataSnapshot([Query] DateTime tradingDay, + [Query, CanBeNull] string correlationId = null); + + /// + /// Get current state of overnight margin parameter. + /// + [Get("/api/service/current-overnight-margin-parameter")] + Task GetOvernightMarginParameterCurrentState(); + + /// + /// Get current margin parameter values for instruments (all / filtered by IDs). + /// + /// + /// Dictionary with key = asset pair ID and value = (Dictionary with key = trading condition ID and value = multiplier) + /// + [Get("/api/service/overnight-margin-parameter")] + Task>> GetOvernightMarginParameterValues( + [Query, CanBeNull] string[] instruments = null); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/ISettingsReadingApi.cs b/src/MarginTrading.Backend.Contracts/ISettingsReadingApi.cs deleted file mode 100644 index 8376f0630..000000000 --- a/src/MarginTrading.Backend.Contracts/ISettingsReadingApi.cs +++ /dev/null @@ -1,17 +0,0 @@ -using JetBrains.Annotations; -using Refit; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace MarginTrading.Backend.Contracts -{ - [PublicAPI] - public interface ISettingsReadingApi - { - /// - /// Returns if margin trading is enabled for client - /// - [Get("/api/settings/enabled/{clientId}")] - Task IsMarginTradingEnabled(string clientId); - } -} diff --git a/src/MarginTrading.Backend.Contracts/ITradeMonitoringReadingApi.cs b/src/MarginTrading.Backend.Contracts/ITradeMonitoringReadingApi.cs index 8b3799864..f31497394 100644 --- a/src/MarginTrading.Backend.Contracts/ITradeMonitoringReadingApi.cs +++ b/src/MarginTrading.Backend.Contracts/ITradeMonitoringReadingApi.cs @@ -1,4 +1,7 @@ -using JetBrains.Annotations; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; using Refit; using System; using System.Collections.Generic; @@ -13,22 +16,23 @@ public interface ITradeMonitoringReadingApi /// /// Returns summary info by assets /// - [Get("/api/trade/assets/summary/")] + // todo remove + [Get("/api/trade/assets/summary/"), Obsolete] Task> AssetSummaryList(); /// /// Returns list of opened positions /// - [Get("/api/trade/openPositions/")] - Task> OpenPositions(); + [Get("/api/trade/openPositions/"), Obsolete] + Task> OpenPositions(); /// /// Returns list of opened positions by Volume /// /// Target volume /// - [Get("/api/trade/openPositions/byVolume/{volume}")] - Task> OpenPositionsByVolume(decimal volume); + [Get("/api/trade/openPositions/byVolume/{volume}"), Obsolete] + Task> OpenPositionsByVolume(decimal volume); /// /// Returns list of opened positions by date interval @@ -36,30 +40,30 @@ public interface ITradeMonitoringReadingApi /// interval start /// interval finish /// - [Get("/api/trade/openPositions/byDate/")] - Task> OpenPositionsByDate([Query] DateTime from, [Query] DateTime to); + [Get("/api/trade/openPositions/byDate/"), Obsolete] + Task> OpenPositionsByDate([Query] DateTime from, [Query] DateTime to); /// - /// Returns list of opened positions by client + /// Returns list of opened positions by accounts /// - /// Client Id + /// Account Ids /// - [Get("/api/trade/openPositions/byClient/{clientId}")] - Task> OpenPositionsByClient(string clientId); + [Get("/api/trade/openPositions/byAccounts"), Obsolete] + Task> OpenPositionsByClient(string[] accountIds); /// /// Returns list of pending orders /// - [Get("/api/trade/pendingOrders/")] - Task> PendingOrders(); + [Get("/api/trade/pendingOrders/"), Obsolete] + Task> PendingOrders(); /// /// Returns list of pending orders by volume /// /// Target volume /// - [Get("/api/trade/pendingOrders/byVolume/{volume}")] - Task> PendingOrdersByVolume(decimal volume); + [Get("/api/trade/pendingOrders/byVolume/{volume}"), Obsolete] + Task> PendingOrdersByVolume(decimal volume); /// /// Returns list of pending orders by date interval @@ -67,23 +71,21 @@ public interface ITradeMonitoringReadingApi /// interval start /// interval finish /// - [Get("/api/trade/pendingOrders/byDate/")] - Task> PendingOrdersByDate([Query] DateTime from, [Query] DateTime to); + [Get("/api/trade/pendingOrders/byDate/"), Obsolete] + Task> PendingOrdersByDate([Query] DateTime from, [Query] DateTime to); /// - /// Returns list of pending orders by client + /// Returns list of pending orders by accounts /// - /// Client Id + /// Account Ids /// - [Get("/api/trade/pendingOrders/byClient/{clientId}")] - Task> PendingOrdersByClient(string clientId); + [Get("/api/trade/pendingOrders/byAccounts"), Obsolete] + Task> PendingOrdersByClient(string[] accountIds); /// /// Returns list of orderbooks /// - /// Client Id - /// - [Get("/api/trade/orderbooks/byInstrument/{instrument}")] + [Get("/api/trade/orderbooks/byInstrument/{instrument}"), Obsolete] Task> OrderBooksByInstrument(string instrument); } } diff --git a/src/MarginTrading.Backend.Contracts/ITradingApi.cs b/src/MarginTrading.Backend.Contracts/ITradingApi.cs deleted file mode 100644 index 4ee88fec0..000000000 --- a/src/MarginTrading.Backend.Contracts/ITradingApi.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Threading.Tasks; -using JetBrains.Annotations; -using MarginTrading.Backend.Contracts.Common; -using MarginTrading.Backend.Contracts.Trading; -using Refit; - -namespace MarginTrading.Backend.Contracts -{ - /// - /// API for performing trading operations - /// - [PublicAPI] - public interface ITradingApi - { - /// - /// Close position - /// - [Post("/api/mt/order.close")] - Task> CloseOrder(CloseOrderBackendRequest request); - - /// - /// Cancel pending order - /// - [Post("/api/mt/order.cancel")] - Task> CancelOrder(CloseOrderBackendRequest request); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/ITradingConditionsEditingApi.cs b/src/MarginTrading.Backend.Contracts/ITradingConditionsEditingApi.cs deleted file mode 100644 index f40bf35d4..000000000 --- a/src/MarginTrading.Backend.Contracts/ITradingConditionsEditingApi.cs +++ /dev/null @@ -1,46 +0,0 @@ -using JetBrains.Annotations; -using MarginTrading.Backend.Contracts.AccountAssetPair; -using MarginTrading.Backend.Contracts.Common; -using MarginTrading.Backend.Contracts.TradingConditions; -using Refit; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace MarginTrading.Backend.Contracts -{ - [PublicAPI] - public interface ITradingConditionsEditingApi - { - /// - /// Insert or update trading condition - /// - /// - /// - [Post("/api/tradingConditions")] - Task> InsertOrUpdate([Body] TradingConditionContract tradingCondition); - - /// - /// Insert or update account group - /// - /// - /// - [Post("/api/tradingConditions/accountGroups")] - Task> InsertOrUpdateAccountGroup([Body] AccountGroupContract accountGroup); - - /// - /// Assing instruments - /// - /// - /// - [Post("/api/tradingConditions/accountAssets/assignInstruments")] - Task>> AssignInstruments([Body] AssignInstrumentsContract assignInstruments); - - /// - /// Insert or update account asset pair - /// - /// - /// - [Post("/api/tradingConditions/accountAssets")] - Task> InsertOrUpdateAccountAsset([Body] AccountAssetPairContract accountAsset); - } -} diff --git a/src/MarginTrading.Backend.Contracts/ITradingConditionsReadingApi.cs b/src/MarginTrading.Backend.Contracts/ITradingConditionsReadingApi.cs deleted file mode 100644 index 70201f366..000000000 --- a/src/MarginTrading.Backend.Contracts/ITradingConditionsReadingApi.cs +++ /dev/null @@ -1,26 +0,0 @@ -using JetBrains.Annotations; -using MarginTrading.Backend.Contracts.TradingConditions; -using Refit; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace MarginTrading.Backend.Contracts -{ - [PublicAPI] - public interface ITradingConditionsReadingApi - { - /// - /// Get all trading conditions - /// - /// - [Get("/api/tradingConditions/")] - Task> List(); - - /// - /// Get trading condition by Id - /// - /// - [Get("/api/tradingConditions/{id}")] - Task Get(string id); - } -} diff --git a/src/MarginTrading.Backend.Contracts/ITradingScheduleApi.cs b/src/MarginTrading.Backend.Contracts/ITradingScheduleApi.cs new file mode 100644 index 000000000..107cf30b1 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/ITradingScheduleApi.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using MarginTrading.Backend.Contracts.TradingSchedule; +using Refit; + +namespace MarginTrading.Backend.Contracts +{ + /// + /// Api to retrieve compiled trading schedule - for cache initialization only. + /// + [PublicAPI] + public interface ITradingScheduleApi + { + /// + /// Get current compiled trading schedule for each asset pair in a form of the list of time intervals. + /// Cache is invalidated and recalculated after 00:00:00.000 each day on request. + /// + [Get("/api/trading-schedule/compiled")] + Task>> CompiledTradingSchedule(); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/MarginTrading.Backend.Contracts.csproj b/src/MarginTrading.Backend.Contracts/MarginTrading.Backend.Contracts.csproj index f7bb7c008..ba2f56bfc 100644 --- a/src/MarginTrading.Backend.Contracts/MarginTrading.Backend.Contracts.csproj +++ b/src/MarginTrading.Backend.Contracts/MarginTrading.Backend.Contracts.csproj @@ -1,9 +1,9 @@  netstandard2.0 - 1.0.1 - Lykke.MarginTrading.Backend.Contracts - 7.1 + 1.16.29 + Lykke.MarginTrading.BackendSnow.Contracts + 7.3 true @@ -13,14 +13,14 @@ 1701;1702;1705;1591 - - - + + + - - - - - + + + + + \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Orders/FxToAssetPairDirectionContract.cs b/src/MarginTrading.Backend.Contracts/Orders/FxToAssetPairDirectionContract.cs new file mode 100644 index 000000000..9f80f0ccd --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Orders/FxToAssetPairDirectionContract.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.Orders +{ + /// + /// Shows if account asset id is directly related on asset pair quote asset. + /// + public enum FxToAssetPairDirectionContract + { + /// + /// AssetPair is {BaseId, QuoteId} and FxAssetPair is {QuoteId, AccountAssetId} + /// + Straight, + + /// + /// AssetPair is {BaseId, QuoteId} and FxAssetPair is {AccountAssetId, QuoteId} + /// + Reverse, + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/TradeMonitoring/MatchedOrderBackendContract.cs b/src/MarginTrading.Backend.Contracts/Orders/MatchedOrderContract.cs similarity index 54% rename from src/MarginTrading.Backend.Contracts/TradeMonitoring/MatchedOrderBackendContract.cs rename to src/MarginTrading.Backend.Contracts/Orders/MatchedOrderContract.cs index 00900b92a..be170b48d 100644 --- a/src/MarginTrading.Backend.Contracts/TradeMonitoring/MatchedOrderBackendContract.cs +++ b/src/MarginTrading.Backend.Contracts/Orders/MatchedOrderContract.cs @@ -1,8 +1,11 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. -namespace MarginTrading.Backend.Contracts.TradeMonitoring +using System; + +namespace MarginTrading.Backend.Contracts.Orders { - public class MatchedOrderBackendContract + public class MatchedOrderContract { public string OrderId { get; set; } public string MarketMakerId { get; set; } @@ -10,5 +13,6 @@ public class MatchedOrderBackendContract public decimal Volume { get; set; } public decimal Price { get; set; } public DateTime MatchedDate { get; set; } + public bool IsExternal { get; set; } } -} +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Orders/OrderCancelRequest.cs b/src/MarginTrading.Backend.Contracts/Orders/OrderCancelRequest.cs new file mode 100644 index 000000000..0798b9c5d --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Orders/OrderCancelRequest.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.Orders +{ + public class OrderCancelRequest + { + public OriginatorTypeContract Originator { get; set; } + + public string Comment { get; set; } + + public string AdditionalInfo { get; set; } + + /// + /// The correlation identifier. Optional: if not passed will be auto-generated. + /// + public string CorrelationId { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Orders/OrderChangeRequest.cs b/src/MarginTrading.Backend.Contracts/Orders/OrderChangeRequest.cs new file mode 100644 index 000000000..fbcecdc4a --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Orders/OrderChangeRequest.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Contracts.Orders +{ + public class OrderChangeRequest + { + public decimal Price { get; set; } + + public DateTime? Validity { get; set; } + + public OriginatorTypeContract Originator { get; set; } + + public string AdditionalInfo { get; set; } + + /// + /// The correlation identifier. Optional: if not passed will be auto-generated. + /// + public string CorrelationId { get; set; } + + public bool? ForceOpen { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Orders/OrderContract.cs b/src/MarginTrading.Backend.Contracts/Orders/OrderContract.cs new file mode 100644 index 000000000..8dc2b0068 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Orders/OrderContract.cs @@ -0,0 +1,251 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using MarginTrading.Backend.Contracts.TradeMonitoring; + +namespace MarginTrading.Backend.Contracts.Orders +{ + /// + /// Info about an order + /// + public class OrderContract + { + /// + /// Order id + /// + public string Id { get; set; } + + /// + /// Account id + /// + public string AccountId { get; set; } + + /// + /// Instrument id (e.g."BTCUSD", where BTC - base asset unit, USD - quoting unit) + /// + public string AssetPairId { get; set; } + + /// + /// Parent order id. Filled if it's a related order. + /// + [CanBeNull] + public string ParentOrderId { get; set; } + + /// + /// Parent position id. Filled if it's a related order. + /// + [CanBeNull] + public string PositionId { get; set; } + + /// + /// The order direction (Buy or Sell) + /// + public OrderDirectionContract Direction { get; set; } + + /// + /// The order type (Market, Limit, Stop, TakeProfit, StopLoss or TrailingStop) + /// + public OrderTypeContract Type { get; set; } + + /// + /// The order status (Active, Inactive, Executed, Canceled, Rejected or Expired) + /// + public OrderStatusContract Status { get; set; } + + /// + /// Order fill type + /// + public OrderFillTypeContract FillType { get; set; } + + /// + /// Who changed the order (Investor, System or OnBehalf) + /// + public OriginatorTypeContract Originator { get; set; } + + /// + /// Order volume in base asset units. Not filled for related orders (TakeProfit, StopLoss or TrailingStop). + /// + public decimal? Volume { get; set; } + + /// + /// Expected open price (in quoting asset units per one base unit). Not filled for Market orders. + /// + public decimal? ExpectedOpenPrice { get; set; } + + /// + /// Execution open price (in quoting asset units per one base unit). Filled for executed orders only. + /// + public decimal? ExecutionPrice { get; set; } + + /// + /// Current FxRate + /// + public decimal FxRate { get; set; } + + /// + /// FX asset pair id + /// + public string FxAssetPairId { get; set; } + + /// + /// Shows if account asset id is directly related on asset pair quote asset. + /// I.e. AssetPair is {BaseId, QuoteId} and FxAssetPair is {QuoteId, AccountAssetId} => Straight + /// If AssetPair is {BaseId, QuoteId} and FxAssetPair is {AccountAssetId, QuoteId} => Reverse + /// + public FxToAssetPairDirectionContract FxToAssetPairDirection { get; set; } + + /// + /// The related orders + /// + [Obsolete] + public List RelatedOrders { get; set; } + + /// + /// Force open separate position for the order, ignoring existing ones + /// + public bool ForceOpen { get; set; } + + /// + /// Till validity time + /// + public DateTime? ValidityTime { get; set; } + + /// + /// Creation date and time + /// + public DateTime CreatedTimestamp { get; set; } + + /// + /// Last modification date and time + /// + public DateTime ModifiedTimestamp { get; set; } + +////-------------- + + /// + /// Digit order code + /// + public long Code { get; set; } + + /// + /// Date when order was activated + /// + public DateTime? ActivatedTimestamp { get; set; } + + /// + /// Date when order started execution + /// + public DateTime? ExecutionStartedTimestamp { get; set; } + + /// + /// Date when order was executed + /// + public DateTime? ExecutedTimestamp { get; set; } + + /// + /// Date when order was canceled + /// + public DateTime? CanceledTimestamp { get; set; } + + /// + /// Date when order was rejected + /// + public DateTime? Rejected { get; set; } + + /// + /// Trading conditions ID + /// + public string TradingConditionId { get; set; } + + /// + /// Account base asset ID + /// + public string AccountAssetId { get; set; } + + /// + /// Asset for representation of equivalent price + /// + public string EquivalentAsset { get; set; } + + /// + /// Rate for calculation of equivalent price + /// + public decimal EquivalentRate { get; set; } + + /// + /// Reject reason + /// + public OrderRejectReasonContract RejectReason { get; set; } + + /// + /// Human-readable reject reason + /// + public string RejectReasonText { get; set; } + + /// + /// Additional comment + /// + public string Comment { get; set; } + + /// + /// ID of exernal order (for STP mode) + /// + public string ExternalOrderId { get; set; } + + /// + /// ID of exernal LP (for STP mode) + /// + public string ExternalProviderId { get; set; } + + /// + /// Matching engine ID + /// + public string MatchingEngineId { get; set; } + + /// + /// Legal Entity ID + /// + public string LegalEntity { get; set; } + + /// + /// Matched orders for execution + /// + public List MatchedOrders { get; set; } + + /// + /// Related orders + /// + public List RelatedOrderInfos { get; set; } + + /// + /// Additional info from last user request + /// + public string AdditionalInfo { get; set; } + + /// + /// The correlation identifier. + /// In every operation that results in the creation of a new message the correlationId should be copied from + /// the inbound message to the outbound message. This facilitates tracking of an operation through the system. + /// If there is no inbound identifier then one should be created eg. on the service layer boundary (API). + /// + public string CorrelationId { get; set; } + + /// + /// Number of pending order retries passed + /// + public int PendingOrderRetriesCount { get; set; } + + /// + /// Max distance between order price and parent order price (only for trailing order) + /// + public decimal? TrailingDistance { get; set; } + + /// + /// Show if order was managed on behalf at least once + /// + public bool HasOnBehalf { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Orders/OrderDirectionContract.cs b/src/MarginTrading.Backend.Contracts/Orders/OrderDirectionContract.cs new file mode 100644 index 000000000..125f970cd --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Orders/OrderDirectionContract.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace MarginTrading.Backend.Contracts.Orders +{ + /// + /// The direction of an order + /// + public enum OrderDirectionContract + { + /// + /// Order to buy the quoting asset of a pair + /// + Buy = 1, + + /// + /// Order to sell the quoting asset of a pair + /// + Sell = 2 + }} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Orders/OrderFillTypeContract.cs b/src/MarginTrading.Backend.Contracts/Orders/OrderFillTypeContract.cs new file mode 100644 index 000000000..79f016d73 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Orders/OrderFillTypeContract.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.Orders +{ + public enum OrderFillTypeContract + { + FillOrKill, + PartialFill + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Orders/OrderPlaceRequest.cs b/src/MarginTrading.Backend.Contracts/Orders/OrderPlaceRequest.cs new file mode 100644 index 000000000..b5f82af73 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Orders/OrderPlaceRequest.cs @@ -0,0 +1,55 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.Orders +{ + [MessagePackObject] + public class OrderPlaceRequest + { + [Key(0)] + public string AccountId { get; set; } + [Key(1)] + public string InstrumentId { get; set; } + [Key(2)] + public string ParentOrderId { get; set; } // null if basic, ParentOrderId if related + [Key(3)] + public string PositionId { get; set; } + + [Key(4)] + public OrderDirectionContract Direction { get; set; } + [Key(5)] + public OrderTypeContract Type { get; set; } + [Key(6)] + public OriginatorTypeContract Originator { get; set; } + + [Key(7)] + public decimal Volume { get; set; } + [Key(8)] + public decimal? Price { get; set; } // null for market + + [Key(9)] + public decimal? StopLoss { get; set; } + [Key(10)] + public decimal? TakeProfit { get; set; } + [Key(11)] + public bool UseTrailingStop { get; set; } + + [Key(12)] + public bool ForceOpen { get; set; } + + [Key(13)] + public DateTime? Validity { get; set; } // null for market + + [Key(14)] + public string AdditionalInfo { get; set; } + + /// + /// The correlation identifier. Optional: if not passed will be auto-generated. + /// + [Key(15)] + public string CorrelationId { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Orders/OrderStatusContract.cs b/src/MarginTrading.Backend.Contracts/Orders/OrderStatusContract.cs new file mode 100644 index 000000000..6e57f2625 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Orders/OrderStatusContract.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.Orders +{ + public enum OrderStatusContract + { + Placed = 0, + Inactive = 1, + Active = 2, + ExecutionStarted = 3, + Executed = 4, + Canceled = 5, + Rejected = 6, + Expired = 7 + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Orders/OrderTypeContract.cs b/src/MarginTrading.Backend.Contracts/Orders/OrderTypeContract.cs new file mode 100644 index 000000000..750e7fff1 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Orders/OrderTypeContract.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace MarginTrading.Backend.Contracts.Orders +{ + /// + /// The type of order + /// + public enum OrderTypeContract + { + /// + /// Market order, a basic one + /// + Market = 1, + + /// + /// Limit order, a basic one + /// + Limit = 2, + + /// + /// Stop order, a basic one (closing only) + /// + Stop = 3, + + /// + /// Take profit order, related to another parent one + /// + TakeProfit = 4, + + /// + /// Stop loss order, related to another parent one + /// + StopLoss = 5, + + /// + /// Trailing stop order, related to another parent one + /// + TrailingStop = 6, + + //Closingout = 7, + //Manual = 8 + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Orders/OriginatorTypeContract.cs b/src/MarginTrading.Backend.Contracts/Orders/OriginatorTypeContract.cs new file mode 100644 index 000000000..9bbceff2e --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Orders/OriginatorTypeContract.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.Orders +{ + public enum OriginatorTypeContract + { + Investor = 1, + System = 2, + OnBehalf = 3 + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Orders/PositionCloseResultContract.cs b/src/MarginTrading.Backend.Contracts/Orders/PositionCloseResultContract.cs new file mode 100644 index 000000000..f5b29ac50 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Orders/PositionCloseResultContract.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.Orders +{ + public enum PositionCloseResultContract + { + Closed, + ClosingIsInProgress, + ClosingStarted, + FailedToClose + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Orders/PositionsCloseResponce.cs b/src/MarginTrading.Backend.Contracts/Orders/PositionsCloseResponce.cs new file mode 100644 index 000000000..77ba25e32 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Orders/PositionsCloseResponce.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.Orders +{ + public class PositionCloseResponse + { + public string PositionId { get; set; } + + public PositionCloseResultContract Result { get; set; } + + public string OrderId { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Orders/PositionsGroupCloseResponse.cs b/src/MarginTrading.Backend.Contracts/Orders/PositionsGroupCloseResponse.cs new file mode 100644 index 000000000..d751ce843 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Orders/PositionsGroupCloseResponse.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.Orders +{ + public class PositionsGroupCloseResponse + { + public PositionCloseResponse[] Responses { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Orders/RelatedOrderInfoContract.cs b/src/MarginTrading.Backend.Contracts/Orders/RelatedOrderInfoContract.cs new file mode 100644 index 000000000..7efc2b7af --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Orders/RelatedOrderInfoContract.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Contracts.Orders +{ + public class RelatedOrderInfoContract + { + public OrderTypeContract Type { get; set; } + public string Id { get; set; } + + public decimal Price { get; set; } + + public OrderStatusContract Status { get; set; } + + public DateTime ModifiedTimestamp { get; set; } + + /// + /// Max distance between order price and parent order price (only for trailing order) + /// + public decimal? TrailingDistance { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Positions/DealContract.cs b/src/MarginTrading.Backend.Contracts/Positions/DealContract.cs new file mode 100644 index 000000000..f7ad15044 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Positions/DealContract.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MarginTrading.Backend.Contracts.Orders; + +namespace MarginTrading.Backend.Contracts.Positions +{ + public class DealContract + { + public string DealId { get; set; } + public string PositionId { get; set; } + public DateTime Created { get; set; } + public string OpenTradeId { get; set; } + public OrderTypeContract OpenOrderType { get; set; } + public decimal OpenOrderVolume { get; set; } + public decimal? OpenOrderExpectedPrice { get; set; } + public string CloseTradeId { get; set; } + public OrderTypeContract CloseOrderType { get; set; } + public decimal CloseOrderVolume { get; set; } + public decimal? CloseOrderExpectedPrice { get; set; } + public decimal Volume { get; set; } + public decimal OpenPrice { get; set; } + public decimal OpenFxPrice { get; set; } + public decimal ClosePrice { get; set; } + public decimal CloseFxPrice { get; set; } + public decimal Fpl { get; set; } + public decimal PnlOfTheLastDay { get; set; } + public string AdditionalInfo { get; set; } + public OriginatorTypeContract Originator { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Positions/OpenPositionContract.cs b/src/MarginTrading.Backend.Contracts/Positions/OpenPositionContract.cs new file mode 100644 index 000000000..fff8d3440 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Positions/OpenPositionContract.cs @@ -0,0 +1,142 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using MarginTrading.Backend.Contracts.Orders; + +namespace MarginTrading.Backend.Contracts.Positions +{ + /// + /// Info about an open position + /// + public class OpenPositionContract + { + /// + /// Position id + /// + public string Id { get; set; } + + /// + /// Account id + /// + public string AccountId { get; set; } + + /// + /// Instrument id (e.g."BTCUSD", where BTC - base asset unit, USD - quoting unit) + /// + public string AssetPairId { get; set; } + + /// + /// Id of the opening trade + /// + public string OpenTradeId { get; set; } + + /// + /// Type of the opening order + /// + public OrderTypeContract OpenOrderType { get; set; } + + /// + /// Volume of the opening order + /// + public decimal OpenOrderVolume { get; set; } + + /// + /// When the position was opened + /// + public DateTime OpenTimestamp { get; set; } + + /// + /// When the position was modified (partially closed) + /// + public DateTime? ModifiedTimestamp { get; set; } + + /// + /// The direction of the position (Long or Short) + /// + public PositionDirectionContract Direction { get; set; } + + /// + /// Open price (in quoting asset units per one base unit) + /// + public decimal OpenPrice { get; set; } + + /// + /// Opening FX Rate + /// + public decimal OpenFxPrice { get; set; } + + /// + /// Expected open price (in quoting asset units per one base unit) + /// + public decimal? ExpectedOpenPrice { get; set; } + + /// + /// Current price for closing of position (in quoting asset units per one base unit) + /// + public decimal ClosePrice { get; set; } + + /// + /// Current position volume in base asset units + /// + public decimal CurrentVolume { get; set; } + + /// + /// Profit and loss of the position in account asset units (without commissions) + /// + public decimal PnL { get; set; } + + /// + /// PnL changed on account balance + /// + public decimal ChargedPnl { get; set; } + + /// + /// Current margin value in account asset units + /// + public decimal Margin { get; set; } + + /// + /// Current FxRate + /// + public decimal FxRate { get; set; } + + /// + /// FX asset pair id + /// + public string FxAssetPairId { get; set; } + + /// + /// Shows if account asset id is directly related on asset pair quote asset. + /// I.e. AssetPair is {BaseId, QuoteId} and FxAssetPair is {QuoteId, AccountAssetId} => Straight + /// If AssetPair is {BaseId, QuoteId} and FxAssetPair is {AccountAssetId, QuoteId} => Reverse + /// + public FxToAssetPairDirectionContract FxToAssetPairDirection { get; set; } + + /// + /// The trade which opened the position + /// + public string TradeId { get; set; } + + /// + /// The related order Ids (sl, tp orders) + /// + public List RelatedOrders { get; set; } + + /// + /// The related orders (sl, tp orders) + /// + public List RelatedOrderInfos { get; set; } + + /// + /// Additional information about the order, that opened position + /// + public string AdditionalInfo { get; set; } + + /// + /// Position status + /// + public PositionStatusContract Status { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Positions/PositionCloseRequest.cs b/src/MarginTrading.Backend.Contracts/Positions/PositionCloseRequest.cs new file mode 100644 index 000000000..9be1b1a9d --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Positions/PositionCloseRequest.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Contracts.Orders; + +namespace MarginTrading.Backend.Contracts.Positions +{ + public class PositionCloseRequest + { + public OriginatorTypeContract Originator { get; set; } + + public string Comment { get; set; } + + public string AdditionalInfo { get; set; } + + /// + /// The correlation identifier. Optional: if not passed will be auto-generated. + /// + public string CorrelationId { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Positions/PositionContract.cs b/src/MarginTrading.Backend.Contracts/Positions/PositionContract.cs new file mode 100644 index 000000000..37717231e --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Positions/PositionContract.cs @@ -0,0 +1,58 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using MarginTrading.Backend.Contracts.Orders; +using MarginTrading.Backend.Contracts.TradeMonitoring; + +namespace MarginTrading.Backend.Contracts.Positions +{ + [UsedImplicitly] + public class PositionContract + { + public string Id { get; set; } + public long Code { get; set; } + public string AssetPairId { get; set; } + public PositionDirectionContract Direction { get; set; } + public decimal Volume { get; set; } + public string AccountId { get; set; } + public string TradingConditionId { get; set; } + public string AccountAssetId { get; set; } + public decimal? ExpectedOpenPrice { get; set; } + public string OpenMatchingEngineId { get; set; } + public DateTime OpenDate { get; set; } + public string OpenTradeId { get; set; } + public OrderTypeContract OpenOrderType { get; set; } + public decimal OpenOrderVolume { get; set; } + public decimal OpenPrice { get; set; } + public decimal OpenFxPrice { get; set; } + public string EquivalentAsset { get; set; } + public decimal OpenPriceEquivalent { get; set; } + public List RelatedOrders { get; set; } + public string LegalEntity { get; set; } + public OriginatorTypeContract OpenOriginator { get; set; } + public string ExternalProviderId { get; set; } + public decimal SwapCommissionRate { get; set; } + public decimal OpenCommissionRate { get; set; } + public decimal CloseCommissionRate { get; set; } + public decimal CommissionLot { get; set; } + public string CloseMatchingEngineId { get; set; } + public decimal ClosePrice { get; set; } + public decimal CloseFxPrice { get; set; } + public decimal ClosePriceEquivalent { get; set; } + public DateTime? StartClosingDate { get; set; } + public DateTime? CloseDate { get; set; } + public OriginatorTypeContract? CloseOriginator { get; set; } + public PositionCloseReasonContract CloseReason { get; set; } + public string CloseComment { get; set; } + public List CloseTrades { get; set; } + public string FxAssetPairId { get; set; } + public FxToAssetPairDirectionContract FxToAssetPairDirection { get; set; } + public DateTime? LastModified { get; set; } + public decimal TotalPnL { get; set; } + public decimal ChargedPnl { get; set; } + public string AdditionalInfo { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Positions/PositionDirectionContract.cs b/src/MarginTrading.Backend.Contracts/Positions/PositionDirectionContract.cs new file mode 100644 index 000000000..b88a1bab4 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Positions/PositionDirectionContract.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; + +namespace MarginTrading.Backend.Contracts.Positions +{ + /// + /// The direction of a position + /// + [PublicAPI] + public enum PositionDirectionContract + { + /// + /// Position is profitable if the price goes up + /// + Long = 1, + + /// + /// Position is profitable if the price goes down + /// + Short = 2 + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Positions/PositionStatusContract.cs b/src/MarginTrading.Backend.Contracts/Positions/PositionStatusContract.cs new file mode 100644 index 000000000..7bc524663 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Positions/PositionStatusContract.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.Positions +{ + public enum PositionStatusContract + { + Active, + Closing, + Closed + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Prices/BestPriceContract.cs b/src/MarginTrading.Backend.Contracts/Prices/BestPriceContract.cs new file mode 100644 index 000000000..c3c6d2ea6 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Prices/BestPriceContract.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using JetBrains.Annotations; + +namespace MarginTrading.Backend.Contracts.Snow.Prices +{ + /// + /// Info about best price + /// + [PublicAPI] + public class BestPriceContract + { + /// + /// Instrument + /// + public string Id { get; set; } + + /// + /// Timestamp + /// + public DateTime Timestamp { get; set; } + + /// + /// Bid value + /// + public decimal Bid { get; set; } + + /// + /// Ask value + /// + public decimal Ask { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Prices/InitPricesBackendRequest.cs b/src/MarginTrading.Backend.Contracts/Prices/InitPricesBackendRequest.cs new file mode 100644 index 000000000..6d0b5fada --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Prices/InitPricesBackendRequest.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.Prices +{ + public class InitPricesBackendRequest + { + public string[] AssetIds { get; set; } + } +} diff --git a/src/MarginTrading.Backend.Contracts/Properties/AssembyAttributes.cs b/src/MarginTrading.Backend.Contracts/Properties/AssembyAttributes.cs deleted file mode 100644 index fe51f884b..000000000 --- a/src/MarginTrading.Backend.Contracts/Properties/AssembyAttributes.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("MarginTradingTests")] \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Routes/MatchingEngineRouteContract.cs b/src/MarginTrading.Backend.Contracts/Routes/MatchingEngineRouteContract.cs deleted file mode 100644 index 795463a0e..000000000 --- a/src/MarginTrading.Backend.Contracts/Routes/MatchingEngineRouteContract.cs +++ /dev/null @@ -1,18 +0,0 @@ -using MarginTrading.Backend.Contracts.TradeMonitoring; - -namespace MarginTrading.Backend.Contracts.Routes -{ - public class MatchingEngineRouteContract - { - public string Id { get; set; } - public int Rank { get; set; } - public string TradingConditionId { get; set; } - public string ClientId { get; set; } - public string Instrument { get; set; } - public OrderDirectionContract? Type { get; set; } - public string MatchingEngineId { get; set; } - public string Asset { get; set; } - public string RiskSystemLimitType { get; set; } - public string RiskSystemMetricType { get; set; } - } -} diff --git a/src/MarginTrading.Backend.Contracts/TradeMonitoring/BaseOrderContract.cs b/src/MarginTrading.Backend.Contracts/TradeMonitoring/BaseOrderContract.cs index 5833d2cd1..021fbbbe4 100644 --- a/src/MarginTrading.Backend.Contracts/TradeMonitoring/BaseOrderContract.cs +++ b/src/MarginTrading.Backend.Contracts/TradeMonitoring/BaseOrderContract.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; namespace MarginTrading.Backend.Contracts.TradeMonitoring diff --git a/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderContract.cs b/src/MarginTrading.Backend.Contracts/TradeMonitoring/DetailedOrderContract.cs similarity index 80% rename from src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderContract.cs rename to src/MarginTrading.Backend.Contracts/TradeMonitoring/DetailedOrderContract.cs index e17eec6cb..73b18addd 100644 --- a/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderContract.cs +++ b/src/MarginTrading.Backend.Contracts/TradeMonitoring/DetailedOrderContract.cs @@ -1,23 +1,25 @@ -using MarginTrading.Backend.Contracts.AssetPairSettings; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Contracts.AssetPairSettings; using System; using System.Collections.Generic; +using MarginTrading.Backend.Contracts.Orders; using Newtonsoft.Json; using Newtonsoft.Json.Converters; namespace MarginTrading.Backend.Contracts.TradeMonitoring { - - public class OrderContract + public class DetailedOrderContract { public string Id { get; set; } public long Code { get; set; } - public string ClientId { get; set; } public string AccountId { get; set; } public string AccountAssetId { get; set; } public string Instrument { get; set; } public OrderDirectionContract Type { get; set; } public OrderStatusContract Status { get; set; } - public OrderCloseReasonContract CloseReason { get; set; } + public PositionCloseReasonContract CloseReason { get; set; } public OrderRejectReasonContract RejectReason { get; set; } public string RejectReasonText { get; set; } public decimal? ExpectedOpenPrice { get; set; } @@ -47,8 +49,8 @@ public class OrderContract public string OpenExternalProviderId { get; set; } public string CloseExternalOrderId { get; set; } public string CloseExternalProviderId { get; set; } - public List MatchedOrders { get; set; } = new List(); - public List MatchedCloseOrders { get; set; } = new List(); + public List MatchedOrders { get; set; } = new List(); + public List MatchedCloseOrders { get; set; } = new List(); public string LegalEntity { get; set; } [JsonConverter(typeof(StringEnumConverter))] diff --git a/src/MarginTrading.Backend.Contracts/TradeMonitoring/LimitOrderContract.cs b/src/MarginTrading.Backend.Contracts/TradeMonitoring/LimitOrderContract.cs index 0c982447f..f75ad708e 100644 --- a/src/MarginTrading.Backend.Contracts/TradeMonitoring/LimitOrderContract.cs +++ b/src/MarginTrading.Backend.Contracts/TradeMonitoring/LimitOrderContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Contracts.TradeMonitoring +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.TradeMonitoring { public class LimitOrderContract : BaseOrderContract { diff --git a/src/MarginTrading.Backend.Contracts/TradeMonitoring/MatchedOrderContract.cs b/src/MarginTrading.Backend.Contracts/TradeMonitoring/MatchedOrderContract.cs index 8c70f35b0..acbc61b52 100644 --- a/src/MarginTrading.Backend.Contracts/TradeMonitoring/MatchedOrderContract.cs +++ b/src/MarginTrading.Backend.Contracts/TradeMonitoring/MatchedOrderContract.cs @@ -1,6 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Backend.Contracts.TradeMonitoring { diff --git a/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderBookContract.cs b/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderBookContract.cs index 5cf7d674a..7773b214b 100644 --- a/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderBookContract.cs +++ b/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderBookContract.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; namespace MarginTrading.Backend.Contracts.TradeMonitoring { diff --git a/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderCloseReasonContract.cs b/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderCloseReasonContract.cs deleted file mode 100644 index fa1be0513..000000000 --- a/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderCloseReasonContract.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace MarginTrading.Backend.Contracts.TradeMonitoring -{ - public enum OrderCloseReasonContract - { - None, - Close, - StopLoss, - TakeProfit, - StopOut, - Canceled, - CanceledBySystem, - CanceledByBroker, - ClosedByBroker - } -} diff --git a/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderDirectionContract.cs b/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderDirectionContract.cs deleted file mode 100644 index 5e6b9d8a1..000000000 --- a/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderDirectionContract.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MarginTrading.Backend.Contracts.TradeMonitoring -{ - public enum OrderDirectionContract - { - Buy, - Sell - } -} diff --git a/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderRejectReasonContract.cs b/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderRejectReasonContract.cs index 8f6ce9f4b..85cb5e813 100644 --- a/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderRejectReasonContract.cs +++ b/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderRejectReasonContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Contracts.TradeMonitoring +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.TradeMonitoring { public enum OrderRejectReasonContract { @@ -13,7 +16,15 @@ public enum OrderRejectReasonContract InvalidStoploss, InvalidInstrument, InvalidAccount, + InvalidParent, TradingConditionError, - TechnicalError + InvalidValidity, + TechnicalError, + ParentPositionDoesNotExist, + ParentPositionIsNotActive, + ShortPositionsDisabled, + MaxPositionLimit, + MinOrderSizeLimit, + MaxOrderSizeLimit, } } diff --git a/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderStatusContract.cs b/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderStatusContract.cs index 54f022ce0..a5e695873 100644 --- a/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderStatusContract.cs +++ b/src/MarginTrading.Backend.Contracts/TradeMonitoring/OrderStatusContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Contracts.TradeMonitoring +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.TradeMonitoring { public enum OrderStatusContract { diff --git a/src/MarginTrading.Backend.Contracts/TradeMonitoring/PositionCloseReasonContract.cs b/src/MarginTrading.Backend.Contracts/TradeMonitoring/PositionCloseReasonContract.cs new file mode 100644 index 000000000..35181f4da --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/TradeMonitoring/PositionCloseReasonContract.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.TradeMonitoring +{ + public enum PositionCloseReasonContract + { + None, + Close, + StopLoss, + TakeProfit, + StopOut + } +} diff --git a/src/MarginTrading.Backend.Contracts/TradeMonitoring/SummaryAssetContract.cs b/src/MarginTrading.Backend.Contracts/TradeMonitoring/SummaryAssetContract.cs index e187e7a65..144f1f7d6 100644 --- a/src/MarginTrading.Backend.Contracts/TradeMonitoring/SummaryAssetContract.cs +++ b/src/MarginTrading.Backend.Contracts/TradeMonitoring/SummaryAssetContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Contracts.TradeMonitoring +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.TradeMonitoring { public class SummaryAssetContract { diff --git a/src/MarginTrading.Backend.Contracts/Trades/TradeContract.cs b/src/MarginTrading.Backend.Contracts/Trades/TradeContract.cs new file mode 100644 index 000000000..8512b2161 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Trades/TradeContract.cs @@ -0,0 +1,42 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using JetBrains.Annotations; + +namespace MarginTrading.Backend.Contracts.Trades +{ + /// + /// Info about a trade + /// + [PublicAPI] + public class TradeContract + { + /// + /// Trade id + /// + public string Id { get; set; } + + /// + /// Account id + /// + public string AccountId { get; set; } + + /// + /// Order id + /// + public string OrderId { get; set; } + + /// + /// Position id + /// + public string PositionId { get; set; } + + /// + /// Trade timestamp + /// + public DateTime Timestamp { get; set; } + + //todo add other fields: volume and price + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Trading/CloseOrderBackendRequest.cs b/src/MarginTrading.Backend.Contracts/Trading/CloseOrderBackendRequest.cs deleted file mode 100644 index 697e0791e..000000000 --- a/src/MarginTrading.Backend.Contracts/Trading/CloseOrderBackendRequest.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace MarginTrading.Backend.Contracts.Trading -{ - /// - /// Request for position closing / order cancelling - /// - public class CloseOrderBackendRequest - { - public string ClientId { get; set; } - - public string OrderId { get; set; } - - public string AccountId { get; set; } - - /// - /// True, if requested by broker - /// False, if requested by user - /// - public bool IsForcedByBroker { get; set; } - - /// - /// Additional info, e.g. broker name or reason of closing - /// - /// - /// Mandatory, if IsForcedByBroker = true - /// - public string Comment { get; set; } - } -} diff --git a/src/MarginTrading.Backend.Contracts/TradingConditions/AccountGroupContract.cs b/src/MarginTrading.Backend.Contracts/TradingConditions/AccountGroupContract.cs deleted file mode 100644 index 24dd24f89..000000000 --- a/src/MarginTrading.Backend.Contracts/TradingConditions/AccountGroupContract.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace MarginTrading.Backend.Contracts.TradingConditions -{ - public class AccountGroupContract - { - public string TradingConditionId { get; set; } - public string BaseAssetId { get; set; } - public decimal MarginCall { get; set; } - public decimal StopOut { get; set; } - public decimal DepositTransferLimit { get; set; } - public decimal ProfitWithdrawalLimit { get; set; } - } -} diff --git a/src/MarginTrading.Backend.Contracts/TradingConditions/AssignInstrumentsContract.cs b/src/MarginTrading.Backend.Contracts/TradingConditions/AssignInstrumentsContract.cs deleted file mode 100644 index b068e428b..000000000 --- a/src/MarginTrading.Backend.Contracts/TradingConditions/AssignInstrumentsContract.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MarginTrading.Backend.Contracts.TradingConditions -{ - public class AssignInstrumentsContract - { - public string TradingConditionId { get; set; } - public string BaseAssetId { get; set; } - public string[] Instruments { get; set; } - } -} diff --git a/src/MarginTrading.Backend.Contracts/TradingConditions/TradingConditionContract.cs b/src/MarginTrading.Backend.Contracts/TradingConditions/TradingConditionContract.cs deleted file mode 100644 index 2b1f95de2..000000000 --- a/src/MarginTrading.Backend.Contracts/TradingConditions/TradingConditionContract.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MarginTrading.Backend.Contracts.TradingConditions -{ - public class TradingConditionContract - { - public string Id { get; set; } - public string Name { get; set; } - public bool IsDefault { get; set; } - public string LegalEntity { get; set; } - } -} diff --git a/src/MarginTrading.Backend.Contracts/TradingSchedule/CompiledScheduleChangedEvent.cs b/src/MarginTrading.Backend.Contracts/TradingSchedule/CompiledScheduleChangedEvent.cs new file mode 100644 index 000000000..3a8223b2c --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/TradingSchedule/CompiledScheduleChangedEvent.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.TradingSchedule +{ + [MessagePackObject] + public class CompiledScheduleChangedEvent + { + [Key(0)] + public string AssetPairId { get; set; } + + [Key(1)] + public DateTime EventTimestamp { get; set; } + + [Key(2)] + public List TimeIntervals { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/TradingSchedule/CompiledScheduleTimeIntervalContract.cs b/src/MarginTrading.Backend.Contracts/TradingSchedule/CompiledScheduleTimeIntervalContract.cs new file mode 100644 index 000000000..8618fefec --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/TradingSchedule/CompiledScheduleTimeIntervalContract.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.TradingSchedule +{ + [MessagePackObject] + public class CompiledScheduleTimeIntervalContract + { + [Key(0)] + public ScheduleSettingsContract Schedule { get; set; } + + [Key(1)] + public DateTime Start { get; set; } + + [Key(2)] + public DateTime End { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/TradingSchedule/MarketStateChangedEvent.cs b/src/MarginTrading.Backend.Contracts/TradingSchedule/MarketStateChangedEvent.cs new file mode 100644 index 000000000..b60eca2e7 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/TradingSchedule/MarketStateChangedEvent.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.TradingSchedule +{ + [MessagePackObject] + public class MarketStateChangedEvent + { + [Key(0)] + public string Id { get; set; } + + [Key(1)] + public bool IsEnabled { get; set; } + + [Key(2)] + public DateTime EventTimestamp { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/TradingSchedule/ScheduleSettingsContract.cs b/src/MarginTrading.Backend.Contracts/TradingSchedule/ScheduleSettingsContract.cs new file mode 100644 index 000000000..1756c1191 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/TradingSchedule/ScheduleSettingsContract.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.TradingSchedule +{ + [MessagePackObject] + public class ScheduleSettingsContract + { + [Key(0)] + public string Id { get; set; } + + [Key(1)] + public int Rank { get; set; } + + [Key(2)] + public bool? IsTradeEnabled { get; set; } = false; + + [Key(3)] + public TimeSpan? PendingOrdersCutOff { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Workflow/Liquidation/Events/LiquidationFailedEvent.cs b/src/MarginTrading.Backend.Contracts/Workflow/Liquidation/Events/LiquidationFailedEvent.cs new file mode 100644 index 000000000..ec77589fa --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Workflow/Liquidation/Events/LiquidationFailedEvent.cs @@ -0,0 +1,50 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using MarginTrading.Backend.Contracts.Positions; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.Workflow.Liquidation.Events +{ + [MessagePackObject] + public class LiquidationFailedEvent + { + [Key(0)] + public string OperationId { get; set; } + + [Key(1)] + public DateTime CreationTime { get; set; } + + [Key(2)] + public string Reason { get; set; } + + [Key(3)] + public LiquidationTypeContract LiquidationType { get; set; } + + [Key(4)] + public string AccountId { get; set; } + + [Key(5)] + public string AssetPairId { get; set; } + + [Key(6)] + public PositionDirectionContract? Direction { get; set; } + + [Key(7)] + public string QuoteInfo { get; set; } + + [Key(8)] + public List ProcessedPositionIds { get; set; } + + [Key(9)] + public List LiquidatedPositionIds { get; set; } + + [Key(10)] + public int OpenPositionsRemainingOnAccount { get; set; } + + [Key(11)] + public decimal CurrentTotalCapital { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Workflow/Liquidation/Events/LiquidationFinishedEvent.cs b/src/MarginTrading.Backend.Contracts/Workflow/Liquidation/Events/LiquidationFinishedEvent.cs new file mode 100644 index 000000000..c6c562e71 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Workflow/Liquidation/Events/LiquidationFinishedEvent.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using MarginTrading.Backend.Contracts.Positions; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.Workflow.Liquidation.Events +{ + [MessagePackObject] + public class LiquidationFinishedEvent + { + [Key(0)] + public string OperationId { get; set; } + + [Key(1)] + public DateTime CreationTime { get; set; } + + [Key(2)] + public LiquidationTypeContract LiquidationType { get; set; } + + [Key(3)] + public string AccountId { get; set; } + + [Key(4)] + public string AssetPairId { get; set; } + + [Key(5)] + public PositionDirectionContract? Direction { get; set; } + + [Key(6)] + public string QuoteInfo { get; set; } + + [Key(7)] + public List ProcessedPositionIds { get; set; } + + [Key(8)] + public List LiquidatedPositionIds { get; set; } + + [Key(9)] + public int OpenPositionsRemainingOnAccount { get; set; } + + [Key(10)] + public decimal CurrentTotalCapital { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Workflow/Liquidation/LiquidationTypeContract.cs b/src/MarginTrading.Backend.Contracts/Workflow/Liquidation/LiquidationTypeContract.cs new file mode 100644 index 000000000..23005f09f --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Workflow/Liquidation/LiquidationTypeContract.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.Workflow.Liquidation +{ + public enum LiquidationTypeContract + { + /// + /// Stop out caused liquidation + /// + Normal = 0, + /// + /// MCO caused liquidation + /// + Mco = 1, + /// + /// Liquidation is started by "Close All" API call + /// + Forced = 2, + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Commands/ExecuteSpecialLiquidationOrderCommand.cs b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Commands/ExecuteSpecialLiquidationOrderCommand.cs new file mode 100644 index 000000000..9e0452ef9 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Commands/ExecuteSpecialLiquidationOrderCommand.cs @@ -0,0 +1,45 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Commands +{ + /// + /// Trade request for particular instrument, volume and price. + /// + [MessagePackObject] + public class ExecuteSpecialLiquidationOrderCommand + { + /// + /// Operation Id + /// + [Key(0)] + public string OperationId { get; set; } + + /// + /// Command creation time + /// + [Key(1)] + public DateTime CreationTime { get; set; } + + /// + /// Instrument + /// + [Key(3)] + public string Instrument { get; set; } + + /// + /// Position volume + /// + [Key(4)] + public decimal Volume { get; set; } + + /// + /// Requested execution price + /// + [Key(5)] + public decimal Price { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Commands/GetPriceForSpecialLiquidationCommand.cs b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Commands/GetPriceForSpecialLiquidationCommand.cs new file mode 100644 index 000000000..c432674e3 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Commands/GetPriceForSpecialLiquidationCommand.cs @@ -0,0 +1,53 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using JetBrains.Annotations; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Commands +{ + /// + /// Quote request for particular instrument and volume. + /// + [MessagePackObject] + public class GetPriceForSpecialLiquidationCommand + { + /// + /// Operation Id + /// + [Key(0)] + public string OperationId { get; set; } + + /// + /// Command creation time + /// + [Key(1)] + public DateTime CreationTime { get; set; } + + /// + /// Instrument + /// + [Key(3)] + public string Instrument { get; set; } + + /// + /// Position volume + /// + [Key(4)] + public decimal Volume { get; set; } + + /// + /// Streaming number of request. Increases in case when price arrived, but volume has changed. + /// + [Key(5)] + public int RequestNumber { get; set; } + + /// + /// Optional. Account Id for the case then we liquidating only positions of a single account. + /// + [CanBeNull] + [Key(6)] + public string AccountId { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Commands/StartSpecialLiquidationCommand.cs b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Commands/StartSpecialLiquidationCommand.cs new file mode 100644 index 000000000..dc953ac57 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Commands/StartSpecialLiquidationCommand.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Commands +{ + /// + /// Command from Corporate Actions to close all positions by instrument + /// + [MessagePackObject] + public class StartSpecialLiquidationCommand + { + /// + /// Operation Id + /// + [Key(0)] + public string OperationId { get; set; } + + /// + /// Command creation time + /// + [Key(1)] + public DateTime CreationTime { get; set; } + + /// + /// Instrument + /// + [Key(3)] + public string Instrument { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Commands/TradeOperationInfo.cs b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Commands/TradeOperationInfo.cs new file mode 100644 index 000000000..dc20b1387 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Commands/TradeOperationInfo.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Commands +{ + public class TradeOperationInfo + { + public string OperationId { get; set; } + + public int RequestNumber { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/PriceForSpecialLiquidationCalculatedEvent.cs b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/PriceForSpecialLiquidationCalculatedEvent.cs new file mode 100644 index 000000000..dcd5e6faa --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/PriceForSpecialLiquidationCalculatedEvent.cs @@ -0,0 +1,54 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Events +{ + /// + /// Quote request for particular instrument and volume succeeded. + /// + [MessagePackObject] + public class PriceForSpecialLiquidationCalculatedEvent + { + /// + /// Operation Id + /// + [Key(0)] + public string OperationId { get; set; } + + /// + /// Event creation time + /// + [Key(1)] + public DateTime CreationTime { get; set; } + + /// + /// Instrument - should not be used! + /// + [Key(3)] + [Obsolete] + public string Instrument { get; set; } + + /// + /// Position volume - should not be used! + /// + [Key(4)] + [Obsolete] + public decimal Volume { get; set; } + + /// + /// Requested price value + /// + [Key(5)] + public decimal Price { get; set; } + + /// + /// Streaming number of request. Increases in case when price arrived, but volume has changed. + /// + [Key(6)] + [Obsolete] + public int RequestNumber { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/PriceForSpecialLiquidationCalculationFailedEvent.cs b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/PriceForSpecialLiquidationCalculationFailedEvent.cs new file mode 100644 index 000000000..b8a938a99 --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/PriceForSpecialLiquidationCalculationFailedEvent.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Events +{ + /// + /// Quote request for particular instrument and volume failed. + /// + [MessagePackObject] + public class PriceForSpecialLiquidationCalculationFailedEvent + { + /// + /// Operation Id + /// + [Key(0)] + public string OperationId { get; set; } + + /// + /// Event creation time + /// + [Key(1)] + public DateTime CreationTime { get; set; } + + /// + /// Reason of failure + /// + [Key(2)] + public string Reason { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/SpecialLiquidationFailedEvent.cs b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/SpecialLiquidationFailedEvent.cs new file mode 100644 index 000000000..42550995f --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/SpecialLiquidationFailedEvent.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Events +{ + /// + /// The special liquidation flow failed. + /// + [MessagePackObject] + public class SpecialLiquidationFailedEvent + { + /// + /// Operation Id + /// + [Key(0)] + public string OperationId { get; set; } + + /// + /// Event creation time + /// + [Key(1)] + public DateTime CreationTime { get; set; } + + /// + /// Reason of failure + /// + [Key(2)] + public string Reason { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/SpecialLiquidationFinishedEvent.cs b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/SpecialLiquidationFinishedEvent.cs new file mode 100644 index 000000000..6dd22605d --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/SpecialLiquidationFinishedEvent.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Events +{ + /// + /// Special liquidation flow successfully finished. + /// + [MessagePackObject] + public class SpecialLiquidationFinishedEvent + { + /// + /// Operation Id + /// + [Key(0)] + public string OperationId { get; set; } + + /// + /// Event creation time + /// + [Key(1)] + public DateTime CreationTime { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/SpecialLiquidationOrderExecutedEvent.cs b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/SpecialLiquidationOrderExecutedEvent.cs new file mode 100644 index 000000000..7bec9a2df --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/SpecialLiquidationOrderExecutedEvent.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Events +{ + /// + /// Trade request for particular instrument, volume and price successfully executed. + /// + [MessagePackObject] + public class SpecialLiquidationOrderExecutedEvent + { + /// + /// Operation Id + /// + [Key(0)] + public string OperationId { get; set; } + + /// + /// Event creation time + /// + [Key(1)] + public DateTime CreationTime { get; set; } + + /// + /// Market Maker Id on which the order was executed + /// + [Key(2)] + public string MarketMakerId { get; set; } + + [Key(3)] + public string OrderId { get; set; } + + [Key(4)] + public DateTime ExecutionTime { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/SpecialLiquidationOrderExecutionFailedEvent.cs b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/SpecialLiquidationOrderExecutionFailedEvent.cs new file mode 100644 index 000000000..bb45cdb9d --- /dev/null +++ b/src/MarginTrading.Backend.Contracts/Workflow/SpecialLiquidation/Events/SpecialLiquidationOrderExecutionFailedEvent.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MessagePack; + +namespace MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Events +{ + /// + /// Trade request for particular instrument, volume and price failed. + /// + [MessagePackObject] + public class SpecialLiquidationOrderExecutionFailedEvent + { + /// + /// Operation Id + /// + [Key(0)] + public string OperationId { get; set; } + + /// + /// Event creation time + /// + [Key(1)] + public DateTime CreationTime { get; set; } + + /// + /// Reason of failure + /// + [Key(2)] + public string Reason { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core.Mappers/BackendContractToDomainMapper.cs b/src/MarginTrading.Backend.Core.Mappers/BackendContractToDomainMapper.cs deleted file mode 100644 index 0faa8f0aa..000000000 --- a/src/MarginTrading.Backend.Core.Mappers/BackendContractToDomainMapper.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System.Linq; -using MarginTrading.Backend.Core.MatchedOrders; -using MarginTrading.Backend.Core.MatchingEngines; -using MarginTrading.Backend.Core.TradingConditions; -using MarginTrading.Common.Extensions; -using MarginTrading.Contract.BackendContracts; -using MarginTrading.Contract.BackendContracts.TradingConditions; - -namespace MarginTrading.Backend.Core.Mappers -{ - public static class BackendContractToDomainMapper - { - public static LimitOrder ToDomain(this LimitOrderBackendContract src) - { - return new LimitOrder - { - Id = src.Id, - MarketMakerId = src.MarketMakerId, - Instrument = src.Instrument, - Volume = src.Volume, - Price = src.Price, - CreateDate = src.CreateDate, - MatchedOrders = new MatchedOrderCollection(src.MatchedOrders.Select(ToDomain)) - }; - } - - public static MatchedOrder ToDomain(this MatchedOrderBackendContract src) - { - return new MatchedOrder - { - OrderId = src.OrderId, - MarketMakerId = src.MarketMakerId, - LimitOrderLeftToMatch = src.LimitOrderLeftToMatch, - Volume = src.Volume, - Price = src.Price, - MatchedDate = src.MatchedDate - }; - } - - public static IOrderHistory ToOrderHistoryDomain(this OrderFullContract src) - { - var orderContract = new OrderHistory - { - Id = src.Id, - Code = src.Code, - ClientId = src.ClientId, - AccountId = src.AccountId, - TradingConditionId = src.TradingConditionId, - AccountAssetId = src.AccountAssetId, - Instrument = src.Instrument, - Type = src.Type.ToType(), - CreateDate = src.CreateDate, - OpenDate = src.OpenDate, - CloseDate = src.CloseDate, - ExpectedOpenPrice = src.ExpectedOpenPrice, - OpenPrice = src.OpenPrice, - ClosePrice = src.ClosePrice, - QuoteRate = src.QuoteRate, - AssetAccuracy = src.AssetAccuracy, - Volume = src.Volume, - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss, - CommissionLot = src.CommissionLot, - OpenCommission = src.OpenCommission, - CloseCommission = src.CloseCommission, - SwapCommission = src.SwapCommission, - StartClosingDate = src.StartClosingDate, - Status = src.Status.ToType(), - CloseReason = src.CloseReason.ToType(), - FillType = src.FillType.ToType(), - RejectReason = src.RejectReason.ToType(), - RejectReasonText = src.RejectReasonText, - Comment = src.Comment, - MatchedVolume = src.MatchedVolume, - MatchedCloseVolume = src.MatchedCloseVolume, - Fpl = src.Fpl, - PnL = src.PnL, - InterestRateSwap = src.InterestRateSwap, - MarginInit = src.MarginInit, - MarginMaintenance = src.MarginMaintenance, - EquivalentAsset = src.EquivalentAsset, - OpenPriceEquivalent = src.OpenPriceEquivalent, - ClosePriceEquivalent = src.ClosePriceEquivalent, - OrderUpdateType = src.OrderUpdateType.ToType(), - OpenExternalOrderId = src.OpenExternalOrderId, - OpenExternalProviderId = src.OpenExternalProviderId, - CloseExternalOrderId = src.CloseExternalOrderId, - CloseExternalProviderId = src.CloseExternalProviderId, - MatchingEngineMode = src.MatchingEngineMode.ToType(), - LegalEntity = src.LegalEntity, - }; - - foreach (var order in src.MatchedOrders) - { - orderContract.MatchedOrders.Add(order.ToDomain()); - } - - foreach (var order in src.MatchedCloseOrders) - { - orderContract.MatchedCloseOrders.Add(order.ToDomain()); - } - - return orderContract; - } - - public static MarginTradingAccountHistory ToAccountHistoryContract(this AccountHistoryBackendContract src) - { - return new MarginTradingAccountHistory - { - Id = src.Id, - ClientId = src.ClientId, - AccountId = src.AccountId, - Amount = src.Amount, - Type = src.Type.ToType(), - Date = src.Date, - Balance = src.Balance, - WithdrawTransferLimit = src.WithdrawTransferLimit, - Comment = src.Comment, - OrderId = src.OrderId, - LegalEntity = src.LegalEntity, - AuditLog = src.AuditLog - }; - } - - public static ITradingCondition ToDomainContract(this TradingConditionModel src) - { - return new TradingCondition - { - Id = src.Id, - Name = src.Name, - IsDefault = src.IsDefault, - LegalEntity = src.LegalEntity - }; - } - - public static IAccountGroup ToDomainContract(this AccountGroupModel src) - { - return new AccountGroup - { - TradingConditionId = src.TradingConditionId, - BaseAssetId = src.BaseAssetId, - MarginCall = src.MarginCall, - StopOut = src.StopOut, - DepositTransferLimit = src.DepositTransferLimit, - ProfitWithdrawalLimit = src.ProfitWithdrawalLimit - }; - } - - public static IAccountAssetPair ToDomainContract(this AccountAssetPairModel src) - { - return new AccountAssetPair - { - TradingConditionId = src.TradingConditionId, - BaseAssetId = src.BaseAssetId, - Instrument = src.Instrument, - LeverageInit = src.LeverageInit, - LeverageMaintenance = src.LeverageMaintenance, - SwapLong = src.SwapLong, - SwapShort = src.SwapShort, - OvernightSwapLong = src.OvernightSwapLong, - OvernightSwapShort = src.OvernightSwapShort, - CommissionLong = src.CommissionLong, - CommissionShort = src.CommissionShort, - CommissionLot = src.CommissionLot, - DeltaBid = src.DeltaBid, - DeltaAsk = src.DeltaAsk, - DealLimit = src.DealLimit, - PositionLimit = src.PositionLimit - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core.Mappers/ContractModelsFactory.cs b/src/MarginTrading.Backend.Core.Mappers/ContractModelsFactory.cs deleted file mode 100644 index 31817c728..000000000 --- a/src/MarginTrading.Backend.Core.Mappers/ContractModelsFactory.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using MarginTrading.Backend.Core.Orderbooks; -using MarginTrading.Backend.Core.TradingConditions; -using MarginTrading.Contract.BackendContracts; - -namespace MarginTrading.Backend.Core.Mappers -{ - public class BackendContractFactory - { - - public static AccountHistoryBackendResponse CreateAccountHistoryBackendResponse(IEnumerable accounts, IEnumerable openPositions, IEnumerable historyOrders) - { - return new AccountHistoryBackendResponse - { - Account = accounts.Select(item => item.ToBackendContract()).ToArray(), - OpenPositions = openPositions.Select(item => item.ToBackendHistoryContract()) - .OrderByDescending(item => item.OpenDate).ToArray(), - PositionsHistory = historyOrders.Where(item => item.OpenDate.HasValue && item.CloseDate.HasValue) - .Select(item => item.ToBackendHistoryContract()).OrderByDescending(item => item.OpenDate).ToArray() - }; - } - - public static AccountNewHistoryBackendResponse CreateAccountNewHistoryBackendResponse(IEnumerable accounts, IEnumerable openOrders, IEnumerable historyOrders) - { - var items = new List(); - var history = historyOrders.Where(item => item.OpenDate.HasValue && item.CloseDate.HasValue).ToList(); - - items.AddRange(accounts.Select(item => new AccountHistoryItemBackend { Account = item.ToBackendContract(), Date = item.Date }).ToList()); - - items.AddRange(openOrders.Select(item => new AccountHistoryItemBackend { Position = item.ToBackendHistoryContract(), Date = item.OpenDate.Value }).ToList()); - - items.AddRange(history.Select(item => - new AccountHistoryItemBackend - { - Position = item.ToBackendHistoryOpenedContract(), - Date = item.OpenDate.Value - }).ToList()); - - items.AddRange(history.Select(item => - new AccountHistoryItemBackend - { - Position = item.ToBackendHistoryContract(), - Date = item.CloseDate.Value - }) - .ToList()); - - items = items.OrderByDescending(item => item.Date).ToList(); - - return new AccountNewHistoryBackendResponse - { - HistoryItems = items.ToArray() - }; - } - - public static ClientOrdersBackendResponse CreateClientOrdersBackendResponse(IEnumerable positions, IEnumerable orders) - { - return new ClientOrdersBackendResponse - { - Positions = positions.Select(item => item.ToBackendContract()).ToArray(), - Orders = orders.Select(item => item.ToBackendContract()).ToArray() - }; - } - - public static InitAccountInstrumentsBackendResponse CreateInitAccountInstrumentsBackendResponse(Dictionary accountAssets) - { - return new InitAccountInstrumentsBackendResponse - { - AccountAssets = accountAssets.ToDictionary(pair => pair.Key, pair => pair.Value.Select(item => item.ToBackendContract()).ToArray()) - }; - } - - public static InitChartDataBackendResponse CreateInitChartDataBackendResponse(Dictionary> chartData) - { - return new InitChartDataBackendResponse - { - ChartData = chartData.ToDictionary(pair => pair.Key, pair => pair.Value.Select(item => item.ToBackendContract()).ToArray()) - }; - } - - public static InitDataBackendResponse CreateInitDataBackendResponse(IEnumerable accounts, - Dictionary accountAssetPairs, bool isLive) - { - return new InitDataBackendResponse - { - Accounts = accounts.Select(item => item.ToFullBackendContract(isLive)).ToArray(), - AccountAssetPairs = accountAssetPairs.ToDictionary(pair => pair.Key, pair => pair.Value.Select(item => item.ToBackendContract()).ToArray()), - }; - } - - public static OpenOrderBackendResponse CreateOpenOrderBackendResponse(IOrder order) - { - return new OpenOrderBackendResponse - { - Order = order.ToBackendContract() - }; - } - - public static OrderbooksBackendResponse CreateOrderbooksBackendResponse(OrderBook orderbook) - { - return new OrderbooksBackendResponse - { - Orderbook = orderbook.ToBackendContract() - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core.Mappers/DomainModelsFactory.cs b/src/MarginTrading.Backend.Core.Mappers/DomainModelsFactory.cs index bbfd4bc07..a0f4a9feb 100644 --- a/src/MarginTrading.Backend.Core.Mappers/DomainModelsFactory.cs +++ b/src/MarginTrading.Backend.Core.Mappers/DomainModelsFactory.cs @@ -1,5 +1,9 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using MarginTrading.Backend.Core.MatchingEngines; +using MarginTrading.Backend.Core.Orders; using MarginTrading.Common.Extensions; using MarginTrading.Contract.BackendContracts; diff --git a/src/MarginTrading.Backend.Core.Mappers/DomainToBackendContractMapper.cs b/src/MarginTrading.Backend.Core.Mappers/DomainToBackendContractMapper.cs deleted file mode 100644 index bbc280be0..000000000 --- a/src/MarginTrading.Backend.Core.Mappers/DomainToBackendContractMapper.cs +++ /dev/null @@ -1,471 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using MarginTrading.Backend.Core.MatchedOrders; -using MarginTrading.Backend.Core.Orderbooks; -using MarginTrading.Backend.Core.TradingConditions; -using MarginTrading.Common.Extensions; -using MarginTrading.Contract.BackendContracts; -using MarginTrading.Contract.BackendContracts.AccountsManagement; -using MarginTrading.Contract.BackendContracts.TradingConditions; - -namespace MarginTrading.Backend.Core.Mappers -{ - public static class DomainToBackendContractMapper - { - public static MarginTradingAccountBackendContract ToFullBackendContract(this IMarginTradingAccount src, bool isLive) - { - return new MarginTradingAccountBackendContract - { - Id = src.Id, - ClientId = src.ClientId, - TradingConditionId = src.TradingConditionId, - BaseAssetId = src.BaseAssetId, - Balance = src.Balance, - WithdrawTransferLimit = src.WithdrawTransferLimit, - MarginCall = src.GetMarginCallLevel(), - StopOut = src.GetStopOutLevel(), - TotalCapital = src.GetTotalCapital(), - FreeMargin = src.GetFreeMargin(), - MarginAvailable = src.GetMarginAvailable(), - UsedMargin = src.GetUsedMargin(), - MarginInit = src.GetMarginInit(), - PnL = src.GetPnl(), - OpenPositionsCount = src.GetOpenPositionsCount(), - MarginUsageLevel = src.GetMarginUsageLevel(), - IsLive = isLive, - LegalEntity = src.LegalEntity, - }; - } - - public static AssetPairBackendContract ToBackendContract(this IAssetPair src) - { - return new AssetPairBackendContract - { - Id = src.Id, - Name = src.Name, - BaseAssetId = src.BaseAssetId, - QuoteAssetId = src.QuoteAssetId, - Accuracy = src.Accuracy - }; - } - - public static AccountAssetPairModel ToBackendContract(this IAccountAssetPair src) - { - return new AccountAssetPairModel - { - TradingConditionId = src.TradingConditionId, - BaseAssetId = src.BaseAssetId, - Instrument = src.Instrument, - LeverageInit = src.LeverageInit, - LeverageMaintenance = src.LeverageMaintenance, - SwapLong = src.SwapLong, - SwapShort = src.SwapShort, - OvernightSwapLong = src.OvernightSwapLong, - OvernightSwapShort =src.OvernightSwapShort, - CommissionLong = src.CommissionLong, - CommissionShort = src.CommissionShort, - CommissionLot = src.CommissionLot, - DeltaBid = src.DeltaBid, - DeltaAsk = src.DeltaAsk, - DealLimit = src.DealLimit, - PositionLimit = src.PositionLimit - }; - } - - public static GraphBidAskPairBackendContract ToBackendContract(this GraphBidAskPair src) - { - return new GraphBidAskPairBackendContract - { - Ask = src.Ask, - Bid = src.Bid, - Date = src.Date - }; - } - - public static AggregatedOrderBookItemBackendContract ToBackendContract(this OrderBookLevel src) - { - return new AggregatedOrderBookItemBackendContract - { - Price = src.Price, - Volume = src.Volume - }; - } - - public static AccountHistoryBackendContract ToBackendContract(this IMarginTradingAccountHistory src) - { - return new AccountHistoryBackendContract - { - Id = src.Id, - Date = src.Date, - AccountId = src.AccountId, - ClientId = src.ClientId, - Amount = src.Amount, - Balance = src.Balance, - WithdrawTransferLimit = src.WithdrawTransferLimit, - Comment = src.Comment, - Type = src.Type.ToType(), - OrderId = src.OrderId, - LegalEntity = src.LegalEntity, - AuditLog = src.AuditLog - }; - } - - public static OrderHistoryBackendContract ToBackendHistoryContract(this IOrder src) - { - return new OrderHistoryBackendContract - { - Id = src.Id, - Code = src.Code, - AccountId = src.AccountId, - Instrument = src.Instrument, - AssetAccuracy = src.AssetAccuracy, - Type = src.GetOrderType().ToType(), - Status = src.Status.ToType(), - CloseReason = src.CloseReason.ToType(), - OpenDate = src.OpenDate, - CloseDate = src.CloseDate, - OpenPrice = src.OpenPrice, - ClosePrice = src.ClosePrice, - Volume = src.Volume, - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss, - TotalPnl = src.GetTotalFpl(), - Pnl = src.GetFpl(), - InterestRateSwap = src.GetSwaps(), - CommissionLot = src.CommissionLot, - OpenCommission = src.GetOpenCommission(), - CloseCommission = src.GetCloseCommission(), - EquivalentAsset = src.EquivalentAsset, - OpenPriceEquivalent = src.OpenPriceEquivalent, - ClosePriceEquivalent = src.ClosePriceEquivalent, - OpenExternalOrderId = src.OpenExternalOrderId, - OpenExternalProviderId = src.OpenExternalProviderId, - CloseExternalOrderId = src.CloseExternalOrderId, - CloseExternalProviderId = src.CloseExternalProviderId, - MatchingEngineMode = src.MatchingEngineMode.ToType(), - LegalEntity = src.LegalEntity, - }; - } - - public static OrderHistoryBackendContract ToBackendHistoryContract(this IOrderHistory src) - { - return new OrderHistoryBackendContract - { - Id = src.Id, - AccountId = src.AccountId, - Instrument = src.Instrument, - AssetAccuracy = src.AssetAccuracy, - Type = src.Type.ToType(), - Status = src.Status.ToType(), - CloseReason = src.CloseReason.ToType(), - OpenDate = src.OpenDate, - CloseDate = src.CloseDate, - OpenPrice = src.OpenPrice, - ClosePrice = src.ClosePrice, - Volume = src.Volume, - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss, - Pnl = src.Fpl, - TotalPnl = src.PnL, - InterestRateSwap = src.InterestRateSwap, - CommissionLot = src.CommissionLot, - OpenCommission = src.OpenCommission, - CloseCommission = src.CloseCommission, - EquivalentAsset = src.EquivalentAsset, - OpenPriceEquivalent = src.OpenPriceEquivalent, - ClosePriceEquivalent = src.ClosePriceEquivalent, - OpenExternalOrderId = src.OpenExternalOrderId, - OpenExternalProviderId = src.OpenExternalProviderId, - CloseExternalOrderId = src.CloseExternalOrderId, - CloseExternalProviderId = src.CloseExternalProviderId, - MatchingEngineMode = src.MatchingEngineMode.ToType(), - LegalEntity = src.LegalEntity, - }; - } - - public static OrderHistoryBackendContract ToBackendHistoryOpenedContract(this IOrderHistory src) - { - return new OrderHistoryBackendContract - { - Id = src.Id, - AccountId = src.AccountId, - Instrument = src.Instrument, - AssetAccuracy = src.AssetAccuracy, - Type = src.Type.ToType(), - Status = OrderStatus.Active.ToType(), - CloseReason = src.CloseReason.ToType(), - OpenDate = src.OpenDate, - CloseDate = null, - OpenPrice = src.OpenPrice, - ClosePrice = src.ClosePrice, - Volume = src.Volume, - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss, - TotalPnl = src.PnL, - Pnl = src.Fpl, - InterestRateSwap = src.InterestRateSwap, - CommissionLot = src.CommissionLot, - OpenCommission = src.OpenCommission, - CloseCommission = src.CloseCommission, - EquivalentAsset = src.EquivalentAsset, - OpenPriceEquivalent = src.OpenPriceEquivalent, - ClosePriceEquivalent = src.ClosePriceEquivalent, - OpenExternalOrderId = src.OpenExternalOrderId, - OpenExternalProviderId = src.OpenExternalProviderId, - CloseExternalOrderId = src.CloseExternalOrderId, - CloseExternalProviderId = src.CloseExternalProviderId, - MatchingEngineMode = src.MatchingEngineMode.ToType(), - LegalEntity = src.LegalEntity, - }; - } - - public static OrderBackendContract ToBackendContract(this IOrder src) - { - return new OrderBackendContract - { - Id = src.Id, - Code = src.Code, - AccountId = src.AccountId, - Instrument = src.Instrument, - Type = src.GetOrderType().ToType(), - Status = src.Status.ToType(), - CloseReason = src.CloseReason.ToType(), - RejectReason = src.RejectReason.ToType(), - RejectReasonText = src.RejectReasonText, - ExpectedOpenPrice = src.ExpectedOpenPrice, - OpenDate = src.OpenDate, - CloseDate = src.CloseDate, - OpenPrice = src.OpenPrice, - ClosePrice = src.ClosePrice, - Volume = src.Volume, - MatchedVolume = src.GetMatchedVolume(), - MatchedCloseVolume = src.GetMatchedCloseVolume(), - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss, - Fpl = src.GetTotalFpl(), - CommissionLot = src.CommissionLot, - OpenCommission = src.GetOpenCommission(), - CloseCommission = src.GetCloseCommission(), - SwapCommission = src.SwapCommission, - EquivalentAsset = src.EquivalentAsset, - OpenPriceEquivalent = src.OpenPriceEquivalent, - ClosePriceEquivalent = src.ClosePriceEquivalent, - OpenExternalOrderId = src.OpenExternalOrderId, - OpenExternalProviderId = src.OpenExternalProviderId, - CloseExternalOrderId = src.CloseExternalOrderId, - CloseExternalProviderId = src.CloseExternalProviderId, - MatchingEngineMode = src.MatchingEngineMode.ToType(), - LegalEntity = src.LegalEntity, - }; - } - - public static OrderFullContract ToFullContract(this IOrder src, OrderUpdateType orderUpdateType) - { - var orderContract = new OrderFullContract - { - Id = src.Id, - Code = src.Code, - ClientId = src.ClientId, - AccountId = src.AccountId, - TradingConditionId = src.TradingConditionId, - AccountAssetId = src.AccountAssetId, - Instrument = src.Instrument, - Type = src.GetOrderType().ToType(), - CreateDate = src.CreateDate, - OpenDate = src.OpenDate, - CloseDate = src.CloseDate, - ExpectedOpenPrice = src.ExpectedOpenPrice, - OpenPrice = src.OpenPrice, - ClosePrice = src.ClosePrice, - QuoteRate = src.GetFplRate(), - MarginRate = src.GetMarginRate(), - AssetAccuracy = src.AssetAccuracy, - Volume = src.Volume, - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss, - CommissionLot = src.CommissionLot, - OpenCommission = src.GetOpenCommission(), - CloseCommission = src.GetCloseCommission(), - SwapCommission = src.SwapCommission, - EquivalentAsset = src.EquivalentAsset, - OpenPriceEquivalent = src.OpenPriceEquivalent, - ClosePriceEquivalent = src.ClosePriceEquivalent, - OpenExternalOrderId = src.OpenExternalOrderId, - OpenExternalProviderId = src.OpenExternalProviderId, - CloseExternalOrderId = src.CloseExternalOrderId, - CloseExternalProviderId = src.CloseExternalProviderId, - StartClosingDate = src.StartClosingDate, - Status = src.Status.ToType(), - CloseReason = src.CloseReason.ToType(), - FillType = src.FillType.ToType(), - RejectReason = src.RejectReason.ToType(), - RejectReasonText = src.RejectReasonText, - Comment = src.Comment, - MatchedVolume = src.GetMatchedVolume(), - MatchedCloseVolume = src.GetMatchedCloseVolume(), - PnL = src.GetTotalFpl(), - Fpl = src.GetFpl(), - InterestRateSwap = src.GetSwaps(), - MarginInit = src.GetMarginInit(), - MarginMaintenance = src.GetMarginMaintenance(), - OrderUpdateType = orderUpdateType.ToType(), - MatchingEngineMode = src.MatchingEngineMode.ToType(), - LegalEntity = src.LegalEntity, - }; - - foreach (var order in src.MatchedOrders) - { - orderContract.MatchedOrders.Add(order.ToBackendContract()); - } - - foreach (var order in src.MatchedCloseOrders) - { - orderContract.MatchedCloseOrders.Add(order.ToBackendContract()); - } - - return orderContract; - } - - public static OrderContract ToBaseContract(this IOrder src) - { - var orderContract = new OrderContract - { - Id = src.Id, - Code = src.Code, - AccountId = src.AccountId, - AccountAssetId = src.AccountAssetId, - ClientId = src.ClientId, - Instrument = src.Instrument, - Status = src.Status.ToType(), - CreateDate = src.CreateDate, - OpenDate = src.OpenDate, - CloseDate = src.CloseDate, - ExpectedOpenPrice = src.ExpectedOpenPrice, - OpenPrice = src.OpenPrice, - ClosePrice = src.ClosePrice, - Type = src.GetOrderType().ToType(), - Volume = src.Volume, - MatchedVolume = src.GetMatchedVolume(), - MatchedCloseVolume = src.GetMatchedCloseVolume(), - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss, - Fpl = src.GetFpl(), - PnL = src.GetTotalFpl(), - CloseReason = src.CloseReason.ToType(), - RejectReason = src.RejectReason.ToType(), - RejectReasonText = src.RejectReasonText, - CommissionLot = src.CommissionLot, - OpenCommission = src.GetOpenCommission(), - CloseCommission = src.GetCloseCommission(), - SwapCommission = src.SwapCommission, - EquivalentAsset = src.EquivalentAsset, - OpenPriceEquivalent = src.OpenPriceEquivalent, - ClosePriceEquivalent = src.ClosePriceEquivalent, - OpenExternalOrderId = src.OpenExternalOrderId, - OpenExternalProviderId = src.OpenExternalProviderId, - CloseExternalOrderId = src.CloseExternalOrderId, - CloseExternalProviderId = src.CloseExternalProviderId, - MatchingEngineMode = src.MatchingEngineMode.ToType(), - LegalEntity = src.LegalEntity, - }; - - foreach (var order in src.MatchedOrders) - { - orderContract.MatchedOrders.Add(order.ToBackendContract()); - } - - foreach (var order in src.MatchedCloseOrders) - { - orderContract.MatchedCloseOrders.Add(order.ToBackendContract()); - } - - return orderContract; - } - - - public static MatchedOrderBackendContract ToBackendContract(this MatchedOrder src) - { - return new MatchedOrderBackendContract - { - OrderId = src.OrderId, - MarketMakerId = src.MarketMakerId, - LimitOrderLeftToMatch = src.LimitOrderLeftToMatch, - Volume = src.Volume, - Price = src.Price, - MatchedDate = src.MatchedDate - }; - } - - public static LimitOrderBackendContract ToBackendContract(this LimitOrder src) - { - return new LimitOrderBackendContract - { - Id = src.Id, - MarketMakerId = src.MarketMakerId, - Instrument = src.Instrument, - Volume = src.Volume, - Price = src.Price, - CreateDate = src.CreateDate, - MatchedOrders = src.MatchedOrders.Select(item => item.ToBackendContract()).ToArray() - }; - } - - public static OrderBookBackendContract ToBackendContract(this OrderBook orderbook) - { - return new OrderBookBackendContract - { - Buy = orderbook.Buy.ToDictionary(pair => pair.Key, pair => pair.Value.Select(item => item.ToBackendContract()).ToArray()), - Sell = orderbook.Sell.ToDictionary(pair => pair.Key, pair => pair.Value.Select(item => item.ToBackendContract()).ToArray()), - }; - } - - public static InstrumentBidAskPairContract ToBackendContract(this InstrumentBidAskPair src) - { - return new InstrumentBidAskPairContract - { - Id = src.Instrument, - Date = src.Date, - Bid = src.Bid, - Ask = src.Ask - }; - } - - public static TradingConditionModel ToBackendContract(this ITradingCondition src) - { - return new TradingConditionModel - { - Id = src.Id, - Name = src.Name, - IsDefault = src.IsDefault, - LegalEntity = src.LegalEntity, - }; - } - - public static AccountGroupModel ToBackendContract(this IAccountGroup src) - { - return new AccountGroupModel - { - TradingConditionId = src.TradingConditionId, - BaseAssetId = src.BaseAssetId, - MarginCall = src.MarginCall, - StopOut = src.StopOut, - DepositTransferLimit = src.DepositTransferLimit, - ProfitWithdrawalLimit = src.ProfitWithdrawalLimit - }; - } - - public static MarginTradingAccountModel ToBackendContract(this IMarginTradingAccount src) - { - return new MarginTradingAccountModel - { - Id = src.Id, - ClientId = src.ClientId, - TradingConditionId = src.TradingConditionId, - BaseAssetId = src.BaseAssetId, - Balance = src.Balance, - WithdrawTransferLimit = src.WithdrawTransferLimit, - LegalEntity = src.LegalEntity, - }; - } - } -} diff --git a/src/MarginTrading.Backend.Core.Mappers/DomainToClientContractMapper.cs b/src/MarginTrading.Backend.Core.Mappers/DomainToClientContractMapper.cs deleted file mode 100644 index f19bb05af..000000000 --- a/src/MarginTrading.Backend.Core.Mappers/DomainToClientContractMapper.cs +++ /dev/null @@ -1,40 +0,0 @@ -using MarginTrading.Contract.ClientContracts; - -namespace MarginTrading.Backend.Core.Mappers -{ - public static class DomainToClientContractMapper - { - public static MarginTradingAccountClientContract ToClientContract(this MarginTradingAccount src) - { - return new MarginTradingAccountClientContract - { - Id = src.Id, - TradingConditionId = src.TradingConditionId, - BaseAssetId = src.BaseAssetId, - Balance = src.Balance, - WithdrawTransferLimit = src.WithdrawTransferLimit, - MarginCall = src.GetMarginCallLevel(), - StopOut = src.GetStopOutLevel(), - TotalCapital = src.GetTotalCapital(), - FreeMargin = src.GetFreeMargin(), - MarginAvailable = src.GetMarginAvailable(), - UsedMargin = src.GetUsedMargin(), - MarginInit = src.GetMarginInit(), - PnL = src.GetPnl(), - OpenPositionsCount = src.GetOpenPositionsCount(), - MarginUsageLevel = src.GetMarginUsageLevel() - }; - } - - public static BidAskClientContract ToClientContract(this InstrumentBidAskPair src) - { - return new BidAskClientContract - { - Id = src.Instrument, - Date = src.Date, - Bid = src.Bid, - Ask = src.Ask - }; - } - } -} diff --git a/src/MarginTrading.Backend.Core.Mappers/DomainToRabbitMqContractMapper.cs b/src/MarginTrading.Backend.Core.Mappers/DomainToRabbitMqContractMapper.cs index db3c6e435..ce62dee68 100644 --- a/src/MarginTrading.Backend.Core.Mappers/DomainToRabbitMqContractMapper.cs +++ b/src/MarginTrading.Backend.Core.Mappers/DomainToRabbitMqContractMapper.cs @@ -1,22 +1,27 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Contracts.TradingSchedule; +using MarginTrading.Backend.Core.DayOffSettings; using MarginTrading.Contract.RabbitMqMessageModels; namespace MarginTrading.Backend.Core.Mappers { public static class DomainToRabbitMqContractMapper { - public static BidAskPairRabbitMqContract ToRabbitMqContract(this InstrumentBidAskPair pair) + public static BidAskPairRabbitMqContract ToRabbitMqContract(this InstrumentBidAskPair pair, bool isEod) { return new BidAskPairRabbitMqContract { Instrument = pair.Instrument, Ask = pair.Ask, Bid = pair.Bid, - Date = pair.Date + Date = pair.Date, + IsEod = isEod ? true : (bool?)null, }; } - public static AccountStatsContract ToRabbitMqContract(this IMarginTradingAccount account, bool isLive) + public static AccountStatsContract ToRabbitMqContract(this IMarginTradingAccount account) { return new AccountStatsContract { @@ -26,7 +31,7 @@ public static AccountStatsContract ToRabbitMqContract(this IMarginTradingAccount BaseAssetId = account.BaseAssetId, Balance = account.Balance, WithdrawTransferLimit = account.WithdrawTransferLimit, - MarginCallLevel = account.GetMarginCallLevel(), + MarginCallLevel = account.GetMarginCall1Level(), StopOutLevel = account.GetStopOutLevel(), TotalCapital = account.GetTotalCapital(), FreeMargin = account.GetFreeMargin(), @@ -36,9 +41,24 @@ public static AccountStatsContract ToRabbitMqContract(this IMarginTradingAccount PnL = account.GetPnl(), OpenPositionsCount = account.GetOpenPositionsCount(), MarginUsageLevel = account.GetMarginUsageLevel(), - IsLive = isLive, LegalEntity = account.LegalEntity, }; } + + public static CompiledScheduleTimeIntervalContract ToRabbitMqContract(this CompiledScheduleTimeInterval schedule) + { + return new CompiledScheduleTimeIntervalContract + { + Schedule = new ScheduleSettingsContract + { + Id = schedule.Schedule.Id, + Rank = schedule.Schedule.Rank, + IsTradeEnabled = schedule.Schedule.IsTradeEnabled, + PendingOrdersCutOff = schedule.Schedule.PendingOrdersCutOff, + }, + Start = schedule.Start, + End = schedule.End, + }; + } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core.Mappers/MarginTrading.Backend.Core.Mappers.csproj b/src/MarginTrading.Backend.Core.Mappers/MarginTrading.Backend.Core.Mappers.csproj index 6a1e8bf25..b2dd83b29 100644 --- a/src/MarginTrading.Backend.Core.Mappers/MarginTrading.Backend.Core.Mappers.csproj +++ b/src/MarginTrading.Backend.Core.Mappers/MarginTrading.Backend.Core.Mappers.csproj @@ -1,9 +1,14 @@  netstandard2.0 - 1.0.1 + 1.16.29 + 7.3 + + + 1701;1702;1705;CA2007;0612;0618;1591 + {08346EF4-8525-4CE0-93C4-CCE510D27D88} diff --git a/src/MarginTrading.Backend.Core/AccountMarginFreezing.cs b/src/MarginTrading.Backend.Core/AccountMarginFreezing.cs new file mode 100644 index 000000000..3898c7c05 --- /dev/null +++ b/src/MarginTrading.Backend.Core/AccountMarginFreezing.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using JetBrains.Annotations; + +namespace MarginTrading.Backend.Core +{ + public class AccountMarginFreezing : IAccountMarginFreezing + { + public AccountMarginFreezing([NotNull] string operationId, + [NotNull] string accountId, decimal amount) + { + OperationId = operationId ?? throw new ArgumentNullException(nameof(operationId)); + AccountId = accountId ?? throw new ArgumentNullException(nameof(accountId)); + Amount = amount; + } + + [NotNull] public string OperationId { get; } + [NotNull] public string AccountId { get; } + public decimal Amount { get; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Caches/IAssetPairsCache.cs b/src/MarginTrading.Backend.Core/Caches/IAssetPairsCache.cs index ef38fd31a..69855e8da 100644 --- a/src/MarginTrading.Backend.Core/Caches/IAssetPairsCache.cs +++ b/src/MarginTrading.Backend.Core/Caches/IAssetPairsCache.cs @@ -1,3 +1,6 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using System.Collections.Generic; using System.Collections.Immutable; using JetBrains.Annotations; @@ -27,5 +30,8 @@ public interface IAssetPairsCache bool TryGetAssetPairQuoteSubst(string substAsset, string instrument, string legalEntity, out IAssetPair assetPair); ImmutableHashSet GetAllIds(); + + void AddOrUpdate(IAssetPair assetPair); + void Remove(string assetPairId); } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Caches/IOvernightSwapCache.cs b/src/MarginTrading.Backend.Core/Caches/IOvernightSwapCache.cs deleted file mode 100644 index 41c625f76..000000000 --- a/src/MarginTrading.Backend.Core/Caches/IOvernightSwapCache.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; - -namespace MarginTrading.Backend.Core -{ - public interface IOvernightSwapCache - { - bool TryGet(string key, out OvernightSwapCalculation item); - IReadOnlyList GetAll(); - bool AddOrReplace(OvernightSwapCalculation item); - void Remove(OvernightSwapCalculation item); - void ClearAll(); - void Initialize(IEnumerable items); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/DayOffSettings/CompiledScheduleTimeInterval.cs b/src/MarginTrading.Backend.Core/DayOffSettings/CompiledScheduleTimeInterval.cs new file mode 100644 index 000000000..0043f5d67 --- /dev/null +++ b/src/MarginTrading.Backend.Core/DayOffSettings/CompiledScheduleTimeInterval.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Core.DayOffSettings +{ + public class CompiledScheduleTimeInterval + { + public ScheduleSettings Schedule { get; } + public DateTime Start { get; } + public DateTime End { get; } + + public CompiledScheduleTimeInterval(ScheduleSettings schedule, DateTime start, DateTime end) + { + Schedule = schedule; + Start = start; + End = end; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/DayOffSettings/DayOffExclusion.cs b/src/MarginTrading.Backend.Core/DayOffSettings/DayOffExclusion.cs deleted file mode 100644 index c67948cf4..000000000 --- a/src/MarginTrading.Backend.Core/DayOffSettings/DayOffExclusion.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace MarginTrading.Backend.Core.DayOffSettings -{ - public class DayOffExclusion - { - public Guid Id { get; } - public string AssetPairRegex { get; } - public DateTime Start { get; } - public DateTime End { get; } - public bool IsTradeEnabled { get; } - - public DayOffExclusion(Guid id, string assetPairRegex, DateTime start, DateTime end, bool isTradeEnabled) - { - Id = id; - AssetPairRegex = assetPairRegex ?? throw new ArgumentNullException(nameof(assetPairRegex)); - Start = start; - End = end; - IsTradeEnabled = isTradeEnabled; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/DayOffSettings/DayOffSettingsRoot.cs b/src/MarginTrading.Backend.Core/DayOffSettings/DayOffSettingsRoot.cs deleted file mode 100644 index e04be8975..000000000 --- a/src/MarginTrading.Backend.Core/DayOffSettings/DayOffSettingsRoot.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Immutable; - -namespace MarginTrading.Backend.Core.DayOffSettings -{ - public class DayOffSettingsRoot - { - public ImmutableDictionary Exclusions { get; } - public ScheduleSettings ScheduleSettings { get; } - - public DayOffSettingsRoot(ImmutableDictionary exclusions, - ScheduleSettings scheduleSettings) - { - Exclusions = exclusions ?? throw new ArgumentNullException(nameof(exclusions)); - ScheduleSettings = scheduleSettings ?? throw new ArgumentNullException(nameof(scheduleSettings)); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/DayOffSettings/MarketState.cs b/src/MarginTrading.Backend.Core/DayOffSettings/MarketState.cs new file mode 100644 index 000000000..a7876dac5 --- /dev/null +++ b/src/MarginTrading.Backend.Core/DayOffSettings/MarketState.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Core.DayOffSettings +{ + public class MarketState + { + public string Id { get; set; } + + public bool IsEnabled { get; set; } + + public override string ToString() + { + return $"Id: {Id}, IsEnabled: {IsEnabled}."; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/DayOffSettings/ScheduleConstraint.cs b/src/MarginTrading.Backend.Core/DayOffSettings/ScheduleConstraint.cs new file mode 100644 index 000000000..dae5c6f98 --- /dev/null +++ b/src/MarginTrading.Backend.Core/DayOffSettings/ScheduleConstraint.cs @@ -0,0 +1,40 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Core.DayOffSettings +{ + public class ScheduleConstraint : IEquatable + { + public DateTime? Date { get; set; } + public DayOfWeek? DayOfWeek { get; set; } + public TimeSpan Time { get; set; } + + public ScheduleConstraintType GetConstraintType() + { + if (Date == null && DayOfWeek == default) + { + return ScheduleConstraintType.Daily; + } + if (Date != null && DayOfWeek == default) + { + return ScheduleConstraintType.Single; + } + if (Date == null && DayOfWeek != default) + { + return ScheduleConstraintType.Weekly; + } + + return ScheduleConstraintType.Invalid; + } + + public bool Equals(ScheduleConstraint other) + { + return other != null + && Date == other.Date + && DayOfWeek == other.DayOfWeek + && Time == other.Time; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/DayOffSettings/ScheduleConstraintType.cs b/src/MarginTrading.Backend.Core/DayOffSettings/ScheduleConstraintType.cs new file mode 100644 index 000000000..e647c69a4 --- /dev/null +++ b/src/MarginTrading.Backend.Core/DayOffSettings/ScheduleConstraintType.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.DayOffSettings +{ + public enum ScheduleConstraintType + { + Invalid = 0, + Daily = 1, + Single = 2, + Weekly = 3, + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/DayOffSettings/ScheduleSettings.cs b/src/MarginTrading.Backend.Core/DayOffSettings/ScheduleSettings.cs index b433404a5..26b3dd411 100644 --- a/src/MarginTrading.Backend.Core/DayOffSettings/ScheduleSettings.cs +++ b/src/MarginTrading.Backend.Core/DayOffSettings/ScheduleSettings.cs @@ -1,27 +1,75 @@ -using System; -using System.Collections.Generic; - -namespace MarginTrading.Backend.Core.DayOffSettings -{ - public class ScheduleSettings - { - public DayOfWeek DayOffStartDay { get; } - public TimeSpan DayOffStartTime { get; } - public DayOfWeek DayOffEndDay { get; } - public TimeSpan DayOffEndTime { get; } - public HashSet AssetPairsWithoutDayOff { get; } - public TimeSpan PendingOrdersCutOff { get; } - - public ScheduleSettings(DayOfWeek dayOffStartDay, TimeSpan dayOffStartTime, DayOfWeek dayOffEndDay, - TimeSpan dayOffEndTime, HashSet assetPairsWithoutDayOff, TimeSpan pendingOrdersCutOff) - { - DayOffStartDay = dayOffStartDay; - DayOffStartTime = dayOffStartTime; - DayOffEndDay = dayOffEndDay; - DayOffEndTime = dayOffEndTime; - AssetPairsWithoutDayOff = assetPairsWithoutDayOff ?? - throw new ArgumentNullException(nameof(assetPairsWithoutDayOff)); - PendingOrdersCutOff = pendingOrdersCutOff; - } - } +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using JetBrains.Annotations; +using MarginTrading.SettingsService.Contracts.Scheduling; +// ReSharper disable NotNullMemberIsNotInitialized + +namespace MarginTrading.Backend.Core.DayOffSettings +{ + public class ScheduleSettings + { + public string Id { get; set; } + public int Rank { get; set; } + public bool? IsTradeEnabled { get; set; } = false; + public TimeSpan? PendingOrdersCutOff { get; set; } + + /// + /// Can't be null. Must be validated before conversion from contracts. + /// + [NotNull] + public ScheduleConstraint Start { get; set; } + /// + /// Can't be null. Must be validated before conversion from contracts. + /// + [NotNull] + public ScheduleConstraint End { get; set; } + + public static ScheduleSettings Create(CompiledScheduleSettingsContract scheduleSettingsContract) + { + return new ScheduleSettings + { + Id = scheduleSettingsContract.Id, + Rank = scheduleSettingsContract.Rank, + IsTradeEnabled = scheduleSettingsContract.IsTradeEnabled, + PendingOrdersCutOff = scheduleSettingsContract.PendingOrdersCutOff, + Start = new ScheduleConstraint + { + Date = scheduleSettingsContract.Start.Date, + DayOfWeek = scheduleSettingsContract.Start.DayOfWeek, + Time = scheduleSettingsContract.Start.Time + }, + End = new ScheduleConstraint + { + Date = scheduleSettingsContract.End.Date, + DayOfWeek = scheduleSettingsContract.End.DayOfWeek, + Time = scheduleSettingsContract.End.Time, + } + }; + } + + public static ScheduleSettings Create(ScheduleSettingsContract scheduleSettingsContract) + { + return new ScheduleSettings + { + Id = scheduleSettingsContract.Id, + Rank = scheduleSettingsContract.Rank, + IsTradeEnabled = scheduleSettingsContract.IsTradeEnabled, + PendingOrdersCutOff = scheduleSettingsContract.PendingOrdersCutOff, + Start = new ScheduleConstraint + { + Date = scheduleSettingsContract.Start.Date, + DayOfWeek = scheduleSettingsContract.Start.DayOfWeek, + Time = scheduleSettingsContract.Start.Time + }, + End = new ScheduleConstraint + { + Date = scheduleSettingsContract.End.Date, + DayOfWeek = scheduleSettingsContract.End.DayOfWeek, + Time = scheduleSettingsContract.End.Time, + } + }; + } + } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Exceptions/AccountNotFoundException.cs b/src/MarginTrading.Backend.Core/Exceptions/AccountNotFoundException.cs index ec47c3fa5..e61d9e0ae 100644 --- a/src/MarginTrading.Backend.Core/Exceptions/AccountNotFoundException.cs +++ b/src/MarginTrading.Backend.Core/Exceptions/AccountNotFoundException.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Backend.Core.Exceptions { diff --git a/src/MarginTrading.Backend.Core/Exceptions/AssetPairNotFoundException.cs b/src/MarginTrading.Backend.Core/Exceptions/AssetPairNotFoundException.cs index 519af8916..5262934db 100644 --- a/src/MarginTrading.Backend.Core/Exceptions/AssetPairNotFoundException.cs +++ b/src/MarginTrading.Backend.Core/Exceptions/AssetPairNotFoundException.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Backend.Core.Exceptions { diff --git a/src/MarginTrading.Backend.Core/Exceptions/FxRateNotFoundException.cs b/src/MarginTrading.Backend.Core/Exceptions/FxRateNotFoundException.cs new file mode 100644 index 000000000..224c11d5a --- /dev/null +++ b/src/MarginTrading.Backend.Core/Exceptions/FxRateNotFoundException.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Core.Exceptions +{ + public class FxRateNotFoundException : Exception + { + public string InstrumentId { get; private set; } + + public FxRateNotFoundException(string instrumentId, string message):base(message) + { + InstrumentId = instrumentId; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Exceptions/InstrumentByAssetsNotFoundException.cs b/src/MarginTrading.Backend.Core/Exceptions/InstrumentByAssetsNotFoundException.cs index 33e9fd1fd..8dbf68d8a 100644 --- a/src/MarginTrading.Backend.Core/Exceptions/InstrumentByAssetsNotFoundException.cs +++ b/src/MarginTrading.Backend.Core/Exceptions/InstrumentByAssetsNotFoundException.cs @@ -1,16 +1,23 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MarginTrading.Backend.Core.Messages; namespace MarginTrading.Backend.Core.Exceptions { public class InstrumentByAssetsNotFoundException : Exception { - public string Asset1 { get; private set; } - public string Asset2 { get; private set; } + public string Asset1 { get; } + public string Asset2 { get; } + public string LegalEntity { get; } - public InstrumentByAssetsNotFoundException(string asset1, string asset2, string message):base(message) + public InstrumentByAssetsNotFoundException(string asset1, string asset2, string legalEntity) + :base(string.Format(MtMessages.InstrumentWithAssetsNotFound, asset1, asset2, legalEntity)) { Asset1 = asset1; Asset2 = asset2; + LegalEntity = legalEntity; } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Exceptions/PositionNotFoundException.cs b/src/MarginTrading.Backend.Core/Exceptions/PositionNotFoundException.cs new file mode 100644 index 000000000..dd37da283 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Exceptions/PositionNotFoundException.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Core.Exceptions +{ + public class PositionNotFoundException : Exception + { + public PositionNotFoundException(string message) + : base(message) + { + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Exceptions/QuoteNotFoundException.cs b/src/MarginTrading.Backend.Core/Exceptions/QuoteNotFoundException.cs index cf0ab0d3d..a2eb36fe5 100644 --- a/src/MarginTrading.Backend.Core/Exceptions/QuoteNotFoundException.cs +++ b/src/MarginTrading.Backend.Core/Exceptions/QuoteNotFoundException.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Backend.Core.Exceptions { diff --git a/src/MarginTrading.Backend.Core/Exceptions/StateTransitionNotFoundException.cs b/src/MarginTrading.Backend.Core/Exceptions/StateTransitionNotFoundException.cs new file mode 100644 index 000000000..91caba804 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Exceptions/StateTransitionNotFoundException.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Core.Exceptions +{ + public class StateTransitionNotFoundException : Exception + { + public StateTransitionNotFoundException(string message) + : base(message) + { + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Exceptions/ValidateOrderException.cs b/src/MarginTrading.Backend.Core/Exceptions/ValidateOrderException.cs index 1d072245c..75f1c632e 100644 --- a/src/MarginTrading.Backend.Core/Exceptions/ValidateOrderException.cs +++ b/src/MarginTrading.Backend.Core/Exceptions/ValidateOrderException.cs @@ -1,4 +1,8 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using System; +using MarginTrading.Backend.Core.Orders; namespace MarginTrading.Backend.Core.Exceptions { diff --git a/src/MarginTrading.Backend.Core/Extensions/SagaExtensions.cs b/src/MarginTrading.Backend.Core/Extensions/SagaExtensions.cs new file mode 100644 index 000000000..409e448e1 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Extensions/SagaExtensions.cs @@ -0,0 +1,40 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using Common; +using Common.Log; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Core.Extensions +{ + public static class SagaExtensions + { + public static bool SwitchState(this OperationDataBase data, TState expectedState, TState nextState) + where TState : struct, IConvertible + { + if (data == null) + { + throw new InvalidOperationException("Operation execution data was not properly initialized."); + } + + if (Convert.ToInt32(data.State) < Convert.ToInt32(expectedState)) + { + // Throws to retry and wait until the operation will be in the required state + throw new InvalidOperationException( + $"Operation execution state can't be switched: {data.State} -> {nextState}. Waiting for the {expectedState} state."); + } + + if (Convert.ToInt32(data.State) > Convert.ToInt32(expectedState)) + { + LogLocator.CommonLog.WriteWarning(nameof(SagaExtensions), nameof(SwitchState), + $"Operation is already in the next state, so this event is ignored, {new {data, expectedState, nextState}.ToJson()}."); + return false; + } + + data.State = nextState; + + return true; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Extensions/ScheduleSettingsExtensions.cs b/src/MarginTrading.Backend.Core/Extensions/ScheduleSettingsExtensions.cs new file mode 100644 index 000000000..ee666ee2a --- /dev/null +++ b/src/MarginTrading.Backend.Core/Extensions/ScheduleSettingsExtensions.cs @@ -0,0 +1,135 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using MarginTrading.Backend.Core.DayOffSettings; +using MarginTrading.SettingsService.Contracts.Scheduling; + +namespace MarginTrading.Backend.Core.Extensions +{ + public static class ScheduleSettingsExtensions + { + public static bool Enabled(this CompiledScheduleTimeInterval compiledScheduleTimeInterval) + { + return compiledScheduleTimeInterval?.Schedule.IsTradeEnabled ?? true; + } + + public static Dictionary> InvalidSchedules( + this IEnumerable scheduleContracts) + { + var invalidSchedules = new Dictionary>(); + foreach (var scheduleContract in scheduleContracts) + { + var scheduleSettings = new List(); + foreach (var scheduleSetting in scheduleContract.ScheduleSettings) + { + try + { + ScheduleConstraintContract.Validate(scheduleSetting); + } + catch + { + scheduleSettings.Add(scheduleSetting); + } + } + + if (scheduleSettings.Any()) + { + invalidSchedules.Add(scheduleContract.AssetPairId, scheduleSettings); + } + } + + return invalidSchedules; + } + + public static List InvalidSchedules( + this IEnumerable scheduleContracts) + { + var scheduleSettings = new List(); + foreach (var scheduleSetting in scheduleContracts) + { + try + { + ScheduleConstraintContract.Validate(scheduleSetting); + } + catch + { + scheduleSettings.Add(scheduleSetting); + } + } + + return scheduleSettings; + } + + public static IEnumerable ConcatWithPlatform( + this IEnumerable targetSchedule, + IEnumerable platformSchedule, + string platformKey) + { + var rank = int.MaxValue; + var prev = 0; + var resultingPlatformSchedule = platformSchedule + .OrderByDescending(x => x.Rank) + .Select(x => + { + var result = x.CloneWithRank(x.Rank == prev ? rank : --rank);//todo add some protection to settings service + + prev = x.Rank; + + return result; + }); + + return targetSchedule + .Where(x => x.Id != platformKey) + .Concat(resultingPlatformSchedule); + } + + public static ScheduleSettingsContract CloneWithRank(this ScheduleSettingsContract schedule, int rank) + => new ScheduleSettingsContract + { + Id = schedule.Id, + Rank = rank, + AssetPairRegex = schedule.AssetPairRegex, + AssetPairs = schedule.AssetPairs, + MarketId = schedule.MarketId, + IsTradeEnabled = schedule.IsTradeEnabled, + PendingOrdersCutOff = schedule.PendingOrdersCutOff, + Start = schedule.Start.Clone(), + End = schedule.End.Clone(), + }; + + private static ScheduleConstraintContract Clone(this ScheduleConstraintContract constraint) + => new ScheduleConstraintContract + { + Date = constraint.Date, + DayOfWeek = constraint.DayOfWeek, + Time = constraint.Time, + }; + + public static MarketState GetMarketState( + this List compiledSchedule, string marketId, DateTime currentDateTime) + { + var currentInterval = compiledSchedule + .Where(x => IsBetween(currentDateTime, x.Start, x.End)) + .OrderByDescending(x => x.Schedule.Rank) + .FirstOrDefault(); + + var isEnabled = currentInterval?.Enabled() ?? true; + + var result = new MarketState + { + Id = marketId, + IsEnabled = isEnabled, + }; + + return result; + } + + private static bool IsBetween(DateTime currentDateTime, DateTime start, DateTime end) + { + return start <= currentDateTime && currentDateTime < end; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/FloatngProfitLoss.cs b/src/MarginTrading.Backend.Core/FloatngProfitLoss.cs index 59409ee5a..cb67f0aca 100644 --- a/src/MarginTrading.Backend.Core/FloatngProfitLoss.cs +++ b/src/MarginTrading.Backend.Core/FloatngProfitLoss.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Core +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core { public class FloatngProfitLoss { diff --git a/src/MarginTrading.Backend.Core/FplData.cs b/src/MarginTrading.Backend.Core/FplData.cs index 0f9eef84d..fcc9085d3 100644 --- a/src/MarginTrading.Backend.Core/FplData.cs +++ b/src/MarginTrading.Backend.Core/FplData.cs @@ -1,18 +1,21 @@ -namespace MarginTrading.Backend.Core +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core { public class FplData { public decimal Fpl { get; set; } - public decimal FplRate { get; set; } public decimal MarginRate { get; set; } public decimal MarginInit { get; set; } public decimal MarginMaintenance { get; set; } - public decimal OpenPrice { get; set; } - public decimal ClosePrice { get; set; } - public decimal TotalFplSnapshot { get; set; } - public decimal SwapsSnapshot { get; set; } public int AccountBaseAssetAccuracy { get; set; } + /// + /// Margin used for open of position + /// + public decimal InitialMargin { get; set; } + public int CalculatedHash { get; set; } public int ActualHash { get; set; } } diff --git a/src/MarginTrading.Backend.Core/Helpers/DictionaryExtensions.cs b/src/MarginTrading.Backend.Core/Helpers/DictionaryExtensions.cs index 31dd041e5..b23cdcc1c 100644 --- a/src/MarginTrading.Backend.Core/Helpers/DictionaryExtensions.cs +++ b/src/MarginTrading.Backend.Core/Helpers/DictionaryExtensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using JetBrains.Annotations; diff --git a/src/MarginTrading.Backend.Core/Helpers/PaginationHelper.cs b/src/MarginTrading.Backend.Core/Helpers/PaginationHelper.cs new file mode 100644 index 000000000..d91af875e --- /dev/null +++ b/src/MarginTrading.Backend.Core/Helpers/PaginationHelper.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Core.Helpers +{ + public static class PaginationHelper + { + public const int MaxResults = 100; + public const int UnspecifiedResults = 20; + + public static int GetTake(int? take) + { + return take == null + ? UnspecifiedResults + : Math.Min(take.Value, MaxResults); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Helpers/ReflectionHelpers.cs b/src/MarginTrading.Backend.Core/Helpers/ReflectionHelpers.cs new file mode 100644 index 000000000..f023d19c6 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Helpers/ReflectionHelpers.cs @@ -0,0 +1,42 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Common; +using Newtonsoft.Json; + +namespace MarginTrading.Backend.Core.Helpers +{ + public static class ReflectionHelpers + { + /// + /// For order/position merging on initialization only! + /// Return true if there was difference, false if items were the same. + /// + public static bool SetIfDiffer(this T obj, Dictionary propertyData) + where T: class + { + var properties = obj.GetType().GetProperties() + .Where(x => Attribute.IsDefined(x, typeof(JsonPropertyAttribute))) + .ToDictionary(x => x.Name, x => x); + + var result = false; + foreach (var data in propertyData) + { + if (!properties.TryGetValue(data.Key, out var property) + || (property.PropertyType.IsValueType && property.GetValue(obj) == data.Value) + || (!property.PropertyType.IsValueType && property.GetValue(obj).ToJson() == data.Value.ToJson()))// kind of a hack + { + continue; + } + + property.SetValue(obj, data.Value); + result = true; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/IAccountMarginFreezing.cs b/src/MarginTrading.Backend.Core/IAccountMarginFreezing.cs new file mode 100644 index 000000000..52985de17 --- /dev/null +++ b/src/MarginTrading.Backend.Core/IAccountMarginFreezing.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core +{ + public interface IAccountMarginFreezing + { + string OperationId { get; } + string AccountId { get; } + decimal Amount { get; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/IAsset.cs b/src/MarginTrading.Backend.Core/IAsset.cs index 74c0b8551..9c18f685c 100644 --- a/src/MarginTrading.Backend.Core/IAsset.cs +++ b/src/MarginTrading.Backend.Core/IAsset.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Core +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core { public interface IAsset { diff --git a/src/MarginTrading.Backend.Core/IAssetPair.cs b/src/MarginTrading.Backend.Core/IAssetPair.cs index ef4b73130..091192ea5 100644 --- a/src/MarginTrading.Backend.Core/IAssetPair.cs +++ b/src/MarginTrading.Backend.Core/IAssetPair.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using JetBrains.Annotations; using MarginTrading.Backend.Core.MatchingEngines; using MarginTrading.Common.Extensions; @@ -63,6 +66,23 @@ public interface IAssetPair /// You cannot specify a value lower or equal to 0 to ensure positive resulting values. /// decimal StpMultiplierMarkupAsk { get; } + + /// + /// Asset pair is blocked due to a zero quote + /// + /// The property is mutable + bool IsSuspended { get; set; } + + /// + /// Asset pair is blocked by API call for some time + /// + bool IsFrozen { get; } + + /// + /// Asset pair is blocked by API call, for all time in most cases + /// + bool IsDiscontinued { get; } + } public class AssetPair : IAssetPair @@ -70,7 +90,7 @@ public class AssetPair : IAssetPair public AssetPair(string id, string name, string baseAssetId, string quoteAssetId, int accuracy, string legalEntity, [CanBeNull] string basePairId, MatchingEngineMode matchingEngineMode, decimal stpMultiplierMarkupBid, - decimal stpMultiplierMarkupAsk) + decimal stpMultiplierMarkupAsk, bool isSuspended, bool isFrozen, bool isDiscontinued) { Id = id ?? throw new ArgumentNullException(nameof(id)); Name = name ?? throw new ArgumentNullException(nameof(name)); @@ -83,6 +103,10 @@ public AssetPair(string id, string name, string baseAssetId, MatchingEngineMode = matchingEngineMode.RequiredEnum(nameof(matchingEngineMode)); StpMultiplierMarkupBid = stpMultiplierMarkupBid.RequiredGreaterThan(0, nameof(stpMultiplierMarkupBid)); StpMultiplierMarkupAsk = stpMultiplierMarkupAsk.RequiredGreaterThan(0, nameof(stpMultiplierMarkupAsk)); + + IsSuspended = isSuspended; + IsFrozen = isFrozen; + IsDiscontinued = isDiscontinued; } public string Id { get; } @@ -96,5 +120,33 @@ public AssetPair(string id, string name, string baseAssetId, public MatchingEngineMode MatchingEngineMode { get; } public decimal StpMultiplierMarkupBid { get; } public decimal StpMultiplierMarkupAsk { get; } + + public bool IsSuspended { get; set; } + public bool IsFrozen { get; } + public bool IsDiscontinued { get; } + + protected bool Equals(AssetPair other) + { + return string.Equals(Id, other.Id) && string.Equals(Name, other.Name) && + string.Equals(BaseAssetId, other.BaseAssetId) && string.Equals(QuoteAssetId, other.QuoteAssetId) && + Accuracy == other.Accuracy && string.Equals(LegalEntity, other.LegalEntity) && + string.Equals(BasePairId, other.BasePairId) && MatchingEngineMode == other.MatchingEngineMode && + StpMultiplierMarkupBid == other.StpMultiplierMarkupBid && + StpMultiplierMarkupAsk == other.StpMultiplierMarkupAsk && IsSuspended == other.IsSuspended && + IsFrozen == other.IsFrozen && IsDiscontinued == other.IsDiscontinued; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((AssetPair) obj); + } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } } } diff --git a/src/MarginTrading.Backend.Core/IAssetPairsCache.cs b/src/MarginTrading.Backend.Core/IAssetPairsCache.cs index e69de29bb..dc30789a1 100644 --- a/src/MarginTrading.Backend.Core/IAssetPairsCache.cs +++ b/src/MarginTrading.Backend.Core/IAssetPairsCache.cs @@ -0,0 +1,2 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. diff --git a/src/MarginTrading.Backend.Core/IBaseOrder.cs b/src/MarginTrading.Backend.Core/IBaseOrder.cs deleted file mode 100644 index e7fd4b86b..000000000 --- a/src/MarginTrading.Backend.Core/IBaseOrder.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using MarginTrading.Backend.Core.MatchedOrders; - -namespace MarginTrading.Backend.Core -{ - public interface IBaseOrder - { - string Id { get; } - string Instrument { get; } - decimal Volume { get; } - DateTime CreateDate { get; } - MatchedOrderCollection MatchedOrders { get; set; } - } - - public class BaseOrder : IBaseOrder - { - public string Id { get; set; } = Guid.NewGuid().ToString("N"); - public string Instrument { get; set; } - public decimal Volume { get; set; } - public DateTime CreateDate { get; set; } = DateTime.UtcNow; - public MatchedOrderCollection MatchedOrders { get; set; } = new MatchedOrderCollection(); - } - - public static class BaseOrderExtension - { - public static OrderDirection GetOrderType(this IBaseOrder order) - { - return order.Volume >= 0 ? OrderDirection.Buy : OrderDirection.Sell; - } - - public static OrderDirection GetCloseType(this IBaseOrder order) - { - return order.Volume >= 0 ? OrderDirection.Sell : OrderDirection.Buy; - } - - public static bool GetIsFullfilled(this IBaseOrder order) - { - return 0 == Math.Round(order.GetRemainingVolume(), MarginTradingHelpers.VolumeAccuracy); - } - - public static decimal GetRemainingVolume(this IBaseOrder order) - { - return Math.Round(Math.Abs(order.Volume) - order.MatchedOrders.SummaryVolume, - MarginTradingHelpers.VolumeAccuracy); - } - } -} diff --git a/src/MarginTrading.Backend.Core/IBidAskPair.cs b/src/MarginTrading.Backend.Core/IBidAskPair.cs index 4fe875866..46d900fbe 100644 --- a/src/MarginTrading.Backend.Core/IBidAskPair.cs +++ b/src/MarginTrading.Backend.Core/IBidAskPair.cs @@ -1,4 +1,8 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MarginTrading.Backend.Core.Orders; namespace MarginTrading.Backend.Core { @@ -14,24 +18,24 @@ public class BidAskPair : IBidAskPair public decimal Ask { get; set; } } - public class GraphBidAskPair : IBidAskPair - { - public decimal Bid { get; set; } - public decimal Ask { get; set; } - public DateTime Date { get; set; } - } - public class InstrumentBidAskPair : BidAskPair { + public decimal BidFirstLevelVolume { get; set; } + public decimal AskFirstLevelVolume { get; set; } public string Instrument { get; set; } public DateTime Date { get; set; } } - public static class BidAskPairExtetsion + public static class BidAskPairExtension { - public static decimal GetPriceForOrderType(this IBidAskPair bidAskPair, OrderDirection orderType) + public static decimal GetPriceForOrderDirection(this InstrumentBidAskPair bidAskPair, OrderDirection orderType) + { + return orderType == OrderDirection.Buy ? bidAskPair.Ask : bidAskPair.Bid; + } + + public static decimal GetVolumeForOrderDirection(this InstrumentBidAskPair bidAskPair, OrderDirection orderType) { - return orderType == OrderDirection.Buy ? bidAskPair.Bid : bidAskPair.Ask; + return orderType == OrderDirection.Buy ? bidAskPair.AskFirstLevelVolume : bidAskPair.BidFirstLevelVolume; } } } diff --git a/src/MarginTrading.Backend.Core/IEquivalentPricesService.cs b/src/MarginTrading.Backend.Core/IEquivalentPricesService.cs index c6e2e24fc..113d091c6 100644 --- a/src/MarginTrading.Backend.Core/IEquivalentPricesService.cs +++ b/src/MarginTrading.Backend.Core/IEquivalentPricesService.cs @@ -1,10 +1,14 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; namespace MarginTrading.Backend.Core { public interface IEquivalentPricesService { void EnrichOpeningOrder(Order order); - void EnrichClosingOrder(Order order); + void EnrichClosingOrder(Position order); } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/ILimitOrder.cs b/src/MarginTrading.Backend.Core/ILimitOrder.cs index 4e86209d9..22a85f89f 100644 --- a/src/MarginTrading.Backend.Core/ILimitOrder.cs +++ b/src/MarginTrading.Backend.Core/ILimitOrder.cs @@ -1,4 +1,9 @@ -namespace MarginTrading.Backend.Core +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Core.Orders; + +namespace MarginTrading.Backend.Core { public interface ILimitOrder { diff --git a/src/MarginTrading.Backend.Core/ILogEntity.cs b/src/MarginTrading.Backend.Core/ILogEntity.cs new file mode 100644 index 000000000..14a0c03d7 --- /dev/null +++ b/src/MarginTrading.Backend.Core/ILogEntity.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Core +{ + public interface ILogEntity + { + DateTime DateTime { get; set; } + string Level { get; set; } + string Env { get; set; } + string AppName { get; set; } + string Version { get; set; } + string Component { get; set; } + string Process { get; set; } + string Context { get; set; } + string Type { get; set; } + string Stack { get; set; } + string Msg { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/ILogRepository.cs b/src/MarginTrading.Backend.Core/ILogRepository.cs new file mode 100644 index 000000000..57bfc9a49 --- /dev/null +++ b/src/MarginTrading.Backend.Core/ILogRepository.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using Lykke.Logs; + +namespace MarginTrading.Backend.Core +{ + public interface ILogRepository + { + Task Insert(ILogEntity log); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/IMarginTradingAccount.cs b/src/MarginTrading.Backend.Core/IMarginTradingAccount.cs index 81896c154..e7e061fdc 100644 --- a/src/MarginTrading.Backend.Core/IMarginTradingAccount.cs +++ b/src/MarginTrading.Backend.Core/IMarginTradingAccount.cs @@ -1,7 +1,11 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; -using System.Threading.Tasks; +using System.Linq; using JetBrains.Annotations; +using MarginTrading.Backend.Core.Settings; namespace MarginTrading.Backend.Core { @@ -15,6 +19,12 @@ public interface IMarginTradingAccount decimal WithdrawTransferLimit { get; } string LegalEntity { get; } [NotNull] AccountFpl AccountFpl { get; } + bool IsDisabled { get; set; } + bool IsDeleted { get; } + DateTime LastUpdateTime { get; } + DateTime LastBalanceChangeTime { get; } + bool IsWithdrawalDisabled { get; } + string LiquidationOperationId { get; } } public class MarginTradingAccount : IMarginTradingAccount, IComparable @@ -26,15 +36,16 @@ public class MarginTradingAccount : IMarginTradingAccount, IComparable(); + + if (!string.IsNullOrEmpty(LiquidationOperationId)) + { + warnings.Add($"Liquidation is in progress with id {LiquidationOperationId}. "); + } + + if (AccountFpl.UnconfirmedMarginData.Any()) + { + warnings.Add($"There is some unconfirmed margin data on account: {string.Join(",", AccountFpl.UnconfirmedMarginData)}. "); + } - return ClientId.CompareTo(other.ClientId); + if (AccountFpl.WithdrawalFrozenMarginData.Any()) + { + warnings.Add($"There is some withdrawal frozen margin data on account: {string.Join(",", AccountFpl.WithdrawalFrozenMarginData)}. "); + } + + Balance = 0; + LiquidationOperationId = string.Empty; + LastUpdateTime = LastBalanceChangeTime = eventTime; + AccountFpl = new AccountFpl(); + + return string.Join(", ", warnings); + } + + public int CompareTo(MarginTradingAccount other) + { + var result = string.Compare(Id, other.Id, StringComparison.Ordinal); + return 0 != result ? result : string.Compare(ClientId, other.ClientId, StringComparison.Ordinal); } public override int GetHashCode() @@ -67,29 +107,10 @@ public override int GetHashCode() public enum AccountLevel { None = 0, - MarginCall = 1, - StopOUt = 2 - } - - public interface IMarginTradingAccountsRepository - { - Task> GetAllAsync(string clientId = null); - - [ItemCanBeNull] - Task GetAsync(string clientId, string accountId); - - [ItemCanBeNull] - Task GetAsync(string accountId); - - Task UpdateBalanceAsync(string clientId, string accountId, decimal amount, - bool changeLimit); - - [ItemCanBeNull] - Task UpdateTradingConditionIdAsync(string clientId, string accountId, - string tradingConditionId); - - Task AddAsync(MarginTradingAccount account); - Task DeleteAsync(string clientId, string accountId); + MarginCall1 = 1, + MarginCall2 = 2, + OvernightMarginCall = 3, + StopOut = 4, } public static class MarginTradingAccountExtensions @@ -109,24 +130,30 @@ private static AccountFpl GetAccountFpl(this IMarginTradingAccount account) return new AccountFpl(); } - public static AccountLevel GetAccountLevel(this IMarginTradingAccount account) + public static AccountLevel GetAccountLevel(this IMarginTradingAccount account, + decimal? overnightUsedMargin = null) { - var marginUsageLevel = account.GetMarginUsageLevel(); + var marginUsageLevel = account.GetMarginUsageLevel(overnightUsedMargin); + var accountFplData = account.GetAccountFpl(); - if (marginUsageLevel <= account.GetStopOutLevel()) - return AccountLevel.StopOUt; - - if (marginUsageLevel <= account.GetMarginCallLevel()) - return AccountLevel.MarginCall; + if (marginUsageLevel <= accountFplData.StopOutLevel) + return AccountLevel.StopOut; + if (marginUsageLevel <= accountFplData.MarginCall2Level) + return AccountLevel.MarginCall2; + + if (marginUsageLevel <= accountFplData.MarginCall1Level) + return AccountLevel.MarginCall1; + return AccountLevel.None; } - public static decimal GetMarginUsageLevel(this IMarginTradingAccount account) + public static decimal GetMarginUsageLevel(this IMarginTradingAccount account, + decimal? overnightUsedMargin = null) { var totalCapital = account.GetTotalCapital(); - var usedMargin = account.GetUsedMargin(); + var usedMargin = overnightUsedMargin ?? account.GetUsedMargin(); //Anton Belkin said 100 is ok ) if (usedMargin <= 0) @@ -137,7 +164,8 @@ public static decimal GetMarginUsageLevel(this IMarginTradingAccount account) public static decimal GetTotalCapital(this IMarginTradingAccount account) { - return account.Balance + account.GetPnl(); + return account.Balance + account.GetUnrealizedDailyPnl() - account.GetFrozenMargin() + + account.GetUnconfirmedMargin(); } public static decimal GetPnl(this IMarginTradingAccount account) @@ -145,10 +173,28 @@ public static decimal GetPnl(this IMarginTradingAccount account) return account.GetAccountFpl().PnL; } + public static decimal GetUnrealizedDailyPnl(this IMarginTradingAccount account) + { + return account.GetAccountFpl().UnrealizedDailyPnl; + } + public static decimal GetUsedMargin(this IMarginTradingAccount account) + { + var fplData = account.GetAccountFpl(); + //According to ESMA MCO rule a stop-out added at 50% of the initially invested margin + //So if the 50% of the initially invested margin (accumulated per the account), is bigger than the recalculated 100%, than this margin requirement is the reference for the free capital determination and the liquidation level. + return Math.Max(fplData.UsedMargin, fplData.InitiallyUsedMargin/2); + } + + public static decimal GetCurrentlyUsedMargin(this IMarginTradingAccount account) { return account.GetAccountFpl().UsedMargin; } + + public static decimal GetInitiallyUsedMargin(this IMarginTradingAccount account) + { + return account.GetAccountFpl().InitiallyUsedMargin; + } public static decimal GetFreeMargin(this IMarginTradingAccount account) { @@ -162,30 +208,52 @@ public static decimal GetMarginInit(this IMarginTradingAccount account) public static decimal GetMarginAvailable(this IMarginTradingAccount account) { - return account.GetTotalCapital() - account.GetMarginInit(); + return account.GetTotalCapital() - Math.Max(account.GetMarginInit(), account.GetUsedMargin()); } - public static decimal GetMarginCallLevel(this IMarginTradingAccount account) + public static decimal GetFrozenMargin(this IMarginTradingAccount account) { - return account.GetAccountFpl().MarginCallLevel; + return account.GetAccountFpl().WithdrawalFrozenMargin; } - public static decimal GetStopOutLevel(this IMarginTradingAccount account) + public static decimal GetUnconfirmedMargin(this IMarginTradingAccount account) + { + return account.GetAccountFpl().UnconfirmedMargin; + } + + public static decimal GetMarginCall1Level(this IMarginTradingAccount account) + { + return account.GetAccountFpl().MarginCall1Level; + } + + public static decimal GetMarginCall2Level(this IMarginTradingAccount account) { - return account.GetAccountFpl().StopoutLevel; + return account.GetAccountFpl().MarginCall2Level; } + public static decimal GetStopOutLevel(this IMarginTradingAccount account) + { + return account.GetAccountFpl().StopOutLevel; + } + public static int GetOpenPositionsCount(this IMarginTradingAccount account) { return account.GetAccountFpl().OpenPositionsCount; } - public static void CacheNeedsToBeUpdated(this IMarginTradingAccount account) + public static int GetActiveOrdersCount(this IMarginTradingAccount account) { - if (account is MarginTradingAccount accountInstance) - { - accountInstance.AccountFpl.ActualHash++; - } + return account.GetAccountFpl().ActiveOrdersCount; + } + + public static void CacheNeedsToBeUpdated(this MarginTradingAccount account) + { + account.AccountFpl.ActualHash++; + } + + public static bool IsInLiquidation(this IMarginTradingAccount account) + { + return !string.IsNullOrEmpty(account.LiquidationOperationId); } } } diff --git a/src/MarginTrading.Backend.Core/IMarginTradingAccountStats.cs b/src/MarginTrading.Backend.Core/IMarginTradingAccountStats.cs index abab17b5a..68c4f7623 100644 --- a/src/MarginTrading.Backend.Core/IMarginTradingAccountStats.cs +++ b/src/MarginTrading.Backend.Core/IMarginTradingAccountStats.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Core +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core { public interface IMarginTradingAccountStats { diff --git a/src/MarginTrading.Backend.Core/IMigrationService.cs b/src/MarginTrading.Backend.Core/IMigrationService.cs index 673f03d22..f424983f2 100644 --- a/src/MarginTrading.Backend.Core/IMigrationService.cs +++ b/src/MarginTrading.Backend.Core/IMigrationService.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; namespace MarginTrading.Backend.Core { diff --git a/src/MarginTrading.Backend.Core/IOrder.cs b/src/MarginTrading.Backend.Core/IOrder.cs deleted file mode 100644 index dabad806f..000000000 --- a/src/MarginTrading.Backend.Core/IOrder.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System; -using MarginTrading.Backend.Core.MatchedOrders; -using MarginTrading.Backend.Core.MatchingEngines; - -namespace MarginTrading.Backend.Core -{ - public interface IOrder : IBaseOrder - { - long Code { get; set; } - string ClientId { get; } - string AccountId { get; } - string TradingConditionId { get; } - string AccountAssetId { get; } - - //Matching Engine ID used for open - string OpenOrderbookId { get; } - - //Matching Engine ID used for close - string CloseOrderbookId { get; } - - DateTime? OpenDate { get; } - DateTime? CloseDate { get; } - decimal? ExpectedOpenPrice { get; } - decimal OpenPrice { get; } - decimal ClosePrice { get; } - decimal? TakeProfit { get; } - decimal? StopLoss { get; } - decimal OpenCommission { get; } - decimal CloseCommission { get; } - decimal CommissionLot { get; } - decimal QuoteRate { get; } - int AssetAccuracy { get; } - DateTime? StartClosingDate { get; } - OrderStatus Status { get; } - OrderCloseReason CloseReason { get; } - OrderFillType FillType { get; } - OrderRejectReason RejectReason { get; } - string CloseRejectReasonText { get; } - string RejectReasonText { get; } - string Comment { get; } - MatchedOrderCollection MatchedCloseOrders { get; } - decimal SwapCommission { get; } - string MarginCalcInstrument { get; } - string EquivalentAsset { get; } - decimal OpenPriceEquivalent { get; } - decimal ClosePriceEquivalent { get; } - - #region Extenal orders matching - - string OpenExternalOrderId { get; } - - string OpenExternalProviderId { get; } - - string CloseExternalOrderId { get; } - - string CloseExternalProviderId { get; } - - MatchingEngineMode MatchingEngineMode { get; } - - string LegalEntity { get; set; } - - #endregion - } - - public class Order : IOrder - { - public string Id { get; set; } - public long Code { get; set; } - public string ClientId { get; set; } - public string AccountId { get; set; } - public string TradingConditionId { get; set; } - public string AccountAssetId { get; set; } - public string OpenOrderbookId { get; set; } - public string CloseOrderbookId { get; set; } - public string Instrument { get; set; } - public string MarginCalcInstrument { get; set; } - public DateTime CreateDate { get; set; } - public DateTime? OpenDate { get; set; } - public DateTime? CloseDate { get; set; } - public decimal? ExpectedOpenPrice { get; set; } - public decimal OpenPrice { get; set; } - public decimal ClosePrice { get; set; } - public decimal QuoteRate { get; set; } - public int AssetAccuracy { get; set; } - public decimal Volume { get; set; } - public decimal? TakeProfit { get; set; } - public decimal? StopLoss { get; set; } - public decimal OpenCommission { get; set; } - public decimal CloseCommission { get; set; } - public decimal CommissionLot { get; set; } - public decimal SwapCommission { get; set; } - public string EquivalentAsset { get; set; } - public decimal OpenPriceEquivalent { get; set; } - public decimal ClosePriceEquivalent { get; set; } - public string OpenExternalOrderId { get; set; } - public string OpenExternalProviderId { get; set; } - public string CloseExternalOrderId { get; set; } - public string CloseExternalProviderId { get; set; } - public DateTime? StartClosingDate { get; set; } - public OrderStatus Status { get; set; } - public OrderCloseReason CloseReason { get; set; } - public OrderFillType FillType { get; set; } - public OrderRejectReason RejectReason { get; set; } - public string CloseRejectReasonText { get; set; } - public string RejectReasonText { get; set; } - public string Comment { get; set; } - public MatchedOrderCollection MatchedOrders { get; set; } = new MatchedOrderCollection(); - public MatchedOrderCollection MatchedCloseOrders { get; set; } = new MatchedOrderCollection(); - public MatchingEngineMode MatchingEngineMode { get; set; } = MatchingEngineMode.MarketMaker; - public string LegalEntity { get; set; } - public FplData FplData { get; set; } = new FplData(); - } - - public enum OrderFillType - { - FillOrKill, - PartialFill - } - - public enum OrderDirection - { - Buy, - Sell - } - - public enum OrderStatus - { - WaitingForExecution, - Active, - Closed, - Rejected, - Closing - } - - public enum OrderCloseReason - { - None, - Close, - StopLoss, - TakeProfit, - StopOut, - Canceled, - CanceledBySystem, - CanceledByBroker, - ClosedByBroker, - } - - public enum OrderRejectReason - { - None, - NoLiquidity, - NotEnoughBalance, - LeadToStopOut, - AccountInvalidState, - InvalidExpectedOpenPrice, - InvalidVolume, - InvalidTakeProfit, - InvalidStoploss, - InvalidInstrument, - InvalidAccount, - TradingConditionError, - TechnicalError - } - - public enum OrderUpdateType - { - Place, - Cancel, - Activate, - Reject, - Closing, - Close, - ChangeOrderLimits, - } - - public static class OrderTypeExtension - { - public static OrderDirection GetOrderTypeToMatchInOrderBook(this OrderDirection orderType) - { - return orderType == OrderDirection.Buy ? OrderDirection.Sell : OrderDirection.Buy; - } - } -} diff --git a/src/MarginTrading.Backend.Core/IOrderHistory.cs b/src/MarginTrading.Backend.Core/IOrderHistory.cs deleted file mode 100644 index 373a5440e..000000000 --- a/src/MarginTrading.Backend.Core/IOrderHistory.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Collections.Generic; -using MarginTrading.Backend.Core.MatchedOrders; -using MarginTrading.Backend.Core.MatchingEngines; - -namespace MarginTrading.Backend.Core -{ - public interface IOrderHistory - { - string Id { get; } - long Code { get; } - string ClientId { get; } - string AccountId { get; } - string TradingConditionId { get; } - string AccountAssetId { get; } - string Instrument { get; } - OrderDirection Type { get; } - DateTime CreateDate { get; } - DateTime? OpenDate { get; } - DateTime? CloseDate { get; } - decimal? ExpectedOpenPrice { get; } - decimal OpenPrice { get; } - decimal ClosePrice { get; } - decimal QuoteRate { get; } - int AssetAccuracy { get; } - decimal Volume { get; } - decimal? TakeProfit { get; } - decimal? StopLoss { get; } - decimal CommissionLot { get; } - decimal OpenCommission { get; } - decimal CloseCommission { get; } - decimal SwapCommission { get; } - string EquivalentAsset { get; } - decimal OpenPriceEquivalent{ get; } - decimal ClosePriceEquivalent { get; } - DateTime? StartClosingDate { get; } - OrderStatus Status { get; } - OrderCloseReason CloseReason { get; } - OrderFillType FillType { get; } - OrderRejectReason RejectReason { get; } - string RejectReasonText { get; } - string Comment { get; } - List MatchedOrders { get; } - List MatchedCloseOrders { get; } - - decimal MatchedVolume { get; } - decimal MatchedCloseVolume { get; } - decimal Fpl { get; } - decimal PnL { get; } - decimal InterestRateSwap { get; } - decimal MarginInit { get; } - decimal MarginMaintenance { get; } - - OrderUpdateType OrderUpdateType { get; } - - string OpenExternalOrderId { get; } - string OpenExternalProviderId { get; } - string CloseExternalOrderId { get; } - string CloseExternalProviderId { get; } - - MatchingEngineMode MatchingEngineMode { get; } - string LegalEntity { get; set; } - } - - public class OrderHistory : IOrderHistory - { - public string Id { get; set; } - public long Code { get; set; } - public string ClientId { get; set; } - public string AccountId { get; set; } - public string TradingConditionId { get; set; } - public string AccountAssetId { get; set; } - public string Instrument { get; set; } - public OrderDirection Type { get; set; } - public DateTime CreateDate { get; set; } - public DateTime? OpenDate { get; set; } - public DateTime? CloseDate { get; set; } - public decimal? ExpectedOpenPrice { get; set; } - public decimal OpenPrice { get; set; } - public decimal ClosePrice { get; set; } - public decimal QuoteRate { get; set; } - public int AssetAccuracy { get; set; } - public decimal Volume { get; set; } - public decimal? TakeProfit { get; set; } - public decimal? StopLoss { get; set; } - public decimal CommissionLot { get; set; } - public decimal OpenCommission { get; set; } - public decimal CloseCommission { get; set; } - public decimal SwapCommission { get; set; } - public string EquivalentAsset { get; set; } - public decimal OpenPriceEquivalent{ get; set; } - public decimal ClosePriceEquivalent { get; set; } - public DateTime? StartClosingDate { get; set; } - public OrderStatus Status { get; set; } - public OrderCloseReason CloseReason { get; set; } - public OrderFillType FillType { get; set; } - public OrderRejectReason RejectReason { get; set; } - public string RejectReasonText { get; set; } - public string Comment { get; set; } - public List MatchedOrders { get; set; } = new List(); - public List MatchedCloseOrders { get; set; } = new List(); - public decimal MatchedVolume { get; set; } - public decimal MatchedCloseVolume { get; set; } - public decimal Fpl { get; set; } - public decimal PnL { get; set; } - public decimal InterestRateSwap { get; set; } - public decimal MarginInit { get; set; } - public decimal MarginMaintenance { get; set; } - public OrderUpdateType OrderUpdateType { get; set; } - public string OpenExternalOrderId { get; set; } - public string OpenExternalProviderId { get; set; } - public string CloseExternalOrderId { get; set; } - public string CloseExternalProviderId { get; set; } - public MatchingEngineMode MatchingEngineMode { get; set; } - public string LegalEntity { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/ITradingEngine.cs b/src/MarginTrading.Backend.Core/ITradingEngine.cs index f7dcd38dc..fd30e3ad0 100644 --- a/src/MarginTrading.Backend.Core/ITradingEngine.cs +++ b/src/MarginTrading.Backend.Core/ITradingEngine.cs @@ -1,13 +1,38 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using MarginTrading.Backend.Core.MatchingEngines; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; namespace MarginTrading.Backend.Core { public interface ITradingEngine { Task PlaceOrderAsync(Order order); - Task CloseActiveOrderAsync(string orderId, OrderCloseReason reason, string comment = null); - Order CancelPendingOrder(string orderId, OrderCloseReason reason, string comment = null); - void ChangeOrderLimits(string orderId, decimal? stopLoss, decimal? takeProfit, decimal? expectedOpenPrice); - bool PingLock(); + + Task<(PositionCloseResult result, Order order)> ClosePositionsAsync(PositionsCloseData data, bool + specialLiquidationEnabled); + + [ItemNotNull] + Task> ClosePositionsGroupAsync(string accountId, + string assetPairId, PositionDirection? direction, OriginatorType originator, string additionalInfo, string correlationId); + + Task<(PositionCloseResult, Order)[]> LiquidatePositionsUsingSpecialWorkflowAsync(IMatchingEngineBase me, string[] positionIds, + string correlationId, string additionalInfo, OriginatorType originator); + + Order CancelPendingOrder(string orderId, string additionalInfo, string correlationId, + string comment = null, OrderCancellationReason reason = OrderCancellationReason.None); + + Task ChangeOrderAsync(string orderId, decimal price, DateTime? validity, OriginatorType originator, + string additionalInfo, string correlationId, bool? forceOpen = null); + + bool ShouldOpenNewPosition(Order order); + + void ProcessExpiredOrders(DateTime operationIntervalEnd); } } diff --git a/src/MarginTrading.Backend.Core/LiquidationType.cs b/src/MarginTrading.Backend.Core/LiquidationType.cs new file mode 100644 index 000000000..f54c2904f --- /dev/null +++ b/src/MarginTrading.Backend.Core/LiquidationType.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core +{ + public enum LiquidationType + { + /// + /// Stop out caused liquidation + /// + Normal = 0, + /// + /// MCO caused liquidation + /// + Mco = 1, + /// + /// Liquidation is started by "Close All" API call + /// + Forced = 2, + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/LykkeConstants.cs b/src/MarginTrading.Backend.Core/LykkeConstants.cs index af38e9925..960b77950 100644 --- a/src/MarginTrading.Backend.Core/LykkeConstants.cs +++ b/src/MarginTrading.Backend.Core/LykkeConstants.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Core +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core { public static class LykkeConstants { @@ -7,6 +10,8 @@ public static class LykkeConstants public const string EthAssetId = "ETH"; public const string SolarAssetId = "SLR"; + public const string SymmetricAssetPair = "SymmetricAssetPair"; + public const decimal DefaultDemoBalance = 50000; public const decimal DefaultMarginCall = 0.8M; public const decimal DefaultStopOut = 0.95M; @@ -15,5 +20,10 @@ public static class LykkeConstants public const string MigrationsBlobContainer = "migrations"; public const string MaintenanceModeRoute = "maintenance"; + + public const string AscendingOrder = "ASC"; + public const string DescendingOrder = "DESC"; + + public const string LiquidationByCaAdditionalInfo = "{\"CreatedBy\":\"EOD\"}"; } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/MarginTrading.Backend.Core.csproj b/src/MarginTrading.Backend.Core/MarginTrading.Backend.Core.csproj index 852486133..860e4ec34 100644 --- a/src/MarginTrading.Backend.Core/MarginTrading.Backend.Core.csproj +++ b/src/MarginTrading.Backend.Core/MarginTrading.Backend.Core.csproj @@ -6,11 +6,18 @@ false false false - 1.0.1 + 1.16.29 + 7.3 + + + 1701;1702;1705;CA2007;0612;0618;1591 - - + + + + + diff --git a/src/MarginTrading.Backend.Core/MarginTradingConsts.cs b/src/MarginTrading.Backend.Core/MarginTradingConsts.cs index 1246922ca..0c73729c2 100644 --- a/src/MarginTrading.Backend.Core/MarginTradingConsts.cs +++ b/src/MarginTrading.Backend.Core/MarginTradingConsts.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Core +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core { public static class MarginTradingHelpers { @@ -9,7 +12,9 @@ public static class MarginTradingHelpers public static class MatchingEngineConstants { public const string Reject = "REJECT"; - public const string LykkeVuMm = "LYKKEVU_MM"; + public const string DefaultMm = "MM"; public const string LykkeCyStp = "LYKKECY_STP"; + public const string DefaultStp = "STP"; + public const string DefaultSpecialLiquidation = "SPECIAL_LIQUIDATION"; } } diff --git a/src/MarginTrading.Backend.Core/MarketMakerFeed/MarketMakerOrderCommand.cs b/src/MarginTrading.Backend.Core/MarketMakerFeed/MarketMakerOrderCommand.cs index 84827d983..db6696cd8 100644 --- a/src/MarginTrading.Backend.Core/MarketMakerFeed/MarketMakerOrderCommand.cs +++ b/src/MarginTrading.Backend.Core/MarketMakerFeed/MarketMakerOrderCommand.cs @@ -1,4 +1,8 @@ -using Newtonsoft.Json; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Core.Orders; +using Newtonsoft.Json; using Newtonsoft.Json.Converters; namespace MarginTrading.Backend.Core.MarketMakerFeed diff --git a/src/MarginTrading.Backend.Core/MarketMakerFeed/MarketMakerOrderCommandType.cs b/src/MarginTrading.Backend.Core/MarketMakerFeed/MarketMakerOrderCommandType.cs index 6ff039682..197b310d5 100644 --- a/src/MarginTrading.Backend.Core/MarketMakerFeed/MarketMakerOrderCommandType.cs +++ b/src/MarginTrading.Backend.Core/MarketMakerFeed/MarketMakerOrderCommandType.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Core.MarketMakerFeed +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.MarketMakerFeed { public enum MarketMakerOrderCommandType { diff --git a/src/MarginTrading.Backend.Core/MarketMakerFeed/MarketMakerOrderCommandsBatchMessage.cs b/src/MarginTrading.Backend.Core/MarketMakerFeed/MarketMakerOrderCommandsBatchMessage.cs index e9a299316..047973d6a 100644 --- a/src/MarginTrading.Backend.Core/MarketMakerFeed/MarketMakerOrderCommandsBatchMessage.cs +++ b/src/MarginTrading.Backend.Core/MarketMakerFeed/MarketMakerOrderCommandsBatchMessage.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; namespace MarginTrading.Backend.Core.MarketMakerFeed diff --git a/src/MarginTrading.Backend.Core/MatchedOrders/MatchedOrder.cs b/src/MarginTrading.Backend.Core/MatchedOrders/MatchedOrder.cs index e54bb0443..7f86567cc 100644 --- a/src/MarginTrading.Backend.Core/MatchedOrders/MatchedOrder.cs +++ b/src/MarginTrading.Backend.Core/MatchedOrders/MatchedOrder.cs @@ -1,8 +1,10 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Backend.Core.MatchedOrders { - public class MatchedOrder { public string OrderId { get; set; } @@ -10,21 +12,7 @@ public class MatchedOrder public decimal LimitOrderLeftToMatch { get; set; } public decimal Volume { get; set; } public decimal Price { get; set; } - public string ClientId { get; set; } public DateTime MatchedDate { get; set; } - - public static MatchedOrder Create(MatchedOrder src) - { - return new MatchedOrder - { - OrderId = src.OrderId, - MarketMakerId = src.MarketMakerId, - LimitOrderLeftToMatch = src.LimitOrderLeftToMatch, - Volume = src.Volume, - Price = src.Price, - ClientId = src.ClientId, - MatchedDate = src.MatchedDate - }; - } + public bool IsExternal { get; set; } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/MatchedOrders/MatchedOrderCollection.cs b/src/MarginTrading.Backend.Core/MatchedOrders/MatchedOrderCollection.cs index 86bad4a76..67875ba5e 100644 --- a/src/MarginTrading.Backend.Core/MatchedOrders/MatchedOrderCollection.cs +++ b/src/MarginTrading.Backend.Core/MatchedOrders/MatchedOrderCollection.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections; using System.Collections.Generic; using System.Linq; diff --git a/src/MarginTrading.Backend.Core/MatchedOrders/MatchedOrderCollectionConverter.cs b/src/MarginTrading.Backend.Core/MatchedOrders/MatchedOrderCollectionConverter.cs index 26d29c446..a7a451f2e 100644 --- a/src/MarginTrading.Backend.Core/MatchedOrders/MatchedOrderCollectionConverter.cs +++ b/src/MarginTrading.Backend.Core/MatchedOrders/MatchedOrderCollectionConverter.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using Newtonsoft.Json; diff --git a/src/MarginTrading.Backend.Core/MatchedOrders/MatchedOrderExtension.cs b/src/MarginTrading.Backend.Core/MatchedOrders/MatchedOrderExtension.cs index 0a58036c7..5ae9b5098 100644 --- a/src/MarginTrading.Backend.Core/MatchedOrders/MatchedOrderExtension.cs +++ b/src/MarginTrading.Backend.Core/MatchedOrders/MatchedOrderExtension.cs @@ -1,4 +1,9 @@ -namespace MarginTrading.Backend.Core.MatchedOrders +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Core.Orders; + +namespace MarginTrading.Backend.Core.MatchedOrders { public static class MatchedOrderExtension { diff --git a/src/MarginTrading.Backend.Core/MatchingEngines/IMarketMakerMatchingEngine.cs b/src/MarginTrading.Backend.Core/MatchingEngines/IMarketMakerMatchingEngine.cs index 1892c5613..e85611108 100644 --- a/src/MarginTrading.Backend.Core/MatchingEngines/IMarketMakerMatchingEngine.cs +++ b/src/MarginTrading.Backend.Core/MatchingEngines/IMarketMakerMatchingEngine.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Core.MatchingEngines +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.MatchingEngines { public interface IMarketMakerMatchingEngine : IMatchingEngineBase { diff --git a/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineBase.cs b/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineBase.cs index 47771bfa5..eeffe353a 100644 --- a/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineBase.cs +++ b/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineBase.cs @@ -1,6 +1,11 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; using MarginTrading.Backend.Core.MatchedOrders; using MarginTrading.Backend.Core.Orderbooks; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; namespace MarginTrading.Backend.Core.MatchingEngines { @@ -9,12 +14,13 @@ public interface IMatchingEngineBase string Id { get; } MatchingEngineMode Mode { get; } + + Task MatchOrderAsync(Order order, bool shouldOpenNewPosition, + OrderModality modality = OrderModality.Regular); - void MatchMarketOrderForOpen(Order order, Func orderProcessed); - - void MatchMarketOrderForClose(Order order, Func orderProcessed); + (string externalProviderId, decimal? price) GetBestPriceForOpen(string assetPairId, decimal volume); - decimal? GetPriceForClose(Order order); + decimal? GetPriceForClose(string assetPairId, decimal volume, string externalProviderId); OrderBook GetOrderBook(string instrument); } diff --git a/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineRepository.cs b/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineRepository.cs index 3a40641a0..0e7576e0e 100644 --- a/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineRepository.cs +++ b/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineRepository.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; namespace MarginTrading.Backend.Core.MatchingEngines { diff --git a/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineRoute.cs b/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineRoute.cs index 9deae937c..31fd50ef3 100644 --- a/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineRoute.cs +++ b/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineRoute.cs @@ -1,5 +1,9 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using System.Threading.Tasks; +using MarginTrading.Backend.Core.Orders; namespace MarginTrading.Backend.Core.MatchingEngines { @@ -47,12 +51,4 @@ public static MatchingEngineRoute Create(IMatchingEngineRoute src) }; } } - - public interface IMatchingEngineRoutesRepository - { - Task AddOrReplaceRouteAsync(IMatchingEngineRoute route); - Task DeleteRouteAsync(string id); - Task> GetAllRoutesAsync(); - Task GetRouteByIdAsync(string id); - } } diff --git a/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineRouter.cs b/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineRouter.cs index c46399596..4f56ac45b 100644 --- a/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineRouter.cs +++ b/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineRouter.cs @@ -1,9 +1,15 @@ -namespace MarginTrading.Backend.Core.MatchingEngines +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; + +namespace MarginTrading.Backend.Core.MatchingEngines { public interface IMatchingEngineRouter { - IMatchingEngineBase GetMatchingEngineForOpen(IOrder order); + IMatchingEngineBase GetMatchingEngineForExecution(Order order); - IMatchingEngineBase GetMatchingEngineForClose(IOrder order); + IMatchingEngineBase GetMatchingEngineForClose(string openMatchingEngineId); } } diff --git a/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineRoutesCacheService.cs b/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineRoutesCacheService.cs index c50ea439d..c67a2c71c 100644 --- a/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineRoutesCacheService.cs +++ b/src/MarginTrading.Backend.Core/MatchingEngines/IMatchingEngineRoutesCacheService.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Core.MatchingEngines +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.MatchingEngines { public interface IMatchingEngineRoutesCacheService { diff --git a/src/MarginTrading.Backend.Core/MatchingEngines/ISpecialLiquidationMatchingEngine.cs b/src/MarginTrading.Backend.Core/MatchingEngines/ISpecialLiquidationMatchingEngine.cs new file mode 100644 index 000000000..72b69fe68 --- /dev/null +++ b/src/MarginTrading.Backend.Core/MatchingEngines/ISpecialLiquidationMatchingEngine.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.MatchingEngines +{ + public interface ISpecialLiquidationMatchingEngine : IMatchingEngineBase + { + + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/MatchingEngines/IStpMatchingEngine.cs b/src/MarginTrading.Backend.Core/MatchingEngines/IStpMatchingEngine.cs index dc85671ae..9cc0cb81c 100644 --- a/src/MarginTrading.Backend.Core/MatchingEngines/IStpMatchingEngine.cs +++ b/src/MarginTrading.Backend.Core/MatchingEngines/IStpMatchingEngine.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Core.MatchingEngines +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.MatchingEngines { public interface IStpMatchingEngine : IMatchingEngineBase { diff --git a/src/MarginTrading.Backend.Core/MatchingEngines/MatchingEngineMode.cs b/src/MarginTrading.Backend.Core/MatchingEngines/MatchingEngineMode.cs index ab1eeda2e..3bd0b1818 100644 --- a/src/MarginTrading.Backend.Core/MatchingEngines/MatchingEngineMode.cs +++ b/src/MarginTrading.Backend.Core/MatchingEngines/MatchingEngineMode.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Core.MatchingEngines +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.MatchingEngines { public enum MatchingEngineMode { diff --git a/src/MarginTrading.Backend.Core/MatchingEngines/MatchingEngineRouteRisksCommand.cs b/src/MarginTrading.Backend.Core/MatchingEngines/MatchingEngineRouteRisksCommand.cs index 322906d78..6fff52b30 100644 --- a/src/MarginTrading.Backend.Core/MatchingEngines/MatchingEngineRouteRisksCommand.cs +++ b/src/MarginTrading.Backend.Core/MatchingEngines/MatchingEngineRouteRisksCommand.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Core.MatchingEngines +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.MatchingEngines { /// /// Routing direction set command for Trading Router from Risk Manager diff --git a/src/MarginTrading.Backend.Core/MatchingEngines/SetOrderModel.cs b/src/MarginTrading.Backend.Core/MatchingEngines/SetOrderModel.cs index bed696e7d..f4a6a92cf 100644 --- a/src/MarginTrading.Backend.Core/MatchingEngines/SetOrderModel.cs +++ b/src/MarginTrading.Backend.Core/MatchingEngines/SetOrderModel.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using JetBrains.Annotations; namespace MarginTrading.Backend.Core.MatchingEngines diff --git a/src/MarginTrading.Backend.Core/Messages/MtMessages.Designer.cs b/src/MarginTrading.Backend.Core/Messages/MtMessages.Designer.cs index 7f10c1f89..e0dc65cc2 100644 --- a/src/MarginTrading.Backend.Core/Messages/MtMessages.Designer.cs +++ b/src/MarginTrading.Backend.Core/Messages/MtMessages.Designer.cs @@ -8,47 +8,36 @@ // //------------------------------------------------------------------------------ -using System.Reflection; - namespace MarginTrading.Backend.Core.Messages { - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class MtMessages { - private static global::System.Resources.ResourceManager resourceMan; + private static System.Resources.ResourceManager resourceMan; - private static global::System.Globalization.CultureInfo resourceCulture; + private static System.Globalization.CultureInfo resourceCulture; + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal MtMessages() { } - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Resources.ResourceManager ResourceManager { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + public static System.Resources.ResourceManager ResourceManager { get { - if (object.ReferenceEquals(resourceMan, null)) { - var temp = new global::System.Resources.ResourceManager("MarginTrading.Backend.Core.Messages.MtMessages", typeof(MtMessages).GetTypeInfo().Assembly); + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("MarginTrading.Backend.Core.Messages.MtMessages", typeof(MtMessages).Assembly); resourceMan = temp; } return resourceMan; } } - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Globalization.CultureInfo Culture { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + public static System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -57,282 +46,207 @@ internal MtMessages() { } } - /// - /// Looks up a localized string similar to Can't find AccountAsset for tradingConditionId: {0}, baseAssetId: {1}, instrument: {2}. - /// public static string AccountAssetForTradingConditionNotFound { get { return ResourceManager.GetString("AccountAssetForTradingConditionNotFound", resourceCulture); } } - /// - /// Looks up a localized string similar to Can't get account by id = {0}. - /// - public static string AccountByIdNotFound { + public static string LeverageMaintanceIncorrect { get { - return ResourceManager.GetString("AccountByIdNotFound", resourceCulture); + return ResourceManager.GetString("LeverageMaintanceIncorrect", resourceCulture); } } - /// - /// Looks up a localized string similar to Can't get AccountGroup for tradingConditionId = {0}, assetId = {1}. - /// - public static string AccountGroupForTradingConditionNotFound { + public static string LeverageInitIncorrect { get { - return ResourceManager.GetString("AccountGroupForTradingConditionNotFound", resourceCulture); + return ResourceManager.GetString("LeverageInitIncorrect", resourceCulture); } } - /// - /// Looks up a localized string similar to Cannot get order with id = {0} and status {1}. - /// - public static string CantGetOrderWithStatus { + public static string AccountByIdNotFound { get { - return ResourceManager.GetString("CantGetOrderWithStatus", resourceCulture); + return ResourceManager.GetString("AccountByIdNotFound", resourceCulture); } } - /// - /// Looks up a localized string similar to Cannot remove order with id = {0} and status {1}. - /// - public static string CantRemoveOrderWithStatus { + public static string ClientIdNotFoundInCache { get { - return ResourceManager.GetString("CantRemoveOrderWithStatus", resourceCulture); + return ResourceManager.GetString("ClientIdNotFoundInCache", resourceCulture); } } - /// - /// Looks up a localized string similar to Client {0} doesn't have account {1} in the cache. - /// - public static string ClientAccountNotFoundInCache { + public static string AccountNotFoundInCache { get { - return ResourceManager.GetString("ClientAccountNotFoundInCache", resourceCulture); + return ResourceManager.GetString("AccountNotFoundInCache", resourceCulture); } } - /// - /// Looks up a localized string similar to Client {0} already has account {1} in the cache. - /// public static string ClientHasAccountInCache { get { return ResourceManager.GetString("ClientHasAccountInCache", resourceCulture); } } - /// - /// Looks up a localized string similar to Client {0} doesn't exist in the cache. - /// - public static string ClientIdNotFoundInCache { + public static string AccountGroupForTradingConditionNotFound { get { - return ResourceManager.GetString("ClientIdNotFoundInCache", resourceCulture); + return ResourceManager.GetString("AccountGroupForTradingConditionNotFound", resourceCulture); + } + } + + public static string NotificationIdNotFound { + get { + return ResourceManager.GetString("NotificationIdNotFound", resourceCulture); } } - /// - /// Looks up a localized string similar to Instrument {0} does not exist in cache. - /// public static string InstrumentNotFoundInCache { get { return ResourceManager.GetString("InstrumentNotFoundInCache", resourceCulture); } } - /// - /// Looks up a localized string similar to There is no instrumet with assets {0} and {1}. - /// public static string InstrumentWithAssetsNotFound { get { return ResourceManager.GetString("InstrumentWithAssetsNotFound", resourceCulture); } } - /// - /// Looks up a localized string similar to LeverageInit < 1 for tradingConditionId: {0}, baseAssetId: {1}, instrument: {2}. - /// - public static string LeverageInitIncorrect { + public static string Notifications_MarginCall { get { - return ResourceManager.GetString("LeverageInitIncorrect", resourceCulture); + return ResourceManager.GetString("Notifications_MarginCall", resourceCulture); } } - /// - /// Looks up a localized string similar to LeverageMaintenance < 1 for tradingConditionId: {0}, baseAssetId: {1}, instrument: {2}. - /// - public static string LeverageMaintanceIncorrect { + public static string OrderNotFound { get { - return ResourceManager.GetString("LeverageMaintanceIncorrect", resourceCulture); + return ResourceManager.GetString("OrderNotFound", resourceCulture); } } - /// - /// Looks up a localized string similar to Can't get notification Id for clientId = {0}. - /// - public static string NotificationIdNotFound { + public static string CantRemoveOrderWithStatus { get { - return ResourceManager.GetString("NotificationIdNotFound", resourceCulture); + return ResourceManager.GetString("CantRemoveOrderWithStatus", resourceCulture); } } - /// - /// Looks up a localized string similar to Margin used {0:P} ({1} Account). - /// - public static string Notifications_MarginCall { + public static string CantRemovePosition { get { - return ResourceManager.GetString("Notifications_MarginCall", resourceCulture); + return ResourceManager.GetString("CantRemovePosition", resourceCulture); } } - /// - /// Looks up a localized string similar to {0} {1} {2} closed{3}, PnL {4}. - /// - public static string Notifications_OrderClosed { + public static string CantGetOrderWithStatus { get { - return ResourceManager.GetString("Notifications_OrderClosed", resourceCulture); + return ResourceManager.GetString("CantGetOrderWithStatus", resourceCulture); } } - /// - /// Looks up a localized string similar to {0} {1} {2} opened, price {3}. - /// - public static string Notifications_OrderPlaced { + public static string CantGetPosition { get { - return ResourceManager.GetString("Notifications_OrderPlaced", resourceCulture); + return ResourceManager.GetString("CantGetPosition", resourceCulture); } } - /// - /// Looks up a localized string similar to Pending order for {0} {1} {2} canceled. - /// - public static string Notifications_PendingOrderCanceled { + public static string QuoteNotFound { get { - return ResourceManager.GetString("Notifications_PendingOrderCanceled", resourceCulture); + return ResourceManager.GetString("QuoteNotFound", resourceCulture); } } - /// - /// Looks up a localized string similar to Pending order for {0} {1} {2} placed, price {3}. - /// - public static string Notifications_PendingOrderPlaced { + public static string FxRateNotFound { get { - return ResourceManager.GetString("Notifications_PendingOrderPlaced", resourceCulture); + return ResourceManager.GetString("FxRateNotFound", resourceCulture); } } - /// - /// Looks up a localized string similar to Pending order triggered. {0} {1} {2} opened, price {3}. - /// - public static string Notifications_PendingOrderTriggered { + public static string Notifications_StopOutNotification { get { - return ResourceManager.GetString("Notifications_PendingOrderTriggered", resourceCulture); + return ResourceManager.GetString("Notifications_StopOutNotification", resourceCulture); } } - /// - /// Looks up a localized string similar to Stop out, {0} position(s) closed, PnL {1} {2}. - /// - public static string Notifications_StopOutNotification { + public static string Validation_PriceBelowAsk { get { - return ResourceManager.GetString("Notifications_StopOutNotification", resourceCulture); + return ResourceManager.GetString("Validation_PriceBelowAsk", resourceCulture); } } - /// - /// Looks up a localized string similar to with stop loss. - /// - public static string Notifications_WithStopLossPhrase { + public static string Validation_PriceAboveBid { get { - return ResourceManager.GetString("Notifications_WithStopLossPhrase", resourceCulture); + return ResourceManager.GetString("Validation_PriceAboveBid", resourceCulture); } } - /// - /// Looks up a localized string similar to with take profit. - /// - public static string Notifications_WithTakeProfitPhrase { + public static string Validation_NotEnoughBalance { get { - return ResourceManager.GetString("Notifications_WithTakeProfitPhrase", resourceCulture); + return ResourceManager.GetString("Validation_NotEnoughBalance", resourceCulture); } } - /// - /// Looks up a localized string similar to Cannot get order with id = {0}. - /// - public static string OrderNotFound { + public static string Validation_TakeProfitMustBeMore { get { - return ResourceManager.GetString("OrderNotFound", resourceCulture); + return ResourceManager.GetString("Validation_TakeProfitMustBeMore", resourceCulture); } } - /// - /// Looks up a localized string similar to There is no quote for instrument {0}. - /// - public static string QuoteNotFound { + public static string Validation_StopLossMustBeLess { get { - return ResourceManager.GetString("QuoteNotFound", resourceCulture); + return ResourceManager.GetString("Validation_StopLossMustBeLess", resourceCulture); } } - /// - /// Looks up a localized string similar to Not enough balance to open position. - /// - public static string Validation_NotEnoughBalance { + public static string Validation_TakeProfitMustBeLess { get { - return ResourceManager.GetString("Validation_NotEnoughBalance", resourceCulture); + return ResourceManager.GetString("Validation_TakeProfitMustBeLess", resourceCulture); } } - /// - /// Looks up a localized string similar to The specified price {0} is above current ask price {1}. - /// - public static string Validation_PriceAboveAsk { + public static string Validation_StopLossMustBeMore { get { - return ResourceManager.GetString("Validation_PriceAboveAsk", resourceCulture); + return ResourceManager.GetString("Validation_StopLossMustBeMore", resourceCulture); } } - /// - /// Looks up a localized string similar to The specified price {0} is below current bid price {1}. - /// - public static string Validation_PriceBelowBid { + public static string Notifications_PendingOrderPlaced { get { - return ResourceManager.GetString("Validation_PriceBelowBid", resourceCulture); + return ResourceManager.GetString("Notifications_PendingOrderPlaced", resourceCulture); } } - /// - /// Looks up a localized string similar to The specified stop loss {0} must be less than {1}. - /// - public static string Validation_StopLossMustBeLess { + public static string Notifications_PendingOrderTriggered { get { - return ResourceManager.GetString("Validation_StopLossMustBeLess", resourceCulture); + return ResourceManager.GetString("Notifications_PendingOrderTriggered", resourceCulture); } } - /// - /// Looks up a localized string similar to The specified stop loss {0} must be more than {1}. - /// - public static string Validation_StopLossMustBeMore { + public static string Notifications_OrderPlaced { get { - return ResourceManager.GetString("Validation_StopLossMustBeMore", resourceCulture); + return ResourceManager.GetString("Notifications_OrderPlaced", resourceCulture); } } - /// - /// Looks up a localized string similar to The specified take profit {0} must be less than {1}. - /// - public static string Validation_TakeProfitMustBeLess { + public static string Notifications_PendingOrderCanceled { get { - return ResourceManager.GetString("Validation_TakeProfitMustBeLess", resourceCulture); + return ResourceManager.GetString("Notifications_PendingOrderCanceled", resourceCulture); } } - /// - /// Looks up a localized string similar to The specified take profit {0} must be more than {1}. - /// - public static string Validation_TakeProfitMustBeMore { + public static string Notifications_WithStopLossPhrase { get { - return ResourceManager.GetString("Validation_TakeProfitMustBeMore", resourceCulture); + return ResourceManager.GetString("Notifications_WithStopLossPhrase", resourceCulture); + } + } + + public static string Notifications_WithTakeProfitPhrase { + get { + return ResourceManager.GetString("Notifications_WithTakeProfitPhrase", resourceCulture); + } + } + + public static string Notifications_OrderClosed { + get { + return ResourceManager.GetString("Notifications_OrderClosed", resourceCulture); } } } diff --git a/src/MarginTrading.Backend.Core/Messages/MtMessages.resx b/src/MarginTrading.Backend.Core/Messages/MtMessages.resx index 7383d6cfe..2b5cbb4cb 100644 --- a/src/MarginTrading.Backend.Core/Messages/MtMessages.resx +++ b/src/MarginTrading.Backend.Core/Messages/MtMessages.resx @@ -118,13 +118,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Can't find AccountAsset for tradingConditionId: {0}, baseAssetId: {1}, instrument: {2} + Can't find AccountAsset for tradingConditionId: {0}, instrument: {1} - LeverageMaintenance < 1 for tradingConditionId: {0}, baseAssetId: {1}, instrument: {2} + LeverageMaintenance < 1 for tradingConditionId: {0}, instrument: {1} - LeverageInit < 1 for tradingConditionId: {0}, baseAssetId: {1}, instrument: {2} + LeverageInit < 1 for tradingConditionId: {0}, instrument: {1} Can't get account by id = {0} @@ -132,8 +132,8 @@ Client {0} doesn't exist in the cache - - Client {0} doesn't have account {1} in the cache + + Account {0} does not exist in the cache Client {0} already has account {1} in the cache @@ -148,7 +148,7 @@ Instrument {0} does not exist in cache - There is no instrument with assets {0} and {1} + There is no instrument with assets {0} and {1} in legal entity {2} Margin used {0:P} ({1} Account) @@ -158,21 +158,30 @@ Cannot remove order with id = {0} and status {1} + + + Cannot remove position with id = {0} Cannot get order with id = {0} and status {1} + + Cannot get position with id = {0} + There is no quote for instrument {0} + + There is no fx rate for instrument {0} + Stop out, {0} position(s) closed, PnL {1} {2} - - The specified price {0} is above current ask price {1} + + The specified price {0} is below current ask price {1} - - The specified price {0} is below current bid price {1} + + The specified price {0} is above current bid price {1} Not enough balance to open position diff --git a/src/MarginTrading.Backend.Core/MtServiceLocator.cs b/src/MarginTrading.Backend.Core/MtServiceLocator.cs index 43b2a8784..f8e50df7b 100644 --- a/src/MarginTrading.Backend.Core/MtServiceLocator.cs +++ b/src/MarginTrading.Backend.Core/MtServiceLocator.cs @@ -1,11 +1,17 @@ -namespace MarginTrading.Backend.Core +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Core.Settings; + +namespace MarginTrading.Backend.Core { + //TODO: think about all this static mess public static class MtServiceLocator { public static IFplService FplService { get; set; } public static IAccountUpdateService AccountUpdateService { get; set; } public static IAccountsCacheService AccountsCacheService { get; set; } public static ICommissionService SwapCommissionService { get; set; } - public static IOvernightSwapService OvernightSwapService { get; set; } } } diff --git a/src/MarginTrading.Backend.Core/Notifications/ISlackNotificationsProducer.cs b/src/MarginTrading.Backend.Core/Notifications/ISlackNotificationsProducer.cs index a37af9b81..8b953e6cc 100644 --- a/src/MarginTrading.Backend.Core/Notifications/ISlackNotificationsProducer.cs +++ b/src/MarginTrading.Backend.Core/Notifications/ISlackNotificationsProducer.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; namespace MarginTrading.Backend.Core.Notifications { diff --git a/src/MarginTrading.Backend.Core/Notifications/NotificationType.cs b/src/MarginTrading.Backend.Core/Notifications/NotificationType.cs index ad2388394..2db860228 100644 --- a/src/MarginTrading.Backend.Core/Notifications/NotificationType.cs +++ b/src/MarginTrading.Backend.Core/Notifications/NotificationType.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Backend.Core.Notifications { diff --git a/src/MarginTrading.Backend.Core/OrderExtensions.cs b/src/MarginTrading.Backend.Core/OrderExtensions.cs deleted file mode 100644 index b50eba0be..000000000 --- a/src/MarginTrading.Backend.Core/OrderExtensions.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System; -using MarginTrading.Backend.Core.Messages; - -namespace MarginTrading.Backend.Core -{ - public static class OrderExtensions - { - public static bool IsSuitablePriceForPendingOrder(this IOrder order, decimal price) - { - return order.ExpectedOpenPrice.HasValue && (order.GetOrderType() == OrderDirection.Buy && price <= order.ExpectedOpenPrice - || order.GetOrderType() == OrderDirection.Sell && price >= order.ExpectedOpenPrice); - } - - public static bool IsStopLoss(this IOrder order) - { - return order.GetOrderType() == OrderDirection.Buy - ? order.StopLoss.HasValue && order.StopLoss.Value > 0 && order.ClosePrice <= order.StopLoss - : order.StopLoss.HasValue && order.StopLoss.Value > 0 && order.ClosePrice >= order.StopLoss; - } - - public static bool IsTakeProfit(this IOrder order) - { - return order.GetOrderType() == OrderDirection.Buy - ? order.TakeProfit.HasValue && order.TakeProfit > 0 && order.ClosePrice >= order.TakeProfit - : order.TakeProfit.HasValue && order.TakeProfit > 0 && order.ClosePrice <= order.TakeProfit; - } - - public static decimal GetTotalFpl(this IOrder order, decimal swaps) - { - return order.GetFpl() - order.GetOpenCommission() - order.GetCloseCommission() - swaps; - } - - public static decimal GetTotalFpl(this IOrder order) - { - return Math.Round(GetTotalFpl(order, order.GetSwaps()), order.CalculateFplData().AccountBaseAssetAccuracy); - } - - public static decimal GetMatchedVolume(this IOrder order) - { - return order.MatchedOrders.SummaryVolume; - } - - public static decimal GetMatchedCloseVolume(this IOrder order) - { - return order.MatchedCloseOrders.SummaryVolume; - } - - public static decimal GetRemainingCloseVolume(this IOrder order) - { - return order.GetMatchedVolume() - order.GetMatchedCloseVolume(); - } - - public static bool GetIsCloseFullfilled(this IOrder order) - { - return Math.Round(order.GetRemainingCloseVolume(), MarginTradingHelpers.VolumeAccuracy) == 0; - } - - private static FplData CalculateFplData(this IOrder order) - { - if (order is Order orderInstance) - { - if (orderInstance.FplData.ActualHash != orderInstance.FplData.CalculatedHash) - { - MtServiceLocator.FplService.UpdateOrderFpl(orderInstance, orderInstance.FplData); - } - - return orderInstance.FplData; - } - - var fplData = new FplData(); - MtServiceLocator.FplService.UpdateOrderFpl(order, fplData); - - return fplData; - } - - public static decimal GetFpl(this IOrder order) - { - return order.CalculateFplData().Fpl; - } - - public static decimal GetFplRate(this IOrder order) - { - return order.CalculateFplData().FplRate; - } - - public static decimal GetMarginRate(this IOrder order) - { - return order.CalculateFplData().MarginRate; - } - - public static decimal GetMarginMaintenance(this IOrder order) - { - return order.CalculateFplData().MarginMaintenance; - } - - public static decimal GetMarginInit(this IOrder order) - { - return order.CalculateFplData().MarginInit; - } - - public static void UpdateClosePrice(this IOrder order, decimal closePrice) - { - if (order is Order orderInstance) - { - orderInstance.ClosePrice = closePrice; - orderInstance.FplData.ActualHash++; - var account = MtServiceLocator.AccountsCacheService.Get(order.ClientId, order.AccountId); - account.CacheNeedsToBeUpdated(); - } - } - - public static void UpdatePendingOrderMargin(this IOrder order) - { - if (order is Order orderInstance) - { - orderInstance.FplData.ActualHash++; - } - } - - public static decimal GetSwaps(this IOrder order) - { - return MtServiceLocator.SwapCommissionService.GetSwaps(order); - } - - public static decimal GetOpenCommission(this IOrder order) - { - return order.CommissionLot == 0 ? 0 : Math.Abs(order.Volume) / order.CommissionLot * order.OpenCommission; - } - - public static decimal GetCloseCommission(this IOrder order) - { - return order.CommissionLot == 0 ? 0 : Math.Abs(order.GetMatchedCloseVolume()) / order.CommissionLot * order.CloseCommission; - } - - public static bool IsOpened(this IOrder order) - { - return order.Status == OrderStatus.Active - && order.OpenDate.HasValue - && order.OpenPrice > 0 - && order.MatchedOrders.SummaryVolume > 0; - } - - public static bool IsClosed(this IOrder order) - { - return order.Status == OrderStatus.Closed - && (order.CloseReason == OrderCloseReason.Close - || order.CloseReason == OrderCloseReason.ClosedByBroker - || order.CloseReason == OrderCloseReason.StopLoss - || order.CloseReason == OrderCloseReason.StopOut - || order.CloseReason == OrderCloseReason.TakeProfit) - && order.CloseDate.HasValue - && order.ClosePrice > 0 - && order.MatchedCloseOrders.SummaryVolume > 0; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orderbooks/ExternalOrderBook.cs b/src/MarginTrading.Backend.Core/Orderbooks/ExternalOrderBook.cs index 6bedda375..3cf2e8903 100644 --- a/src/MarginTrading.Backend.Core/Orderbooks/ExternalOrderBook.cs +++ b/src/MarginTrading.Backend.Core/Orderbooks/ExternalOrderBook.cs @@ -1,12 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MarginTrading.Backend.Core.Orders; namespace MarginTrading.Backend.Core.Orderbooks { public class ExternalOrderBook { - public ExternalOrderBook(string exchangeName, string assetPairId, DateTime timestamp, List asks, List bids) + public ExternalOrderBook(string exchangeName, string assetPairId, DateTime timestamp, VolumePrice[] asks, VolumePrice[] bids) { ExchangeName = exchangeName; AssetPairId = assetPairId; @@ -15,37 +17,56 @@ public ExternalOrderBook(string exchangeName, string assetPairId, DateTime times Bids = bids; } - public string ExchangeName { get; } + public string ExchangeName { get; private set; } public string AssetPairId { get; } public DateTime Timestamp { get; } - public List Asks { get; } + public VolumePrice[] Asks { get; set; } + + public VolumePrice[] Bids { get; set; } - public List Bids { get; } + public void ApplyExchangeIdFromSettings(string exchangeIdFromSettings) + { + if (!string.IsNullOrWhiteSpace(exchangeIdFromSettings)) + { + ExchangeName = exchangeIdFromSettings; + } + } + + public InstrumentBidAskPair GetBestPrice() + { + return new InstrumentBidAskPair + { + Bid = Bids[0].Price, + Ask = Asks[0].Price, + BidFirstLevelVolume = Bids[0].Volume, + AskFirstLevelVolume = Asks[0].Volume, + Date = Timestamp, + Instrument = AssetPairId + }; + } public decimal? GetMatchedPrice(decimal volumeToMatch, OrderDirection orderTypeToMatch) { + volumeToMatch = Math.Abs(volumeToMatch); + if (volumeToMatch == 0) return null; var source = orderTypeToMatch == OrderDirection.Buy ? Asks : Bids; - var leftVolumeToMatch = Math.Abs(volumeToMatch); - - var matchedVolumePrices = new List(); + var leftVolumeToMatch = volumeToMatch; + decimal matched = 0; + foreach (var priceLevel in source) { var matchedVolume = Math.Min(priceLevel.Volume, leftVolumeToMatch); - matchedVolumePrices.Add(new VolumePrice - { - Price = priceLevel.Price, - Volume = matchedVolume - }); - + matched += priceLevel.Price * matchedVolume; + leftVolumeToMatch = Math.Round(leftVolumeToMatch - matchedVolume, MarginTradingHelpers.VolumeAccuracy); if (leftVolumeToMatch <= 0) @@ -56,7 +77,7 @@ public ExternalOrderBook(string exchangeName, string assetPairId, DateTime times if (leftVolumeToMatch > 0) return null; - return matchedVolumePrices.Sum(x => x.Price * Math.Abs(x.Volume)) / Math.Abs(volumeToMatch); + return matched / volumeToMatch; } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orderbooks/OrderBook.cs b/src/MarginTrading.Backend.Core/Orderbooks/OrderBook.cs index 2295ff5b6..703e84bee 100644 --- a/src/MarginTrading.Backend.Core/Orderbooks/OrderBook.cs +++ b/src/MarginTrading.Backend.Core/Orderbooks/OrderBook.cs @@ -1,7 +1,12 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Linq; using MarginTrading.Backend.Core.MatchedOrders; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; namespace MarginTrading.Backend.Core.Orderbooks { @@ -74,7 +79,7 @@ private void FillOrders(SortedDictionary> dst, } } - public IEnumerable Match(Order order, OrderDirection orderTypeToMatch, decimal volumeToMatch, int maxMarketMakerLimitOrderAge) + public IEnumerable Match(OrderDirection orderTypeToMatch, decimal volumeToMatch, int maxMarketMakerLimitOrderAge) { if (volumeToMatch == 0) yield break; @@ -101,8 +106,7 @@ public IEnumerable Match(Order order, OrderDirection orderTypeToMa MarginTradingHelpers.VolumeAccuracy), Volume = matchedVolume, MatchedDate = DateTime.UtcNow, - Price = pair.Key, - ClientId = limitOrder.MarketMakerId + Price = pair.Key }; volumeToMatch = Math.Round(volumeToMatch - matchedVolume, MarginTradingHelpers.VolumeAccuracy); @@ -111,7 +115,7 @@ public IEnumerable Match(Order order, OrderDirection orderTypeToMa } } - public void Update(Order order, OrderDirection orderTypeToMatch, IEnumerable matchedOrders) + public void Update(OrderDirection orderTypeToMatch, IEnumerable matchedOrders) { var source = orderTypeToMatch == OrderDirection.Buy ? Buy : Sell; foreach (var matchedOrder in matchedOrders) @@ -134,7 +138,7 @@ public void Update(Order order, OrderDirection orderTypeToMatch, IEnumerable()); @@ -174,11 +178,31 @@ private void UpdateBestPrice() var newBestPrice = new InstrumentBidAskPair { Instrument = Instrument, - Ask = Sell.Any() ? Sell.First().Key : BestPrice?.Ask ?? 0, - Bid = Buy.Any() ? Buy.First().Key : BestPrice?.Bid ?? 0, Date = DateTime.UtcNow }; + if (Sell.Any()) + { + var fl = Sell.First(); + newBestPrice.Ask = fl.Key; + newBestPrice.AskFirstLevelVolume = fl.Value.Sum(o => Math.Abs(o.Volume)); + } + else + { + newBestPrice.Ask = BestPrice?.Ask ?? 0; + } + + if (Buy.Any()) + { + var fl = Buy.First(); + newBestPrice.Bid = fl.Key; + newBestPrice.BidFirstLevelVolume = fl.Value.Sum(o => Math.Abs(o.Volume)); + } + else + { + newBestPrice.Bid = BestPrice?.Bid ?? 0; + } + if (newBestPrice.Ask > 0 && newBestPrice.Bid > 0) BestPrice = newBestPrice; } diff --git a/src/MarginTrading.Backend.Core/Orderbooks/OrderBookExt.cs b/src/MarginTrading.Backend.Core/Orderbooks/OrderBookExt.cs index f36bbd2ed..519adec16 100644 --- a/src/MarginTrading.Backend.Core/Orderbooks/OrderBookExt.cs +++ b/src/MarginTrading.Backend.Core/Orderbooks/OrderBookExt.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using System.Linq; namespace MarginTrading.Backend.Core.Orderbooks diff --git a/src/MarginTrading.Backend.Core/Orderbooks/OrderBookLevel.cs b/src/MarginTrading.Backend.Core/Orderbooks/OrderBookLevel.cs index 2dc46daea..c518d59a9 100644 --- a/src/MarginTrading.Backend.Core/Orderbooks/OrderBookLevel.cs +++ b/src/MarginTrading.Backend.Core/Orderbooks/OrderBookLevel.cs @@ -1,4 +1,9 @@ -namespace MarginTrading.Backend.Core.Orderbooks +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Core.Orders; + +namespace MarginTrading.Backend.Core.Orderbooks { public class OrderBookLevel { @@ -11,7 +16,7 @@ public static OrderBookLevel Create(LimitOrder order) { return new OrderBookLevel { - Direction = order.GetOrderType(), + Direction = order.GetOrderDirection(), Instrument = order.Instrument, Volume = order.Volume, Price = order.Price @@ -33,7 +38,7 @@ public static OrderBookLevel CreateDeleted(LimitOrder order) { return new OrderBookLevel { - Direction = order.GetOrderType(), + Direction = order.GetOrderDirection(), Instrument = order.Instrument, Volume = 0, Price = order.Price diff --git a/src/MarginTrading.Backend.Core/Orderbooks/OrderBookList.cs b/src/MarginTrading.Backend.Core/Orderbooks/OrderBookList.cs index bdae56067..916a752fb 100644 --- a/src/MarginTrading.Backend.Core/Orderbooks/OrderBookList.cs +++ b/src/MarginTrading.Backend.Core/Orderbooks/OrderBookList.cs @@ -1,8 +1,13 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using System.Linq; using MarginTrading.Backend.Core.Helpers; using MarginTrading.Backend.Core.MatchedOrders; +using MarginTrading.Backend.Core.Orders; using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Core.Trading; namespace MarginTrading.Backend.Core.Orderbooks { @@ -14,9 +19,9 @@ namespace MarginTrading.Backend.Core.Orderbooks /// public class OrderBookList { - private readonly MarginSettings _marginSettings; + private readonly MarginTradingSettings _marginSettings; - public OrderBookList(MarginSettings marginSettings) + public OrderBookList(MarginTradingSettings marginSettings) { _marginSettings = marginSettings; } @@ -33,20 +38,20 @@ public void Init(Dictionary orderBook) _orderBooks = orderBook ?? new Dictionary(); } - public MatchedOrderCollection Match(Order order, OrderDirection orderTypeToMatch, decimal volumeToMatch) + public MatchedOrderCollection Match(string assetPairId, OrderDirection orderTypeToMatch, decimal volumeToMatch) { - if (!_orderBooks.ContainsKey(order.Instrument)) + if (!_orderBooks.ContainsKey(assetPairId)) return new MatchedOrderCollection(); - return new MatchedOrderCollection(_orderBooks[order.Instrument] - .Match(order, orderTypeToMatch, volumeToMatch, _marginSettings.MaxMarketMakerLimitOrderAge)); + return new MatchedOrderCollection(_orderBooks[assetPairId] + .Match(orderTypeToMatch, volumeToMatch, _marginSettings.MaxMarketMakerLimitOrderAge)); } - public void Update(Order order, OrderDirection orderTypeToMatch, IEnumerable matchedOrders) + public void Update(string assetPairId, OrderDirection orderTypeToMatch, IEnumerable matchedOrders) { - if (_orderBooks.TryGetValue(order.Instrument, out var orderBook)) + if (_orderBooks.TryGetValue(assetPairId, out var orderBook)) { - orderBook.Update(order, orderTypeToMatch, matchedOrders); + orderBook.Update(orderTypeToMatch, matchedOrders); } } diff --git a/src/MarginTrading.Backend.Core/Orderbooks/ReverseComparer.cs b/src/MarginTrading.Backend.Core/Orderbooks/ReverseComparer.cs index ad31661eb..1ebefb180 100644 --- a/src/MarginTrading.Backend.Core/Orderbooks/ReverseComparer.cs +++ b/src/MarginTrading.Backend.Core/Orderbooks/ReverseComparer.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; namespace MarginTrading.Backend.Core.Orderbooks { diff --git a/src/MarginTrading.Backend.Core/Orderbooks/VolumePrice.cs b/src/MarginTrading.Backend.Core/Orderbooks/VolumePrice.cs index 4c1119d0d..193893679 100644 --- a/src/MarginTrading.Backend.Core/Orderbooks/VolumePrice.cs +++ b/src/MarginTrading.Backend.Core/Orderbooks/VolumePrice.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Core.Orderbooks +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Orderbooks { public class VolumePrice { diff --git a/src/MarginTrading.Backend.Core/Orders/BaseOrder.cs b/src/MarginTrading.Backend.Core/Orders/BaseOrder.cs new file mode 100644 index 000000000..50dc657d3 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/BaseOrder.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MarginTrading.Backend.Core.MatchedOrders; + +namespace MarginTrading.Backend.Core.Orders +{ + public class BaseOrder : IBaseOrder + { + public string Id { get; set; } = Guid.NewGuid().ToString("N"); + public string Instrument { get; set; } + public decimal Volume { get; set; } + public DateTime CreateDate { get; set; } = DateTime.UtcNow; + public MatchedOrderCollection MatchedOrders { get; set; } = new MatchedOrderCollection(); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/FxToAssetPairDirection.cs b/src/MarginTrading.Backend.Core/Orders/FxToAssetPairDirection.cs new file mode 100644 index 000000000..eedfe4ce7 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/FxToAssetPairDirection.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Orders +{ + public enum FxToAssetPairDirection + { + /// + /// AssetPair is {BaseId, QuoteId} and FxAssetPair is {QuoteId, AccountAssetId} + /// + Straight, + + /// + /// AssetPair is {BaseId, QuoteId} and FxAssetPair is {AccountAssetId, QuoteId} + /// + Reverse, + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/IBaseOrder.cs b/src/MarginTrading.Backend.Core/Orders/IBaseOrder.cs new file mode 100644 index 000000000..5ea8c9d33 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/IBaseOrder.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MarginTrading.Backend.Core.MatchedOrders; + +namespace MarginTrading.Backend.Core.Orders +{ + public interface IBaseOrder + { + string Id { get; } + string Instrument { get; } + decimal Volume { get; } + DateTime CreateDate { get; } + MatchedOrderCollection MatchedOrders { get; set; } + } +} diff --git a/src/MarginTrading.Backend.Core/Orders/IOrderHistory.cs b/src/MarginTrading.Backend.Core/Orders/IOrderHistory.cs new file mode 100644 index 000000000..bc4fd5e5f --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/IOrderHistory.cs @@ -0,0 +1,236 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using MarginTrading.Backend.Core.MatchedOrders; +using MarginTrading.Backend.Core.MatchingEngines; + +namespace MarginTrading.Backend.Core.Orders +{ + public interface IOrderHistory + { + /// + /// Order id + /// + string Id { get; } + + /// + /// Account id + /// + string AccountId { get; } + + /// + /// Instrument id (e.g."BTCUSD", where BTC - base asset unit, USD - quoting unit) + /// + string AssetPairId { get; } + + /// + /// Parent order id. Filled if it's a related order. + /// + [CanBeNull] + string ParentOrderId { get; } + + /// + /// Parent position id. Filled if it's a related order. + /// + [CanBeNull] + string PositionId { get; } + + /// + /// The order direction (Buy or Sell) + /// + OrderDirection Direction { get; } + + /// + /// The order type (Market, Limit, Stop, TakeProfit, StopLoss or TrailingStop) + /// + OrderType Type { get; } + + /// + /// The order status (Active, Inactive, Executed, Canceled, Rejected or Expired) + /// + OrderStatus Status { get; } + + /// + /// Order fill type + /// + OrderFillType FillType { get; } + + /// + /// Who created the order (Investor, System or OnBehalf) + /// + OriginatorType Originator { get; } + + /// + /// Who cancelled/rejected the order (Investor, System or OnBehalf) + /// + OriginatorType? CancellationOriginator { get; } + + /// + /// Order volume in base asset units. Not filled for related orders (TakeProfit, StopLoss or TrailingStop). + /// + decimal Volume { get; } + + /// + /// Expected open price (in quoting asset units per one base unit). Not filled for Market orders. + /// + decimal? ExpectedOpenPrice { get; } + + /// + /// Execution open price (in quoting asset units per one base unit). Filled for executed orders only. + /// + decimal? ExecutionPrice { get; } + + /// + /// Current FxRate + /// + decimal FxRate { get; } + + /// + /// FX asset pair id + /// + string FxAssetPairId { get; } + + /// + /// Shows if account asset id is directly related on asset pair quote asset. + /// I.e. AssetPair is {BaseId, QuoteId} and FxAssetPair is {QuoteId, AccountAssetId} => Straight + /// If AssetPair is {BaseId, QuoteId} and FxAssetPair is {AccountAssetId, QuoteId} => Reverse + /// + FxToAssetPairDirection FxToAssetPairDirection { get; } + + /// + /// Force open separate position for the order, ignoring existing ones + /// + bool ForceOpen { get; } + + /// + /// Till validity time + /// + DateTime? ValidityTime { get; } + + /// + /// Creation date and time + /// + DateTime CreatedTimestamp { get; } + + /// + /// Last modification date and time + /// + DateTime ModifiedTimestamp { get; } + +////-------------- + + /// + /// Digit order code + /// + long Code { get; } + + /// + /// Date when order was activated + /// + DateTime? ActivatedTimestamp { get; } + + /// + /// Date when order started execution + /// + DateTime? ExecutionStartedTimestamp { get; } + + /// + /// Date when order was executed + /// + DateTime? ExecutedTimestamp { get; } + + /// + /// Date when order was canceled + /// + DateTime? CanceledTimestamp { get; } + + /// + /// Date when order was rejected + /// + DateTime? Rejected { get; } + + /// + /// Trading conditions ID + /// + string TradingConditionId { get; } + + /// + /// Account base asset ID + /// + string AccountAssetId { get; } + + /// Asset for representation of equivalent price + /// + string EquivalentAsset { get; } + + /// + /// Rate for calculation of equivalent price + /// + decimal EquivalentRate { get; } + + /// + /// Reject reason + /// + OrderRejectReason RejectReason { get; } + + /// + /// Human-readable reject reason + /// + string RejectReasonText { get; } + + /// + /// Additional comment + /// + string Comment { get; } + + /// + /// ID of exernal order (for STP mode) + /// + string ExternalOrderId { get; } + + /// + /// ID of external LP (for STP mode) + /// + string ExternalProviderId { get; } + + /// + /// Matching engine ID + /// + string MatchingEngineId { get; } + + /// + /// Legal Entity ID + /// + string LegalEntity { get; } + + /// + /// Matched orders for execution + /// + MatchedOrderCollection MatchedOrders { get; } + + /// + /// Related orders + /// + List RelatedOrderInfos { get; } + + OrderUpdateType UpdateType { get; } + + string AdditionalInfo { get; } + + /// + /// The correlation identifier. + /// In every operation that results in the creation of a new message the correlationId should be copied from + /// the inbound message to the outbound message. This facilitates tracking of an operation through the system. + /// If there is no inbound identifier then one should be created eg. on the service layer boundary (API). + /// + string CorrelationId { get; } + + /// + /// Number of pending order retries passed + /// + int PendingOrderRetriesCount { get; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/IPositionHistory.cs b/src/MarginTrading.Backend.Core/Orders/IPositionHistory.cs new file mode 100644 index 000000000..8535ad708 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/IPositionHistory.cs @@ -0,0 +1,57 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; + +namespace MarginTrading.Backend.Core.Orders +{ + public interface IPositionHistory + { + string Id { get; } + string DealId { get; } + long Code { get; } + string AssetPairId { get; } + PositionDirection Direction { get; } + decimal Volume { get; } + string AccountId { get; } + string TradingConditionId { get; } + string AccountAssetId { get; } + decimal? ExpectedOpenPrice { get; } + string OpenMatchingEngineId { get; } + DateTime OpenDate { get; } + string OpenTradeId { get; } + OrderType OpenOrderType { get; } + decimal OpenOrderVolume { get; } + decimal OpenPrice { get; } + decimal OpenFxPrice { get; } + string EquivalentAsset { get; } + decimal OpenPriceEquivalent { get; } + List RelatedOrders { get; } + string LegalEntity { get; } + OriginatorType OpenOriginator { get; } + string ExternalProviderId { get; } + decimal SwapCommissionRate { get; } + decimal OpenCommissionRate { get; } + decimal CloseCommissionRate { get; } + decimal CommissionLot { get; } + string CloseMatchingEngineId { get; } + decimal ClosePrice { get; } + decimal CloseFxPrice { get; } + decimal ClosePriceEquivalent { get; } + DateTime? StartClosingDate { get; } + DateTime? CloseDate { get; } + OriginatorType? CloseOriginator { get; } + OrderCloseReason CloseReason { get; } + string CloseComment { get; } + List CloseTrades { get; } + string FxAssetPairId { get; } + FxToAssetPairDirection FxToAssetPairDirection { get; } + DateTime? LastModified { get; } + decimal TotalPnL { get; } + decimal ChargedPnl { get; } + string AdditionalInfo { get; } + PositionHistoryType HistoryType { get; } + DateTime HistoryTimestamp { get; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/OrderCancellationReason.cs b/src/MarginTrading.Backend.Core/Orders/OrderCancellationReason.cs new file mode 100644 index 000000000..0faa471c1 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/OrderCancellationReason.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace MarginTrading.Backend.Core.Orders +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum OrderCancellationReason + { + None, + BaseOrderCancelled, + ParentPositionClosed, + ConnectedOrderExecuted, + CorporateAction, + InstrumentInvalidated, + AccountInactivated, + Expired + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/OrderCloseReason.cs b/src/MarginTrading.Backend.Core/Orders/OrderCloseReason.cs new file mode 100644 index 000000000..f1b2a4f54 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/OrderCloseReason.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace MarginTrading.Backend.Core.Orders +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum OrderCloseReason + { + + None, + Close, + StopLoss, + TakeProfit, + StopOut, + Canceled, + CanceledBySystem, + CanceledByBroker, + ClosedByBroker, + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/OrderCommand.cs b/src/MarginTrading.Backend.Core/Orders/OrderCommand.cs new file mode 100644 index 000000000..0b05642b3 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/OrderCommand.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Orders +{ + public enum OrderCommand + { + MakeInactive = 0, + Activate = 1, + StartExecution = 2, + FinishExecution = 3, + Reject = 4, + Cancel = 5, + Expire = 6, + CancelExecution = 7, + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/OrderDirection.cs b/src/MarginTrading.Backend.Core/Orders/OrderDirection.cs new file mode 100644 index 000000000..e8e8b56c4 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/OrderDirection.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Orders +{ + public enum OrderDirection + { + Buy, + Sell + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/OrderFillType.cs b/src/MarginTrading.Backend.Core/Orders/OrderFillType.cs new file mode 100644 index 000000000..c44af65fc --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/OrderFillType.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Orders +{ + public enum OrderFillType + { + FillOrKill, + PartialFill + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/OrderHistory.cs b/src/MarginTrading.Backend.Core/Orders/OrderHistory.cs new file mode 100644 index 000000000..1ed6bb923 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/OrderHistory.cs @@ -0,0 +1,109 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using MarginTrading.Backend.Core.MatchedOrders; +using MarginTrading.Backend.Core.MatchingEngines; + +namespace MarginTrading.Backend.Core.Orders +{ + public class OrderHistory : IOrderHistory + { + public string Id { get; set; } + public string AccountId { get; set; } + public string AssetPairId { get; set; } + public string ParentOrderId { get; set; } + public string PositionId { get; set; } + public OrderDirection Direction { get; set; } + public OrderType Type { get; set; } + public OrderStatus Status { get; set; } + public OrderFillType FillType { get; set; } + public OriginatorType Originator { get; set; } + public OriginatorType? CancellationOriginator { get; set; } + public decimal Volume { get; set; } + public decimal? ExpectedOpenPrice { get; set; } + public decimal? ExecutionPrice { get; set; } + public decimal FxRate { get; set; } + public string FxAssetPairId { get; set; } + public FxToAssetPairDirection FxToAssetPairDirection { get; set; } + public bool ForceOpen { get; set; } + public DateTime? ValidityTime { get; set; } + public DateTime CreatedTimestamp { get; set; } + public DateTime ModifiedTimestamp { get; set; } + public long Code { get; set; } + public DateTime? ActivatedTimestamp { get; set; } + public DateTime? ExecutionStartedTimestamp { get; set; } + public DateTime? ExecutedTimestamp { get; set; } + public DateTime? CanceledTimestamp { get; set; } + public DateTime? Rejected { get; set; } + public string TradingConditionId { get; set; } + public string AccountAssetId { get; set; } + public string EquivalentAsset { get; set; } + public decimal EquivalentRate { get; set; } + public OrderRejectReason RejectReason { get; set; } + public string RejectReasonText { get; set; } + public string Comment { get; set; } + public string ExternalOrderId { get; set; } + public string ExternalProviderId { get; set; } + public string MatchingEngineId { get; set; } + public string LegalEntity { get; set; } + public MatchedOrderCollection MatchedOrders { get; set; } + public List RelatedOrderInfos { get; set; } + public OrderUpdateType UpdateType { get; set; } + public string AdditionalInfo { get; set; } + public string CorrelationId { get; set; } + public int PendingOrderRetriesCount { get; set; } + + public static OrderHistory Create(IOrderHistory orderHistory) + { + return new OrderHistory + { + Id = orderHistory.Id, + AccountId = orderHistory.AccountId, + AssetPairId = orderHistory.AssetPairId, + ParentOrderId = orderHistory.ParentOrderId, + PositionId = orderHistory.PositionId, + Direction = orderHistory.Direction, + Type = orderHistory.Type, + Status = orderHistory.Status, + FillType = orderHistory.FillType, + Originator = orderHistory.Originator, + CancellationOriginator = orderHistory.CancellationOriginator, + Volume =orderHistory.Volume, + ExpectedOpenPrice = orderHistory.ExpectedOpenPrice, + ExecutionPrice = orderHistory.ExecutionPrice, + FxRate = orderHistory.FxRate, + FxAssetPairId = orderHistory.FxAssetPairId, + FxToAssetPairDirection = orderHistory.FxToAssetPairDirection, + ForceOpen = orderHistory.ForceOpen, + ValidityTime = orderHistory.ValidityTime, + CreatedTimestamp = orderHistory.CreatedTimestamp, + ModifiedTimestamp = orderHistory.ModifiedTimestamp, + Code = orderHistory.Code, + ActivatedTimestamp = orderHistory.ActivatedTimestamp, + ExecutionStartedTimestamp = orderHistory.ExecutionStartedTimestamp, + ExecutedTimestamp = orderHistory.ExecutedTimestamp, + CanceledTimestamp = orderHistory.CanceledTimestamp, + Rejected = orderHistory.Rejected, + TradingConditionId = orderHistory.TradingConditionId, + AccountAssetId = orderHistory.AccountAssetId, + EquivalentAsset = orderHistory.EquivalentAsset, + EquivalentRate = orderHistory.EquivalentRate, + RejectReason = orderHistory.RejectReason, + RejectReasonText = orderHistory.RejectReasonText, + Comment = orderHistory.Comment, + ExternalOrderId = orderHistory.ExternalOrderId, + ExternalProviderId = orderHistory.ExternalProviderId, + MatchingEngineId = orderHistory.MatchingEngineId, + LegalEntity = orderHistory.LegalEntity, + MatchedOrders = orderHistory.MatchedOrders, + RelatedOrderInfos = orderHistory.RelatedOrderInfos, + UpdateType = orderHistory.UpdateType, + AdditionalInfo = orderHistory.AdditionalInfo, + CorrelationId = orderHistory.CorrelationId, + PendingOrderRetriesCount = orderHistory.PendingOrderRetriesCount, + }; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/OrderInitialParameters.cs b/src/MarginTrading.Backend.Core/Orders/OrderInitialParameters.cs new file mode 100644 index 000000000..e6164562f --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/OrderInitialParameters.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Core.Orders +{ + public class OrderInitialParameters + { + public string Id { get; set; } + + public long Code { get; set; } + + public DateTime Now { get; set; } + + public decimal EquivalentPrice { get; set; } + + public decimal FxPrice { get; set; } + + public string FxAssetPairId { get; set; } + + public FxToAssetPairDirection FxToAssetPairDirection { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/OrderModality.cs b/src/MarginTrading.Backend.Core/Orders/OrderModality.cs new file mode 100644 index 000000000..d26b62501 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/OrderModality.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace MarginTrading.Backend.Core.Orders +{ + [JsonConverter(typeof (StringEnumConverter))] + public enum OrderModality + { + [EnumMember(Value = "Unspecified")] Unspecified = 0, + [EnumMember(Value = "Liquidation")] Liquidation = 76, // 0x4C + [EnumMember(Value = "Regular")] Regular = 82, // 0x52 + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/OrderRejectReason.cs b/src/MarginTrading.Backend.Core/Orders/OrderRejectReason.cs new file mode 100644 index 000000000..5904cf421 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/OrderRejectReason.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Orders +{ + public enum OrderRejectReason + { + None, + NoLiquidity, + NotEnoughBalance, + LeadToStopOut, + AccountInvalidState, + InvalidExpectedOpenPrice, + InvalidVolume, + InvalidTakeProfit, + InvalidStoploss, + InvalidInstrument, + InvalidAccount, + InvalidParent, + TradingConditionError, + InvalidValidity, + TechnicalError, + ParentPositionDoesNotExist, + ParentPositionIsNotActive, + ShortPositionsDisabled, + MaxPositionLimit, + MinOrderSizeLimit, + MaxOrderSizeLimit, + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/OrderStatus.cs b/src/MarginTrading.Backend.Core/Orders/OrderStatus.cs new file mode 100644 index 000000000..c896757c5 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/OrderStatus.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Orders +{ + public enum OrderStatus + { + Placed = 0, + Inactive = 1, + Active = 2, + ExecutionStarted = 3, + Executed = 4, + Canceled = 5, + Rejected = 6, + Expired = 7 + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/OrderType.cs b/src/MarginTrading.Backend.Core/Orders/OrderType.cs new file mode 100644 index 000000000..19bece724 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/OrderType.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Orders +{ + /// + /// The type of order + /// + public enum OrderType + { + /// + /// Market order, a basic one + /// + Market = 1, + + /// + /// Limit order, a basic one + /// + Limit = 2, + + /// + /// Stop order, a basic one (closing only) + /// + Stop = 3, + + /// + /// Take profit order, related to other order or position + /// + TakeProfit = 4, + + /// + /// Stop loss order, related to other order or position + /// + StopLoss = 5, + + /// + /// Trailing stop order, related to other order or position + /// + TrailingStop = 6, + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/OrderUpdateType.cs b/src/MarginTrading.Backend.Core/Orders/OrderUpdateType.cs new file mode 100644 index 000000000..ee2a2ccf3 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/OrderUpdateType.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Orders +{ + public enum OrderUpdateType + { + Place, + Activate, + Change, + Cancel, + Reject, + ExecutionStarted, + Executed + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/OriginatorType.cs b/src/MarginTrading.Backend.Core/Orders/OriginatorType.cs new file mode 100644 index 000000000..5443b41b1 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/OriginatorType.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Orders +{ + public enum OriginatorType + { + Investor = 1, + System = 2, + OnBehalf = 3 + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/Position.cs b/src/MarginTrading.Backend.Core/Orders/Position.cs new file mode 100644 index 000000000..548d3ed61 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/Position.cs @@ -0,0 +1,329 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Common; +using MarginTrading.Backend.Core.Exceptions; +using MarginTrading.Backend.Core.StateMachines; +using MarginTrading.Backend.Core.Trading; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace MarginTrading.Backend.Core.Orders +{ + // ReSharper disable once ClassWithVirtualMembersNeverInherited.Global - it is inherited via Mock + public class Position : StatefulObject + { + private decimal _openFxPrice; + private decimal _closeFxPrice; + + #region Properties + + [JsonProperty] + public virtual string Id { get; protected set; } + [JsonProperty] + public long Code { get; private set; } + [JsonProperty] + public virtual string AssetPairId { get; protected set; } + [JsonProperty] + public PositionDirection Direction { get; private set; } + [JsonProperty] + public decimal Volume { get; private set; } + [JsonProperty] + public virtual string AccountId { get; protected set; } + [JsonProperty] + public string TradingConditionId { get; private set; } + [JsonProperty] + public string AccountAssetId { get; private set; } + [JsonProperty] + public decimal? ExpectedOpenPrice { get; private set; } + [JsonProperty] + public string OpenMatchingEngineId { get; private set; } + [JsonProperty] + public DateTime OpenDate { get; private set; } + [JsonProperty] + public string OpenTradeId { get; private set; } + [JsonProperty] + public OrderType OpenOrderType { get; private set; } + [JsonProperty] + public decimal OpenOrderVolume { get; private set; } + [JsonProperty] + public decimal OpenPrice { get; private set; } + + [JsonProperty] + public decimal OpenFxPrice + { + get => _openFxPrice; + set + { + _openFxPrice = value; + if (CloseFxPrice == default) //migration + CloseFxPrice = _openFxPrice; + } + } + + [JsonProperty] + public string EquivalentAsset { get; private set; } + [JsonProperty] + public decimal OpenPriceEquivalent { get; private set; } + [JsonProperty] + public List RelatedOrders { get; private set; } + [JsonProperty] + public string LegalEntity { get; private set; } + [JsonProperty] + public OriginatorType OpenOriginator { get; private set; } + [JsonProperty] + public string ExternalProviderId { get; private set; } + + [JsonProperty] + public decimal SwapCommissionRate { get; private set; } + [JsonProperty] + public decimal OpenCommissionRate { get; private set; } + [JsonProperty] + public decimal CloseCommissionRate { get; private set; } + [JsonProperty] + public decimal CommissionLot { get; private set; } + + [JsonProperty] + public string CloseMatchingEngineId { get; private set; } + [JsonProperty] + public decimal ClosePrice { get; private set; } + + [JsonProperty] + public decimal CloseFxPrice + { + get => _closeFxPrice; + private set + { + if (value != default) + _closeFxPrice = value; + } + } + + [JsonProperty] + public decimal ClosePriceEquivalent { get; private set; } + [JsonProperty] + public DateTime? StartClosingDate { get; private set; } + [JsonProperty] + public DateTime? CloseDate { get; private set; } + [JsonProperty] + public OriginatorType? CloseOriginator { get; private set; } + [JsonProperty] + public PositionCloseReason CloseReason { get; private set; } + [JsonProperty] + public string CloseComment { get; private set; } + [JsonProperty] + public List CloseTrades { get; private set; } + + [JsonProperty] + public virtual string FxAssetPairId { get; protected set; } + [JsonProperty] + public virtual FxToAssetPairDirection FxToAssetPairDirection { get; protected set; } + + [JsonProperty] + public override PositionStatus Status { get; protected set; } + + [JsonProperty] + public DateTime? LastModified { get; private set; } + + [JsonProperty] + public decimal ChargedPnL { get; private set; } + + [JsonProperty] + public virtual HashSet ChargePnlOperations { get; protected set; } + + [JsonProperty] + public FplData FplData { get; private set; } + + /// + /// Additional information about the order, that opened position + /// + [JsonProperty] + public string AdditionalInfo { get; private set; } + + #endregion Properties + + /// + /// For testing and deserialization + /// + [JsonConstructor] + public Position() + { + FplData = new FplData {ActualHash = 1}; + } + + public Position(string id, long code, string assetPairId, decimal volume, string accountId, + string tradingConditionId, string accountAssetId, decimal? expectedOpenPrice, string openMatchingEngineId, + DateTime openDate, string openTradeId, OrderType openOrderType, decimal openOrderVolume, decimal openPrice, decimal + openFxPrice, string equivalentAsset, decimal openPriceEquivalent, List relatedOrders, string legalEntity, + OriginatorType openOriginator, string externalProviderId, string fxAssetPairId, + FxToAssetPairDirection fxToAssetPairDirection, string additionalInfo) + { + // ReSharper disable VirtualMemberCallInConstructor + // ^^^ props are virtual for tests, derived constructor call is overriden by this one, but it's ok + Id = id; + Code = code; + AssetPairId = assetPairId; + Volume = volume; + Direction = volume.GetPositionDirection(); + AccountId = accountId; + TradingConditionId = tradingConditionId; + AccountAssetId = accountAssetId; + ExpectedOpenPrice = expectedOpenPrice; + OpenMatchingEngineId = openMatchingEngineId; + OpenDate = openDate; + OpenTradeId = openTradeId; + OpenOrderType = openOrderType; + OpenOrderVolume = openOrderVolume; + OpenPrice = openPrice; + OpenFxPrice = openFxPrice; + CloseFxPrice = openFxPrice; + EquivalentAsset = equivalentAsset; + OpenPriceEquivalent = openPriceEquivalent; + RelatedOrders = relatedOrders; + LegalEntity = legalEntity; + OpenOriginator = openOriginator; + ExternalProviderId = externalProviderId; + CloseTrades = new List(); + ChargePnlOperations = new HashSet(); + FxAssetPairId = fxAssetPairId; + FxToAssetPairDirection = fxToAssetPairDirection; + AdditionalInfo = additionalInfo; + // ReSharper restore VirtualMemberCallInConstructor + FplData = new FplData {ActualHash = 1}; + } + + #region Actions + + public void UpdateCloseFxPrice(decimal closeFxPrice) + { + CloseFxPrice = closeFxPrice; + FplData.ActualHash++; + var account = MtServiceLocator.AccountsCacheService.Get(AccountId); + account.CacheNeedsToBeUpdated(); + } + + //TODO: temp solution in order not to have a lot of changes + public void UpdateClosePriceWithoutAccountUpdate(decimal closePrice) + { + ClosePrice = closePrice; + FplData.ActualHash++; + } + + public void UpdateClosePrice(decimal closePrice) + { + ClosePrice = closePrice; + FplData.ActualHash++; + var account = MtServiceLocator.AccountsCacheService.Get(AccountId); + account.CacheNeedsToBeUpdated(); + } + + public void SetCommissionRates(decimal swapCommissionRate, decimal openCommissionRate, decimal closeCommissionRate, + decimal commissionLot) + { + SwapCommissionRate = swapCommissionRate; + OpenCommissionRate = openCommissionRate; + CloseCommissionRate = closeCommissionRate; + CommissionLot = commissionLot; + } + + public void AddRelatedOrder(Order order) + { + var info = new RelatedOrderInfo {Type = order.OrderType, Id = order.Id}; + + if (!RelatedOrders.Contains(info)) + RelatedOrders.Add(info); + } + + public void RemoveRelatedOrder(string relatedOrderId) + { + var relatedOrder = RelatedOrders.FirstOrDefault(o => o.Id == relatedOrderId); + + if (relatedOrder != null) + RelatedOrders.Remove(relatedOrder); + } + + public virtual void ChargePnL(string operationId, decimal value) + { + //if operation was already processed - it is duplicated event + if (ChargePnlOperations.Contains(operationId)) + return; + + ChargePnlOperations.Add(operationId); + ChargedPnL += value; + FplData.ActualHash++; + } + + public void PartiallyClose(DateTime date, decimal closedVolume, string tradeId, decimal chargedPnl) + { + LastModified = date; + Volume = Volume > 0 ? Volume - closedVolume : Volume + closedVolume; + CloseTrades.Add(tradeId); + ChargedPnL -= chargedPnl; + } + + #endregion Actions + + #region State changes + + public void StartClosing(DateTime date, PositionCloseReason reason, OriginatorType originator, string comment) + { + ChangeState(PositionCommand.StartClosing, () => + { + LastModified = date; + StartClosingDate = date; + CloseReason = reason; + CloseOriginator = originator; + CloseComment = comment; + }); + } + + public bool TryStartClosing(DateTime date, PositionCloseReason reason, OriginatorType originator, string comment) + { + try + { + StartClosing(date, reason, originator, comment); + return true; + } + catch (StateTransitionNotFoundException e) + { + return false; + } + } + + public void CancelClosing(DateTime date) + { + ChangeState(PositionCommand.CancelClosing, () => + { + LastModified = date; + StartClosingDate = null; + CloseReason = PositionCloseReason.None; + CloseOriginator = null; + CloseComment = null; + }); + } + + public void Close(DateTime date, string closeMatchingEngineId, decimal closePrice, decimal closeFxPrice, + decimal closePriceEquivalent, OriginatorType originator, PositionCloseReason closeReason, string comment, + string tradeId) + { + ChangeState(PositionCommand.Close, () => + { + CloseDate = date; + LastModified = date; + CloseMatchingEngineId = closeMatchingEngineId; + CloseFxPrice = closeFxPrice; + ClosePriceEquivalent = closePriceEquivalent; + CloseOriginator = CloseOriginator ?? originator; + CloseReason = closeReason; + CloseComment = comment; + CloseTrades.Add(tradeId); + UpdateClosePrice(closePrice); + }); + } + + #endregion State changes + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/PositionCloseReason.cs b/src/MarginTrading.Backend.Core/Orders/PositionCloseReason.cs new file mode 100644 index 000000000..39e1cdc1c --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/PositionCloseReason.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Orders +{ + public enum PositionCloseReason + { + None, + Close, + StopLoss, + TakeProfit, + StopOut + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/PositionCloseResult.cs b/src/MarginTrading.Backend.Core/Orders/PositionCloseResult.cs new file mode 100644 index 000000000..8bb9a7f78 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/PositionCloseResult.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Orders +{ + public enum PositionCloseResult + { + Closed, + ClosingIsInProgress, + ClosingStarted, + FailedToClose + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/PositionCommand.cs b/src/MarginTrading.Backend.Core/Orders/PositionCommand.cs new file mode 100644 index 000000000..7f5712378 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/PositionCommand.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Orders +{ + public enum PositionCommand + { + StartClosing = 0, + CancelClosing = 1, + Close = 2, + + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/PositionDirection.cs b/src/MarginTrading.Backend.Core/Orders/PositionDirection.cs new file mode 100644 index 000000000..3038b4029 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/PositionDirection.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Orders +{ + public enum PositionDirection + { + Long = 1, + Short = 2 + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/PositionHistory.cs b/src/MarginTrading.Backend.Core/Orders/PositionHistory.cs new file mode 100644 index 000000000..3444b8c4b --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/PositionHistory.cs @@ -0,0 +1,57 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; + +namespace MarginTrading.Backend.Core.Orders +{ + public class PositionHistory : IPositionHistory + { + public string Id { get; set; } + public string DealId { get; set; } + public long Code { get; set; } + public string AssetPairId { get; set; } + public PositionDirection Direction { get; set; } + public decimal Volume { get; set; } + public string AccountId { get; set; } + public string TradingConditionId { get; set; } + public string AccountAssetId { get; set; } + public decimal? ExpectedOpenPrice { get; set; } + public string OpenMatchingEngineId { get; set; } + public DateTime OpenDate { get; set; } + public string OpenTradeId { get; set; } + public OrderType OpenOrderType { get; set; } + public decimal OpenOrderVolume { get; set; } + public decimal OpenPrice { get; set; } + public decimal OpenFxPrice { get; set; } + public string EquivalentAsset { get; set; } + public decimal OpenPriceEquivalent { get; set; } + public List RelatedOrders { get; set; } + public string LegalEntity { get; set; } + public OriginatorType OpenOriginator { get; set; } + public string ExternalProviderId { get; set; } + public decimal SwapCommissionRate { get; set; } + public decimal OpenCommissionRate { get; set; } + public decimal CloseCommissionRate { get; set; } + public decimal CommissionLot { get; set; } + public string CloseMatchingEngineId { get; set; } + public decimal ClosePrice { get; set; } + public decimal CloseFxPrice { get; set; } + public decimal ClosePriceEquivalent { get; set; } + public DateTime? StartClosingDate { get; set; } + public DateTime? CloseDate { get; set; } + public OriginatorType? CloseOriginator { get; set; } + public OrderCloseReason CloseReason { get; set; } + public string CloseComment { get; set; } + public List CloseTrades { get; set; } + public string FxAssetPairId { get; set; } + public FxToAssetPairDirection FxToAssetPairDirection { get; set; } + public DateTime? LastModified { get; set; } + public decimal TotalPnL { get; set; } + public decimal ChargedPnl { get; set; } + public string AdditionalInfo { get; set; } + public PositionHistoryType HistoryType { get; set; } + public DateTime HistoryTimestamp { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/PositionHistoryType.cs b/src/MarginTrading.Backend.Core/Orders/PositionHistoryType.cs new file mode 100644 index 000000000..0a97016eb --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/PositionHistoryType.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace MarginTrading.Backend.Core.Orders +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum PositionHistoryType + { + Open, + PartiallyClose, + Close + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/PositionStatus.cs b/src/MarginTrading.Backend.Core/Orders/PositionStatus.cs new file mode 100644 index 000000000..dba1766cb --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/PositionStatus.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Orders +{ + public enum PositionStatus + { + Active, + Closing, + Closed + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/PositionsCloseData.cs b/src/MarginTrading.Backend.Core/Orders/PositionsCloseData.cs new file mode 100644 index 000000000..344f7a5e1 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/PositionsCloseData.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using MarginTrading.Backend.Core.MatchingEngines; + +namespace MarginTrading.Backend.Core.Orders +{ + public class PositionsCloseData + { + public PositionsCloseData( + List positions, + string accountId, + string assetPairId, + string openMatchingEngineId, + string externalProviderId, + OriginatorType originator, + string additionalInfo, + string correlationId, + string equivalentAsset, + string comment = null, + IMatchingEngineBase matchingEngine = null, + OrderModality modality = OrderModality.Regular) + { + Positions = positions; + AccountId = accountId; + AssetPairId = assetPairId; + OpenMatchingEngineId = openMatchingEngineId; + ExternalProviderId = externalProviderId; + Originator = originator; + AdditionalInfo = additionalInfo; + CorrelationId = correlationId; + EquivalentAsset = equivalentAsset; + Comment = comment; + MatchingEngine = matchingEngine; + Modality = modality; + } + + public List Positions { get; } + + public string AccountId { get; } + + public string AssetPairId { get; } + + public string OpenMatchingEngineId { get; } + + public string ExternalProviderId { get; } + + public string EquivalentAsset { get; } + + public OriginatorType Originator { get; } + + public string AdditionalInfo { get; } + + public string CorrelationId { get; } + + public string Comment { get; } + + public IMatchingEngineBase MatchingEngine { get; } + + public OrderModality Modality { get; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Orders/RelatedOrderInfo.cs b/src/MarginTrading.Backend.Core/Orders/RelatedOrderInfo.cs new file mode 100644 index 000000000..584b80f2b --- /dev/null +++ b/src/MarginTrading.Backend.Core/Orders/RelatedOrderInfo.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Core.Orders +{ + public class RelatedOrderInfo : IEquatable + { + public OrderType Type { get; set; } + public string Id { get; set; } + + public bool Equals(RelatedOrderInfo other) + { + return Id == other?.Id; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/OvernightSwaps/IOvernightSwapHistory.cs b/src/MarginTrading.Backend.Core/OvernightSwaps/IOvernightSwapHistory.cs deleted file mode 100644 index 0abe441d5..000000000 --- a/src/MarginTrading.Backend.Core/OvernightSwaps/IOvernightSwapHistory.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MarginTrading.Backend.Core -{ - public interface IOvernightSwapHistory : IOvernightSwapState - { - bool IsSuccess { get; } - Exception Exception { get; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/OvernightSwaps/IOvernightSwapState.cs b/src/MarginTrading.Backend.Core/OvernightSwaps/IOvernightSwapState.cs deleted file mode 100644 index 59d4f566e..000000000 --- a/src/MarginTrading.Backend.Core/OvernightSwaps/IOvernightSwapState.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MarginTrading.Backend.Core -{ - public interface IOvernightSwapState - { - string ClientId { get; } - string AccountId { get; } - string Instrument { get; } - OrderDirection? Direction { get; } - DateTime Time { get; } - decimal Volume { get; } - decimal Value { get; } - decimal SwapRate { get; } - List OpenOrderIds { get; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/OvernightSwaps/OvernightSwapCalculation.cs b/src/MarginTrading.Backend.Core/OvernightSwaps/OvernightSwapCalculation.cs deleted file mode 100644 index 512b31040..000000000 --- a/src/MarginTrading.Backend.Core/OvernightSwaps/OvernightSwapCalculation.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; - -namespace MarginTrading.Backend.Core -{ - public class OvernightSwapCalculation : IOvernightSwapHistory, IOvernightSwapState - { - public string Key => GetKey(AccountId, Instrument, Direction); - - public string ClientId { get; set; } - public string AccountId { get; set; } - public string Instrument { get; set; } - public OrderDirection? Direction { get; set; } - public DateTime Time { get; set; } - public decimal Volume { get; set; } - public decimal Value { get; set; } - public decimal SwapRate { get; set; } - public List OpenOrderIds { get; set; } - - public bool IsSuccess { get; set; } - public Exception Exception { get; set; } - - public static string GetKey(string accountId, string instrument, OrderDirection? direction) => - $"{accountId}_{instrument ?? ""}_{direction?.ToString() ?? ""}"; - - public static OvernightSwapCalculation Create(IOvernightSwapState state) - { - return new OvernightSwapCalculation - { - ClientId = state.ClientId, - AccountId = state.AccountId, - Instrument = state.Instrument, - Direction = state.Direction, - Time = state.Time, - Volume = state.Volume, - Value = state.Value, - SwapRate = state.SwapRate, - OpenOrderIds = state.OpenOrderIds, - IsSuccess = true - }; - } - - public static OvernightSwapCalculation Create(string clientId, string accountId, string instrument, - List orderIds, DateTime timestamp, bool isSuccess, Exception exception = null, decimal volume = default(decimal), - decimal value = default(decimal), decimal swapRate = default(decimal), OrderDirection? direction = null) - { - return new OvernightSwapCalculation - { - ClientId = clientId, - AccountId = accountId, - Instrument = instrument, - Direction = direction, - Time = timestamp, - Volume = volume, - Value = value, - SwapRate = swapRate, - OpenOrderIds = orderIds, - IsSuccess = isSuccess, - Exception = exception, - }; - } - - public static OvernightSwapCalculation Update(OvernightSwapCalculation newCalc, OvernightSwapCalculation lastCalc) - { - return new OvernightSwapCalculation - { - ClientId = newCalc.ClientId, - AccountId = newCalc.AccountId, - Instrument = newCalc.Instrument, - Direction = newCalc.Direction, - Time = newCalc.Time, - Volume = newCalc.Volume, - Value = newCalc.Value + lastCalc.Value, - OpenOrderIds = newCalc.OpenOrderIds.Concat(lastCalc.OpenOrderIds).ToList(), - SwapRate = newCalc.SwapRate, - IsSuccess = true - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/OvernightSwaps/OvernightSwapNotification.cs b/src/MarginTrading.Backend.Core/OvernightSwaps/OvernightSwapNotification.cs deleted file mode 100644 index d787168e7..000000000 --- a/src/MarginTrading.Backend.Core/OvernightSwaps/OvernightSwapNotification.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace MarginTrading.Backend.Core -{ - public class OvernightSwapNotification - { - public string CliendId { get; set; } - public IReadOnlyList CalculationsByAccount { get; set; } - public string CurrentDate => DateTime.UtcNow.ToString("dd MMMM yyyy"); - - public class AccountCalculations - { - public string AccountId { get; set; } - public string AccountCurrency { get; set; } - public IReadOnlyList Calculations { get; set; } - public decimal TotalCost => Calculations.Sum(x => x.Cost); - } - - public class SingleCalculation - { - public string Instrument { get; set; } - public string Direction { get; set; } - public decimal Volume { get; set; } - public decimal SwapRate { get; set; } - public decimal Cost { get; set; } - public List PositionIds { get; set; } - public string PositionIdsString => string.Join(", ", PositionIds); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/PaginatedResponse.cs b/src/MarginTrading.Backend.Core/PaginatedResponse.cs new file mode 100644 index 000000000..8cd566bcb --- /dev/null +++ b/src/MarginTrading.Backend.Core/PaginatedResponse.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace MarginTrading.Backend.Core +{ + public class PaginatedResponse + { + [NotNull] + public IReadOnlyList Contents { get; } + + public int Start { get; } + + public int Size { get; } + + public int TotalSize { get; } + + public PaginatedResponse([NotNull] IReadOnlyList contents, int start, int size, int totalSize) + { + Contents = contents ?? throw new ArgumentNullException(nameof(contents)); + Start = start; + Size = size; + TotalSize = totalSize; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Properties/AssemblyInfo.cs b/src/MarginTrading.Backend.Core/Properties/AssemblyInfo.cs index d21e85913..0ff906a2c 100644 --- a/src/MarginTrading.Backend.Core/Properties/AssemblyInfo.cs +++ b/src/MarginTrading.Backend.Core/Properties/AssemblyInfo.cs @@ -1,5 +1,7 @@ -using System.Reflection; -using System.Runtime.CompilerServices; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Reflection; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/src/MarginTrading.Backend.Core/Repositories/IAccountMarginFreezingRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IAccountMarginFreezingRepository.cs new file mode 100644 index 000000000..b6d5d1366 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Repositories/IAccountMarginFreezingRepository.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MarginTrading.Backend.Core.Repositories +{ + public interface IAccountMarginFreezingRepository + { + Task> GetAllAsync(); + Task TryInsertAsync(IAccountMarginFreezing item); + Task DeleteAsync(string operationId); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Repositories/IAccountMarginUnconfirmedRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IAccountMarginUnconfirmedRepository.cs new file mode 100644 index 000000000..46a6ba159 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Repositories/IAccountMarginUnconfirmedRepository.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MarginTrading.Backend.Core.Repositories +{ + public interface IAccountMarginUnconfirmedRepository + { + Task> GetAllAsync(); + Task TryInsertAsync(IAccountMarginFreezing item); + Task DeleteAsync(string operationId); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Repositories/IAccountStatRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IAccountStatRepository.cs new file mode 100644 index 000000000..a44e07f76 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Repositories/IAccountStatRepository.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MarginTrading.Backend.Core.Repositories +{ + public interface IAccountStatRepository + { + Task Dump(IEnumerable accounts); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Repositories/IDayOffSettingsRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IDayOffSettingsRepository.cs deleted file mode 100644 index 239ab305c..000000000 --- a/src/MarginTrading.Backend.Core/Repositories/IDayOffSettingsRepository.cs +++ /dev/null @@ -1,11 +0,0 @@ -using JetBrains.Annotations; -using MarginTrading.Backend.Core.DayOffSettings; - -namespace MarginTrading.Backend.Core -{ - public interface IDayOffSettingsRepository - { - [CanBeNull] DayOffSettingsRoot Read(); - void Write(DayOffSettingsRoot settings); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Repositories/IIdentityGenerator.cs b/src/MarginTrading.Backend.Core/Repositories/IIdentityGenerator.cs index a2e38a204..02b132c43 100644 --- a/src/MarginTrading.Backend.Core/Repositories/IIdentityGenerator.cs +++ b/src/MarginTrading.Backend.Core/Repositories/IIdentityGenerator.cs @@ -1,9 +1,16 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; namespace MarginTrading.Backend.Core.Repositories { public interface IIdentityGenerator { Task GenerateIdAsync(string entityType); + + string GenerateAlphanumericId(); + + string GenerateGuid(); } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Repositories/IMarginTradingAccountHistoryRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IMarginTradingAccountHistoryRepository.cs index 8401afed9..8752adda4 100644 --- a/src/MarginTrading.Backend.Core/Repositories/IMarginTradingAccountHistoryRepository.cs +++ b/src/MarginTrading.Backend.Core/Repositories/IMarginTradingAccountHistoryRepository.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/src/MarginTrading.Backend.Core/Repositories/IMarginTradingAggregateValuesAtRiskRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IMarginTradingAggregateValuesAtRiskRepository.cs index 6d0d5952e..aba5b0b39 100644 --- a/src/MarginTrading.Backend.Core/Repositories/IMarginTradingAggregateValuesAtRiskRepository.cs +++ b/src/MarginTrading.Backend.Core/Repositories/IMarginTradingAggregateValuesAtRiskRepository.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; namespace MarginTrading.Backend.Core { diff --git a/src/MarginTrading.Backend.Core/Repositories/IMarginTradingBlobRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IMarginTradingBlobRepository.cs index 15e91f432..4f0178b71 100644 --- a/src/MarginTrading.Backend.Core/Repositories/IMarginTradingBlobRepository.cs +++ b/src/MarginTrading.Backend.Core/Repositories/IMarginTradingBlobRepository.cs @@ -1,4 +1,8 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; using JetBrains.Annotations; namespace MarginTrading.Backend.Core @@ -7,8 +11,12 @@ public interface IMarginTradingBlobRepository { [CanBeNull] T Read(string blobContainer, string key); - Task Write(string blobContainer, string key, T obj); [ItemCanBeNull] Task ReadAsync(string blobContainer, string key); + + (T, DateTime) ReadWithTimestamp(string blobContainer, string key); + Task<(T, DateTime)> ReadWithTimestampAsync(string blobContainer, string key); + + Task WriteAsync(string blobContainer, string key, T obj); } } diff --git a/src/MarginTrading.Backend.Core/Repositories/IMarginTradingIndividualValuesAtRiskRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IMarginTradingIndividualValuesAtRiskRepository.cs index 3c955d3a7..c88b6ca72 100644 --- a/src/MarginTrading.Backend.Core/Repositories/IMarginTradingIndividualValuesAtRiskRepository.cs +++ b/src/MarginTrading.Backend.Core/Repositories/IMarginTradingIndividualValuesAtRiskRepository.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; namespace MarginTrading.Backend.Core { diff --git a/src/MarginTrading.Backend.Core/Repositories/IMarginTradingOrdersHistoryRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IMarginTradingOrdersHistoryRepository.cs deleted file mode 100644 index 1cda73f15..000000000 --- a/src/MarginTrading.Backend.Core/Repositories/IMarginTradingOrdersHistoryRepository.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace MarginTrading.Backend.Core -{ - public interface IMarginTradingOrdersHistoryRepository - { - Task AddAsync(IOrderHistory order); - Task> GetHistoryAsync(); - Task> GetHistoryAsync(string clientId, string[] accountIds, DateTime? from, DateTime? to); - } -} diff --git a/src/MarginTrading.Backend.Core/Repositories/IMarginTradingOrdersRejectedRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IMarginTradingOrdersRejectedRepository.cs deleted file mode 100644 index b24b18d75..000000000 --- a/src/MarginTrading.Backend.Core/Repositories/IMarginTradingOrdersRejectedRepository.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace MarginTrading.Backend.Core -{ - public interface IMarginTradingOrdersRejectedRepository - { - Task AddAsync(IOrderHistory order); - Task> GetHisotryAsync(string[] accountIds, DateTime from, DateTime to); - } -} diff --git a/src/MarginTrading.Backend.Core/Repositories/IOpenPositionsRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IOpenPositionsRepository.cs new file mode 100644 index 000000000..79f4e8988 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Repositories/IOpenPositionsRepository.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using MarginTrading.Backend.Core.Orders; + +namespace MarginTrading.Backend.Core.Repositories +{ + public interface IOpenPositionsRepository + { + Task Dump(IEnumerable openPositions); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Repositories/IOperationExecutionInfoRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IOperationExecutionInfoRepository.cs new file mode 100644 index 000000000..9d00109f4 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Repositories/IOperationExecutionInfoRepository.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace MarginTrading.Backend.Core.Repositories +{ + public interface IOperationExecutionInfoRepository + { + Task> GetOrAddAsync(string operationName, + string operationId, Func> factory) where TData : class; + + [ItemCanBeNull] + Task> GetAsync(string operationName, string id) where TData : class; + + Task Save(IOperationExecutionInfo executionInfo) where TData : class; + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Repositories/IOrdersHistoryRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IOrdersHistoryRepository.cs new file mode 100644 index 000000000..500154f82 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Repositories/IOrdersHistoryRepository.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using MarginTrading.Backend.Core.Orders; + +namespace MarginTrading.Backend.Core.Repositories +{ + public interface IOrdersHistoryRepository + { + Task> GetLastSnapshot(DateTime @from); + } +} diff --git a/src/MarginTrading.Backend.Core/Repositories/IOvernightSwapHistoryRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IOvernightSwapHistoryRepository.cs deleted file mode 100644 index 6d5ba179a..000000000 --- a/src/MarginTrading.Backend.Core/Repositories/IOvernightSwapHistoryRepository.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace MarginTrading.Backend.Core.Repositories -{ - public interface IOvernightSwapHistoryRepository - { - Task AddAsync(IOvernightSwapHistory obj); - Task> GetAsync(); - Task> GetAsync(DateTime? @from, DateTime? to); - Task> GetAsync(string accountId, DateTime? from, DateTime? to); - - /// - /// For testing purposes - /// - /// - /// - Task DeleteAsync(IOvernightSwapHistory obj); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Repositories/IOvernightSwapStateRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IOvernightSwapStateRepository.cs deleted file mode 100644 index d8c57b225..000000000 --- a/src/MarginTrading.Backend.Core/Repositories/IOvernightSwapStateRepository.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace MarginTrading.Backend.Core.Repositories -{ - public interface IOvernightSwapStateRepository - { - Task AddOrReplaceAsync(IOvernightSwapState obj); - Task> GetAsync(); - Task DeleteAsync(IOvernightSwapState obj); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Repositories/IPositionsHistoryRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IPositionsHistoryRepository.cs new file mode 100644 index 000000000..fe7f10926 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Repositories/IPositionsHistoryRepository.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using MarginTrading.Backend.Core.Orders; + +namespace MarginTrading.Backend.Core.Repositories +{ + public interface IPositionsHistoryRepository + { + Task> GetLastSnapshot(DateTime @from); + } +} diff --git a/src/MarginTrading.Backend.Core/Repositories/IQuoteHistoryRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IQuoteHistoryRepository.cs index 33a18aabf..b66e920c5 100644 --- a/src/MarginTrading.Backend.Core/Repositories/IQuoteHistoryRepository.cs +++ b/src/MarginTrading.Backend.Core/Repositories/IQuoteHistoryRepository.cs @@ -1,4 +1,8 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using MarginTrading.Backend.Core.Orders; namespace MarginTrading.Backend.Core { diff --git a/src/MarginTrading.Backend.Core/Repositories/ITradingEngineSnapshotsRepository.cs b/src/MarginTrading.Backend.Core/Repositories/ITradingEngineSnapshotsRepository.cs new file mode 100644 index 000000000..1465e983a --- /dev/null +++ b/src/MarginTrading.Backend.Core/Repositories/ITradingEngineSnapshotsRepository.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; + +namespace MarginTrading.Backend.Core.Repositories +{ + public interface ITradingEngineSnapshotsRepository + { + Task Add(DateTime tradingDay, string correlationId, DateTime timestamp, string orders, string positions, + string accounts, + string bestFxPrices, string bestTradingPrices); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Services/AccountFpl.cs b/src/MarginTrading.Backend.Core/Services/AccountFpl.cs new file mode 100644 index 000000000..c7457cc61 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Services/AccountFpl.cs @@ -0,0 +1,45 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace MarginTrading.Backend.Core +{ + public class AccountFpl + { + public AccountFpl() + { + ActualHash = 1; + } + + public decimal PnL { get; set; } + public decimal UnrealizedDailyPnl { get; set; } + + /// + /// Currently used margin for maintaining of positions + /// + public decimal UsedMargin { get; set; } + + /// + /// Margin used for initial open of positions + /// + public decimal InitiallyUsedMargin { get; set; } + + /// + /// Currently used margin for opening of positions + /// + public decimal MarginInit { get; set; } + public int OpenPositionsCount { get; set; } + public int ActiveOrdersCount { get; set; } + public decimal MarginCall1Level { get; set; } + public decimal MarginCall2Level { get; set; } + public decimal StopOutLevel { get; set; } + public decimal WithdrawalFrozenMargin { get; set; } + public Dictionary WithdrawalFrozenMarginData { get; set; } = new Dictionary(); + public decimal UnconfirmedMargin { get; set; } + public Dictionary UnconfirmedMarginData { get; set; } = new Dictionary(); + + public int CalculatedHash { get; set; } + public int ActualHash { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Services/IAccountUpdateService.cs b/src/MarginTrading.Backend.Core/Services/IAccountUpdateService.cs index 97e646629..55d03edb4 100644 --- a/src/MarginTrading.Backend.Core/Services/IAccountUpdateService.cs +++ b/src/MarginTrading.Backend.Core/Services/IAccountUpdateService.cs @@ -1,27 +1,23 @@ -namespace MarginTrading.Backend.Core +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using MarginTrading.Backend.Core.MatchingEngines; +using MarginTrading.Backend.Core.Trading; + +namespace MarginTrading.Backend.Core.Services { public interface IAccountUpdateService { void UpdateAccount(IMarginTradingAccount account); - bool IsEnoughBalance(Order order); - MarginTradingAccount GuessAccountWithNewActiveOrder(Order order); - } - - public class AccountFpl - { - public AccountFpl() - { - ActualHash = 1; - } - - public decimal PnL { get; set; } - public decimal UsedMargin { get; set; } - public decimal MarginInit { get; set; } - public int OpenPositionsCount { get; set; } - public decimal MarginCallLevel { get; set; } - public decimal StopoutLevel { get; set; } + Task FreezeWithdrawalMargin(string accountId, string operationId, decimal amount); + Task UnfreezeWithdrawalMargin(string accountId, string operationId); + Task FreezeUnconfirmedMargin(string accountId, string operationId, decimal amount); + Task UnfreezeUnconfirmedMargin(string accountId, string operationId); + void CheckIsEnoughBalance(Order order, IMatchingEngineBase matchingEngine); + void RemoveLiquidationStateIfNeeded(string accountId, string reason, + string liquidationOperationId = null, LiquidationType liquidationType = LiquidationType.Normal); - public int CalculatedHash { get; set; } - public int ActualHash { get; set; } + decimal CalculateOvernightUsedMargin(IMarginTradingAccount account); } } diff --git a/src/MarginTrading.Backend.Core/Services/IAccountsCacheService.cs b/src/MarginTrading.Backend.Core/Services/IAccountsCacheService.cs index eb9cdc22e..e4c60567f 100644 --- a/src/MarginTrading.Backend.Core/Services/IAccountsCacheService.cs +++ b/src/MarginTrading.Backend.Core/Services/IAccountsCacheService.cs @@ -1,17 +1,32 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; using JetBrains.Annotations; namespace MarginTrading.Backend.Core { public interface IAccountsCacheService { - IEnumerable GetAll([CanBeNull] string clientId); + [NotNull] + MarginTradingAccount Get(string accountId); + [CanBeNull] + MarginTradingAccount TryGet(string accountId); IReadOnlyList GetAll(); - MarginTradingAccount Get(string clientId, string accountId); - void UpdateBalance(MarginTradingAccount account); - IMarginTradingAccount SetTradingCondition(string clientId, string accountId, string tradingConditionId); - IEnumerable GetClientIdsByTradingConditionId(string tradingConditionId, string accountId = null); - void UpdateAccountsCache(string clientId, IEnumerable newValues); - MarginTradingAccount TryGet(string clientId, string accountId); + PaginatedResponse GetAllByPages(int? skip = null, int? take = null); + + void TryAddNew(MarginTradingAccount account); + void Remove(string accountId); + string Reset(string accountId, DateTime eventTime); + + Task UpdateAccountChanges(string accountId, string updatedTradingConditionId, + decimal updatedWithdrawTransferLimit, bool isDisabled, bool isWithdrawalDisabled, DateTime eventTime); + Task UpdateAccountBalance(string accountId, decimal accountBalance, DateTime eventTime); + + bool TryStartLiquidation(string accountId, string operationId, out string currentOperationId); + + bool TryFinishLiquidation(string accountId, string reason, string liquidationOperationId = null); } -} +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Services/ICfdCalculatorService.cs b/src/MarginTrading.Backend.Core/Services/ICfdCalculatorService.cs index 5fec75367..12c3c5d39 100644 --- a/src/MarginTrading.Backend.Core/Services/ICfdCalculatorService.cs +++ b/src/MarginTrading.Backend.Core/Services/ICfdCalculatorService.cs @@ -1,10 +1,24 @@ -namespace MarginTrading.Backend.Core +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Core.Orders; + +namespace MarginTrading.Backend.Core { + //TODO: think about removal of legalEntity from methods (take from instrument) public interface ICfdCalculatorService { decimal GetQuoteRateForBaseAsset(string accountAssetId, string instrument, string legalEntity, - bool metricIsPositive = true); + bool useAsk); + decimal GetQuoteRateForQuoteAsset(string accountAssetId, string instrument, string legalEntity, bool metricIsPositive = true); + + decimal GetPrice(InstrumentBidAskPair quote, FxToAssetPairDirection direction, + bool metricIsPositive = true); + + (string id, FxToAssetPairDirection direction) GetFxAssetPairIdAndDirection(string accountAssetId, + string assetPairId, + string legalEntity); } } diff --git a/src/MarginTrading.Backend.Core/Services/IClientNotifyService.cs b/src/MarginTrading.Backend.Core/Services/IClientNotifyService.cs deleted file mode 100644 index 9890030c7..000000000 --- a/src/MarginTrading.Backend.Core/Services/IClientNotifyService.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Threading.Tasks; - -namespace MarginTrading.Backend.Core -{ - public interface IClientNotifyService - { - void NotifyOrderChanged(Order order); - void NotifyAccountUpdated(IMarginTradingAccount account); - void NotifyAccountStopout(string clientId, string accountId, int positionsCount, decimal totalPnl); - Task NotifyTradingConditionsChanged(string tradingConditionId = null, string accountId = null); - } -} diff --git a/src/MarginTrading.Backend.Core/Services/IEmailService.cs b/src/MarginTrading.Backend.Core/Services/IEmailService.cs index 6a2875409..0427c0a09 100644 --- a/src/MarginTrading.Backend.Core/Services/IEmailService.cs +++ b/src/MarginTrading.Backend.Core/Services/IEmailService.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; namespace MarginTrading.Backend.Core { @@ -6,6 +9,5 @@ public interface IEmailService { Task SendMarginCallEmailAsync(string email, string baseAssetId, string accountId); Task SendStopOutEmailAsync(string email, string baseAssetId, string accountId); - Task SendOvernightSwapEmailAsync(string email, OvernightSwapNotification overnightSwapNotification); } } diff --git a/src/MarginTrading.Backend.Core/Services/IExternalOrderbookService.cs b/src/MarginTrading.Backend.Core/Services/IExternalOrderbookService.cs new file mode 100644 index 000000000..5dda4f9df --- /dev/null +++ b/src/MarginTrading.Backend.Core/Services/IExternalOrderbookService.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using MarginTrading.Backend.Core.Orderbooks; + +namespace MarginTrading.Backend.Core.Services +{ + public interface IExternalOrderbookService + { + Task InitializeAsync(); + + List GetOrderBooks(); + + void SetOrderbook(ExternalOrderBook orderbook); + + List<(string source, decimal? price)> GetOrderedPricesForExecution(string assetPairId, decimal volume, + bool validateOppositeDirectionVolume); + + decimal? GetPriceForPositionClose(string assetPairId, decimal volume, string externalProviderId); + ExternalOrderBook GetOrderBook(string assetPairId); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Services/IFplService.cs b/src/MarginTrading.Backend.Core/Services/IFplService.cs index 74e2ee0e6..fb5e78436 100644 --- a/src/MarginTrading.Backend.Core/Services/IFplService.cs +++ b/src/MarginTrading.Backend.Core/Services/IFplService.cs @@ -1,12 +1,17 @@ -using System.Collections.Generic; -using MarginTrading.Backend.Core.MatchedOrders; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; namespace MarginTrading.Backend.Core { public interface IFplService { - void UpdateOrderFpl(IOrder order, FplData fplData); - decimal GetMatchedOrdersPrice(List matchedOrders, string instrument); - void CalculateMargin(IOrder order, FplData fplData); + void UpdatePositionFpl(Position order); + + decimal GetInitMarginForOrder(Order order); + + decimal CalculateOvernightMaintenanceMargin(Position position); } } diff --git a/src/MarginTrading.Backend.Core/Services/IFxRateCacheService.cs b/src/MarginTrading.Backend.Core/Services/IFxRateCacheService.cs new file mode 100644 index 000000000..7e55c16ad --- /dev/null +++ b/src/MarginTrading.Backend.Core/Services/IFxRateCacheService.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using MarginTrading.OrderbookAggregator.Contracts.Messages; + +namespace MarginTrading.Backend.Core.Services +{ + public interface IFxRateCacheService + { + InstrumentBidAskPair GetQuote(string instrument); + Dictionary GetAllQuotes(); + Task SetQuote(ExternalExchangeOrderbookMessage orderBookMessage); + void SetQuote(InstrumentBidAskPair bidAskPair); + void RemoveQuote(string assetPairId); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Services/IMicrographCacheService.cs b/src/MarginTrading.Backend.Core/Services/IMicrographCacheService.cs deleted file mode 100644 index 3b4d08996..000000000 --- a/src/MarginTrading.Backend.Core/Services/IMicrographCacheService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace MarginTrading.Backend.Core -{ - public interface IMicrographCacheService - { - Dictionary> GetGraphData(); - } -} diff --git a/src/MarginTrading.Backend.Core/Services/IOvernightMarginService.cs b/src/MarginTrading.Backend.Core/Services/IOvernightMarginService.cs new file mode 100644 index 000000000..d6b7ad0d3 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Services/IOvernightMarginService.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using MarginTrading.Backend.Core.DayOffSettings; + +namespace MarginTrading.Backend.Core.Services +{ + /// + /// Service to manage overnight margin parameter + /// + public interface IOvernightMarginService + { + /// + /// Act and schedule the next job: warning, start or end. Job type depends on the next state. + /// + void ScheduleNext(); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Services/IOvernightSwapNotificationService.cs b/src/MarginTrading.Backend.Core/Services/IOvernightSwapNotificationService.cs deleted file mode 100644 index dbae1893d..000000000 --- a/src/MarginTrading.Backend.Core/Services/IOvernightSwapNotificationService.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace MarginTrading.Backend.Core -{ - public interface IOvernightSwapNotificationService - { - void PerformEmailNotification(DateTime calculationTime); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Services/IOvernightSwapService.cs b/src/MarginTrading.Backend.Core/Services/IOvernightSwapService.cs deleted file mode 100644 index 23b9de9d1..000000000 --- a/src/MarginTrading.Backend.Core/Services/IOvernightSwapService.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Threading.Tasks; - -namespace MarginTrading.Backend.Core -{ - public interface IOvernightSwapService - { - /// - /// Scheduler entry point for overnight swaps calculation. Successfully calculated swaps are immediately charged. - /// - /// - void CalculateAndChargeSwaps(); - - /// - /// Fire at app start. Initialize cache from storage. Detect if calc was missed and invoke it if needed. - /// - void Start(); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Services/IQuoteCacheService.cs b/src/MarginTrading.Backend.Core/Services/IQuoteCacheService.cs index 3369898d2..55c97355a 100644 --- a/src/MarginTrading.Backend.Core/Services/IQuoteCacheService.cs +++ b/src/MarginTrading.Backend.Core/Services/IQuoteCacheService.cs @@ -1,3 +1,6 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using System.Collections.Generic; namespace MarginTrading.Backend.Core @@ -7,6 +10,6 @@ public interface IQuoteCacheService InstrumentBidAskPair GetQuote(string instrument); Dictionary GetAllQuotes(); bool TryGetQuoteById(string instrument, out InstrumentBidAskPair result); - void RemoveQuote(string assetPair); + void RemoveQuote(string assetPairId); } } diff --git a/src/MarginTrading.Backend.Core/Services/IReportService.cs b/src/MarginTrading.Backend.Core/Services/IReportService.cs new file mode 100644 index 000000000..cfeb601fa --- /dev/null +++ b/src/MarginTrading.Backend.Core/Services/IReportService.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; + +namespace MarginTrading.Backend.Core.Services +{ + public interface IReportService + { + Task DumpReportData(); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Services/IScheduleControlService.cs b/src/MarginTrading.Backend.Core/Services/IScheduleControlService.cs new file mode 100644 index 000000000..f4c9a3455 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Services/IScheduleControlService.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Services +{ + /// + /// Service to control trading schedule + /// + public interface IScheduleControlService + { + void ScheduleNext(); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Services/ISnapshotService.cs b/src/MarginTrading.Backend.Core/Services/ISnapshotService.cs new file mode 100644 index 000000000..0e9a5015c --- /dev/null +++ b/src/MarginTrading.Backend.Core/Services/ISnapshotService.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; + +namespace MarginTrading.Backend.Core.Services +{ + public interface ISnapshotService + { + Task MakeTradingDataSnapshot(DateTime tradingDay, string correlationId); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Services/ISpecialLiquidationService.cs b/src/MarginTrading.Backend.Core/Services/ISpecialLiquidationService.cs new file mode 100644 index 000000000..0fb3b216c --- /dev/null +++ b/src/MarginTrading.Backend.Core/Services/ISpecialLiquidationService.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; + +namespace MarginTrading.Backend.Core.Services +{ + public interface ISpecialLiquidationService + { + void FakeGetPriceForSpecialLiquidation(string operationId, string instrument, decimal volume); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Services/ISwapCommissionService.cs b/src/MarginTrading.Backend.Core/Services/ISwapCommissionService.cs index 9f6045453..568e40a79 100644 --- a/src/MarginTrading.Backend.Core/Services/ISwapCommissionService.cs +++ b/src/MarginTrading.Backend.Core/Services/ISwapCommissionService.cs @@ -1,9 +1,14 @@ -namespace MarginTrading.Backend.Core +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Core.Orders; + +namespace MarginTrading.Backend.Core { public interface ICommissionService { - void SetCommissionRates(string accountAssetId, string tradingConditionId, Order order); - decimal GetSwaps(IOrder order); - decimal GetOvernightSwap(IOrder order, decimal swapRate); + void SetCommissionRates(string accountAssetId, Position order); + decimal GetSwaps(Position order); + decimal GetOvernightSwap(Position order, decimal swapRate); } } diff --git a/src/MarginTrading.Backend.Core/Services/IValidateOrderService.cs b/src/MarginTrading.Backend.Core/Services/IValidateOrderService.cs deleted file mode 100644 index 0e06a31b4..000000000 --- a/src/MarginTrading.Backend.Core/Services/IValidateOrderService.cs +++ /dev/null @@ -1,13 +0,0 @@ -using MarginTrading.Backend.Core.TradingConditions; - -namespace MarginTrading.Backend.Core -{ - public interface IValidateOrderService - { - void Validate(Order order); - void ValidateOrderStops(OrderDirection type, BidAskPair quote, decimal deltaBid, decimal deltaAsk, decimal? takeProfit, - decimal? stopLoss, decimal? expectedOpenPrice, int assetAccuracy); - - void ValidateInstrumentPositionVolume(IAccountAssetPair assetPair, Order order); - } -} diff --git a/src/MarginTrading.Backend.Core/Settings/AccountAssetsSettings.cs b/src/MarginTrading.Backend.Core/Settings/AccountAssetsSettings.cs deleted file mode 100644 index 8551979d2..000000000 --- a/src/MarginTrading.Backend.Core/Settings/AccountAssetsSettings.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Lykke.SettingsReader.Attributes; - -namespace MarginTrading.Backend.Core.Settings -{ - public class AccountAssetsSettings - { - [Optional] - public int LeverageInit { get; set; } - - [Optional] - public int LeverageMaintenance { get; set; } - - [Optional] - public decimal SwapLong { get; set; } - - [Optional] - public decimal SwapShort { get; set; } - - [Optional] - public decimal OvernightSwapLong { get; set; } - - [Optional] - public decimal OvernightSwapShort { get; set; } - - [Optional] - public decimal SwapLongPct { get; set; } - - [Optional] - public decimal SwapShortPct { get; set; } - - [Optional] - public decimal CommissionLong { get; set; } - - [Optional] - public decimal CommissionShort { get; set; } - - [Optional] - public decimal CommissionLot { get; set; } - - [Optional] - public decimal DeltaBid { get; set; } - - [Optional] - public decimal DeltaAsk { get; set; } - - [Optional] - public decimal DealLimit { get; set; } - - [Optional] - public decimal PositionLimit { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/AssetServiceClientSettings.cs b/src/MarginTrading.Backend.Core/Settings/AssetServiceClientSettings.cs deleted file mode 100644 index fccd5852e..000000000 --- a/src/MarginTrading.Backend.Core/Settings/AssetServiceClientSettings.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using Lykke.SettingsReader.Attributes; - -namespace MarginTrading.Backend.Core.Settings -{ - public class AssetClientSettings - { - [HttpCheck("/api/isalive")] - public string ServiceUrl { get; set; } - public TimeSpan CacheExpirationPeriod { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/BlobPersistenceSettings.cs b/src/MarginTrading.Backend.Core/Settings/BlobPersistenceSettings.cs new file mode 100644 index 000000000..4ad6b9cb2 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Settings/BlobPersistenceSettings.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Settings +{ + public class BlobPersistenceSettings + { + public int QuotesDumpPeriodMilliseconds { get; set; } + public int FxRatesDumpPeriodMilliseconds { get; set; } + public int OrderbooksDumpPeriodMilliseconds { get; set; } + public int OrdersDumpPeriodMilliseconds { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/CqrsContextNamesSettings.cs b/src/MarginTrading.Backend.Core/Settings/CqrsContextNamesSettings.cs new file mode 100644 index 000000000..7110fcdde --- /dev/null +++ b/src/MarginTrading.Backend.Core/Settings/CqrsContextNamesSettings.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Lykke.SettingsReader.Attributes; + +namespace MarginTrading.Backend.Core.Settings +{ + public class CqrsContextNamesSettings + { + [Optional] public string AccountsManagement { get; set; } = nameof(AccountsManagement); + + [Optional] public string TradingEngine { get; set; } = nameof(TradingEngine); + + [Optional] public string SettingsService { get; set; } = nameof(SettingsService); + + [Optional] public string Gavel { get; set; } = nameof(Gavel); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/CqrsSettings.cs b/src/MarginTrading.Backend.Core/Settings/CqrsSettings.cs new file mode 100644 index 000000000..6186bb824 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Settings/CqrsSettings.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using JetBrains.Annotations; +using Lykke.SettingsReader.Attributes; + +namespace MarginTrading.Backend.Core.Settings +{ + public class CqrsSettings + { + [AmqpCheck] + public string ConnectionString { get; set; } + + public TimeSpan RetryDelay { get; set; } + + [Optional, CanBeNull] + public string EnvironmentName { get; set; } + + [Optional] + public CqrsContextNamesSettings ContextNames { get; set; } = new CqrsContextNamesSettings(); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/Db.cs b/src/MarginTrading.Backend.Core/Settings/Db.cs index 7449951f9..388379b3b 100644 --- a/src/MarginTrading.Backend.Core/Settings/Db.cs +++ b/src/MarginTrading.Backend.Core/Settings/Db.cs @@ -1,16 +1,34 @@ -using Lykke.SettingsReader.Attributes; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; +using Lykke.SettingsReader.Attributes; namespace MarginTrading.Backend.Core.Settings { + [UsedImplicitly] public class Db { - [AzureTableCheck] + public StorageMode StorageMode { get; set; } + + [Optional] public string LogsConnString { get; set; } - [AzureTableCheck] + public string MarginTradingConnString { get; set; } - [AzureTableCheck] - public string HistoryConnString { get; set; } - [AzureBlobCheck] + public string StateConnString { get; set; } + + public string SqlConnectionString { get; set; } + + public string OrdersHistorySqlConnectionString { get; set; } + + public string OrdersHistoryTableName { get; set; } + + public string PositionsHistorySqlConnectionString { get; set; } + + public string PositionsHistoryTableName { get; set; } + + [Optional] + public QueryTimeouts QueryTimeouts { get; set; } = new QueryTimeouts(); } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/EmailSenderSettings.cs b/src/MarginTrading.Backend.Core/Settings/EmailSenderSettings.cs index 59f255a1b..80e0f9637 100644 --- a/src/MarginTrading.Backend.Core/Settings/EmailSenderSettings.cs +++ b/src/MarginTrading.Backend.Core/Settings/EmailSenderSettings.cs @@ -1,4 +1,7 @@ -using Lykke.SettingsReader.Attributes; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Lykke.SettingsReader.Attributes; namespace MarginTrading.Backend.Core.Settings { diff --git a/src/MarginTrading.Backend.Core/Settings/ExchangeConnectorType.cs b/src/MarginTrading.Backend.Core/Settings/ExchangeConnectorType.cs new file mode 100644 index 000000000..d9b376c46 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Settings/ExchangeConnectorType.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Settings +{ + public enum ExchangeConnectorType + { + FakeExchangeConnector = 0, + RealExchangeConnector = 1, + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/MarginSettings.cs b/src/MarginTrading.Backend.Core/Settings/MarginSettings.cs deleted file mode 100644 index b8cbdb3e6..000000000 --- a/src/MarginTrading.Backend.Core/Settings/MarginSettings.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using JetBrains.Annotations; -using Lykke.SettingsReader.Attributes; -using MarginTrading.Common.RabbitMq; -using MarginTrading.Common.Settings; - -namespace MarginTrading.Backend.Core.Settings -{ - public class MarginSettings - { - public string ApiKey { get; set; } - public string DemoAccountIdPrefix { get; set; } - - #region from Env variables - - [Optional] - public string Env { get; set; } - - [Optional] - public bool IsLive { get; set; } - - #endregion - - public Db Db { get; set; } - - public RabbitMqQueues RabbitMqQueues { get; set; } - - public RabbitMqSettings MarketMakerRabbitMqSettings { get; set; } - - [Optional] - public RabbitMqSettings StpAggregatorRabbitMqSettings { get; set; } - - [Optional, CanBeNull] - public RabbitMqSettings RisksRabbitMqSettings { get; set; } - - [AmqpCheck] - public string MtRabbitMqConnString { get; set; } - - public string[] BaseAccountAssets { get; set; } = new string[0]; - - [Optional] - public AccountAssetsSettings DefaultAccountAssetsSettings { get; set; } - - public RequestLoggerSettings RequestLoggerSettings { get; set; } - - [Optional] - public string ApplicationInsightsKey { get; set; } - - [Optional] - public virtual TelemetrySettings Telemetry { get; set; } - - public int MaxMarketMakerLimitOrderAge { get; set; } - - public ReportingEquivalentPricesSettings[] ReportingEquivalentPricesSettings { get; set; } - - public TimeSpan OvernightSwapCalculationTime { get; set; } - public bool SendOvernightSwapEmails { get; set; } - - [Optional] - public bool UseAzureIdentityGenerator { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/MarginTradingSettings.cs b/src/MarginTrading.Backend.Core/Settings/MarginTradingSettings.cs index 669325590..fae5d4fb5 100644 --- a/src/MarginTrading.Backend.Core/Settings/MarginTradingSettings.cs +++ b/src/MarginTrading.Backend.Core/Settings/MarginTradingSettings.cs @@ -1,8 +1,106 @@ -namespace MarginTrading.Backend.Core.Settings +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using JetBrains.Annotations; +using Lykke.Common.Chaos; +using Lykke.RabbitMqBroker.Subscriber; +using Lykke.SettingsReader.Attributes; +using MarginTrading.Common.Settings; +using RabbitMqSettings = MarginTrading.Common.RabbitMq.RabbitMqSettings; + +namespace MarginTrading.Backend.Core.Settings { public class MarginTradingSettings { - public MarginSettings MarginTradingLive { get; set; } - public MarginSettings MarginTradingDemo { get; set; } + + #region from Env variables + + [Optional] + public string Env { get; set; } + + #endregion + + public string ApiKey { get; set; } + + public Db Db { get; set; } + + public RabbitMqQueues RabbitMqQueues { get; set; } + + [Optional, CanBeNull] + public RabbitMqSettings MarketMakerRabbitMqSettings { get; set; } + + [Optional, CanBeNull] + public RabbitMqSettings StpAggregatorRabbitMqSettings { get; set; } + + [Optional, CanBeNull] + public RabbitMqSettings FxRateRabbitMqSettings { get; set; } + + [Optional, CanBeNull] + public RabbitMqSettings RisksRabbitMqSettings { get; set; } + + [AmqpCheck] + public string MtRabbitMqConnString { get; set; } + + public RequestLoggerSettings RequestLoggerSettings { get; set; } + + [Optional] + public virtual TelemetrySettings Telemetry { get; set; } + + [Optional] + public int MaxMarketMakerLimitOrderAge { get; set; } + + public ReportingEquivalentPricesSettings[] ReportingEquivalentPricesSettings { get; set; } + + [Optional] + public bool UseDbIdentityGenerator { get; set; } + + public BlobPersistenceSettings BlobPersistence { get; set; } + + public CqrsSettings Cqrs { get; set; } + + public ExchangeConnectorType ExchangeConnector { get; set; } + + public bool WriteOperationLog { get; set; } + + public SpecialLiquidationSettings SpecialLiquidation { get; set; } + + [Optional, CanBeNull] + public ChaosSettings ChaosKitty { get; set; } + + [Optional] + public ThrottlingSettings Throttling { get; set; } = new ThrottlingSettings(); + + [Optional] + public bool UseSerilog { get; set; } + + [Optional] + public OvernightMarginSettings OvernightMargin { get; set; } = new OvernightMarginSettings(); + + [Optional] + public string DefaultExternalExchangeId { get; set; } + + [Optional] + public int PendingOrderRetriesThreshold { get; set; } = 100; + + [Optional] + public int SnapshotInsertTimeoutSec { get; set; } = 3600; + + [UsedImplicitly(ImplicitUseKindFlags.Assign)] + public RedisSettings RedisSettings { get; set; } + + [Optional] + public TimeSpan DeduplicationLockExtensionPeriod { get; set; } = TimeSpan.FromSeconds(1); + + [Optional] + public TimeSpan DeduplicationLockExpiryPeriod { get; set; } = TimeSpan.FromSeconds(10); + + public StartupQueuesCheckerSettings StartupQueuesChecker { get; set; } + + [Optional] + public TimeSpan GavelTimeout { get; set; } = TimeSpan.FromSeconds(3); + + [Optional] + public OrderbookValidationSettings OrderbookValidation { get; set; } = new OrderbookValidationSettings(); } -} +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/NotificationSettings.cs b/src/MarginTrading.Backend.Core/Settings/NotificationSettings.cs index bdd1a002d..970797948 100644 --- a/src/MarginTrading.Backend.Core/Settings/NotificationSettings.cs +++ b/src/MarginTrading.Backend.Core/Settings/NotificationSettings.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Core.Settings +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Settings { public class NotificationSettings { diff --git a/src/MarginTrading.Backend.Core/Settings/OrderbookValidationSettings.cs b/src/MarginTrading.Backend.Core/Settings/OrderbookValidationSettings.cs new file mode 100644 index 000000000..a787a66e8 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Settings/OrderbookValidationSettings.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Lykke.SettingsReader.Attributes; + +namespace MarginTrading.Backend.Core.Settings +{ + public class OrderbookValidationSettings + { + [Optional] + public bool ValidateInstrumentStatusForTradingQuotes { get; set; } = false; + + [Optional] + public bool ValidateInstrumentStatusForTradingFx { get; set; } = false; + + [Optional] + public bool ValidateInstrumentStatusForEodQuotes { get; set; } = true; + + [Optional] + public bool ValidateInstrumentStatusForEodFx { get; set; } = true; + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/OvernightMarginSettings.cs b/src/MarginTrading.Backend.Core/Settings/OvernightMarginSettings.cs new file mode 100644 index 000000000..5563d49cf --- /dev/null +++ b/src/MarginTrading.Backend.Core/Settings/OvernightMarginSettings.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using Lykke.SettingsReader.Attributes; + +namespace MarginTrading.Backend.Core.Settings +{ + public class OvernightMarginSettings + { + /// + /// Id of market which determines platform trading schedule. + /// + [Optional] + public string ScheduleMarketId { get; set; } = "PlatformScheduleMarketId"; + + /// + /// Stop out warnings will be done this minutes before activating OvernightMarginParameter. + /// + [Optional] + public int WarnPeriodMinutes { get; set; } = 30; + + /// + /// OvernightMarginParameter will be activated this minutes before EOD. + /// + [Optional] + public int ActivationPeriodMinutes { get; set; } = 30; + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/QueryTimeouts.cs b/src/MarginTrading.Backend.Core/Settings/QueryTimeouts.cs new file mode 100644 index 000000000..1e5377b25 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Settings/QueryTimeouts.cs @@ -0,0 +1,15 @@ +using JetBrains.Annotations; +using Lykke.SettingsReader.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MarginTrading.Backend.Core.Settings +{ + [UsedImplicitly] + public class QueryTimeouts + { + [Optional] + public int GetLastSnapshotTimeoutS { get; set; } = 120; + } +} diff --git a/src/MarginTrading.Backend.Core/Settings/RabbitMqQueues.cs b/src/MarginTrading.Backend.Core/Settings/RabbitMqQueues.cs index 22fb92ec4..714a11dfb 100644 --- a/src/MarginTrading.Backend.Core/Settings/RabbitMqQueues.cs +++ b/src/MarginTrading.Backend.Core/Settings/RabbitMqQueues.cs @@ -1,21 +1,20 @@ -using MarginTrading.Common.RabbitMq; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Common.RabbitMq; namespace MarginTrading.Backend.Core.Settings { public class RabbitMqQueues { - public RabbitMqQueueInfo AccountHistory { get; set; } public RabbitMqQueueInfo OrderHistory { get; set; } - public RabbitMqQueueInfo OrderRejected { get; set; } public RabbitMqQueueInfo OrderbookPrices { get; set; } - public RabbitMqQueueInfo OrderChanged { get; set; } - public RabbitMqQueueInfo AccountChanged { get; set; } - public RabbitMqQueueInfo AccountStopout { get; set; } - public RabbitMqQueueInfo UserUpdates { get; set; } public RabbitMqQueueInfo AccountMarginEvents { get; set; } public RabbitMqQueueInfo AccountStats { get; set; } public RabbitMqQueueInfo Trades { get; set; } + public RabbitMqQueueInfo PositionHistory { get; set; } public RabbitMqQueueInfo MarginTradingEnabledChanged { get; set; } public RabbitMqQueueInfo ExternalOrder { get; set; } + public RabbitMqQueueInfo SettingsChanged { get; set; } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/RedisSettings.cs b/src/MarginTrading.Backend.Core/Settings/RedisSettings.cs new file mode 100644 index 000000000..abeac5bb9 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Settings/RedisSettings.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; + +namespace MarginTrading.Backend.Core.Settings +{ + [UsedImplicitly] + public class RedisSettings + { + public string Configuration { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/ReportingEquivalentPricesSettings.cs b/src/MarginTrading.Backend.Core/Settings/ReportingEquivalentPricesSettings.cs index 765c8c8ac..95692d5c5 100644 --- a/src/MarginTrading.Backend.Core/Settings/ReportingEquivalentPricesSettings.cs +++ b/src/MarginTrading.Backend.Core/Settings/ReportingEquivalentPricesSettings.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Core.Settings +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Settings { public class ReportingEquivalentPricesSettings { diff --git a/src/MarginTrading.Backend.Core/Settings/RiskInformingParams.cs b/src/MarginTrading.Backend.Core/Settings/RiskInformingParams.cs index 07d26ebf4..7c8adee8a 100644 --- a/src/MarginTrading.Backend.Core/Settings/RiskInformingParams.cs +++ b/src/MarginTrading.Backend.Core/Settings/RiskInformingParams.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Core.Settings +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Settings { public class RiskInformingParams { diff --git a/src/MarginTrading.Backend.Core/Settings/RiskInformingSettings.cs b/src/MarginTrading.Backend.Core/Settings/RiskInformingSettings.cs index d13a04b14..662c74b81 100644 --- a/src/MarginTrading.Backend.Core/Settings/RiskInformingSettings.cs +++ b/src/MarginTrading.Backend.Core/Settings/RiskInformingSettings.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Core.Settings +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Settings { public class RiskInformingSettings { diff --git a/src/MarginTrading.Backend.Core/Settings/ServiceClientSettings.cs b/src/MarginTrading.Backend.Core/Settings/ServiceClientSettings.cs new file mode 100644 index 000000000..4fd3f298e --- /dev/null +++ b/src/MarginTrading.Backend.Core/Settings/ServiceClientSettings.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; +using Lykke.SettingsReader.Attributes; + +namespace MarginTrading.Backend.Core.Settings +{ + [UsedImplicitly] + public class ServiceClientSettings + { + [HttpCheck("/api/isalive")] + public string ServiceUrl { get; set; } + + [Optional] + public string ApiKey { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/SettingsServiceClient.cs b/src/MarginTrading.Backend.Core/Settings/SettingsServiceClient.cs new file mode 100644 index 000000000..4c341cc49 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Settings/SettingsServiceClient.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; +using Lykke.SettingsReader.Attributes; + +namespace MarginTrading.Backend.Core.Settings +{ + [UsedImplicitly] + public class SettingsServiceClient + { + [HttpCheck("/api/isalive")] + public string ServiceUrl { get; set; } + + [Optional] + public string ApiKey { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/SpecialLiquidationSettings.cs b/src/MarginTrading.Backend.Core/Settings/SpecialLiquidationSettings.cs new file mode 100644 index 000000000..cd5de31ff --- /dev/null +++ b/src/MarginTrading.Backend.Core/Settings/SpecialLiquidationSettings.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using JetBrains.Annotations; +using Lykke.SettingsReader.Attributes; + +namespace MarginTrading.Backend.Core.Settings +{ + [UsedImplicitly] + public class SpecialLiquidationSettings + { + public bool Enabled { get; set; } + + [Optional] + public decimal FakePriceMultiplier { get; set; } = 1; + + [Optional] + public int PriceRequestTimeoutSec { get; set; } = 3600; + + [Optional] + public TimeSpan RetryTimeout { get; set; } = new TimeSpan(0, 1, 0); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/StartupQueuesCheckerSettings.cs b/src/MarginTrading.Backend.Core/Settings/StartupQueuesCheckerSettings.cs new file mode 100644 index 000000000..0b4a20ae0 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Settings/StartupQueuesCheckerSettings.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace MarginTrading.Backend.Core.Settings +{ + [UsedImplicitly] + public class StartupQueuesCheckerSettings + { + public string ConnectionString { get; set; } + + public string OrderHistoryQueueName { get; set; } + + public string PositionHistoryQueueName { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/StorageMode.cs b/src/MarginTrading.Backend.Core/Settings/StorageMode.cs new file mode 100644 index 000000000..b528daf34 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Settings/StorageMode.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Settings +{ + public enum StorageMode + { + SqlServer = 0, + Azure = 1, + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Settings/TelemetrySettings.cs b/src/MarginTrading.Backend.Core/Settings/TelemetrySettings.cs index b7e8e1934..ee18e8c86 100644 --- a/src/MarginTrading.Backend.Core/Settings/TelemetrySettings.cs +++ b/src/MarginTrading.Backend.Core/Settings/TelemetrySettings.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Core.Settings +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.Settings { /// /// Telementry settings diff --git a/src/MarginTrading.Backend.Core/Settings/ThrottlingSettings.cs b/src/MarginTrading.Backend.Core/Settings/ThrottlingSettings.cs new file mode 100644 index 000000000..d03ca6177 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Settings/ThrottlingSettings.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; +using Lykke.SettingsReader.Attributes; + +namespace MarginTrading.Backend.Core.Settings +{ + [UsedImplicitly] + public class ThrottlingSettings + { + [Optional] public int MarginCallThrottlingPeriodMin { get; set; } = 30; + + [Optional] public int StopOutThrottlingPeriodMin { get; set; } = 1; + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/StateMachines/StateTransition.cs b/src/MarginTrading.Backend.Core/StateMachines/StateTransition.cs new file mode 100644 index 000000000..5cc83a812 --- /dev/null +++ b/src/MarginTrading.Backend.Core/StateMachines/StateTransition.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Core.StateMachines +{ + public class StateTransition + where TState : struct, IConvertible + where TCommand : struct, IConvertible + { + private readonly TState _currentState; + private readonly TCommand _command; + + public StateTransition(TState currentState, TCommand command) + { + _currentState = currentState; + _command = command; + } + + public override int GetHashCode() + { + return 17 + 31 * _currentState.GetHashCode() + 31 * _command.GetHashCode(); + } + + public override bool Equals(object obj) + { + return obj is StateTransition other + && Convert.ToInt32(_currentState) == Convert.ToInt32(other._currentState) + && Convert.ToInt32(_command) == Convert.ToInt32(other._command); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/StateMachines/StatefulObject.cs b/src/MarginTrading.Backend.Core/StateMachines/StatefulObject.cs new file mode 100644 index 000000000..127214703 --- /dev/null +++ b/src/MarginTrading.Backend.Core/StateMachines/StatefulObject.cs @@ -0,0 +1,56 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using MarginTrading.Backend.Core.Exceptions; + +namespace MarginTrading.Backend.Core.StateMachines +{ + /// + /// Stateful object abstraction. Represents a simple state machine. + /// Status change and handler are executed under an object-level lock. + /// Status must set to initial state in derived type ctor. + /// Transitions config must be initialized in derived type's static constructor from TransitionConfig.GetConfig. + /// GetTransitionConfig method must return it's value. + /// + /// + /// + public abstract class StatefulObject + where TState : struct, IConvertible + where TCommand : struct, IConvertible + { + public abstract TState Status { get; protected set; } + + private object LockObj { get; } = new object(); + + private static Dictionary, TState> TransitionConfig { get; } + + static StatefulObject() + { + TransitionConfig = TransitionConfigs.GetConfig(typeof(TState)); + } + + private TState GetTransition(TCommand command) + { + var transition = new StateTransition(Status, command); + + if (!TransitionConfig.TryGetValue(transition, out var transitionConfig)) + { + throw new StateTransitionNotFoundException($"Invalid {GetType().Name} transition: {Status} -> {command}"); + } + + return transitionConfig; + } + + protected void ChangeState(TCommand command, Action handler) + { + lock (LockObj) + { + Status = GetTransition(command); + + handler(); + } + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/StateMachines/TransitionConfigs.cs b/src/MarginTrading.Backend.Core/StateMachines/TransitionConfigs.cs new file mode 100644 index 000000000..5adf5ca35 --- /dev/null +++ b/src/MarginTrading.Backend.Core/StateMachines/TransitionConfigs.cs @@ -0,0 +1,59 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; +using MarginTrading.Common.Extensions; +using MoreLinq; + +namespace MarginTrading.Backend.Core.StateMachines +{ + internal static class TransitionConfigs + { + private static Dictionary> Config { get; } + + static TransitionConfigs() + { + Config = new Dictionary> + { + { + typeof(OrderStatus), new Dictionary<(Enum, Enum), Enum> + { + {(OrderStatus.Placed, OrderCommand.MakeInactive), OrderStatus.Inactive}, + {(OrderStatus.Placed, OrderCommand.Activate), OrderStatus.Active}, + {(OrderStatus.Inactive, OrderCommand.Activate), OrderStatus.Active}, + {(OrderStatus.Placed, OrderCommand.StartExecution), OrderStatus.ExecutionStarted}, + {(OrderStatus.Active, OrderCommand.StartExecution), OrderStatus.ExecutionStarted}, + {(OrderStatus.ExecutionStarted, OrderCommand.CancelExecution), OrderStatus.Active}, + {(OrderStatus.ExecutionStarted, OrderCommand.FinishExecution), OrderStatus.Executed}, + {(OrderStatus.ExecutionStarted, OrderCommand.Reject), OrderStatus.Rejected}, + {(OrderStatus.Inactive, OrderCommand.Cancel), OrderStatus.Canceled}, + {(OrderStatus.Active, OrderCommand.Cancel), OrderStatus.Canceled}, + {(OrderStatus.Active, OrderCommand.Expire), OrderStatus.Expired}, + } + }, + { + typeof(PositionStatus), new Dictionary<(Enum, Enum), Enum> + { + {(PositionStatus.Active, PositionCommand.StartClosing), PositionStatus.Closing}, + {(PositionStatus.Closing, PositionCommand.CancelClosing), PositionStatus.Active}, + {(PositionStatus.Closing, PositionCommand.Close), PositionStatus.Closed}, + } + }, + }; + } + + public static Dictionary, TState> GetConfig(Type objectType) + where TState : struct, IConvertible + where TCommand : struct, IConvertible + { + return Config[objectType].ToDictionary( + x => new StateTransition(x.Key.Item1.ToType(), x.Key.Item2.ToType()), + x => x.Value.ToType()); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Trading/BaseOrderExtension.cs b/src/MarginTrading.Backend.Core/Trading/BaseOrderExtension.cs new file mode 100644 index 000000000..8cf2793a1 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Trading/BaseOrderExtension.cs @@ -0,0 +1,42 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Core.Orders +{ + public static class BaseOrderExtension + { + public static OrderDirection GetOrderDirection(this IBaseOrder order) + { + return order.Volume >= 0 ? OrderDirection.Buy : OrderDirection.Sell; + } + + public static OrderDirection GetOrderDirection(this decimal volume) + { + return volume >= 0 ? OrderDirection.Buy : OrderDirection.Sell; + } + + public static PositionDirection GetPositionDirection(this decimal volume) + { + return volume >= 0 ? PositionDirection.Long : PositionDirection.Short; + } + + + public static OrderDirection GetClosePositionOrderDirection(this decimal volume) + { + return volume >= 0 ? OrderDirection.Sell : OrderDirection.Buy; + } + + public static bool GetIsFullfilled(this IBaseOrder order) + { + return 0 == Math.Round(order.GetRemainingVolume(), MarginTradingHelpers.VolumeAccuracy); + } + + public static decimal GetRemainingVolume(this IBaseOrder order) + { + return Math.Round(Math.Abs(order.Volume) - order.MatchedOrders.SummaryVolume, + MarginTradingHelpers.VolumeAccuracy); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Trading/Order.cs b/src/MarginTrading.Backend.Core/Trading/Order.cs new file mode 100644 index 000000000..5180b22c9 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Trading/Order.cs @@ -0,0 +1,647 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Common; +using MarginTrading.Backend.Core.MatchedOrders; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.StateMachines; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace MarginTrading.Backend.Core.Trading +{ + public class Order : StatefulObject + { + private List _positionsToBeClosed; + private string _parentPositionId; + + #region Properties + + + /// + /// Order ID + /// + [JsonProperty] + public string Id { get; private set; } + + /// + /// Digit order code + /// + [JsonProperty] + public long Code { get; private set; } + + /// + /// Asset Pair ID (eg. EURUSD) + /// + [JsonProperty] + public string AssetPairId { get; private set; } + + /// + /// Order size + /// + [JsonProperty] + public decimal Volume { get; private set; } + + /// + /// Ordr direction (Buy/Sell) + /// + [JsonProperty] + public OrderDirection Direction { get; private set; } + + /// + /// Date when order was created + /// + [JsonProperty] + public DateTime Created { get; private set; } + + /// + /// Date when order was activated + /// + [JsonProperty] + public DateTime? Activated { get; private set; } + + /// + /// Date when order was modified + /// + [JsonProperty] + public DateTime LastModified { get; private set; } + + /// + /// Date when order will expire (null for Market) + /// + [JsonProperty] + public DateTime? Validity { get; private set; } + + /// + /// Date when order started execution + /// + [JsonProperty] + public DateTime? ExecutionStarted { get; private set; } + + /// + /// Date when order was executed + /// + [JsonProperty] + public DateTime? Executed { get; private set; } + + /// + /// Date when order was canceled + /// + [JsonProperty] + public DateTime? Canceled { get; private set; } + + /// + /// Date when order was rejected + /// + [JsonProperty] + public DateTime? Rejected { get; private set; } + + /// + /// Trading account ID + /// + [JsonProperty] + public string AccountId { get; private set; } + + /// + /// Trading conditions ID + /// + [JsonProperty] + public string TradingConditionId { get; private set; } + + /// + /// Account base asset ID + /// + [JsonProperty] + public string AccountAssetId { get; private set; } + + /// + /// Price level when order should be executed + /// + [JsonProperty] + public decimal? Price { get; private set; } + + /// + /// Price of order execution + /// + [JsonProperty] + public decimal? ExecutionPrice { get; private set; } + + /// + /// Asset for representation of equivalent price + /// + [JsonProperty] + public string EquivalentAsset { get; private set; } + + /// + /// Rate for calculation of equivalent price + /// + [JsonProperty] + public decimal EquivalentRate { get; private set; } + + /// + /// Rate for calculation of price in account asset + /// + [JsonProperty] + public decimal FxRate { get; private set; } + + /// + /// FX asset pair id + /// + [JsonProperty] + public string FxAssetPairId { get; protected set; } + + /// + /// Shows if account asset id is directly related on asset pair quote asset. + /// I.e. AssetPair is {BaseId, QuoteId} and FxAssetPair is {QuoteId, AccountAssetId} => Straight + /// If AssetPair is {BaseId, QuoteId} and FxAssetPair is {AccountAssetId, QuoteId} => Reverse + /// + [JsonProperty] + public FxToAssetPairDirection FxToAssetPairDirection { get; protected set; } + + /// + /// Current order status + /// + [JsonProperty] + public sealed override OrderStatus Status { get; protected set; } + + public bool IsExecutionNotStarted => Status != OrderStatus.Executed && Status != OrderStatus.ExecutionStarted; + + /// + /// Order fill type + /// + [JsonProperty] + public OrderFillType FillType { get; private set; } + + /// + /// Reject reason + /// + [JsonProperty] + public OrderRejectReason RejectReason { get; private set; } + + /// + /// Human-readable reject reason + /// + [JsonProperty] + public string RejectReasonText { get; private set; } + + /// + /// Additional comment + /// + [JsonProperty] + public string Comment { get; private set; } + + /// + /// ID of external order (for STP mode) + /// + [JsonProperty] + public string ExternalOrderId { get; private set; } + + /// + /// ID of external LP (for STP mode) + /// + [JsonProperty] + public string ExternalProviderId { get; private set; } + + /// + /// Matching engine ID + /// + [JsonProperty] + public string MatchingEngineId { get; private set; } + + /// + /// Legal Entity ID + /// + [JsonProperty] + public string LegalEntity { get; private set; } + + /// + /// Force open of new position + /// + [JsonProperty] + public bool ForceOpen { get; private set; } + + /// + /// Order type + /// + [JsonProperty] + public OrderType OrderType { get; private set; } + + /// + /// ID of parent order (for related orders) + /// + [JsonProperty] + public string ParentOrderId { get; private set; } + + /// + /// ID of parent position (for related orders) + /// + [JsonProperty] + public string ParentPositionId + { + get => _parentPositionId; + + //TODO: remove after version is applied and data is migrated + private set + { + _parentPositionId = value; + + if (string.IsNullOrEmpty(value)) + { + return; + } + + if (_positionsToBeClosed == null) + _positionsToBeClosed = new List(); + + if (!_positionsToBeClosed.Contains(value)) + { + _positionsToBeClosed.Add(value); + } + } + } + + /// + /// Order initiator + /// + [JsonProperty] + public OriginatorType Originator { get; private set; } + + /// + /// Matched orders for execution + /// + [JsonProperty] + public MatchedOrderCollection MatchedOrders { get; private set; } + + /// + /// Related orders + /// + [JsonProperty] + public List RelatedOrders { get; private set; } + + private string _additionalInfo; + /// + /// Additional information about order, changed every time, when order is changed via user request + /// + [JsonProperty] + public string AdditionalInfo + { + get => _additionalInfo; + set + { + _additionalInfo = value; + UpdateHasOnBehalf(value); + } + } + + /// + /// Max distance between order price and parent order price (only for trailing order) + /// + [JsonProperty] + public decimal? TrailingDistance { get; private set; } + + /// + /// The correlation identifier. + /// In every operation that results in the creation of a new message the correlationId should be copied from + /// the inbound message to the outbound message. This facilitates tracking of an operation through the system. + /// If there is no inbound identifier then one should be created eg. on the service layer boundary (API). + /// + [JsonProperty] + public string CorrelationId { get; private set; } + + [JsonProperty] + public List PositionsToBeClosed + { + get => _positionsToBeClosed.Distinct().ToList(); + + private set => _positionsToBeClosed = value?.Distinct().ToList() ?? new List(); + } + + /// + /// Order execution rank, calculated based on type and direction + /// + [JsonProperty] + public byte ExecutionRank { get; private set; } + + /// + /// Order execution price rank, calculated based on type, direction and price + /// + [JsonProperty] + public decimal? ExecutionPriceRank { get; private set; } + + /// + /// Number of pending order retries passed + /// + [JsonProperty] + public int PendingOrderRetriesCount { get; private set; } + + /// + /// Show if order was managed on behalf at least once + /// + [JsonProperty] + public bool HasOnBehalf { get; set; } + + #endregion + + /// + /// For testing and deserialization + /// + [JsonConstructor] + protected Order() + { + MatchedOrders = new MatchedOrderCollection(); + RelatedOrders = new List(); + _positionsToBeClosed = new List(); + } + + public Order(string id, long code, string assetPairId, decimal volume, + DateTime created, DateTime lastModified, DateTime? validity, string accountId, string tradingConditionId, + string accountAssetId, decimal? price, string equivalentAsset, OrderFillType fillType, string comment, + string legalEntity, bool forceOpen, OrderType orderType, string parentOrderId, string parentPositionId, + OriginatorType originator, decimal equivalentRate, decimal fxRate, + string fxAssetPairId, FxToAssetPairDirection fxToAssetPairDirection, OrderStatus status, + string additionalInfo, string correlationId, List positionsToBeClosed = null, + string externalProviderId = null) + { + Id = id; + Code = code; + AssetPairId = assetPairId; + Volume = volume; + Created = created; + LastModified = lastModified; + Validity = validity; + AccountId = accountId; + TradingConditionId = tradingConditionId; + AccountAssetId = accountAssetId; + Price = price; + EquivalentAsset = equivalentAsset; + FillType = fillType; + Comment = comment; + LegalEntity = legalEntity; + ForceOpen = forceOpen; + OrderType = orderType; + ParentOrderId = parentOrderId; + ParentPositionId = parentPositionId; + Originator = originator; + EquivalentRate = equivalentRate; + FxRate = fxRate; + FxAssetPairId = fxAssetPairId; + FxToAssetPairDirection = fxToAssetPairDirection; + Direction = volume.GetOrderDirection(); + Status = status; + AdditionalInfo = additionalInfo; + CorrelationId = correlationId; + _positionsToBeClosed = positionsToBeClosed?.Distinct().ToList() ?? (string.IsNullOrEmpty(parentPositionId) + ? new List() + : new List {parentPositionId}); + ExternalProviderId = externalProviderId; + ExecutionRank = (byte) (OrderType.GetExecutionRank() | Direction.GetExecutionRank()); + SetExecutionSortRank(); + MatchedOrders = new MatchedOrderCollection(); + RelatedOrders = new List(); + } + + #region Actions + + public void ChangePrice(decimal newPrice, DateTime dateTime, OriginatorType originator, string additionalInfo, + string correlationId, bool shouldUpdateTrailingDistance = false) + { + if (OrderType == OrderType.TrailingStop && shouldUpdateTrailingDistance) + { + TrailingDistance += newPrice - Price; + } + + LastModified = dateTime; + Price = newPrice; + Originator = originator; + AdditionalInfo = additionalInfo ?? AdditionalInfo; + CorrelationId = correlationId; + SetExecutionSortRank(); + } + + public void ChangeValidity(DateTime? newValidity, DateTime dateTime, OriginatorType originator, string additionalInfo, + string correlationId) + { + LastModified = dateTime; + Validity = newValidity; + Originator = originator; + AdditionalInfo = additionalInfo ?? AdditionalInfo; + CorrelationId = correlationId; + } + + public void ChangeForceOpen(bool newForceOpen, DateTime dateTime, OriginatorType originator, string additionalInfo, + string correlationId) + { + LastModified = dateTime; + ForceOpen = newForceOpen; + Originator = originator; + AdditionalInfo = additionalInfo ?? AdditionalInfo; + CorrelationId = correlationId; + } + + public void ChangeVolume(decimal newVolume, DateTime dateTime, OriginatorType originator) + { + LastModified = dateTime; + Volume = newVolume; + Originator = originator; + } + + public void SetTrailingDistance(decimal parentOrderPrice) + { + if (OrderType == OrderType.TrailingStop && Price.HasValue) + { + TrailingDistance = Price.Value - parentOrderPrice; + } + } + + public void SetRates(decimal equivalentRate, decimal fxRate) + { + EquivalentRate = equivalentRate; + FxRate = fxRate; + } + + public void AddRelatedOrder(Order order) + { + var info = new RelatedOrderInfo {Type = order.OrderType, Id = order.Id}; + + if (!RelatedOrders.Contains(info)) + RelatedOrders.Add(info); + } + + public void RemoveRelatedOrder(string relatedOrderId) + { + var relatedOrder = RelatedOrders.FirstOrDefault(o => o.Id == relatedOrderId); + + if (relatedOrder != null) + RelatedOrders.Remove(relatedOrder); + } + + private void SetExecutionSortRank() + { + if (Price == null) + return; + + //for Buy Limit and Sell Stop order should be Desc, to have Asc always, inverse price + if (OrderType == OrderType.Limit && Direction == OrderDirection.Buy + || + OrderType == OrderType.Stop && Direction == OrderDirection.Sell) + { + ExecutionPriceRank = -Price; + } + else + { + ExecutionPriceRank = Price; + } + } + + public void PartiallyExecute(DateTime dateTime, MatchedOrderCollection matchedOrders) + { + LastModified = dateTime; + MatchedOrders.AddRange(matchedOrders); + } + + #endregion Actions + + #region State changes + + public void MakeInactive(DateTime dateTime) + { + ChangeState(OrderCommand.MakeInactive, () => + { + LastModified = dateTime; + }); + } + + public void Activate(DateTime dateTime, bool relinkFromOrderToPosition, decimal? positionClosePrice) + { + ChangeState(OrderCommand.Activate, () => + { + Activated = dateTime; + LastModified = dateTime; + + if (relinkFromOrderToPosition) + { + ParentPositionId = ParentOrderId; + + if (!PositionsToBeClosed.Contains(ParentOrderId)) + { + PositionsToBeClosed.Add(ParentOrderId); + } + } + + if (positionClosePrice.HasValue && OrderType == OrderType.TrailingStop) + { + SetTrailingDistance(positionClosePrice.Value); + } + }); + } + + public void StartExecution(DateTime dateTime, string matchingEngineId) + { + ChangeState(OrderCommand.StartExecution, () => + { + ExecutionStarted = dateTime; + LastModified = dateTime; + MatchingEngineId = matchingEngineId; + }); + } + + public void CancelExecution(DateTime dateTime) + { + ChangeState(OrderCommand.CancelExecution, () => + { + ExecutionStarted = null; + LastModified = dateTime; + MatchingEngineId = null; + + PendingOrderRetriesCount++; + }); + } + + public void Execute(DateTime dateTime, MatchedOrderCollection matchedOrders, int assetPairAccuracy) + { + ChangeState(OrderCommand.FinishExecution, () => + { + var externalOrderId = string.Empty; + var externalProviderId = string.Empty; + + if (matchedOrders.Count == 1) + { + var matched = matchedOrders.First(); + + if (matched.IsExternal) + { + externalOrderId = matched.OrderId; + externalProviderId = matched.MarketMakerId; + } + } + + Executed = dateTime; + LastModified = dateTime; + ExecutionPrice = Math.Round(matchedOrders.WeightedAveragePrice, assetPairAccuracy); + ExternalOrderId = externalOrderId; + ExternalProviderId = externalProviderId; + MatchedOrders.AddRange(matchedOrders); + }); + } + + public void Reject(OrderRejectReason reason, string reasonText, string comment, DateTime dateTime) + { + ChangeState(OrderCommand.Reject, () => + { + RejectReason = reason; + RejectReasonText = reasonText; + Comment = comment; + Rejected = dateTime; + LastModified = dateTime; + }); + } + + public void Cancel(DateTime dateTime, string additionalInfo, string correlationId) + { + ChangeState(OrderCommand.Cancel, () => + { + Canceled = dateTime; + LastModified = dateTime; + AdditionalInfo = additionalInfo ?? AdditionalInfo; + CorrelationId = correlationId; + }); + } + + public void Expire(DateTime dateTime) + { + ChangeState(OrderCommand.Expire, () => + { + Canceled = dateTime; + LastModified = dateTime; + }); + } + + #endregion State changes + + #region Helpers + + private void UpdateHasOnBehalf(string additionalInfo) + { + HasOnBehalf |= GetOnBehalfFlag(additionalInfo); + } + + private static bool GetOnBehalfFlag(string additionalInfo) + { + try + { + return JsonConvert.DeserializeAnonymousType(additionalInfo, new {WithOnBehalfFees = false}) + .WithOnBehalfFees; + } + catch + { + return false; + } + } + + #endregion Helpers + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Trading/OrderExtensions.cs b/src/MarginTrading.Backend.Core/Trading/OrderExtensions.cs new file mode 100644 index 000000000..2abdbf301 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Trading/OrderExtensions.cs @@ -0,0 +1,208 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; +using Newtonsoft.Json; + +namespace MarginTrading.Backend.Core +{ + public static class OrderExtensions + { + public static bool IsSuitablePriceForPendingOrder(this Order order, decimal price) + { + switch (order.OrderType) + { + case OrderType.Limit: + case OrderType.TakeProfit: + return order.Direction == OrderDirection.Buy && price <= order.Price + || order.Direction == OrderDirection.Sell && price >= order.Price; + case OrderType.Stop: + case OrderType.StopLoss: + case OrderType.TrailingStop: + return order.Direction == OrderDirection.Buy && price >= order.Price + || order.Direction == OrderDirection.Sell && price <= order.Price; + default: + return false; + } + } + + public static decimal GetTotalFpl(this Position position, decimal swaps) + { + return position.GetFpl() - position.GetOpenCommission() - position.GetCloseCommission() - swaps; + } + + public static decimal GetTotalFpl(this Position order) + { + return Math.Round(GetTotalFpl(order, order.GetSwaps()), order.CalculateFplData().AccountBaseAssetAccuracy); + } + + private static FplData CalculateFplData(this Position position) + { + if (position.FplData.ActualHash != position.FplData.CalculatedHash || position.FplData.ActualHash == 0) + { + MtServiceLocator.FplService.UpdatePositionFpl(position); + } + + return position.FplData; + } + + public static decimal GetFpl(this Position order) + { + return order.CalculateFplData().Fpl; + } + + public static decimal GetMarginRate(this Position order) + { + return order.CalculateFplData().MarginRate; + } + + public static decimal GetOvernightMarginMaintenance(this Position position) + { + return MtServiceLocator.FplService.CalculateOvernightMaintenanceMargin(position); + } + + public static decimal GetMarginMaintenance(this Position order) + { + return order.CalculateFplData().MarginMaintenance; + } + + public static decimal GetMarginInit(this Position order) + { + return order.CalculateFplData().MarginInit; + } + + public static decimal GetInitialMargin(this Position order) + { + return order.CalculateFplData().InitialMargin; + } + + public static void FplDataShouldBeRecalculated(this Position position) + { + position.FplData.ActualHash++; + } + + public static decimal GetSwaps(this Position order) + { + return MtServiceLocator.SwapCommissionService.GetSwaps(order); + } + + public static decimal GetOpenCommission(this Position order) + { + return Math.Abs(order.Volume) * order.OpenCommissionRate; + } + + public static decimal GetCloseCommission(this Position order) + { + return Math.Abs(order.Volume) * order.CloseCommissionRate; + } + + public static OrderDirection GetOpositeDirection(this OrderDirection orderType) + { + return orderType == OrderDirection.Buy ? OrderDirection.Sell : OrderDirection.Buy; + } + + public static PositionDirection GetClosePositionDirection(this OrderDirection orderType) + { + return orderType == OrderDirection.Buy ? PositionDirection.Short : PositionDirection.Long; + } + + public static OrderDirection GetOrderDirectionToMatchInOrderBook(this OrderDirection orderType) + { + return orderType.GetOpositeDirection(); + } + + public static bool IsBasicPendingOrder(this Order order) + { + return order.OrderType == OrderType.Limit || order.OrderType == OrderType.Stop; + } + + public static bool IsBasicOrder(this Order order) + { + return order.OrderType == OrderType.Market || + order.IsBasicPendingOrder(); + } + + public static PositionCloseReason GetCloseReason(this OrderType orderType) + { + switch (orderType) + { + case OrderType.StopLoss: + case OrderType.TrailingStop: + return PositionCloseReason.StopLoss; + case OrderType.TakeProfit: + return PositionCloseReason.TakeProfit; + default: + return PositionCloseReason.Close; + } + } + + public static byte GetExecutionRank(this OrderType orderType) + { + switch (orderType) + { + case OrderType.Market: + return 0; + case OrderType.Stop: + case OrderType.StopLoss: + case OrderType.TrailingStop: + return 2; + case OrderType.Limit: + case OrderType.TakeProfit: + return 4; + default: + return 8; + } + } + + public static byte GetExecutionRank(this OrderDirection orderDirection) + { + switch (orderDirection) + { + case OrderDirection.Sell: + return 0; + case OrderDirection.Buy: + return 1; + default: + return 0; + } + } + + public static ICollection GetSortedForExecution(this IEnumerable orders) + { + return orders.OrderBy(o => o.ExecutionRank) + .ThenBy(o => o.ExecutionPriceRank) + .ThenBy(o => o.Created) + .ToList(); + } + + public static bool IsCancellationTrade(this string additionalInfo, out string cancellationTradeExternalId) + { + try + { + var model = JsonConvert.DeserializeAnonymousType(additionalInfo, + new {IsCancellationTrade = false, CancellationTradeExternalId = ""}); + + cancellationTradeExternalId = model.CancellationTradeExternalId; + + return model.IsCancellationTrade; + } + catch + { + cancellationTradeExternalId = null; + return false; + } + } + + public static string MakeNonOnBehalf(this string additionalInfo) + { + var obj = JsonConvert.DeserializeObject(additionalInfo); + obj.WithOnBehalfFees = false; + return JsonConvert.SerializeObject(obj); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/TradingConditions/AccountAssetPair.cs b/src/MarginTrading.Backend.Core/TradingConditions/AccountAssetPair.cs deleted file mode 100644 index a50ca8539..000000000 --- a/src/MarginTrading.Backend.Core/TradingConditions/AccountAssetPair.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace MarginTrading.Backend.Core.TradingConditions -{ - public class AccountAssetPair : IAccountAssetPair - { - public string TradingConditionId { get; set; } - public string BaseAssetId { get; set; } - public string Instrument { get; set; } - public int LeverageInit { get; set; } - public int LeverageMaintenance { get; set; } - public decimal SwapLong { get; set; } - public decimal SwapShort { get; set; } - public decimal OvernightSwapLong { get; set; } - public decimal OvernightSwapShort { get; set; } - public decimal CommissionLong { get; set; } - public decimal CommissionShort { get; set; } - public decimal CommissionLot { get; set; } - public decimal DeltaBid { get; set; } - public decimal DeltaAsk { get; set; } - public decimal DealLimit { get; set; } - public decimal PositionLimit { get; set; } - - public static AccountAssetPair Create(IAccountAssetPair src) - { - return new AccountAssetPair - { - TradingConditionId = src.TradingConditionId, - BaseAssetId = src.BaseAssetId, - Instrument = src.Instrument, - LeverageInit = src.LeverageInit, - LeverageMaintenance = src.LeverageMaintenance, - SwapLong = src.SwapLong, - SwapShort = src.SwapShort, - OvernightSwapLong = src.OvernightSwapLong, - OvernightSwapShort = src.OvernightSwapShort, - CommissionLong = src.CommissionLong, - CommissionShort = src.CommissionShort, - CommissionLot = src.CommissionLot, - DeltaBid = src.DeltaBid, - DeltaAsk = src.DeltaAsk, - DealLimit = src.DealLimit, - PositionLimit = src.PositionLimit - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/TradingConditions/AccountGroup.cs b/src/MarginTrading.Backend.Core/TradingConditions/AccountGroup.cs deleted file mode 100644 index 644b78503..000000000 --- a/src/MarginTrading.Backend.Core/TradingConditions/AccountGroup.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace MarginTrading.Backend.Core.TradingConditions -{ - public class AccountGroup : IAccountGroup - { - public string TradingConditionId { get; set; } - public string BaseAssetId { get; set; } - public decimal MarginCall { get; set; } - public decimal StopOut { get; set; } - public decimal DepositTransferLimit { get; set; } - public decimal ProfitWithdrawalLimit { get; set; } - - public static AccountGroup Create(IAccountGroup src) - { - return new AccountGroup - { - TradingConditionId = src.TradingConditionId, - BaseAssetId = src.BaseAssetId, - MarginCall = src.MarginCall, - StopOut = src.StopOut, - DepositTransferLimit = src.DepositTransferLimit, - ProfitWithdrawalLimit = src.ProfitWithdrawalLimit - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/TradingConditions/IAccountAssetPair.cs b/src/MarginTrading.Backend.Core/TradingConditions/IAccountAssetPair.cs deleted file mode 100644 index 8a3c8fcd4..000000000 --- a/src/MarginTrading.Backend.Core/TradingConditions/IAccountAssetPair.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace MarginTrading.Backend.Core.TradingConditions -{ - public interface IAccountAssetPair - { - string TradingConditionId { get; } - string BaseAssetId { get; } - string Instrument { get; } - int LeverageInit { get; } - int LeverageMaintenance { get; } - decimal SwapLong { get; } - decimal SwapShort { get; } - decimal OvernightSwapLong { get; } - decimal OvernightSwapShort { get; } - decimal CommissionLong { get; } - decimal CommissionShort { get; } - decimal CommissionLot { get; } - decimal DeltaBid { get; } - decimal DeltaAsk { get; } - decimal DealLimit { get; } - decimal PositionLimit { get; } - } -} diff --git a/src/MarginTrading.Backend.Core/TradingConditions/IAccountGroup.cs b/src/MarginTrading.Backend.Core/TradingConditions/IAccountGroup.cs deleted file mode 100644 index 6f53b354d..000000000 --- a/src/MarginTrading.Backend.Core/TradingConditions/IAccountGroup.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace MarginTrading.Backend.Core.TradingConditions -{ - public interface IAccountGroup - { - string TradingConditionId { get; } - string BaseAssetId { get; } - decimal MarginCall { get; } - decimal StopOut { get; } - decimal DepositTransferLimit { get; } - decimal ProfitWithdrawalLimit { get; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/TradingConditions/ITradingCondition.cs b/src/MarginTrading.Backend.Core/TradingConditions/ITradingCondition.cs index f676f512d..5fb09fbb2 100644 --- a/src/MarginTrading.Backend.Core/TradingConditions/ITradingCondition.cs +++ b/src/MarginTrading.Backend.Core/TradingConditions/ITradingCondition.cs @@ -1,4 +1,9 @@ -namespace MarginTrading.Backend.Core.TradingConditions +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace MarginTrading.Backend.Core.TradingConditions { public interface ITradingCondition { @@ -6,5 +11,12 @@ public interface ITradingCondition string Name { get; } bool IsDefault { get; } string LegalEntity { get; } + decimal MarginCall1 { get; } + decimal MarginCall2 { get; } + decimal StopOut { get; } + decimal DepositLimit { get; } + decimal WithdrawalLimit { get; } + string LimitCurrency { get; } + List BaseAssets { get; } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/TradingConditions/ITradingInstrument.cs b/src/MarginTrading.Backend.Core/TradingConditions/ITradingInstrument.cs new file mode 100644 index 000000000..1fd166d0f --- /dev/null +++ b/src/MarginTrading.Backend.Core/TradingConditions/ITradingInstrument.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core.TradingConditions +{ + public interface ITradingInstrument + { + string TradingConditionId { get; } + string Instrument { get; } + int LeverageInit { get; } + int LeverageMaintenance { get; } + decimal SwapLong { get; } + decimal SwapShort { get; } + + decimal Delta { get; } + decimal DealMinLimit { get; } + decimal DealMaxLimit { get; } + decimal PositionLimit { get; } + bool ShortPosition { get; } + decimal OvernightMarginMultiplier { get; } + + decimal CommissionRate { get; } + decimal CommissionMin { get; } + decimal CommissionMax { get; } + string CommissionCurrency { get; } + } +} diff --git a/src/MarginTrading.Backend.Core/TradingConditions/TradingCondition.cs b/src/MarginTrading.Backend.Core/TradingConditions/TradingCondition.cs index 347d663cf..19179c4fd 100644 --- a/src/MarginTrading.Backend.Core/TradingConditions/TradingCondition.cs +++ b/src/MarginTrading.Backend.Core/TradingConditions/TradingCondition.cs @@ -1,4 +1,9 @@ -namespace MarginTrading.Backend.Core.TradingConditions +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace MarginTrading.Backend.Core.TradingConditions { public class TradingCondition : ITradingCondition { @@ -6,16 +11,12 @@ public class TradingCondition : ITradingCondition public string Name { get; set; } public bool IsDefault { get; set; } public string LegalEntity { get; set; } - - public static TradingCondition Create(ITradingCondition src) - { - return new TradingCondition - { - Id = src.Id, - Name = src.Name, - IsDefault = src.IsDefault, - LegalEntity = src.LegalEntity, - }; - } + public decimal MarginCall1 { get; set; } + public decimal MarginCall2 { get; set; } + public decimal StopOut { get; set; } + public decimal DepositLimit { get; set; } + public decimal WithdrawalLimit { get; set; } + public string LimitCurrency { get; set; } + public List BaseAssets { get; set; } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/TradingConditions/TradingInstrument.cs b/src/MarginTrading.Backend.Core/TradingConditions/TradingInstrument.cs new file mode 100644 index 000000000..5dc0f6d60 --- /dev/null +++ b/src/MarginTrading.Backend.Core/TradingConditions/TradingInstrument.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; + +namespace MarginTrading.Backend.Core.TradingConditions +{ + [UsedImplicitly] + public class TradingInstrument : ITradingInstrument + { + public string TradingConditionId { get; set; } + public string Instrument { get; set; } + public int LeverageInit { get; set; } + public int LeverageMaintenance { get; set; } + public decimal SwapLong { get; set; } + public decimal SwapShort { get; set; } + public decimal Delta { get; set; } + public decimal DealMinLimit { get; set; } + public decimal DealMaxLimit { get; set; } + public decimal PositionLimit { get; set; } + public bool ShortPosition { get; set; } + public decimal OvernightMarginMultiplier { get; set; } + + public decimal CommissionRate { get; set; } + public decimal CommissionMin { get; set; } + public decimal CommissionMax { get; set; } + public string CommissionCurrency { get; set; } + + public (string, string) GetKey() => (TradingConditionId, Instrument); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Workflow/DeleteAccountsOperationData.cs b/src/MarginTrading.Backend.Core/Workflow/DeleteAccountsOperationData.cs new file mode 100644 index 000000000..5df544e43 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Workflow/DeleteAccountsOperationData.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace MarginTrading.Backend.Core +{ + public class DeleteAccountsOperationData: OperationDataBase + { + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Workflow/EodOperationData.cs b/src/MarginTrading.Backend.Core/Workflow/EodOperationData.cs new file mode 100644 index 000000000..5f7f735d5 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Workflow/EodOperationData.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Core.Workflow +{ + public class EodOperationData : OperationDataBase + { + public DateTime TradingDay { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Workflow/IOperationExecutionInfo.cs b/src/MarginTrading.Backend.Core/Workflow/IOperationExecutionInfo.cs new file mode 100644 index 000000000..90f942932 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Workflow/IOperationExecutionInfo.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Core +{ + public interface IOperationExecutionInfo where T: class + { + string OperationName { get; } + string Id { get; } + DateTime LastModified { get; } + + T Data { get; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Workflow/LiquidationOperationData.cs b/src/MarginTrading.Backend.Core/Workflow/LiquidationOperationData.cs new file mode 100644 index 000000000..b1abc08ee --- /dev/null +++ b/src/MarginTrading.Backend.Core/Workflow/LiquidationOperationData.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using MarginTrading.Backend.Core.Orders; + +namespace MarginTrading.Backend.Core +{ + public class LiquidationOperationData : OperationDataBase + { + public string AccountId { get; set; } + public string AssetPairId { get; set; } + public PositionDirection? Direction { get; set; } + public string QuoteInfo { get; set; } + public List ProcessedPositionIds { get; set; } + public List LiquidatedPositionIds { get; set; } + public LiquidationType LiquidationType { get; set; } + public OriginatorType OriginatorType { get; set; } + public string AdditionalInfo { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Workflow/LiquidationOperationState.cs b/src/MarginTrading.Backend.Core/Workflow/LiquidationOperationState.cs new file mode 100644 index 000000000..c7baa2c2a --- /dev/null +++ b/src/MarginTrading.Backend.Core/Workflow/LiquidationOperationState.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace MarginTrading.Backend.Core +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum LiquidationOperationState + { + Initiated = 0, + Started = 1, + SpecialLiquidationStarted = 2, + Finished = 3, + Failed = 4, + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Workflow/OperationData.cs b/src/MarginTrading.Backend.Core/Workflow/OperationData.cs new file mode 100644 index 000000000..178f3d6ca --- /dev/null +++ b/src/MarginTrading.Backend.Core/Workflow/OperationData.cs @@ -0,0 +1,9 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core +{ + public class OperationData : OperationDataBase + { + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Workflow/OperationDataBase.cs b/src/MarginTrading.Backend.Core/Workflow/OperationDataBase.cs new file mode 100644 index 000000000..e698dc60d --- /dev/null +++ b/src/MarginTrading.Backend.Core/Workflow/OperationDataBase.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; + +namespace MarginTrading.Backend.Core +{ + public class OperationDataBase + where TState : struct, IConvertible + { + public TState State { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Workflow/OperationExecutionInfo.cs b/src/MarginTrading.Backend.Core/Workflow/OperationExecutionInfo.cs new file mode 100644 index 000000000..d37dcfc4a --- /dev/null +++ b/src/MarginTrading.Backend.Core/Workflow/OperationExecutionInfo.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using JetBrains.Annotations; + +namespace MarginTrading.Backend.Core +{ + public class OperationExecutionInfo : IOperationExecutionInfo + where T: class + { + public string OperationName { get; } + public string Id { get; } + public DateTime LastModified { get; } + + public T Data { get; } + + public OperationExecutionInfo([NotNull] string operationName, [NotNull] string id, DateTime lastModified, + [NotNull] T data) + { + OperationName = operationName ?? throw new ArgumentNullException(nameof(operationName)); + Id = id ?? throw new ArgumentNullException(nameof(id)); + LastModified = lastModified; + Data = data ?? throw new ArgumentNullException(nameof(data)); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Workflow/OperationState.cs b/src/MarginTrading.Backend.Core/Workflow/OperationState.cs new file mode 100644 index 000000000..ed241474e --- /dev/null +++ b/src/MarginTrading.Backend.Core/Workflow/OperationState.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace MarginTrading.Backend.Core +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum OperationState + { + Initiated = 0, + Started = 1, + Finished = 2, + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Workflow/SpecialLiquidationOperationData.cs b/src/MarginTrading.Backend.Core/Workflow/SpecialLiquidationOperationData.cs new file mode 100644 index 000000000..08eaff4b2 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Workflow/SpecialLiquidationOperationData.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using JetBrains.Annotations; +using MarginTrading.Backend.Core.Orders; + +namespace MarginTrading.Backend.Core +{ + public class SpecialLiquidationOperationData : OperationDataBase + { + public string Instrument { get; set; } + public List PositionIds { get; set; } + public decimal Volume { get; set; } + public decimal Price { get; set; } + public string ExternalProviderId { get; set; } + [CanBeNull] + public string AccountId { get; set; } + [CanBeNull] + public string CausationOperationId { get; set; } + public string AdditionalInfo { get; set; } + public OriginatorType OriginatorType { get; set; } + public int RequestNumber { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Workflow/SpecialLiquidationOperationState.cs b/src/MarginTrading.Backend.Core/Workflow/SpecialLiquidationOperationState.cs new file mode 100644 index 000000000..a7b1f4a15 --- /dev/null +++ b/src/MarginTrading.Backend.Core/Workflow/SpecialLiquidationOperationState.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace MarginTrading.Backend.Core +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum SpecialLiquidationOperationState + { + Initiated = 0, + Started = 1, + PriceRequested = 2, + PriceReceived = 3, + ExternalOrderExecuted = 4, + InternalOrderExecutionStarted = 5, + InternalOrdersExecuted = 6, + Finished = 7, + OnTheWayToFail = 8, + Failed = 9, + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Core/Workflow/WithdrawalFreezeOperationData.cs b/src/MarginTrading.Backend.Core/Workflow/WithdrawalFreezeOperationData.cs new file mode 100644 index 000000000..9fcf27b0d --- /dev/null +++ b/src/MarginTrading.Backend.Core/Workflow/WithdrawalFreezeOperationData.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Core +{ + public class WithdrawalFreezeOperationData : OperationDataBase + { + public string AccountId { get; set; } + public decimal Amount { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/AccountManager.cs b/src/MarginTrading.Backend.Services/AccountManager.cs index 81c88750a..e1bf723e0 100644 --- a/src/MarginTrading.Backend.Services/AccountManager.cs +++ b/src/MarginTrading.Backend.Services/AccountManager.cs @@ -1,457 +1,140 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; +using AutoMapper; using Common; using Common.Log; -using JetBrains.Annotations; -using Lykke.Service.ClientAccount.Client; -using Lykke.Service.ClientAccount.Client.AutorestClient.Models; +using MarginTrading.AccountsManagement.Contracts; +using MarginTrading.AccountsManagement.Contracts.Models; using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.Mappers; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; using MarginTrading.Backend.Core.Settings; -using MarginTrading.Backend.Services.Events; +using MarginTrading.Backend.Core.Trading; using MarginTrading.Backend.Services.Notifications; +using MarginTrading.Common.Services; using MarginTrading.Contract.RabbitMqMessageModels; -using MarginTrading.Backend.Services.TradingConditions; -using MarginTrading.Common.Extensions; using MoreLinq; namespace MarginTrading.Backend.Services { - public class AccountManager: TimerPeriod + public class AccountManager : TimerPeriod { private readonly AccountsCacheService _accountsCacheService; - private readonly IMarginTradingAccountsRepository _repository; - private readonly IConsole _console; - private readonly MarginSettings _marginSettings; + private readonly MarginTradingSettings _marginSettings; private readonly IRabbitMqNotifyService _rabbitMqNotifyService; - private readonly IAccountGroupCacheService _accountGroupCacheService; - private readonly IClientNotifyService _clientNotifyService; - private readonly IClientAccountClient _clientAccountClient; - private readonly IMarginTradingAccountsRepository _accountsRepository; - private readonly ITradingConditionsCacheService _tradingConditionsCacheService; private readonly ILog _log; private readonly OrdersCache _ordersCache; - private readonly IEventChannel _acountBalanceChangedEventChannel; private readonly ITradingEngine _tradingEngine; + private readonly IAccountsApi _accountsApi; + private readonly IConvertService _convertService; + private readonly IDateService _dateService; + + private readonly IAccountMarginFreezingRepository _accountMarginFreezingRepository; + private readonly IAccountMarginUnconfirmedRepository _accountMarginUnconfirmedRepository; - private static readonly ConcurrentDictionary Semaphores = new ConcurrentDictionary(); - - public AccountManager(AccountsCacheService accountsCacheService, - IMarginTradingAccountsRepository repository, - IConsole console, - MarginSettings marginSettings, + public AccountManager( + AccountsCacheService accountsCacheService, + MarginTradingSettings marginSettings, IRabbitMqNotifyService rabbitMqNotifyService, - IAccountGroupCacheService accountGroupCacheService, - IClientNotifyService clientNotifyService, - IClientAccountClient clientAccountClient, - IMarginTradingAccountsRepository accountsRepository, - ITradingConditionsCacheService tradingConditionsCacheService, ILog log, OrdersCache ordersCache, - IEventChannel acountBalanceChangedEventChannel, - ITradingEngine tradingEngine) + ITradingEngine tradingEngine, + IAccountsApi accountsApi, + IConvertService convertService, + IDateService dateService, + IAccountMarginFreezingRepository accountMarginFreezingRepository, + IAccountMarginUnconfirmedRepository accountMarginUnconfirmedRepository) : base(nameof(AccountManager), 60000, log) { _accountsCacheService = accountsCacheService; - _clientAccountClient = clientAccountClient; - _repository = repository; - _console = console; _marginSettings = marginSettings; _rabbitMqNotifyService = rabbitMqNotifyService; - _accountGroupCacheService = accountGroupCacheService; - _accountsRepository = accountsRepository; - _tradingConditionsCacheService = tradingConditionsCacheService; _log = log; - _clientNotifyService = clientNotifyService; _ordersCache = ordersCache; - _acountBalanceChangedEventChannel = acountBalanceChangedEventChannel; _tradingEngine = tradingEngine; + _accountsApi = accountsApi; + _convertService = convertService; + _dateService = dateService; + _accountMarginFreezingRepository = accountMarginFreezingRepository; + _accountMarginUnconfirmedRepository = accountMarginUnconfirmedRepository; } - - #region TimePeriod - public override Task Execute() { - var accounts = GetAccountsToWriteStats(); - var accountsStatsMessages = GenerateAccountsStatsUpdateMessages(accounts); - var tasks = accountsStatsMessages.Select(m => _rabbitMqNotifyService.UpdateAccountStats(m)); - - return Task.WhenAll(tasks); - } - - public override void Start() - { - var accounts = _repository.GetAllAsync().GetAwaiter().GetResult() - .Select(MarginTradingAccount.Create).GroupBy(x => x.ClientId).ToDictionary(x => x.Key, x => x.ToArray()); - - _accountsCacheService.InitAccountsCache(accounts); - _console.WriteLine($"InitAccountsCache (clients count:{accounts.Count})"); - - base.Start(); - } - - private IReadOnlyList GetAccountsToWriteStats() - { - var accountsIdsToWrite = Enumerable.ToHashSet(_ordersCache.GetActive().Select(a => a.AccountId).Distinct()); - return _accountsCacheService.GetAll().Where(a => accountsIdsToWrite.Contains(a.Id)).ToList(); - } - - private IEnumerable GenerateAccountsStatsUpdateMessages(IReadOnlyList accounts) - { - var accountStats = accounts.Select(a => a.ToRabbitMqContract(_marginSettings.IsLive)); - - var chunks = accountStats.Batch(100); - - foreach (var chunk in chunks) - { - yield return new AccountStatsUpdateMessage {Accounts = chunk.ToArray()}; - } - } - - #endregion - - - public async Task UpdateBalanceAsync(IMarginTradingAccount account, decimal amount, AccountHistoryType historyType, - string comment, string eventSourceId = null, bool changeTransferLimit = false, string auditLog = null) - { - if (historyType == AccountHistoryType.Deposit && changeTransferLimit) - { - CheckDepositLimits(account, amount); - } - - if (changeTransferLimit) - { - CheckTransferLimits(account, amount); - } - - var semaphore = GetSemaphore(account); - - await semaphore.WaitAsync(); - - try - { - var updatedAccount = - await _repository.UpdateBalanceAsync(account.ClientId, account.Id, amount, changeTransferLimit); - _acountBalanceChangedEventChannel.SendEvent(this, new AccountBalanceChangedEventArgs(updatedAccount)); - //todo: move to separate event consumers - _accountsCacheService.UpdateBalance(updatedAccount); - _clientNotifyService.NotifyAccountUpdated(updatedAccount); - - var transactionId = Guid.NewGuid().ToString("N"); - - await _rabbitMqNotifyService.AccountHistory( - transactionId, - account.Id, - account.ClientId, - amount, - updatedAccount.Balance, - updatedAccount.WithdrawTransferLimit, - historyType, - comment, - eventSourceId, - auditLog); - - return transactionId; - } - finally - { - semaphore.Release(); - } - } - - public async Task DeleteAccountAsync(string clientId, string accountId) - { - var orders = _ordersCache.GetAll().Where(o => o.AccountId == accountId).ToArray(); - - if (orders.Any()) - { - throw new Exception( - $"Account [{accountId}] has not closed orders: [{orders.Select(o => $"{o.Id}:{o.Status.ToString()}").ToJson()}]"); - } - - var account = _accountsCacheService.Get(clientId, accountId); - - if (_marginSettings.IsLive && account.Balance > 0) - throw new Exception( - $"Account [{accountId}] balance is higher than zero: [{account.Balance}]"); - - await _clientAccountClient.DeleteWalletAsync(accountId); - await _repository.DeleteAsync(clientId, accountId); - await ProcessAccountsSetChange(clientId); - await _rabbitMqNotifyService.AccountDeleted(account); - } - - //TODO: close/remove all orders - public Task ResetAccountAsync(string clientId, string accountId) - { - var account = _accountsCacheService.Get(clientId, accountId); - - return UpdateBalanceAsync(account, LykkeConstants.DefaultDemoBalance - account.Balance, - AccountHistoryType.Reset, - "Reset account"); - } - - public async Task AddAccountAsync(string clientId, string baseAssetId, string tradingConditionId) - { - var accountGroup = - _accountGroupCacheService.GetAccountGroup(tradingConditionId, baseAssetId); - - if (accountGroup == null) - { - throw new Exception( - $"Account group with base asset [{baseAssetId}] and trading condition [{tradingConditionId}] is not found"); - } - - var clientAccounts = _accountsCacheService.GetAll(clientId); - - if (clientAccounts.Any(a => a.BaseAssetId == baseAssetId && a.TradingConditionId == tradingConditionId)) - { - throw new Exception( - $"Client [{clientId}] already has account with base asset [{baseAssetId}] and trading condition [{tradingConditionId}]"); - } - - var account = await CreateAccount(clientId, baseAssetId, tradingConditionId); - await _repository.AddAsync(account); - await ProcessAccountsSetChange(account.ClientId); - await _rabbitMqNotifyService.AccountCreated(account); - } - - public async Task CreateDefaultAccounts(string clientId, string tradingConditionsId = null) - { - var existingAccounts = (await _accountsRepository.GetAllAsync(clientId)).ToList(); - - if (existingAccounts.Any()) - { - var accounts = existingAccounts.Select(MarginTradingAccount.Create).ToArray(); - _accountsCacheService.UpdateAccountsCache(clientId, accounts); - return accounts; - } - - if (string.IsNullOrEmpty(tradingConditionsId)) - tradingConditionsId = GetTradingConditions(); - - var baseAssets = GetBaseAssets(tradingConditionsId); - - var newAccounts = new List(); - - foreach (var baseAsset in baseAssets) - { - try - { - var account = await CreateAccount(clientId, baseAsset, tradingConditionsId); - await _repository.AddAsync(account); - await _rabbitMqNotifyService.AccountCreated(account); - newAccounts.Add(account); - } - catch (Exception e) - { - await _log.WriteErrorAsync(nameof(AccountManager), "Create default accounts", - $"clientId={clientId}, tradingConditionsId={tradingConditionsId}", e); - } - } - - await ProcessAccountsSetChange(clientId, newAccounts); - - return newAccounts.ToArray(); + //TODO: to think if we need this process, at the current moment it is not used and only increases load on RabbitMq + // var accounts = GetAccountsToWriteStats(); + // var accountsStatsMessages = GenerateAccountsStatsUpdateMessages(accounts); + // var tasks = accountsStatsMessages.Select(m => _rabbitMqNotifyService.UpdateAccountStats(m)); + // + // return Task.WhenAll(tasks); + return Task.CompletedTask; } - public async Task> CreateAccounts(string tradingConditionId, - string baseAssetId) + public override void Start() { - var result = new List(); - - var clientAccountGroups = _accountsCacheService.GetAll() - .GroupBy(a => a.ClientId) - .Where(g => - g.Any(a => a.TradingConditionId == tradingConditionId) - && g.All(a => a.BaseAssetId != baseAssetId)); - - foreach (var group in clientAccountGroups) - { - try - { - var account = await CreateAccount(group.Key, baseAssetId, tradingConditionId); - await _repository.AddAsync(account); - await _rabbitMqNotifyService.AccountCreated(account); - await ProcessAccountsSetChange(group.Key, group.Concat(new[] {account}).ToArray()); - result.Add(account); - } - catch (Exception e) - { - await _log.WriteErrorAsync(nameof(AccountManager), "Create accounts by account group", - $"clientId={group.Key}, tradingConditionsId={tradingConditionId}, baseAssetId={baseAssetId}", - e); - } - } + _log.WriteInfo(nameof(Start), nameof(AccountManager), "Starting InitAccountsCache"); - return result; - } - - public async Task> CloseAccountOrders(string accountId) - { - var openedOrders = _ordersCache.ActiveOrders.GetOrdersByAccountIds(accountId).ToArray(); - var closedOrders = new List(); + var accounts = _accountsApi.List().GetAwaiter().GetResult() + .Select(Convert).ToDictionary(x => x.Id); - foreach (var order in openedOrders) - { - try - { - var closedOrder = await _tradingEngine.CloseActiveOrderAsync(order.Id, - OrderCloseReason.ClosedByBroker, "Close orders for account"); - - closedOrders.Add(closedOrder); - } - catch (Exception e) - { - await _log.WriteWarningAsync(nameof(AccountManager), "CloseAccountActiveOrders", - $"AccountId: {accountId}, OrderId: {order.Id}", $"Error closing order: {e.Message}"); - } - } + //TODO: think about approach + //ApplyMarginFreezing(accounts); - var pendingOrders = _ordersCache.WaitingForExecutionOrders.GetOrdersByAccountIds(accountId); - - foreach (var order in pendingOrders) - { - try - { - var closedOrder = _tradingEngine.CancelPendingOrder(order.Id, OrderCloseReason.CanceledByBroker, - "Close orders for account"); - closedOrders.Add(closedOrder); - } - catch (Exception e) - { - await _log.WriteWarningAsync(nameof(AccountManager), "CloseAccountOrders", - $"AccountId: {accountId}, OrderId: {order.Id}", $"Error cancelling order: {e.Message}"); - } - } + _accountsCacheService.InitAccountsCache(accounts); + _log.WriteInfo(nameof(Start), nameof(AccountManager), $"Finished InitAccountsCache. Count: {accounts.Count}"); - return closedOrders; + base.Start(); } - [ItemCanBeNull] - public async Task SetTradingCondition(string clientId, string accountId, - string tradingConditionId) + private void ApplyMarginFreezing(Dictionary accounts) { - var result = - await _accountsRepository.UpdateTradingConditionIdAsync(clientId, accountId, tradingConditionId); - - if (result != null) + var marginFreezings = _accountMarginFreezingRepository.GetAllAsync().GetAwaiter().GetResult() + .GroupBy(x => x.AccountId) + .ToDictionary(x => x.Key, x => x.ToDictionary(z => z.OperationId, z => z.Amount)); + var unconfirmedMargin = _accountMarginUnconfirmedRepository.GetAllAsync().GetAwaiter().GetResult() + .GroupBy(x => x.AccountId) + .ToDictionary(x => x.Key, x => x.ToDictionary(z => z.OperationId, z => z.Amount)); + foreach (var account in accounts.Select(x => x.Value)) { - _accountsCacheService.SetTradingCondition(clientId, accountId, tradingConditionId); - - await _clientNotifyService.NotifyTradingConditionsChanged(tradingConditionId, accountId); + account.AccountFpl.WithdrawalFrozenMarginData = marginFreezings.TryGetValue(account.Id, out var withdrawalFrozenMargin) + ? withdrawalFrozenMargin + : new Dictionary(); + account.AccountFpl.WithdrawalFrozenMargin = account.AccountFpl.WithdrawalFrozenMarginData.Sum(x => x.Value); + account.AccountFpl.UnconfirmedMarginData = unconfirmedMargin.TryGetValue(account.Id, out var unconfirmedFrozenMargin) + ? unconfirmedFrozenMargin + : new Dictionary(); + account.AccountFpl.UnconfirmedMargin = account.AccountFpl.UnconfirmedMarginData.Sum(x => x.Value); } - - return result; } - - #region Helpers - - private string[] GetBaseAssets(string tradingConditionsId) - { - var accountGroups = - _accountGroupCacheService.GetAllAccountGroups().Where(g => g.TradingConditionId == tradingConditionsId); - var baseAssets = accountGroups.Select(g => g.BaseAssetId).Distinct().ToArray(); - - if (!baseAssets.Any()) - throw new Exception( - $"No account groups found for trading conditions {tradingConditionsId}"); - - return baseAssets; - } - - private string GetTradingConditions() - { - //use default trading conditions for demo - if (!_marginSettings.IsLive) - { - var tradingConditions = _tradingConditionsCacheService.GetAllTradingConditions(); - var defaultConditions = tradingConditions.FirstOrDefault(item => item.IsDefault); - - if (defaultConditions == null) - throw new Exception("No default trading conditions set for demo"); - else - return defaultConditions.Id; - } - else - { - throw new Exception("No trading conditions found"); - } - } - - private async Task CreateAccount(string clientId, string baseAssetId, string tradingConditionId) + private IReadOnlyList GetAccountsToWriteStats() { - var tradingCondition = _tradingConditionsCacheService.GetTradingCondition(tradingConditionId) - .RequiredNotNull("tradingCondition id " + tradingConditionId); - var wallet = _marginSettings.IsLive - ? await _clientAccountClient.CreateWalletAsync(clientId, WalletType.Trading, OwnerType.Mt, - $"{baseAssetId} margin wallet", null) - : null; - var id = _marginSettings.IsLive ? wallet?.Id : $"{_marginSettings.DemoAccountIdPrefix}{Guid.NewGuid():N}"; - var initialBalance = _marginSettings.IsLive ? 0 : LykkeConstants.DefaultDemoBalance; - - return new MarginTradingAccount - { - Id = id, - BaseAssetId = baseAssetId, - ClientId = clientId, - Balance = initialBalance, - TradingConditionId = tradingConditionId, - LegalEntity = tradingCondition.LegalEntity, - }; + var accountsIdsToWrite = Enumerable.ToHashSet(_ordersCache.GetPositions().Select(a => a.AccountId).Distinct()); + return _accountsCacheService.GetAll().Where(a => accountsIdsToWrite.Contains(a.Id)).ToList(); } - - private void CheckDepositLimits(IMarginTradingAccount account, decimal amount) - { - //limit can not be more then max after deposit - if (amount > 0) - { - var accountGroup = - _accountGroupCacheService.GetAccountGroup(account.TradingConditionId, account.BaseAssetId); - if (accountGroup.DepositTransferLimit > 0 && accountGroup.DepositTransferLimit < account.Balance + amount) - { - throw new Exception( - $"Margin Trading is in beta testing. The cash-ins are temporarily limited when Total Capital exceeds {accountGroup.DepositTransferLimit} {accountGroup.BaseAssetId}. Thank you for using Lykke Margin Trading, the limit will be cancelled soon!"); - } - } - } - - private void CheckTransferLimits(IMarginTradingAccount account, decimal amount) - { - //withdraw can not be more then limit - if (amount < 0 && account.WithdrawTransferLimit < Math.Abs(amount)) - { - throw new Exception( - $"Can not transfer {Math.Abs(amount)}. Current limit is {account.WithdrawTransferLimit}"); - } - } - - private async Task ProcessAccountsSetChange(string clientId, IReadOnlyList allClientsAccounts = null) + // todo: extract this to a cqrs process + private IEnumerable GenerateAccountsStatsUpdateMessages( + IEnumerable accounts) { - if (allClientsAccounts == null) - { - allClientsAccounts = (await _repository.GetAllAsync(clientId)).Select(MarginTradingAccount.Create).ToList(); - } - - _accountsCacheService.UpdateAccountsCache(clientId, allClientsAccounts); - await _rabbitMqNotifyService.UserUpdates(false, true, new[] {clientId}); + return accounts.Select(a => a.ToRabbitMqContract()).Batch(100) + .Select(ch => new AccountStatsUpdateMessage { Accounts = ch.ToArray() }); } - private SemaphoreSlim GetSemaphore(IMarginTradingAccount account) + private MarginTradingAccount Convert(AccountContract accountContract) { - var hash = account.Id.GetHashCode() % 100; - - return Semaphores.GetOrAdd(hash, new SemaphoreSlim(1, 1)); + return _convertService.Convert(accountContract, + o => o.ConfigureMap(MemberList.Source) + // The line below is related to LT-1786 ticket. + // After restarting core we cannot have LastBalanceChangeTime less than in donut's cache to avoid infinite account reloading + .ForMember(x => x.LastBalanceChangeTime, opts => opts.UseValue(_dateService.Now())) + .ForSourceMember(x => x.ModificationTimestamp, c => c.Ignore())); } - - #endregion - } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/AccountMarginEventMessageConverter.cs b/src/MarginTrading.Backend.Services/AccountMarginEventMessageConverter.cs index 5de6bd6a6..2a471d260 100644 --- a/src/MarginTrading.Backend.Services/AccountMarginEventMessageConverter.cs +++ b/src/MarginTrading.Backend.Services/AccountMarginEventMessageConverter.cs @@ -1,28 +1,32 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MarginTrading.Backend.Contracts.Events; using MarginTrading.Backend.Core; -using MarginTrading.Contract.RabbitMqMessageModels; namespace MarginTrading.Backend.Services { class AccountMarginEventMessageConverter { - public static AccountMarginEventMessage Create(IMarginTradingAccount account, bool isStopout, DateTime eventTime) + public static MarginEventMessage Create(IMarginTradingAccount account, MarginEventTypeContract eventType, + DateTime eventTime) { - return new AccountMarginEventMessage + return new MarginEventMessage { EventId = Guid.NewGuid().ToString("N"), EventTime = eventTime, - IsEventStopout = isStopout, + EventType = eventType, - ClientId = account.ClientId, AccountId = account.Id, TradingConditionId = account.TradingConditionId, BaseAssetId = account.BaseAssetId, Balance = account.Balance, WithdrawTransferLimit = account.WithdrawTransferLimit, - MarginCall = account.GetMarginCallLevel(), - StopOut = account.GetStopOutLevel(), + MarginCall1Level = account.GetMarginCall1Level(), + MarginCall2Level = account.GetMarginCall2Level(), + StopOutLevel = account.GetStopOutLevel(), TotalCapital = account.GetTotalCapital(), FreeMargin = account.GetFreeMargin(), MarginAvailable = account.GetMarginAvailable(), diff --git a/src/MarginTrading.Backend.Services/AssetPairs/AssetPairDayOffService.cs b/src/MarginTrading.Backend.Services/AssetPairs/AssetPairDayOffService.cs index bd4dc519f..66a8aae2e 100644 --- a/src/MarginTrading.Backend.Services/AssetPairs/AssetPairDayOffService.cs +++ b/src/MarginTrading.Backend.Services/AssetPairs/AssetPairDayOffService.cs @@ -1,22 +1,27 @@ -using System; -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; using System.Linq; +using System.Text.RegularExpressions; using JetBrains.Annotations; using MarginTrading.Backend.Core.DayOffSettings; -using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Services.Infrastructure; using MarginTrading.Common.Services; +// ReSharper disable PossibleInvalidOperationException namespace MarginTrading.Backend.Services.AssetPairs { public class AssetPairDayOffService : IAssetPairDayOffService { private readonly IDateService _dateService; - private readonly IDayOffSettingsService _dayOffSettingsService; + private readonly IScheduleSettingsCacheService _scheduleSettingsCacheService; - public AssetPairDayOffService(IDateService dateService, IDayOffSettingsService dayOffSettingsService) + public AssetPairDayOffService(IDateService dateService, IScheduleSettingsCacheService scheduleSettingsCacheService) { _dateService = dateService; - _dayOffSettingsService = dayOffSettingsService; + _scheduleSettingsCacheService = scheduleSettingsCacheService; } public bool IsDayOff(string assetPairId) @@ -26,67 +31,19 @@ public bool IsDayOff(string assetPairId) public bool ArePendingOrdersDisabled(string assetPairId) { - return IsNowNotInSchedule(assetPairId, _dayOffSettingsService.GetScheduleSettings().PendingOrdersCutOff); + //TODO TBD in https://lykke-snow.atlassian.net/browse/MTC-155 + return false; //IsNowNotInSchedule(assetPairId, _dayOffSettingsService.GetScheduleSettings().PendingOrdersCutOff); } /// /// Check if current time is not in schedule /// /// - /// - /// Timespan to reduce schedule from both sides - /// + /// Timespan to reduce schedule from both sides /// private bool IsNowNotInSchedule(string assetPairId, TimeSpan scheduleCutOff) { - var currentDateTime = _dateService.Now(); - var isDayOffByExclusion = IsDayOffByExclusion(assetPairId, scheduleCutOff, currentDateTime); - if (isDayOffByExclusion != null) - return isDayOffByExclusion.Value; - - var scheduleSettings = _dayOffSettingsService.GetScheduleSettings(); - if (scheduleSettings.AssetPairsWithoutDayOff.Contains(assetPairId)) - return false; - - var closestDayOffStart = GetNextWeekday(currentDateTime, scheduleSettings.DayOffStartDay) - .Add(scheduleSettings.DayOffStartTime.Subtract(scheduleCutOff)); - - var closestDayOffEnd = GetNextWeekday(currentDateTime, scheduleSettings.DayOffEndDay) - .Add(scheduleSettings.DayOffEndTime.Add(scheduleCutOff)); - - if (closestDayOffStart > closestDayOffEnd) - { - closestDayOffStart = closestDayOffEnd.AddDays(-7); - } - - return (currentDateTime >= closestDayOffStart && currentDateTime < closestDayOffEnd) - //don't even try to understand - || currentDateTime < closestDayOffEnd.AddDays(-7); - } - - [CanBeNull] - private bool? IsDayOffByExclusion(string assetPairId, TimeSpan scheduleCutOff, DateTime currentDateTime) - { - var dayOffExclusions = _dayOffSettingsService.GetExclusions(assetPairId); - return dayOffExclusions - .Where(e => - { - var start = e.IsTradeEnabled ? e.Start.Add(scheduleCutOff) : e.Start.Subtract(scheduleCutOff); - var end = e.IsTradeEnabled ? e.End.Subtract(scheduleCutOff) : e.End.Add(scheduleCutOff); - return IsBetween(currentDateTime, start, end); - }).DefaultIfEmpty() - .Select(e => e == null ? (bool?) null : !e.IsTradeEnabled).Max(); - } - - private static bool IsBetween(DateTime currentDateTime, DateTime start, DateTime end) - { - return start <= currentDateTime && currentDateTime <= end; - } - - private static DateTime GetNextWeekday(DateTime start, DayOfWeek day) - { - var daysToAdd = ((int) day - (int) start.DayOfWeek + 7) % 7; - return start.Date.AddDays(daysToAdd); + return !_scheduleSettingsCacheService.AssetPairTradingEnabled(assetPairId, scheduleCutOff); } } } diff --git a/src/MarginTrading.Backend.Services/AssetPairs/AssetPairsCache.cs b/src/MarginTrading.Backend.Services/AssetPairs/AssetPairsCache.cs index 957c26e52..fc30de2a3 100644 --- a/src/MarginTrading.Backend.Services/AssetPairs/AssetPairsCache.cs +++ b/src/MarginTrading.Backend.Services/AssetPairs/AssetPairsCache.cs @@ -1,6 +1,11 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Threading; using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.Exceptions; using MarginTrading.Backend.Core.Messages; @@ -13,80 +18,185 @@ namespace MarginTrading.Backend.Services.AssetPairs /// /// Cashes data about assets in the backend app. /// - /// - /// Note this type is thread-safe, though it has no synchronization. - /// This is due to the fact that the dictionary - /// is used as read-only: never updated, only reference-assigned. - /// Their contents are also readonly. - /// public class AssetPairsCache : IAssetPairsInitializableCache { - private IReadOnlyDictionary _assetPairs = - ImmutableSortedDictionary.Empty; + public const int DefaultAssetPairAccuracy = 5; + + private Dictionary _assetPairs = new Dictionary(); + + private readonly ReaderWriterLockSlim _readerWriterLockSlim = new ReaderWriterLockSlim(); - private readonly ICachedCalculation> _assetPairsByAssets; - private readonly ICachedCalculation> _assetPairsIds; + private ICachedCalculation> _assetPairsByAssets; + private ICachedCalculation> _assetPairsIds; + + private Func, Dictionary, bool> + CacheChangedCondition => (first, second) => first.Count == second.Count + && first.All(x => + second.ContainsKey(x.Key) && x.Value.Equals(second[x.Key])); public AssetPairsCache() { _assetPairsByAssets = GetAssetPairsByAssetsCache(); - _assetPairsIds = Calculate.Cached(() => _assetPairs, ReferenceEquals, p => p.Keys.ToImmutableHashSet()); + _assetPairsIds = Calculate.Cached(() => _assetPairs, CacheChangedCondition, p => p.Keys.ToImmutableHashSet()); } public IAssetPair GetAssetPairById(string assetPairId) { - return _assetPairs.TryGetValue(assetPairId, out var result) - ? result - : throw new AssetPairNotFoundException(assetPairId, - string.Format(MtMessages.InstrumentNotFoundInCache, assetPairId)); + _readerWriterLockSlim.EnterReadLock(); + + try + { + return _assetPairs.TryGetValue(assetPairId, out var result) + ? result + : throw new AssetPairNotFoundException(assetPairId, + string.Format(MtMessages.InstrumentNotFoundInCache, assetPairId)); + } + finally + { + _readerWriterLockSlim.ExitReadLock(); + } } public IAssetPair GetAssetPairByIdOrDefault(string assetPairId) { - return _assetPairs.GetValueOrDefault(assetPairId); + _readerWriterLockSlim.EnterReadLock(); + + try + { + return _assetPairs.GetValueOrDefault(assetPairId); + } + finally + { + _readerWriterLockSlim.ExitReadLock(); + } } public IEnumerable GetAll() { - return _assetPairs.Values; + _readerWriterLockSlim.EnterReadLock(); + + try + { + return _assetPairs.Values; + } + finally + { + _readerWriterLockSlim.ExitReadLock(); + } } public ImmutableHashSet GetAllIds() { - return _assetPairsIds.Get(); + _readerWriterLockSlim.EnterReadLock(); + + try + { + return _assetPairsIds.Get(); + } + finally + { + _readerWriterLockSlim.ExitReadLock(); + } } - + + public void AddOrUpdate(IAssetPair assetPair) + { + _readerWriterLockSlim.EnterWriteLock(); + + try + { + if (_assetPairs.ContainsKey(assetPair.Id)) + { + _assetPairs.Remove(assetPair.Id); + } + + _assetPairs.Add(assetPair.Id, assetPair); + + _assetPairsByAssets = GetAssetPairsByAssetsCache(); + _assetPairsIds = Calculate.Cached(() => _assetPairs, CacheChangedCondition, p => p.Keys.ToImmutableHashSet()); + } + finally + { + _readerWriterLockSlim.ExitWriteLock(); + } + } + + public void Remove(string assetPairId) + { + _readerWriterLockSlim.EnterWriteLock(); + + try + { + if (_assetPairs.ContainsKey(assetPairId)) + { + _assetPairs.Remove(assetPairId); + _assetPairsByAssets = GetAssetPairsByAssetsCache(); + _assetPairsIds = Calculate.Cached(() => _assetPairs, CacheChangedCondition, p => p.Keys.ToImmutableHashSet()); + } + } + finally + { + _readerWriterLockSlim.ExitWriteLock(); + } + } + public bool TryGetAssetPairQuoteSubst(string substAsset, string instrument, string legalEntity, out IAssetPair assetPair) { - assetPair = null; - var baseAssetPair = GetAssetPairByIdOrDefault(instrument); - if (baseAssetPair == null) - return false; + _readerWriterLockSlim.EnterReadLock(); + + try + { + assetPair = null; + var baseAssetPair = GetAssetPairByIdOrDefault(instrument); + if (baseAssetPair == null) + return false; - return _assetPairsByAssets.Get().TryGetValue( - GetAssetPairKey(baseAssetPair.BaseAssetId, substAsset, legalEntity), out assetPair); + return _assetPairsByAssets.Get().TryGetValue( + GetAssetPairKey(baseAssetPair.BaseAssetId, substAsset, legalEntity), out assetPair); + } + finally + { + _readerWriterLockSlim.ExitReadLock(); + } } public IAssetPair FindAssetPair(string asset1, string asset2, string legalEntity) { - var key = GetAssetPairKey(asset1, asset2, legalEntity); + _readerWriterLockSlim.EnterReadLock(); + + try + { + var key = GetAssetPairKey(asset1, asset2, legalEntity); - if (_assetPairsByAssets.Get().TryGetValue(key, out var result)) - return result; + if (_assetPairsByAssets.Get().TryGetValue(key, out var result)) + return result; - throw new InstrumentByAssetsNotFoundException(asset1, asset2, - string.Format(MtMessages.InstrumentWithAssetsNotFound, asset1, asset2)); + throw new InstrumentByAssetsNotFoundException(asset1, asset2, legalEntity); + } + finally + { + _readerWriterLockSlim.ExitReadLock(); + } } void IAssetPairsInitializableCache.InitPairsCache(Dictionary instruments) { - _assetPairs = instruments; + _readerWriterLockSlim.EnterWriteLock(); + + try + { + _assetPairs = instruments; + } + finally + { + _readerWriterLockSlim.ExitWriteLock(); + } } private ICachedCalculation> GetAssetPairsByAssetsCache() { - return Calculate.Cached(() => _assetPairs, ReferenceEquals, + return Calculate.Cached(() => _assetPairs, CacheChangedCondition, pairs => pairs.Values.SelectMany(p => new [] { (GetAssetPairKey(p.BaseAssetId, p.QuoteAssetId, p.LegalEntity), p), diff --git a/src/MarginTrading.Backend.Services/AssetPairs/AssetPairsManager.cs b/src/MarginTrading.Backend.Services/AssetPairs/AssetPairsManager.cs index 336574625..805fadf6e 100644 --- a/src/MarginTrading.Backend.Services/AssetPairs/AssetPairsManager.cs +++ b/src/MarginTrading.Backend.Services/AssetPairs/AssetPairsManager.cs @@ -1,26 +1,32 @@ -using System; -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using System.Linq; -using System.Threading.Tasks; using Autofac; -using MarginTrading.AzureRepositories.Contract; +using JetBrains.Annotations; using MarginTrading.Backend.Core; -using MarginTrading.Common.Extensions; +using MarginTrading.Common.Services; +using MarginTrading.SettingsService.Contracts; +using MarginTrading.SettingsService.Contracts.AssetPair; namespace MarginTrading.Backend.Services.AssetPairs { + [UsedImplicitly] internal class AssetPairsManager : IStartable, IAssetPairsManager { private static readonly object InitAssetPairsLock = new object(); private readonly IAssetPairsInitializableCache _assetPairsCache; - private readonly IAssetPairsRepository _assetPairsRepository; + private readonly IAssetPairsApi _assetPairs; + private readonly IConvertService _convertService; public AssetPairsManager(IAssetPairsInitializableCache assetPairsCache, - IAssetPairsRepository assetPairsRepository) + IAssetPairsApi assetPairs, + IConvertService convertService) { _assetPairsCache = assetPairsCache; - _assetPairsRepository = assetPairsRepository; + _assetPairs = assetPairs; + _convertService = convertService; } public void Start() @@ -28,55 +34,15 @@ public void Start() InitAssetPairs(); } - private void InitAssetPairs() + public void InitAssetPairs() { lock (InitAssetPairsLock) { - var pairs = _assetPairsRepository.GetAsync().GetAwaiter().GetResult() - .ToDictionary(a => a.Id, s => s); + var pairs = _assetPairs.List().GetAwaiter().GetResult() + .ToDictionary(a => a.Id, + s => (IAssetPair) _convertService.Convert(s)); _assetPairsCache.InitPairsCache(pairs); } } - - public async Task UpdateAssetPair(IAssetPair assetPair) - { - ValidatePair(assetPair); - await _assetPairsRepository.ReplaceAsync(assetPair); - InitAssetPairs(); - return _assetPairsCache.GetAssetPairByIdOrDefault(assetPair.Id) - .RequiredNotNull("AssetPair " + assetPair.Id); - } - - public async Task InsertAssetPair(IAssetPair assetPair) - { - ValidatePair(assetPair); - await _assetPairsRepository.InsertAsync(assetPair); - InitAssetPairs(); - return _assetPairsCache.GetAssetPairByIdOrDefault(assetPair.Id) - .RequiredNotNull("AssetPair " + assetPair.Id); - } - - public async Task DeleteAssetPair(string assetPairId) - { - var pair = await _assetPairsRepository.DeleteAsync(assetPairId); - InitAssetPairs(); - return pair; - } - - private void ValidatePair(IAssetPair newValue) - { - if (newValue.BasePairId == null) - return; - - if (_assetPairsCache.GetAssetPairByIdOrDefault(newValue.BasePairId) == null) - throw new InvalidOperationException($"BasePairId {newValue.BasePairId} does not exist"); - - if (_assetPairsCache.GetAll().Any(s => - s.Id != newValue.Id && - s.BasePairId == newValue.BasePairId)) - { - throw new InvalidOperationException($"BasePairId {newValue.BasePairId} cannot be added twice"); - } - } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/AssetPairs/DayOffSettingsService.cs b/src/MarginTrading.Backend.Services/AssetPairs/DayOffSettingsService.cs deleted file mode 100644 index 48974481b..000000000 --- a/src/MarginTrading.Backend.Services/AssetPairs/DayOffSettingsService.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text.RegularExpressions; -using Autofac; -using JetBrains.Annotations; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.DayOffSettings; -using MarginTrading.Backend.Services.Infrastructure; -using MarginTrading.Common.Extensions; - -namespace MarginTrading.Backend.Services.AssetPairs -{ - internal class DayOffSettingsService : IDayOffSettingsService, IStartable - { - [CanBeNull] private DayOffSettingsRoot _cache; - private static readonly object _updateLock = new object(); - - private readonly IDayOffSettingsRepository _dayOffSettingsRepository; - private readonly IAssetPairsCache _assetPairsCache; - private readonly ICachedCalculation>> _exclusionsByAssetPairId; - - public DayOffSettingsService(IDayOffSettingsRepository dayOffSettingsRepository, IAssetPairsCache assetPairsCache) - { - _dayOffSettingsRepository = dayOffSettingsRepository; - _assetPairsCache = assetPairsCache; - _exclusionsByAssetPairId = GetExclusionsByAssetPairIdCache(); - } - - public ImmutableDictionary GetExclusions() - { - return GetRoot().Exclusions; - } - - public ImmutableDictionary> GetCompiledExclusions() - { - return _exclusionsByAssetPairId.Get(); - } - - public ScheduleSettings GetScheduleSettings() - { - return GetRoot().ScheduleSettings; - } - - public ScheduleSettings SetScheduleSettings(ScheduleSettings scheduleSettings) - { - Change(root => new DayOffSettingsRoot(root.Exclusions, scheduleSettings)); - return scheduleSettings; - } - - public IReadOnlyList GetExclusions(string assetPairId) - { - return _exclusionsByAssetPairId.Get().GetValueOrDefault(assetPairId, ImmutableArray.Empty); - } - - public void DeleteExclusion(Guid id) - { - id.RequiredNotEqualsTo(default, nameof(id)); - Change(root => - { - root.Exclusions.ContainsKey(id).RequiredEqualsTo(true, "oldExclusion", - "Trying to delete non-existent exclusion with id " + id); - return new DayOffSettingsRoot(root.Exclusions.Remove(id), root.ScheduleSettings); - }); - } - - private DayOffSettingsRoot GetRoot() - { - return _cache.RequiredNotNull("_cache != null"); - } - - public DayOffExclusion GetExclusion(Guid id) - { - id.RequiredNotEqualsTo(default, nameof(id)); - return GetExclusions().GetValueOrDefault(id); - } - - public DayOffExclusion CreateExclusion([NotNull] DayOffExclusion exclusion) - { - if (exclusion == null) throw new ArgumentNullException(nameof(exclusion)); - exclusion.Id.RequiredNotEqualsTo(default, nameof(exclusion.Id)); - Change(root => - { - root.Exclusions.ContainsKey(exclusion.Id).RequiredEqualsTo(false, "oldExclusion", - "Trying to add already existing exclusion with id " + exclusion.Id); - return SetExclusion(root, exclusion); - }); - return exclusion; - } - - public DayOffExclusion UpdateExclusion([NotNull] DayOffExclusion exclusion) - { - if (exclusion == null) throw new ArgumentNullException(nameof(exclusion)); - exclusion.Id.RequiredNotEqualsTo(default, nameof(exclusion.Id)); - Change(root => - { - root.Exclusions.ContainsKey(exclusion.Id).RequiredEqualsTo(true, "oldExclusion", - "Trying to update non-existent exclusion with id " + exclusion.Id); - return SetExclusion(root, exclusion); - }); - - return exclusion; - } - - public void Start() - { - _cache = _dayOffSettingsRepository.Read() - ?? new DayOffSettingsRoot(ImmutableDictionary.Empty, - new ScheduleSettings( - dayOffStartDay: DayOfWeek.Friday, - dayOffStartTime: new TimeSpan(20, 55, 0), - dayOffEndDay: DayOfWeek.Sunday, - dayOffEndTime: new TimeSpan(22, 05, 0), - assetPairsWithoutDayOff: new[] {"BTCUSD"}.ToHashSet(), - pendingOrdersCutOff: new TimeSpan(0, 55, 0))); - } - - private static DayOffSettingsRoot SetExclusion(DayOffSettingsRoot old, DayOffExclusion exclusion) - { - return new DayOffSettingsRoot(old.Exclusions.SetItem(exclusion.Id, exclusion), old.ScheduleSettings); - } - - private void Change(Func changeFunc) - { - lock (_updateLock) - { - var oldSettings = _cache; - var settings = changeFunc(oldSettings); - _dayOffSettingsRepository.Write(settings); - _cache = settings; - } - } - - private ICachedCalculation>> GetExclusionsByAssetPairIdCache() - { - return Calculate.Cached( - () => (Exclusions: GetExclusions(), Pairs: _assetPairsCache.GetAllIds()), - (o, n) => ReferenceEquals(o.Exclusions, n.Exclusions) && o.Pairs.SetEquals(n.Pairs), - t => t.Pairs.ToImmutableDictionary(p => p, - p => t.Exclusions.Values.Where(v => Regex.IsMatch(p, v.AssetPairRegex, RegexOptions.IgnoreCase)).ToImmutableArray())); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/AssetPairs/IAssetPairDayOffService.cs b/src/MarginTrading.Backend.Services/AssetPairs/IAssetPairDayOffService.cs index d737db40e..c42635c43 100644 --- a/src/MarginTrading.Backend.Services/AssetPairs/IAssetPairDayOffService.cs +++ b/src/MarginTrading.Backend.Services/AssetPairs/IAssetPairDayOffService.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Services.AssetPairs +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Services.AssetPairs { public interface IAssetPairDayOffService { diff --git a/src/MarginTrading.Backend.Services/AssetPairs/IAssetPairsInitializableCache.cs b/src/MarginTrading.Backend.Services/AssetPairs/IAssetPairsInitializableCache.cs index 00005704b..44e28cd84 100644 --- a/src/MarginTrading.Backend.Services/AssetPairs/IAssetPairsInitializableCache.cs +++ b/src/MarginTrading.Backend.Services/AssetPairs/IAssetPairsInitializableCache.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using MarginTrading.Backend.Core; namespace MarginTrading.Backend.Services.AssetPairs diff --git a/src/MarginTrading.Backend.Services/AssetPairs/IAssetPairsManager.cs b/src/MarginTrading.Backend.Services/AssetPairs/IAssetPairsManager.cs index c4af44243..b6dff0b47 100644 --- a/src/MarginTrading.Backend.Services/AssetPairs/IAssetPairsManager.cs +++ b/src/MarginTrading.Backend.Services/AssetPairs/IAssetPairsManager.cs @@ -1,12 +1,13 @@ -using System.Threading.Tasks; -using MarginTrading.Backend.Core; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. namespace MarginTrading.Backend.Services.AssetPairs { public interface IAssetPairsManager { - Task UpdateAssetPair(IAssetPair assetPair); - Task InsertAssetPair(IAssetPair assetPair); - Task DeleteAssetPair(string assetPairId); + /// + /// Initialize asset pairs cache + /// + void InitAssetPairs(); } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/AssetPairs/IDayOffSettingsService.cs b/src/MarginTrading.Backend.Services/AssetPairs/IDayOffSettingsService.cs deleted file mode 100644 index c4d3414aa..000000000 --- a/src/MarginTrading.Backend.Services/AssetPairs/IDayOffSettingsService.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using JetBrains.Annotations; -using MarginTrading.Backend.Core.DayOffSettings; - -namespace MarginTrading.Backend.Services.AssetPairs -{ - public interface IDayOffSettingsService - { - ImmutableDictionary GetExclusions(); - [CanBeNull] DayOffExclusion GetExclusion(Guid id); - DayOffExclusion CreateExclusion(DayOffExclusion exclusion); - DayOffExclusion UpdateExclusion(DayOffExclusion exclusion); - ScheduleSettings GetScheduleSettings(); - ScheduleSettings SetScheduleSettings(ScheduleSettings scheduleSettings); - IReadOnlyList GetExclusions(string assetPairId); - void DeleteExclusion(Guid id); - ImmutableDictionary> GetCompiledExclusions(); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/AssetPairs/IScheduleSettingsCacheService.cs b/src/MarginTrading.Backend.Services/AssetPairs/IScheduleSettingsCacheService.cs new file mode 100644 index 000000000..88a54fae4 --- /dev/null +++ b/src/MarginTrading.Backend.Services/AssetPairs/IScheduleSettingsCacheService.cs @@ -0,0 +1,50 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; +using JetBrains.Annotations; +using MarginTrading.Backend.Core.DayOffSettings; + +namespace MarginTrading.Backend.Services.AssetPairs +{ + public interface IScheduleSettingsCacheService + { + Dictionary> GetCompiledAssetPairScheduleSettings(); + + /// + /// Get compiled schedule timeline from cache, recalculate it if needed. + /// + List GetCompiledAssetPairScheduleSettings(string assetPairId, + DateTime currentDateTime, TimeSpan scheduleCutOff); + + void CacheWarmUp(params string[] assetPairIds); + + void CacheWarmUpIncludingValidation(); + + void MarketsCacheWarmUp(); + + Task UpdateAllSettingsAsync(); + + Task UpdateScheduleSettingsAsync(); + + Task UpdateMarketsScheduleSettingsAsync(); + + /// + /// Get current and next day time intervals of the platform disablement hours. + /// + List GetPlatformTradingSchedule(); + + Dictionary> GetMarketsTradingSchedule(); + + Dictionary GetMarketState(); + + void HandleMarketStateChanges(DateTime currentTime); + + bool TryGetPlatformCurrentDisabledInterval(out CompiledScheduleTimeInterval disabledInterval); + + bool AssetPairTradingEnabled(string assetPairId, TimeSpan scheduleCutOff); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/AssetPairs/ScheduleSettingsCacheService.cs b/src/MarginTrading.Backend.Services/AssetPairs/ScheduleSettingsCacheService.cs new file mode 100644 index 000000000..9b6f0b5f9 --- /dev/null +++ b/src/MarginTrading.Backend.Services/AssetPairs/ScheduleSettingsCacheService.cs @@ -0,0 +1,555 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Common; +using Common.Log; +using MarginTrading.Backend.Contracts.TradingSchedule; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.DayOffSettings; +using MarginTrading.Backend.Core.Extensions; +using MarginTrading.Backend.Core.Mappers; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Services.Infrastructure; +using MarginTrading.Common.Extensions; +using MarginTrading.Common.Services; +using MarginTrading.SettingsService.Contracts; +using MarginTrading.SettingsService.Contracts.Scheduling; +using MoreLinq; +using ScheduleSettingsContract = MarginTrading.SettingsService.Contracts.Scheduling.ScheduleSettingsContract; + +namespace MarginTrading.Backend.Services.AssetPairs +{ + public class ScheduleSettingsCacheService : IScheduleSettingsCacheService + { + private readonly ICqrsSender _cqrsSender; + private readonly IScheduleSettingsApi _scheduleSettingsApi; + private readonly IAssetPairsCache _assetPairsCache; + private readonly IDateService _dateService; + private readonly ILog _log; + private readonly OvernightMarginSettings _overnightMarginSettings; + + private Dictionary> _rawAssetPairScheduleCache = + new Dictionary>(); + private Dictionary> _compiledAssetPairScheduleCache = + new Dictionary>(); + + private Dictionary> _rawMarketScheduleCache = + new Dictionary>(); + private Dictionary> _compiledMarketScheduleCache = + new Dictionary>(); + + private readonly Dictionary _marketStates = new Dictionary(); + + private DateTime _lastCacheRecalculationTime = DateTime.MinValue; + + private readonly ReaderWriterLockSlim _readerWriterLockSlim = new ReaderWriterLockSlim(); + + public ScheduleSettingsCacheService( + ICqrsSender cqrsSender, + IScheduleSettingsApi scheduleSettingsApi, + IAssetPairsCache assetPairsCache, + IDateService dateService, + ILog log, + OvernightMarginSettings overnightMarginSettings) + { + _cqrsSender = cqrsSender; + _scheduleSettingsApi = scheduleSettingsApi; + _assetPairsCache = assetPairsCache; + _dateService = dateService; + _log = log; + _overnightMarginSettings = overnightMarginSettings; + } + + public async Task UpdateAllSettingsAsync() + { + await UpdateScheduleSettingsAsync(); + await UpdateMarketsScheduleSettingsAsync(); + } + + public async Task UpdateScheduleSettingsAsync() + { + var newScheduleContracts = (await _scheduleSettingsApi.StateList(null)) + .Where(x => x.ScheduleSettings.Any()).ToList(); + var invalidSchedules = newScheduleContracts.InvalidSchedules(); + + var assertPairIdsToWarmUp = new List(); + + _readerWriterLockSlim.EnterWriteLock(); + + try + { + var newRawScheduleSettings = newScheduleContracts.ToDictionary(x => x.AssetPairId, + x => x.ScheduleSettings.Except(invalidSchedules.TryGetValue(x.AssetPairId, out var invalid) + ? invalid + : new List()) + .Select(ScheduleSettings.Create).ToList()); + + _rawAssetPairScheduleCache + .Where(x => TradingScheduleChanged(x.Key, _rawAssetPairScheduleCache, newRawScheduleSettings)) + .Select(x => x.Key) + .ForEach(key => + { + _compiledAssetPairScheduleCache.Remove(key); + assertPairIdsToWarmUp.Add(key); + }); + + _rawAssetPairScheduleCache = newRawScheduleSettings; + } + catch (Exception exception) + { + await _log.WriteErrorAsync(nameof(ScheduleSettingsCacheService), nameof(UpdateScheduleSettingsAsync), + exception); + } + finally + { + _readerWriterLockSlim.ExitWriteLock(); + CacheWarmUp(assertPairIdsToWarmUp.ToArray()); + } + + if (invalidSchedules.Any()) + { + await _log.WriteWarningAsync(nameof(ScheduleSettingsCacheService), nameof(UpdateScheduleSettingsAsync), + $"Some of CompiledScheduleSettingsContracts were invalid, so they were skipped. The first one: {invalidSchedules.First().ToJson()}"); + } + } + + public async Task UpdateMarketsScheduleSettingsAsync() + { + var marketsScheduleSettingsRaw = (await _scheduleSettingsApi.List()) + .Where(x => !string.IsNullOrWhiteSpace(x.MarketId)) + .ToList(); + var invalidSchedules = marketsScheduleSettingsRaw.InvalidSchedules(); + + _readerWriterLockSlim.EnterWriteLock(); + + try + { + var platformScheduleSettings = marketsScheduleSettingsRaw + .Where(x => x.MarketId == _overnightMarginSettings.ScheduleMarketId).ToList(); + + var newMarketsScheduleSettings = marketsScheduleSettingsRaw + .Except(invalidSchedules) + .GroupBy(x => x.MarketId) + .ToDictionary(x => x.Key, x => x.ConcatWithPlatform(platformScheduleSettings, x.Key) + .Select(ScheduleSettings.Create) + .ToList()); + + _rawMarketScheduleCache = newMarketsScheduleSettings; + + var now = MarketsCacheWarmUpUnsafe(); + + HandleMarketStateChangesUnsafe(now, newMarketsScheduleSettings.Keys.ToArray()); + } + finally + { + _readerWriterLockSlim.ExitWriteLock(); + } + + if (invalidSchedules.Any()) + { + await _log.WriteWarningAsync(nameof(ScheduleSettingsCacheService), nameof(UpdateMarketsScheduleSettingsAsync), + $"{invalidSchedules.Count} of ScheduleSettingsContracts were invalid, so they were skipped: {invalidSchedules.ToJson()}"); + } + } + + public void HandleMarketStateChanges(DateTime currentTime) + { + _readerWriterLockSlim.EnterWriteLock(); + + try + { + HandleMarketStateChangesUnsafe(currentTime); + } + finally + { + _readerWriterLockSlim.ExitWriteLock(); + } + } + + private void HandleMarketStateChangesUnsafe(DateTime currentTime, string[] marketIds = null) + { + foreach (var (marketId, scheduleSettings) in _compiledMarketScheduleCache + // ReSharper disable once AssignNullToNotNullAttribute + .Where(x => marketIds.IsNullOrEmpty() || marketIds.Contains(x.Key))) + { + var newState = scheduleSettings.GetMarketState(marketId, currentTime); + if (!_marketStates.TryGetValue(marketId, out var oldState) || oldState.IsEnabled != newState.IsEnabled) + { + _cqrsSender.PublishEvent(new MarketStateChangedEvent + { + Id = marketId, + IsEnabled = newState.IsEnabled, + EventTimestamp = _dateService.Now(), + }); + } + + _marketStates[marketId] = newState; + } + } + + /// + public List GetPlatformTradingSchedule() + { + _readerWriterLockSlim.EnterReadLock(); + + try + { + return _compiledMarketScheduleCache[_overnightMarginSettings.ScheduleMarketId].ToList(); + } + finally + { + _readerWriterLockSlim.ExitReadLock(); + } + } + + /// + public Dictionary> GetMarketsTradingSchedule() + { + _readerWriterLockSlim.EnterReadLock(); + + try + { + return _compiledMarketScheduleCache.ToDictionary(); + } + finally + { + _readerWriterLockSlim.ExitReadLock(); + } + } + + public bool TryGetPlatformCurrentDisabledInterval(out CompiledScheduleTimeInterval disabledInterval) + { + var platformSchedule = GetPlatformTradingSchedule(); + + return !GetTradingEnabled(platformSchedule, out disabledInterval); + } + + public bool AssetPairTradingEnabled(string assetPairId, TimeSpan scheduleCutOff) + { + var schedule = GetCompiledAssetPairScheduleSettings(assetPairId, _dateService.Now(), scheduleCutOff); + + return GetTradingEnabled(schedule, out _); + } + + private bool GetTradingEnabled(IEnumerable timeIntervals, + out CompiledScheduleTimeInterval selectedInterval) + { + var currentDateTime = _dateService.Now(); + + var intersecting = timeIntervals.Where(x => x.Start <= currentDateTime && currentDateTime < x.End); + + selectedInterval = intersecting + .OrderByDescending(x => x.Schedule.Rank) + .FirstOrDefault(); + + return selectedInterval?.Schedule.IsTradeEnabled ?? true; + } + + private static bool TradingScheduleChanged(string key, + Dictionary> oldRawScheduleSettingsCache, + Dictionary> newRawScheduleSettingsCache) + { + if (!oldRawScheduleSettingsCache.TryGetValue(key, out var oldScheduleSettings) + || !newRawScheduleSettingsCache.TryGetValue(key, out var newRawScheduleSettings) + || oldScheduleSettings.Count != newRawScheduleSettings.Count) + { + return true; + } + + foreach (var oldScheduleSetting in oldScheduleSettings) + { + var newScheduleSetting = newRawScheduleSettings.FirstOrDefault(x => x.Id == oldScheduleSetting.Id); + + if (newScheduleSetting == null + || newScheduleSetting.Rank != oldScheduleSetting.Rank + || newScheduleSetting.IsTradeEnabled != oldScheduleSetting.IsTradeEnabled + || newScheduleSetting.PendingOrdersCutOff != oldScheduleSetting.PendingOrdersCutOff + || !newScheduleSetting.Start.Equals(oldScheduleSetting.Start) + || !newScheduleSetting.End.Equals(oldScheduleSetting.End)) + { + return true; + } + } + + return false; + } + + public Dictionary> GetCompiledAssetPairScheduleSettings() + { + _readerWriterLockSlim.EnterUpgradeableReadLock(); + + try + { + CacheWarmUpIncludingValidationUnsafe(); + + return _compiledAssetPairScheduleCache; + } + finally + { + _readerWriterLockSlim.ExitUpgradeableReadLock(); + } + } + + public Dictionary GetMarketState() + { + _readerWriterLockSlim.EnterReadLock(); + + try + { + return _marketStates.ToDictionary(); + } + finally + { + _readerWriterLockSlim.ExitReadLock(); + } + } + + public List GetCompiledAssetPairScheduleSettings(string assetPairId, + DateTime currentDateTime, TimeSpan scheduleCutOff) + { + if (string.IsNullOrEmpty(assetPairId)) + { + return new List(); + } + + _readerWriterLockSlim.EnterUpgradeableReadLock(); + + EnsureCacheValidUnsafe(currentDateTime); + + try + { + if (!_compiledAssetPairScheduleCache.ContainsKey(assetPairId)) + { + RecompileScheduleTimelineCacheUnsafe(assetPairId, currentDateTime, scheduleCutOff); + } + + return _compiledAssetPairScheduleCache.TryGetValue(assetPairId, out var timeline) + ? timeline + : new List(); + } + finally + { + _readerWriterLockSlim.ExitUpgradeableReadLock(); + } + } + + public void CacheWarmUpIncludingValidation() + { + _readerWriterLockSlim.EnterUpgradeableReadLock(); + + try + { + CacheWarmUpIncludingValidationUnsafe(); + } + finally + { + _readerWriterLockSlim.ExitUpgradeableReadLock(); + } + } + + private void CacheWarmUpIncludingValidationUnsafe() + { + EnsureCacheValidUnsafe(_dateService.Now()); + + if (!_compiledAssetPairScheduleCache.Any()) + { + CacheWarmUpUnsafe(); + } + } + + public void CacheWarmUp(params string[] assetPairIds) + { + _log.WriteInfoAsync(nameof(ScheduleSettingsCacheService), nameof(CacheWarmUp), + "Started asset pairs schedule cache update"); + + _readerWriterLockSlim.EnterUpgradeableReadLock(); + + try + { + CacheWarmUpUnsafe(assetPairIds); + } + finally + { + _readerWriterLockSlim.ExitUpgradeableReadLock(); + } + + _log.WriteInfoAsync(nameof(ScheduleSettingsCacheService), nameof(CacheWarmUp), + "Finished asset pairs schedule cache update"); + } + + private void CacheWarmUpUnsafe(params string[] assetPairIds) + { + var currentDateTime = _dateService.Now(); + var assetPairIdsToWarmUp = assetPairIds.Any() + ? assetPairIds.ToArray() + : _assetPairsCache.GetAllIds().ToArray(); + + foreach (var assetPairId in assetPairIdsToWarmUp) + { + if (!_compiledAssetPairScheduleCache.ContainsKey(assetPairId)) + { + //todo Zero timespan is ok for market orders, but if pending cut off should be applied, we will need one more cache for them.. + RecompileScheduleTimelineCacheUnsafe(assetPairId, currentDateTime, TimeSpan.Zero); + } + } + } + + public void MarketsCacheWarmUp() + { + _log.WriteInfoAsync(nameof(ScheduleSettingsCacheService), nameof(MarketsCacheWarmUp), + "Started market schedule cache update"); + + _readerWriterLockSlim.EnterWriteLock(); + + try + { + MarketsCacheWarmUpUnsafe(); + } + finally + { + _readerWriterLockSlim.ExitWriteLock(); + } + + _log.WriteInfoAsync(nameof(ScheduleSettingsCacheService), nameof(MarketsCacheWarmUp), + "Finished market schedule cache update"); + } + + private DateTime MarketsCacheWarmUpUnsafe() + { + var now = _dateService.Now(); + + _compiledMarketScheduleCache = _rawMarketScheduleCache + .ToDictionary(x => x.Key, x => CompileSchedule(x.Value, now, TimeSpan.Zero)); + HandleMarketStateChangesUnsafe(now, _rawMarketScheduleCache.Keys.ToArray()); + + return now; + } + + /// + /// It invalidates the cache after 00:00:00.000 each day on request + /// + /// + private void EnsureCacheValidUnsafe(DateTime currentDateTime) + { + //it must be safe to take _lastCacheRecalculationTime without a lock, because of upper UpgradeableReadLock + if (currentDateTime.Date.Subtract(_lastCacheRecalculationTime.Date) < TimeSpan.FromDays(1)) + { + return; + } + + _readerWriterLockSlim.EnterWriteLock(); + + try + { + _compiledAssetPairScheduleCache = + new Dictionary>(); + _lastCacheRecalculationTime = currentDateTime; + } + finally + { + _readerWriterLockSlim.ExitWriteLock(); + } + } + + private void RecompileScheduleTimelineCacheUnsafe(string assetPairId, DateTime currentDateTime, + TimeSpan scheduleCutOff) + { + var scheduleSettings = _rawAssetPairScheduleCache.TryGetValue(assetPairId, out var settings) + ? settings + : new List(); + + if (!scheduleSettings.Any()) + { + return; + } + + var resultingTimeIntervals = CompileSchedule(scheduleSettings, currentDateTime, scheduleCutOff); + + _readerWriterLockSlim.EnterWriteLock(); + try + { + _compiledAssetPairScheduleCache[assetPairId] = resultingTimeIntervals; + + _cqrsSender.PublishEvent(new CompiledScheduleChangedEvent + { + AssetPairId = assetPairId, + EventTimestamp = _dateService.Now(), + TimeIntervals = resultingTimeIntervals.Select(x => x.ToRabbitMqContract()).ToList(), + }); + } + finally + { + _readerWriterLockSlim.ExitWriteLock(); + } + } + + private static List CompileSchedule( + IEnumerable scheduleSettings, DateTime currentDateTime, TimeSpan scheduleCutOff) + { + var scheduleSettingsByType = scheduleSettings + .GroupBy(x => x.Start.GetConstraintType()) + .ToDictionary(x => x.Key, value => value); + + //handle weekly + var weekly = scheduleSettingsByType.TryGetValue(ScheduleConstraintType.Weekly, out var weeklySchedule) + ? weeklySchedule.SelectMany(sch => + { + var currentStart = CurrentWeekday(currentDateTime, sch.Start.DayOfWeek.Value) + .Add(sch.Start.Time.Subtract(scheduleCutOff)); + var currentEnd = CurrentWeekday(currentDateTime, sch.End.DayOfWeek.Value) + .Add(sch.End.Time.Add(scheduleCutOff)); + if (currentEnd < currentStart) + { + currentEnd = currentEnd.AddDays(7); + } + + return new[] + { + new CompiledScheduleTimeInterval(sch, currentStart, currentEnd), + new CompiledScheduleTimeInterval(sch, currentStart.AddDays(-7), currentEnd.AddDays(-7)), + new CompiledScheduleTimeInterval(sch, currentStart.AddDays(7), currentEnd.AddDays(7)) + }; + }) + : new List(); + + //handle single + var single = scheduleSettingsByType.TryGetValue(ScheduleConstraintType.Single, out var singleSchedule) + ? singleSchedule.Select(sch => new CompiledScheduleTimeInterval(sch, + sch.Start.Date.Value.Add(sch.Start.Time.Subtract(scheduleCutOff)), + sch.End.Date.Value.Add(sch.End.Time.Add(scheduleCutOff)))) + : new List(); + + //handle daily + var daily = scheduleSettingsByType.TryGetValue(ScheduleConstraintType.Daily, out var dailySchedule) + ? dailySchedule.SelectMany(sch => + { + var start = currentDateTime.Date.Add(sch.Start.Time.Subtract(scheduleCutOff)); + var end = currentDateTime.Date.Add(sch.End.Time.Add(scheduleCutOff)); + if (end < start) + { + end = end.AddDays(1); + } + + return new[] + { + new CompiledScheduleTimeInterval(sch, start, end), + new CompiledScheduleTimeInterval(sch, start.AddDays(-1), end.AddDays(-1)), + new CompiledScheduleTimeInterval(sch, start.AddDays(1), end.AddDays(1)) + }; + }) + : new List(); + + return weekly.Concat(single).Concat(daily).ToList(); + } + + private static DateTime CurrentWeekday(DateTime start, DayOfWeek day) + { + return start.Date.AddDays((int)day - (int)start.DayOfWeek); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Assets/AssetsCache.cs b/src/MarginTrading.Backend.Services/Assets/AssetsCache.cs index 8f10430ea..d90f66994 100644 --- a/src/MarginTrading.Backend.Services/Assets/AssetsCache.cs +++ b/src/MarginTrading.Backend.Services/Assets/AssetsCache.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using System.Threading; using MarginTrading.Backend.Core; diff --git a/src/MarginTrading.Backend.Services/Assets/AssetsManager.cs b/src/MarginTrading.Backend.Services/Assets/AssetsManager.cs index 40e14fe49..5a68dcbc5 100644 --- a/src/MarginTrading.Backend.Services/Assets/AssetsManager.cs +++ b/src/MarginTrading.Backend.Services/Assets/AssetsManager.cs @@ -1,39 +1,44 @@ -using System.Linq; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Linq; using System.Threading.Tasks; using Autofac; -using Lykke.Service.Assets.Client; +using JetBrains.Annotations; using MarginTrading.Backend.Core; +using MarginTrading.Common.Services; +using MarginTrading.SettingsService.Contracts; +using MarginTrading.SettingsService.Contracts.Asset; namespace MarginTrading.Backend.Services.Assets { - public class AssetsManager : IStartable + [UsedImplicitly] + public class AssetsManager : IStartable, IAssetsManager { - private readonly IAssetsService _assetsService; + private readonly IAssetsApi _assets; private readonly AssetsCache _assetsCache; + private readonly IConvertService _convertService; - public AssetsManager(IAssetsService assetsService, - AssetsCache assetsCache) + public AssetsManager(IAssetsApi assets, + AssetsCache assetsCache, + IConvertService convertService) { - _assetsService = assetsService; + _assets = assets; _assetsCache = assetsCache; + _convertService = convertService; } public void Start() { - UpdateCache().Wait(); + UpdateCacheAsync().Wait(); } - public async Task UpdateCache() + public async Task UpdateCacheAsync() { - var assets = (await _assetsService.AssetGetAllAsync()) + var assets = (await _assets.List()) .ToDictionary( a => a.Id, - a => (IAsset) new Asset - { - Id = a.Id, - Name = a.Name, - Accuracy = a.Accuracy - }); + a => (IAsset)_convertService.Convert(a)); _assetsCache.Init(assets); } diff --git a/src/MarginTrading.Backend.Services/Assets/IAssetsCache.cs b/src/MarginTrading.Backend.Services/Assets/IAssetsCache.cs index 5cf8b16b0..bc14b2ee0 100644 --- a/src/MarginTrading.Backend.Services/Assets/IAssetsCache.cs +++ b/src/MarginTrading.Backend.Services/Assets/IAssetsCache.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Services.Assets +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Services.Assets { public interface IAssetsCache { diff --git a/src/MarginTrading.Backend.Services/Assets/IAssetsManager.cs b/src/MarginTrading.Backend.Services/Assets/IAssetsManager.cs new file mode 100644 index 000000000..e35728879 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Assets/IAssetsManager.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; + +namespace MarginTrading.Backend.Services.Assets +{ + public interface IAssetsManager + { + Task UpdateCacheAsync(); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Caches/OrderCache.cs b/src/MarginTrading.Backend.Services/Caches/OrderCache.cs index 2ed0c5475..ceb75c4ae 100644 --- a/src/MarginTrading.Backend.Services/Caches/OrderCache.cs +++ b/src/MarginTrading.Backend.Services/Caches/OrderCache.cs @@ -1,64 +1,79 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.Messages; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; using MarginTrading.Backend.Services.Infrastructure; namespace MarginTrading.Backend.Services { public interface IOrderReader { - ImmutableArray GetAll(); - ImmutableArray GetActive(); + ImmutableArray GetAllOrders(); + ImmutableArray GetPositions(); + ImmutableArray GetPositions(string instrument); + ImmutableArray GetPositionsByFxAssetPairId(string fxAssetPairId); ImmutableArray GetPending(); + bool TryGetOrderById(string orderId, out Order order); } public class OrdersCache : IOrderReader { - private readonly IContextFactory _contextFactory; + public OrdersCache() + { + Active = new OrderCacheGroup(new Order[0], OrderStatus.Active); + Inactive = new OrderCacheGroup(new Order[0], OrderStatus.Inactive); + InProgress = new OrderCacheGroup(new Order[0], OrderStatus.ExecutionStarted); + Positions = new PositionsCache(new Position[0]); + } - public OrdersCache(IContextFactory contextFactory) + public OrderCacheGroup Active { get; private set; } + public OrderCacheGroup Inactive { get; private set; } + public OrderCacheGroup InProgress { get; private set; } + public PositionsCache Positions { get; private set; } + + public ImmutableArray GetAllOrders() { - _contextFactory = contextFactory; - - ActiveOrders = new OrderCacheGroup(new Order[0], OrderStatus.Active); - WaitingForExecutionOrders = new OrderCacheGroup(new Order[0], OrderStatus.WaitingForExecution); - ClosingOrders = new OrderCacheGroup(new Order[0], OrderStatus.Closing); + return Active.GetAllOrders() + .Union(Inactive.GetAllOrders()) + .Union(InProgress.GetAllOrders()).ToImmutableArray(); } - public OrderCacheGroup ActiveOrders { get; private set; } - public OrderCacheGroup WaitingForExecutionOrders { get; private set; } - public OrderCacheGroup ClosingOrders { get; private set; } - - public ImmutableArray GetAll() + public ImmutableArray GetPositions() { - using (_contextFactory.GetReadSyncContext($"{nameof(OrdersCache)}.{nameof(GetAll)}")) - return ActiveOrders.GetAllOrders() - .Union(WaitingForExecutionOrders.GetAllOrders()) - .Union(ClosingOrders.GetAllOrders()).ToImmutableArray(); + return Positions.GetAllPositions().ToImmutableArray(); } - public ImmutableArray GetActive() + public ImmutableArray GetPositions(string instrument) { - return ActiveOrders.GetAllOrders().ToImmutableArray(); + return Positions.GetPositionsByInstrument(instrument).ToImmutableArray(); } - public ImmutableArray GetPending() + public ImmutableArray GetPositionsByFxAssetPairId(string fxAssetPairId) { - return WaitingForExecutionOrders.GetAllOrders().ToImmutableArray(); + return Positions.GetPositionsByFxInstrument(fxAssetPairId).ToImmutableArray(); } - public ImmutableArray GetPendingForMarginRecalc(string instrument) + public ImmutableArray GetPending() { - return WaitingForExecutionOrders.GetOrdersByMarginInstrument(instrument).ToImmutableArray(); + return Active.GetAllOrders().ToImmutableArray(); } +// public ImmutableArray GetPendingForMarginRecalc(string instrument) +// { +// return WaitingForExecutionOrders.GetOrdersByMarginInstrument(instrument).ToImmutableArray(); +// } + public bool TryGetOrderById(string orderId, out Order order) { - return WaitingForExecutionOrders.TryGetOrderById(orderId, out order) || - ActiveOrders.TryGetOrderById(orderId, out order); + return Active.TryGetOrderById(orderId, out order) || + Inactive.TryGetOrderById(orderId, out order); } public Order GetOrderById(string orderId) @@ -69,11 +84,12 @@ public Order GetOrderById(string orderId) throw new Exception(string.Format(MtMessages.OrderNotFound, orderId)); } - public void InitOrders(List orders) + public void InitOrders(List orders, List positions) { - ActiveOrders = new OrderCacheGroup(orders, OrderStatus.Active); - WaitingForExecutionOrders = new OrderCacheGroup(orders, OrderStatus.WaitingForExecution); - ClosingOrders = new OrderCacheGroup(orders, OrderStatus.Closing); + Active = new OrderCacheGroup(orders, OrderStatus.Active); + Inactive = new OrderCacheGroup(orders, OrderStatus.Inactive); + InProgress = new OrderCacheGroup(orders, OrderStatus.ExecutionStarted); + Positions = new PositionsCache(positions); } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/OrderCacheGroup.cs b/src/MarginTrading.Backend.Services/Caches/OrderCacheGroup.cs similarity index 62% rename from src/MarginTrading.Backend.Services/OrderCacheGroup.cs rename to src/MarginTrading.Backend.Services/Caches/OrderCacheGroup.cs index dd639417a..defdc5a68 100644 --- a/src/MarginTrading.Backend.Services/OrderCacheGroup.cs +++ b/src/MarginTrading.Backend.Services/Caches/OrderCacheGroup.cs @@ -1,9 +1,14 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; using System.Threading; using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.Messages; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; namespace MarginTrading.Backend.Services { @@ -12,8 +17,9 @@ public class OrderCacheGroup private readonly Dictionary _ordersById; private readonly Dictionary> _orderIdsByAccountId; private readonly Dictionary> _orderIdsByInstrumentId; + private readonly Dictionary> _orderIdsByFxInstrumentId; private readonly Dictionary<(string, string), HashSet> _orderIdsByAccountIdAndInstrumentId; - private readonly Dictionary> _orderIdsByMarginInstrumentId; + //private readonly Dictionary> _orderIdsByMarginInstrumentId; private readonly OrderStatus _status; private readonly ReaderWriterLockSlim _lockSlim = new ReaderWriterLockSlim(); @@ -29,18 +35,21 @@ public OrderCacheGroup(IReadOnlyCollection orders, OrderStatus status) { _ordersById = statusOrders.ToDictionary(x => x.Id); - _orderIdsByInstrumentId = statusOrders.GroupBy(x => x.Instrument) + _orderIdsByInstrumentId = statusOrders.GroupBy(x => x.AssetPairId) + .ToDictionary(x => x.Key, x => x.Select(o => o.Id).ToHashSet()); + + _orderIdsByFxInstrumentId = statusOrders.GroupBy(x => x.FxAssetPairId) .ToDictionary(x => x.Key, x => x.Select(o => o.Id).ToHashSet()); _orderIdsByAccountId = statusOrders.GroupBy(x => x.AccountId) .ToDictionary(x => x.Key, x => x.Select(o => o.Id).ToHashSet()); - _orderIdsByAccountIdAndInstrumentId = statusOrders.GroupBy(x => GetAccountInstrumentCacheKey(x.AccountId, x.Instrument)) + _orderIdsByAccountIdAndInstrumentId = statusOrders.GroupBy(x => GetAccountInstrumentCacheKey(x.AccountId, x.AssetPairId)) .ToDictionary(x => x.Key, x => x.Select(o => o.Id).ToHashSet()); - _orderIdsByMarginInstrumentId = statusOrders.Where(x => !string.IsNullOrEmpty(x.MarginCalcInstrument)) - .GroupBy(x => x.MarginCalcInstrument) - .ToDictionary(x => x.Key, x => x.Select(o => o.Id).ToHashSet()); +// _orderIdsByMarginInstrumentId = statusOrders.Where(x => !string.IsNullOrEmpty(x.MarginCalcInstrument)) +// .GroupBy(x => x.MarginCalcInstrument) +// .ToDictionary(x => x.Key, x => x.Select(o => o.Id).ToHashSet()); } finally { @@ -63,29 +72,33 @@ public void Add(Order order) _orderIdsByAccountId.Add(order.AccountId, new HashSet()); _orderIdsByAccountId[order.AccountId].Add(order.Id); - if (!_orderIdsByInstrumentId.ContainsKey(order.Instrument)) - _orderIdsByInstrumentId.Add(order.Instrument, new HashSet()); - _orderIdsByInstrumentId[order.Instrument].Add(order.Id); + if (!_orderIdsByInstrumentId.ContainsKey(order.AssetPairId)) + _orderIdsByInstrumentId.Add(order.AssetPairId, new HashSet()); + _orderIdsByInstrumentId[order.AssetPairId].Add(order.Id); - var accountInstrumentCacheKey = GetAccountInstrumentCacheKey(order.AccountId, order.Instrument); + if (!_orderIdsByFxInstrumentId.ContainsKey(order.FxAssetPairId)) + _orderIdsByFxInstrumentId.Add(order.FxAssetPairId, new HashSet()); + _orderIdsByFxInstrumentId[order.FxAssetPairId].Add(order.Id); + + var accountInstrumentCacheKey = GetAccountInstrumentCacheKey(order.AccountId, order.AssetPairId); if (!_orderIdsByAccountIdAndInstrumentId.ContainsKey(accountInstrumentCacheKey)) _orderIdsByAccountIdAndInstrumentId.Add(accountInstrumentCacheKey, new HashSet()); _orderIdsByAccountIdAndInstrumentId[accountInstrumentCacheKey].Add(order.Id); - if (!string.IsNullOrEmpty(order.MarginCalcInstrument)) - { - if(!_orderIdsByMarginInstrumentId.ContainsKey(order.MarginCalcInstrument)) - _orderIdsByMarginInstrumentId.Add(order.MarginCalcInstrument, new HashSet()); - _orderIdsByMarginInstrumentId[order.MarginCalcInstrument].Add(order.Id); - } +// if (!string.IsNullOrEmpty(order.MarginCalcInstrument)) +// { +// if(!_orderIdsByMarginInstrumentId.ContainsKey(order.MarginCalcInstrument)) +// _orderIdsByMarginInstrumentId.Add(order.MarginCalcInstrument, new HashSet()); +// _orderIdsByMarginInstrumentId[order.MarginCalcInstrument].Add(order.Id); +// } } finally { _lockSlim.ExitWriteLock(); } - var account = MtServiceLocator.AccountsCacheService.Get(order.ClientId, order.AccountId); + var account = MtServiceLocator.AccountsCacheService.Get(order.AccountId); account.CacheNeedsToBeUpdated(); } @@ -97,13 +110,11 @@ public void Remove(Order order) { if (_ordersById.Remove(order.Id)) { - _orderIdsByInstrumentId[order.Instrument].Remove(order.Id); - _orderIdsByAccountId[order.AccountId].Remove(order.Id); - _orderIdsByAccountIdAndInstrumentId[GetAccountInstrumentCacheKey(order.AccountId, order.Instrument)].Remove(order.Id); + OnRemove(order); - if (!string.IsNullOrEmpty(order.MarginCalcInstrument) - && (_orderIdsByMarginInstrumentId[order.MarginCalcInstrument]?.Contains(order.Id) ?? false)) - _orderIdsByMarginInstrumentId[order.MarginCalcInstrument].Remove(order.Id); +// if (!string.IsNullOrEmpty(order.MarginCalcInstrument) +// && (_orderIdsByMarginInstrumentId[order.MarginCalcInstrument]?.Contains(order.Id) ?? false)) +// _orderIdsByMarginInstrumentId[order.MarginCalcInstrument].Remove(order.Id); } else throw new Exception(string.Format(MtMessages.CantRemoveOrderWithStatus, order.Id, _status)); @@ -113,9 +124,40 @@ public void Remove(Order order) _lockSlim.ExitWriteLock(); } - var account = MtServiceLocator.AccountsCacheService?.Get(order.ClientId, order.AccountId); + var account = MtServiceLocator.AccountsCacheService?.Get(order.AccountId); account?.CacheNeedsToBeUpdated(); } + + public bool TryPopById(string orderId, out Order result) + { + _lockSlim.EnterWriteLock(); + + try + { + if (!_ordersById.ContainsKey(orderId)) + { + result = null; + return false; + } + result = _ordersById[orderId]; + _ordersById.Remove(orderId); + OnRemove(result); + return true; + } + finally + { + _lockSlim.ExitWriteLock(); + } + } + + private void OnRemove(Order order) + { + _orderIdsByInstrumentId[order.AssetPairId].Remove(order.Id); + _orderIdsByFxInstrumentId[order.FxAssetPairId].Remove(order.Id); + _orderIdsByAccountId[order.AccountId].Remove(order.Id); + _orderIdsByAccountIdAndInstrumentId[GetAccountInstrumentCacheKey(order.AccountId, order.AssetPairId)] + .Remove(order.Id); + } #endregion @@ -149,7 +191,7 @@ public bool TryGetOrderById(string orderId, out Order result) _lockSlim.ExitReadLock(); } } - + public IReadOnlyCollection GetOrdersByInstrument(string instrument) { if (string.IsNullOrWhiteSpace(instrument)) @@ -170,19 +212,18 @@ public IReadOnlyCollection GetOrdersByInstrument(string instrument) } } - public IReadOnlyCollection GetOrdersByMarginInstrument(string instrument) + public IReadOnlyCollection GetOrdersByFxInstrument(string fxInstrument) { - if (string.IsNullOrWhiteSpace(instrument)) - throw new ArgumentException(nameof(instrument)); + if (string.IsNullOrWhiteSpace(fxInstrument)) + throw new ArgumentException(nameof(fxInstrument)); _lockSlim.EnterReadLock(); try { - if (!_orderIdsByMarginInstrumentId.ContainsKey(instrument)) - return new List(); - - return _orderIdsByMarginInstrumentId[instrument].Select(id => _ordersById[id]).ToList(); + return _orderIdsByFxInstrumentId.ContainsKey(fxInstrument) + ? _orderIdsByFxInstrumentId[fxInstrument].Select(id => _ordersById[id]).ToList() + : new List(); } finally { @@ -190,6 +231,26 @@ public IReadOnlyCollection GetOrdersByMarginInstrument(string instrument) } } +// public IReadOnlyCollection GetOrdersByMarginInstrument(string instrument) +// { +// if (string.IsNullOrWhiteSpace(instrument)) +// throw new ArgumentException(nameof(instrument)); +// +// _lockSlim.EnterReadLock(); +// +// try +// { +// if (!_orderIdsByMarginInstrumentId.ContainsKey(instrument)) +// return new List(); +// +// return _orderIdsByMarginInstrumentId[instrument].Select(id => _ordersById[id]).ToList(); +// } +// finally +// { +// _lockSlim.ExitReadLock(); +// } +// } + public ICollection GetOrdersByInstrumentAndAccount(string instrument, string accountId) { if (string.IsNullOrWhiteSpace(instrument)) @@ -221,7 +282,7 @@ public IReadOnlyCollection GetAllOrders() try { - return _ordersById.Values; + return _ordersById.Values.ToArray(); } finally { diff --git a/src/MarginTrading.Backend.Services/Caches/OrderCacheManager.cs b/src/MarginTrading.Backend.Services/Caches/OrderCacheManager.cs index 163d7417e..3c12c14ff 100644 --- a/src/MarginTrading.Backend.Services/Caches/OrderCacheManager.cs +++ b/src/MarginTrading.Backend.Services/Caches/OrderCacheManager.cs @@ -1,69 +1,79 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Common; using Common.Log; using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Helpers; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Core.Trading; +using MarginTrading.Backend.Services.Helpers; -namespace MarginTrading.Backend.Services +namespace MarginTrading.Backend.Services.Caches { public class OrderCacheManager : TimerPeriod { private readonly OrdersCache _orderCache; - private readonly IMarginTradingBlobRepository _marginTradingBlobRepository; + private readonly IMarginTradingBlobRepository _blobRepository; + private readonly IOrdersHistoryRepository _ordersHistoryRepository; + private readonly IPositionsHistoryRepository _positionsHistoryRepository; private readonly ILog _log; - private readonly IAccountsCacheService _accountsCacheService; - private const string BlobName= "orders"; + + public const string OrdersBlobName= "orders"; + public const string PositionsBlobName= "positions"; + + private static readonly OrderStatus[] OrderTerminalStatuses = {OrderStatus.Canceled, OrderStatus.Rejected, OrderStatus.Executed}; + private static readonly PositionHistoryType PositionTerminalStatus = PositionHistoryType.Close; public OrderCacheManager(OrdersCache orderCache, - IMarginTradingBlobRepository marginTradingBlobRepository, - ILog log, IAccountsCacheService accountsCacheService) - : base(nameof(OrderCacheManager), 5000, log) + IMarginTradingBlobRepository blobRepository, + IOrdersHistoryRepository ordersHistoryRepository, + IPositionsHistoryRepository positionsHistoryRepository, + MarginTradingSettings marginTradingSettings, + ILog log) + : base(nameof(OrderCacheManager), marginTradingSettings.BlobPersistence.OrdersDumpPeriodMilliseconds, log) { _orderCache = orderCache; - _marginTradingBlobRepository = marginTradingBlobRepository; + _blobRepository = blobRepository; + _ordersHistoryRepository = ordersHistoryRepository; + _positionsHistoryRepository = positionsHistoryRepository; _log = log; - _accountsCacheService = accountsCacheService; } public override void Start() { - var orders = _marginTradingBlobRepository.Read>(LykkeConstants.StateBlobContainer, BlobName) ?? new List(); - - orders.ForEach(o => - { - // migrate orders to add LegalEntity field - // todo: can be removed once published to prod - if (o.LegalEntity == null) - o.LegalEntity = _accountsCacheService.Get(o.ClientId, o.AccountId).LegalEntity; - }); - - _orderCache.InitOrders(orders); + InferInitDataFromBlobAndHistory(); base.Start(); } public override async Task Execute() { - await DumpToRepository(); + await Task.WhenAll(DumpOrdersToRepository(), DumpPositionsToRepository()); } public override void Stop() { - DumpToRepository().Wait(); + DumpOrdersToRepository().Wait(); + DumpPositionsToRepository().Wait(); base.Stop(); } - private async Task DumpToRepository() + private async Task DumpOrdersToRepository() { - try { - var orders = _orderCache.GetAll(); + var orders = _orderCache.GetAllOrders(); if (orders != null) { - await _marginTradingBlobRepository.Write(LykkeConstants.StateBlobContainer, BlobName, orders); + await _blobRepository.WriteAsync(LykkeConstants.StateBlobContainer, OrdersBlobName, orders); } } catch (Exception ex) @@ -71,5 +81,205 @@ private async Task DumpToRepository() await _log.WriteErrorAsync(nameof(OrdersCache), "Save orders", "", ex); } } + + private async Task DumpPositionsToRepository() + { + try + { + var positions = _orderCache.GetPositions(); + + if (positions != null) + { + await _blobRepository.WriteAsync(LykkeConstants.StateBlobContainer, PositionsBlobName, positions); + } + } + catch (Exception ex) + { + await _log.WriteErrorAsync(nameof(OrdersCache), "Save positions", "", ex); + } + } + + /// + /// Infer init data from blob and history. + /// + private (List Orders, List Positions) InferInitDataFromBlobAndHistory() + { + _log.WriteInfo(nameof(OrderCacheManager), nameof(InferInitDataFromBlobAndHistory), + $"Start reading order and position data from blob."); + + var blobOrdersTask = _blobRepository.ReadWithTimestampAsync>( + LykkeConstants.StateBlobContainer, OrdersBlobName); + var blobPositionsTask = _blobRepository.ReadWithTimestampAsync>( + LykkeConstants.StateBlobContainer, PositionsBlobName); + var (blobOrders, blobOrdersTimestamp) = blobOrdersTask.GetAwaiter().GetResult(); + var (blobPositions, blobPositionsTimestamp) = blobPositionsTask.GetAwaiter().GetResult(); + + _log.WriteInfo(nameof(OrderCacheManager), nameof(InferInitDataFromBlobAndHistory), + $"Finish reading data from blob, there are [{blobOrders.Count}] orders, [{blobPositions.Count}] positions. Start checking historical data."); + + var orderSnapshotsTask = _ordersHistoryRepository.GetLastSnapshot(blobOrdersTimestamp); + var positionSnapshotsTask = _positionsHistoryRepository.GetLastSnapshot(blobPositionsTimestamp); + var orderSnapshots = orderSnapshotsTask.GetAwaiter().GetResult().Select(OrderHistory.Create).ToList(); + PreProcess(orderSnapshots); + var positionSnapshots = positionSnapshotsTask.GetAwaiter().GetResult(); + + _log.WriteInfo(nameof(OrderCacheManager), nameof(InferInitDataFromBlobAndHistory), + $"Finish reading historical data. #{orderSnapshots.Count} order history items since [{blobOrdersTimestamp:s}], #{positionSnapshots.Count} position history items since [{blobPositionsTimestamp:s}]."); + + var (ordersResult, orderIdsChangedFromHistory) = MapOrders(blobOrders.ToDictionary(x => x.Id, x => x), + orderSnapshots.ToDictionary(x => x.Id, x => x)); + var (positionsResult, positionIdsChangedFromHistory) = MapPositions( + blobPositions.ToDictionary(x => x.Id, x => x), positionSnapshots.ToDictionary(x => x.Id, x => x)); + + RefreshRelated(ordersResult.ToDictionary(x => x.Id), positionsResult.ToDictionary(x => x.Id), + orderSnapshots); + + _log.WriteInfo(nameof(OrderCacheManager), nameof(InferInitDataFromBlobAndHistory), + $"Initializing cache with [{ordersResult.Count}] orders and [{positionsResult.Count}] positions."); + + _orderCache.InitOrders(ordersResult, positionsResult); + + if (orderIdsChangedFromHistory.Any() || positionIdsChangedFromHistory.Any()) + { + _log.WriteInfo(nameof(OrderCacheManager), nameof(InferInitDataFromBlobAndHistory), + (orderIdsChangedFromHistory.Any() ? $"Some orders state was different from history: [{string.Join(",", orderIdsChangedFromHistory)}]. " : string.Empty) + + (positionIdsChangedFromHistory.Any() ? $"Some positions state was different from history: [{string.Join(",", positionIdsChangedFromHistory)}]. " : string.Empty) + + "Dumping merged order and position data to the blob." + ); + + if (orderIdsChangedFromHistory.Any()) + { + DumpOrdersToRepository().Wait(); + } + + if (positionIdsChangedFromHistory.Any()) + { + DumpPositionsToRepository().Wait(); + } + + _log.WriteInfo(nameof(OrderCacheManager), nameof(InferInitDataFromBlobAndHistory), + "Finished dumping merged order and position data to the blob." + ); + } + + return (ordersResult, positionsResult); + } + + private void PreProcess(List orderHistories) + { + foreach (var orderHistory in orderHistories) + { + if (orderHistory.Status == OrderStatus.Placed) + { + orderHistory.Status = OrderStatus.Inactive; + } + } + } + + private static (List orders, List orderIdsChangedFromHistory) MapOrders( + Dictionary blobOrders, IReadOnlyDictionary orderSnapshots) + { + if (!orderSnapshots.Any()) + { + return (blobOrders.Values.ToList(), new List()); + } + + var changedIds = new List(); + var result = new List(); + + foreach (var (id, order) in blobOrders) + { + if (orderSnapshots.TryGetValue(id, out var orderHistory) + && order.Merge(orderHistory)) + { + if (OrderTerminalStatuses.Contains(orderHistory.Status)) + { + continue; + } + + changedIds.Add(id); + } + + result.Add(order); + } + + foreach (var (id, orderHistory) in orderSnapshots + .Where(x => !blobOrders.Keys.Contains(x.Key) && OrderTerminalStatuses.All(ts => ts != x.Value.Status))) + { + changedIds.Add(id); + result.Add(orderHistory.FromHistory()); + } + + return (result, changedIds); + } + + private static (List positions, List positionIdsChangedFromHistory) MapPositions( + Dictionary blobPositions, IReadOnlyDictionary positionSnapshots) + { + if (!positionSnapshots.Any()) + { + return (blobPositions.Values.ToList(), new List()); + } + + var changedIds = new List(); + var result = new List(); + + foreach (var (id, position) in blobPositions) + { + if (positionSnapshots.TryGetValue(id, out var positionHistory) + && position.Merge(positionHistory)) + { + if (positionHistory.HistoryType == PositionTerminalStatus) + { + continue; + } + + changedIds.Add(id); + } + result.Add(position); + } + + foreach (var (id, positionHistory) in positionSnapshots + .Where(x => !blobPositions.Keys.Contains(x.Key) && x.Value.HistoryType != PositionTerminalStatus)) + { + changedIds.Add(id); + result.Add(positionHistory.FromHistory()); + } + + return (result, changedIds); + } + + private static void RefreshRelated(Dictionary orders, Dictionary positions, + IEnumerable ordersFromHistory) + { + foreach (var orderHistory in ordersFromHistory) + { + if (!string.IsNullOrEmpty(orderHistory.ParentOrderId) + && orders.TryGetValue(orderHistory.ParentOrderId, out var order)) + { + if (OrderTerminalStatuses.Contains(orderHistory.Status)) + { + order.RemoveRelatedOrder(orderHistory.Id); + } + else + { + order.AddRelatedOrder(orderHistory.FromHistory()); + } + } + + if (!string.IsNullOrEmpty(orderHistory.PositionId) + && positions.TryGetValue(orderHistory.PositionId, out var position)) + { + if (OrderTerminalStatuses.Contains(orderHistory.Status)) + { + position.RemoveRelatedOrder(orderHistory.Id); + } + else + { + position.AddRelatedOrder(orderHistory.FromHistory()); + } + } + } + } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Caches/OvernightSwapCache.cs b/src/MarginTrading.Backend.Services/Caches/OvernightSwapCache.cs deleted file mode 100644 index 5509f8eb1..000000000 --- a/src/MarginTrading.Backend.Services/Caches/OvernightSwapCache.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using MarginTrading.Backend.Core; -using MoreLinq; - -namespace MarginTrading.Backend.Services.Caches -{ - public class OvernightSwapCache : IOvernightSwapCache - { - private Dictionary _cache; - - private static readonly object LockObj = new object(); - - public OvernightSwapCache() - { - ClearAll(); - } - - public bool TryGet(string key, out OvernightSwapCalculation item) - { - lock (LockObj) - { - return _cache.TryGetValue(key, out item); - } - } - - public IReadOnlyList GetAll() - { - lock(LockObj) - { - return _cache.Values.ToList(); - } - } - - public bool TryAdd(OvernightSwapCalculation item) - { - if (item == null) - return false; - - lock (LockObj) - { - if (_cache.ContainsKey(item.Key)) - return false; - - _cache[item.Key] = item; - return true; - } - } - - public bool AddOrReplace(OvernightSwapCalculation item) - { - if (item == null) - return false; - - lock (LockObj) - { - _cache.Remove(item.Key); - _cache[item.Key] = item; - } - - return true; - } - - private void SetAll(IEnumerable items) - { - if (items == null) - return; - - lock (LockObj) - { - items.Where(x => x != null).ForEach(x => _cache[x.Key] = x); - } - } - - public void Remove(OvernightSwapCalculation item) - { - if (item == null) - return; - - lock (LockObj) - { - _cache.Remove(item.Key); - } - } - - public void ClearAll() - { - lock (LockObj) - { - _cache = new Dictionary(); - } - } - - public void Initialize(IEnumerable items) - { - ClearAll(); - SetAll(items); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Caches/PositionsCache.cs b/src/MarginTrading.Backend.Services/Caches/PositionsCache.cs new file mode 100644 index 000000000..5b0484d89 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Caches/PositionsCache.cs @@ -0,0 +1,252 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Exceptions; +using MarginTrading.Backend.Core.Messages; +using MarginTrading.Backend.Core.Orders; + +namespace MarginTrading.Backend.Services +{ + public class PositionsCache + { + private readonly Dictionary _positionsById; + private readonly Dictionary> _positionIdsByAccountId; + private readonly Dictionary> _positionIdsByInstrumentId; + private readonly Dictionary> _positionIdsByFxInstrumentId; + private readonly Dictionary<(string, string), HashSet> _positionIdsByAccountIdAndInstrumentId; + private readonly ReaderWriterLockSlim _lockSlim = new ReaderWriterLockSlim(); + + public PositionsCache(IReadOnlyCollection positions) + { + _lockSlim.EnterWriteLock(); + + try + { + _positionsById = positions.ToDictionary(x => x.Id); + + _positionIdsByInstrumentId = positions.GroupBy(x => x.AssetPairId) + .ToDictionary(x => x.Key, x => x.Select(o => o.Id).ToHashSet()); + + _positionIdsByFxInstrumentId = positions.GroupBy(x => x.FxAssetPairId) + .ToDictionary(x => x.Key, x => x.Select(o => o.Id).ToHashSet()); + + _positionIdsByAccountId = positions.GroupBy(x => x.AccountId) + .ToDictionary(x => x.Key, x => x.Select(o => o.Id).ToHashSet()); + + _positionIdsByAccountIdAndInstrumentId = positions.GroupBy(x => GetAccountInstrumentCacheKey(x.AccountId, x.AssetPairId)) + .ToDictionary(x => x.Key, x => x.Select(o => o.Id).ToHashSet()); + } + finally + { + _lockSlim.ExitWriteLock(); + } + } + + #region Setters + + public void Add(Position position) + { + _lockSlim.EnterWriteLock(); + + try + { + _positionsById.Add(position.Id, position); + + if (!_positionIdsByAccountId.ContainsKey(position.AccountId)) + _positionIdsByAccountId.Add(position.AccountId, new HashSet()); + _positionIdsByAccountId[position.AccountId].Add(position.Id); + + if (!_positionIdsByInstrumentId.ContainsKey(position.AssetPairId)) + _positionIdsByInstrumentId.Add(position.AssetPairId, new HashSet()); + _positionIdsByInstrumentId[position.AssetPairId].Add(position.Id); + + if (!_positionIdsByFxInstrumentId.ContainsKey(position.FxAssetPairId)) + _positionIdsByFxInstrumentId.Add(position.FxAssetPairId, new HashSet()); + _positionIdsByFxInstrumentId[position.FxAssetPairId].Add(position.Id); + + var accountInstrumentCacheKey = GetAccountInstrumentCacheKey(position.AccountId, position.AssetPairId); + + if (!_positionIdsByAccountIdAndInstrumentId.ContainsKey(accountInstrumentCacheKey)) + _positionIdsByAccountIdAndInstrumentId.Add(accountInstrumentCacheKey, new HashSet()); + _positionIdsByAccountIdAndInstrumentId[accountInstrumentCacheKey].Add(position.Id); + } + finally + { + _lockSlim.ExitWriteLock(); + } + + var account = MtServiceLocator.AccountsCacheService.Get(position.AccountId); + account.CacheNeedsToBeUpdated(); + } + + public void Remove(Position order) + { + _lockSlim.EnterWriteLock(); + + try + { + if (_positionsById.Remove(order.Id)) + { + _positionIdsByInstrumentId[order.AssetPairId].Remove(order.Id); + _positionIdsByFxInstrumentId[order.FxAssetPairId].Remove(order.Id); + _positionIdsByAccountId[order.AccountId].Remove(order.Id); + _positionIdsByAccountIdAndInstrumentId[GetAccountInstrumentCacheKey(order.AccountId, order.AssetPairId)].Remove(order.Id); + } + else + throw new Exception(string.Format(MtMessages.CantRemovePosition, order.Id)); + } + finally + { + _lockSlim.ExitWriteLock(); + } + + var account = MtServiceLocator.AccountsCacheService?.Get(order.AccountId); + account?.CacheNeedsToBeUpdated(); + } + + #endregion + + + #region Getters + + public Position GetPositionById(string positionId) + { + if (TryGetPositionById(positionId, out var result)) + return result; + + throw new PositionNotFoundException(string.Format(MtMessages.CantGetPosition, positionId)); + } + + public bool TryGetPositionById(string positionId, out Position result) + { + _lockSlim.EnterReadLock(); + + try + { + return _positionsById.TryGetValue(positionId, out result); + } + finally + { + _lockSlim.ExitReadLock(); + } + } + + public IReadOnlyCollection GetPositionsByInstrument(string instrument) + { + if (string.IsNullOrWhiteSpace(instrument)) + throw new ArgumentException(nameof(instrument)); + + _lockSlim.EnterReadLock(); + + try + { + return _positionIdsByInstrumentId.ContainsKey(instrument) + ? _positionIdsByInstrumentId[instrument].Select(id => _positionsById[id]).ToList() + : new List(); + } + finally + { + _lockSlim.ExitReadLock(); + } + } + + public IReadOnlyCollection GetPositionsByFxInstrument(string fxInstrument) + { + if (string.IsNullOrWhiteSpace(fxInstrument)) + throw new ArgumentException(nameof(fxInstrument)); + + _lockSlim.EnterReadLock(); + + try + { + return _positionIdsByFxInstrumentId.ContainsKey(fxInstrument) + ? _positionIdsByFxInstrumentId[fxInstrument].Select(id => _positionsById[id]).ToList() + : new List(); + } + finally + { + _lockSlim.ExitReadLock(); + } + } + + public ICollection GetPositionsByInstrumentAndAccount(string instrument, string accountId) + { + if (string.IsNullOrWhiteSpace(instrument)) + throw new ArgumentException(nameof(instrument)); + + if (string.IsNullOrWhiteSpace(accountId)) + throw new ArgumentException(nameof(instrument)); + + var key = GetAccountInstrumentCacheKey(accountId, instrument); + + _lockSlim.EnterReadLock(); + + try + { + if (!_positionIdsByAccountIdAndInstrumentId.ContainsKey(key)) + return new List(); + + return _positionIdsByAccountIdAndInstrumentId[key].Select(id => _positionsById[id]).ToList(); + } + finally + { + _lockSlim.ExitReadLock(); + } + } + + public IReadOnlyCollection GetAllPositions() + { + _lockSlim.EnterReadLock(); + + try + { + return _positionsById.Values.ToArray(); + } + finally + { + _lockSlim.ExitReadLock(); + } + } + + public ICollection GetPositionsByAccountIds(params string[] accountIds) + { + _lockSlim.EnterReadLock(); + + var result = new List(); + + try + { + foreach (var accountId in accountIds) + { + if (!_positionIdsByAccountId.ContainsKey(accountId)) + continue; + + foreach (var orderId in _positionIdsByAccountId[accountId]) + result.Add(_positionsById[orderId]); + } + + return result; + } + finally + { + _lockSlim.ExitReadLock(); + } + } + #endregion + + + #region Helpers + + private (string, string) GetAccountInstrumentCacheKey(string accountId, string instrumentId) + { + return (accountId, instrumentId); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/EquivalentPricesService.cs b/src/MarginTrading.Backend.Services/EquivalentPricesService.cs deleted file mode 100644 index adf916de2..000000000 --- a/src/MarginTrading.Backend.Services/EquivalentPricesService.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Common; -using Common.Log; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Settings; - -namespace MarginTrading.Backend.Services -{ - public class EquivalentPricesService : IEquivalentPricesService - { - private readonly IAccountsCacheService _accountsCacheService; - private readonly ICfdCalculatorService _cfdCalculatorService; - private readonly MarginSettings _marginSettings; - private readonly ILog _log; - - public EquivalentPricesService( - IAccountsCacheService accountsCacheService, - ICfdCalculatorService cfdCalculatorService, - MarginSettings marginSettings, - ILog log) - { - _accountsCacheService = accountsCacheService; - _cfdCalculatorService = cfdCalculatorService; - _marginSettings = marginSettings; - _log = log; - } - - private string GetEquivalentAsset(string clientId, string accountId) - { - var account = _accountsCacheService.Get(clientId, accountId); - var equivalentSettings = - _marginSettings.ReportingEquivalentPricesSettings.FirstOrDefault(x => x.LegalEntity == account.LegalEntity); - - if(string.IsNullOrEmpty(equivalentSettings?.EquivalentAsset)) - throw new Exception($"No reporting equivalent prices asset found for legalEntity: {account.LegalEntity}"); - - return equivalentSettings.EquivalentAsset; - } - - public void EnrichOpeningOrder(Order order) - { - try - { - order.EquivalentAsset = GetEquivalentAsset(order.ClientId, order.AccountId); - - order.OpenPriceEquivalent = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.EquivalentAsset, - order.Instrument, order.LegalEntity); - } - catch (Exception e) - { - _log.WriteError("EnrichOpeningOrder", order.ToJson(), e); - } - } - - public void EnrichClosingOrder(Order order) - { - try - { - if (string.IsNullOrEmpty(order.EquivalentAsset)) - { - order.EquivalentAsset = GetEquivalentAsset(order.ClientId, order.AccountId); - } - - order.ClosePriceEquivalent = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.EquivalentAsset, - order.Instrument, order.LegalEntity); - } - catch (Exception e) - { - _log.WriteError("EnrichClosingOrder", order.ToJson(), e); - } - - - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/EventTypeEnum.cs b/src/MarginTrading.Backend.Services/EventTypeEnum.cs index 220c08968..424209eda 100644 --- a/src/MarginTrading.Backend.Services/EventTypeEnum.cs +++ b/src/MarginTrading.Backend.Services/EventTypeEnum.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Services +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Services { public enum EventTypeEnum { diff --git a/src/MarginTrading.Backend.Services/Events/AccountBalanceChangedEventArgs.cs b/src/MarginTrading.Backend.Services/Events/AccountBalanceChangedEventArgs.cs index f63c71e32..1ce7cc0d8 100644 --- a/src/MarginTrading.Backend.Services/Events/AccountBalanceChangedEventArgs.cs +++ b/src/MarginTrading.Backend.Services/Events/AccountBalanceChangedEventArgs.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using JetBrains.Annotations; using MarginTrading.Backend.Core; @@ -6,11 +9,13 @@ namespace MarginTrading.Backend.Services.Events { public class AccountBalanceChangedEventArgs { - public AccountBalanceChangedEventArgs([NotNull] MarginTradingAccount account) + public AccountBalanceChangedEventArgs([NotNull] string accountId) { - Account = account ?? throw new ArgumentNullException(nameof(account)); + AccountId = string.IsNullOrEmpty(accountId) + ? throw new ArgumentNullException(nameof(accountId)) + : accountId; } - public MarginTradingAccount Account { get; } + public string AccountId { get; } } } diff --git a/src/MarginTrading.Backend.Services/Events/BestPriceChangeEventArgs.cs b/src/MarginTrading.Backend.Services/Events/BestPriceChangeEventArgs.cs index b71e78f10..4e26a3113 100644 --- a/src/MarginTrading.Backend.Services/Events/BestPriceChangeEventArgs.cs +++ b/src/MarginTrading.Backend.Services/Events/BestPriceChangeEventArgs.cs @@ -1,3 +1,6 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using System; using MarginTrading.Backend.Core; @@ -5,11 +8,14 @@ namespace MarginTrading.Backend.Services.Events { public class BestPriceChangeEventArgs { - public BestPriceChangeEventArgs(InstrumentBidAskPair pair) + public BestPriceChangeEventArgs(InstrumentBidAskPair pair, bool isEod = false) { BidAskPair = pair ?? throw new ArgumentNullException(nameof(pair)); + IsEod = isEod; } public InstrumentBidAskPair BidAskPair { get; } + + public bool IsEod { get; } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Events/EventChannel.cs b/src/MarginTrading.Backend.Services/Events/EventChannel.cs index 1ec4a6307..480024719 100644 --- a/src/MarginTrading.Backend.Services/Events/EventChannel.cs +++ b/src/MarginTrading.Backend.Services/Events/EventChannel.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Linq; using Autofac; @@ -13,6 +16,7 @@ public class EventChannel : IEventChannel, IDisposable private readonly ILog _log; private IEventConsumer[] _consumers; private readonly object _sync = new object(); + private bool _isDisposed; public EventChannel(IComponentContext container, ILog log) { @@ -22,6 +26,9 @@ public EventChannel(IComponentContext container, ILog log) public void SendEvent(object sender, TEventArgs ea) { + if (_isDisposed) + return; + AssertInitialized(); foreach (var consumer in _consumers) { @@ -38,27 +45,25 @@ public void SendEvent(object sender, TEventArgs ea) public void Dispose() { + _isDisposed = true; _consumers = null; _container = null; } - public int AssertInitialized() + public void AssertInitialized() { if (null != _consumers) - return _consumers.Length; + return; + lock (_sync) { if (null != _consumers) - return _consumers.Length; - - if (null == _container) - throw new ObjectDisposedException(GetType().Name); + return; _consumers = Enumerable.OrderBy(_container.Resolve>>(), x => x.ConsumerRank).ToArray(); _container = null; } - return _consumers.Length; } } } diff --git a/src/MarginTrading.Backend.Services/Events/FxBestPriceChangeEventArgs.cs b/src/MarginTrading.Backend.Services/Events/FxBestPriceChangeEventArgs.cs new file mode 100644 index 000000000..18f2240fe --- /dev/null +++ b/src/MarginTrading.Backend.Services/Events/FxBestPriceChangeEventArgs.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MarginTrading.Backend.Core; + +namespace MarginTrading.Backend.Services.Events +{ + public class FxBestPriceChangeEventArgs + { + public FxBestPriceChangeEventArgs(InstrumentBidAskPair pair) + { + BidAskPair = pair ?? throw new ArgumentNullException(nameof(pair)); + } + + public InstrumentBidAskPair BidAskPair { get; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Events/IEventChannel.cs b/src/MarginTrading.Backend.Services/Events/IEventChannel.cs index fdb0d1a13..bd70ddd1e 100644 --- a/src/MarginTrading.Backend.Services/Events/IEventChannel.cs +++ b/src/MarginTrading.Backend.Services/Events/IEventChannel.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Services.Events +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Services.Events { public interface IEventChannel { diff --git a/src/MarginTrading.Backend.Services/Events/IEventConsumer.cs b/src/MarginTrading.Backend.Services/Events/IEventConsumer.cs index 461218127..384d1d055 100644 --- a/src/MarginTrading.Backend.Services/Events/IEventConsumer.cs +++ b/src/MarginTrading.Backend.Services/Events/IEventConsumer.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Services.Events +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Services.Events { public interface IEventConsumer { diff --git a/src/MarginTrading.Backend.Services/Events/LimitOrderSetEventArgs.cs b/src/MarginTrading.Backend.Services/Events/LimitOrderSetEventArgs.cs index cf3fdc6af..55cb577f3 100644 --- a/src/MarginTrading.Backend.Services/Events/LimitOrderSetEventArgs.cs +++ b/src/MarginTrading.Backend.Services/Events/LimitOrderSetEventArgs.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using System.Linq; using MarginTrading.Backend.Core; diff --git a/src/MarginTrading.Backend.Services/Events/LiquidationEndEventArgs.cs b/src/MarginTrading.Backend.Services/Events/LiquidationEndEventArgs.cs new file mode 100644 index 000000000..f18d6404d --- /dev/null +++ b/src/MarginTrading.Backend.Services/Events/LiquidationEndEventArgs.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; + +namespace MarginTrading.Backend.Services.Events +{ + public class LiquidationEndEventArgs + { + public string OperationId { get; set; } + + public DateTime CreationTime { get; set; } + + public string AccountId { get; set; } + + public List LiquidatedPositionIds { get; set; } + + public string FailReason { get; set; } + + public bool IsSuccess => string.IsNullOrEmpty(FailReason); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Events/MarginCallEventArgs.cs b/src/MarginTrading.Backend.Services/Events/MarginCallEventArgs.cs index 1df5f9b0a..34ff68454 100644 --- a/src/MarginTrading.Backend.Services/Events/MarginCallEventArgs.cs +++ b/src/MarginTrading.Backend.Services/Events/MarginCallEventArgs.cs @@ -1,3 +1,6 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using System; using MarginTrading.Backend.Core; @@ -5,12 +8,14 @@ namespace MarginTrading.Backend.Services.Events { public class MarginCallEventArgs { - public MarginCallEventArgs(MarginTradingAccount account) + public MarginCallEventArgs(MarginTradingAccount account, AccountLevel level) { - if (account == null) throw new ArgumentNullException(nameof(account)); - Account = account; + Account = account ?? throw new ArgumentNullException(nameof(account)); + MarginCallLevel = level; } - public MarginTradingAccount Account { get; set; } + public AccountLevel MarginCallLevel { get; } + + public MarginTradingAccount Account { get; } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Events/OrderActivatedEventArgs.cs b/src/MarginTrading.Backend.Services/Events/OrderActivatedEventArgs.cs index 1ff2629e5..db44f4642 100644 --- a/src/MarginTrading.Backend.Services/Events/OrderActivatedEventArgs.cs +++ b/src/MarginTrading.Backend.Services/Events/OrderActivatedEventArgs.cs @@ -1,5 +1,10 @@ -using JetBrains.Annotations; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; namespace MarginTrading.Backend.Services.Events { diff --git a/src/MarginTrading.Backend.Services/Events/OrderCancelledEventArgs.cs b/src/MarginTrading.Backend.Services/Events/OrderCancelledEventArgs.cs index 3ff56c298..75eb2f5f6 100644 --- a/src/MarginTrading.Backend.Services/Events/OrderCancelledEventArgs.cs +++ b/src/MarginTrading.Backend.Services/Events/OrderCancelledEventArgs.cs @@ -1,12 +1,19 @@ -using System; -using MarginTrading.Backend.Core; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Common; +using MarginTrading.Backend.Contracts.Activities; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; namespace MarginTrading.Backend.Services.Events { - public class OrderCancelledEventArgs:OrderUpdateBaseEventArgs + public class OrderCancelledEventArgs : OrderUpdateBaseEventArgs { - public OrderCancelledEventArgs(Order order): base(order) + public OrderCancelledEventArgs(Order order, OrderCancelledMetadata metadata) + : base(order) { + ActivitiesMetadata = metadata.ToJson(); } public override OrderUpdateType UpdateType => OrderUpdateType.Cancel; diff --git a/src/MarginTrading.Backend.Services/Events/OrderChangedEventArgs.cs b/src/MarginTrading.Backend.Services/Events/OrderChangedEventArgs.cs new file mode 100644 index 000000000..84a0a1928 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Events/OrderChangedEventArgs.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Common; +using MarginTrading.Backend.Contracts.Activities; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; + +namespace MarginTrading.Backend.Services.Events +{ + public class OrderChangedEventArgs: OrderUpdateBaseEventArgs + { + public OrderChangedEventArgs(Order order, OrderChangedMetadata metadata) + :base(order) + { + ActivitiesMetadata = metadata.ToJson(); + } + + public override OrderUpdateType UpdateType => OrderUpdateType.Change; + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Events/OrderClosedEventArgs.cs b/src/MarginTrading.Backend.Services/Events/OrderClosedEventArgs.cs deleted file mode 100644 index 13a31a60a..000000000 --- a/src/MarginTrading.Backend.Services/Events/OrderClosedEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using MarginTrading.Backend.Core; - -namespace MarginTrading.Backend.Services.Events -{ - public class OrderClosedEventArgs: OrderUpdateBaseEventArgs - { - public OrderClosedEventArgs(Order order):base(order) - { - } - - public override OrderUpdateType UpdateType => OrderUpdateType.Close; - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Events/OrderClosingEventArgs.cs b/src/MarginTrading.Backend.Services/Events/OrderClosingEventArgs.cs deleted file mode 100644 index 08f1c1a04..000000000 --- a/src/MarginTrading.Backend.Services/Events/OrderClosingEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using JetBrains.Annotations; -using MarginTrading.Backend.Core; - -namespace MarginTrading.Backend.Services.Events -{ - public class OrderClosingEventArgs: OrderUpdateBaseEventArgs - { - public OrderClosingEventArgs([NotNull] Order order) : base(order) - { - } - - public override OrderUpdateType UpdateType => OrderUpdateType.Closing; - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Events/OrderExecutedEventArgs.cs b/src/MarginTrading.Backend.Services/Events/OrderExecutedEventArgs.cs new file mode 100644 index 000000000..901594c46 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Events/OrderExecutedEventArgs.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; + +namespace MarginTrading.Backend.Services.Events +{ + public class OrderExecutedEventArgs: OrderUpdateBaseEventArgs + { + public OrderExecutedEventArgs(Order order):base(order) + { + } + + public override OrderUpdateType UpdateType => OrderUpdateType.Executed; + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Events/OrderExecutionStartedEventArgs.cs b/src/MarginTrading.Backend.Services/Events/OrderExecutionStartedEventArgs.cs new file mode 100644 index 000000000..2bd0ec8a8 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Events/OrderExecutionStartedEventArgs.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; + +namespace MarginTrading.Backend.Services.Events +{ + public class OrderExecutionStartedEventArgs: OrderUpdateBaseEventArgs + { + public OrderExecutionStartedEventArgs([NotNull] Order order) : base(order) + { + } + + public override OrderUpdateType UpdateType => OrderUpdateType.ExecutionStarted; + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Events/OrderLimitsChangedEventArgs.cs b/src/MarginTrading.Backend.Services/Events/OrderLimitsChangedEventArgs.cs deleted file mode 100644 index 06da9a308..000000000 --- a/src/MarginTrading.Backend.Services/Events/OrderLimitsChangedEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using MarginTrading.Backend.Core; - -namespace MarginTrading.Backend.Services.Events -{ - public class OrderLimitsChangedEventArgs: OrderUpdateBaseEventArgs - { - public OrderLimitsChangedEventArgs(Order order):base(order) - { - } - - public override OrderUpdateType UpdateType => OrderUpdateType.ChangeOrderLimits; - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Events/OrderPlacedEventArgs.cs b/src/MarginTrading.Backend.Services/Events/OrderPlacedEventArgs.cs index 9a7d0b986..eda57f798 100644 --- a/src/MarginTrading.Backend.Services/Events/OrderPlacedEventArgs.cs +++ b/src/MarginTrading.Backend.Services/Events/OrderPlacedEventArgs.cs @@ -1,5 +1,9 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; namespace MarginTrading.Backend.Services.Events { diff --git a/src/MarginTrading.Backend.Services/Events/OrderRejectedEventArgs.cs b/src/MarginTrading.Backend.Services/Events/OrderRejectedEventArgs.cs index 8381f896b..0f7b301f0 100644 --- a/src/MarginTrading.Backend.Services/Events/OrderRejectedEventArgs.cs +++ b/src/MarginTrading.Backend.Services/Events/OrderRejectedEventArgs.cs @@ -1,5 +1,10 @@ -using JetBrains.Annotations; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; namespace MarginTrading.Backend.Services.Events { diff --git a/src/MarginTrading.Backend.Services/Events/OrderUpdateBaseEventArgs.cs b/src/MarginTrading.Backend.Services/Events/OrderUpdateBaseEventArgs.cs index 15aa7d3b8..4e1496888 100644 --- a/src/MarginTrading.Backend.Services/Events/OrderUpdateBaseEventArgs.cs +++ b/src/MarginTrading.Backend.Services/Events/OrderUpdateBaseEventArgs.cs @@ -1,6 +1,10 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using JetBrains.Annotations; -using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; namespace MarginTrading.Backend.Services.Events { @@ -12,6 +16,9 @@ protected OrderUpdateBaseEventArgs([NotNull] Order order) } public abstract OrderUpdateType UpdateType { get; } + [NotNull] public Order Order { get; } + + public string ActivitiesMetadata { get; protected set; } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Events/PositionUpdateEventArgs.cs b/src/MarginTrading.Backend.Services/Events/PositionUpdateEventArgs.cs index 008c50273..5777af455 100644 --- a/src/MarginTrading.Backend.Services/Events/PositionUpdateEventArgs.cs +++ b/src/MarginTrading.Backend.Services/Events/PositionUpdateEventArgs.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Services.Events +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Services.Events { public class PositionUpdateEventArgs { diff --git a/src/MarginTrading.Backend.Services/Events/StopOutEventArgs.cs b/src/MarginTrading.Backend.Services/Events/StopOutEventArgs.cs index fa7e93fd9..4acbc1bfe 100644 --- a/src/MarginTrading.Backend.Services/Events/StopOutEventArgs.cs +++ b/src/MarginTrading.Backend.Services/Events/StopOutEventArgs.cs @@ -1,3 +1,6 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using System; using MarginTrading.Backend.Core; @@ -5,15 +8,12 @@ namespace MarginTrading.Backend.Services.Events { public class StopOutEventArgs { - public StopOutEventArgs(MarginTradingAccount account, Order[] orders) + public StopOutEventArgs(MarginTradingAccount account) { if (account == null) throw new ArgumentNullException(nameof(account)); - if (orders == null) throw new ArgumentNullException(nameof(orders)); Account = account; - Orders = orders; } public MarginTradingAccount Account { get; } - public Order[] Orders { get; } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/EventsConsumers/MarginCallConsumer.cs b/src/MarginTrading.Backend.Services/EventsConsumers/MarginCallConsumer.cs index bc21327a1..f2a9e1da4 100644 --- a/src/MarginTrading.Backend.Services/EventsConsumers/MarginCallConsumer.cs +++ b/src/MarginTrading.Backend.Services/EventsConsumers/MarginCallConsumer.cs @@ -1,50 +1,52 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Concurrent; using System.Threading.Tasks; using Common; using Lykke.Common; +using MarginTrading.Backend.Contracts.Events; using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Messages; -using MarginTrading.Backend.Services.Assets; +using MarginTrading.Backend.Core.Settings; using MarginTrading.Backend.Services.Events; using MarginTrading.Backend.Services.Notifications; using MarginTrading.Common.Services; using MarginTrading.Common.Services.Client; -using MarginTrading.Common.Settings; namespace MarginTrading.Backend.Services.EventsConsumers { - // TODO: Rename by role - public class MarginCallConsumer : NotificationSenderBase, - IEventConsumer, - IEventConsumer, - IEventConsumer, - IEventConsumer + public class MarginCallConsumer : IEventConsumer + //IEventConsumer { private readonly IThreadSwitcher _threadSwitcher; private readonly IEmailService _emailService; private readonly IClientAccountService _clientAccountService; - private readonly IMarginTradingOperationsLogService _operationsLogService; - private static readonly ConcurrentDictionary LastNotifications = new ConcurrentDictionary(); - private const int NotificationsTimeout = 30; + private readonly IOperationsLogService _operationsLogService; + private readonly MarginTradingSettings _settings; private readonly IRabbitMqNotifyService _rabbitMqNotifyService; private readonly IDateService _dateService; + + private readonly ConcurrentDictionary _mc1LastNotifications = + new ConcurrentDictionary(); + private readonly ConcurrentDictionary _mc2LastNotifications = + new ConcurrentDictionary(); + private readonly ConcurrentDictionary _overnightMcLastNotifications = + new ConcurrentDictionary(); public MarginCallConsumer(IThreadSwitcher threadSwitcher, - IAppNotifications appNotifications, IEmailService emailService, IClientAccountService clientAccountService, - IMarginTradingOperationsLogService operationsLogService, + IOperationsLogService operationsLogService, + MarginTradingSettings settings, IRabbitMqNotifyService rabbitMqNotifyService, - IDateService dateService, - IAssetsCache assetsCache, - IAssetPairsCache assetPairsCache) - : base(appNotifications, clientAccountService, assetsCache, assetPairsCache) + IDateService dateService) { _threadSwitcher = threadSwitcher; _emailService = emailService; _clientAccountService = clientAccountService; _operationsLogService = operationsLogService; + _settings = settings; _rabbitMqNotifyService = rabbitMqNotifyService; _dateService = dateService; } @@ -55,25 +57,26 @@ void IEventConsumer.ConsumeEvent(object sender, MarginCallE { var account = ea.Account; var eventTime = _dateService.Now(); - var accountMarginEventMessage = AccountMarginEventMessageConverter.Create(account, false, eventTime); + var (level, lastNotifications) = LevelAndNotificationsCache(ea.MarginCallLevel); + + if (lastNotifications == null) + { + return; + } + + var accountMarginEventMessage = AccountMarginEventMessageConverter.Create(account, level, eventTime); + _threadSwitcher.SwitchThread(async () => { - if (LastNotifications.TryGetValue(account.Id, out var lastNotification) - && lastNotification.AddMinutes(NotificationsTimeout) > eventTime) + if (lastNotifications.TryGetValue(account.Id, out var lastNotification) + && lastNotification.AddMinutes(_settings.Throttling.MarginCallThrottlingPeriodMin) > eventTime) { return; } var marginEventTask = _rabbitMqNotifyService.AccountMarginEvent(accountMarginEventMessage); - _operationsLogService.AddLog("margin call", account.ClientId, account.Id, "", ea.ToJson()); - - var marginUsageLevel = account.GetMarginUsageLevel(); - var marginUsedPerc = marginUsageLevel == 0 ? 0 : 1 / marginUsageLevel; - - var notificationTask = SendMarginEventNotification(account.ClientId, string.Format( - MtMessages.Notifications_MarginCall, marginUsedPerc, - account.BaseAssetId)); + _operationsLogService.AddLog($"margin call: {level.ToString()}", account.Id, "", ea.ToJson()); var clientEmail = await _clientAccountService.GetEmail(account.ClientId); @@ -81,25 +84,30 @@ void IEventConsumer.ConsumeEvent(object sender, MarginCallE ? _emailService.SendMarginCallEmailAsync(clientEmail, account.BaseAssetId, account.Id) : Task.CompletedTask; - await Task.WhenAll(marginEventTask, notificationTask, emailTask); + await Task.WhenAll(marginEventTask, emailTask); - LastNotifications.AddOrUpdate(account.Id, eventTime, (s, time) => eventTime); + lastNotifications.AddOrUpdate(account.Id, eventTime, (s, times) => eventTime); }); } - public void ConsumeEvent(object sender, OrderPlacedEventArgs ea) - { - LastNotifications.TryRemove(ea.Order.AccountId, out var tmp); - } - - public void ConsumeEvent(object sender, OrderClosedEventArgs ea) - { - LastNotifications.TryRemove(ea.Order.AccountId, out var tmp); - } +//todo uncomment here, at class registration and in module when MTC-155 task is done + /// + /// That's for limit orders margin + /// +// public void ConsumeEvent(object sender, OrderPlacedEventArgs ea) +// { +// LastNotifications.TryRemove(ea.Order.AccountId, out var tmp); +// } - public void ConsumeEvent(object sender, OrderCancelledEventArgs ea) + private (MarginEventTypeContract, ConcurrentDictionary) LevelAndNotificationsCache(AccountLevel level) { - LastNotifications.TryRemove(ea.Order.AccountId, out var tmp); + switch (level) + { + case AccountLevel.MarginCall1: return (MarginEventTypeContract.MarginCall1, _mc1LastNotifications); + case AccountLevel.MarginCall2: return (MarginEventTypeContract.MarginCall2, _mc2LastNotifications); + case AccountLevel.OvernightMarginCall: return (MarginEventTypeContract.OvernightMarginCall, _overnightMcLastNotifications); + default: return (default, null); + } } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/EventsConsumers/OrderStateConsumer.cs b/src/MarginTrading.Backend.Services/EventsConsumers/OrderStateConsumer.cs index 24f20fee9..77ac093e3 100644 --- a/src/MarginTrading.Backend.Services/EventsConsumers/OrderStateConsumer.cs +++ b/src/MarginTrading.Backend.Services/EventsConsumers/OrderStateConsumer.cs @@ -1,129 +1,109 @@ -using System; -using System.Threading.Tasks; -using Lykke.Common; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Services.Assets; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Security.Cryptography; +using MarginTrading.Backend.Contracts.Activities; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; using MarginTrading.Backend.Services.Events; using MarginTrading.Backend.Services.Notifications; -using MarginTrading.Common.Services.Client; -using MarginTrading.Common.Settings; +using MarginTrading.Common.Extensions; +using MarginTrading.Common.Services; namespace MarginTrading.Backend.Services.EventsConsumers { - public class OrderStateConsumer : NotificationSenderBase, - IEventConsumer - { - private readonly IThreadSwitcher _threadSwitcher; - private readonly IClientNotifyService _clientNotifyService; - private readonly IAccountsCacheService _accountsCacheService; - private readonly AccountManager _accountManager; - private readonly IRabbitMqNotifyService _rabbitMqNotifyService; - - public OrderStateConsumer(IThreadSwitcher threadSwitcher, - IClientNotifyService clientNotifyService, - IAccountsCacheService accountsCacheService, - IAppNotifications appNotifications, - IClientAccountService clientAccountService, - AccountManager accountManager, - IRabbitMqNotifyService rabbitMqNotifyService, - IAssetsCache assetsCache, - IAssetPairsCache assetPairsCache) - : base(appNotifications, - clientAccountService, - assetsCache, - assetPairsCache) - { - _threadSwitcher = threadSwitcher; - _clientNotifyService = clientNotifyService; - _accountsCacheService = accountsCacheService; - _accountManager = accountManager; - _rabbitMqNotifyService = rabbitMqNotifyService; - } - - void IEventConsumer.ConsumeEvent(object sender, OrderUpdateBaseEventArgs ea) - { - SendOrderHistory(ea); - switch (ea.UpdateType) - { - case OrderUpdateType.Closing: - break; - case OrderUpdateType.Activate: - case OrderUpdateType.Place: - OnPlacedOrActivated(ea); - break; - case OrderUpdateType.Cancel: - OnCancelled(ea); - break; - case OrderUpdateType.Reject: - OnRejected(ea); - break; - case OrderUpdateType.Close: - OnClosed(ea); - break; - case OrderUpdateType.ChangeOrderLimits: - OnLimitsChanged(ea); - break; - default: - throw new ArgumentOutOfRangeException(nameof(ea.UpdateType), ea.UpdateType, string.Empty); - } - } - - int IEventConsumer.ConsumerRank => 100; - - private void OnRejected(OrderUpdateBaseEventArgs ea) - { - _rabbitMqNotifyService.OrderReject(ea.Order); - } + public class OrderStateConsumer : IEventConsumer + { + private readonly IRabbitMqNotifyService _rabbitMqNotifyService; + private readonly OrdersCache _ordersCache; + private readonly IDateService _dateService; + private readonly IEventChannel _orderCancelledEventChannel; + private readonly IEventChannel _orderChangedEventChannel; - private void OnClosed(OrderUpdateBaseEventArgs ea) - { - var order = ea.Order; - _threadSwitcher.SwitchThread(async () => - { - _clientNotifyService.NotifyOrderChanged(order); - - var totalFpl = order.GetTotalFpl(); - var account = _accountsCacheService.Get(order.ClientId, order.AccountId); - await _accountManager.UpdateBalanceAsync(account, totalFpl, AccountHistoryType.OrderClosed, - $"Balance changed on order close (id = {order.Id})", order.Id); + public OrderStateConsumer(IRabbitMqNotifyService rabbitMqNotifyService, + OrdersCache ordersCache, + IDateService dateService, + IEventChannel orderCancelledEventChannel, + IEventChannel orderChangedEventChannel) + { + _rabbitMqNotifyService = rabbitMqNotifyService; + _ordersCache = ordersCache; + _dateService = dateService; + _orderCancelledEventChannel = orderCancelledEventChannel; + _orderChangedEventChannel = orderChangedEventChannel; + } - await SendOrderChangedNotification(order.ClientId, order); - }); - } + void IEventConsumer.ConsumeEvent(object sender, OrderUpdateBaseEventArgs ea) + { + SendOrderHistory(ea); - private void OnCancelled(OrderUpdateBaseEventArgs ea) - { - var order = ea.Order; - _threadSwitcher.SwitchThread(async () => - { - _clientNotifyService.NotifyOrderChanged(order); - await SendOrderChangedNotification(order.ClientId, order); - }); - } + switch (ea.UpdateType) + { + case OrderUpdateType.Cancel: + case OrderUpdateType.Reject: + CancelRelatedOrders( + ea.Order.RelatedOrders, + ea.Order.CorrelationId, + OrderCancellationReason.BaseOrderCancelled); + RemoveRelatedOrderFromParent(ea.Order); + break; + } + } - private void OnPlacedOrActivated(OrderUpdateBaseEventArgs ea) - { - var order = ea.Order; - _threadSwitcher.SwitchThread(async () => - { - _clientNotifyService.NotifyOrderChanged(order); - await SendOrderChangedNotification(order.ClientId, order); - }); - } + int IEventConsumer.ConsumerRank => 100; - private void OnLimitsChanged(OrderUpdateBaseEventArgs ea) - { - var order = ea.Order; - _threadSwitcher.SwitchThread(() => - { - _clientNotifyService.NotifyOrderChanged(order); - return Task.CompletedTask; - }); - } + private void SendOrderHistory(OrderUpdateBaseEventArgs ea) + { + _rabbitMqNotifyService.OrderHistory( + ea.Order, + ea.UpdateType, + ea.ActivitiesMetadata); + } + + private void CancelRelatedOrders(List relatedOrderInfos, string correlationId, + OrderCancellationReason reason) + { + var metadata = new OrderCancelledMetadata + { + Reason = reason.ToType() + }; + + foreach (var relatedOrderInfo in relatedOrderInfos) + { + if (_ordersCache.Inactive.TryPopById(relatedOrderInfo.Id, out var inactiveRelatedOrder)) + { + inactiveRelatedOrder.Cancel(_dateService.Now(), null, correlationId); + _orderCancelledEventChannel.SendEvent(this, new OrderCancelledEventArgs(inactiveRelatedOrder, metadata)); + } + else if (_ordersCache.Active.TryPopById(relatedOrderInfo.Id, out var activeRelatedOrder)) + { + activeRelatedOrder.Cancel(_dateService.Now(), null, correlationId); + _orderCancelledEventChannel.SendEvent(this, new OrderCancelledEventArgs(activeRelatedOrder, metadata)); + } + } + } - private void SendOrderHistory(OrderUpdateBaseEventArgs ea) - { - _rabbitMqNotifyService.OrderHistory(ea.Order, ea.UpdateType); - } - } + private void RemoveRelatedOrderFromParent(Order order) + { + if (!string.IsNullOrEmpty(order.ParentOrderId) + && _ordersCache.TryGetOrderById(order.ParentOrderId, out var parentOrder)) + { + var metadata = new OrderChangedMetadata + { + UpdatedProperty = OrderChangedProperty.RelatedOrderRemoved, + OldValue = order.Id + }; + + parentOrder.RemoveRelatedOrder(order.Id); + _orderChangedEventChannel.SendEvent(this, new OrderChangedEventArgs(parentOrder, metadata)); + } + + if (!string.IsNullOrEmpty(order.ParentPositionId) + && _ordersCache.Positions.TryGetPositionById(order.ParentPositionId, out var parentPosition)) + { + parentPosition.RemoveRelatedOrder(order.Id); + } + } + } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/EventsConsumers/PositionsConsumer.cs b/src/MarginTrading.Backend.Services/EventsConsumers/PositionsConsumer.cs new file mode 100644 index 000000000..981bc6f3a --- /dev/null +++ b/src/MarginTrading.Backend.Services/EventsConsumers/PositionsConsumer.cs @@ -0,0 +1,367 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Common; +using Common.Log; +using MarginTrading.Backend.Contracts.Activities; +using MarginTrading.Backend.Contracts.Events; +using MarginTrading.Backend.Contracts.Orders; +using MarginTrading.Backend.Contracts.Positions; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.MatchingEngines; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Core.Trading; +using MarginTrading.Backend.Services.Assets; +using MarginTrading.Backend.Services.Events; +using MarginTrading.Backend.Services.Infrastructure; +using MarginTrading.Backend.Services.Notifications; +using MarginTrading.Common.Extensions; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Services.EventsConsumers +{ + public class PositionsConsumer: + IEventConsumer + { + private readonly OrdersCache _ordersCache; + private readonly IRabbitMqNotifyService _rabbitMqNotifyService; + private readonly IConvertService _convertService; + private readonly IDateService _dateService; + private readonly IAccountsCacheService _accountsCacheService; + private readonly IAccountUpdateService _accountUpdateService; + private readonly IIdentityGenerator _identityGenerator; + private readonly ICqrsSender _cqrsSender; + private readonly IEventChannel _orderCancelledEventChannel; + private readonly IEventChannel _orderChangedEventChannel; + private readonly IEventChannel _orderActivatedEventChannel; + private readonly IMatchingEngineRouter _meRouter; + private readonly ILog _log; + private readonly IAssetsCache _assetsCache; + + private static readonly ConcurrentDictionary LockObjects = + new ConcurrentDictionary(); + + public int ConsumerRank => 100; + + public PositionsConsumer(OrdersCache ordersCache, + IRabbitMqNotifyService rabbitMqNotifyService, + IConvertService convertService, + IDateService dateService, + IAccountsCacheService accountsCacheService, + IAccountUpdateService accountUpdateService, + IIdentityGenerator identityGenerator, + ICqrsSender cqrsSender, + IEventChannel orderCancelledEventChannel, + IEventChannel orderChangedEventChannel, + IEventChannel orderActivatedEventChannel, + IMatchingEngineRouter meRouter, + ILog log, + IAssetsCache assetsCache) + { + _ordersCache = ordersCache; + _rabbitMqNotifyService = rabbitMqNotifyService; + _convertService = convertService; + _dateService = dateService; + _accountsCacheService = accountsCacheService; + _accountUpdateService = accountUpdateService; + _identityGenerator = identityGenerator; + _cqrsSender = cqrsSender; + _orderCancelledEventChannel = orderCancelledEventChannel; + _orderChangedEventChannel = orderChangedEventChannel; + _orderActivatedEventChannel = orderActivatedEventChannel; + _meRouter = meRouter; + _log = log; + _assetsCache = assetsCache; + } + + public void ConsumeEvent(object sender, OrderExecutedEventArgs ea) + { + var order = ea.Order; + + lock (GetLockObject(order)) + { + if (order.ForceOpen) + { + OpenNewPosition(order, order.Volume); + + return; + } + + if (order.PositionsToBeClosed.Any()) + { + foreach (var positionId in order.PositionsToBeClosed) + { + var position = _ordersCache.Positions.GetPositionById(positionId); + + CloseExistingPosition(order, position); + } + + return; + } + + MatchOrderOnExistingPositions(order); + } + } + + private void CloseExistingPosition(Order order, Position position) + { + position.Close(order.Executed.Value, order.MatchingEngineId, order.ExecutionPrice.Value, + order.EquivalentRate, order.FxRate, order.Originator, order.OrderType.GetCloseReason(), order.Comment, + order.Id); + + _ordersCache.Positions.Remove(position); + + SendPositionHistoryEvent(position, PositionHistoryTypeContract.Close, + position.ChargedPnL, order.AdditionalInfo, order, Math.Abs(position.Volume)); + + var reason = OrderCancellationReason.None; + + if (order.IsBasicOrder()) + { + reason = OrderCancellationReason.ParentPositionClosed; + CancelRelatedOrdersForOrder(order, order.CorrelationId, reason); + } + else + { + reason = OrderCancellationReason.ConnectedOrderExecuted; + } + + CancelRelatedOrdersForPosition(position, order.CorrelationId, reason); + + } + + private void OpenNewPosition(Order order, decimal volume) + { + if (order.ExecutionPrice == null) + { + _log.WriteWarning(nameof(OpenNewPosition), order.ToJson(), + "Execution price is null. Position was not opened"); + return; + } + + var position = new Position(order.Id, order.Code, order.AssetPairId, volume, order.AccountId, + order.TradingConditionId, order.AccountAssetId, order.Price, order.MatchingEngineId, + order.Executed.Value, order.Id, order.OrderType, order.Volume, order.ExecutionPrice.Value, order.FxRate, + order.EquivalentAsset, order.EquivalentRate, order.RelatedOrders, order.LegalEntity, order.Originator, + order.ExternalProviderId, order.FxAssetPairId, order.FxToAssetPairDirection, order.AdditionalInfo); + + var defaultMatchingEngine = _meRouter.GetMatchingEngineForClose(position.OpenMatchingEngineId); + + var closePrice = defaultMatchingEngine.GetPriceForClose(position.AssetPairId, position.Volume, + position.ExternalProviderId); + + position.UpdateClosePrice(closePrice ?? order.ExecutionPrice.Value); + + var isPositionAlreadyExist = _ordersCache.Positions.GetPositionsByInstrumentAndAccount( + position.AssetPairId, + position.AccountId).Any(p => p.Direction == position.Direction); + + _ordersCache.Positions.Add(position); + + var metadata = new PositionOpenMetadata {ExistingPositionIncreased = isPositionAlreadyExist}; + + SendPositionHistoryEvent(position, PositionHistoryTypeContract.Open, 0, + order.AdditionalInfo, metadata: metadata); + + ActivateRelatedOrders(position); + + } + + private void MatchOrderOnExistingPositions(Order order) + { + var leftVolumeToMatch = Math.Abs(order.Volume); + + var openedPositions = + _ordersCache.Positions.GetPositionsByInstrumentAndAccount(order.AssetPairId, order.AccountId) + .Where(p => p.Status != PositionStatus.Closing && + p.Direction == order.Direction.GetClosePositionDirection()); + + foreach (var openedPosition in openedPositions) + { + if (!openedPosition.TryStartClosing(_dateService.Now(), order.OrderType.GetCloseReason(), order + .Originator, "")) + { + continue; + } + + var absVolume = Math.Abs(openedPosition.Volume); + + if (absVolume <= leftVolumeToMatch) + { + CloseExistingPosition(order, openedPosition); + + leftVolumeToMatch = leftVolumeToMatch - absVolume; + } + else + { + var chargedPnl = leftVolumeToMatch / absVolume * openedPosition.ChargedPnL; + + openedPosition.PartiallyClose(order.Executed.Value, leftVolumeToMatch, order.Id, chargedPnl); + + SendPositionHistoryEvent(openedPosition, PositionHistoryTypeContract.PartiallyClose, chargedPnl, + order.AdditionalInfo, order, Math.Abs(leftVolumeToMatch)); + + ChangeRelatedOrderVolume(openedPosition.RelatedOrders, -openedPosition.Volume); + + CancelRelatedOrdersForOrder(order, order.CorrelationId, OrderCancellationReason.ParentPositionClosed); + + openedPosition.CancelClosing(_dateService.Now()); + + leftVolumeToMatch = 0; + } + + if (leftVolumeToMatch <= 0) + break; + } + + if (leftVolumeToMatch > 0) + { + var volume = order.Volume > 0 ? leftVolumeToMatch : -leftVolumeToMatch; + + OpenNewPosition(order, volume); + } + } + + private void SendPositionHistoryEvent(Position position, PositionHistoryTypeContract historyType, + decimal chargedPnl, string orderAdditionalInfo, Order dealOrder = null, decimal? dealVolume = null, + PositionOpenMetadata metadata = null) + { + DealContract deal = null; + + if (dealOrder != null && dealVolume != null) + { + var sign = position.Volume > 0 ? 1 : -1; + + var accountBaseAssetAccuracy = _assetsCache.GetAssetAccuracy(position.AccountAssetId); + + var fpl = Math.Round((dealOrder.ExecutionPrice.Value - position.OpenPrice) * + dealOrder.FxRate * dealVolume.Value * sign, accountBaseAssetAccuracy); + var balanceDelta = Math.Round(fpl - chargedPnl, accountBaseAssetAccuracy); + + deal = new DealContract + { + DealId = _identityGenerator.GenerateAlphanumericId(), + PositionId = position.Id, + Volume = dealVolume.Value, + Created = dealOrder.Executed.Value, + OpenTradeId = position.OpenTradeId, + OpenOrderType = position.OpenOrderType.ToType(), + OpenOrderVolume = position.OpenOrderVolume, + OpenOrderExpectedPrice = position.ExpectedOpenPrice, + CloseTradeId = dealOrder.Id, + CloseOrderType = dealOrder.OrderType.ToType(), + CloseOrderVolume = dealOrder.Volume, + CloseOrderExpectedPrice = dealOrder.Price, + OpenPrice = position.OpenPrice, + OpenFxPrice = position.OpenFxPrice, + ClosePrice = dealOrder.ExecutionPrice.Value, + CloseFxPrice = dealOrder.FxRate, + Fpl = fpl, + PnlOfTheLastDay = balanceDelta, + AdditionalInfo = dealOrder.AdditionalInfo, + Originator = dealOrder.Originator.ToType() + }; + + var account = _accountsCacheService.Get(position.AccountId); + + _cqrsSender.PublishEvent(new PositionClosedEvent(account.Id, account.ClientId, + deal.DealId, position.AssetPairId, balanceDelta)); + + _accountUpdateService.FreezeUnconfirmedMargin(position.AccountId, deal.DealId, balanceDelta) + .GetAwaiter().GetResult();//todo consider making this async or pass to broker + } + + var positionContract = _convertService.Convert(position, + o => o.ConfigureMap(MemberList.Destination).ForMember(x => x.TotalPnL, c => c.Ignore())); + positionContract.TotalPnL = position.GetFpl(); + + var historyEvent = new PositionHistoryEvent + { + PositionSnapshot = positionContract, + Deal = deal, + EventType = historyType, + Timestamp = _dateService.Now(), + ActivitiesMetadata = metadata?.ToJson(), + OrderAdditionalInfo = orderAdditionalInfo, + }; + + _rabbitMqNotifyService.PositionHistory(historyEvent); + } + + private void ActivateRelatedOrders(Position position) + { + foreach (var relatedOrderInfo in position.RelatedOrders) + { + if (_ordersCache.Inactive.TryPopById(relatedOrderInfo.Id, out var relatedOrder)) + { + relatedOrder.Activate(_dateService.Now(), true, position.ClosePrice); + _ordersCache.Active.Add(relatedOrder); + _orderActivatedEventChannel.SendEvent(this, new OrderActivatedEventArgs(relatedOrder)); + } + } + } + + private void CancelRelatedOrdersForPosition(Position position, string correlationId, + OrderCancellationReason reason) + { + var metadata = new OrderCancelledMetadata {Reason = reason.ToType()}; + + foreach (var relatedOrderInfo in position.RelatedOrders) + { + if (_ordersCache.Active.TryPopById(relatedOrderInfo.Id, out var relatedOrder)) + { + relatedOrder.Cancel(_dateService.Now(), null, correlationId); + _orderCancelledEventChannel.SendEvent(this, new OrderCancelledEventArgs(relatedOrder, metadata)); + } + } + } + + private void CancelRelatedOrdersForOrder(Order order, string correlationId, + OrderCancellationReason reason) + { + var metadata = new OrderCancelledMetadata {Reason = reason.ToType()}; + + foreach (var relatedOrderInfo in order.RelatedOrders) + { + if (_ordersCache.Inactive.TryPopById(relatedOrderInfo.Id, out var relatedOrder)) + { + relatedOrder.Cancel(_dateService.Now(), null, correlationId); + _orderCancelledEventChannel.SendEvent(this, new OrderCancelledEventArgs(relatedOrder, metadata)); + } + } + } + + private void ChangeRelatedOrderVolume(List relatedOrderInfos, decimal newVolume) + { + foreach (var relatedOrderInfo in relatedOrderInfos) + { + if (_ordersCache.TryGetOrderById(relatedOrderInfo.Id, out var relatedOrder) + && relatedOrder.Volume != newVolume) + { + var oldVolume = relatedOrder.Volume; + + relatedOrder.ChangeVolume(newVolume, _dateService.Now(), OriginatorType.System); + var metadata = new OrderChangedMetadata + { + UpdatedProperty = OrderChangedProperty.Volume, + OldValue = oldVolume.ToString("F2") + }; + + _orderChangedEventChannel.SendEvent(this, new OrderChangedEventArgs(relatedOrder, metadata)); + } + } + } + + private object GetLockObject(Order order) + { + return LockObjects.GetOrAdd(order.AccountId, new object()); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/EventsConsumers/StopoutConsumer.cs b/src/MarginTrading.Backend.Services/EventsConsumers/StopoutConsumer.cs new file mode 100644 index 000000000..5a016cd49 --- /dev/null +++ b/src/MarginTrading.Backend.Services/EventsConsumers/StopoutConsumer.cs @@ -0,0 +1,76 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Concurrent; +using System.Linq; +using Common; +using Lykke.Common; +using MarginTrading.Backend.Contracts.Events; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Services.Events; +using MarginTrading.Backend.Services.Notifications; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Services +{ + public class StopOutConsumer : IEventConsumer, + IEventConsumer + { + private readonly IThreadSwitcher _threadSwitcher; + private readonly IOperationsLogService _operationsLogService; + private readonly IRabbitMqNotifyService _rabbitMqNotifyService; + private readonly IDateService _dateService; + private readonly MarginTradingSettings _settings; + + private readonly ConcurrentDictionary _lastNotifications = + new ConcurrentDictionary(); + + public StopOutConsumer(IThreadSwitcher threadSwitcher, + IOperationsLogService operationsLogService, + IRabbitMqNotifyService rabbitMqNotifyService, + IDateService dateService, + MarginTradingSettings settings) + { + _threadSwitcher = threadSwitcher; + _operationsLogService = operationsLogService; + _rabbitMqNotifyService = rabbitMqNotifyService; + _dateService = dateService; + + _settings = settings; + } + + int IEventConsumer.ConsumerRank => 100; + + void IEventConsumer.ConsumeEvent(object sender, StopOutEventArgs ea) + { + var account = ea.Account; + var eventTime = _dateService.Now(); + var accountMarginEventMessage = + AccountMarginEventMessageConverter.Create(account, MarginEventTypeContract.Stopout, eventTime); + + _threadSwitcher.SwitchThread(async () => + { + if (_lastNotifications.TryGetValue(account.Id, out var lastNotification) + && lastNotification.AddMinutes(_settings.Throttling.StopOutThrottlingPeriodMin) > eventTime) + { + return; + } + + _operationsLogService.AddLog("stopout", account.Id, "", ea.ToJson()); + + await _rabbitMqNotifyService.AccountMarginEvent(accountMarginEventMessage); + + _lastNotifications.AddOrUpdate(account.Id, eventTime, (s, times) => eventTime); + }); + } + + public void ConsumeEvent(object sender, LiquidationEndEventArgs ea) + { + if (ea.LiquidatedPositionIds.Any()) + { + _lastNotifications.TryRemove(ea.AccountId, out _); + } + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/EventsConsumers/TradesConsumer.cs b/src/MarginTrading.Backend.Services/EventsConsumers/TradesConsumer.cs index 014c3f62e..e86badcd1 100644 --- a/src/MarginTrading.Backend.Services/EventsConsumers/TradesConsumer.cs +++ b/src/MarginTrading.Backend.Services/EventsConsumers/TradesConsumer.cs @@ -1,6 +1,9 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Mappers; +using MarginTrading.Backend.Core.Orders; using MarginTrading.Backend.Services.Events; using MarginTrading.Backend.Services.Notifications; using MarginTrading.Common.Extensions; @@ -8,9 +11,9 @@ namespace MarginTrading.Backend.Services.EventsConsumers { + //TODO: change events and models public class TradesConsumer: - IEventConsumer, - IEventConsumer + IEventConsumer { private readonly IRabbitMqNotifyService _rabbitMqNotifyService; @@ -18,47 +21,24 @@ public TradesConsumer(IRabbitMqNotifyService rabbitMqNotifyService) { _rabbitMqNotifyService = rabbitMqNotifyService; } - - public void ConsumeEvent(object sender, OrderPlacedEventArgs ea) - { - if (ea.Order.IsOpened()) - { - var trade = new TradeContract - { - Id = Guid.NewGuid().ToString("N"), - AccountId = ea.Order.AccountId, - ClientId = ea.Order.ClientId, - OrderId = ea.Order.Id, - AssetPairId = ea.Order.Instrument, - Date = ea.Order.OpenDate.Value, - Price = ea.Order.OpenPrice, - Volume = ea.Order.MatchedOrders.SummaryVolume, - Type = ea.Order.GetOrderType().ToType() - }; - - _rabbitMqNotifyService.NewTrade(trade); - } - } - public void ConsumeEvent(object sender, OrderClosedEventArgs ea) + public void ConsumeEvent(object sender, OrderExecutedEventArgs ea) { - if (ea.Order.IsClosed()) + var tradeType = ea.Order.Direction.ToType(); + + var trade = new TradeContract { - var trade = new TradeContract - { - Id = Guid.NewGuid().ToString("N"), - AccountId = ea.Order.AccountId, - ClientId = ea.Order.ClientId, - OrderId = ea.Order.Id, - AssetPairId = ea.Order.Instrument, - Date = ea.Order.CloseDate.Value, - Price = ea.Order.ClosePrice, - Volume = ea.Order.MatchedCloseOrders.SummaryVolume, - Type = ea.Order.GetCloseType().ToType() - }; + Id = ea.Order.Id, + AccountId = ea.Order.AccountId, + OrderId = ea.Order.Id, + AssetPairId = ea.Order.AssetPairId, + Date = ea.Order.Executed.Value, + Price = ea.Order.ExecutionPrice.Value, + Volume = ea.Order.Volume, + Type = tradeType + }; - _rabbitMqNotifyService.NewTrade(trade); - } + _rabbitMqNotifyService.NewTrade(trade); } public int ConsumerRank => 101; diff --git a/src/MarginTrading.Backend.Services/EventsConsumers/UpdatedAccountsStatsConsumer.cs b/src/MarginTrading.Backend.Services/EventsConsumers/UpdatedAccountsStatsConsumer.cs index 34fee2efb..24cfc094c 100644 --- a/src/MarginTrading.Backend.Services/EventsConsumers/UpdatedAccountsStatsConsumer.cs +++ b/src/MarginTrading.Backend.Services/EventsConsumers/UpdatedAccountsStatsConsumer.cs @@ -1,4 +1,7 @@ -using MarginTrading.Backend.Core; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.Mappers; using MarginTrading.Backend.Core.Settings; using MarginTrading.Backend.Services.Events; @@ -10,56 +13,52 @@ namespace MarginTrading.Backend.Services.EventsConsumers public class UpdatedAccountsStatsConsumer : IEventConsumer, IEventConsumer, - IEventConsumer, + IEventConsumer, IEventConsumer { private readonly IAccountsCacheService _accountsCacheService; - private readonly MarginSettings _marginSettings; private readonly IRabbitMqNotifyService _rabbitMqNotifyService; public UpdatedAccountsStatsConsumer(IAccountsCacheService accountsCacheService, - MarginSettings marginSettings, IRabbitMqNotifyService rabbitMqNotifyService) { _accountsCacheService = accountsCacheService; - _marginSettings = marginSettings; _rabbitMqNotifyService = rabbitMqNotifyService; } public void ConsumeEvent(object sender, AccountBalanceChangedEventArgs ea) { - NotifyAccountStatsChanged(ea.Account); + NotifyAccountStatsChanged(ea.AccountId); } public void ConsumeEvent(object sender, OrderPlacedEventArgs ea) { - NotifyAccountStatsChanged(ea.Order.ClientId, ea.Order.AccountId); + NotifyAccountStatsChanged(ea.Order.AccountId); } - public void ConsumeEvent(object sender, OrderClosedEventArgs ea) + public void ConsumeEvent(object sender, OrderExecutedEventArgs ea) { - NotifyAccountStatsChanged(ea.Order.ClientId, ea.Order.AccountId); + NotifyAccountStatsChanged(ea.Order.AccountId); } public void ConsumeEvent(object sender, OrderCancelledEventArgs ea) { - NotifyAccountStatsChanged(ea.Order.ClientId, ea.Order.AccountId); + NotifyAccountStatsChanged(ea.Order.AccountId); } public int ConsumerRank => 102; - private void NotifyAccountStatsChanged(IMarginTradingAccount account) + private void NotifyAccountStatsChanged(string accountId) { - var stats = account.ToRabbitMqContract(_marginSettings.IsLive); + var account = _accountsCacheService.Get(accountId); - _rabbitMqNotifyService.UpdateAccountStats(new AccountStatsUpdateMessage {Accounts = new[] {stats}}); - } - - private void NotifyAccountStatsChanged(string clientId, string accountId) - { - var account = _accountsCacheService.Get(clientId, accountId); + account.CacheNeedsToBeUpdated(); + + // not needed right now + + //var stats = account.ToRabbitMqContract(); - NotifyAccountStatsChanged(account); + //_rabbitMqNotifyService.UpdateAccountStats(new AccountStatsUpdateMessage {Accounts = new[] {stats}}); } } } diff --git a/src/MarginTrading.Backend.Services/FakeExchangeConnector/FakeExchangeConnectorClient.cs b/src/MarginTrading.Backend.Services/FakeExchangeConnector/FakeExchangeConnectorClient.cs new file mode 100644 index 000000000..7929a34a8 --- /dev/null +++ b/src/MarginTrading.Backend.Services/FakeExchangeConnector/FakeExchangeConnectorClient.cs @@ -0,0 +1,165 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Common; +using Common.Log; +using Lykke.Common.Chaos; +using Lykke.MarginTrading.OrderBookService.Contracts.Models; +using MarginTrading.Backend.Contracts.ExchangeConnector; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orderbooks; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Services.Infrastructure; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Services.FakeExchangeConnector +{ + public class FakeExchangeConnectorClient : IExchangeConnectorClient + { + private readonly IExternalOrderbookService _orderbookService; + private readonly IChaosKitty _chaosKitty; + private readonly MarginTradingSettings _settings; + private readonly ILog _log; + private readonly IDateService _dateService; + private readonly ICqrsSender _cqrsSender; + + public FakeExchangeConnectorClient( + IExternalOrderbookService orderbookService, + IChaosKitty chaosKitty, + MarginTradingSettings settings, + ILog log, + IDateService dateService, + ICqrsSender cqrsSender) + { + _chaosKitty = chaosKitty; + _settings = settings; + _log = log; + _dateService = dateService; + _cqrsSender = cqrsSender; + _orderbookService = orderbookService; + } + + public Task ExecuteOrder(OrderModel orderModel, CancellationToken cancellationToken) + { + if (orderModel == null || orderModel.Volume == 0) + { + return Task.FromResult(new ExecutionReport + { + Success = false, + ExecutionStatus = OrderExecutionStatus.Rejected, + FailureType = OrderStatusUpdateFailureType.ConnectorError, + }); + } + + ExecutionReport result; + + try + { + _chaosKitty.Meow(nameof(FakeExchangeConnectorClient)); + + ExternalOrderBook orderbook; + decimal? currentPrice; + + if (orderModel.Modality == TradeRequestModality.Liquidation) + { + if (orderModel.Price == null) + { + throw new InvalidOperationException("Order should have price specified in case of special liquidation"); + } + + currentPrice = (decimal?) orderModel.Price; + + orderbook = new ExternalOrderBook(MatchingEngineConstants.DefaultSpecialLiquidation, + orderModel.Instrument, _dateService.Now(), new + [] + { + new VolumePrice + {Price = currentPrice.Value, Volume = currentPrice.Value} + }, + new + [] + { + new VolumePrice + {Price = currentPrice.Value, Volume = currentPrice.Value} + }); + } + else + { + orderbook = _orderbookService.GetOrderBook(orderModel.Instrument); + + if (orderbook == null) + { + throw new InvalidOperationException("Orderbook was not found"); + } + + currentPrice = orderbook.GetMatchedPrice((decimal) orderModel.Volume, + orderModel.TradeType == TradeType.Buy ? OrderDirection.Buy : OrderDirection.Sell); + } + + result = new ExecutionReport( + type: orderModel.TradeType, + time: DateTime.UtcNow, + price: (double) (currentPrice ?? throw new Exception("No price")), + volume: orderModel.Volume, + fee: 0, + success: true, + executionStatus: OrderExecutionStatus.Fill, + failureType: OrderStatusUpdateFailureType.None, + orderType: orderModel.OrderType, + execType: ExecType.Trade, + clientOrderId: Guid.NewGuid().ToString(), + exchangeOrderId: Guid.NewGuid().ToString(), + instrument: new Instrument(orderModel.Instrument, orderModel.ExchangeName)); + + _cqrsSender.PublishEvent(new OrderExecutionOrderBookContract + { + OrderId = orderModel.OrderId, + Volume = (decimal) orderModel.Volume, + OrderBook = new ExternalOrderBookContract + { + AssetPairId = orderbook.AssetPairId, + ExchangeName = orderbook.ExchangeName, + Timestamp = orderbook.Timestamp, + ReceiveTimestamp = _dateService.Now(), + Asks = orderbook.Asks.Select(a => new VolumePriceContract + { + Price = a.Price, Volume = a.Volume + }).ToList(), + Bids = orderbook.Bids.Select(a => new VolumePriceContract + { + Price = a.Price, Volume = a.Volume + }).ToList() + } + }, _settings.Cqrs.ContextNames.Gavel); + } + catch (Exception ex) + { + _log.WriteErrorAsync(nameof(FakeExchangeConnectorClient), nameof(ExecuteOrder), + orderModel.ToJson(), ex); + + result = new ExecutionReport( + type: orderModel.TradeType, + time: DateTime.UtcNow, + price: 0, + volume: 0, + fee: 0, + success: false, + executionStatus: OrderExecutionStatus.Rejected, + failureType: OrderStatusUpdateFailureType.ExchangeError, + orderType: orderModel.OrderType, + execType: ExecType.Trade, + clientOrderId: null, + exchangeOrderId: null, + instrument: new Instrument(orderModel.Instrument, orderModel.ExchangeName)); + } + + return Task.FromResult(result); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/FplService.cs b/src/MarginTrading.Backend.Services/FplService.cs deleted file mode 100644 index c163ac420..000000000 --- a/src/MarginTrading.Backend.Services/FplService.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.MatchedOrders; -using MarginTrading.Backend.Services.Assets; -using MarginTrading.Backend.Services.TradingConditions; - -namespace MarginTrading.Backend.Services -{ - public class FplService : IFplService - { - private readonly ICfdCalculatorService _cfdCalculatorService; - private readonly IAssetPairsCache _assetPairsCache; - private readonly IAccountsCacheService _accountsCacheService; - private readonly IAccountAssetsCacheService _accountAssetsCacheService; - private readonly IAssetsCache _assetsCache; - - public FplService( - ICfdCalculatorService cfdCalculatorService, - IAssetPairsCache assetPairsCache, - IAccountsCacheService accountsCacheService, - IAccountAssetsCacheService accountAssetsCacheService, - IAssetsCache assetsCache) - { - _cfdCalculatorService = cfdCalculatorService; - _assetPairsCache = assetPairsCache; - _accountsCacheService = accountsCacheService; - _accountAssetsCacheService = accountAssetsCacheService; - _assetsCache = assetsCache; - } - - public void UpdateOrderFpl(IOrder order, FplData fplData) - { - var handler = order.Status != OrderStatus.WaitingForExecution - ? UpdateOrderFplData - : (Action)UpdatePendingOrderMargin; - - handler(order, fplData); - } - - private void UpdateOrderFplData(IOrder order, FplData fplData) - { - fplData.AccountBaseAssetAccuracy = _assetsCache.GetAssetAccuracy(order.AccountAssetId); - fplData.FplRate = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.AccountAssetId, order.Instrument, - order.LegalEntity, order.Volume * (order.ClosePrice - order.OpenPrice) > 0); - - var fpl = (order.ClosePrice - order.OpenPrice) * fplData.FplRate * order.Volume; - - fplData.Fpl = Math.Round(fpl, fplData.AccountBaseAssetAccuracy); - - CalculateMargin(order, fplData); - - fplData.OpenPrice = order.OpenPrice; - fplData.ClosePrice = order.ClosePrice; - fplData.SwapsSnapshot = order.GetSwaps(); - - fplData.CalculatedHash = fplData.ActualHash; - - fplData.TotalFplSnapshot = order.GetTotalFpl(fplData.SwapsSnapshot); - - _accountsCacheService.Get(order.ClientId, order.AccountId).CacheNeedsToBeUpdated(); - } - - private void UpdatePendingOrderMargin(IOrder order, FplData fplData) - { - fplData.AccountBaseAssetAccuracy = _assetsCache.GetAssetAccuracy(order.AccountAssetId); - - CalculateMargin(order, fplData); - - fplData.CalculatedHash = fplData.ActualHash; - _accountsCacheService.Get(order.ClientId, order.AccountId).CacheNeedsToBeUpdated(); - } - - public void CalculateMargin(IOrder order, FplData fplData) - { - var accountAsset = _accountAssetsCacheService.GetAccountAsset(order.TradingConditionId, order.AccountAssetId, order.Instrument); - - fplData.MarginRate = _cfdCalculatorService.GetQuoteRateForBaseAsset(order.AccountAssetId, order.Instrument, - order.LegalEntity); - fplData.MarginInit = - Math.Round(Math.Abs(order.Volume) * fplData.MarginRate / accountAsset.LeverageInit, - fplData.AccountBaseAssetAccuracy); - fplData.MarginMaintenance = - Math.Round(Math.Abs(order.Volume) * fplData.MarginRate / accountAsset.LeverageMaintenance, - fplData.AccountBaseAssetAccuracy); - } - - public decimal GetMatchedOrdersPrice(List matchedOrders, string instrument) - { - if (matchedOrders.Count == 0) - { - return 0; - } - - var accuracy = _assetPairsCache.GetAssetPairById(instrument).Accuracy; - - return Math.Round(matchedOrders.Sum(item => item.Price * item.Volume) / - matchedOrders.Sum(item => item.Volume), accuracy); - } - } -} diff --git a/src/MarginTrading.Backend.Services/Helpers/MarginTradingCalculations.cs b/src/MarginTrading.Backend.Services/Helpers/MarginTradingCalculations.cs index d38545285..b612d4f34 100644 --- a/src/MarginTrading.Backend.Services/Helpers/MarginTradingCalculations.cs +++ b/src/MarginTrading.Backend.Services/Helpers/MarginTradingCalculations.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Backend.Services.Helpers { diff --git a/src/MarginTrading.Backend.Services/Helpers/MarginTradingHelpers.cs b/src/MarginTrading.Backend.Services/Helpers/MarginTradingHelpers.cs index af9a3c703..b2a5a9c47 100644 --- a/src/MarginTrading.Backend.Services/Helpers/MarginTradingHelpers.cs +++ b/src/MarginTrading.Backend.Services/Helpers/MarginTradingHelpers.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Services.Helpers +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Services.Helpers { public static class MarginTradingHelpers { diff --git a/src/MarginTrading.Backend.Services/Helpers/OrderMergingHelper.cs b/src/MarginTrading.Backend.Services/Helpers/OrderMergingHelper.cs new file mode 100644 index 000000000..308c09769 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Helpers/OrderMergingHelper.cs @@ -0,0 +1,171 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using MarginTrading.Backend.Core.Helpers; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; +using MarginTrading.Backend.Services.Mappers; + +namespace MarginTrading.Backend.Services.Helpers +{ + public static class OrderMergingHelper + { + /// + /// Return true if there was difference, false if items were the same. + /// + public static bool Merge(this Order order, IOrderHistory orderHistory) + { + return order.SetIfDiffer(new Dictionary + { + {nameof(Order.Volume), orderHistory.Volume}, + {nameof(Order.Status), orderHistory.Status}, + {nameof(Order.ParentOrderId), orderHistory.ParentOrderId}, + {nameof(Order.ParentPositionId), orderHistory.PositionId}, + {nameof(Order.ExecutionPrice), orderHistory.ExecutionPrice}, + {nameof(Order.FxRate), orderHistory.FxRate}, + {nameof(Order.Validity), orderHistory.ValidityTime}, + {nameof(Order.LastModified), orderHistory.ModifiedTimestamp}, + {nameof(Order.Activated), orderHistory.ActivatedTimestamp}, + {nameof(Order.ExecutionStarted), orderHistory.ExecutionStartedTimestamp}, + {nameof(Order.Executed), orderHistory.ExecutedTimestamp}, + {nameof(Order.Canceled), orderHistory.CanceledTimestamp}, + {nameof(Order.Rejected), orderHistory.Rejected}, + {nameof(Order.EquivalentRate), orderHistory.EquivalentRate}, + {nameof(Order.RejectReason), orderHistory.RejectReason}, + {nameof(Order.RejectReasonText), orderHistory.RejectReasonText}, + {nameof(Order.ExternalOrderId), orderHistory.ExternalOrderId}, + {nameof(Order.ExternalProviderId), orderHistory.ExternalProviderId}, + {nameof(Order.MatchingEngineId), orderHistory.MatchingEngineId}, + {nameof(Order.MatchedOrders), orderHistory.MatchedOrders}, + {nameof(Order.RelatedOrders), orderHistory.RelatedOrderInfos}, + {nameof(Order.AdditionalInfo), orderHistory.AdditionalInfo}, + {nameof(Order.PendingOrderRetriesCount), orderHistory.PendingOrderRetriesCount}, + }); + } + + public static Order FromHistory(this IOrderHistory orderHistory) + { + return new Order( + id: orderHistory.Id, + code: orderHistory.Code, + assetPairId: orderHistory.AssetPairId, + volume: orderHistory.Volume, + created: orderHistory.CreatedTimestamp, + lastModified: orderHistory.ModifiedTimestamp, + validity: orderHistory.ValidityTime, + accountId: orderHistory.AccountId, + tradingConditionId: orderHistory.TradingConditionId, + accountAssetId: orderHistory.AccountAssetId, + price: orderHistory.ExpectedOpenPrice, + equivalentAsset: orderHistory.EquivalentAsset, + fillType: orderHistory.FillType, + comment: orderHistory.Comment, + legalEntity: orderHistory.LegalEntity, + forceOpen: orderHistory.ForceOpen, + orderType: orderHistory.Type, + parentOrderId: orderHistory.ParentOrderId, + parentPositionId: orderHistory.PositionId, + originator: orderHistory.Originator, + equivalentRate: orderHistory.EquivalentRate, + fxRate: orderHistory.FxRate, + fxAssetPairId: orderHistory.FxAssetPairId, + fxToAssetPairDirection: orderHistory.FxToAssetPairDirection, + status: orderHistory.Status, + additionalInfo: orderHistory.AdditionalInfo, + correlationId: orderHistory.CorrelationId, + positionsToBeClosed: string.IsNullOrWhiteSpace(orderHistory.PositionId) + ? new List() + : new List {orderHistory.PositionId}, + externalProviderId: orderHistory.ExternalProviderId + ); + } + + /// + /// Return true if there was difference, false if items were the same. + /// + public static bool Merge(this Position position, IPositionHistory positionHistory) + { + return position.SetIfDiffer(new Dictionary + { + {nameof(Position.Volume), positionHistory.Volume}, + {nameof(Position.RelatedOrders), positionHistory.RelatedOrders}, + {nameof(Position.SwapCommissionRate), positionHistory.SwapCommissionRate}, + {nameof(Position.CloseCommissionRate), positionHistory.CloseCommissionRate}, + {nameof(Position.CommissionLot), positionHistory.CommissionLot}, + {nameof(Position.CloseMatchingEngineId), positionHistory.CloseMatchingEngineId}, + {nameof(Position.ClosePrice), positionHistory.ClosePrice}, + {nameof(Position.CloseFxPrice), positionHistory.CloseFxPrice}, + {nameof(Position.ClosePriceEquivalent), positionHistory.ClosePriceEquivalent}, + {nameof(Position.StartClosingDate), positionHistory.StartClosingDate}, + {nameof(Position.CloseDate), positionHistory.CloseDate}, + {nameof(Position.CloseOriginator), positionHistory.CloseOriginator}, + {nameof(Position.CloseReason), positionHistory.CloseReason}, + {nameof(Position.CloseComment), positionHistory.CloseComment}, + {nameof(Position.CloseTrades), positionHistory.CloseTrades}, + {nameof(Position.LastModified), positionHistory.LastModified}, + {nameof(Position.ChargedPnL), positionHistory.ChargedPnl}, + {nameof(Position.AdditionalInfo), positionHistory.AdditionalInfo}, + }); + } + + public static Position FromHistory(this IPositionHistory positionHistory) + { + return new Position( + id: positionHistory.Id, + code: positionHistory.Code, + assetPairId: positionHistory.AssetPairId, + volume: positionHistory.Volume, + accountId: positionHistory.AccountId, + tradingConditionId: positionHistory.TradingConditionId, + accountAssetId: positionHistory.AccountAssetId, + expectedOpenPrice: positionHistory.ExpectedOpenPrice, + openMatchingEngineId: positionHistory.OpenMatchingEngineId, + openDate: positionHistory.OpenDate, + openTradeId: positionHistory.OpenTradeId, + openOrderType: positionHistory.OpenOrderType, + openOrderVolume: positionHistory.OpenOrderVolume, + openPrice: positionHistory.OpenPrice, + openFxPrice: positionHistory.OpenFxPrice, + equivalentAsset: positionHistory.EquivalentAsset, + openPriceEquivalent: positionHistory.OpenPriceEquivalent, + relatedOrders: positionHistory.RelatedOrders, + legalEntity: positionHistory.LegalEntity, + openOriginator: positionHistory.OpenOriginator, + externalProviderId: positionHistory.ExternalProviderId, + fxAssetPairId: positionHistory.FxAssetPairId, + fxToAssetPairDirection: positionHistory.FxToAssetPairDirection, + additionalInfo: positionHistory.AdditionalInfo + ); + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MarginTrading.Backend.Services/Infrastructure/AlertSeverityLevelService.cs b/src/MarginTrading.Backend.Services/Infrastructure/AlertSeverityLevelService.cs index a8fd7391b..e264f9028 100644 --- a/src/MarginTrading.Backend.Services/Infrastructure/AlertSeverityLevelService.cs +++ b/src/MarginTrading.Backend.Services/Infrastructure/AlertSeverityLevelService.cs @@ -1,8 +1,10 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.ObjectModel; using System.Linq; using JetBrains.Annotations; -using Lykke.SettingsReader; using MarginTrading.Backend.Core.Settings; using MoreLinq; @@ -10,18 +12,15 @@ namespace MarginTrading.Backend.Services.Infrastructure { public class AlertSeverityLevelService : IAlertSeverityLevelService { - private readonly IReloadingManager> _levels; + private readonly ReadOnlyCollection<(EventTypeEnum Event, string SlackChannelType)> _levels; private static readonly string _defaultLevel = "mt-critical"; - public AlertSeverityLevelService(IReloadingManager settings) + public AlertSeverityLevelService(RiskInformingSettings settings) { - _levels = settings.Nested(s => - { - return s.Data.Where(d => d.System == "QuotesMonitor") - .Select(d => (ConvertEventTypeCode(d.EventTypeCode), ConvertLevel(d.Level))) - .ToList().AsReadOnly(); - }); + _levels = settings.Data.Where(d => d.System == "QuotesMonitor") + .Select(d => (ConvertEventTypeCode(d.EventTypeCode), ConvertLevel(d.Level))) + .ToList().AsReadOnly(); } private static EventTypeEnum ConvertEventTypeCode(string eventTypeCode) @@ -53,8 +52,10 @@ private static string ConvertLevel(string alertSeverityLevel) public string GetSlackChannelType(EventTypeEnum eventType) { - return _levels.CurrentValue.Where(l => l.Event == eventType).Select(l => l.SlackChannelType) - .FallbackIfEmpty(_defaultLevel).Single(); + return _levels != null + ? _levels.Where(l => l.Event == eventType).Select(l => l.SlackChannelType) + .FallbackIfEmpty(_defaultLevel).Single() + : _defaultLevel; } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Infrastructure/BackendMaintenanceModeService.cs b/src/MarginTrading.Backend.Services/Infrastructure/BackendMaintenanceModeService.cs index 76cd118b8..3d0643671 100644 --- a/src/MarginTrading.Backend.Services/Infrastructure/BackendMaintenanceModeService.cs +++ b/src/MarginTrading.Backend.Services/Infrastructure/BackendMaintenanceModeService.cs @@ -1,4 +1,6 @@ -using Lykke.Logs; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using Lykke.SlackNotifications; using MarginTrading.Backend.Core.Settings; using MarginTrading.Common.Enums; @@ -8,10 +10,11 @@ namespace MarginTrading.Backend.Services.Infrastructure public class BackendMaintenanceModeService : IMaintenanceModeService { private readonly ISlackNotificationsSender _slackNotificationsSender; - private readonly MarginSettings _settings; + private readonly MarginTradingSettings _settings; - public BackendMaintenanceModeService(ISlackNotificationsSender slackNotificationsSender, - MarginSettings settings) + public BackendMaintenanceModeService( + ISlackNotificationsSender slackNotificationsSender, + MarginTradingSettings settings) { _slackNotificationsSender = slackNotificationsSender; _settings = settings; diff --git a/src/MarginTrading.Backend.Services/Infrastructure/Calculate.cs b/src/MarginTrading.Backend.Services/Infrastructure/Calculate.cs index aae295173..e4c14fa56 100644 --- a/src/MarginTrading.Backend.Services/Infrastructure/Calculate.cs +++ b/src/MarginTrading.Backend.Services/Infrastructure/Calculate.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using JetBrains.Annotations; namespace MarginTrading.Backend.Services.Infrastructure diff --git a/src/MarginTrading.Backend.Services/Infrastructure/ContextFactory.cs b/src/MarginTrading.Backend.Services/Infrastructure/ContextFactory.cs index d0d6c0dc3..b5b130da4 100644 --- a/src/MarginTrading.Backend.Services/Infrastructure/ContextFactory.cs +++ b/src/MarginTrading.Backend.Services/Infrastructure/ContextFactory.cs @@ -1,4 +1,7 @@ -using MarginTrading.Backend.Core.Settings; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Core.Settings; using MarginTrading.Common.Services.Telemetry; namespace MarginTrading.Backend.Services.Infrastructure @@ -6,9 +9,9 @@ namespace MarginTrading.Backend.Services.Infrastructure public class ContextFactory : IContextFactory { private readonly ITelemetryPublisher _telemetryPublisher; - private readonly MarginSettings _marginSettings; + private readonly MarginTradingSettings _marginSettings; - public ContextFactory(ITelemetryPublisher telemetryPublisher, MarginSettings marginSettings) + public ContextFactory(ITelemetryPublisher telemetryPublisher, MarginTradingSettings marginSettings) { _telemetryPublisher = telemetryPublisher; _marginSettings = marginSettings; diff --git a/src/MarginTrading.Backend.Services/Infrastructure/CqrsSender.cs b/src/MarginTrading.Backend.Services/Infrastructure/CqrsSender.cs new file mode 100644 index 000000000..6852434c4 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Infrastructure/CqrsSender.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using JetBrains.Annotations; +using Lykke.Cqrs; +using MarginTrading.Backend.Core.Settings; + +namespace MarginTrading.Backend.Services.Infrastructure +{ + public class CqrsSender : ICqrsSender + { + [NotNull] public ICqrsEngine CqrsEngine { get; set; }//property injection + [NotNull] private readonly CqrsContextNamesSettings _cqrsContextNamesSettings; + + public CqrsSender([NotNull] CqrsContextNamesSettings cqrsContextNamesSettings) + { + _cqrsContextNamesSettings = cqrsContextNamesSettings ?? + throw new ArgumentNullException(nameof(cqrsContextNamesSettings)); + } + + public void SendCommandToAccountManagement(T command) + { + CqrsEngine.SendCommand(command, _cqrsContextNamesSettings.TradingEngine, + _cqrsContextNamesSettings.AccountsManagement); + } + + public void SendCommandToSettingsService(T command) + { + CqrsEngine.SendCommand(command, _cqrsContextNamesSettings.TradingEngine, + _cqrsContextNamesSettings.SettingsService); + } + + public void SendCommandToSelf(T command) + { + CqrsEngine.SendCommand(command, _cqrsContextNamesSettings.TradingEngine, + _cqrsContextNamesSettings.TradingEngine); + } + + public void PublishEvent(T ev, string boundedContext = null) + { + try + { + CqrsEngine.PublishEvent(ev, boundedContext ?? _cqrsContextNamesSettings.TradingEngine); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Infrastructure/FakeIdentityGenerator.cs b/src/MarginTrading.Backend.Services/Infrastructure/FakeIdentityGenerator.cs deleted file mode 100644 index 75fdadaa0..000000000 --- a/src/MarginTrading.Backend.Services/Infrastructure/FakeIdentityGenerator.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Threading.Tasks; -using MarginTrading.Backend.Core.Repositories; - -namespace MarginTrading.Backend.Services.Infrastructure -{ - public class FakeIdentityGenerator : IIdentityGenerator - { - private long _currentId; - - public FakeIdentityGenerator() - { - _currentId = DateTime.Now.Ticks; - } - - public Task GenerateIdAsync(string entityType) - { - _currentId++; - return Task.FromResult(_currentId); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Infrastructure/IAlertSeverityLevelService.cs b/src/MarginTrading.Backend.Services/Infrastructure/IAlertSeverityLevelService.cs index 408516a30..a334a90aa 100644 --- a/src/MarginTrading.Backend.Services/Infrastructure/IAlertSeverityLevelService.cs +++ b/src/MarginTrading.Backend.Services/Infrastructure/IAlertSeverityLevelService.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Services.Infrastructure +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Services.Infrastructure { public interface IAlertSeverityLevelService { diff --git a/src/MarginTrading.Backend.Services/Infrastructure/ICachedCalculation.cs b/src/MarginTrading.Backend.Services/Infrastructure/ICachedCalculation.cs index 118e98fa0..9573dfcfe 100644 --- a/src/MarginTrading.Backend.Services/Infrastructure/ICachedCalculation.cs +++ b/src/MarginTrading.Backend.Services/Infrastructure/ICachedCalculation.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Services.Infrastructure +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Services.Infrastructure { public interface ICachedCalculation { diff --git a/src/MarginTrading.Backend.Services/Infrastructure/IContextFactory.cs b/src/MarginTrading.Backend.Services/Infrastructure/IContextFactory.cs index 48fc94145..346d7b29f 100644 --- a/src/MarginTrading.Backend.Services/Infrastructure/IContextFactory.cs +++ b/src/MarginTrading.Backend.Services/Infrastructure/IContextFactory.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Services.Infrastructure +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Services.Infrastructure { public interface IContextFactory { diff --git a/src/MarginTrading.Backend.Services/Infrastructure/ICqrsSender.cs b/src/MarginTrading.Backend.Services/Infrastructure/ICqrsSender.cs new file mode 100644 index 000000000..a4705e2a8 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Infrastructure/ICqrsSender.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Services.Infrastructure +{ + /// + /// Sends cqrs messages from Trading Engine contexts + /// + public interface ICqrsSender + { + void SendCommandToAccountManagement(T command); + void SendCommandToSettingsService(T command); + void SendCommandToSelf(T command); + void PublishEvent(T ev, string boundedContext = null); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Infrastructure/IMaintenanceModeService.cs b/src/MarginTrading.Backend.Services/Infrastructure/IMaintenanceModeService.cs index c5de0f37c..7a34d397d 100644 --- a/src/MarginTrading.Backend.Services/Infrastructure/IMaintenanceModeService.cs +++ b/src/MarginTrading.Backend.Services/Infrastructure/IMaintenanceModeService.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Backend.Services.Infrastructure +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Services.Infrastructure { public interface IMaintenanceModeService { diff --git a/src/MarginTrading.Backend.Services/IMarginTradingEnablingService.cs b/src/MarginTrading.Backend.Services/Infrastructure/IMarginTradingEnablingService.cs similarity index 69% rename from src/MarginTrading.Backend.Services/IMarginTradingEnablingService.cs rename to src/MarginTrading.Backend.Services/Infrastructure/IMarginTradingEnablingService.cs index af5f3c611..eed9660cc 100644 --- a/src/MarginTrading.Backend.Services/IMarginTradingEnablingService.cs +++ b/src/MarginTrading.Backend.Services/Infrastructure/IMarginTradingEnablingService.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; namespace MarginTrading.Backend.Services { diff --git a/src/MarginTrading.Backend.Services/MarginTradingEnablingService.cs b/src/MarginTrading.Backend.Services/Infrastructure/MarginTradingEnablingService.cs similarity index 68% rename from src/MarginTrading.Backend.Services/MarginTradingEnablingService.cs rename to src/MarginTrading.Backend.Services/Infrastructure/MarginTradingEnablingService.cs index 28ad80c09..d28443c55 100644 --- a/src/MarginTrading.Backend.Services/MarginTradingEnablingService.cs +++ b/src/MarginTrading.Backend.Services/Infrastructure/MarginTradingEnablingService.cs @@ -1,9 +1,13 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; using Autofac; using Common; -using Lykke.Service.ClientAccount.Client; using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Services.Services; using MarginTrading.Common.RabbitMq; +using MarginTrading.Common.Services.Client; using MarginTrading.Common.Services.Settings; namespace MarginTrading.Backend.Services @@ -11,35 +15,29 @@ namespace MarginTrading.Backend.Services public class MarginTradingEnablingService : IMarginTradingEnablingService, IStartable { private IMessageProducer _eventsPublisher; - private readonly IClientAccountClient _clientAccountClient; + private readonly IClientAccountService _clientAccountService; private readonly IMarginTradingSettingsCacheService _marginTradingSettingsCacheService; private readonly IRabbitMqService _rabbitMqService; - private readonly MarginSettings _marginSettings; + private readonly MarginTradingSettings _marginSettings; - public MarginTradingEnablingService(IClientAccountClient clientAccountClient, IRabbitMqService rabbitMqService, - MarginSettings settings, + public MarginTradingEnablingService(IClientAccountService clientAccountService, IRabbitMqService rabbitMqService, + MarginTradingSettings settings, IMarginTradingSettingsCacheService marginTradingSettingsCacheService) { _marginSettings = settings; _rabbitMqService = rabbitMqService; - _clientAccountClient = clientAccountClient; + _clientAccountService = clientAccountService; _marginTradingSettingsCacheService = marginTradingSettingsCacheService; } public async Task SetMarginTradingEnabled(string clientId, bool enabled) { - var settings = await _clientAccountClient.GetMarginEnabledAsync(clientId); + var settings = await _clientAccountService.GetMarginEnabledAsync(clientId); - if (_marginSettings.IsLive) - { - settings.EnabledLive = enabled; - } - else - { - settings.Enabled = enabled; - } + settings.EnabledLive = enabled; + settings.Enabled = enabled; - await _clientAccountClient.SetMarginEnabledAsync(clientId, settings.Enabled, settings.EnabledLive, + await _clientAccountService.SetMarginEnabledAsync(clientId, settings.Enabled, settings.EnabledLive, settings.TermsOfUseAgreed); var marginEnabledChangedMessage = new MarginTradingEnabledChangedMessage @@ -61,7 +59,6 @@ public void Start() ConnectionString = _marginSettings.MtRabbitMqConnString, ExchangeName = _marginSettings.RabbitMqQueues.MarginTradingEnabledChanged.ExchangeName }, - true, _rabbitMqService.GetJsonSerializer()); } } diff --git a/src/MarginTrading.Backend.Services/MigrationService.cs b/src/MarginTrading.Backend.Services/Infrastructure/MigrationService.cs similarity index 88% rename from src/MarginTrading.Backend.Services/MigrationService.cs rename to src/MarginTrading.Backend.Services/Infrastructure/MigrationService.cs index 9677e6fff..ebc9dcb55 100644 --- a/src/MarginTrading.Backend.Services/MigrationService.cs +++ b/src/MarginTrading.Backend.Services/Infrastructure/MigrationService.cs @@ -1,7 +1,8 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; -using System.Linq; -using System.Reflection; using System.Threading.Tasks; using Common.Log; using MarginTrading.Backend.Core; @@ -53,7 +54,7 @@ public async Task InvokeAll() } } - await _marginTradingBlobRepository.Write(LykkeConstants.MigrationsBlobContainer, "versions", migrationVersions); + await _marginTradingBlobRepository.WriteAsync(LykkeConstants.MigrationsBlobContainer, "versions", migrationVersions); } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Infrastructure/PendingOrdersCleaningService.cs b/src/MarginTrading.Backend.Services/Infrastructure/PendingOrdersCleaningService.cs index d4383e80c..1e722de07 100644 --- a/src/MarginTrading.Backend.Services/Infrastructure/PendingOrdersCleaningService.cs +++ b/src/MarginTrading.Backend.Services/Infrastructure/PendingOrdersCleaningService.cs @@ -1,9 +1,14 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Linq; using System.Threading.Tasks; using Common; using Common.Log; using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; using MarginTrading.Backend.Services.AssetPairs; namespace MarginTrading.Backend.Services.Infrastructure @@ -14,40 +19,47 @@ public class PendingOrdersCleaningService : TimerPeriod private readonly IOrderReader _orderReader; private readonly ITradingEngine _tradingEngine; private readonly IAssetPairDayOffService _assetDayOffService; + private readonly IIdentityGenerator _identityGenerator; public PendingOrdersCleaningService(ILog log, IOrderReader orderReader, ITradingEngine tradingEngine, - IAssetPairDayOffService assetDayOffService) + IAssetPairDayOffService assetDayOffService, IIdentityGenerator identityGenerator) : base(nameof(PendingOrdersCleaningService), 60000, log) { _log = log; _orderReader = orderReader; _tradingEngine = tradingEngine; _assetDayOffService = assetDayOffService; + _identityGenerator = identityGenerator; } + //TODO: add setting public override Task Execute() { - var pendingOrders = _orderReader.GetPending().GroupBy(o => o.Instrument); - foreach (var gr in pendingOrders) - { - if (!_assetDayOffService.ArePendingOrdersDisabled(gr.Key)) - continue; - - foreach (var pendingOrder in gr) - { - try - { - _tradingEngine.CancelPendingOrder(pendingOrder.Id, OrderCloseReason.CanceledBySystem, "Day off started"); - } - catch (Exception e) - { - _log.WriteErrorAsync(nameof(PendingOrdersCleaningService), - $"Cancelling pending order {pendingOrder.Id}", pendingOrder.ToJson(), e); - } - } - } - return Task.CompletedTask; + + //TODO: add flag to settings in MTC-155 +// var pendingOrders = _orderReader.GetPending().GroupBy(o => o.AssetPairId); +// foreach (var gr in pendingOrders) +// { +// //if (!_assetDayOffService.ArePendingOrdersDisabled(gr.Key)) +// // continue; +// +// foreach (var pendingOrder in gr) +// { +// try +// { +// _tradingEngine.CancelPendingOrder(pendingOrder.Id, OriginatorType.System, "Day off started", +// _identityGenerator.GenerateGuid()); +// } +// catch (Exception e) +// { +// _log.WriteErrorAsync(nameof(PendingOrdersCleaningService), +// $"Cancelling pending order {pendingOrder.Id}", pendingOrder.ToJson(), e); +// } +// } +// } +// +// return Task.CompletedTask; } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Infrastructure/SimpleIdentityGenerator.cs b/src/MarginTrading.Backend.Services/Infrastructure/SimpleIdentityGenerator.cs new file mode 100644 index 000000000..783ddb93a --- /dev/null +++ b/src/MarginTrading.Backend.Services/Infrastructure/SimpleIdentityGenerator.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Common; +using MarginTrading.Backend.Core.Repositories; + +namespace MarginTrading.Backend.Services.Infrastructure +{ + public class SimpleIdentityGenerator : IIdentityGenerator + { + private long _currentId; + private readonly Random _random = new Random(); + private const string Pool = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + private readonly object _lockObject = new object(); + + public SimpleIdentityGenerator() + { + _currentId = DateTime.Now.Ticks; + } + + public Task GenerateIdAsync(string entityType) + { + _currentId++; + return Task.FromResult(_currentId); + } + + public string GenerateAlphanumericId() + { + lock(_lockObject) + { + var chars = Enumerable.Range(0, 10).Select(x => Pool[_random.Next(0, Pool.Length)]); + return new string(chars.ToArray()); + } + } + + public string GenerateGuid() + { + return Guid.NewGuid().ToString("N"); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Infrastructure/SnapshotService.cs b/src/MarginTrading.Backend.Services/Infrastructure/SnapshotService.cs new file mode 100644 index 000000000..260f968a6 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Infrastructure/SnapshotService.cs @@ -0,0 +1,134 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Common; +using Common.Log; +using MarginTrading.Backend.Contracts.Account; +using MarginTrading.Backend.Contracts.Orders; +using MarginTrading.Backend.Contracts.Positions; +using MarginTrading.Backend.Contracts.Snow.Prices; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Core.Trading; +using MarginTrading.Backend.Services.AssetPairs; +using MarginTrading.Backend.Services.Mappers; +using MarginTrading.Common.Extensions; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Services.Infrastructure +{ + public class SnapshotService : ISnapshotService + { + private readonly IScheduleSettingsCacheService _scheduleSettingsCacheService; + private readonly IAccountsCacheService _accountsCacheService; + private readonly IQuoteCacheService _quoteCacheService; + private readonly IFxRateCacheService _fxRateCacheService; + private readonly IOrderReader _orderReader; + private readonly IDateService _dateService; + + private readonly ITradingEngineSnapshotsRepository _tradingEngineSnapshotsRepository; + private readonly ILog _log; + + private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); + + public SnapshotService( + IScheduleSettingsCacheService scheduleSettingsCacheService, + IAccountsCacheService accountsCacheService, + IQuoteCacheService quoteCacheService, + IFxRateCacheService fxRateCacheService, + IOrderReader orderReader, + IDateService dateService, + ITradingEngineSnapshotsRepository tradingEngineSnapshotsRepository, + ILog log) + { + _scheduleSettingsCacheService = scheduleSettingsCacheService; + _accountsCacheService = accountsCacheService; + _quoteCacheService = quoteCacheService; + _fxRateCacheService = fxRateCacheService; + _orderReader = orderReader; + _dateService = dateService; + _tradingEngineSnapshotsRepository = tradingEngineSnapshotsRepository; + _log = log; + } + + public async Task MakeTradingDataSnapshot(DateTime tradingDay, string correlationId) + { + if (!_scheduleSettingsCacheService.TryGetPlatformCurrentDisabledInterval(out var disabledInterval)) + { + //TODO: remove later (if everything will work and we will never go to this branch) + _scheduleSettingsCacheService.MarketsCacheWarmUp(); + + if (!_scheduleSettingsCacheService.TryGetPlatformCurrentDisabledInterval(out disabledInterval)) + { + throw new Exception( + $"Trading should be stopped for whole platform in order to make trading data snapshot. Current schedule: {_scheduleSettingsCacheService.GetPlatformTradingSchedule()?.ToJson()}"); + } + } + + if (disabledInterval.Start.AddDays(-1) > tradingDay.Date || disabledInterval.End < tradingDay.Date) + { + throw new Exception( + $"{nameof(tradingDay)}'s Date component must be from current disabled interval's Start -1d to End: [{disabledInterval.Start.AddDays(-1)}, {disabledInterval.End}]."); + } + + if (_semaphoreSlim.CurrentCount == 0) + { + throw new ArgumentException("Trading data snapshot creation is already in progress", "snapshot"); + } + + await _semaphoreSlim.WaitAsync(); + + try + { + var orders = _orderReader.GetAllOrders(); + var ordersData = orders.Select(x => x.ConvertToContract(_orderReader)).ToJson(); + await _log.WriteInfoAsync(nameof(SnapshotService), nameof(MakeTradingDataSnapshot), + $"Preparing data... {orders.Length} orders prepared."); + + var positions = _orderReader.GetPositions(); + var positionsData = positions.Select(x => x.ConvertToContract(_orderReader)).ToJson(); + await _log.WriteInfoAsync(nameof(SnapshotService), nameof(MakeTradingDataSnapshot), + $"Preparing data... {positions.Length} positions prepared."); + + var accountStats = _accountsCacheService.GetAll(); + var accountStatsData = accountStats.Select(x => x.ConvertToContract()).ToJson(); + await _log.WriteInfoAsync(nameof(SnapshotService), nameof(MakeTradingDataSnapshot), + $"Preparing data... {accountStats.Count} accounts prepared."); + + var bestFxPrices = _fxRateCacheService.GetAllQuotes(); + var bestFxPricesData = bestFxPrices.ToDictionary(q => q.Key, q => q.Value.ConvertToContract()).ToJson(); + await _log.WriteInfoAsync(nameof(SnapshotService), nameof(MakeTradingDataSnapshot), + $"Preparing data... {bestFxPrices.Count} best FX prices prepared."); + + var bestPrices = _quoteCacheService.GetAllQuotes(); + var bestPricesData = bestPrices.ToDictionary(q => q.Key, q => q.Value.ConvertToContract()).ToJson(); + await _log.WriteInfoAsync(nameof(SnapshotService), nameof(MakeTradingDataSnapshot), + $"Preparing data... {bestPrices.Count} best trading prices prepared."); + + var msg = $"TradingDay: {tradingDay:yyyy-MM-dd}, Orders: {orders.Length}, positions: {positions.Length}, accounts: {accountStats.Count}, best FX prices: {bestFxPrices.Count}, best trading prices: {bestPrices.Count}."; + + await _log.WriteInfoAsync(nameof(SnapshotService), nameof(MakeTradingDataSnapshot), + $"Starting to write trading data snapshot. {msg}"); + + await _tradingEngineSnapshotsRepository.Add(tradingDay, correlationId, _dateService.Now(), + ordersData, positionsData, accountStatsData, bestFxPricesData, + bestPricesData); + + await _log.WriteInfoAsync(nameof(SnapshotService), nameof(MakeTradingDataSnapshot), + $"Trading data snapshot was written to the storage. {msg}"); + return $"Trading data snapshot was written to the storage. {msg}"; + } + finally + { + _semaphoreSlim.Release(); + } + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Infrastructure/StartupDeduplicationService.cs b/src/MarginTrading.Backend.Services/Infrastructure/StartupDeduplicationService.cs new file mode 100644 index 000000000..81c1e5214 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Infrastructure/StartupDeduplicationService.cs @@ -0,0 +1,100 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Common.Log; +using Lykke.Common; +using MarginTrading.Backend.Core.Settings; +using Microsoft.AspNetCore.Hosting; +using StackExchange.Redis; + +namespace MarginTrading.Backend.Services.Infrastructure +{ + /// + /// + /// Ensure that only single instance of the app is running. + /// + public class StartupDeduplicationService : IDisposable + { + private const string LockKey = "TradingEngine:DeduplicationLock"; + private readonly string _lockValue = Environment.MachineName; + + private readonly IHostingEnvironment _hostingEnvironment; + private readonly ILog _log; + private readonly MarginTradingSettings _marginTradingSettings; + + private IDatabase _database = null; + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + public StartupDeduplicationService( + IHostingEnvironment hostingEnvironment, + ILog log, + MarginTradingSettings marginTradingSettings) + { + _hostingEnvironment = hostingEnvironment; + _log = log; + _marginTradingSettings = marginTradingSettings; + } + + /// + /// Check that no instance is currently running and hold Redis distributed lock during app lifetime. + /// Does nothing in debug mode. + /// Clarification. + /// There is a possibility of race condition in case when Redis is used in clustered/replicated mode: + /// - Client A acquires the lock in the master. + /// - The master crashes before the write to the key is transmitted to the slave. + /// - The slave gets promoted to master. + /// - Client B acquires the lock to the same resource A already holds a lock for. SAFETY VIOLATION! + /// But the probability of such situation is extremely small, so current implementation neglects it. + /// In case if it is required to assure safety in clustered/replicated mode RedLock algorithm may be used. + /// + public void HoldLock() + { + if (_hostingEnvironment.IsDevelopment()) + { + return; + } + + var multiplexer = ConnectionMultiplexer.Connect(_marginTradingSettings.RedisSettings.Configuration); + _database = multiplexer.GetDatabase(); + + if (!_database.LockTake(LockKey, _lockValue, _marginTradingSettings.DeduplicationLockExpiryPeriod)) + { + throw new Exception("Trading Engine failed to start due to deduplication validation failure."); + // exception is logged by the global handler + } + + Exception workerException = null; + // ReSharper disable once PossibleNullReferenceException + _cancellationTokenSource.Token.Register(() => throw workerException); + + Task.Run(async () => + { + try + { + while (true) + { + // wait and extend lock + Thread.Sleep(_marginTradingSettings.DeduplicationLockExtensionPeriod); + + await _database.LockExtendAsync(LockKey, _lockValue, + _marginTradingSettings.DeduplicationLockExpiryPeriod); + } + } + catch (Exception exception) + { + workerException = exception; + _cancellationTokenSource.Cancel(); + } + }); + } + + public void Dispose() + { + _database?.LockRelease(LockKey, _lockValue); + _cancellationTokenSource.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Infrastructure/StartupQueuesCheckerService.cs b/src/MarginTrading.Backend.Services/Infrastructure/StartupQueuesCheckerService.cs new file mode 100644 index 000000000..add998892 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Infrastructure/StartupQueuesCheckerService.cs @@ -0,0 +1,45 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Common.RabbitMq; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Services.Infrastructure +{ + public class StartupQueuesCheckerService + { + private readonly MarginTradingSettings _marginTradingSettings; + + public StartupQueuesCheckerService(MarginTradingSettings marginTradingSettings) + { + _marginTradingSettings = marginTradingSettings; + } + + /// + /// Check that RabbitMQ queues of other services which data is required for initialization are empty. + /// + public void Check() + { + foreach (var queueName in new[] + { + _marginTradingSettings.StartupQueuesChecker.OrderHistoryQueueName, + _marginTradingSettings.StartupQueuesChecker.PositionHistoryQueueName + }) + { + var messageCount = RabbitMqService.GetMessageCount( + _marginTradingSettings.StartupQueuesChecker.ConnectionString, + queueName); + if (messageCount == 0) + { + continue; + } + + throw new Exception( + $"All Order/Position broker queues from StartupQueuesChecker setting must be empty. Currently [{queueName}] contains [{messageCount}] messages."); + } + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Infrastructure/TradingSyncContext.cs b/src/MarginTrading.Backend.Services/Infrastructure/TradingSyncContext.cs index cf6a300e1..3a9cac3af 100644 --- a/src/MarginTrading.Backend.Services/Infrastructure/TradingSyncContext.cs +++ b/src/MarginTrading.Backend.Services/Infrastructure/TradingSyncContext.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; @@ -25,14 +28,14 @@ public class TradingSyncContext : IDisposable private readonly string _contextType; private readonly string _contextSource; private readonly ITelemetryPublisher _telemetryPublisher; - private readonly MarginSettings _marginTradingSettings; + private readonly MarginTradingSettings _marginTradingSettings; private static int _contextNestingDepth; private readonly long _waitingForLockTime; private readonly Stopwatch _sw = new Stopwatch(); public TradingSyncContext(string contextType, string contextSource, - ITelemetryPublisher telemetryPublisher, MarginSettings marginTradingSettings) + ITelemetryPublisher telemetryPublisher, MarginTradingSettings marginTradingSettings) { _contextType = contextType; _contextSource = contextSource; diff --git a/src/MarginTrading.Backend.Services/Mappers/DomainToContractMappers.cs b/src/MarginTrading.Backend.Services/Mappers/DomainToContractMappers.cs new file mode 100644 index 000000000..9e8a946c6 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Mappers/DomainToContractMappers.cs @@ -0,0 +1,210 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using MarginTrading.Backend.Contracts.Account; +using MarginTrading.Backend.Contracts.Orders; +using MarginTrading.Backend.Contracts.Positions; +using MarginTrading.Backend.Contracts.Snow.Prices; +using MarginTrading.Backend.Contracts.TradeMonitoring; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; +using MarginTrading.Common.Extensions; +using MatchedOrderContract = MarginTrading.Backend.Contracts.Orders.MatchedOrderContract; +using OrderDirectionContract = MarginTrading.Backend.Contracts.Orders.OrderDirectionContract; +using OrderStatusContract = MarginTrading.Backend.Contracts.Orders.OrderStatusContract; + +namespace MarginTrading.Backend.Services.Mappers +{ + public static class DomainToContractMappers + { + public static OrderContract ConvertToContract(this Order order, IOrderReader orderReader) + { + var relatedOrders = new List(); + + foreach (var relatedOrderInfo in order.RelatedOrders) + { + if (orderReader.TryGetOrderById(relatedOrderInfo.Id, out var relatedOrder)) + { + relatedOrders.Add(relatedOrder); + } + } + + return order.ConvertToContract(relatedOrders); + } + + public static OrderContract ConvertToContract(this Order order, List relatedOrders) + { + RelatedOrderInfoContract Map(RelatedOrderInfo relatedOrderInfo) + { + var relateOrder = relatedOrders.FirstOrDefault(o => o.Id == relatedOrderInfo.Id); + + if (relateOrder == null) + { + return null; + } + + return new RelatedOrderInfoContract + { + Id = relateOrder.Id, + Price = relateOrder.Price ?? 0, + Type = relateOrder.OrderType.ToType(), + Status = relateOrder.Status.ToType(), + ModifiedTimestamp = relateOrder.LastModified, + TrailingDistance = relateOrder.TrailingDistance + }; + } + + return new OrderContract + { + Id = order.Id, + AccountId = order.AccountId, + AssetPairId = order.AssetPairId, + CreatedTimestamp = order.Created, + Direction = order.Direction.ToType(), + ExecutionPrice = order.ExecutionPrice, + FxRate = order.FxRate, + FxAssetPairId = order.FxAssetPairId, + FxToAssetPairDirection = order.FxToAssetPairDirection.ToType(), + ExpectedOpenPrice = order.Price, + ForceOpen = order.ForceOpen, + ModifiedTimestamp = order.LastModified, + Originator = order.Originator.ToType(), + ParentOrderId = order.ParentOrderId, + PositionId = order.ParentPositionId, + RelatedOrders = order.RelatedOrders.Select(o => o.Id).ToList(), + Status = order.Status.ToType(), + FillType = order.FillType.ToType(), + Type = order.OrderType.ToType(), + ValidityTime = order.Validity, + Volume = order.Volume, + //------ + AccountAssetId = order.AccountAssetId, + EquivalentAsset = order.EquivalentAsset, + ActivatedTimestamp = order.Activated, + CanceledTimestamp = order.Canceled, + Code = order.Code, + Comment = order.Comment, + EquivalentRate = order.EquivalentRate, + ExecutedTimestamp = order.Executed, + ExecutionStartedTimestamp = order.ExecutionStarted, + ExternalOrderId = order.ExternalOrderId, + ExternalProviderId = order.ExternalProviderId, + LegalEntity = order.LegalEntity, + MatchedOrders = order.MatchedOrders.Select(o => new MatchedOrderContract + { + OrderId = o.OrderId, + Volume = o.Volume, + Price = o.Price, + MarketMakerId = o.MarketMakerId, + LimitOrderLeftToMatch = o.LimitOrderLeftToMatch, + MatchedDate = o.MatchedDate, + IsExternal = o.IsExternal + }).ToList(), + MatchingEngineId = order.MatchingEngineId, + Rejected = order.Rejected, + RejectReason = order.RejectReason.ToType(), + RejectReasonText = order.RejectReasonText, + RelatedOrderInfos = order.RelatedOrders.Select(Map).Where(o => o != null).ToList(), + TradingConditionId = order.TradingConditionId, + AdditionalInfo = order.AdditionalInfo, + CorrelationId = order.CorrelationId, + PendingOrderRetriesCount = order.PendingOrderRetriesCount, + TrailingDistance = order.TrailingDistance, + HasOnBehalf = order.HasOnBehalf, + }; + } + + public static OpenPositionContract ConvertToContract(this Position position, IOrderReader orderReader) + { + var relatedOrders = new List(); + + foreach (var relatedOrderInfo in position.RelatedOrders) + { + if (orderReader.TryGetOrderById(relatedOrderInfo.Id, out var relatedOrder)) + { + relatedOrders.Add(new RelatedOrderInfoContract + { + Id = relatedOrder.Id, + Price = relatedOrder.Price ?? 0, + Type = relatedOrder.OrderType.ToType(), + Status = relatedOrder.Status.ToType(), + ModifiedTimestamp = relatedOrder.LastModified, + TrailingDistance = relatedOrder.TrailingDistance + }); + } + } + + return new OpenPositionContract + { + AccountId = position.AccountId, + AssetPairId = position.AssetPairId, + CurrentVolume = position.Volume, + Direction = position.Direction.ToType(), + Id = position.Id, + OpenPrice = position.OpenPrice, + OpenFxPrice = position.OpenFxPrice, + ClosePrice = position.ClosePrice, + ExpectedOpenPrice = position.ExpectedOpenPrice, + OpenTradeId = position.OpenTradeId, + OpenOrderType = position.OpenOrderType.ToType(), + OpenOrderVolume = position.OpenOrderVolume, + PnL = position.GetFpl(), + ChargedPnl = position.ChargedPnL, + Margin = position.GetMarginMaintenance(), + FxRate = position.CloseFxPrice, + FxAssetPairId = position.FxAssetPairId, + FxToAssetPairDirection = position.FxToAssetPairDirection.ToType(), + RelatedOrders = position.RelatedOrders.Select(o => o.Id).ToList(), + RelatedOrderInfos = relatedOrders, + OpenTimestamp = position.OpenDate, + ModifiedTimestamp = position.LastModified, + TradeId = position.Id, + AdditionalInfo = position.AdditionalInfo, + Status = position.Status.ToType(), + }; + } + + public static AccountStatContract ConvertToContract(this IMarginTradingAccount account) + { + return new AccountStatContract + { + AccountId = account.Id, + BaseAssetId = account.BaseAssetId, + Balance = account.Balance, + LastBalanceChangeTime = account.LastBalanceChangeTime, + MarginCallLevel = account.GetMarginCall1Level(), + StopOutLevel = account.GetStopOutLevel(), + TotalCapital = account.GetTotalCapital(), + FreeMargin = account.GetFreeMargin(), + MarginAvailable = account.GetMarginAvailable(), + UsedMargin = account.GetUsedMargin(), + CurrentlyUsedMargin = account.GetCurrentlyUsedMargin(), + InitiallyUsedMargin = account.GetInitiallyUsedMargin(), + MarginInit = account.GetMarginInit(), + PnL = account.GetPnl(), + UnrealizedDailyPnl = account.GetUnrealizedDailyPnl(), + OpenPositionsCount = account.GetOpenPositionsCount(), + ActiveOrdersCount = account.GetActiveOrdersCount(), + MarginUsageLevel = account.GetMarginUsageLevel(), + LegalEntity = account.LegalEntity, + IsInLiquidation = account.IsInLiquidation(), + MarginNotificationLevel = account.GetAccountLevel().ToString() + }; + } + + public static BestPriceContract ConvertToContract(this InstrumentBidAskPair arg) + { + return new BestPriceContract + { + Ask = arg.Ask, + Bid = arg.Bid, + Id = arg.Instrument, + Timestamp = arg.Date, + }; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj b/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj index c1d5d65bf..c8134779e 100644 --- a/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj +++ b/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj @@ -1,15 +1,19 @@  - netcoreapp2.0 + netcoreapp2.2 MarginTrading.Backend.Services MarginTrading.Backend.Services false false false - 1.0.1 + 1.16.29 latest + + 1701;1702;1705;CA2007;0612;0618;1591 + + @@ -17,23 +21,31 @@ - + - - + + + + + - + + + + + - - - + + - + - - - + + + + + \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/MatchingEngines/IMatchingEngineRoutesManager.cs b/src/MarginTrading.Backend.Services/MatchingEngines/IMatchingEngineRoutesManager.cs new file mode 100644 index 000000000..3c3f4d26f --- /dev/null +++ b/src/MarginTrading.Backend.Services/MatchingEngines/IMatchingEngineRoutesManager.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.MatchingEngines; +using MarginTrading.Backend.Core.Orders; + +namespace MarginTrading.Backend.Services.MatchingEngines +{ + public interface IMatchingEngineRoutesManager + { + Task UpdateRoutesCacheAsync(); + IMatchingEngineRoute FindRoute(string clientId, string tradingConditionId, string instrumentId, OrderDirection orderType); + Task HandleRiskManagerCommand(MatchingEngineRouteRisksCommand command); + Task HandleRiskManagerBlockTradingCommand(MatchingEngineRouteRisksCommand command); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/MatchingEngines/MarketMakerMatchingEngine.cs b/src/MarginTrading.Backend.Services/MatchingEngines/MarketMakerMatchingEngine.cs index 9968e5929..4b5ffd2b8 100644 --- a/src/MarginTrading.Backend.Services/MatchingEngines/MarketMakerMatchingEngine.cs +++ b/src/MarginTrading.Backend.Services/MatchingEngines/MarketMakerMatchingEngine.cs @@ -1,10 +1,16 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.MatchedOrders; using MarginTrading.Backend.Core.MatchingEngines; using MarginTrading.Backend.Core.Orderbooks; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; using MarginTrading.Backend.Services.Events; using MarginTrading.Backend.Services.Infrastructure; @@ -76,13 +82,29 @@ public void SetOrders(SetOrderModel model) } } - public decimal? GetPriceForClose(Order order) + public (string externalProviderId, decimal? price) GetBestPriceForOpen(string assetPairId, decimal volume) + { + using (_contextFactory.GetWriteSyncContext($"{nameof(MarketMakerMatchingEngine)}.{nameof(GetBestPriceForOpen)}")) + { + var orderBookTypeToMatch = + volume.GetOrderDirection().GetOrderDirectionToMatchInOrderBook(); + + var matchedOrders = _orderBooks.Match(assetPairId, orderBookTypeToMatch, Math.Abs(volume)); + + var price = matchedOrders.Any() ? matchedOrders.WeightedAveragePrice : (decimal?) null; + + return (null, price); + } // lock + } + + public decimal? GetPriceForClose(string assetPairId, decimal volume, string externalProviderId) { using (_contextFactory.GetWriteSyncContext($"{nameof(MarketMakerMatchingEngine)}.{nameof(GetPriceForClose)}")) { - var orderBookTypeToMatch = order.GetCloseType().GetOrderTypeToMatchInOrderBook(); + var orderBookTypeToMatch = + volume.GetClosePositionOrderDirection().GetOrderDirectionToMatchInOrderBook(); - var matchedOrders = _orderBooks.Match(order, orderBookTypeToMatch, Math.Abs(order.GetRemainingCloseVolume())); + var matchedOrders = _orderBooks.Match(assetPairId, orderBookTypeToMatch, Math.Abs(volume)); return matchedOrders.Any() ? matchedOrders.WeightedAveragePrice : (decimal?) null; } // lock @@ -96,37 +118,24 @@ public OrderBook GetOrderBook(string instrument) } } - public void MatchMarketOrderForOpen(Order order, Func matchedFunc) + public Task MatchOrderAsync(Order order, bool shouldOpenNewPosition, + OrderModality modality = OrderModality.Regular) { - using (_contextFactory.GetWriteSyncContext($"{nameof(MarketMakerMatchingEngine)}.{nameof(MatchMarketOrderForOpen)}")) + using (_contextFactory.GetWriteSyncContext( + $"{nameof(MarketMakerMatchingEngine)}.{nameof(MatchOrderAsync)}")) { - var orderBookTypeToMatch = order.GetOrderType().GetOrderTypeToMatchInOrderBook(); + var orderBookTypeToMatch = order.Direction.GetOrderDirectionToMatchInOrderBook(); + + //TODO: validate opposite direction if will open new position var matchedOrders = - _orderBooks.Match(order, orderBookTypeToMatch, Math.Abs(order.Volume)); - - if (matchedFunc(matchedOrders)) - { - _orderBooks.Update(order, orderBookTypeToMatch, matchedOrders); - ProduceBestPrice(order.Instrument); - } - } - } - - public void MatchMarketOrderForClose(Order order, Func matchedAction) - { - using (_contextFactory.GetWriteSyncContext($"{nameof(MarketMakerMatchingEngine)}.{nameof(MatchMarketOrderForClose)}")) - { - var orderBookTypeToMatch = order.GetCloseType().GetOrderTypeToMatchInOrderBook(); - - var matchedOrders = _orderBooks.Match(order, orderBookTypeToMatch, Math.Abs(order.GetRemainingCloseVolume())); + _orderBooks.Match(order.AssetPairId, orderBookTypeToMatch, Math.Abs(order.Volume)); - if (!matchedAction(matchedOrders)) - return; + _orderBooks.Update(order.AssetPairId, orderBookTypeToMatch, matchedOrders); + ProduceBestPrice(order.AssetPairId); - _orderBooks.Update(order, orderBookTypeToMatch, matchedOrders); - ProduceBestPrice(order.Instrument); - } // lock + return Task.FromResult(matchedOrders); + } } public bool PingLock() diff --git a/src/MarginTrading.Backend.Services/MatchingEngines/MatchingEngineInMemoryRepository.cs b/src/MarginTrading.Backend.Services/MatchingEngines/MatchingEngineInMemoryRepository.cs index 7275ba6cc..1d324c14c 100644 --- a/src/MarginTrading.Backend.Services/MatchingEngines/MatchingEngineInMemoryRepository.cs +++ b/src/MarginTrading.Backend.Services/MatchingEngines/MatchingEngineInMemoryRepository.cs @@ -1,14 +1,14 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Linq; -using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.MatchingEngines; -using MarginTrading.Backend.Core.Orderbooks; -using MarginTrading.Backend.Services.Events; -using MarginTrading.Backend.Services.Infrastructure; namespace MarginTrading.Backend.Services.MatchingEngines { + //TODO: rework public class MatchingEngineInMemoryRepository : IMatchingEngineRepository { private readonly Dictionary _matchingEngines; diff --git a/src/MarginTrading.Backend.Services/MatchingEngines/MatchingEngineRouter.cs b/src/MarginTrading.Backend.Services/MatchingEngines/MatchingEngineRouter.cs index d79448079..1749ee69a 100644 --- a/src/MarginTrading.Backend.Services/MatchingEngines/MatchingEngineRouter.cs +++ b/src/MarginTrading.Backend.Services/MatchingEngines/MatchingEngineRouter.cs @@ -1,6 +1,10 @@ -using MarginTrading.Backend.Core; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.MatchingEngines; -using MarginTrading.Backend.Services.TradingConditions; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; namespace MarginTrading.Backend.Services.MatchingEngines { @@ -22,34 +26,28 @@ public MatchingEngineRouter( _assetPairsCache = assetPairsCache; } - public IMatchingEngineBase GetMatchingEngineForOpen(IOrder order) + //TODO: implement routes logic, to consider account LE and take only ME with same LE as account, find ME with correct mode that owns the same Entity as asset pair + public IMatchingEngineBase GetMatchingEngineForExecution(Order order) { - var route = _routesManager.FindRoute(order.ClientId, order.TradingConditionId, order.Instrument, - order.GetOrderType()); + var route = _routesManager.FindRoute(null, order.TradingConditionId, order.AssetPairId, + order.Direction); if (route != null) - { - //TODO: to consider account LE and take only ME with same LE as account - + { return _matchingEngineRepository.GetMatchingEngineById(route.MatchingEngineId); } - var assetPair = _assetPairsCache.GetAssetPairByIdOrDefault(order.Instrument); + var assetPair = _assetPairsCache.GetAssetPairByIdOrDefault(order.AssetPairId); - //TODO: find ME with correct mode that ownes the same Entity as asset pair return _matchingEngineRepository.GetMatchingEngineById( (assetPair?.MatchingEngineMode ?? MatchingEngineMode.MarketMaker) == MatchingEngineMode.MarketMaker - ? MatchingEngineConstants.LykkeVuMm - : MatchingEngineConstants.LykkeCyStp); + ? MatchingEngineConstants.DefaultMm + : MatchingEngineConstants.DefaultStp); } - public IMatchingEngineBase GetMatchingEngineForClose(IOrder order) + public IMatchingEngineBase GetMatchingEngineForClose(string openMatchingEngineId) { - var meId = order.OpenOrderbookId == Lykke - ? MatchingEngineConstants.LykkeVuMm - : order.OpenOrderbookId; - - return _matchingEngineRepository.GetMatchingEngineById(meId); + return _matchingEngineRepository.GetMatchingEngineById(openMatchingEngineId); } } } diff --git a/src/MarginTrading.Backend.Services/MatchingEngines/MatchingEngineRoutesCacheService.cs b/src/MarginTrading.Backend.Services/MatchingEngines/MatchingEngineRoutesCacheService.cs index c54d293fc..2664953b6 100644 --- a/src/MarginTrading.Backend.Services/MatchingEngines/MatchingEngineRoutesCacheService.cs +++ b/src/MarginTrading.Backend.Services/MatchingEngines/MatchingEngineRoutesCacheService.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using System.Linq; using System.Threading; using MarginTrading.Backend.Core.MatchingEngines; diff --git a/src/MarginTrading.Backend.Services/MatchingEngines/MatchingEngineRoutesManager.cs b/src/MarginTrading.Backend.Services/MatchingEngines/MatchingEngineRoutesManager.cs index c127d3be7..7771862e8 100644 --- a/src/MarginTrading.Backend.Services/MatchingEngines/MatchingEngineRoutesManager.cs +++ b/src/MarginTrading.Backend.Services/MatchingEngines/MatchingEngineRoutesManager.cs @@ -1,47 +1,57 @@ -using System; -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Linq; using System.Threading.Tasks; using Autofac; using Common; using Common.Log; +using JetBrains.Annotations; using MarginTrading.AzureRepositories.Logs; using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.MatchingEngines; -using MarginTrading.Backend.Core.TradingConditions; +using MarginTrading.Backend.Core.Orders; using MarginTrading.Backend.Services.TradingConditions; using MarginTrading.Common.Extensions; +using MarginTrading.Common.Services; +using MarginTrading.SettingsService.Contracts; +using MarginTrading.SettingsService.Contracts.Routes; namespace MarginTrading.Backend.Services.MatchingEngines { - public class MatchingEngineRoutesManager : IStartable + [UsedImplicitly] + public class MatchingEngineRoutesManager : IStartable, IMatchingEngineRoutesManager { private const string AnyValue = "*"; private readonly MatchingEngineRoutesCacheService _routesCacheService; - private readonly IMatchingEngineRoutesRepository _repository; + private readonly ITradingRoutesApi _routesApi; private readonly IAssetPairsCache _assetPairsCache; private readonly ITradingConditionsCacheService _tradingConditionsCacheService; private readonly IAccountsCacheService _accountsCacheService; - private readonly IRiskSystemCommandsLogRepository _riskSystemCommandsLogRepository; + //private readonly IRiskSystemCommandsLogRepository _riskSystemCommandsLogRepository; private readonly ILog _log; + private readonly IConvertService _convertService; public MatchingEngineRoutesManager( MatchingEngineRoutesCacheService routesCacheService, - IMatchingEngineRoutesRepository repository, + ITradingRoutesApi routesApi, IAssetPairsCache assetPairsCache, ITradingConditionsCacheService tradingConditionsCacheService, IAccountsCacheService accountsCacheService, - IRiskSystemCommandsLogRepository riskSystemCommandsLogRepository, - ILog log) + //IRiskSystemCommandsLogRepository riskSystemCommandsLogRepository, + ILog log, + IConvertService convertService) { _routesCacheService = routesCacheService; - _repository = repository; + _routesApi = routesApi; _assetPairsCache = assetPairsCache; _tradingConditionsCacheService = tradingConditionsCacheService; _accountsCacheService = accountsCacheService; - _riskSystemCommandsLogRepository = riskSystemCommandsLogRepository; + //_riskSystemCommandsLogRepository = riskSystemCommandsLogRepository; _log = log; + _convertService = convertService; } @@ -54,24 +64,20 @@ public void Start() public async Task UpdateRoutesCacheAsync() { - var routes = await _repository.GetAllRoutesAsync(); + var routes = await _routesApi.List(); + + if (routes != null) + { + _routesCacheService.InitCache( + routes.Select(r => _convertService.Convert(r))); + } + - _routesCacheService.InitCache(routes); } #endregion - public IEnumerable GetRoutes() - { - return _routesCacheService.GetRoutes().Select(ToViewModel); - } - - public IMatchingEngineRoute GetRouteById(string id) - { - return ToViewModel(_routesCacheService.GetRoute(id)); - } - public IMatchingEngineRoute FindRoute(string clientId, string tradingConditionId, string instrumentId, OrderDirection orderType) { var routes = _routesCacheService.GetRoutes(); @@ -123,24 +129,28 @@ public IMatchingEngineRoute FindRoute(string clientId, string tradingConditionId return null; } - public async Task AddOrReplaceRouteAsync(IMatchingEngineRoute route) + //TODO: Risk manager related stuff may be removed one time.. + + public Task AddOrReplaceRouteInCacheAsync(IMatchingEngineRoute route) { // Create Editable Object var matchingEngineRoute = MatchingEngineRoute.Create(route); - matchingEngineRoute.ClientId = GetValueOrAnyIfValid(route.ClientId, ValidateClient); + matchingEngineRoute.ClientId = GetValueOrAnyIfValid(route.ClientId, null); matchingEngineRoute.TradingConditionId = GetValueOrAnyIfValid(route.TradingConditionId, ValidateTradingCondition); matchingEngineRoute.Instrument = GetValueOrAnyIfValid(route.Instrument, ValidateInstrument); matchingEngineRoute.Asset = GetValueOrAnyIfValid(route.Asset, null); - await _repository.AddOrReplaceRouteAsync(matchingEngineRoute); _routesCacheService.SaveRoute(matchingEngineRoute); - } - - public async Task DeleteRouteAsync(string routeId) + + return Task.CompletedTask; + } + + public Task DeleteRouteFromCacheAsync(string routeId) { - await _repository.DeleteRouteAsync(routeId); _routesCacheService.DeleteRoute(routeId); + + return Task.CompletedTask; } public async Task HandleRiskManagerCommand(MatchingEngineRouteRisksCommand command) @@ -161,18 +171,19 @@ public async Task HandleRiskManagerCommand(MatchingEngineRouteRisksCommand comma throw new NotSupportedException($"Command of type [{command.ActionType}] from risk manager is not supported"); } - await _riskSystemCommandsLogRepository.AddProcessedAsync(command.ActionType.ToString(), - command.ToJson()); + //await _riskSystemCommandsLogRepository.AddProcessedAsync(command.ActionType.ToString(), + // command.ToJson()); } catch (Exception e) { - await _riskSystemCommandsLogRepository.AddErrorAsync(command.ActionType.ToString(), command.ToJson(), - e.Message); + _log.WriteError(nameof(HandleRiskManagerCommand), command, e); + //await _riskSystemCommandsLogRepository.AddErrorAsync(command.ActionType.ToString(), command.ToJson(), + // e.Message); throw; } } - private async Task HandleRiskManagerBlockTradingCommand(MatchingEngineRouteRisksCommand command) + public async Task HandleRiskManagerBlockTradingCommand(MatchingEngineRouteRisksCommand command) { switch (command.Action) { @@ -182,9 +193,9 @@ private async Task HandleRiskManagerBlockTradingCommand(MatchingEngineRouteRisks if (routes.Any()) { - await _riskSystemCommandsLogRepository.AddErrorAsync(command.ActionType.ToString(), - command.ToJson(), - $"Route already exists: {routes.ToJson()}"); + //await _riskSystemCommandsLogRepository.AddErrorAsync(command.ActionType.ToString(), + // command.ToJson(), + // $"Route already exists: {routes.ToJson()}"); await _log.WriteWarningAsync(nameof(MatchingEngineRoutesManager), nameof(HandleRiskManagerBlockTradingCommand), routes.ToJson(), $"Route already exists. Command from risk system is not processed: {command.ToJson()} "); @@ -205,7 +216,7 @@ await _log.WriteWarningAsync(nameof(MatchingEngineRoutesManager), RiskSystemMetricType = command.Type }; - await AddOrReplaceRouteAsync(newRoute); + await AddOrReplaceRouteInCacheAsync(newRoute); break; case RiskManagerAction.Off: @@ -216,7 +227,7 @@ await _log.WriteWarningAsync(nameof(MatchingEngineRoutesManager), throw new Exception( $"Cannot disable BlockTradingForNewOrders route for command: {command.ToJson()}. Existing routes found for command: {existingRoutes.ToJson()}"); - await DeleteRouteAsync(existingRoutes[0].Id); + await DeleteRouteFromCacheAsync(existingRoutes[0].Id); break; default: @@ -265,14 +276,6 @@ private string GetValueOrAnyIfValid(string value, Action validationActio return value; } - private void ValidateClient(string clientId) - { - var userAccounts = _accountsCacheService.GetAll(clientId); - - if (!userAccounts.Any()) - throw new ArgumentException("Invalid ClientId"); - } - private void ValidateTradingCondition(string tradingConditionId) { if (!_tradingConditionsCacheService.IsTradingConditionExists(tradingConditionId)) diff --git a/src/MarginTrading.Backend.Services/MatchingEngines/RejectMatchingEngine.cs b/src/MarginTrading.Backend.Services/MatchingEngines/RejectMatchingEngine.cs index 68865f865..320ab51d2 100644 --- a/src/MarginTrading.Backend.Services/MatchingEngines/RejectMatchingEngine.cs +++ b/src/MarginTrading.Backend.Services/MatchingEngines/RejectMatchingEngine.cs @@ -1,8 +1,14 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.MatchedOrders; using MarginTrading.Backend.Core.MatchingEngines; using MarginTrading.Backend.Core.Orderbooks; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; namespace MarginTrading.Backend.Services.MatchingEngines { @@ -12,17 +18,25 @@ public class RejectMatchingEngine : IMatchingEngineBase public MatchingEngineMode Mode => MatchingEngineMode.MarketMaker; - public void MatchMarketOrderForOpen(Order order, Func orderProcessed) + public Task MatchOrderAsync(Order order, bool shouldOpenNewPosition, + OrderModality modality = OrderModality.Regular) { - orderProcessed(new MatchedOrderCollection()); + return Task.FromResult(new MatchedOrderCollection()); + } + + public (string externalProviderId, decimal? price) GetBestPriceForOpen(string assetPairId, decimal volume) + { + return (null, null); } - public void MatchMarketOrderForClose(Order order, Func orderProcessed) + public Task MatchMarketOrderForCloseAsync(Position order, Func orderProcessed) { orderProcessed(new MatchedOrderCollection()); + + return Task.CompletedTask; } - public decimal? GetPriceForClose(Order order) + public decimal? GetPriceForClose(string assetPairId, decimal volume, string externalProviderId) { return null; } diff --git a/src/MarginTrading.Backend.Services/MatchingEngines/SpecialLiquidationMatchingEngine.cs b/src/MarginTrading.Backend.Services/MatchingEngines/SpecialLiquidationMatchingEngine.cs new file mode 100644 index 000000000..5bfee2914 --- /dev/null +++ b/src/MarginTrading.Backend.Services/MatchingEngines/SpecialLiquidationMatchingEngine.cs @@ -0,0 +1,72 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.MatchedOrders; +using MarginTrading.Backend.Core.MatchingEngines; +using MarginTrading.Backend.Core.Orderbooks; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Trading; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Services.MatchingEngines +{ + /// + /// Matching Engine for Special Liquidation ONLY! + /// Instance is created in CommandsHandler and passed to TradingEngine, no IoC registration here. + /// + public class SpecialLiquidationMatchingEngine : ISpecialLiquidationMatchingEngine + { + public string Id => MatchingEngineConstants.DefaultSpecialLiquidation; + public MatchingEngineMode Mode => MatchingEngineMode.Stp; + private readonly decimal _price; + private readonly string _marketMakerId; + private readonly string _externalOrderId; + private readonly DateTime _externalExecutionTime; + + public SpecialLiquidationMatchingEngine( + decimal price, + string marketMakerId, + string externalOrderId, + DateTime externalExecutionTime) + { + _price = price; + _marketMakerId = marketMakerId; + _externalOrderId = externalOrderId; + _externalExecutionTime = externalExecutionTime; + } + + public Task MatchOrderAsync(Order order, bool shouldOpenNewPosition, + OrderModality modality = OrderModality.Regular) + { + var col = new MatchedOrderCollection(new [] {new MatchedOrder + { + OrderId = _externalOrderId, + MarketMakerId = _marketMakerId, + Volume = Math.Abs(order.Volume), + Price = _price, + MatchedDate = _externalExecutionTime, + IsExternal = true, + }}); + return Task.FromResult(col); + } + + public (string externalProviderId, decimal? price) GetBestPriceForOpen(string assetPairId, decimal volume) + { + return (_marketMakerId, _price); + } + + public decimal? GetPriceForClose(string assetPairId, decimal volume, string externalProviderId) + { + return _price; + } + + public OrderBook GetOrderBook(string instrument) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/MatchingEngines/StpMatchingEngine.cs b/src/MarginTrading.Backend.Services/MatchingEngines/StpMatchingEngine.cs index 25b697c7f..af917ad3d 100644 --- a/src/MarginTrading.Backend.Services/MatchingEngines/StpMatchingEngine.cs +++ b/src/MarginTrading.Backend.Services/MatchingEngines/StpMatchingEngine.cs @@ -1,215 +1,196 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Common; using Common.Log; -using Lykke.Service.ExchangeConnector.Client; -using Lykke.Service.ExchangeConnector.Client.Models; +using MarginTrading.Backend.Contracts.ExchangeConnector; using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.MatchedOrders; using MarginTrading.Backend.Core.MatchingEngines; using MarginTrading.Backend.Core.Orderbooks; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Core.Trading; using MarginTrading.Backend.Services.Notifications; -using MarginTrading.Backend.Services.Stp; using MarginTrading.Common.Extensions; using MarginTrading.Common.Services; +using MarginTrading.Common.Settings; +using OrderType = MarginTrading.Backend.Contracts.ExchangeConnector.OrderType; namespace MarginTrading.Backend.Services.MatchingEngines { public class StpMatchingEngine : IStpMatchingEngine { - private readonly ExternalOrderBooksList _externalOrderBooksList; - private readonly IExchangeConnectorService _exchangeConnectorService; + private readonly IExternalOrderbookService _externalOrderbookService; + private readonly IExchangeConnectorClient _exchangeConnectorClient; private readonly ILog _log; + private readonly IOperationsLogService _operationsLogService; private readonly IDateService _dateService; private readonly IRabbitMqNotifyService _rabbitMqNotifyService; private readonly IAssetPairsCache _assetPairsCache; + private readonly MarginTradingSettings _marginTradingSettings; + private readonly ExchangeConnectorServiceClient _exchangeConnectorServiceClient; + private readonly IQuoteCacheService _quoteCacheService; public string Id { get; } public MatchingEngineMode Mode => MatchingEngineMode.Stp; public StpMatchingEngine(string id, - ExternalOrderBooksList externalOrderBooksList, - IExchangeConnectorService exchangeConnectorService, + IExternalOrderbookService externalOrderbookService, + IExchangeConnectorClient exchangeConnectorClient, ILog log, + IOperationsLogService operationsLogService, IDateService dateService, IRabbitMqNotifyService rabbitMqNotifyService, - IAssetPairsCache assetPairsCache) + IAssetPairsCache assetPairsCache, + MarginTradingSettings marginTradingSettings, + ExchangeConnectorServiceClient exchangeConnectorServiceClient, + IQuoteCacheService quoteCacheService) { - _externalOrderBooksList = externalOrderBooksList; - _exchangeConnectorService = exchangeConnectorService; + _externalOrderbookService = externalOrderbookService; + _exchangeConnectorClient = exchangeConnectorClient; _log = log; + _operationsLogService = operationsLogService; _dateService = dateService; _rabbitMqNotifyService = rabbitMqNotifyService; _assetPairsCache = assetPairsCache; + _marginTradingSettings = marginTradingSettings; + _exchangeConnectorServiceClient = exchangeConnectorServiceClient; + _quoteCacheService = quoteCacheService; Id = id; } - //TODO: remove orderProcessed function and make all validations before match - public void MatchMarketOrderForOpen(Order order, Func orderProcessed) + public async Task MatchOrderAsync(Order order, bool shouldOpenNewPosition, + OrderModality modality = OrderModality.Regular) { - var prices = _externalOrderBooksList.GetPricesForOpen(order); + List<(string source, decimal? price)> prices = null; + + if (!string.IsNullOrEmpty(_marginTradingSettings.DefaultExternalExchangeId)) + { + var quote = _quoteCacheService.GetQuote(order.AssetPairId); + if (quote.GetVolumeForOrderDirection(order.Direction) >= Math.Abs(order.Volume)) + { + prices = new List<(string source, decimal? price)> + { + (_marginTradingSettings + .DefaultExternalExchangeId, quote.GetPriceForOrderDirection(order.Direction)) + }; + } + } + if (prices == null) { - orderProcessed(new MatchedOrderCollection()); - return; + prices = _externalOrderbookService.GetOrderedPricesForExecution(order.AssetPairId, order.Volume, shouldOpenNewPosition); + + if (prices == null || !prices.Any()) + { + return new MatchedOrderCollection(); + } } - - prices = order.GetOrderType() == OrderDirection.Buy - ? prices.OrderBy(tuple => tuple.price).ToList() - : prices.OrderByDescending(tuple => tuple.price).ToList(); - - var assetPair = _assetPairsCache.GetAssetPairByIdOrDefault(order.Instrument); - var externalAssetPair = assetPair?.BasePairId ?? order.Instrument; - foreach (var sourcePrice in prices) + var assetPair = _assetPairsCache.GetAssetPairByIdOrDefault(order.AssetPairId); + var externalAssetPair = assetPair?.BasePairId ?? order.AssetPairId; + + foreach (var (source, price) in prices + .Where(x => string.IsNullOrEmpty(order.ExternalProviderId) || x.source == order.ExternalProviderId)) { var externalOrderModel = new OrderModel(); + + var orderType = order.OrderType == Core.Orders.OrderType.Limit + || order.OrderType == Core.Orders.OrderType.TakeProfit + ? Core.Orders.OrderType.Limit + : Core.Orders.OrderType.Market; + + var isCancellationTrade = order.AdditionalInfo.IsCancellationTrade(out var cancellationTradeExternalId); + + var targetPrice = order.OrderType != Core.Orders.OrderType.Market || isCancellationTrade + ? (double?) order.Price + : (double?) price; try { externalOrderModel = new OrderModel( - order.GetOrderType().ToType(), - OrderType.Market, - TimeInForce.FillOrKill, - (double) Math.Abs(order.Volume), - _dateService.Now(), - sourcePrice.source, - externalAssetPair); - - var executionResult = _exchangeConnectorService.CreateOrderAsync(externalOrderModel).GetAwaiter() - .GetResult(); + tradeType: order.Direction.ToType(), + orderType: orderType.ToType(), + timeInForce: TimeInForce.FillOrKill, + volume: (double) Math.Abs(order.Volume), + dateTime: _dateService.Now(), + exchangeName: source, + instrument: externalAssetPair, + price: targetPrice, + orderId: order.Id, + modality: modality.ToType(), + isCancellationTrade: isCancellationTrade, + cancellationTradeExternalId: cancellationTradeExternalId); + + var cts = new CancellationTokenSource(); + cts.CancelAfter(_marginTradingSettings.GavelTimeout); + + var executionResult = await _exchangeConnectorClient.ExecuteOrder(externalOrderModel, cts.Token); + + if (!executionResult.Success) + { + throw new Exception( + $"External order was not executed. Status: {executionResult.ExecutionStatus}. Failure: {executionResult.FailureType}"); + } var executedPrice = Math.Abs(executionResult.Price) > 0 ? (decimal) executionResult.Price - : sourcePrice.price.Value; + : price.Value; var matchedOrders = new MatchedOrderCollection { new MatchedOrder { - ClientId = order.ClientId, - MarketMakerId = sourcePrice.source, + MarketMakerId = source, MatchedDate = _dateService.Now(), OrderId = executionResult.ExchangeOrderId, - Price = CalculatePriceWithMarkups(assetPair, order.GetOrderType(), executedPrice), - Volume = (decimal) executionResult.Volume + Price = CalculatePriceWithMarkups(assetPair, order.Direction, executedPrice), + Volume = (decimal) executionResult.Volume, + IsExternal = true } }; - - order.OpenExternalProviderId = sourcePrice.source; - order.OpenExternalOrderId = executionResult.ExchangeOrderId; - - _rabbitMqNotifyService.ExternalOrder(executionResult).GetAwaiter().GetResult(); - if (orderProcessed(matchedOrders)) - { - return; - } - else - { - var cancelOrderModel = new OrderModel( - order.GetCloseType().ToType(), - OrderType.Market, - TimeInForce.FillOrKill, - (double) Math.Abs(order.Volume), - _dateService.Now(), - sourcePrice.source, - externalAssetPair); - - var cancelOrderResult = _exchangeConnectorService.CreateOrderAsync(cancelOrderModel).GetAwaiter().GetResult(); - - _rabbitMqNotifyService.ExternalOrder(cancelOrderResult).GetAwaiter().GetResult(); - } + await _rabbitMqNotifyService.ExternalOrder(executionResult); + + _operationsLogService.AddLog("external order executed", order.AccountId, + externalOrderModel.ToJson(), executionResult.ToJson()); - return; + return matchedOrders; } catch (Exception e) { - _log.WriteErrorAsync(nameof(StpMatchingEngine), nameof(MatchMarketOrderForOpen), + var connector = + _marginTradingSettings.ExchangeConnector == ExchangeConnectorType.FakeExchangeConnector + ? "Fake" + : _exchangeConnectorServiceClient.ServiceUrl; + + _log.WriteError( + $"{nameof(StpMatchingEngine)}:{nameof(MatchOrderAsync)}:{connector}", $"Internal order: {order.ToJson()}, External order model: {externalOrderModel.ToJson()}", e); } } - if (string.IsNullOrEmpty(order.OpenExternalProviderId) || - string.IsNullOrEmpty(order.OpenExternalOrderId)) - { - order.Status = OrderStatus.Rejected; - order.RejectReason = OrderRejectReason.NoLiquidity; - order.RejectReasonText = "Error executing external order"; - } - + return new MatchedOrderCollection(); } - //TODO: remove orderProcessed function and make all validations before match - public void MatchMarketOrderForClose(Order order, Func orderProcessed) + public (string externalProviderId, decimal? price) GetBestPriceForOpen(string assetPairId, decimal volume) { - var closePrice = _externalOrderBooksList.GetPriceForClose(order); - - //TODO: rework! - if (!closePrice.HasValue) - { - orderProcessed(new MatchedOrderCollection()); - return; - } - - var closeLp = order.OpenExternalProviderId; - var assetPair = _assetPairsCache.GetAssetPairByIdOrDefault(order.Instrument); - var externalAssetPair = assetPair?.BasePairId ?? order.Instrument; - - var externalOrderModel = new OrderModel(); - - try - { - externalOrderModel = new OrderModel( - order.GetCloseType().ToType(), - OrderType.Market, - TimeInForce.FillOrKill, - (double) Math.Abs(order.Volume), - _dateService.Now(), - closeLp, - externalAssetPair); - - var executionResult = _exchangeConnectorService.CreateOrderAsync(externalOrderModel).GetAwaiter() - .GetResult(); - - var executedPrice = Math.Abs(executionResult.Price) > 0 - ? (decimal) executionResult.Price - : closePrice.Value; - - order.CloseExternalProviderId = closeLp; - order.CloseExternalOrderId = executionResult.ExchangeOrderId; - order.ClosePrice = - CalculatePriceWithMarkups(assetPair, order.GetCloseType(), executedPrice); - - _rabbitMqNotifyService.ExternalOrder(executionResult).GetAwaiter().GetResult(); - - var matchedOrders = new MatchedOrderCollection - { - new MatchedOrder - { - ClientId = order.ClientId, - MarketMakerId = closeLp, - MatchedDate = _dateService.Now(), - OrderId = executionResult.ExchangeOrderId, - Price = order.ClosePrice, - Volume = Math.Abs(order.Volume) - } - }; - - orderProcessed(matchedOrders); - } - catch (Exception e) - { - _log.WriteErrorAsync(nameof(StpMatchingEngine), nameof(MatchMarketOrderForClose), - $"Internal order: {order.ToJson()}, External order model: {externalOrderModel.ToJson()}", e); - } + var prices = _externalOrderbookService.GetOrderedPricesForExecution(assetPairId, volume, true); + return prices?.FirstOrDefault() ?? default; } - public decimal? GetPriceForClose(Order order) + public decimal? GetPriceForClose(string assetPairId, decimal volume, string externalProviderId) { - return _externalOrderBooksList.GetPriceForClose(order); + return _externalOrderbookService.GetPriceForPositionClose(assetPairId, volume, externalProviderId); } //TODO: implement orderbook diff --git a/src/MarginTrading.Backend.Services/Migrations/IMigration.cs b/src/MarginTrading.Backend.Services/Migrations/IMigration.cs index 214eeb960..ac7cc3d42 100644 --- a/src/MarginTrading.Backend.Services/Migrations/IMigration.cs +++ b/src/MarginTrading.Backend.Services/Migrations/IMigration.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; namespace MarginTrading.Backend.Services.Migrations { diff --git a/src/MarginTrading.Backend.Services/Migrations/PendingMarginInstrumentMigration.cs b/src/MarginTrading.Backend.Services/Migrations/PendingMarginInstrumentMigration.cs index 713e2f483..99515c237 100644 --- a/src/MarginTrading.Backend.Services/Migrations/PendingMarginInstrumentMigration.cs +++ b/src/MarginTrading.Backend.Services/Migrations/PendingMarginInstrumentMigration.cs @@ -1,9 +1,11 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using System.Linq; using System.Threading.Tasks; using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orders; using MarginTrading.Backend.Services.Infrastructure; -using MoreLinq; namespace MarginTrading.Backend.Services.Migrations { @@ -31,33 +33,33 @@ public PendingMarginInstrumentMigration( public Task Invoke() { - using (_contextFactory.GetWriteSyncContext($"{nameof(PendingMarginInstrumentMigration)}.{nameof(Invoke)}")) - { - //open orders from cache - var allOrders = _orderCache.GetAll().ToList(); - var pendingOrders = _orderCache.GetPending().Where(x => string.IsNullOrEmpty(x.MarginCalcInstrument)).ToList(); - if (!pendingOrders.Any()) - return Task.CompletedTask; - - foreach (var order in pendingOrders) - { - HandleOrder(order); - } - - //reinit orders cache with modified data - _orderCache.InitOrders(allOrders); - } - +// using (_contextFactory.GetWriteSyncContext($"{nameof(PendingMarginInstrumentMigration)}.{nameof(Invoke)}")) +// { +// //open orders from cache +// var allOrders = _orderCache.GetAllOrders().ToList(); +// var pendingOrders = _orderCache.GetPending().Where(x => string.IsNullOrEmpty(x.MarginCalcInstrument)).ToList(); +// if (!pendingOrders.Any()) +// return Task.CompletedTask; +// +// foreach (var order in pendingOrders) +// { +// HandleOrder(order); +// } +// +// //reinit orders cache with modified data +// _orderCache.InitOrders(allOrders); +// } +// return Task.CompletedTask; } - private void HandleOrder(Order order) + private void HandleOrder(Position order) { - if (_assetPairsCache.TryGetAssetPairQuoteSubst(order.AccountAssetId, order.Instrument, - order.LegalEntity, out var substAssetPair)) - { - order.MarginCalcInstrument = substAssetPair.Id; - } +// if (_assetPairsCache.TryGetAssetPairQuoteSubst(order.AccountAssetId, order.AssetPairId, +// order.LegalEntity, out var substAssetPair)) +// { +// order.MarginCalcInstrument = substAssetPair.Id; +// } } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Modules/AutofacDependencyResolver.cs b/src/MarginTrading.Backend.Services/Modules/AutofacDependencyResolver.cs new file mode 100644 index 000000000..a0e30ad25 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Modules/AutofacDependencyResolver.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using Autofac; +using JetBrains.Annotations; +using Lykke.Cqrs; + +namespace MarginTrading.Backend.Services.Modules +{ + internal class AutofacDependencyResolver : IDependencyResolver + { + private readonly IComponentContext _context; + + public AutofacDependencyResolver([NotNull] IComponentContext kernel) + { + _context = kernel ?? throw new ArgumentNullException(nameof(kernel)); + } + + public object GetService(Type type) + { + return _context.Resolve(type); + } + + public bool HasService(Type type) + { + return _context.IsRegistered(type); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Modules/BaseServicesModule.cs b/src/MarginTrading.Backend.Services/Modules/BaseServicesModule.cs index fb9a195bf..9c64691ee 100644 --- a/src/MarginTrading.Backend.Services/Modules/BaseServicesModule.cs +++ b/src/MarginTrading.Backend.Services/Modules/BaseServicesModule.cs @@ -1,4 +1,7 @@ -using Autofac; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Autofac; using Common.Log; using Lykke.Common; using MarginTrading.Backend.Core; @@ -25,15 +28,6 @@ protected override void Load(ContainerBuilder builder) .As() .SingleInstance(); - builder.Register(ctx => - new SrvAppNotifications(_mtSettings.Jobs.NotificationsHubConnectionString, - _mtSettings.Jobs.NotificationsHubName, _log) - ).SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); - builder.RegisterType() .As() .SingleInstance(); diff --git a/src/MarginTrading.Backend.Services/Modules/CacheModule.cs b/src/MarginTrading.Backend.Services/Modules/CacheModule.cs index 173387b77..a8d6a9631 100644 --- a/src/MarginTrading.Backend.Services/Modules/CacheModule.cs +++ b/src/MarginTrading.Backend.Services/Modules/CacheModule.cs @@ -1,8 +1,10 @@ -using Autofac; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Autofac; using MarginTrading.Backend.Core; using MarginTrading.Backend.Services.AssetPairs; using MarginTrading.Backend.Services.Assets; -using MarginTrading.Backend.Services.Caches; using Rocks.Caching; namespace MarginTrading.Backend.Services.Modules @@ -32,10 +34,6 @@ protected override void Load(ContainerBuilder builder) .AsSelf() .SingleInstance(); - builder.RegisterType() - .As() - .SingleInstance(); - builder.RegisterType() .As() .AsSelf() diff --git a/src/MarginTrading.Backend.Services/Modules/CqrsModule.cs b/src/MarginTrading.Backend.Services/Modules/CqrsModule.cs new file mode 100644 index 000000000..a17f9333c --- /dev/null +++ b/src/MarginTrading.Backend.Services/Modules/CqrsModule.cs @@ -0,0 +1,361 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using Autofac; +using BookKeeper.Client.Workflow.Commands; +using BookKeeper.Client.Workflow.Events; +using Common.Log; +using Lykke.Cqrs; +using Lykke.Cqrs.Configuration; +using Lykke.Cqrs.Configuration.BoundedContext; +using Lykke.Cqrs.Configuration.Routing; +using Lykke.Cqrs.Configuration.Saga; +using Lykke.MarginTrading.OrderBookService.Contracts.Models; +using Lykke.Messaging; +using Lykke.Messaging.Contract; +using Lykke.Messaging.RabbitMq; +using MarginTrading.AccountsManagement.Contracts.Commands; +using MarginTrading.AccountsManagement.Contracts.Events; +using MarginTrading.Backend.Contracts.Events; +using MarginTrading.Backend.Contracts.TradingSchedule; +using MarginTrading.Backend.Contracts.Workflow.Liquidation.Events; +using MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Commands; +using MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Events; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Services.Infrastructure; +using MarginTrading.Backend.Services.Workflow; +using MarginTrading.Backend.Services.Workflow.Liquidation; +using MarginTrading.Backend.Services.Workflow.Liquidation.Commands; +using MarginTrading.Backend.Services.Workflow.Liquidation.Events; +using MarginTrading.SettingsService.Contracts.AssetPair; +using MarginTrading.Backend.Services.Workflow.SpecialLiquidation; +using MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Commands; +using MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Events; + +namespace MarginTrading.Backend.Services.Modules +{ + public class CqrsModule : Module + { + private const string EventsRoute = "events"; + private const string CommandsRoute = "commands"; + private readonly CqrsSettings _settings; + private readonly MarginTradingSettings _marginTradingSettings; + private readonly ILog _log; + private readonly long _defaultRetryDelayMs; + + public CqrsModule(CqrsSettings settings, ILog log, MarginTradingSettings marginTradingSettings) + { + _settings = settings; + _marginTradingSettings = marginTradingSettings; + _log = log; + _defaultRetryDelayMs = (long) _settings.RetryDelay.TotalMilliseconds; + } + + protected override void Load(ContainerBuilder builder) + { + builder.RegisterInstance(_settings.ContextNames).AsSelf().SingleInstance(); + builder.Register(context => new AutofacDependencyResolver(context)).As() + .SingleInstance(); + builder.RegisterType().As() + .PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies) + .SingleInstance(); + builder.RegisterInstance(new CqrsContextNamesSettings()).AsSelf().SingleInstance(); + + var rabbitMqSettings = new RabbitMQ.Client.ConnectionFactory + { + Uri = _settings.ConnectionString + }; + var messagingEngine = new MessagingEngine(_log, new TransportResolver( + new Dictionary + { + { + "RabbitMq", + new TransportInfo(rabbitMqSettings.Endpoint.ToString(), rabbitMqSettings.UserName, + rabbitMqSettings.Password, "None", "RabbitMq") + } + }), new RabbitMqTransportFactory()); + + // Sagas & command handlers + builder.RegisterAssemblyTypes(GetType().Assembly).Where(t => + new [] {"Saga", "CommandsHandler", "Projection"}.Any(ending=> t.Name.EndsWith(ending))).AsSelf(); + + builder.Register(ctx => CreateEngine(ctx, messagingEngine)).As().SingleInstance() + .AutoActivate(); + } + + private CqrsEngine CreateEngine(IComponentContext ctx, IMessagingEngine messagingEngine) + { + var rabbitMqConventionEndpointResolver = + new RabbitMqConventionEndpointResolver("RabbitMq", "messagepack", + environment: _settings.EnvironmentName); + + var registrations = new List + { + Register.DefaultEndpointResolver(rabbitMqConventionEndpointResolver), + RegisterDefaultRouting(), + RegisterSpecialLiquidationSaga(), + RegisterLiquidationSaga(), + RegisterContext(), + }; + + var fakeGavel = RegisterGavelContextIfNeeded(); + if (fakeGavel != null) + registrations.Add(fakeGavel); + + return new CqrsEngine(_log, ctx.Resolve(), messagingEngine, + new DefaultEndpointProvider(), true, registrations.ToArray()); + } + + private IRegistration RegisterGavelContextIfNeeded() + { + if (_marginTradingSettings.ExchangeConnector == ExchangeConnectorType.FakeExchangeConnector) + { + var contextRegistration = Register.BoundedContext(_settings.ContextNames.Gavel) + .FailedCommandRetryDelay(_defaultRetryDelayMs).ProcessingOptions(CommandsRoute).MultiThreaded(8) + .QueueCapacity(1024); + + contextRegistration + .PublishingEvents( + typeof(PriceForSpecialLiquidationCalculatedEvent), + typeof(PriceForSpecialLiquidationCalculationFailedEvent), + typeof(OrderExecutionOrderBookContract) + ).With(EventsRoute); + + return contextRegistration; + } + else + { + return null; + } + } + + private IRegistration RegisterContext() + { + var contextRegistration = Register.BoundedContext(_settings.ContextNames.TradingEngine) + .FailedCommandRetryDelay(_defaultRetryDelayMs).ProcessingOptions(CommandsRoute).MultiThreaded(8) + .QueueCapacity(1024); + + RegisterWithdrawalCommandsHandler(contextRegistration); + RegisterDeleteAccountsCommandsHandler(contextRegistration); + RegisterSpecialLiquidationCommandsHandler(contextRegistration); + RegisterLiquidationCommandsHandler(contextRegistration); + RegisterEodCommandsHandler(contextRegistration); + RegisterAccountsProjection(contextRegistration); + RegisterAssetPairsProjection(contextRegistration); + + contextRegistration.PublishingEvents(typeof(PositionClosedEvent)).With(EventsRoute); + contextRegistration.PublishingEvents(typeof(CompiledScheduleChangedEvent)).With(EventsRoute); + contextRegistration.PublishingEvents(typeof(MarketStateChangedEvent)).With(EventsRoute); + contextRegistration.PublishingEvents(typeof(OvernightMarginParameterChangedEvent)).With(EventsRoute); + contextRegistration.PublishingEvents(typeof(OrderPlacementRejectedEvent)).With(EventsRoute); + + return contextRegistration; + } + + private void RegisterAssetPairsProjection( + ProcessingOptionsDescriptor contextRegistration) + { + contextRegistration.ListeningEvents( + typeof(AssetPairChangedEvent)) + .From(_settings.ContextNames.SettingsService) + .On(EventsRoute) + .WithProjection( + typeof(AssetPairProjection), _settings.ContextNames.SettingsService); + } + + private PublishingCommandsDescriptor RegisterDefaultRouting() + { + return Register.DefaultRouting + .PublishingCommands( + typeof(SuspendAssetPairCommand), + typeof(UnsuspendAssetPairCommand) + ) + .To(_settings.ContextNames.SettingsService) + .With(CommandsRoute) + .PublishingCommands( + typeof(StartLiquidationInternalCommand), + typeof(ResumeLiquidationInternalCommand), + typeof(StartSpecialLiquidationInternalCommand) + ) + .To(_settings.ContextNames.TradingEngine) + .With(CommandsRoute); + } + + private void RegisterAccountsProjection( + ProcessingOptionsDescriptor contextRegistration) + { + contextRegistration.ListeningEvents( + typeof(AccountChangedEvent)) + .From(_settings.ContextNames.AccountsManagement).On(EventsRoute) + .WithProjection( + typeof(AccountsProjection), _settings.ContextNames.AccountsManagement); + } + + private void RegisterWithdrawalCommandsHandler( + ProcessingOptionsDescriptor contextRegistration) + { + contextRegistration.ListeningCommands( + typeof(FreezeAmountForWithdrawalCommand), + typeof(UnfreezeMarginOnFailWithdrawalCommand)) + .On(CommandsRoute) + .WithCommandsHandler() + .PublishingEvents( + typeof(AmountForWithdrawalFrozenEvent), + typeof(AmountForWithdrawalFreezeFailedEvent)) + .With(EventsRoute); + } + + private void RegisterDeleteAccountsCommandsHandler( + ProcessingOptionsDescriptor contextRegistration) + { + contextRegistration.ListeningCommands( + typeof(BlockAccountsForDeletionCommand), + typeof(MtCoreFinishAccountsDeletionCommand)) + .On(CommandsRoute) + .WithCommandsHandler() + .PublishingEvents( + typeof(AccountsBlockedForDeletionEvent), + typeof(MtCoreDeleteAccountsFinishedEvent)) + .With(EventsRoute); + } + + private IRegistration RegisterSpecialLiquidationSaga() + { + var sagaRegistration = RegisterSaga(); + + sagaRegistration + + .PublishingCommands( + typeof(GetPriceForSpecialLiquidationCommand) + ) + .To(_settings.ContextNames.Gavel) + .With(CommandsRoute) + + .ListeningEvents( + typeof(PriceForSpecialLiquidationCalculatedEvent), + typeof(PriceForSpecialLiquidationCalculationFailedEvent) + ) + .From(_settings.ContextNames.Gavel) + .On(EventsRoute) + + .PublishingCommands( + typeof(FailSpecialLiquidationInternalCommand), + typeof(ExecuteSpecialLiquidationOrderCommand), + typeof(ExecuteSpecialLiquidationOrdersInternalCommand), + typeof(GetPriceForSpecialLiquidationTimeoutInternalCommand), + typeof(ResumeLiquidationInternalCommand) + ) + .To(_settings.ContextNames.TradingEngine) + .With(CommandsRoute) + + .ListeningEvents( + typeof(SpecialLiquidationOrderExecutedEvent), + typeof(SpecialLiquidationStartedInternalEvent), + typeof(SpecialLiquidationOrderExecutionFailedEvent), + typeof(SpecialLiquidationFinishedEvent), + typeof(SpecialLiquidationFailedEvent) + ) + .From(_settings.ContextNames.TradingEngine) + .On(EventsRoute); + + return sagaRegistration; + } + + private void RegisterSpecialLiquidationCommandsHandler( + ProcessingOptionsDescriptor contextRegistration) + { + contextRegistration.ListeningCommands( + typeof(StartSpecialLiquidationCommand), + typeof(StartSpecialLiquidationInternalCommand), + typeof(GetPriceForSpecialLiquidationTimeoutInternalCommand), + typeof(ExecuteSpecialLiquidationOrderCommand), + typeof(FailSpecialLiquidationInternalCommand), + typeof(ExecuteSpecialLiquidationOrdersInternalCommand) + ) + .On(CommandsRoute) + .WithCommandsHandler() + .PublishingEvents( + typeof(SpecialLiquidationStartedInternalEvent), + typeof(SpecialLiquidationOrderExecutedEvent), + typeof(SpecialLiquidationOrderExecutionFailedEvent), + typeof(SpecialLiquidationFinishedEvent), + typeof(SpecialLiquidationFailedEvent) + ) + .With(EventsRoute); + } + + private void RegisterEodCommandsHandler( + ProcessingOptionsDescriptor contextRegistration) + { + contextRegistration.ListeningCommands( + typeof(CreateSnapshotCommand) + ) + .On(CommandsRoute) + .WithCommandsHandler() + .PublishingEvents( + typeof(SnapshotCreatedEvent), + typeof(SnapshotCreationFailedEvent) + ) + .With(EventsRoute); + } + + private IRegistration RegisterLiquidationSaga() + { + var sagaRegistration = RegisterSaga(); + + sagaRegistration + + .PublishingCommands( + typeof(FailLiquidationInternalCommand), + typeof(FinishLiquidationInternalCommand), + typeof(LiquidatePositionsInternalCommand), + typeof(StartSpecialLiquidationInternalCommand) + ) + .To(_settings.ContextNames.TradingEngine) + .With(CommandsRoute) + + .ListeningEvents( + typeof(LiquidationFailedEvent), + typeof(LiquidationFinishedEvent), + typeof(LiquidationResumedInternalEvent), + typeof(LiquidationStartedInternalEvent), + typeof(NotEnoughLiquidityInternalEvent), + typeof(PositionsLiquidationFinishedInternalEvent) + ) + .From(_settings.ContextNames.TradingEngine) + .On(EventsRoute); + + return sagaRegistration; + } + + private void RegisterLiquidationCommandsHandler( + ProcessingOptionsDescriptor contextRegistration) + { + contextRegistration.ListeningCommands( + typeof(StartLiquidationInternalCommand), + typeof(FailLiquidationInternalCommand), + typeof(FinishLiquidationInternalCommand), + typeof(LiquidatePositionsInternalCommand), + typeof(ResumeLiquidationInternalCommand) + ) + .On(CommandsRoute) + .WithCommandsHandler() + .PublishingEvents( + typeof(LiquidationFailedEvent), + typeof(LiquidationFinishedEvent), + typeof(LiquidationResumedInternalEvent), + typeof(LiquidationStartedInternalEvent), + typeof(NotEnoughLiquidityInternalEvent), + typeof(PositionsLiquidationFinishedInternalEvent) + ) + .With(EventsRoute); + } + + private ISagaRegistration RegisterSaga() + { + return Register.Saga($"{_settings.ContextNames.TradingEngine}.{typeof(TSaga).Name}"); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Modules/EventModule.cs b/src/MarginTrading.Backend.Services/Modules/EventModule.cs index b562c3fdc..b4e252787 100644 --- a/src/MarginTrading.Backend.Services/Modules/EventModule.cs +++ b/src/MarginTrading.Backend.Services/Modules/EventModule.cs @@ -1,4 +1,7 @@ -using Autofac; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Autofac; using MarginTrading.Backend.Services.Events; namespace MarginTrading.Backend.Services.Modules @@ -10,6 +13,10 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType>() .As>() .SingleInstance(); + + builder.RegisterType>() + .As>() + .SingleInstance(); builder.RegisterType>() .As>() @@ -23,8 +30,8 @@ protected override void Load(ContainerBuilder builder) .As>() .SingleInstance(); - builder.RegisterType>() - .As>() + builder.RegisterType>() + .As>() .SingleInstance(); builder.RegisterType>() @@ -39,12 +46,12 @@ protected override void Load(ContainerBuilder builder) .As>() .SingleInstance(); - builder.RegisterType>() - .As>() + builder.RegisterType>() + .As>() .SingleInstance(); - builder.RegisterType>() - .As>() + builder.RegisterType>() + .As>() .SingleInstance(); builder.RegisterType>() @@ -54,6 +61,10 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType>() .As>() .SingleInstance(); + + builder.RegisterType>() + .As>() + .SingleInstance(); } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Modules/ExternalServicesModule.cs b/src/MarginTrading.Backend.Services/Modules/ExternalServicesModule.cs deleted file mode 100644 index f8f3c40db..000000000 --- a/src/MarginTrading.Backend.Services/Modules/ExternalServicesModule.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using Autofac; -using Autofac.Extensions.DependencyInjection; -using Lykke.Service.Assets.Client; -using Lykke.Service.ClientAccount.Client; -using Lykke.Service.ExchangeConnector.Client; -using Lykke.SettingsReader; -using MarginTrading.Backend.Services.Settings; -using Microsoft.Extensions.DependencyInjection; - -namespace MarginTrading.Backend.Services.Modules -{ - public class ExternalServicesModule : Module - { - private readonly IReloadingManager _settings; - - public ExternalServicesModule(IReloadingManager settings) - { - _settings = settings; - } - - protected override void Load(ContainerBuilder builder) - { - var services = new ServiceCollection(); - - services.RegisterAssetsClient(AssetServiceSettings.Create( - new Uri(_settings.CurrentValue.Assets.ServiceUrl), - _settings.CurrentValue.Assets.CacheExpirationPeriod)); - - builder.RegisterType() - .As() - .WithParameter("settings", _settings.CurrentValue.MtStpExchangeConnectorClient) - .SingleInstance(); - - builder.Populate(services); - - builder.RegisterLykkeServiceClient(_settings.CurrentValue.ClientAccountServiceClient.ServiceUrl); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Modules/ManagersModule.cs b/src/MarginTrading.Backend.Services/Modules/ManagersModule.cs index 8ab31b055..45d57c952 100644 --- a/src/MarginTrading.Backend.Services/Modules/ManagersModule.cs +++ b/src/MarginTrading.Backend.Services/Modules/ManagersModule.cs @@ -1,6 +1,11 @@ -using Autofac; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Autofac; +using MarginTrading.Backend.Core.Services; using MarginTrading.Backend.Services.AssetPairs; using MarginTrading.Backend.Services.Assets; +using MarginTrading.Backend.Services.Caches; using MarginTrading.Backend.Services.Infrastructure; using MarginTrading.Backend.Services.MatchingEngines; using MarginTrading.Backend.Services.Quotes; @@ -25,26 +30,19 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType() .AsSelf() .As() + .As() .SingleInstance(); - builder.RegisterType() - .AsSelf() - .SingleInstance() - .OnActivated(args => args.Instance.Start()); - - builder.RegisterType() - .AsSelf() - .As() - .SingleInstance(); - - builder.RegisterType() + builder.RegisterType() .AsSelf() + .As() .SingleInstance() .OnActivated(args => args.Instance.Start()); builder.RegisterType() .AsSelf() .As() + .As() .SingleInstance(); builder.RegisterType() @@ -56,6 +54,7 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType() .AsSelf() .As() + .As() .SingleInstance(); builder.RegisterType() @@ -67,6 +66,10 @@ protected override void Load(ContainerBuilder builder) .AsSelf() .As() .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); } } } diff --git a/src/MarginTrading.Backend.Services/Modules/ServicesModule.cs b/src/MarginTrading.Backend.Services/Modules/ServicesModule.cs index 355f26e58..fe6f7e9c6 100644 --- a/src/MarginTrading.Backend.Services/Modules/ServicesModule.cs +++ b/src/MarginTrading.Backend.Services/Modules/ServicesModule.cs @@ -1,48 +1,48 @@ -using Autofac; -using Autofac.Core; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Autofac; using Common.Log; using Autofac.Features.Variance; +using Lykke.Common.Chaos; +using Lykke.RabbitMqBroker.Publisher; using Lykke.SettingsReader; using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.MarketMakerFeed; using MarginTrading.Backend.Core.MatchingEngines; using MarginTrading.Backend.Core.Orderbooks; +using MarginTrading.Backend.Core.Services; using MarginTrading.Backend.Core.Settings; -using MarginTrading.Backend.Core.TradingConditions; using MarginTrading.Backend.Services.AssetPairs; using MarginTrading.Backend.Services.Events; using MarginTrading.Backend.Services.EventsConsumers; using MarginTrading.Backend.Services.Infrastructure; using MarginTrading.Backend.Services.MatchingEngines; using MarginTrading.Backend.Services.Quotes; +using MarginTrading.Backend.Services.Scheduling; using MarginTrading.Backend.Services.Services; using MarginTrading.Backend.Services.Stp; using MarginTrading.Backend.Services.TradingConditions; using MarginTrading.Common.RabbitMq; -using MarginTrading.Common.Services.Client; -using MarginTrading.Common.Services.Settings; +using MarginTrading.Common.Services; using MarginTrading.Common.Services.Telemetry; -using MarginTrading.Common.Settings; namespace MarginTrading.Backend.Services.Modules { public class ServicesModule : Module { - private readonly IReloadingManager _riskInformingSettings; - - public ServicesModule(IReloadingManager riskInformingSettings) - { - _riskInformingSettings = riskInformingSettings; - } - protected override void Load(ContainerBuilder builder) { builder.RegisterType() .AsSelf() .As() .As>() + .SingleInstance(); + + builder.RegisterType() + .AsSelf() + .As() .SingleInstance() - .OnActivated(args => args.Instance.Start()); + .OnActivated(args => args.Instance.Start()); builder.RegisterType() .As() @@ -53,14 +53,10 @@ protected override void Load(ContainerBuilder builder) .As() .SingleInstance(); - builder.RegisterType() - .AsSelf() - .As() - .SingleInstance(); - - builder.RegisterType() + builder.RegisterType() .AsSelf() - .As() + .As() + .As() .SingleInstance(); builder.RegisterType() @@ -75,30 +71,26 @@ protected override void Load(ContainerBuilder builder) .As() .SingleInstance(); - builder.RegisterType() - .As() - .SingleInstance(); - + //TODO: rework ME registrations builder.RegisterType() .As() - .WithParameter(TypedParameter.From(MatchingEngineConstants.LykkeVuMm)) + .WithParameter(TypedParameter.From(MatchingEngineConstants.DefaultMm)) .SingleInstance(); builder.RegisterType() .As() - .WithParameter(TypedParameter.From(MatchingEngineConstants.LykkeCyStp)) + .WithParameter(TypedParameter.From(MatchingEngineConstants.DefaultStp)) .SingleInstance(); builder.RegisterType() .As() .As>() + .As>() .SingleInstance(); builder.RegisterType() .As>() - .As>() - .As>() - .As>() + //.As>() .SingleInstance(); builder.RegisterType() @@ -108,17 +100,20 @@ protected override void Load(ContainerBuilder builder) builder.RegisterSource(new ContravariantRegistrationSource()); builder.RegisterType() .As>() - .As>() + .As>() .As>() - .As>() - .As>() + .As>() + .As>() .As>() .As>() .SingleInstance(); builder.RegisterType() - .As>() - .As>() + .As>() + .SingleInstance(); + + builder.RegisterType() + .As>() .SingleInstance(); builder.RegisterType() @@ -129,20 +124,14 @@ protected override void Load(ContainerBuilder builder) .AsSelf() .SingleInstance(); - builder.RegisterType() - .AsSelf() + builder.RegisterType() + .As() .SingleInstance(); builder.RegisterType() .AsSelf() .SingleInstance(); - builder.RegisterType() - .As>() - .As() - .AsSelf() - .SingleInstance(); - builder.RegisterType() .As() .SingleInstance(); @@ -170,38 +159,37 @@ protected override void Load(ContainerBuilder builder) builder.Register(c => { - var settings = c.Resolve>(); - return new RabbitMqService(c.Resolve(), c.Resolve(), - settings.Nested(s => s.Db.StateConnString), settings.CurrentValue.Env); + var settings = c.Resolve>(); + return new RabbitMqService(c.Resolve(), + c.Resolve(), + settings.Nested(s => s.Db.StateConnString), + settings.CurrentValue.Env, + c.Resolve()); }) .As() .SingleInstance(); - builder.RegisterType() - .As() - .As() + builder.RegisterType() + .As() .SingleInstance(); builder.RegisterType() .As() .SingleInstance(); - builder.RegisterInstance(_riskInformingSettings) - .As>() + builder.RegisterType() + .As() .SingleInstance(); - builder.RegisterType() - .As() - .As() + builder.RegisterType() + .As() .SingleInstance(); - builder.RegisterType() - .As() - .SingleInstance() - .OnActivated(args => args.Instance.Start()); + builder.RegisterType() + .As() + .SingleInstance(); - builder.RegisterType() - .As() + builder.RegisterType() .SingleInstance(); } } diff --git a/src/MarginTrading.Backend.Services/NotificationSenderBase.cs b/src/MarginTrading.Backend.Services/NotificationSenderBase.cs deleted file mode 100644 index 50ed27b44..000000000 --- a/src/MarginTrading.Backend.Services/NotificationSenderBase.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; -using System.Threading.Tasks; -using Lykke.Service.ClientAccount.Client; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Mappers; -using MarginTrading.Backend.Core.Messages; -using MarginTrading.Backend.Core.Notifications; -using MarginTrading.Backend.Services.Assets; -using MarginTrading.Backend.Services.Notifications; -using MarginTrading.Common.Services.Client; -using MarginTrading.Common.Settings; -using MarginTrading.Contract.BackendContracts; - -namespace MarginTrading.Backend.Services -{ - public class NotificationSenderBase - { - private readonly IAppNotifications _appNotifications; - private readonly IClientAccountService _clientAccountService; - private readonly IAssetsCache _assetsCache; - private readonly IAssetPairsCache _assetPairsCache; - - public NotificationSenderBase( - IAppNotifications appNotifications, - IClientAccountService clientAccountService, - IAssetsCache assetsCache, - IAssetPairsCache assetPairsCache) - { - _appNotifications = appNotifications; - _clientAccountService = clientAccountService; - _assetsCache = assetsCache; - _assetPairsCache = assetPairsCache; - } - - protected async Task SendOrderChangedNotification(string clientId, IOrder order) - { - var notificationType = order.Status == OrderStatus.Closed - ? NotificationType.PositionClosed - : NotificationType.PositionOpened; - - await SendNotification(clientId, notificationType, GetPushMessage(order), - order.ToBackendHistoryContract()); - } - - protected async Task SendMarginEventNotification(string clientId, string message) - { - await SendNotification(clientId, NotificationType.MarginCall, message, null); - } - - private async Task SendNotification(string clientId, NotificationType notificationType, string message, - OrderHistoryBackendContract order) - { - if (await _clientAccountService.IsPushEnabled(clientId)) - { - var notificationId = await _clientAccountService.GetNotificationId(clientId); - - await _appNotifications.SendNotification(notificationId, notificationType, message, order); - } - } - - private string GetPushMessage(IOrder order) - { - var message = string.Empty; - var volume = Math.Abs(order.Volume); - var type = order.GetOrderType() == OrderDirection.Buy ? "Long" : "Short"; - var assetPair = _assetPairsCache.GetAssetPairByIdOrDefault(order.Instrument); - var instrumentName = assetPair?.Name ?? order.Instrument; - - switch (order.Status) - { - case OrderStatus.WaitingForExecution: - message = string.Format(MtMessages.Notifications_PendingOrderPlaced, type, instrumentName, volume, Math.Round(order.ExpectedOpenPrice ?? 0, order.AssetAccuracy)); - break; - case OrderStatus.Active: - message = order.ExpectedOpenPrice.HasValue - ? string.Format(MtMessages.Notifications_PendingOrderTriggered, type, instrumentName, volume, - Math.Round(order.OpenPrice, order.AssetAccuracy)) - : string.Format(MtMessages.Notifications_OrderPlaced, type, instrumentName, volume, - Math.Round(order.OpenPrice, order.AssetAccuracy)); - break; - case OrderStatus.Closed: - var reason = string.Empty; - - switch (order.CloseReason) - { - case OrderCloseReason.StopLoss: - reason = MtMessages.Notifications_WithStopLossPhrase; - break; - case OrderCloseReason.TakeProfit: - reason = MtMessages.Notifications_WithTakeProfitPhrase; - break; - } - - var accuracy = _assetsCache.GetAssetAccuracy(order.AccountAssetId); - - message = order.ExpectedOpenPrice.HasValue && - (order.CloseReason == OrderCloseReason.Canceled || - order.CloseReason == OrderCloseReason.CanceledBySystem || - order.CloseReason == OrderCloseReason.CanceledByBroker) - ? string.Format(MtMessages.Notifications_PendingOrderCanceled, type, instrumentName, volume) - : string.Format(MtMessages.Notifications_OrderClosed, type, instrumentName, volume, reason, - order.GetTotalFpl().ToString($"F{accuracy}"), - order.AccountAssetId); - break; - case OrderStatus.Rejected: - break; - case OrderStatus.Closing: - break; - default: - throw new ArgumentOutOfRangeException(); - } - - return message; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Notifications/AndroidNotification.cs b/src/MarginTrading.Backend.Services/Notifications/AndroidNotification.cs index f869e6bdb..c459c4356 100644 --- a/src/MarginTrading.Backend.Services/Notifications/AndroidNotification.cs +++ b/src/MarginTrading.Backend.Services/Notifications/AndroidNotification.cs @@ -1,4 +1,7 @@ -using Newtonsoft.Json; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; namespace MarginTrading.Backend.Services.Notifications { diff --git a/src/MarginTrading.Backend.Services/Notifications/AndroidPositionFields.cs b/src/MarginTrading.Backend.Services/Notifications/AndroidPositionFields.cs index ecf679670..4302c68e3 100644 --- a/src/MarginTrading.Backend.Services/Notifications/AndroidPositionFields.cs +++ b/src/MarginTrading.Backend.Services/Notifications/AndroidPositionFields.cs @@ -1,4 +1,7 @@ -using MarginTrading.Contract.BackendContracts; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Contract.BackendContracts; using Newtonsoft.Json; namespace MarginTrading.Backend.Services.Notifications diff --git a/src/MarginTrading.Backend.Services/Notifications/CustomNotificationHubClient.cs b/src/MarginTrading.Backend.Services/Notifications/CustomNotificationHubClient.cs index 1dc174550..d8f3d237e 100644 --- a/src/MarginTrading.Backend.Services/Notifications/CustomNotificationHubClient.cs +++ b/src/MarginTrading.Backend.Services/Notifications/CustomNotificationHubClient.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.IO; using System.Net; diff --git a/src/MarginTrading.Backend.Services/Notifications/IAppNotifications.cs b/src/MarginTrading.Backend.Services/Notifications/IAppNotifications.cs deleted file mode 100644 index 6419c666e..000000000 --- a/src/MarginTrading.Backend.Services/Notifications/IAppNotifications.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Threading.Tasks; -using MarginTrading.Backend.Core.Notifications; -using MarginTrading.Contract.BackendContracts; - -namespace MarginTrading.Backend.Services.Notifications -{ - public interface IAppNotifications - { - Task SendNotification(string notificationId, NotificationType notificationType, string message, - OrderHistoryBackendContract order); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Notifications/IRabbitMqNotifyService.cs b/src/MarginTrading.Backend.Services/Notifications/IRabbitMqNotifyService.cs index c9ddb6ea4..ca639a61d 100644 --- a/src/MarginTrading.Backend.Services/Notifications/IRabbitMqNotifyService.cs +++ b/src/MarginTrading.Backend.Services/Notifications/IRabbitMqNotifyService.cs @@ -1,28 +1,24 @@ -using System.Threading.Tasks; -using Lykke.Service.ExchangeConnector.Client.Models; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using MarginTrading.Backend.Contracts.Events; +using MarginTrading.Backend.Contracts.ExchangeConnector; using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; using MarginTrading.Contract.RabbitMqMessageModels; namespace MarginTrading.Backend.Services.Notifications { public interface IRabbitMqNotifyService { - Task AccountHistory(string transactionId, string accountId, string clientId, decimal amount, decimal balance, - decimal withdrawTransferLimit, AccountHistoryType type, string comment = null, string eventSourceId = null, - string auditLog = null); - Task OrderHistory(IOrder order, OrderUpdateType orderUpdateType); - Task OrderReject(IOrder order); - Task OrderBookPrice(InstrumentBidAskPair quote); - Task OrderChanged(IOrder order); - Task AccountUpdated(IMarginTradingAccount account); - Task AccountStopout(string clientId, string accountId, int positionsCount, decimal totalPnl); - Task UserUpdates(bool updateAccountAssets, bool updateAccounts, string[] clientIds); - void Stop(); - Task AccountCreated(IMarginTradingAccount account); - Task AccountDeleted(IMarginTradingAccount account); - Task AccountMarginEvent(AccountMarginEventMessage eventMessage); + Task OrderHistory(Order order, OrderUpdateType orderUpdateType, string activitiesMetadata = null); + Task OrderBookPrice(InstrumentBidAskPair quote, bool isEod); + Task AccountMarginEvent(MarginEventMessage eventMessage); Task UpdateAccountStats(AccountStatsUpdateMessage message); Task NewTrade(TradeContract trade); Task ExternalOrder(ExecutionReport trade); + Task PositionHistory(PositionHistoryEvent historyEvent); } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Notifications/IosNotification.cs b/src/MarginTrading.Backend.Services/Notifications/IosNotification.cs index 62b40f549..4a5221e96 100644 --- a/src/MarginTrading.Backend.Services/Notifications/IosNotification.cs +++ b/src/MarginTrading.Backend.Services/Notifications/IosNotification.cs @@ -1,4 +1,7 @@ -using Newtonsoft.Json; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; namespace MarginTrading.Backend.Services.Notifications { diff --git a/src/MarginTrading.Backend.Services/Notifications/IosPositionFields.cs b/src/MarginTrading.Backend.Services/Notifications/IosPositionFields.cs index 260fb5634..d8bf9cc0b 100644 --- a/src/MarginTrading.Backend.Services/Notifications/IosPositionFields.cs +++ b/src/MarginTrading.Backend.Services/Notifications/IosPositionFields.cs @@ -1,4 +1,7 @@ -using MarginTrading.Backend.Core.Notifications; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Core.Notifications; using MarginTrading.Contract.BackendContracts; using Newtonsoft.Json; diff --git a/src/MarginTrading.Backend.Services/Notifications/RabbitMqNotifyService.cs b/src/MarginTrading.Backend.Services/Notifications/RabbitMqNotifyService.cs index f2888127c..3bba8d350 100644 --- a/src/MarginTrading.Backend.Services/Notifications/RabbitMqNotifyService.cs +++ b/src/MarginTrading.Backend.Services/Notifications/RabbitMqNotifyService.cs @@ -1,116 +1,81 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; using System.Threading.Tasks; -using Autofac.Features.Indexed; using Common; using Common.Log; -using Lykke.Service.ExchangeConnector.Client.Models; +using MarginTrading.Backend.Contracts.Events; +using MarginTrading.Backend.Contracts.ExchangeConnector; using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.Mappers; +using MarginTrading.Backend.Core.Orders; using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Core.Trading; +using MarginTrading.Common.Extensions; +using MarginTrading.Common.Services; using MarginTrading.Contract.RabbitMqMessageModels; +using MarginTrading.Backend.Services.Mappers; +using MarginTrading.Common.RabbitMq; namespace MarginTrading.Backend.Services.Notifications { public class RabbitMqNotifyService : IRabbitMqNotifyService { - private readonly MarginSettings _settings; - private readonly IIndex> _publishers; + private readonly IDateService _dateService; + private readonly MarginTradingSettings _settings; + private readonly Dictionary> _publishers; private readonly ILog _log; + private readonly IOrderReader _orderReader; - public RabbitMqNotifyService( - MarginSettings settings, - IIndex> publishers, - ILog log) + public RabbitMqNotifyService(IDateService dateService, + MarginTradingSettings settings, + ILog log, + IOrderReader orderReader, + IRabbitMqService rabbitMqService) { + _dateService = dateService; _settings = settings; - _publishers = publishers; _log = log; - } - - public Task AccountHistory(string transactionId, string accountId, string clientId, decimal amount, decimal balance, - decimal withdrawTransferLimit, AccountHistoryType type, string comment = null, string eventSourceId = null, - string auditLog = null) - { - var record = new MarginTradingAccountHistory - { - Id = transactionId, - AccountId = accountId, - ClientId = clientId, - Type = type, - Amount = amount, - Balance = balance, - WithdrawTransferLimit = withdrawTransferLimit, - Date = DateTime.UtcNow, - Comment = comment, - OrderId = type == AccountHistoryType.OrderClosed ? eventSourceId : null, - AuditLog = auditLog - }; + _orderReader = orderReader; + _publishers = new Dictionary>(); - return TryProduceMessageAsync(_settings.RabbitMqQueues.AccountHistory.ExchangeName, record.ToBackendContract()); + RegisterPublishers(rabbitMqService); } - public Task OrderHistory(IOrder order, OrderUpdateType orderUpdateType) + public Task OrderHistory(Order order, OrderUpdateType orderUpdateType, string activitiesMetadata = null) { - return TryProduceMessageAsync(_settings.RabbitMqQueues.OrderHistory.ExchangeName, order.ToFullContract(orderUpdateType)); - } - - public Task OrderReject(IOrder order) - { - return TryProduceMessageAsync(_settings.RabbitMqQueues.OrderRejected.ExchangeName, order.ToFullContract(OrderUpdateType.Reject)); - } + var relatedOrders = new List(); - public Task OrderBookPrice(InstrumentBidAskPair quote) - { - return TryProduceMessageAsync(_settings.RabbitMqQueues.OrderbookPrices.ExchangeName, quote.ToRabbitMqContract()); - } - - public Task OrderChanged(IOrder order) - { - var message = order.ToBaseContract(); - return TryProduceMessageAsync(_settings.RabbitMqQueues.OrderChanged.ExchangeName, message); - } - - public Task AccountUpdated(IMarginTradingAccount account) - { - return AccountChanged(account, AccountEventTypeEnum.Updated); - } - - public Task AccountDeleted(IMarginTradingAccount account) - { - return AccountChanged(account, AccountEventTypeEnum.Deleted); - } - - public Task AccountCreated(IMarginTradingAccount account) - { - return AccountChanged(account, AccountEventTypeEnum.Created); - } - - private Task AccountChanged(IMarginTradingAccount account, AccountEventTypeEnum eventType) - { - var message = new AccountChangedMessage + foreach (var relatedOrderInfo in order.RelatedOrders) { - Account = account.ToFullBackendContract(_settings.IsLive), - EventType = eventType, + if (_orderReader.TryGetOrderById(relatedOrderInfo.Id, out var relatedOrder)) + { + relatedOrders.Add(relatedOrder); + } + } + + var historyEvent = new OrderHistoryEvent + { + OrderSnapshot = order.ConvertToContract(relatedOrders), + Timestamp = _dateService.Now(), + Type = orderUpdateType.ToType(), + ActivitiesMetadata = activitiesMetadata }; - - return TryProduceMessageAsync(_settings.RabbitMqQueues.AccountChanged.ExchangeName, message); + + return TryProduceMessageAsync(_settings.RabbitMqQueues.OrderHistory.ExchangeName, historyEvent); } - public Task AccountMarginEvent(AccountMarginEventMessage eventMessage) + public Task OrderBookPrice(InstrumentBidAskPair quote, bool isEod) { - return TryProduceMessageAsync(_settings.RabbitMqQueues.AccountMarginEvents.ExchangeName, eventMessage); + return TryProduceMessageAsync(_settings.RabbitMqQueues.OrderbookPrices.ExchangeName, + quote.ToRabbitMqContract(isEod)); } - public Task AccountStopout(string clientId, string accountId, int positionsCount, decimal totalPnl) + public Task AccountMarginEvent(MarginEventMessage eventMessage) { - var message = new { clientId, accountId, positionsCount, totalPnl }; - return TryProduceMessageAsync(_settings.RabbitMqQueues.AccountStopout.ExchangeName, message); - } - - public Task UserUpdates(bool updateAccountAssets, bool updateAccounts, string[] clientIds) - { - var message = new { updateAccountAssetPairs = updateAccountAssets, UpdateAccounts = updateAccounts, clientIds }; - return TryProduceMessageAsync(_settings.RabbitMqQueues.UserUpdates.ExchangeName, message); + return TryProduceMessageAsync(_settings.RabbitMqQueues.AccountMarginEvents.ExchangeName, eventMessage); } public Task UpdateAccountStats(AccountStatsUpdateMessage message) @@ -122,12 +87,17 @@ public Task NewTrade(TradeContract trade) { return TryProduceMessageAsync(_settings.RabbitMqQueues.Trades.ExchangeName, trade); } - + public Task ExternalOrder(ExecutionReport trade) { return TryProduceMessageAsync(_settings.RabbitMqQueues.ExternalOrder.ExchangeName, trade); } + public Task PositionHistory(PositionHistoryEvent historyEvent) + { + return TryProduceMessageAsync(_settings.RabbitMqQueues.PositionHistory.ExchangeName, historyEvent); + } + private async Task TryProduceMessageAsync(string exchangeName, object message) { string messageStr = null; @@ -144,19 +114,30 @@ private async Task TryProduceMessageAsync(string exchangeName, object message) } } - public void Stop() + private void RegisterPublishers(IRabbitMqService rabbitMqService) { - ((IStopable)_publishers[_settings.RabbitMqQueues.AccountHistory.ExchangeName]).Stop(); - ((IStopable)_publishers[_settings.RabbitMqQueues.OrderHistory.ExchangeName]).Stop(); - ((IStopable)_publishers[_settings.RabbitMqQueues.OrderRejected.ExchangeName]).Stop(); - ((IStopable)_publishers[_settings.RabbitMqQueues.OrderbookPrices.ExchangeName]).Stop(); - ((IStopable)_publishers[_settings.RabbitMqQueues.AccountStopout.ExchangeName]).Stop(); - ((IStopable)_publishers[_settings.RabbitMqQueues.AccountChanged.ExchangeName]).Stop(); - ((IStopable)_publishers[_settings.RabbitMqQueues.UserUpdates.ExchangeName]).Stop(); - ((IStopable)_publishers[_settings.RabbitMqQueues.AccountMarginEvents.ExchangeName]).Stop(); - ((IStopable)_publishers[_settings.RabbitMqQueues.AccountStats.ExchangeName]).Stop(); - ((IStopable)_publishers[_settings.RabbitMqQueues.Trades.ExchangeName]).Stop(); - ((IStopable)_publishers[_settings.RabbitMqQueues.ExternalOrder.ExchangeName]).Stop(); + var publishExchanges = new List + { + _settings.RabbitMqQueues.OrderHistory.ExchangeName, + _settings.RabbitMqQueues.OrderbookPrices.ExchangeName, + _settings.RabbitMqQueues.AccountMarginEvents.ExchangeName, + _settings.RabbitMqQueues.AccountStats.ExchangeName, + _settings.RabbitMqQueues.Trades.ExchangeName, + _settings.RabbitMqQueues.PositionHistory.ExchangeName, + _settings.RabbitMqQueues.ExternalOrder.ExchangeName, + }; + + var bytesSerializer = new BytesStringSerializer(); + + foreach (var exchangeName in publishExchanges) + { + var settings = new RabbitMqSettings + { + ConnectionString = _settings.MtRabbitMqConnString, ExchangeName + = exchangeName + }; + _publishers[exchangeName] = rabbitMqService.GetProducer(settings, bytesSerializer); + } } } -} +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Notifications/SrvAppNotifications.cs b/src/MarginTrading.Backend.Services/Notifications/SrvAppNotifications.cs deleted file mode 100644 index bf6919765..000000000 --- a/src/MarginTrading.Backend.Services/Notifications/SrvAppNotifications.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Threading.Tasks; -using Common; -using Common.Log; -using MarginTrading.Backend.Core.Notifications; -using MarginTrading.Contract.BackendContracts; - -namespace MarginTrading.Backend.Services.Notifications -{ - public class SrvAppNotifications : IAppNotifications - { - private readonly string _connectionString; - private readonly string _hubName; - private readonly ILog _log; - - public SrvAppNotifications(string connectionString, string hubName, ILog log) - { - _connectionString = connectionString; - _hubName = hubName; - _log = log; - } - - public async Task SendNotification(string notificationsId, NotificationType notificationType, string message, OrderHistoryBackendContract order = null) - { - if (string.IsNullOrEmpty(notificationsId)) - { - _log.WriteWarning(nameof(SendNotification), notificationType, "Notification id is empty"); - return; - } - - await SendIosNotificationAsync(notificationsId, notificationType, message, order); - await SendAndroidNotificationAsync(notificationsId, notificationType, message, order); - } - - private async Task SendIosNotificationAsync(string notificationsId, NotificationType notificationType, - string message, OrderHistoryBackendContract order = null) - { - var apnsMessage = new IosNotification - { - Aps = new IosPositionFields - { - Alert = message, - Type = notificationType, - Order = order - } - }; - - var payload = apnsMessage.ToJson(ignoreNulls: true); - - try - { - var hub = CustomNotificationHubClient.CreateClientFromConnectionString(_connectionString, _hubName); - - await hub.SendAppleNativeNotificationAsync(payload, new[] {notificationsId}); - } - catch (Exception e) - { - _log.WriteError(nameof(SendIosNotificationAsync), payload, e); - } - - - } - - private async Task SendAndroidNotificationAsync(string notificationsId, NotificationType notificationType, string message, OrderHistoryBackendContract order = null) - { - var gcmMessage = new AndroidNotification - { - Data = new AndroidPositionFields - { - Entity = EventsAndEntities.GetEntity(notificationType), - Event = EventsAndEntities.GetEvent(notificationType), - Order = order, - Message = message - } - }; - - var payload = gcmMessage.ToJson(ignoreNulls: true); - - try - { - var hub = CustomNotificationHubClient.CreateClientFromConnectionString(_connectionString, _hubName); - - await hub.SendGcmNativeNotificationAsync(payload, new[] {notificationsId}); - } - catch (Exception e) - { - _log.WriteError(nameof(SendAndroidNotificationAsync), payload, e); - } - - } - } -} diff --git a/src/MarginTrading.Backend.Services/Properties/AssemblyInfo.cs b/src/MarginTrading.Backend.Services/Properties/AssemblyInfo.cs index 4e04551ae..38a4c310c 100644 --- a/src/MarginTrading.Backend.Services/Properties/AssemblyInfo.cs +++ b/src/MarginTrading.Backend.Services/Properties/AssemblyInfo.cs @@ -1,5 +1,7 @@ -using System.Reflection; -using System.Runtime.CompilerServices; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Reflection; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/src/MarginTrading.Backend.Services/Quotes/FxRateCacheService.cs b/src/MarginTrading.Backend.Services/Quotes/FxRateCacheService.cs new file mode 100644 index 000000000..9bb8f4623 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Quotes/FxRateCacheService.cs @@ -0,0 +1,249 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Common; +using Common.Log; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Exceptions; +using MarginTrading.Backend.Core.Messages; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Services.AssetPairs; +using MarginTrading.Backend.Services.Events; +using MarginTrading.Backend.Services.Stp; +using MarginTrading.Common.Extensions; +using MarginTrading.OrderbookAggregator.Contracts.Messages; + +namespace MarginTrading.Backend.Services.Quotes +{ + public class FxRateCacheService : TimerPeriod, IFxRateCacheService + { + private readonly ILog _log; + private readonly IMarginTradingBlobRepository _blobRepository; + private readonly IEventChannel _fxBestPriceChangeEventChannel; + private readonly MarginTradingSettings _marginTradingSettings; + private readonly IAssetPairDayOffService _assetPairDayOffService; + private Dictionary _quotes; + private readonly ReaderWriterLockSlim _lockSlim = new ReaderWriterLockSlim(); + private const string BlobName = "FxRates"; + + public FxRateCacheService(ILog log, + IMarginTradingBlobRepository blobRepository, + IEventChannel fxBestPriceChangeEventChannel, + MarginTradingSettings marginTradingSettings, + IAssetPairDayOffService assetPairDayOffService) + : base(nameof(FxRateCacheService), marginTradingSettings.BlobPersistence.FxRatesDumpPeriodMilliseconds, log) + { + _log = log; + _blobRepository = blobRepository; + _fxBestPriceChangeEventChannel = fxBestPriceChangeEventChannel; + _marginTradingSettings = marginTradingSettings; + _assetPairDayOffService = assetPairDayOffService; + _quotes = new Dictionary(); + } + + public InstrumentBidAskPair GetQuote(string instrument) + { + _lockSlim.EnterReadLock(); + try + { + if (!_quotes.TryGetValue(instrument, out var quote)) + throw new FxRateNotFoundException(instrument, string.Format(MtMessages.FxRateNotFound, instrument)); + + return quote; + } + finally + { + _lockSlim.ExitReadLock(); + } + } + + public Dictionary GetAllQuotes() + { + _lockSlim.EnterReadLock(); + try + { + return _quotes.ToDictionary(x => x.Key, y => y.Value); + } + finally + { + _lockSlim.ExitReadLock(); + } + } + + public Task SetQuote(ExternalExchangeOrderbookMessage orderBookMessage) + { + var isEodOrderbook = orderBookMessage.ExchangeName == ExternalOrderbookService.EodExternalExchange; + + if (_marginTradingSettings.OrderbookValidation.ValidateInstrumentStatusForEodFx && isEodOrderbook || + _marginTradingSettings.OrderbookValidation.ValidateInstrumentStatusForTradingFx && !isEodOrderbook) + { + var isDayOff = _assetPairDayOffService.IsDayOff(orderBookMessage.AssetPairId); + + // we should process normal orderbook only if asset is currently tradable + if (_marginTradingSettings.OrderbookValidation.ValidateInstrumentStatusForTradingFx && isDayOff && !isEodOrderbook) + { + return Task.CompletedTask; + } + + // and process EOD orderbook only if asset is currently not tradable + if (_marginTradingSettings.OrderbookValidation.ValidateInstrumentStatusForEodFx && !isDayOff && isEodOrderbook) + { + _log.WriteWarning("EOD FX quotes processing", "", + $"EOD FX quote for {orderBookMessage.AssetPairId} is skipped, because instrument is within trading hours"); + + return Task.CompletedTask; + } + } + + var bidAskPair = CreatePair(orderBookMessage); + + if (bidAskPair == null) + { + return Task.CompletedTask; + } + + SetQuote(bidAskPair); + + _fxBestPriceChangeEventChannel.SendEvent(this, new FxBestPriceChangeEventArgs(bidAskPair)); + + return Task.CompletedTask; + } + + public void SetQuote(InstrumentBidAskPair bidAskPair) + { + _lockSlim.EnterWriteLock(); + try + { + + if (bidAskPair == null) + { + return; + } + + if (_quotes.ContainsKey(bidAskPair.Instrument)) + { + _quotes[bidAskPair.Instrument] = bidAskPair; + } + else + { + _quotes.Add(bidAskPair.Instrument, bidAskPair); + } + } + finally + { + _lockSlim.ExitWriteLock(); + } + } + + public void RemoveQuote(string assetPairId) + { + _lockSlim.EnterWriteLock(); + try + { + if (_quotes.ContainsKey(assetPairId)) + _quotes.Remove(assetPairId); + else + throw new QuoteNotFoundException(assetPairId, string.Format(MtMessages.QuoteNotFound, assetPairId)); + } + finally + { + _lockSlim.ExitWriteLock(); + } + } + + private InstrumentBidAskPair CreatePair(ExternalExchangeOrderbookMessage message) + { + if (!ValidateOrderbook(message)) + { + return null; + } + + var ask = GetBestPrice(true, message.Asks); + var bid = GetBestPrice(false, message.Bids); + + return ask == null || bid == null + ? null + : new InstrumentBidAskPair + { + Instrument = message.AssetPairId, + Date = message.Timestamp, + Ask = ask.Value, + Bid = bid.Value + }; + } + + private decimal? GetBestPrice(bool isBuy, IReadOnlyCollection prices) + { + if (!prices.Any()) + return null; + return isBuy + ? prices.Min(x => x.Price) + : prices.Max(x => x.Price); + } + + private bool ValidateOrderbook(ExternalExchangeOrderbookMessage orderbook) + { + try + { + orderbook.AssetPairId.RequiredNotNullOrWhiteSpace("orderbook.AssetPairId"); + orderbook.ExchangeName.RequiredNotNullOrWhiteSpace("orderbook.ExchangeName"); + orderbook.RequiredNotNull(nameof(orderbook)); + + orderbook.Bids.RequiredNotNullOrEmpty("orderbook.Bids"); + orderbook.Bids.RemoveAll(e => e == null || e.Price <= 0 || e.Volume == 0); + orderbook.Bids.RequiredNotNullOrEmptyEnumerable("orderbook.Bids"); + + orderbook.Asks.RequiredNotNullOrEmpty("orderbook.Asks"); + orderbook.Asks.RemoveAll(e => e == null || e.Price <= 0 || e.Volume == 0); + orderbook.Asks.RequiredNotNullOrEmptyEnumerable("orderbook.Asks"); + + return true; + } + catch (Exception e) + { + _log.WriteError(nameof(ExternalExchangeOrderbookMessage), orderbook.ToJson(), e); + return false; + } + } + + public override void Start() + { + _quotes = + _blobRepository + .Read>(LykkeConstants.StateBlobContainer, BlobName) + ?.ToDictionary(d => d.Key, d => d.Value) ?? + new Dictionary(); + + base.Start(); + } + + public override Task Execute() + { + return DumpToRepository(); + } + + public override void Stop() + { + DumpToRepository().Wait(); + base.Stop(); + } + + private async Task DumpToRepository() + { + try + { + await _blobRepository.WriteAsync(LykkeConstants.StateBlobContainer, BlobName, GetAllQuotes()); + } + catch (Exception ex) + { + await _log.WriteErrorAsync(nameof(FxRateCacheService), "Save fx rates", "", ex); + } + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Quotes/MicrographCacheService.cs b/src/MarginTrading.Backend.Services/Quotes/MicrographCacheService.cs deleted file mode 100644 index 7a3fd4ba8..000000000 --- a/src/MarginTrading.Backend.Services/Quotes/MicrographCacheService.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Services.Events; - -namespace MarginTrading.Backend.Services.Quotes -{ - public sealed class MicrographCacheService : IMicrographCacheService, - IEventConsumer - { - private Dictionary> _graphQueue; - private readonly Dictionary _lastPrices; - private const int GraphPointsCount = 150; - private static readonly object GraphQueueLock = new object(); - - public MicrographCacheService() - { - _lastPrices = new Dictionary(); - _graphQueue = new Dictionary>(); - } - - public Dictionary> GetGraphData() - { - lock (GraphQueueLock) - { - var copy = new Dictionary>(); - - foreach (var pair in _graphQueue) - { - copy.Add(pair.Key, new List()); - - foreach (var bidAsk in pair.Value) - { - copy[pair.Key].Add(new GraphBidAskPair - { - Ask = bidAsk.Ask, - Bid = bidAsk.Bid, - Date = bidAsk.Date - }); - } - } - - return copy; - } - } - - - internal void InitCache(Dictionary> graphData) - { - lock (GraphQueueLock) - { - _graphQueue = graphData; - } - } - - int IEventConsumer.ConsumerRank => 100; - void IEventConsumer.ConsumeEvent(object sender, BestPriceChangeEventArgs ea) - { - var bidAskPair = ea.BidAskPair; - - if (!_lastPrices.ContainsKey(bidAskPair.Instrument)) - { - _lastPrices.Add(bidAskPair.Instrument, bidAskPair); - } - else - { - _lastPrices[bidAskPair.Instrument] = bidAskPair; - } - - lock (GraphQueueLock) - { - if (!_graphQueue.ContainsKey(bidAskPair.Instrument)) - { - _graphQueue.Add(bidAskPair.Instrument, new List()); - } - - _graphQueue[bidAskPair.Instrument].Add(new GraphBidAskPair - { - Bid = bidAskPair.Bid, - Ask = bidAskPair.Ask, - Date = DateTime.UtcNow - }); - - if (_graphQueue[bidAskPair.Instrument].Count > GraphPointsCount) - { - _graphQueue[bidAskPair.Instrument] = _graphQueue[bidAskPair.Instrument] - .GetRange(1, _graphQueue[bidAskPair.Instrument].Count - 1); - } - } - } - } -} diff --git a/src/MarginTrading.Backend.Services/Quotes/MicrographManager.cs b/src/MarginTrading.Backend.Services/Quotes/MicrographManager.cs deleted file mode 100644 index 95b9732c1..000000000 --- a/src/MarginTrading.Backend.Services/Quotes/MicrographManager.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Common; -using Common.Log; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Services.TradingConditions; - -namespace MarginTrading.Backend.Services.Quotes -{ - public class MicrographManager : TimerPeriod - { - private readonly MicrographCacheService _micrographCacheService; - private readonly IMarginTradingBlobRepository _blobRepository; - - public MicrographManager( - MicrographCacheService micrographCacheService, - IMarginTradingBlobRepository blobRepository, - ILog log) : base(nameof(MicrographManager), 60000, log) - { - _micrographCacheService = micrographCacheService; - _blobRepository = blobRepository; - } - - public override void Start() - { - var graphData = _blobRepository.Read>>("prices", "graph") - ?.ToDictionary(d => d.Key, d => d.Value) ?? - new Dictionary>(); - - if (graphData.Count > 0) - { - FixGraphData(graphData); - _micrographCacheService.InitCache(graphData); - } - - base.Start(); - } - - public override async Task Execute() - { - var dataToWrite = _micrographCacheService.GetGraphData(); - await _blobRepository.Write("prices", "graph", dataToWrite); - } - - private void FixGraphData(Dictionary> graphData) - { - foreach (var pair in graphData) - { - for (var i = pair.Value.Count - 1; i >= 0; i--) - { - var bidAsk = pair.Value[i]; - - if (bidAsk.Bid > bidAsk.Ask) - { - graphData[pair.Key].Remove(bidAsk); - } - } - } - } - } -} diff --git a/src/MarginTrading.Backend.Services/Quotes/PricesUpdateRabbitMqNotifier.cs b/src/MarginTrading.Backend.Services/Quotes/PricesUpdateRabbitMqNotifier.cs index 14c6ec36d..e0438fbf7 100644 --- a/src/MarginTrading.Backend.Services/Quotes/PricesUpdateRabbitMqNotifier.cs +++ b/src/MarginTrading.Backend.Services/Quotes/PricesUpdateRabbitMqNotifier.cs @@ -1,4 +1,7 @@ -using MarginTrading.Backend.Services.Events; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Services.Events; using MarginTrading.Backend.Services.Notifications; namespace MarginTrading.Backend.Services.Quotes @@ -14,10 +17,10 @@ IRabbitMqNotifyService rabbitMqNotifyService _rabbitMqNotifyService = rabbitMqNotifyService; } - int IEventConsumer.ConsumerRank => 100; + int IEventConsumer.ConsumerRank => 110; void IEventConsumer.ConsumeEvent(object sender, BestPriceChangeEventArgs ea) { - _rabbitMqNotifyService.OrderBookPrice(ea.BidAskPair); + _rabbitMqNotifyService.OrderBookPrice(ea.BidAskPair, ea.IsEod); } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Quotes/QuoteCacheService.cs b/src/MarginTrading.Backend.Services/Quotes/QuoteCacheService.cs index 1027c8fa6..9e12e4c73 100644 --- a/src/MarginTrading.Backend.Services/Quotes/QuoteCacheService.cs +++ b/src/MarginTrading.Backend.Services/Quotes/QuoteCacheService.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -8,7 +11,10 @@ using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.Exceptions; using MarginTrading.Backend.Core.Messages; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Core.Settings; using MarginTrading.Backend.Services.Events; +using MarginTrading.Backend.Services.Stp; namespace MarginTrading.Backend.Services.Quotes { @@ -16,16 +22,76 @@ public class QuoteCacheService : TimerPeriod, IQuoteCacheService, IEventConsumer { private readonly ILog _log; private readonly IMarginTradingBlobRepository _blobRepository; - private Dictionary _quotes; + private readonly IExternalOrderbookService _externalOrderbookService; + + private Dictionary _cache = new Dictionary(); + private readonly ReaderWriterLockSlim _lockSlim = new ReaderWriterLockSlim(); - private static string BlobName = "Quotes"; - public QuoteCacheService(ILog log, IMarginTradingBlobRepository blobRepository) - : base(nameof(QuoteCacheService), 10000, log) + private const string BlobName = "Quotes"; + + public QuoteCacheService(ILog log, + IMarginTradingBlobRepository blobRepository, + IExternalOrderbookService externalOrderbookService, + MarginTradingSettings marginTradingSettings) + : base(nameof(QuoteCacheService), + marginTradingSettings.BlobPersistence.QuotesDumpPeriodMilliseconds, + log) { _log = log; _blobRepository = blobRepository; - _quotes = new Dictionary(); + _externalOrderbookService = externalOrderbookService; + } + + public override void Start() + { + _log.WriteInfo(nameof(QuoteCacheService), nameof(Start), "Quote cache init started."); + + var blobQuotes = + _blobRepository + .Read>(LykkeConstants.StateBlobContainer, BlobName) + ?.ToDictionary(d => d.Key, d => d.Value) ?? + new Dictionary(); + _log.WriteInfo(nameof(QuoteCacheService), nameof(Start), + $"{blobQuotes.Count} quotes read from blob."); + + var orderBooks = _externalOrderbookService.GetOrderBooks(); + _log.WriteInfo(nameof(QuoteCacheService), nameof(Start), + $"{orderBooks.Count} order books read from {nameof(IExternalOrderbookService)}."); + + var result = new Dictionary(); + foreach (var orderBook in orderBooks) + { + if (!blobQuotes.TryGetValue(orderBook.AssetPairId, out var quote) + || orderBook.Timestamp > quote.Date) + { + result.Add(orderBook.AssetPairId, orderBook.GetBestPrice()); + } + } + + foreach (var remainsToAdd in blobQuotes.Keys.Except(result.Keys)) + { + var item = blobQuotes[remainsToAdd]; + result.Add(item.Instrument, item); + } + + _cache = result; + + _log.WriteInfo(nameof(QuoteCacheService), nameof(Start), + $"Quote cache initialised with total {result.Count} items."); + + base.Start(); + } + + public override Task Execute() + { + return DumpToRepository(); + } + + public override void Stop() + { + DumpToRepository().Wait(); + base.Stop(); } public InstrumentBidAskPair GetQuote(string instrument) @@ -33,7 +99,7 @@ public InstrumentBidAskPair GetQuote(string instrument) _lockSlim.EnterReadLock(); try { - if (!_quotes.TryGetValue(instrument, out var quote)) + if (!_cache.TryGetValue(instrument, out var quote)) throw new QuoteNotFoundException(instrument, string.Format(MtMessages.QuoteNotFound, instrument)); return quote; @@ -49,7 +115,7 @@ public bool TryGetQuoteById(string instrument, out InstrumentBidAskPair result) _lockSlim.EnterReadLock(); try { - if (!_quotes.TryGetValue(instrument, out var quote)) + if (!_cache.TryGetValue(instrument, out var quote)) { result = null; return false; @@ -69,7 +135,7 @@ public Dictionary GetAllQuotes() _lockSlim.EnterReadLock(); try { - return _quotes.ToDictionary(x => x.Key, y => y.Value); + return _cache.ToDictionary(x => x.Key, y => y.Value); } finally { @@ -77,15 +143,15 @@ public Dictionary GetAllQuotes() } } - public void RemoveQuote(string assetPair) + public void RemoveQuote(string assetPairId) { _lockSlim.EnterWriteLock(); try { - if (_quotes.ContainsKey(assetPair)) - _quotes.Remove(assetPair); + if (_cache.ContainsKey(assetPairId)) + _cache.Remove(assetPairId); else - throw new QuoteNotFoundException(assetPair, string.Format(MtMessages.QuoteNotFound, assetPair)); + throw new QuoteNotFoundException(assetPairId, string.Format(MtMessages.QuoteNotFound, assetPairId)); } finally { @@ -102,13 +168,13 @@ void IEventConsumer.ConsumeEvent(object sender, BestPr { var bidAskPair = ea.BidAskPair; - if (_quotes.ContainsKey(bidAskPair.Instrument)) + if (_cache.ContainsKey(bidAskPair.Instrument)) { - _quotes[bidAskPair.Instrument] = bidAskPair; + _cache[bidAskPair.Instrument] = bidAskPair; } else { - _quotes.Add(bidAskPair.Instrument, bidAskPair); + _cache.Add(bidAskPair.Instrument, bidAskPair); } } finally @@ -117,33 +183,11 @@ void IEventConsumer.ConsumeEvent(object sender, BestPr } } - public override void Start() - { - _quotes = - _blobRepository - .Read>(LykkeConstants.StateBlobContainer, BlobName) - ?.ToDictionary(d => d.Key, d => d.Value) ?? - new Dictionary(); - - base.Start(); - } - - public override Task Execute() - { - return DumpToRepository(); - } - - public override void Stop() - { - DumpToRepository().Wait(); - base.Stop(); - } - private async Task DumpToRepository() { try { - await _blobRepository.Write(LykkeConstants.StateBlobContainer, BlobName, GetAllQuotes()); + await _blobRepository.WriteAsync(LykkeConstants.StateBlobContainer, BlobName, GetAllQuotes()); } catch (Exception ex) { diff --git a/src/MarginTrading.Backend.Services/Quotes/QuotesMonitor.cs b/src/MarginTrading.Backend.Services/Quotes/QuotesMonitor.cs index a390ed9ad..e57d9f134 100644 --- a/src/MarginTrading.Backend.Services/Quotes/QuotesMonitor.cs +++ b/src/MarginTrading.Backend.Services/Quotes/QuotesMonitor.cs @@ -1,14 +1,15 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Threading.Tasks; using Common; using Common.Log; -using Lykke.SlackNotifications; using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.Settings; using MarginTrading.Backend.Services.AssetPairs; using MarginTrading.Backend.Services.Infrastructure; -using MarginTrading.Common.Enums; using MarginTrading.Common.Services; namespace MarginTrading.Backend.Services.Quotes @@ -17,20 +18,19 @@ public class QuotesMonitor : TimerPeriod { private readonly ILog _log; private readonly IMtSlackNotificationsSender _slackNotificationsSender; - private readonly MarginSettings _marginSettings; + private readonly MarginTradingSettings _marginSettings; private readonly IQuoteCacheService _quoteCacheService; private readonly IDateService _dateService; private readonly IAssetPairDayOffService _dayOffService; private readonly IAlertSeverityLevelService _alertSeverityLevelService; - private const int DefaultMaxQuoteAgeInSeconds = 300; private const int NotificationRepeatTimeoutCoef = 5; private readonly Dictionary _outdatedQuotes; public QuotesMonitor(ILog log, IMtSlackNotificationsSender slackNotificationsSender, - MarginSettings marginSettings, + MarginTradingSettings marginSettings, IQuoteCacheService quoteCacheService, IDateService dateService, IAssetPairDayOffService dayOffService, @@ -49,9 +49,10 @@ public QuotesMonitor(ILog log, public override Task Execute() { - var maxQuoteAgeInSeconds = _marginSettings.MaxMarketMakerLimitOrderAge >= 0 - ? _marginSettings.MaxMarketMakerLimitOrderAge - : DefaultMaxQuoteAgeInSeconds; + if (_marginSettings.MaxMarketMakerLimitOrderAge <= 0) + return Task.CompletedTask; + + var maxQuoteAgeInSeconds = _marginSettings.MaxMarketMakerLimitOrderAge; var now = _dateService.Now(); var minQuoteDateTime = now.AddSeconds(-maxQuoteAgeInSeconds); diff --git a/src/MarginTrading.Backend.Services/RabbitMq/PublishingQueueRepository.cs b/src/MarginTrading.Backend.Services/RabbitMq/PublishingQueueRepository.cs new file mode 100644 index 000000000..113282c1e --- /dev/null +++ b/src/MarginTrading.Backend.Services/RabbitMq/PublishingQueueRepository.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Lykke.RabbitMqBroker.Publisher; +using MarginTrading.Backend.Core; + +namespace MarginTrading.Backend.Services.RabbitMq +{ + public class PublishingQueueRepository : IPublishingQueueRepository + { + private readonly IMarginTradingBlobRepository _blobRepository; + + private const string BlobContainer = "PublishingQueue"; + + public PublishingQueueRepository(IMarginTradingBlobRepository blobRepository) + { + _blobRepository = blobRepository; + } + + public async Task SaveAsync(IReadOnlyCollection items, string exchangeName) + { + await _blobRepository.WriteAsync(BlobContainer, exchangeName, items); + } + + public async Task> LoadAsync(string exchangeName) + { + return await _blobRepository.ReadAsync>(BlobContainer, exchangeName); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Scheduling/ScheduleSettingsCacheWarmUpJob.cs b/src/MarginTrading.Backend.Services/Scheduling/ScheduleSettingsCacheWarmUpJob.cs new file mode 100644 index 000000000..42edcee88 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Scheduling/ScheduleSettingsCacheWarmUpJob.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using FluentScheduler; +using JetBrains.Annotations; +using MarginTrading.Backend.Services.AssetPairs; + +namespace MarginTrading.Backend.Services.Scheduling +{ + [UsedImplicitly] + public class ScheduleSettingsCacheWarmUpJob : IJob, IDisposable + { + private readonly IScheduleSettingsCacheService _scheduleSettingsCacheService; + + public ScheduleSettingsCacheWarmUpJob( + IScheduleSettingsCacheService scheduleSettingsCacheService) + { + _scheduleSettingsCacheService = scheduleSettingsCacheService; + } + + public void Execute() + { + _scheduleSettingsCacheService.CacheWarmUpIncludingValidation(); + _scheduleSettingsCacheService.MarketsCacheWarmUp(); + } + + public void Dispose() + { + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Services/AccountUpdateService.cs b/src/MarginTrading.Backend.Services/Services/AccountUpdateService.cs index c30b4f951..3465e3816 100644 --- a/src/MarginTrading.Backend.Services/Services/AccountUpdateService.cs +++ b/src/MarginTrading.Backend.Services/Services/AccountUpdateService.cs @@ -1,99 +1,248 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using Common; +using Common.Log; +using JetBrains.Annotations; +using Lykke.Common.Log; using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Exceptions; +using MarginTrading.Backend.Core.MatchingEngines; using MarginTrading.Backend.Core.Messages; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Core.Trading; using MarginTrading.Backend.Services.Assets; using MarginTrading.Backend.Services.TradingConditions; +#pragma warning disable 1998 namespace MarginTrading.Backend.Services { + [UsedImplicitly] public class AccountUpdateService : IAccountUpdateService { private readonly IFplService _fplService; - private readonly IAccountGroupCacheService _accountGroupCacheService; + private readonly ITradingConditionsCacheService _tradingConditionsCache; private readonly IAccountsCacheService _accountsCacheService; private readonly OrdersCache _ordersCache; private readonly IAssetsCache _assetsCache; + + private readonly IAccountMarginFreezingRepository _accountMarginFreezingRepository; + private readonly IAccountMarginUnconfirmedRepository _accountMarginUnconfirmedRepository; + private readonly ILog _log; + private readonly MarginTradingSettings _marginTradingSettings; + private readonly ICfdCalculatorService _cfdCalculatorService; + private readonly IQuoteCacheService _quoteCacheService; public AccountUpdateService( IFplService fplService, - IAccountGroupCacheService accountGroupCacheService, + ITradingConditionsCacheService tradingConditionsCache, IAccountsCacheService accountsCacheService, OrdersCache ordersCache, - IAssetsCache assetsCache) + IAssetsCache assetsCache, + IAccountMarginFreezingRepository accountMarginFreezingRepository, + IAccountMarginUnconfirmedRepository accountMarginUnconfirmedRepository, + ILog log, + MarginTradingSettings marginTradingSettings, + ICfdCalculatorService cfdCalculatorService, + IQuoteCacheService quoteCacheService) { _fplService = fplService; - _accountGroupCacheService = accountGroupCacheService; + _tradingConditionsCache = tradingConditionsCache; _accountsCacheService = accountsCacheService; _ordersCache = ordersCache; _assetsCache = assetsCache; + _accountMarginFreezingRepository = accountMarginFreezingRepository; + _accountMarginUnconfirmedRepository = accountMarginUnconfirmedRepository; + _log = log; + _marginTradingSettings = marginTradingSettings; + _cfdCalculatorService = cfdCalculatorService; + _quoteCacheService = quoteCacheService; } public void UpdateAccount(IMarginTradingAccount account) { - UpdateAccount(account, GetActiveOrders(account.Id), GetPendingOrders(account.Id)); + UpdateAccount(account, GetPositions(account.Id), GetActiveOrders(account.Id)); } - public bool IsEnoughBalance(Order order) + public async Task FreezeWithdrawalMargin(string accountId, string operationId, decimal amount) { - _fplService.CalculateMargin(order, order.FplData); - var orderMargin = order.GetMarginInit(); - var accountMarginAvailable = _accountsCacheService.Get(order.ClientId, order.AccountId).GetMarginAvailable(); + var account = _accountsCacheService.Get(accountId); - return accountMarginAvailable >= orderMargin; + if (account.AccountFpl.WithdrawalFrozenMarginData.TryAdd(operationId, amount)) + { + account.AccountFpl.WithdrawalFrozenMargin = account.AccountFpl.WithdrawalFrozenMarginData.Values.Sum(); + //TODO: think about approach + //await _accountMarginFreezingRepository.TryInsertAsync(new AccountMarginFreezing(operationId, + // accountId, amount)); + } } - public MarginTradingAccount GuessAccountWithNewActiveOrder(Order order) + public async Task UnfreezeWithdrawalMargin(string accountId, string operationId) { - var newInstance = MarginTradingAccount.Create(_accountsCacheService.Get(order.ClientId, order.AccountId)); - - var activeOrders = GetActiveOrders(newInstance.Id); - activeOrders.Add(order); + var account = _accountsCacheService.Get(accountId); - var pendingOrders = GetPendingOrders(newInstance.Id); + if (account.AccountFpl.WithdrawalFrozenMarginData.Remove(operationId)) + { + account.AccountFpl.WithdrawalFrozenMargin = account.AccountFpl.WithdrawalFrozenMarginData.Values.Sum(); + //TODO: think about approach + //await _accountMarginFreezingRepository.DeleteAsync(operationId); + } + } - UpdateAccount(newInstance, activeOrders, pendingOrders); + public async Task FreezeUnconfirmedMargin(string accountId, string operationId, decimal amount) + { + var account = _accountsCacheService.Get(accountId); + + if (account.AccountFpl.UnconfirmedMarginData.TryAdd(operationId, amount)) + { + account.AccountFpl.UnconfirmedMargin = account.AccountFpl.UnconfirmedMarginData.Values.Sum(); + //TODO: think about approach + //await _accountMarginUnconfirmedRepository.TryInsertAsync(new AccountMarginFreezing(operationId, + // accountId, amount)); + } + } - return newInstance; + public async Task UnfreezeUnconfirmedMargin(string accountId, string operationId) + { + var account = _accountsCacheService.Get(accountId); + + if (account.AccountFpl.UnconfirmedMarginData.Remove(operationId)) + { + account.AccountFpl.UnconfirmedMargin = account.AccountFpl.UnconfirmedMarginData.Values.Sum(); + //TODO: think about approach + //await _accountMarginUnconfirmedRepository.DeleteAsync(operationId); + } } - - private void UpdateAccount(IMarginTradingAccount account, - ICollection activeOrders, - ICollection pendingOrders) + + public void CheckIsEnoughBalance(Order order, IMatchingEngineBase matchingEngine) { - var accuracy = _assetsCache.GetAssetAccuracy(account.BaseAssetId); - var activeOrdersMaintenanceMargin = activeOrders.Sum(item => item.GetMarginMaintenance()); - var activeOrdersInitMargin = activeOrders.Sum(item => item.GetMarginInit()); - var pendingOrdersMargin = pendingOrders.Sum(item => item.GetMarginInit()); + var orderMargin = _fplService.GetInitMarginForOrder(order); + var accountMarginAvailable = _accountsCacheService.Get(order.AccountId).GetMarginAvailable(); - account.AccountFpl.PnL = Math.Round(activeOrders.Sum(x => x.GetTotalFpl()), accuracy); + var quote = _quoteCacheService.GetQuote(order.AssetPairId); - account.AccountFpl.UsedMargin = Math.Round(activeOrdersMaintenanceMargin + pendingOrdersMargin, accuracy); - account.AccountFpl.MarginInit = Math.Round(activeOrdersInitMargin + pendingOrdersMargin, accuracy); - account.AccountFpl.OpenPositionsCount = activeOrders.Count; + var openPrice = order.Price ?? 0; + var closePrice = 0m; + var directionForClose = order.Volume.GetClosePositionOrderDirection(); - var accountGroup = _accountGroupCacheService.GetAccountGroup(account.TradingConditionId, account.BaseAssetId); + if (quote.GetVolumeForOrderDirection(order.Direction) >= Math.Abs(order.Volume) && + quote.GetVolumeForOrderDirection(directionForClose) >= Math.Abs(order.Volume)) + { + closePrice = quote.GetPriceForOrderDirection(directionForClose); - if (accountGroup == null) + if (openPrice == 0) + openPrice = quote.GetPriceForOrderDirection(order.Direction); + } + else { - throw new Exception(string.Format(MtMessages.AccountGroupForTradingConditionNotFound, - account.TradingConditionId, account.BaseAssetId)); + var openPriceInfo = matchingEngine.GetBestPriceForOpen(order.AssetPairId, order.Volume); + var closePriceInfo = + matchingEngine.GetPriceForClose(order.AssetPairId, order.Volume, openPriceInfo.externalProviderId); + + if (openPriceInfo.price == null || closePriceInfo == null) + { + throw new ValidateOrderException(OrderRejectReason.NoLiquidity, + "Price for open/close can not be calculated"); + } + + closePrice = closePriceInfo.Value; + + if (openPrice == 0) + openPrice = openPriceInfo.price.Value; + } - account.AccountFpl.MarginCallLevel = accountGroup.MarginCall; - account.AccountFpl.StopoutLevel = accountGroup.StopOut; + var pnlInTradingCurrency = (closePrice - openPrice) * order.Volume; + var fxRate = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.AccountAssetId, + order.AssetPairId, order.LegalEntity, + pnlInTradingCurrency > 0); + var pnl = pnlInTradingCurrency * fxRate; + + // just in case... is should be always negative + if (pnl > 0) + { + _log.WriteWarning(nameof(CheckIsEnoughBalance), order.ToJson(), + $"Theoretical PnL at the moment of order execution is positive"); + pnl = 0; + } + + if (accountMarginAvailable + pnl < orderMargin) + throw new ValidateOrderException(OrderRejectReason.NotEnoughBalance, + MtMessages.Validation_NotEnoughBalance, + $"Account available margin: {accountMarginAvailable}, order margin: {orderMargin}, pnl: {pnl} " + + $"(open price: {openPrice}, close price: {closePrice}, fx rate: {fxRate})"); + } + + public void RemoveLiquidationStateIfNeeded(string accountId, string reason, + string liquidationOperationId = null, LiquidationType liquidationType = LiquidationType.Normal) + { + var account = _accountsCacheService.TryGet(accountId); + + if (account == null) + return; + + if (!string.IsNullOrEmpty(account.LiquidationOperationId) + && (liquidationType == LiquidationType.Forced + || account.GetAccountLevel() != AccountLevel.StopOut)) + { + _accountsCacheService.TryFinishLiquidation(accountId, reason, liquidationOperationId); + } + } + + public decimal CalculateOvernightUsedMargin(IMarginTradingAccount account) + { + var positions = GetPositions(account.Id); + var accuracy = _assetsCache.GetAssetAccuracy(account.BaseAssetId); + var positionsMargin = positions.Sum(item => item.GetOvernightMarginMaintenance()); + var pendingOrdersMargin = 0;// pendingOrders.Sum(item => item.GetMarginInit()); + + return Math.Round(positionsMargin + pendingOrdersMargin, accuracy); + } + + private void UpdateAccount(IMarginTradingAccount account, + ICollection positions, + ICollection pendingOrders) + { account.AccountFpl.CalculatedHash = account.AccountFpl.ActualHash; + + var accuracy = _assetsCache.GetAssetAccuracy(account.BaseAssetId); + var positionsMaintenanceMargin = positions.Sum(item => item.GetMarginMaintenance()); + var positionsInitMargin = positions.Sum(item => item.GetMarginInit()); + var pendingOrdersMargin = 0;// pendingOrders.Sum(item => item.GetMarginInit()); + + account.AccountFpl.PnL = Math.Round(positions.Sum(x => x.GetTotalFpl()), accuracy); + account.AccountFpl.UnrealizedDailyPnl = + Math.Round(positions.Sum(x => x.GetTotalFpl() - x.ChargedPnL), accuracy); + + account.AccountFpl.UsedMargin = Math.Round(positionsMaintenanceMargin + pendingOrdersMargin, accuracy); + account.AccountFpl.MarginInit = Math.Round(positionsInitMargin + pendingOrdersMargin, accuracy); + account.AccountFpl.InitiallyUsedMargin = positions.Sum(p => p.GetInitialMargin()); + account.AccountFpl.OpenPositionsCount = positions.Count; + account.AccountFpl.ActiveOrdersCount = pendingOrders.Count; + + var tradingCondition = _tradingConditionsCache.GetTradingCondition(account.TradingConditionId); + + account.AccountFpl.MarginCall1Level = tradingCondition.MarginCall1; + account.AccountFpl.MarginCall2Level = tradingCondition.MarginCall2; + account.AccountFpl.StopOutLevel = tradingCondition.StopOut; + } - private ICollection GetActiveOrders(string accountId) + private ICollection GetPositions(string accountId) { - return _ordersCache.ActiveOrders.GetOrdersByAccountIds(accountId); + return _ordersCache.Positions.GetPositionsByAccountIds(accountId); } - private ICollection GetPendingOrders(string accountId) + private ICollection GetActiveOrders(string accountId) { - return _ordersCache.WaitingForExecutionOrders.GetOrdersByAccountIds(accountId); + return _ordersCache.Active.GetOrdersByAccountIds(accountId); } } } diff --git a/src/MarginTrading.Backend.Services/Services/AccountsCacheService.cs b/src/MarginTrading.Backend.Services/Services/AccountsCacheService.cs index 17890a8eb..9bd281e79 100644 --- a/src/MarginTrading.Backend.Services/Services/AccountsCacheService.cs +++ b/src/MarginTrading.Backend.Services/Services/AccountsCacheService.cs @@ -1,162 +1,242 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; +using Common.Log; using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.Exceptions; +using MarginTrading.Backend.Core.Helpers; using MarginTrading.Backend.Core.Messages; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Common.Services; namespace MarginTrading.Backend.Services { public class AccountsCacheService : IAccountsCacheService { - private Dictionary _accounts = new Dictionary(); + private Dictionary _accounts = new Dictionary(); private readonly ReaderWriterLockSlim _lockSlim = new ReaderWriterLockSlim(); - public void UpdateAccountsCache(string clientId, IEnumerable newValues) + private readonly IDateService _dateService; + private readonly ILog _log; + + public AccountsCacheService( + IDateService dateService, + ILog log) { - var newInstances = newValues.Select(MarginTradingAccount.Create); - UpdateClientAccounts(clientId, newInstances); + _dateService = dateService; + _log = log; } - - public IEnumerable GetAll(string clientId) + + public IReadOnlyList GetAll() { - return GetClientAccounts(clientId); + return _accounts.Values.ToArray(); } - public IReadOnlyList GetAll() + public PaginatedResponse GetAllByPages(int? skip = null, int? take = null) { - return GetClientAccounts(null); + var accounts = _accounts.Values.OrderBy(x => x.Id).ToList();//todo think again about ordering + var data = (!take.HasValue ? accounts : accounts.Skip(skip.Value)) + .Take(PaginationHelper.GetTake(take)).ToList(); + return new PaginatedResponse( + contents: data, + start: skip ?? 0, + size: data.Count, + totalSize: accounts.Count + ); } - public MarginTradingAccount Get(string clientId, string accountId) + public MarginTradingAccount Get(string accountId) { - var result = GetClientAccount(clientId, accountId); - if (null == result) + return TryGetAccount(accountId) ?? throw new AccountNotFoundException(accountId, string.Format(MtMessages.AccountByIdNotFound, accountId)); - - return result; } - - public MarginTradingAccount TryGet(string clientId, string accountId) + + public MarginTradingAccount TryGet(string accountId) { - return GetClientAccount(clientId, accountId); + return TryGetAccount(accountId); } - public void UpdateBalance(MarginTradingAccount account) + private MarginTradingAccount TryGetAccount(string accountId) { - UpdateAccount(account.ClientId, account.Id, x => + _lockSlim.EnterReadLock(); + try { - x.Balance = account.Balance; - x.WithdrawTransferLimit = account.WithdrawTransferLimit; - }); + return _accounts.TryGetValue(accountId, out var result) ? result : null; + } + finally + { + _lockSlim.ExitReadLock(); + } } - public IMarginTradingAccount SetTradingCondition(string clientId, string accountId, string tradingConditionId) + internal void InitAccountsCache(Dictionary accounts) { - UpdateAccount(clientId, accountId, account => account.TradingConditionId = tradingConditionId); - - return GetClientAccount(clientId, accountId, true); + _lockSlim.EnterWriteLock(); + try + { + _accounts = accounts; + } + finally + { + _lockSlim.ExitWriteLock(); + } } - public IEnumerable GetClientIdsByTradingConditionId(string tradingConditionId, string accountId = null) + public bool TryStartLiquidation(string accountId, string operationId, out string currentOperationId) { - _lockSlim.EnterReadLock(); + _lockSlim.EnterWriteLock(); try { - foreach (var clientId in _accounts.Keys) - if (_accounts[clientId].Any(item => item.TradingConditionId == tradingConditionId && - (string.IsNullOrEmpty(accountId) || item.Id == accountId))) - yield return clientId; + if (!_accounts.TryGetValue(accountId, out var account)) + { + currentOperationId = string.Empty; + return false; + } + + if (!string.IsNullOrEmpty(account.LiquidationOperationId)) + { + currentOperationId = account.LiquidationOperationId; + return false; + } + + account.LiquidationOperationId = operationId; + currentOperationId = operationId; + return true; } finally { - _lockSlim.ExitReadLock(); + _lockSlim.ExitWriteLock(); } } - private MarginTradingAccount[] GetClientAccounts(string clientId) + public bool TryFinishLiquidation(string accountId, string reason, + string liquidationOperationId = null) { - _lockSlim.EnterReadLock(); + _lockSlim.EnterWriteLock(); + try { - if (clientId != null) - { - if (_accounts.ContainsKey(clientId)) - return _accounts[clientId]; + if (!_accounts.TryGetValue(accountId, out var account)) + return false; - return Array.Empty(); + if (string.IsNullOrEmpty(liquidationOperationId) || + liquidationOperationId == account.LiquidationOperationId) + { + account.LiquidationOperationId = string.Empty; + _log.WriteInfo(nameof(TryFinishLiquidation), account, + $"Liquidation state was removed for account {accountId}. Reason: {reason}"); + return true; } else { - return _accounts.SelectMany(a => a.Value).ToArray(); + _log.WriteInfo(nameof(TryFinishLiquidation), account, + $"Liquidation state was not removed for account {accountId} " + + $"by liquidationOperationId {liquidationOperationId} " + + $"Current LiquidationOperationId: {account.LiquidationOperationId}."); + return false; } } finally { - _lockSlim.ExitReadLock(); + _lockSlim.ExitWriteLock(); } } - private void UpdateClientAccounts(string clientId, IEnumerable newValue) + public async Task UpdateAccountChanges(string accountId, string updatedTradingConditionId, + decimal updatedWithdrawTransferLimit, bool isDisabled, bool isWithdrawalDisabled, DateTime eventTime) { - var accounts = newValue.ToArray(); - _lockSlim.EnterWriteLock(); try { - if (!_accounts.ContainsKey(clientId)) - _accounts.Add(clientId, accounts); - else - _accounts[clientId] = accounts; + var account = _accounts[accountId]; + + if (account.LastUpdateTime > eventTime) + { + await _log.WriteInfoAsync(nameof(AccountsCacheService), nameof(UpdateAccountChanges), + $"Account with id {account.Id} is in newer state then the event"); + return false; + } + + account.TradingConditionId = updatedTradingConditionId; + account.WithdrawTransferLimit = updatedWithdrawTransferLimit; + account.IsDisabled = isDisabled; + account.IsWithdrawalDisabled = isWithdrawalDisabled; + account.LastUpdateTime = eventTime; } finally { _lockSlim.ExitWriteLock(); } + return true; } - private MarginTradingAccount GetClientAccount(string clientId, string accountId, bool throwIfNotExists = false) + public async Task UpdateAccountBalance(string accountId, decimal accountBalance, DateTime eventTime) { - var accounts = GetClientAccounts(clientId); - - if (accounts.Length == 0) + _lockSlim.EnterWriteLock(); + try { - if (throwIfNotExists) - throw new Exception(string.Format(MtMessages.ClientIdNotFoundInCache, clientId)); + var account = _accounts[accountId]; - return null; + if (account.LastBalanceChangeTime > eventTime) + { + await _log.WriteInfoAsync(nameof(AccountsCacheService), nameof(UpdateAccountBalance), + $"Account with id {account.Id} has balance in newer state then the event"); + return false; + } + + account.Balance = accountBalance; + account.LastBalanceChangeTime = eventTime; } + finally + { + _lockSlim.ExitWriteLock(); + } + return true; + } - _lockSlim.EnterReadLock(); + public void TryAddNew(MarginTradingAccount account) + { + _lockSlim.EnterWriteLock(); try { - var result = accounts.FirstOrDefault(x => x.Id == accountId); - - if (null == result && throwIfNotExists) - throw new Exception(string.Format(MtMessages.ClientAccountNotFoundInCache, clientId, - accountId)); - - return result; + account.LastUpdateTime = _dateService.Now(); + _accounts.TryAdd(account.Id, account); } finally { - _lockSlim.ExitReadLock(); + _lockSlim.ExitWriteLock(); } } - private void UpdateAccount(string clientId, string accountId, Action updateAction) + public void Remove(string accountId) { - var account = GetClientAccount(clientId, accountId, true); - updateAction(account); + _lockSlim.EnterWriteLock(); + try + { + _accounts.Remove(accountId); + } + finally + { + _lockSlim.ExitWriteLock(); + } } - internal void InitAccountsCache(Dictionary accounts) + public string Reset(string accountId, DateTime eventTime) { _lockSlim.EnterWriteLock(); try { - _accounts = accounts; + if (!_accounts.TryGetValue(accountId, out var account)) + { + throw new Exception($"Account {accountId} does not exist."); + } + + return account.Reset(eventTime); } finally { @@ -164,4 +244,4 @@ internal void InitAccountsCache(Dictionary accou } } } -} +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Services/CfdCalculatorService.cs b/src/MarginTrading.Backend.Services/Services/CfdCalculatorService.cs index 0458f77b8..03f149625 100644 --- a/src/MarginTrading.Backend.Services/Services/CfdCalculatorService.cs +++ b/src/MarginTrading.Backend.Services/Services/CfdCalculatorService.cs @@ -1,41 +1,50 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Exceptions; -using MarginTrading.Backend.Services.AssetPairs; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Services; namespace MarginTrading.Backend.Services { public class CfdCalculatorService : ICfdCalculatorService { private readonly IAssetPairsCache _assetPairsCache; + private readonly IFxRateCacheService _fxRateCacheService; private readonly IQuoteCacheService _quoteCacheService; public CfdCalculatorService( IAssetPairsCache assetPairsCache, + IFxRateCacheService fxRateCacheService, IQuoteCacheService quoteCacheService) { _assetPairsCache = assetPairsCache; + _fxRateCacheService = fxRateCacheService; _quoteCacheService = quoteCacheService; } public decimal GetQuoteRateForBaseAsset(string accountAssetId, string assetPairId, string legalEntity, - bool metricIsPositive = true) + bool useAsk) { var assetPair = _assetPairsCache.GetAssetPairById(assetPairId); - if (accountAssetId == assetPair.BaseAssetId) - return 1; - - var assetPairSubst = _assetPairsCache.FindAssetPair(assetPair.BaseAssetId, accountAssetId, legalEntity); + // two step transform: base -> quote from QuoteCache, quote -> account from FxCache + // if accountAssetId == assetPair.BaseAssetId, rate != 1, because trading and fx rates can be different + + var assetPairQuote = _quoteCacheService.GetQuote(assetPairId); + var tradingRate = useAsk ? assetPairQuote.Ask : assetPairQuote.Bid; - var rate = metricIsPositive - ? assetPairSubst.BaseAssetId == assetPair.BaseAssetId - ? _quoteCacheService.GetQuote(assetPairSubst.Id).Ask - : 1 / _quoteCacheService.GetQuote(assetPairSubst.Id).Bid - : assetPairSubst.BaseAssetId == assetPair.BaseAssetId - ? _quoteCacheService.GetQuote(assetPairSubst.Id).Bid - : 1 / _quoteCacheService.GetQuote(assetPairSubst.Id).Ask; + if (assetPair.QuoteAssetId == accountAssetId) + return tradingRate; + var fxPair = + _assetPairsCache.FindAssetPair(assetPair.QuoteAssetId, accountAssetId, legalEntity); + var fxQuote = _fxRateCacheService.GetQuote(fxPair.Id); + + var rate = fxPair.BaseAssetId == assetPair.QuoteAssetId + ? fxQuote.Ask * tradingRate + : 1 / fxQuote.Bid * tradingRate; + return rate; } @@ -47,17 +56,47 @@ public decimal GetQuoteRateForQuoteAsset(string accountAssetId, string assetPair if (accountAssetId == assetPair.QuoteAssetId) return 1; - var assetPairSubst = _assetPairsCache.FindAssetPair(assetPair.QuoteAssetId, accountAssetId, legalEntity); + var fxPair = + _assetPairsCache.FindAssetPair(assetPair.QuoteAssetId, accountAssetId, legalEntity); + var fxQuote = _fxRateCacheService.GetQuote(fxPair.Id); var rate = metricIsPositive - ? assetPairSubst.BaseAssetId == assetPair.QuoteAssetId - ? _quoteCacheService.GetQuote(assetPairSubst.Id).Ask - : 1 / _quoteCacheService.GetQuote(assetPairSubst.Id).Bid - : assetPairSubst.BaseAssetId == assetPair.QuoteAssetId - ? _quoteCacheService.GetQuote(assetPairSubst.Id).Bid - : 1 / _quoteCacheService.GetQuote(assetPairSubst.Id).Ask; + ? fxPair.BaseAssetId == assetPair.QuoteAssetId + ? fxQuote.Ask + : 1 / fxQuote.Bid + : fxPair.BaseAssetId == assetPair.QuoteAssetId + ? fxQuote.Bid + : 1 / fxQuote.Ask; return rate; } + + public decimal GetPrice(InstrumentBidAskPair quote, FxToAssetPairDirection direction, + bool metricIsPositive = true) + { + return metricIsPositive + ? direction == FxToAssetPairDirection.Straight + ? quote.Ask + : 1 / quote.Bid + : direction == FxToAssetPairDirection.Straight + ? quote.Bid + : 1 / quote.Ask; + } + + public (string id, FxToAssetPairDirection direction) GetFxAssetPairIdAndDirection(string accountAssetId, + string assetPairId, string legalEntity) + { + var assetPair = _assetPairsCache.GetAssetPairById(assetPairId); + + if (accountAssetId == assetPair.QuoteAssetId) + return (LykkeConstants.SymmetricAssetPair, FxToAssetPairDirection.Straight); + + var fxAssetPair = _assetPairsCache.FindAssetPair(assetPair.QuoteAssetId, accountAssetId, legalEntity); + var direction = assetPair.QuoteAssetId == fxAssetPair.BaseAssetId + ? FxToAssetPairDirection.Straight + : FxToAssetPairDirection.Reverse; + + return (fxAssetPair.Id, direction); + } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Services/ClientNotifyService.cs b/src/MarginTrading.Backend.Services/Services/ClientNotifyService.cs deleted file mode 100644 index aca596421..000000000 --- a/src/MarginTrading.Backend.Services/Services/ClientNotifyService.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using Common; -using Common.Log; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Settings; -using MarginTrading.Backend.Services.Notifications; -using MarginTrading.Common.RabbitMq; -using MarginTrading.Common.Services; - -namespace MarginTrading.Backend.Services -{ - public class ClientNotifyService : IClientNotifyService - { - private readonly IRabbitMqNotifyService _rabbitMqNotifyService; - private readonly IMarginTradingOperationsLogService _operationsLogService; - private readonly MarginSettings _marginSettings; - private readonly IConsole _consoleWriter; - private readonly IAccountsCacheService _accountsCacheService; - - public ClientNotifyService( - IRabbitMqNotifyService rabbitMqNotifyService, - IMarginTradingOperationsLogService operationsLogService, - MarginSettings marginSettings, - IConsole consoleWriter, - IAccountsCacheService accountsCacheService) - { - _rabbitMqNotifyService = rabbitMqNotifyService; - _operationsLogService = operationsLogService; - _marginSettings = marginSettings; - _consoleWriter = consoleWriter; - _accountsCacheService = accountsCacheService; - } - - public void NotifyOrderChanged(Order order) - { - _rabbitMqNotifyService.OrderChanged(order); - var queueName = QueueHelper.BuildQueueName(_marginSettings.RabbitMqQueues.OrderChanged.ExchangeName, _marginSettings.Env); - _consoleWriter.WriteLine($"send order changed to queue {queueName}"); - _operationsLogService.AddLog($"queue {queueName}", order.ClientId, order.AccountId, null, order.ToJson()); - } - - public void NotifyAccountUpdated(IMarginTradingAccount account) - { - _rabbitMqNotifyService.AccountUpdated(account); - var queueName = QueueHelper.BuildQueueName(_marginSettings.RabbitMqQueues.AccountChanged.ExchangeName, _marginSettings.Env); - _consoleWriter.WriteLine($"send account changed to queue {queueName}"); - _operationsLogService.AddLog($"queue {queueName}", account.ClientId, account.Id, null, account.ToJson()); - } - - public void NotifyAccountStopout(string clientId, string accountId, int positionsCount, decimal totalPnl) - { - _rabbitMqNotifyService.AccountStopout(clientId, accountId, positionsCount, totalPnl); - var queueName = QueueHelper.BuildQueueName(_marginSettings.RabbitMqQueues.AccountStopout.ExchangeName, _marginSettings.Env); - _consoleWriter.WriteLine($"send account stopout to queue {queueName}"); - _operationsLogService.AddLog($"queue {queueName}", clientId, accountId, null, - new {clientId = clientId, accountId = accountId, positionsCount = positionsCount, totalPnl = totalPnl}.ToJson()); - } - - public async Task NotifyTradingConditionsChanged(string tradingConditionId = null, string accountId = null) - { - if (!string.IsNullOrEmpty(tradingConditionId)) - { - var clientIds = _accountsCacheService - .GetClientIdsByTradingConditionId(tradingConditionId, accountId).ToArray(); - - if (clientIds.Length > 0) - { - await _rabbitMqNotifyService.UserUpdates(true, false, clientIds); - _consoleWriter.WriteLine( - $"send user updates to queue {QueueHelper.BuildQueueName(_marginSettings.RabbitMqQueues.UserUpdates.ExchangeName, _marginSettings.Env)}"); - } - } - } - } -} diff --git a/src/MarginTrading.Backend.Services/Services/CommissionService.cs b/src/MarginTrading.Backend.Services/Services/CommissionService.cs index d1ea18312..2e0a971e3 100644 --- a/src/MarginTrading.Backend.Services/Services/CommissionService.cs +++ b/src/MarginTrading.Backend.Services/Services/CommissionService.cs @@ -1,18 +1,24 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using JetBrains.Annotations; using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orders; using MarginTrading.Backend.Services.Assets; using MarginTrading.Backend.Services.TradingConditions; namespace MarginTrading.Backend.Services { + [UsedImplicitly] public class CommissionService : ICommissionService { - private readonly IAccountAssetsCacheService _accountAssetsCacheService; + private readonly ITradingInstrumentsCacheService _accountAssetsCacheService; private readonly ICfdCalculatorService _cfdCalculatorService; private readonly IAssetsCache _assetsCache; public CommissionService( - IAccountAssetsCacheService accountAssetsCacheService, + ITradingInstrumentsCacheService accountAssetsCacheService, ICfdCalculatorService cfdCalculatorService, IAssetsCache assetsCache) { @@ -41,40 +47,26 @@ private decimal GetSwaps(string accountAssetId, string instrument, DateTime? ope return result; } - public decimal GetSwaps(IOrder order) + public decimal GetSwaps(Position order) { - return GetSwaps(order.AccountAssetId, order.Instrument, order.OpenDate, order.CloseDate, - order.GetMatchedVolume(), order.SwapCommission, order.LegalEntity); + return GetSwaps(order.AccountAssetId, order.AssetPairId, order.OpenDate, order.CloseDate, + Math.Abs(order.Volume), order.SwapCommissionRate, order.LegalEntity); } - public decimal GetOvernightSwap(IOrder order, decimal swapRate) + public decimal GetOvernightSwap(Position order, decimal swapRate) { var openDate = DateTime.UtcNow; var closeDate = openDate.AddDays(1); - return GetSwaps(order.AccountAssetId, order.Instrument, openDate, closeDate, + return GetSwaps(order.AccountAssetId, order.AssetPairId, openDate, closeDate, Math.Abs(order.Volume), swapRate, order.LegalEntity); } - public void SetCommissionRates(string tradingConditionId, string accountAssetId, Order order) + public void SetCommissionRates(string tradingConditionId, Position position) { - var accountAsset = _accountAssetsCacheService - .GetAccountAsset(tradingConditionId, accountAssetId, order.Instrument); - - order.CommissionLot = accountAsset.CommissionLot; + var tradingInstrument = _accountAssetsCacheService + .GetTradingInstrument(tradingConditionId, position.AssetPairId); - switch (order.GetOrderType()) - { - case OrderDirection.Buy: - order.OpenCommission = accountAsset.CommissionLong; - order.CloseCommission = accountAsset.CommissionShort; - order.SwapCommission = accountAsset.SwapLong; - break; - case OrderDirection.Sell: - order.OpenCommission = accountAsset.CommissionShort; - order.CloseCommission = accountAsset.CommissionLong; - order.SwapCommission = accountAsset.SwapShort; - break; - } + //TODO: understand what to do with comissions } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Services/EquivalentPricesService.cs b/src/MarginTrading.Backend.Services/Services/EquivalentPricesService.cs new file mode 100644 index 000000000..64dbe537b --- /dev/null +++ b/src/MarginTrading.Backend.Services/Services/EquivalentPricesService.cs @@ -0,0 +1,56 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using Common; +using Common.Log; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Core.Trading; + +namespace MarginTrading.Backend.Services +{ + public class EquivalentPricesService : IEquivalentPricesService + { + private readonly ICfdCalculatorService _cfdCalculatorService; + private readonly ILog _log; + + public EquivalentPricesService( + ICfdCalculatorService cfdCalculatorService, + ILog log) + { + _cfdCalculatorService = cfdCalculatorService; + _log = log; + } + + public void EnrichOpeningOrder(Order order) + { + try + { + //order.OpenPriceEquivalent = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.EquivalentAsset, + // order.Instrument, order.LegalEntity); + } + catch (Exception e) + { + _log.WriteError("EnrichOpeningOrder", order.ToJson(), e); + } + } + + public void EnrichClosingOrder(Position order) + { + try + { + //order.ClosePriceEquivalent = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.EquivalentAsset, + // order.AssetPairId, order.LegalEntity); + } + catch (Exception e) + { + _log.WriteError("EnrichClosingOrder", order.ToJson(), e); + } + + + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Services/FplService.cs b/src/MarginTrading.Backend.Services/Services/FplService.cs new file mode 100644 index 000000000..3574cdab3 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Services/FplService.cs @@ -0,0 +1,131 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using JetBrains.Annotations; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Core.Trading; +using MarginTrading.Backend.Core.TradingConditions; +using MarginTrading.Backend.Services.Assets; +using MarginTrading.Backend.Services.TradingConditions; + +namespace MarginTrading.Backend.Services +{ + [UsedImplicitly] + public class FplService : IFplService + { + private readonly ICfdCalculatorService _cfdCalculatorService; + private readonly IAccountsCacheService _accountsCacheService; + private readonly ITradingInstrumentsCacheService _tradingInstrumentsCache; + private readonly IAssetsCache _assetsCache; + private readonly MarginTradingSettings _marginTradingSettings; + + public FplService( + ICfdCalculatorService cfdCalculatorService, + IAssetPairsCache assetPairsCache, + IAccountsCacheService accountsCacheService, + ITradingInstrumentsCacheService tradingInstrumentsCache, + IAssetsCache assetsCache, + MarginTradingSettings marginTradingSettings) + { + _cfdCalculatorService = cfdCalculatorService; + _accountsCacheService = accountsCacheService; + _tradingInstrumentsCache = tradingInstrumentsCache; + _assetsCache = assetsCache; + _marginTradingSettings = marginTradingSettings; + } + + public void UpdatePositionFpl(Position position) + { +// var handler = order.Status != OrderStatus.WaitingForExecution +// ? UpdateOrderFplData +// : (Action)UpdatePendingOrderMargin; +// +// handler(order, fplData); + + UpdatePositionFplData(position, position.FplData); + + } + + public decimal GetInitMarginForOrder(Order order) + { + var accountAsset = + _tradingInstrumentsCache.GetTradingInstrument(order.TradingConditionId, order.AssetPairId); + var marginRate = _cfdCalculatorService.GetQuoteRateForBaseAsset(order.AccountAssetId, order.AssetPairId, + order.LegalEntity, order.Direction == OrderDirection.Buy); + var accountBaseAssetAccuracy = _assetsCache.GetAssetAccuracy(order.AccountAssetId); + + return Math.Round( + GetMargins(accountAsset, Math.Abs(order.Volume), marginRate).MarginInit, + accountBaseAssetAccuracy); + } + + public decimal CalculateOvernightMaintenanceMargin(Position position) + { + var fplData = new FplData {AccountBaseAssetAccuracy = position.FplData.AccountBaseAssetAccuracy}; + + CalculateMargin(position, fplData, true); + + return fplData.MarginMaintenance; + } + + private void UpdatePositionFplData(Position position, FplData fplData) + { + fplData.AccountBaseAssetAccuracy = _assetsCache.GetAssetAccuracy(position.AccountAssetId); + + var fpl = (position.ClosePrice - position.OpenPrice) * position.CloseFxPrice * position.Volume; + + fplData.Fpl = Math.Round(fpl, fplData.AccountBaseAssetAccuracy); + + if (position.ClosePrice == 0) + position.UpdateClosePrice(position.OpenPrice); + + CalculateMargin(position, fplData); + + if (fplData.ActualHash == 0) + { + fplData.ActualHash = 1; + } + fplData.CalculatedHash = fplData.ActualHash; + } + + private void UpdatePendingOrderMargin(Position order, FplData fplData) + { + fplData.AccountBaseAssetAccuracy = _assetsCache.GetAssetAccuracy(order.AccountAssetId); + + CalculateMargin(order, fplData); + + fplData.CalculatedHash = fplData.ActualHash; + _accountsCacheService.Get(order.AccountId).CacheNeedsToBeUpdated(); + } + + private void CalculateMargin(Position position, FplData fplData, bool isWarnCheck = false) + { + var tradingInstrument = + _tradingInstrumentsCache.GetTradingInstrument(position.TradingConditionId, position.AssetPairId); + var volumeForCalculation = Math.Abs(position.Volume); + + fplData.MarginRate = _cfdCalculatorService.GetQuoteRateForBaseAsset(position.AccountAssetId, + position.AssetPairId, + position.LegalEntity, position.Direction == PositionDirection.Short); // to use close price + + var (marginInit, marginMaintenance) = GetMargins(tradingInstrument, volumeForCalculation, + fplData.MarginRate, isWarnCheck); + fplData.MarginInit = Math.Round(marginInit, fplData.AccountBaseAssetAccuracy); + fplData.MarginMaintenance = Math.Round(marginMaintenance, fplData.AccountBaseAssetAccuracy); + fplData.InitialMargin = Math.Round(position.OpenPrice * position.OpenFxPrice * volumeForCalculation / + tradingInstrument.LeverageInit, fplData.AccountBaseAssetAccuracy); + } + + private (decimal MarginInit, decimal MarginMaintenance) GetMargins(ITradingInstrument tradingInstrument, + decimal volumeForCalculation, decimal marginRate, bool isWarnCheck = false) + { + var (marginInit, marginMaintenance) = _tradingInstrumentsCache.GetMarginRates(tradingInstrument, isWarnCheck); + + return (volumeForCalculation * marginRate * marginInit, + volumeForCalculation * marginRate * marginMaintenance); + } + } +} diff --git a/src/MarginTrading.Common/Services/Settings/IMarginTradingSettingsCacheService.cs b/src/MarginTrading.Backend.Services/Services/IMarginTradingSettingsCacheService.cs similarity index 68% rename from src/MarginTrading.Common/Services/Settings/IMarginTradingSettingsCacheService.cs rename to src/MarginTrading.Backend.Services/Services/IMarginTradingSettingsCacheService.cs index 34796b9c9..107df39ad 100644 --- a/src/MarginTrading.Common/Services/Settings/IMarginTradingSettingsCacheService.cs +++ b/src/MarginTrading.Backend.Services/Services/IMarginTradingSettingsCacheService.cs @@ -1,6 +1,10 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. -namespace MarginTrading.Common.Services.Settings +using System.Threading.Tasks; +using MarginTrading.Common.Services.Settings; + +namespace MarginTrading.Backend.Services.Services { /// /// Detects if margin trading of particular types (live and demo) is available globally and for user. @@ -16,6 +20,11 @@ public interface IMarginTradingSettingsCacheService /// Detects if margin trading of specified in type is available globally and for user /// Task IsMarginTradingEnabled(string clientId, bool isLive); + + /// + /// Detects if margin trading for + /// + bool? IsMarginTradingEnabledByAccountId(string accountId); /// /// Notifies the service of availability change diff --git a/src/MarginTrading.Backend.Services/Services/IValidateOrderService.cs b/src/MarginTrading.Backend.Services/Services/IValidateOrderService.cs new file mode 100644 index 000000000..37b7615d4 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Services/IValidateOrderService.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using MarginTrading.Backend.Contracts.Orders; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.MatchingEngines; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Trading; + +namespace MarginTrading.Backend.Services +{ + public interface IValidateOrderService + { + Task<(Order order, List relatedOrders)> ValidateRequestAndCreateOrders(OrderPlaceRequest request); + + void MakePreTradeValidation(Order order, bool shouldOpenNewPosition, IMatchingEngineBase matchingEngine); + + void ValidateOrderPriceChange(Order order, decimal newPrice); + + Task GetOrderInitialParameters(string assetPairId, string accountId); + + IAssetPair GetAssetPairIfAvailableForTrading(string assetPairId, OrderType orderType, + bool shouldOpenNewPosition, bool isPreTradeValidation); + + bool CheckIfPendingOrderExecutionPossible(string assetPairId, OrderType orderType, bool shouldOpenNewPosition); + void ValidateValidity(DateTime? validity, OrderType orderType); + void ValidateForceOpenChange(Order order, bool? forceOpen); + } +} diff --git a/src/MarginTrading.Common/Services/Settings/MarginTradingEnabledCacheService.cs b/src/MarginTrading.Backend.Services/Services/MarginTradingEnabledCacheService.cs similarity index 71% rename from src/MarginTrading.Common/Services/Settings/MarginTradingEnabledCacheService.cs rename to src/MarginTrading.Backend.Services/Services/MarginTradingEnabledCacheService.cs index 850387ab2..db8cb4156 100644 --- a/src/MarginTrading.Common/Services/Settings/MarginTradingEnabledCacheService.cs +++ b/src/MarginTrading.Backend.Services/Services/MarginTradingEnabledCacheService.cs @@ -1,8 +1,13 @@ -using System.Threading.Tasks; -using Lykke.Service.ClientAccount.Client; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using MarginTrading.Backend.Core; +using MarginTrading.Common.Services.Client; +using MarginTrading.Common.Services.Settings; using Rocks.Caching; -namespace MarginTrading.Common.Services.Settings +namespace MarginTrading.Backend.Services.Services { /// /// Detects if margin trading of particular types (live and demo) is available globally and for user. @@ -12,14 +17,16 @@ public class MarginTradingEnabledCacheService : IMarginTradingSettingsCacheServi private static readonly CachingParameters ClientTradingEnabledCachingParameters = CachingParameters.InfiniteCache; - private readonly IClientAccountClient _clientAccountClient; + private readonly IClientAccountService _clientAccountService; + private readonly IAccountsCacheService _accountsCacheService; private readonly ICacheProvider _cacheProvider; public MarginTradingEnabledCacheService(ICacheProvider cacheProvider, - IClientAccountClient clientAccountClient) + IClientAccountService clientAccountService, IAccountsCacheService accountsCacheService) { _cacheProvider = cacheProvider; - _clientAccountClient = clientAccountClient; + _clientAccountService = clientAccountService; + _accountsCacheService = accountsCacheService; } public Task IsMarginTradingEnabled(string clientId) @@ -31,6 +38,12 @@ public async Task IsMarginTradingEnabled(string clientId, bool isLive) return isLive ? enabledTypes.Live : enabledTypes.Demo; } + public bool? IsMarginTradingEnabledByAccountId(string accountId) + { + var acc = _accountsCacheService.TryGet(accountId); + return !acc?.IsDisabled ; // todo review and fix? + } + public void OnMarginTradingEnabledChanged(MarginTradingEnabledChangedMessage message) { _cacheProvider.Add(GetClientTradingEnabledCacheKey(message.ClientId), @@ -42,7 +55,7 @@ private Task IsMarginTradingEnabledInternal(string cl { async Task MarginEnabled() { - var marginEnabledSettings = await _clientAccountClient.GetMarginEnabledAsync(clientId); + var marginEnabledSettings = await _clientAccountService.GetMarginEnabledAsync(clientId); return new EnabledMarginTradingTypes { Demo = marginEnabledSettings.Enabled, diff --git a/src/MarginTrading.Backend.Services/Services/MarketMakerService.cs b/src/MarginTrading.Backend.Services/Services/MarketMakerService.cs index 84c489594..e315fe936 100644 --- a/src/MarginTrading.Backend.Services/Services/MarketMakerService.cs +++ b/src/MarginTrading.Backend.Services/Services/MarketMakerService.cs @@ -1,8 +1,12 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Linq; using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.MarketMakerFeed; using MarginTrading.Backend.Core.MatchingEngines; +using MarginTrading.Backend.Core.Orders; using MarginTrading.Backend.Services.AssetPairs; using MarginTrading.Backend.Services.Infrastructure; using MarginTrading.Common.Extensions; diff --git a/src/MarginTrading.Backend.Services/Services/OrderBookSaveService.cs b/src/MarginTrading.Backend.Services/Services/OrderBookSaveService.cs index 610fd2fce..4db162284 100644 --- a/src/MarginTrading.Backend.Services/Services/OrderBookSaveService.cs +++ b/src/MarginTrading.Backend.Services/Services/OrderBookSaveService.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -6,8 +9,8 @@ using Common.Log; using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.Orderbooks; +using MarginTrading.Backend.Core.Settings; using MarginTrading.Backend.Services.Infrastructure; -using MarginTrading.Backend.Services.TradingConditions; namespace MarginTrading.Backend.Services { @@ -23,8 +26,10 @@ public class OrderBookSaveService : TimerPeriod public OrderBookSaveService( IMarginTradingBlobRepository blobRepository, OrderBookList orderBookList, + MarginTradingSettings marginTradingSettings, ILog log, - IContextFactory contextFactory) : base(nameof(OrderBookSaveService), 5000, log) + IContextFactory contextFactory) + : base(nameof(OrderBookSaveService), marginTradingSettings.BlobPersistence.OrderbooksDumpPeriodMilliseconds, log) { _blobRepository = blobRepository; _orderBookList = orderBookList; @@ -69,7 +74,7 @@ private async Task DumpToRepository() if (orderbookState != null) { - await _blobRepository.Write(LykkeConstants.StateBlobContainer, BlobName, orderbookState); + await _blobRepository.WriteAsync(LykkeConstants.StateBlobContainer, BlobName, orderbookState); } } catch (Exception ex) diff --git a/src/MarginTrading.Backend.Services/Services/OvernightMarginService.cs b/src/MarginTrading.Backend.Services/Services/OvernightMarginService.cs new file mode 100644 index 000000000..7ecb89d74 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Services/OvernightMarginService.cs @@ -0,0 +1,256 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Common; +using Common.Log; +using FluentScheduler; +using Lykke.Common.Log; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.DayOffSettings; +using MarginTrading.Backend.Core.Extensions; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Services.AssetPairs; +using MarginTrading.Backend.Services.Events; +using MarginTrading.Backend.Services.Infrastructure; +using MarginTrading.Backend.Services.Scheduling; +using MarginTrading.Backend.Services.TradingConditions; +using MarginTrading.Backend.Services.Workflow.Liquidation.Commands; +using MarginTrading.Common.Services; +using MoreLinq; + +namespace MarginTrading.Backend.Services.Services +{ + /// + public class OvernightMarginService : IOvernightMarginService + { + private readonly IDateService _dateService; + private readonly ITradingEngine _tradingEngine; + private readonly IAccountsCacheService _accountsCacheService; + private readonly IAccountUpdateService _accountUpdateService; + private readonly IScheduleSettingsCacheService _scheduleSettingsCacheService; + private readonly IOvernightMarginParameterContainer _overnightMarginParameterContainer; + private readonly ILog _log; + private readonly IEventChannel _marginCallEventChannel; + private readonly OvernightMarginSettings _overnightMarginSettings; + private readonly ICqrsSender _cqrsSender; + + private static readonly object LockObj = new object(); + + public OvernightMarginService( + IDateService dateService, + ITradingEngine tradingEngine, + IAccountsCacheService accountsCacheService, + IAccountUpdateService accountUpdateService, + IScheduleSettingsCacheService scheduleSettingsCacheService, + IOvernightMarginParameterContainer overnightMarginParameterContainer, + ILog log, + IEventChannel marginCallEventChannel, + OvernightMarginSettings overnightMarginSettings, + ICqrsSender cqrsSender) + { + _dateService = dateService; + _tradingEngine = tradingEngine; + _accountsCacheService = accountsCacheService; + _accountUpdateService = accountUpdateService; + _scheduleSettingsCacheService = scheduleSettingsCacheService; + _overnightMarginParameterContainer = overnightMarginParameterContainer; + _log = log; + _marginCallEventChannel = marginCallEventChannel; + _overnightMarginSettings = overnightMarginSettings; + _cqrsSender = cqrsSender; + } + + /// + public void ScheduleNext() + { + lock (LockObj) + { + //need to know in which OvernightMarginJobType we are now and the moment of next change + var platformTradingSchedule = _scheduleSettingsCacheService.GetPlatformTradingSchedule(); + + _overnightMarginParameterContainer.SetOvernightMarginParameterState(false); + + var currentDateTime = _dateService.Now(); + DateTime nextStart; + (DateTime Warn, DateTime Start, DateTime End) operatingInterval = default; + + //no disabled trading schedule items.. doing nothing + if (platformTradingSchedule.All(x => x.Enabled()) + //OR no disabled intervals in current compilation, schedule recheck + || !TryGetOperatingInterval(platformTradingSchedule, currentDateTime, out operatingInterval)) + { + nextStart = currentDateTime.Date.AddDays(1); + RestartFailedLiquidation(); + } + //schedule warning + else if (currentDateTime < operatingInterval.Warn) + { + nextStart = operatingInterval.Warn; + RestartFailedLiquidation(); + } + //schedule overnight margin parameter start + else if (currentDateTime < operatingInterval.Start) + { + HandleOvernightMarginWarnings(); + nextStart = operatingInterval.Start; + } + //schedule overnight margin parameter drop + else if (currentDateTime < operatingInterval.End) + { + _overnightMarginParameterContainer.SetOvernightMarginParameterState(true); + nextStart = operatingInterval.End; + + PlanEodJob(operatingInterval.Start, operatingInterval.End, currentDateTime); + } + else + { + _log.WriteFatalErrorAsync(nameof(OvernightMarginService), nameof(ScheduleNext), + new Exception( + $"Incorrect platform trading schedule! Need to fix it and restart the service. Check time: [{currentDateTime:s}], detected operation interval: [{operatingInterval.ToJson()}]")) + .Wait(); + return; + } + + _log.WriteInfo(nameof(OvernightMarginService), nameof(ScheduleNext), + $"Planning next check to [{nextStart:s}]." + + $" Current margin parameter state: [{_overnightMarginParameterContainer.GetOvernightMarginParameterState()}]." + + $" Check time: [{currentDateTime:s}]." + + (operatingInterval != default + ? $" Detected operation interval: [{operatingInterval.ToJson()}]." + : "")); + + JobManager.RemoveJob(nameof(OvernightMarginService)); + JobManager.AddJob(ScheduleNext, s => s + .WithName(nameof(OvernightMarginService)).NonReentrant().ToRunOnceAt(nextStart)); + + _log.WriteInfo(nameof(OvernightMarginService), nameof(ScheduleNext), + $@"All current job schedules: {string.Join(", ", + JobManager.AllSchedules.Select(x => $"[{x.Name}:{x.NextRun:O}:{x.Disabled}]"))}."); + } + } + + private void RestartFailedLiquidation() + { + _log.WriteInfo(nameof(OvernightMarginService), nameof(RestartFailedLiquidation), "Trying to restart failed liquidations"); + + var accounts = _accountsCacheService.GetAll().Where(a => a.IsInLiquidation()).ToArray(); + + _log.WriteInfo(nameof(OvernightMarginService), nameof(RestartFailedLiquidation), + $"{accounts.Length} accounts in liquidation state found"); + + foreach (var account in accounts) + { + _cqrsSender.SendCommandToSelf(new ResumeLiquidationInternalCommand + { + OperationId = account.LiquidationOperationId, + CreationTime = DateTime.UtcNow, + IsCausedBySpecialLiquidation = false, + ResumeOnlyFailed = true, + Comment = "Trying to resume liquidation because trading started" + }); + } + } + + private void PlanEodJob(DateTime operatingIntervalStart, DateTime operatingIntervalEnd, DateTime currentDateTime) + { + var eodTime = operatingIntervalStart.AddMinutes(_overnightMarginSettings.ActivationPeriodMinutes); + + if (currentDateTime < eodTime) + { + JobManager.RemoveJob(nameof(PlanEodJob)); + JobManager.AddJob(() => _tradingEngine.ProcessExpiredOrders(operatingIntervalEnd), + (s) => s.WithName(nameof(PlanEodJob)).NonReentrant().ToRunOnceAt(eodTime)); + } + else + { + JobManager.RemoveJob(nameof(PlanEodJob)); + JobManager.AddJob(() => _tradingEngine.ProcessExpiredOrders(operatingIntervalEnd), + (s) => s.WithName(nameof(PlanEodJob)).ToRunNow()); + } + } + + private void HandleOvernightMarginWarnings() + { + foreach (var account in _accountsCacheService.GetAll()) + { + var accountOvernightUsedMargin = _accountUpdateService.CalculateOvernightUsedMargin(account); + var accountLevel = account.GetAccountLevel(accountOvernightUsedMargin); + + if (accountLevel == AccountLevel.StopOut) + { + _marginCallEventChannel.SendEvent(this, + new MarginCallEventArgs(account, AccountLevel.OvernightMarginCall)); + } + } + } + + public bool TryGetOperatingInterval(List platformTrading, + DateTime currentDateTime, out (DateTime Warn, DateTime Start, DateTime End) resultingInterval) + { + //no disabled intervals ahead => false + if (!platformTrading.Any(x => x.End > currentDateTime && !x.Enabled())) + { + resultingInterval = default; + return false; + } + + //add "all-the-time-enabled" time interval with min rank + platformTrading.Add(new CompiledScheduleTimeInterval( + new ScheduleSettings { Rank = int.MinValue, IsTradeEnabled = true, }, + DateTime.MinValue, DateTime.MaxValue)); + + //find current active interval + var currentActiveInterval = platformTrading + .Where(x => x.Start <= currentDateTime && x.End > currentDateTime) + .MaxBy(x => x.Schedule.Rank); + + //find changing time: MIN(end of current, start of next with higher Rank) + //if the same state => continue + // other state => ON to OFF => Warn, Start, End + // OFF to ON => End & Warn, Start => DateTime.Min + DateTime? endOfInterval = null; + foreach (var nextWithHigherRank in platformTrading.Where(x => x.Start > currentDateTime + && x.Start < currentActiveInterval.End + && x.Schedule.Rank > currentActiveInterval.Schedule.Rank) + .OrderBy(x => x.Start) + .ThenByDescending(x => x.Schedule.Rank)) + { + //if the state is the same - it changes nothing but shifts start border + if (currentActiveInterval.Enabled() == nextWithHigherRank.Enabled()) + { + endOfInterval = nextWithHigherRank.End; + continue; + } + + //if interval end before the shifted border we can skip it + if (endOfInterval.HasValue && nextWithHigherRank.End < endOfInterval.Value) + { + continue; + } + + if (nextWithHigherRank.Enabled()) + { + resultingInterval = (DateTime.MinValue, DateTime.MinValue, nextWithHigherRank.Start); + return true; + } + + var resultingStart = endOfInterval ?? nextWithHigherRank.Start; + + resultingInterval = (resultingStart.Subtract(TimeSpan.FromMinutes( + _overnightMarginSettings.WarnPeriodMinutes + _overnightMarginSettings.ActivationPeriodMinutes)), + resultingStart.Subtract(TimeSpan.FromMinutes(_overnightMarginSettings.ActivationPeriodMinutes)), + nextWithHigherRank.End); + return true; + } + + resultingInterval = (DateTime.MinValue, DateTime.MinValue, currentActiveInterval.End); + return true; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Services/OvernightSwapNotificationService.cs b/src/MarginTrading.Backend.Services/Services/OvernightSwapNotificationService.cs deleted file mode 100644 index e0ae27974..000000000 --- a/src/MarginTrading.Backend.Services/Services/OvernightSwapNotificationService.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using Common; -using Common.Log; -using Lykke.Common; -using Lykke.Service.ClientAccount.Client; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Repositories; -using MarginTrading.Backend.Services.TradingConditions; -using MarginTrading.Common.Services; -using MarginTrading.Common.Services.Client; - -namespace MarginTrading.Backend.Services.Services -{ - public class OvernightSwapNotificationService : IOvernightSwapNotificationService - { - private readonly IOvernightSwapCache _overnightSwapCache; - private readonly IAssetPairsCache _assetPairsCache; - private readonly IAccountsCacheService _accountsCacheService; - private readonly IEmailService _emailService; - private readonly IClientAccountService _clientAccountService; - private readonly IDateService _dateService; - - private readonly IOvernightSwapHistoryRepository _overnightSwapHistoryRepository; - - private readonly IThreadSwitcher _threadSwitcher; - - private readonly ILog _log; - - private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); - - public OvernightSwapNotificationService( - IOvernightSwapCache overnightSwapCache, - IAssetPairsCache assetPairsCache, - IAccountsCacheService accountsCacheService, - IEmailService emailService, - IClientAccountService clientAccountService, - IDateService dateService, - - IOvernightSwapHistoryRepository overnightSwapHistoryRepository, - - IThreadSwitcher threadSwitcher, - - ILog log) - { - _overnightSwapCache = overnightSwapCache; - _assetPairsCache = assetPairsCache; - _accountsCacheService = accountsCacheService; - _emailService = emailService; - _clientAccountService = clientAccountService; - _dateService = dateService; - - _overnightSwapHistoryRepository = overnightSwapHistoryRepository; - - _threadSwitcher = threadSwitcher; - - _log = log; - } - - public void PerformEmailNotification(DateTime calculationTime) - { - _threadSwitcher.SwitchThread(async () => - { - await _semaphore.WaitAsync(); - - try - { - var processedCalculations = (await _overnightSwapHistoryRepository.GetAsync(calculationTime, null)) - .Where(x => x.IsSuccess && x.Time >= calculationTime) - .ToList(); - - var notifications = processedCalculations - .GroupBy(x => x.ClientId) - .Select(c => new OvernightSwapNotification - { - CliendId = c.Key, - CalculationsByAccount = c.GroupBy(a => a.AccountId) - .Select(a => - { - var account = _accountsCacheService.Get(c.Key, a.Key); - if (account == null) - return null; - return new OvernightSwapNotification.AccountCalculations() - { - AccountId = a.Key, - AccountCurrency = account.BaseAssetId, - Calculations = a.Select(calc => - { - var instrumentName = _assetPairsCache.GetAssetPairByIdOrDefault(calc.Instrument)?.Name - ?? calc.Instrument; - return new OvernightSwapNotification.SingleCalculation - { - Instrument = instrumentName, - Direction = calc.Direction == OrderDirection.Buy ? "Long" : "Short", - Volume = calc.Volume, - SwapRate = calc.SwapRate, - Cost = calc.Value, - PositionIds = calc.OpenOrderIds, - }; - }).ToList() - }; - }).Where(x => x != null).ToList() - } - ); - - var clientsWithIncorrectMail = new List(); - var clientsSentEmails = new List(); - foreach (var notification in notifications) - { - try - { - var clientEmail = await _clientAccountService.GetEmail(notification.CliendId); - if (string.IsNullOrEmpty(clientEmail)) - { - clientsWithIncorrectMail.Add(notification.CliendId); - continue; - } - - await _emailService.SendOvernightSwapEmailAsync(clientEmail, notification); - clientsSentEmails.Add(notification.CliendId); - } - catch (Exception e) - { - await _log.WriteErrorAsync(nameof(OvernightSwapNotificationService), - nameof(PerformEmailNotification), e, DateTime.UtcNow); - } - } - - if (clientsWithIncorrectMail.Any()) - await _log.WriteWarningAsync(nameof(OvernightSwapNotificationService), nameof(PerformEmailNotification), - $"Emails of some clients are incorrect: {string.Join(", ", clientsWithIncorrectMail)}.", DateTime.UtcNow); - if (clientsSentEmails.Any()) - await _log.WriteInfoAsync(nameof(OvernightSwapNotificationService), nameof(PerformEmailNotification), - $"Emails sent to: {string.Join(", ", clientsSentEmails)}.", DateTime.UtcNow); - } - finally - { - _semaphore.Release(); - } - }); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Services/OvernightSwapService.cs b/src/MarginTrading.Backend.Services/Services/OvernightSwapService.cs deleted file mode 100644 index 1d329dc21..000000000 --- a/src/MarginTrading.Backend.Services/Services/OvernightSwapService.cs +++ /dev/null @@ -1,311 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Common; -using Common.Log; -using Lykke.Common; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Repositories; -using MarginTrading.Backend.Core.Settings; -using MarginTrading.Backend.Core.TradingConditions; -using MarginTrading.Backend.Services.Helpers; -using MarginTrading.Backend.Services.TradingConditions; -using MarginTrading.Common.Services; -using Newtonsoft.Json; - -namespace MarginTrading.Backend.Services.Services -{ - /// - /// Take care of overnight swap calculation and charging. - /// - public class OvernightSwapService : IOvernightSwapService - { - private readonly IOvernightSwapCache _overnightSwapCache; - private readonly IAssetPairsCache _assetPairsCache; - private readonly IAccountAssetsCacheService _accountAssetsCacheService; - private readonly IAccountsCacheService _accountsCacheService; - private readonly ICommissionService _commissionService; - private readonly IOvernightSwapNotificationService _overnightSwapNotificationService; - - private readonly IOvernightSwapStateRepository _overnightSwapStateRepository; - private readonly IOvernightSwapHistoryRepository _overnightSwapHistoryRepository; - private readonly IOrderReader _orderReader; - private readonly IThreadSwitcher _threadSwitcher; - private readonly IDateService _dateService; - private readonly AccountManager _accountManager; - private readonly MarginSettings _marginSettings; - private readonly ILog _log; - - private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); - - private DateTime _currentStartTimestamp; - - public OvernightSwapService( - IOvernightSwapCache overnightSwapCache, - IAssetPairsCache assetPairsCache, - IAccountAssetsCacheService accountAssetsCacheService, - IAccountsCacheService accountsCacheService, - ICommissionService commissionService, - IOvernightSwapNotificationService overnightSwapNotificationService, - - IOvernightSwapStateRepository overnightSwapStateRepository, - IOvernightSwapHistoryRepository overnightSwapHistoryRepository, - IOrderReader orderReader, - IThreadSwitcher threadSwitcher, - IDateService dateService, - AccountManager accountManager, - MarginSettings marginSettings, - ILog log) - { - _overnightSwapCache = overnightSwapCache; - _assetPairsCache = assetPairsCache; - _accountAssetsCacheService = accountAssetsCacheService; - _accountsCacheService = accountsCacheService; - _commissionService = commissionService; - _overnightSwapNotificationService = overnightSwapNotificationService; - - _overnightSwapStateRepository = overnightSwapStateRepository; - _overnightSwapHistoryRepository = overnightSwapHistoryRepository; - _orderReader = orderReader; - _threadSwitcher = threadSwitcher; - _dateService = dateService; - _accountManager = accountManager; - _marginSettings = marginSettings; - _log = log; - } - - public void Start() - { - //initialize cache from storage - var savedState = _overnightSwapStateRepository.GetAsync().GetAwaiter().GetResult().ToList(); - _overnightSwapCache.Initialize(savedState.Select(OvernightSwapCalculation.Create)); - - //start calculation - CalculateAndChargeSwaps(); - - //TODO if server was down more that a day.. calc N days - } - - /// - /// Filter orders that are already calculated - /// - /// - private IEnumerable GetOrdersForCalculation() - { - //read orders syncronously - var openOrders = _orderReader.GetActive(); - - //prepare the list of orders - var lastInvocationTime = CalcLastInvocationTime(); - var calculatedIds = _overnightSwapCache.GetAll().Where(x => x.IsSuccess && x.Time >= lastInvocationTime) - .SelectMany(x => x.OpenOrderIds).ToHashSet(); - //select only non-calculated orders, changed before current invocation time - var filteredOrders = openOrders.Where(x => !calculatedIds.Contains(x.Id)); - - //detect orders for which last calculation failed and it was closed - var failedClosedOrders = _overnightSwapHistoryRepository.GetAsync(lastInvocationTime, _currentStartTimestamp) - .GetAwaiter().GetResult() - .Where(x => !x.IsSuccess).SelectMany(x => x.OpenOrderIds) - .Except(openOrders.Select(y => y.Id)).ToList(); - if (failedClosedOrders.Any()) - { - _log.WriteErrorAsync(nameof(OvernightSwapService), nameof(GetOrdersForCalculation), new Exception( - $"Overnight swap calculation failed for some orders and they were closed before recalculation: {string.Join(", ", failedClosedOrders)}."), - DateTime.UtcNow).GetAwaiter().GetResult(); - } - - return filteredOrders; - } - - public void CalculateAndChargeSwaps() - { - _currentStartTimestamp = _dateService.Now(); - - var filteredOrders = GetOrdersForCalculation().ToList(); - - //start calculation in a separate thread - _threadSwitcher.SwitchThread(async () => - { - await _semaphore.WaitAsync(); - - try - { - await _log.WriteInfoAsync(nameof(OvernightSwapService), nameof(CalculateAndChargeSwaps), - $"Started, # of orders: {filteredOrders.Count}.", DateTime.UtcNow); - - foreach (var accountOrders in filteredOrders.GroupBy(x => x.AccountId)) - { - var clientId = accountOrders.First().ClientId; - MarginTradingAccount account; - try - { - account = _accountsCacheService.Get(clientId, accountOrders.Key); - } - catch (Exception ex) - { - await ProcessFailedOrders(accountOrders.ToList(), clientId, accountOrders.Key, null, ex); - continue; - } - - foreach (var ordersByInstrument in accountOrders.GroupBy(x => x.Instrument)) - { - var firstOrder = ordersByInstrument.FirstOrDefault(); - IAccountAssetPair accountAssetPair; - try - { - accountAssetPair = _accountAssetsCacheService.GetAccountAsset( - firstOrder?.TradingConditionId, firstOrder?.AccountAssetId, firstOrder?.Instrument); - } - catch (Exception ex) - { - await ProcessFailedOrders(ordersByInstrument.ToList(), clientId, account.Id, ordersByInstrument.Key, ex); - continue; - } - - foreach (OrderDirection direction in Enum.GetValues(typeof(OrderDirection))) - { - var orders = ordersByInstrument.Where(order => order.GetOrderType() == direction).ToList(); - if (orders.Count == 0) - continue; - - try - { - await ProcessOrders(orders, ordersByInstrument.Key, account, accountAssetPair, direction); - } - catch (Exception ex) - { - await ProcessFailedOrders(orders, clientId, account.Id, ordersByInstrument.Key, ex); - } - } - } - } - - await ClearOldState(); - - await _log.WriteInfoAsync(nameof(OvernightSwapService), nameof(CalculateAndChargeSwaps), - $"Finished, # of calculations: {_overnightSwapCache.GetAll().Count(x => x.Time >= _currentStartTimestamp)}.", DateTime.UtcNow); - } - finally - { - _semaphore.Release(); - } - - if (_marginSettings.SendOvernightSwapEmails) - _overnightSwapNotificationService.PerformEmailNotification(_currentStartTimestamp); - }); - } - - /// - /// Calculate overnight swaps for account/instrument/direction order package. - /// - /// - /// - /// - /// - /// - /// - private async Task ProcessOrders(IReadOnlyList orders, string instrument, IMarginTradingAccount account, - IAccountAssetPair accountAssetPair, OrderDirection direction) - { - IReadOnlyList filteredOrders = orders.ToList(); - - //check if swaps had already been taken - var lastCalcExists = _overnightSwapCache.TryGet(OvernightSwapCalculation.GetKey(account.Id, instrument, direction), - out var lastCalc) - && lastCalc.Time >= CalcLastInvocationTime(); - if (lastCalcExists) - { - await _log.WriteErrorAsync(nameof(OvernightSwapService), nameof(ProcessOrders), - new Exception($"Overnight swaps had already been taken, filtering: {JsonConvert.SerializeObject(lastCalc)}"), DateTime.UtcNow); - - filteredOrders = orders.Where(x => !lastCalc.OpenOrderIds.Contains(x.Id)).ToList(); - } - - //calc swaps - var swapRate = direction == OrderDirection.Buy ? accountAssetPair.OvernightSwapLong : accountAssetPair.OvernightSwapShort; - if (swapRate == 0) - return; - - var total = filteredOrders.Sum(order => _commissionService.GetOvernightSwap(order, swapRate)); - if (total == 0) - return; - - //create calculation obj & add to cache - var volume = filteredOrders.Select(x => Math.Abs(x.Volume)).Sum(); - var calculation = OvernightSwapCalculation.Create(account.ClientId, account.Id, instrument, - filteredOrders.Select(order => order.Id).ToList(), _currentStartTimestamp, true, null, volume, total, swapRate, direction); - - //charge comission - var instrumentName = _assetPairsCache.GetAssetPairByIdOrDefault(accountAssetPair.Instrument)?.Name - ?? accountAssetPair.Instrument; - await _accountManager.UpdateBalanceAsync( - account: account, - amount: - total, - historyType: AccountHistoryType.Swap, - comment : $"{instrumentName} {(direction == OrderDirection.Buy ? "long" : "short")} swaps. Volume: {volume}. Positions count: {filteredOrders.Count}. Rate: {swapRate}. Time: {_currentStartTimestamp:u}.", - auditLog: calculation.ToJson()); - - //update calculation state if previous existed - var newCalcState = lastCalcExists - ? OvernightSwapCalculation.Update(calculation, lastCalc) - : OvernightSwapCalculation.Create(calculation); - - //add to cache - _overnightSwapCache.AddOrReplace(newCalcState); - - //write state and log - await _overnightSwapStateRepository.AddOrReplaceAsync(newCalcState); - await _overnightSwapHistoryRepository.AddAsync(calculation); - } - - /// - /// Log failed orders. - /// - /// - /// - /// - /// - /// - /// - private async Task ProcessFailedOrders(IReadOnlyList orders, string clientId, string accountId, - string instrument, Exception exception) - { - var volume = orders.Select(x => Math.Abs(x.Volume)).Sum(); - var failedCalculation = OvernightSwapCalculation.Create(clientId, accountId, instrument, - orders.Select(o => o.Id).ToList(), _currentStartTimestamp, false, exception, volume); - - await _log.WriteErrorAsync(nameof(OvernightSwapService), nameof(ProcessFailedOrders), - new Exception(failedCalculation.ToJson()), DateTime.UtcNow); - - await _overnightSwapHistoryRepository.AddAsync(failedCalculation); - } - - /// - /// Return last invocation time. - /// - private DateTime CalcLastInvocationTime() - { - var dt = _currentStartTimestamp; - var settingsCalcTime = (_marginSettings.OvernightSwapCalculationTime.Hours, - _marginSettings.OvernightSwapCalculationTime.Minutes); - - var result = new DateTime(dt.Year, dt.Month, dt.Day, settingsCalcTime.Hours, settingsCalcTime.Minutes, 0) - .AddDays(dt.Hour > settingsCalcTime.Hours || (dt.Hour == settingsCalcTime.Hours && dt.Minute >= settingsCalcTime.Minutes) - ? 0 : -1); - return result; - } - - private async Task ClearOldState() - { - var oldEntries = _overnightSwapCache.GetAll().Where(x => x.Time < DateTime.UtcNow.AddDays(-2)); - - foreach(var obj in oldEntries) - { - _overnightSwapCache.Remove(obj); - await _overnightSwapStateRepository.DeleteAsync(obj); - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Services/ReportService.cs b/src/MarginTrading.Backend.Services/Services/ReportService.cs new file mode 100644 index 000000000..2229441c6 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Services/ReportService.cs @@ -0,0 +1,46 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using MarginTrading.Backend.Contracts.Positions; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Mappers; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Services.Services +{ + public class ReportService : IReportService + { + private readonly IAccountsCacheService _accountsCacheService; + private readonly OrdersCache _ordersCache; + private readonly IOpenPositionsRepository _openPositionsRepository; + private readonly IAccountStatRepository _accountStatRepository; + + public ReportService( + IAccountsCacheService accountsCacheService, + OrdersCache ordersCache, + IOpenPositionsRepository openPositionsRepository, + IAccountStatRepository accountStatRepository) + { + _accountsCacheService = accountsCacheService; + _ordersCache = ordersCache; + _openPositionsRepository = openPositionsRepository; + _accountStatRepository = accountStatRepository; + } + + public async Task DumpReportData() + { + var positions = _ordersCache.GetPositions(); + var accountStat = _accountsCacheService.GetAll(); + + await Task.WhenAll( + _openPositionsRepository.Dump(positions), + _accountStatRepository.Dump(accountStat)); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Services/ScheduleControlService.cs b/src/MarginTrading.Backend.Services/Services/ScheduleControlService.cs new file mode 100644 index 000000000..1a4cb49aa --- /dev/null +++ b/src/MarginTrading.Backend.Services/Services/ScheduleControlService.cs @@ -0,0 +1,139 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Common; +using Common.Log; +using FluentScheduler; +using MarginTrading.Backend.Core.DayOffSettings; +using MarginTrading.Backend.Core.Extensions; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Services.AssetPairs; +using MarginTrading.Common.Services; +using MoreLinq; + +namespace MarginTrading.Backend.Services.Services +{ + public class ScheduleControlService : IScheduleControlService + { + private readonly IScheduleSettingsCacheService _scheduleSettingsCacheService; + private readonly ILog _log; + private readonly IDateService _dateService; + + private static readonly object LockObj = new object(); + + public ScheduleControlService(IScheduleSettingsCacheService scheduleSettingsCacheService, ILog log, IDateService dateService) + { + _scheduleSettingsCacheService = scheduleSettingsCacheService; + _log = log; + _dateService = dateService; + } + + /// + public void ScheduleNext() + { + lock (LockObj) + { + var currentDateTime = _dateService.Now(); + + //need to know in which OvernightMarginJobType we are now and the moment of next change + var marketsSchedule = _scheduleSettingsCacheService.GetMarketsTradingSchedule(); + + var nextStart = TryGetClosestPoint(marketsSchedule, currentDateTime); + + _log.WriteInfo(nameof(ScheduleControlService), nameof(ScheduleNext), + $"Planning next check to [{nextStart:s}]." + + $" Check time: [{currentDateTime:s}]."); + + _scheduleSettingsCacheService.HandleMarketStateChanges(currentDateTime); + + JobManager.RemoveJob(nameof(ScheduleControlService)); + JobManager.AddJob(ScheduleNext, s => s + .WithName(nameof(ScheduleControlService)).NonReentrant().ToRunOnceAt(nextStart)); + } + } + + public DateTime TryGetClosestPoint(Dictionary> marketsSchedule, + DateTime currentDateTime) + { + var intervals = new Dictionary(); + + foreach (var (marketId, compiledScheduleTimeIntervals) in marketsSchedule) + { + if (!compiledScheduleTimeIntervals.All(x => x.Enabled()) + && TryGetOperatingInterval(compiledScheduleTimeIntervals, currentDateTime, out var interval)) + { + intervals.Add(marketId, interval); + } + } + + var followingPoints = intervals.Values.SelectMany(x => new[] {x.Start, x.End}) + .Where(x => x > currentDateTime) + .ToList(); + return followingPoints.Any() ? followingPoints.Min() : currentDateTime.AddDays(1); + } + + public bool TryGetOperatingInterval(List compiledScheduleTimeIntervals, + DateTime currentDateTime, out (DateTime Start, DateTime End) resultingInterval) + { + //no disabled intervals ahead => false + if (!compiledScheduleTimeIntervals.Any(x => x.End > currentDateTime && !x.Enabled())) + { + resultingInterval = default; + return false; + } + + //add "all-the-time-enabled" time interval with min rank + compiledScheduleTimeIntervals.Add(new CompiledScheduleTimeInterval( + new ScheduleSettings { Rank = int.MinValue, IsTradeEnabled = true, }, + DateTime.MinValue, DateTime.MaxValue)); + + //find current active interval + var currentActiveInterval = compiledScheduleTimeIntervals + .Where(x => x.Start <= currentDateTime && x.End > currentDateTime) + .MaxBy(x => x.Schedule.Rank); + + //find changing time: MIN(end of current, start of next with higher Rank) + //if the same state => continue + // other state => ON to OFF => Start, End + // OFF to ON => End, Start => DateTime.Min + DateTime? endOfInterval = null; + foreach (var nextWithHigherRank in compiledScheduleTimeIntervals.Where(x => x.Start > currentDateTime + && x.Start < currentActiveInterval.End + && x.Schedule.Rank > currentActiveInterval.Schedule.Rank) + .OrderBy(x => x.Start) + .ThenByDescending(x => x.Schedule.Rank)) + { + //if the state is the same - it changes nothing but shifts start border + if (currentActiveInterval.Enabled() == nextWithHigherRank.Enabled()) + { + endOfInterval = nextWithHigherRank.End; + continue; + } + + //if interval end before the shifted border we can skip it + if (endOfInterval.HasValue && nextWithHigherRank.End < endOfInterval.Value) + { + continue; + } + + if (nextWithHigherRank.Enabled()) + { + resultingInterval = (DateTime.MinValue, nextWithHigherRank.Start); + return true; + } + + var resultingStart = endOfInterval ?? nextWithHigherRank.Start; + + resultingInterval = (resultingStart, nextWithHigherRank.End); + return true; + } + + resultingInterval = (DateTime.MinValue, currentActiveInterval.End); + return true; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Services/SpecialLiquidationService.cs b/src/MarginTrading.Backend.Services/Services/SpecialLiquidationService.cs new file mode 100644 index 000000000..3b60e3518 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Services/SpecialLiquidationService.cs @@ -0,0 +1,80 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; +using Lykke.Common; +using Lykke.Common.Chaos; +using MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Events; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Services.Infrastructure; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Services.Services +{ + public class SpecialLiquidationService : ISpecialLiquidationService + { + private readonly ICqrsSender _cqrsSender; + private readonly IDateService _dateService; + private readonly IThreadSwitcher _threadSwitcher; + private readonly SpecialLiquidationSettings _specialLiquidationSettings; + private readonly CqrsContextNamesSettings _cqrsContextNamesSettings; + private readonly IQuoteCacheService _quoteCacheService; + private readonly IChaosKitty _chaosKitty; + + public SpecialLiquidationService( + ICqrsSender cqrsSender, + IDateService dateService, + IThreadSwitcher threadSwitcher, + SpecialLiquidationSettings specialLiquidationSettings, + CqrsContextNamesSettings cqrsContextNamesSettings, + IQuoteCacheService quoteCacheService, + IChaosKitty chaosKitty) + { + _cqrsSender = cqrsSender; + _dateService = dateService; + _threadSwitcher = threadSwitcher; + _specialLiquidationSettings = specialLiquidationSettings; + _cqrsContextNamesSettings = cqrsContextNamesSettings; + _quoteCacheService = quoteCacheService; + _chaosKitty = chaosKitty; + } + + public void FakeGetPriceForSpecialLiquidation(string operationId, string instrument, decimal volume) + { + _threadSwitcher.SwitchThread(async () => + { + try + { + _chaosKitty.Meow($"FakeGetPriceForSpecialLiquidation : {operationId} : {instrument} : {volume}"); + + var quote = _quoteCacheService.GetQuote(instrument); + + var price = (volume > 0 ? quote.Ask : quote.Bid) * _specialLiquidationSettings.FakePriceMultiplier; + + await Task.Delay(1000);//waiting for the state to be saved into the repo + + _cqrsSender.PublishEvent(new PriceForSpecialLiquidationCalculatedEvent + { + OperationId = operationId, + CreationTime = _dateService.Now(), + Instrument = instrument, + Volume = volume, + Price = price, + }, _cqrsContextNamesSettings.Gavel); + } + catch (Exception e) + { + _cqrsSender.PublishEvent(new PriceForSpecialLiquidationCalculationFailedEvent + { + OperationId = operationId, + CreationTime = _dateService.Now(), + Reason = e.Message + }); + } + }); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Services/ValidateOrderService.cs b/src/MarginTrading.Backend.Services/Services/ValidateOrderService.cs index 85197b081..463ecead8 100644 --- a/src/MarginTrading.Backend.Services/Services/ValidateOrderService.cs +++ b/src/MarginTrading.Backend.Services/Services/ValidateOrderService.cs @@ -1,12 +1,27 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using MarginTrading.Backend.Contracts.Orders; using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.Exceptions; +using MarginTrading.Backend.Core.MatchingEngines; using MarginTrading.Backend.Core.Messages; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Core.Trading; using MarginTrading.Backend.Core.TradingConditions; using MarginTrading.Backend.Services.AssetPairs; using MarginTrading.Backend.Services.Helpers; using MarginTrading.Backend.Services.TradingConditions; +using MarginTrading.Common.Extensions; +using MarginTrading.Common.Services; +// ReSharper disable ParameterOnlyUsedForPreconditionCheck.Local namespace MarginTrading.Backend.Services { @@ -15,233 +30,636 @@ public class ValidateOrderService : IValidateOrderService private readonly IQuoteCacheService _quoteCashService; private readonly IAccountUpdateService _accountUpdateService; private readonly IAccountsCacheService _accountsCacheService; - private readonly IAccountAssetsCacheService _accountAssetsCacheService; + private readonly ITradingInstrumentsCacheService _tradingInstrumentsCache; private readonly IAssetPairsCache _assetPairsCache; private readonly OrdersCache _ordersCache; private readonly IAssetPairDayOffService _assetDayOffService; + private readonly IIdentityGenerator _identityGenerator; + private readonly IDateService _dateService; + private readonly MarginTradingSettings _marginSettings; + private readonly ICfdCalculatorService _cfdCalculatorService; public ValidateOrderService( IQuoteCacheService quoteCashService, IAccountUpdateService accountUpdateService, IAccountsCacheService accountsCacheService, - IAccountAssetsCacheService accountAssetsCacheService, + ITradingInstrumentsCacheService accountAssetsCacheService, IAssetPairsCache assetPairsCache, OrdersCache ordersCache, - IAssetPairDayOffService assetDayOffService) + IAssetPairDayOffService assetDayOffService, + IIdentityGenerator identityGenerator, + IDateService dateService, + MarginTradingSettings marginSettings, + ICfdCalculatorService cfdCalculatorService) { _quoteCashService = quoteCashService; _accountUpdateService = accountUpdateService; _accountsCacheService = accountsCacheService; - _accountAssetsCacheService = accountAssetsCacheService; + _tradingInstrumentsCache = accountAssetsCacheService; _assetPairsCache = assetPairsCache; _ordersCache = ordersCache; _assetDayOffService = assetDayOffService; + _identityGenerator = identityGenerator; + _dateService = dateService; + _marginSettings = marginSettings; + _cfdCalculatorService = cfdCalculatorService; } - - //has to be beyond global lock - public void Validate(Order order) + + + #region Base validations + + public async Task<(Order order, List relatedOrders)> ValidateRequestAndCreateOrders( + OrderPlaceRequest request) { - #region Validate input params + #region Validate properties - if (_assetDayOffService.IsDayOff(order.Instrument)) + if (request.Volume == 0) { - throw new ValidateOrderException(OrderRejectReason.NoLiquidity, "Trades for instrument are not available"); + throw new ValidateOrderException(OrderRejectReason.InvalidVolume, "Volume can not be 0"); } - if (order.Volume == 0) + var assetPair = GetAssetPairIfAvailableForTrading(request.InstrumentId, request.Type.ToType(), + request.ForceOpen, false); + + if (request.Type != OrderTypeContract.Market || request.AdditionalInfo.IsCancellationTrade(out _)) { - throw new ValidateOrderException(OrderRejectReason.InvalidVolume, "Volume cannot be 0"); - } + if (!request.Price.HasValue) + { + throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice, + $"Price is required for {request.Type} order"); + } + else + { + request.Price = Math.Round(request.Price.Value, assetPair.Accuracy); - var asset = _assetPairsCache.GetAssetPairByIdOrDefault(order.Instrument); - if (asset == null) + if (request.Price <= 0) + { + throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice, + $"Price should be more than 0"); + } + } + } + else { - throw new ValidateOrderException(OrderRejectReason.InvalidInstrument, "Instrument not found"); + //always ignore price for market orders + request.Price = null; } - - var account = _accountsCacheService.TryGet(order.ClientId, order.AccountId); + + var account = _accountsCacheService.TryGet(request.AccountId); if (account == null) { throw new ValidateOrderException(OrderRejectReason.InvalidAccount, "Account not found"); } - - if (!_quoteCashService.TryGetQuoteById(order.Instrument, out var quote)) + + ValidateValidity(request.Validity, request.Type.ToType()); + + ITradingInstrument tradingInstrument; + try { - throw new ValidateOrderException(OrderRejectReason.NoLiquidity, "Quote not found"); + tradingInstrument = + _tradingInstrumentsCache.GetTradingInstrument(account.TradingConditionId, assetPair.Id); + } + catch + { + throw new ValidateOrderException(OrderRejectReason.InvalidInstrument, + $"Instrument {assetPair.Id} is not available for trading on selected account"); + } + + if (tradingInstrument.DealMinLimit > 0 && Math.Abs(request.Volume) < tradingInstrument.DealMinLimit) + { + throw new ValidateOrderException(OrderRejectReason.MinOrderSizeLimit, + $"The minimum volume of a single order is limited to {tradingInstrument.DealMinLimit} {tradingInstrument.Instrument}."); + } + + //set special account-quote instrument +// if (_assetPairsCache.TryGetAssetPairQuoteSubst(order.AccountAssetId, order.Instrument, +// order.LegalEntity, out var substAssetPair)) +// { +// order.MarginCalcInstrument = substAssetPair.Id; +// } + if (string.IsNullOrWhiteSpace(request.CorrelationId)) + { + request.CorrelationId = _identityGenerator.GenerateGuid(); } #endregion - - order.AssetAccuracy = asset.Accuracy; - order.AccountAssetId = account.BaseAssetId; - order.TradingConditionId = account.TradingConditionId; - order.LegalEntity = account.LegalEntity; - - //check ExpectedOpenPrice for pending order - if (order.ExpectedOpenPrice.HasValue) + + var equivalentSettings = GetReportingEquivalentPricesSettings(account.LegalEntity); + + if (request.Type == OrderTypeContract.StopLoss || + request.Type == OrderTypeContract.TakeProfit || + request.Type == OrderTypeContract.TrailingStop) + { + var order = await ValidateAndGetSlOrTpOrder(request, request.Type, request.Price, equivalentSettings, + null); + + return (order, new List()); + } + + var initialParameters = await GetOrderInitialParameters(request.InstrumentId, account.LegalEntity, + equivalentSettings, account.BaseAssetId); + + var volume = request.Direction == OrderDirectionContract.Sell ? -Math.Abs(request.Volume) + : request.Direction == OrderDirectionContract.Buy ? Math.Abs(request.Volume) + : request.Volume; + + var originator = GetOriginator(request.Originator); + + var baseOrder = new Order(initialParameters.Id, initialParameters.Code, request.InstrumentId, volume, + initialParameters.Now, initialParameters.Now, request.Validity, account.Id, account.TradingConditionId, + account.BaseAssetId, request.Price, equivalentSettings.EquivalentAsset, OrderFillType.FillOrKill, + string.Empty, account.LegalEntity, request.ForceOpen, request.Type.ToType(), + request.ParentOrderId, null, originator, initialParameters.EquivalentPrice, + initialParameters.FxPrice, initialParameters.FxAssetPairId, initialParameters.FxToAssetPairDirection, + OrderStatus.Placed, request.AdditionalInfo, request.CorrelationId); + + ValidateBaseOrderPrice(baseOrder, baseOrder.Price); + + var relatedOrders = new List(); + + if (request.StopLoss.HasValue) { - if (_assetDayOffService.ArePendingOrdersDisabled(order.Instrument)) + request.StopLoss = Math.Round(request.StopLoss.Value, assetPair.Accuracy); + + if (request.StopLoss <= 0) { - throw new ValidateOrderException(OrderRejectReason.NoLiquidity, "Trades for instrument are not available"); + throw new ValidateOrderException(OrderRejectReason.InvalidStoploss, + $"StopLoss should be more then 0"); } + + var orderType = request.UseTrailingStop ? OrderTypeContract.TrailingStop : OrderTypeContract.StopLoss; - if (order.ExpectedOpenPrice <= 0) + var sl = await ValidateAndGetSlOrTpOrder(request, orderType, request.StopLoss, + equivalentSettings, baseOrder); + + if (sl != null) + relatedOrders.Add(sl); + } + + if (request.TakeProfit.HasValue) + { + request.TakeProfit = Math.Round(request.TakeProfit.Value, assetPair.Accuracy); + + if (request.TakeProfit <= 0) { - throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice, "Incorrect expected open price"); + throw new ValidateOrderException(OrderRejectReason.InvalidTakeProfit, + $"TakeProfit should be more then 0"); } - order.ExpectedOpenPrice = Math.Round(order.ExpectedOpenPrice ?? 0, order.AssetAccuracy); + var tp = await ValidateAndGetSlOrTpOrder(request, OrderTypeContract.TakeProfit, request.TakeProfit, + equivalentSettings, baseOrder); + + if (tp != null) + relatedOrders.Add(tp); + } + + return (baseOrder, relatedOrders); + } - if (order.GetOrderType() == OrderDirection.Buy && order.ExpectedOpenPrice > quote.Ask || - order.GetOrderType() == OrderDirection.Sell && order.ExpectedOpenPrice < quote.Bid) + public void ValidateForceOpenChange(Order order, bool? forceOpen) + { + if (!forceOpen.HasValue || forceOpen.Value == order.ForceOpen) + { + return; + } + + if (!order.IsBasicPendingOrder() && forceOpen.Value) + { + throw new ValidateOrderException(OrderRejectReason.None, + "Force open cannot be set to true for related order"); + } + + if (forceOpen.Value && order.Direction == OrderDirection.Sell) + { + var tradingInstrument = + _tradingInstrumentsCache.GetTradingInstrument(order.TradingConditionId, order.AssetPairId); + if (!tradingInstrument.ShortPosition) { - var reasonText = order.GetOrderType() == OrderDirection.Buy - ? string.Format(MtMessages.Validation_PriceAboveAsk, order.ExpectedOpenPrice, quote.Ask) - : string.Format(MtMessages.Validation_PriceBelowBid, order.ExpectedOpenPrice, quote.Bid); + throw new ValidateOrderException(OrderRejectReason.ShortPositionsDisabled, + $"Short positions are disabled for {tradingInstrument.Instrument}."); + } + } + } + + public void ValidateValidity(DateTime? validity, OrderType orderType) + { + if (validity.HasValue && + orderType != OrderType.Market && + validity.Value.Date < _dateService.Now().Date) + { + throw new ValidateOrderException(OrderRejectReason.InvalidValidity, "Invalid validity date"); + } + } + + public void ValidateOrderPriceChange(Order order, decimal newPrice) + { + if (order.Price == newPrice) + return; + + if (newPrice <= 0) + { + throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice, + $"Price should be more than 0"); + } + + if (order.IsBasicOrder()) + { + ValidateBaseOrderPrice(order, newPrice); + + var relatedOrders = GetRelatedOrders(order); - throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice, reasonText, $"{order.Instrument} quote (bid/ask): {quote.Bid}/{quote.Ask}"); + ValidateBaseOrderPriceAgainstRelated(order, relatedOrders, newPrice); + } + else + { + ValidateRelatedOrderPriceAgainstBase(order, newPrice: newPrice); + } + } + + public async Task GetOrderInitialParameters(string assetPairId, string accountId) + { + var account = _accountsCacheService.Get(accountId); + + var equivalentPricesSettings = GetReportingEquivalentPricesSettings(account.LegalEntity); + + return await GetOrderInitialParameters(assetPairId, account.LegalEntity, equivalentPricesSettings, + account.BaseAssetId); + } + + private ReportingEquivalentPricesSettings GetReportingEquivalentPricesSettings(string legalEntity) + { + var equivalentPricesSettings = + _marginSettings.ReportingEquivalentPricesSettings.FirstOrDefault(x => x.LegalEntity == legalEntity); + + if (string.IsNullOrEmpty(equivalentPricesSettings?.EquivalentAsset)) + { + throw new Exception($"No reporting equivalent prices asset found for legalEntity: {legalEntity}"); + } + + return equivalentPricesSettings; + } + + private async Task ValidateAndGetSlOrTpOrder(OrderPlaceRequest request, OrderTypeContract type, + decimal? price, ReportingEquivalentPricesSettings equivalentSettings, Order parentOrder) + { + var orderType = type.ToType(); + Order order = null; + var placedAltogether = parentOrder != null; + + if (parentOrder == null) + { + if (!string.IsNullOrEmpty(request.ParentOrderId)) + { + parentOrder = _ordersCache.GetOrderById(request.ParentOrderId); } } - var accountAsset = _accountAssetsCacheService.GetAccountAsset(order.TradingConditionId, order.AccountAssetId, order.Instrument); + if (parentOrder != null) + { + ValidateRelatedOrderAlreadyExists(parentOrder.RelatedOrders, orderType); + + var initialParameters = await GetOrderInitialParameters(parentOrder.AssetPairId, + parentOrder.LegalEntity, equivalentSettings, parentOrder.AccountAssetId); + + var originator = GetOriginator(request.Originator); - if (accountAsset.DealLimit > 0 && Math.Abs(order.Volume) > accountAsset.DealLimit) + order = new Order(initialParameters.Id, initialParameters.Code, parentOrder.AssetPairId, + -parentOrder.Volume, initialParameters.Now, initialParameters.Now, + placedAltogether ? null : request.Validity, + parentOrder.AccountId, parentOrder.TradingConditionId, parentOrder.AccountAssetId, price, + parentOrder.EquivalentAsset, OrderFillType.FillOrKill, string.Empty, parentOrder.LegalEntity, false, + orderType, parentOrder.Id, null, originator, initialParameters.EquivalentPrice, + initialParameters.FxPrice, initialParameters.FxAssetPairId, + initialParameters.FxToAssetPairDirection, OrderStatus.Placed, request.AdditionalInfo, + request.CorrelationId); + } + else if (!string.IsNullOrEmpty(request.PositionId)) { - throw new ValidateOrderException(OrderRejectReason.InvalidVolume, - $"Margin Trading is in beta testing. The volume of a single order is temporarily limited to {accountAsset.DealLimit} {accountAsset.Instrument}. Thank you for using Lykke Margin Trading, the limit will be cancelled soon!"); + var position = _ordersCache.Positions.GetPositionById(request.PositionId); + + ValidateRelatedOrderAlreadyExists(position.RelatedOrders, orderType); + + var initialParameters = await GetOrderInitialParameters(position.AssetPairId, + position.LegalEntity, equivalentSettings, position.AccountAssetId); + + var originator = GetOriginator(request.Originator); + + order = new Order(initialParameters.Id, initialParameters.Code, position.AssetPairId, -position.Volume, + initialParameters.Now, initialParameters.Now, request.Validity, position.AccountId, + position.TradingConditionId, position.AccountAssetId, price, position.EquivalentAsset, + OrderFillType.FillOrKill, string.Empty, position.LegalEntity, false, orderType, null, position.Id, + originator, initialParameters.EquivalentPrice, initialParameters.FxPrice, + initialParameters.FxAssetPairId, initialParameters.FxToAssetPairDirection, OrderStatus.Placed, + request.AdditionalInfo, request.CorrelationId); } - //set special account-quote instrument - if (_assetPairsCache.TryGetAssetPairQuoteSubst(order.AccountAssetId, order.Instrument, - order.LegalEntity, out var substAssetPair)) + if (order == null) { - order.MarginCalcInstrument = substAssetPair.Id; + throw new ValidateOrderException(OrderRejectReason.InvalidParent, + "Related order must have parent order or position"); } - //check TP/SL - if (order.TakeProfit.HasValue) + ValidateRelatedOrderPriceAgainstBase(order, parentOrder, order.Price); + + return order; + } + + private static void ValidateRelatedOrderAlreadyExists(List relatedOrders, OrderType orderType) + { + if ((orderType == OrderType.TakeProfit + && relatedOrders.Any(o => o.Type == OrderType.TakeProfit)) + || ((orderType == OrderType.StopLoss || orderType == OrderType.TrailingStop) + && relatedOrders.Any(o => o.Type == OrderType.StopLoss || o.Type == OrderType.TrailingStop))) { - order.TakeProfit = Math.Round(order.TakeProfit.Value, order.AssetAccuracy); + throw new ValidateOrderException(OrderRejectReason.InvalidParent, + $"Parent order already has related order with type {orderType}"); } + } - if (order.StopLoss.HasValue) + private async Task GetOrderInitialParameters(string assetPairId, string legalEntity, + ReportingEquivalentPricesSettings equivalentSettings, string accountAssetId) + { + var fxAssetPairIdAndDirection = _cfdCalculatorService.GetFxAssetPairIdAndDirection(accountAssetId, + assetPairId, legalEntity); + + return new OrderInitialParameters + { + Id = _identityGenerator.GenerateAlphanumericId(), + Code = await _identityGenerator.GenerateIdAsync(nameof(Order)), + Now = _dateService.Now(), + EquivalentPrice = _cfdCalculatorService.GetQuoteRateForQuoteAsset(equivalentSettings.EquivalentAsset, + assetPairId, legalEntity), + FxPrice = _cfdCalculatorService.GetQuoteRateForQuoteAsset(accountAssetId, + assetPairId, legalEntity), + FxAssetPairId = fxAssetPairIdAndDirection.id, + FxToAssetPairDirection = fxAssetPairIdAndDirection.direction, + }; + } + + private void ValidateBaseOrderPrice(Order order, decimal? orderPrice) + { + if (!_quoteCashService.TryGetQuoteById(order.AssetPairId, out var quote)) { - order.StopLoss = Math.Round(order.StopLoss.Value, order.AssetAccuracy); + throw new ValidateOrderException(OrderRejectReason.NoLiquidity, "Quote not found"); } - ValidateOrderStops(order.GetOrderType(), quote, accountAsset.DeltaBid, accountAsset.DeltaAsk, order.TakeProfit, order.StopLoss, order.ExpectedOpenPrice, order.AssetAccuracy); + //TODO: implement in MTC-155 +// if (_assetDayOffService.ArePendingOrdersDisabled(order.AssetPairId)) +// { +// throw new ValidateOrderException(OrderRejectReason.NoLiquidity, +// "Trades for instrument are not available"); +// } - ValidateInstrumentPositionVolume(accountAsset, order); + if (order.OrderType != OrderType.Stop) + return; - if (!_accountUpdateService.IsEnoughBalance(order)) + if (order.Direction == OrderDirection.Buy && quote.Ask >= orderPrice) + { + throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice, + string.Format(MtMessages.Validation_PriceBelowAsk, orderPrice, quote.Ask), + $"{order.AssetPairId} quote (bid/ask): {quote.Bid}/{quote.Ask}"); + } + + if (order.Direction == OrderDirection.Sell && quote.Bid <= orderPrice ) { - throw new ValidateOrderException(OrderRejectReason.NotEnoughBalance, MtMessages.Validation_NotEnoughBalance, $"Account available balance is {account.GetTotalCapital()}"); + throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice, + string.Format(MtMessages.Validation_PriceAboveBid, orderPrice, quote.Bid), + $"{order.AssetPairId} quote (bid/ask): {quote.Bid}/{quote.Ask}"); } } - public void ValidateOrderStops(OrderDirection type, BidAskPair quote, decimal deltaBid, decimal deltaAsk, decimal? takeProfit, - decimal? stopLoss, decimal? expectedOpenPrice, int assetAccuracy) + private void ValidateBaseOrderPriceAgainstRelated(Order baseOrder, List relatedOrders, decimal newPrice) { - var deltaBidValue = MarginTradingCalculations.GetVolumeFromPoints(deltaBid, assetAccuracy); - var deltaAskValue = MarginTradingCalculations.GetVolumeFromPoints(deltaAsk, assetAccuracy); + //even if price is defined for market - ignore it + if (baseOrder.OrderType == OrderType.Market) + return; + + var slPrice = relatedOrders + .FirstOrDefault(o => o.OrderType == OrderType.StopLoss || o.OrderType == OrderType.TrailingStop)?.Price; - if (expectedOpenPrice.HasValue) + var tpPrice = relatedOrders + .FirstOrDefault(o => o.OrderType == OrderType.TakeProfit)?.Price; + + if (baseOrder.Direction == OrderDirection.Buy && + (slPrice.HasValue && slPrice > newPrice + || tpPrice.HasValue && tpPrice < newPrice) + || + baseOrder.Direction == OrderDirection.Sell && + (slPrice.HasValue && slPrice < newPrice + || tpPrice.HasValue && tpPrice > newPrice)) { - decimal minGray; - decimal maxGray; + throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice, + "Price is not valid against related orders prices."); + } + } - //check TP/SL for pending order - if (type == OrderDirection.Buy) - { - minGray = Math.Round(expectedOpenPrice.Value - 2 * deltaBidValue, assetAccuracy); - maxGray = Math.Round(expectedOpenPrice.Value + deltaAskValue, assetAccuracy); + private void ValidateRelatedOrderPriceAgainstBase(Order relatedOrder, Order baseOrder = null, decimal? newPrice = null) + { + if (newPrice == null) + newPrice = relatedOrder.Price; - if (takeProfit.HasValue && takeProfit > 0 && takeProfit < maxGray) - { - throw new ValidateOrderException(OrderRejectReason.InvalidTakeProfit, - string.Format(MtMessages.Validation_TakeProfitMustBeMore, Math.Round((decimal) takeProfit, assetAccuracy), maxGray), - $"quote (bid/ask): {quote.Bid}/{quote.Ask}"); - } + decimal basePrice; - if (stopLoss.HasValue && stopLoss > 0 && stopLoss > minGray) - { - throw new ValidateOrderException(OrderRejectReason.InvalidStoploss, - string.Format(MtMessages.Validation_StopLossMustBeLess, Math.Round((decimal) stopLoss, assetAccuracy), minGray), - $"quote (bid/ask): {quote.Bid}/{quote.Ask}"); - } - } - else + if ((baseOrder != null || + string.IsNullOrEmpty(relatedOrder.ParentPositionId) && + !string.IsNullOrEmpty(relatedOrder.ParentOrderId) && + _ordersCache.TryGetOrderById(relatedOrder.ParentOrderId, out baseOrder)) && + baseOrder.Price.HasValue) + { + basePrice = baseOrder.Price.Value; + } + else + { + if (!_quoteCashService.TryGetQuoteById(relatedOrder.AssetPairId, out var quote)) { - minGray = Math.Round(expectedOpenPrice.Value - deltaBidValue, assetAccuracy); - maxGray = Math.Round(expectedOpenPrice.Value + 2 * deltaAskValue, assetAccuracy); + throw new ValidateOrderException(OrderRejectReason.NoLiquidity, "Quote not found"); + } - if (takeProfit.HasValue && takeProfit > 0 && takeProfit > minGray) - { - throw new ValidateOrderException(OrderRejectReason.InvalidTakeProfit, - string.Format(MtMessages.Validation_TakeProfitMustBeLess, Math.Round((decimal) takeProfit, assetAccuracy), minGray), - $"quote (bid/ask): {quote.Bid}/{quote.Ask}"); - } + basePrice = quote.GetPriceForOrderDirection(relatedOrder.Direction); + } - if (stopLoss.HasValue && stopLoss > 0 && stopLoss < maxGray) - { - throw new ValidateOrderException(OrderRejectReason.InvalidStoploss, - string.Format(MtMessages.Validation_StopLossMustBeMore, Math.Round((decimal) stopLoss, assetAccuracy), maxGray), - $"quote (bid/ask): {quote.Bid}/{quote.Ask}"); - } - } + switch (relatedOrder.OrderType) + { + case OrderType.StopLoss: + case OrderType.TrailingStop: + ValidateStopLossOrderPrice(relatedOrder.Direction, newPrice, basePrice); + break; + + case OrderType.TakeProfit: + ValidateTakeProfitOrderPrice(relatedOrder.Direction, newPrice, basePrice); + break; } - else + } + + private void ValidateTakeProfitOrderPrice(OrderDirection orderDirection, decimal? orderPrice, decimal basePrice) + { + if (orderDirection == OrderDirection.Buy && basePrice <= orderPrice) + { + throw new ValidateOrderException(OrderRejectReason.InvalidTakeProfit, + string.Format(MtMessages.Validation_TakeProfitMustBeLess, orderPrice, basePrice)); + } + + if (orderDirection == OrderDirection.Sell && basePrice >= orderPrice) + { + throw new ValidateOrderException(OrderRejectReason.InvalidTakeProfit, + string.Format(MtMessages.Validation_TakeProfitMustBeMore, orderPrice, basePrice)); + } + } + + private void ValidateStopLossOrderPrice(OrderDirection orderDirection, decimal? orderPrice, decimal basePrice) + { + if (orderDirection == OrderDirection.Buy && basePrice >= orderPrice) + { + throw new ValidateOrderException(OrderRejectReason.InvalidStoploss, + string.Format(MtMessages.Validation_StopLossMustBeMore, orderPrice, basePrice)); + } + + if (orderDirection == OrderDirection.Sell && basePrice <= orderPrice) + { + throw new ValidateOrderException(OrderRejectReason.InvalidStoploss, + string.Format(MtMessages.Validation_StopLossMustBeLess, orderPrice, basePrice)); + } + } + + private OriginatorType GetOriginator(OriginatorTypeContract? originator) + { + if (originator == null || originator.Value == default(OriginatorTypeContract)) { - //check TP/SL for market order - var minGray = Math.Round(quote.Bid - deltaBidValue, assetAccuracy); - var maxGray = Math.Round(quote.Ask + deltaAskValue, assetAccuracy); + return OriginatorType.Investor; + } - if (type == OrderDirection.Buy) + return originator.ToType(); + } + + #endregion + + + #region Pre-trade validations + + public void MakePreTradeValidation(Order order, bool shouldOpenNewPosition, IMatchingEngineBase matchingEngine) + { + GetAssetPairIfAvailableForTrading(order.AssetPairId, order.OrderType, shouldOpenNewPosition, true); + + ValidateTradeLimits(order.AssetPairId, order.TradingConditionId, order.AccountId, order.Volume, shouldOpenNewPosition); + + if (shouldOpenNewPosition) + _accountUpdateService.CheckIsEnoughBalance(order, matchingEngine); + } + + public bool CheckIfPendingOrderExecutionPossible(string assetPairId, OrderType orderType, bool shouldOpenNewPosition) + { + try + { + GetAssetPairIfAvailableForTrading(assetPairId, orderType, shouldOpenNewPosition, true); + } + catch + { + return false; + } + + return true; + } + + public IAssetPair GetAssetPairIfAvailableForTrading(string assetPairId, OrderType orderType, + bool shouldOpenNewPosition, bool isPreTradeValidation) + { + if (isPreTradeValidation || orderType == OrderType.Market) + { + if (_assetDayOffService.IsDayOff(assetPairId)) { - if (takeProfit.HasValue && takeProfit > 0 && takeProfit < maxGray) - { - throw new ValidateOrderException(OrderRejectReason.InvalidTakeProfit, - string.Format(MtMessages.Validation_TakeProfitMustBeMore, Math.Round((decimal) takeProfit, assetAccuracy), maxGray), - $"quote (bid/ask): {quote.Bid}/{quote.Ask}"); - } + throw new ValidateOrderException(OrderRejectReason.InvalidInstrument, + $"Trades for instrument {assetPairId} are not available due to trading is closed"); + } + } + else if (_assetDayOffService.ArePendingOrdersDisabled(assetPairId)) + { + throw new ValidateOrderException(OrderRejectReason.InvalidInstrument, + $"Pending orders for instrument {assetPairId} are not available"); + } - if (stopLoss.HasValue && stopLoss > 0 && stopLoss > minGray) - { - throw new ValidateOrderException(OrderRejectReason.InvalidStoploss, - string.Format(MtMessages.Validation_StopLossMustBeLess, Math.Round((decimal) stopLoss, assetAccuracy), minGray), - $"quote (bid/ask): {quote.Bid}/{quote.Ask}"); - } + var assetPair = _assetPairsCache.GetAssetPairByIdOrDefault(assetPairId); + + if (assetPair == null) + { + throw new ValidateOrderException(OrderRejectReason.InvalidInstrument, $"Instrument {assetPairId} not found"); + } + + if (assetPair.IsDiscontinued) + { + throw new ValidateOrderException(OrderRejectReason.InvalidInstrument, + $"Trading for the instrument {assetPairId} is discontinued"); + } + + if (assetPair.IsSuspended) + { + if (isPreTradeValidation) + { + throw new ValidateOrderException(OrderRejectReason.InvalidInstrument, + $"Orders execution for instrument {assetPairId} is temporarily unavailable (instrument is suspended)"); } - else + + if (orderType == OrderType.Market) { - if (takeProfit.HasValue && takeProfit > 0 && takeProfit > minGray) - { - throw new ValidateOrderException(OrderRejectReason.InvalidTakeProfit, - string.Format(MtMessages.Validation_TakeProfitMustBeLess, Math.Round((decimal) takeProfit, assetAccuracy), minGray), - $"quote (bid/ask): {quote.Bid}/{quote.Ask}"); - } - - if (stopLoss.HasValue && stopLoss > 0 && stopLoss < maxGray) - { - throw new ValidateOrderException(OrderRejectReason.InvalidStoploss, - string.Format(MtMessages.Validation_StopLossMustBeMore, - Math.Round((decimal) stopLoss, assetAccuracy), maxGray), - $"quote (bid/ask): {quote.Bid}/{quote.Ask}"); - } + throw new ValidateOrderException(OrderRejectReason.InvalidInstrument, + $"Market orders for instrument {assetPairId} are temporarily unavailable (instrument is suspended)"); } + } + + if (assetPair.IsFrozen && shouldOpenNewPosition) + { + throw new ValidateOrderException(OrderRejectReason.InvalidInstrument, + $"Opening new positions for instrument {assetPairId} is temporarily unavailable (instrument is frozen)"); } + + return assetPair; } - public void ValidateInstrumentPositionVolume(IAccountAssetPair assetPair, Order order) + private void ValidateTradeLimits(string assetPairId, string tradingConditionId, string accountId, + decimal volume, bool shouldOpenNewPosition) { - var existingPositionsVolume = _ordersCache.ActiveOrders.GetOrdersByInstrumentAndAccount(assetPair.Instrument, order.AccountId).Sum(o => o.Volume); + var tradingInstrument = + _tradingInstrumentsCache.GetTradingInstrument(tradingConditionId, assetPairId); - if (assetPair.PositionLimit > 0 && Math.Abs(existingPositionsVolume + order.Volume) > assetPair.PositionLimit) + if (tradingInstrument.DealMaxLimit > 0 && Math.Abs(volume) > tradingInstrument.DealMaxLimit) { - throw new ValidateOrderException(OrderRejectReason.InvalidVolume, - $"Margin Trading is in beta testing. The volume of the net open position is temporarily limited to {assetPair.PositionLimit} {assetPair.Instrument}. Thank you for using Lykke Margin Trading, the limit will be cancelled soon!"); + throw new ValidateOrderException(OrderRejectReason.MaxOrderSizeLimit, + $"The volume of a single order is limited to {tradingInstrument.DealMaxLimit} {tradingInstrument.Instrument}."); } + + var existingPositionsVolume = _ordersCache.Positions.GetPositionsByInstrumentAndAccount(assetPairId, accountId) + .Sum(o => o.Volume); + + if (tradingInstrument.PositionLimit > 0 && + Math.Abs(existingPositionsVolume + volume) > tradingInstrument.PositionLimit) + { + throw new ValidateOrderException(OrderRejectReason.MaxPositionLimit, + $"The volume of the net open position is limited to {tradingInstrument.PositionLimit} {tradingInstrument.Instrument}."); + } + + if (shouldOpenNewPosition && volume < 0 && !tradingInstrument.ShortPosition) + { + throw new ValidateOrderException(OrderRejectReason.ShortPositionsDisabled, + $"Short positions are disabled for {tradingInstrument.Instrument}."); + } + } + + private List GetRelatedOrders(Order baseOrder) + { + var result = new List(); + + foreach (var relatedOrderInfo in baseOrder.RelatedOrders) + { + if (_ordersCache.TryGetOrderById(relatedOrderInfo.Id, out var relatedOrder)) + { + result.Add(relatedOrder); + } + } + + return result; } + + #endregion + } } diff --git a/src/MarginTrading.Backend.Services/Settings/AccountsManagementServiceClient.cs b/src/MarginTrading.Backend.Services/Settings/AccountsManagementServiceClient.cs new file mode 100644 index 000000000..5ab550544 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Settings/AccountsManagementServiceClient.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; +using Lykke.SettingsReader.Attributes; + +namespace MarginTrading.Backend.Services.Settings +{ + [UsedImplicitly] + public class AccountsManagementServiceClient + { + [HttpCheck("/api/isalive")] + public string ServiceUrl { get; set; } + + [Optional] + public string ApiKey { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Settings/MtBackendSettings.cs b/src/MarginTrading.Backend.Services/Settings/MtBackendSettings.cs index c87b74563..4f3eb496c 100644 --- a/src/MarginTrading.Backend.Services/Settings/MtBackendSettings.cs +++ b/src/MarginTrading.Backend.Services/Settings/MtBackendSettings.cs @@ -1,19 +1,39 @@ -using Lykke.Service.ExchangeConnector.Client; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; +using Lykke.SettingsReader.Attributes; using MarginTrading.Backend.Core.Settings; using MarginTrading.Common.Settings; namespace MarginTrading.Backend.Services.Settings { + [UsedImplicitly] public class MtBackendSettings { public MarginTradingSettings MtBackend { get; set; } + + [Optional, CanBeNull] public EmailSenderSettings EmailSender { get; set; } + + [Optional, CanBeNull] public NotificationSettings Jobs { get; set; } + + [Optional, CanBeNull] public SlackNotificationSettings SlackNotifications { get; set; } + + [Optional, CanBeNull] public RiskInformingSettings RiskInformingSettings { get; set; } - public RiskInformingSettings RiskInformingSettingsDemo { get; set; } - public AssetClientSettings Assets { get; set; } + + [Optional, CanBeNull] public ClientAccountServiceSettings ClientAccountServiceClient { get; set; } - public ExchangeConnectorServiceSettings MtStpExchangeConnectorClient { get; set; } + + public ExchangeConnectorServiceClient MtStpExchangeConnectorClient { get; set; } + + public SettingsServiceClient SettingsServiceClient { get; set; } + + public AccountsManagementServiceClient AccountsManagementServiceClient { get; set; } + + public ServiceClientSettings OrderBookServiceClient { get; set; } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/StopoutConsumer.cs b/src/MarginTrading.Backend.Services/StopoutConsumer.cs deleted file mode 100644 index 499656eb9..000000000 --- a/src/MarginTrading.Backend.Services/StopoutConsumer.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Common; -using Lykke.Common; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Messages; -using MarginTrading.Backend.Services.Assets; -using MarginTrading.Backend.Services.Events; -using MarginTrading.Backend.Services.Notifications; -using MarginTrading.Common.Services; -using MarginTrading.Common.Services.Client; -using MarginTrading.Common.Settings; - -namespace MarginTrading.Backend.Services -{ - // TODO: Rename by role - public class StopOutConsumer : NotificationSenderBase, IEventConsumer - { - private readonly IThreadSwitcher _threadSwitcher; - private readonly IClientAccountService _clientAccountService; - private readonly IClientNotifyService _notifyService; - private readonly IEmailService _emailService; - private readonly IMarginTradingOperationsLogService _operationsLogService; - private readonly IRabbitMqNotifyService _rabbitMqNotifyService; - private readonly IDateService _dateService; - private readonly IAssetsCache _assetsCache; - - public StopOutConsumer(IThreadSwitcher threadSwitcher, - IAppNotifications appNotifications, - IClientAccountService clientAccountService, - IClientNotifyService notifyService, - IEmailService emailService, - IMarginTradingOperationsLogService operationsLogService, - IRabbitMqNotifyService rabbitMqNotifyService, - IDateService dateService, - IAssetsCache assetsCache, - IAssetPairsCache assetPairsCache) : base(appNotifications, - clientAccountService, - assetsCache, - assetPairsCache) - { - _threadSwitcher = threadSwitcher; - _clientAccountService = clientAccountService; - _notifyService = notifyService; - _emailService = emailService; - _operationsLogService = operationsLogService; - _rabbitMqNotifyService = rabbitMqNotifyService; - _dateService = dateService; - _assetsCache = assetsCache; - } - - int IEventConsumer.ConsumerRank => 100; - - void IEventConsumer.ConsumeEvent(object sender, StopOutEventArgs ea) - { - var account = ea.Account; - var orders = ea.Orders; - var eventTime = _dateService.Now(); - var accountMarginEventMessage = AccountMarginEventMessageConverter.Create(account, true, eventTime); - var accuracy = _assetsCache.GetAssetAccuracy(account.BaseAssetId); - var totalPnl = Math.Round(orders.Sum(x => x.GetTotalFpl()), accuracy); - - _threadSwitcher.SwitchThread(async () => - { - _operationsLogService.AddLog("stopout", account.ClientId, account.Id, "", ea.ToJson()); - - var marginEventTask = _rabbitMqNotifyService.AccountMarginEvent(accountMarginEventMessage); - - _notifyService.NotifyAccountStopout(account.ClientId, account.Id, orders.Length, totalPnl); - - var notificationTask = SendMarginEventNotification(account.ClientId, - string.Format(MtMessages.Notifications_StopOutNotification, orders.Length, totalPnl, - account.BaseAssetId)); - - var clientEmail = await _clientAccountService.GetEmail(account.ClientId); - - var emailTask = !string.IsNullOrEmpty(clientEmail) - ? _emailService.SendStopOutEmailAsync(clientEmail, account.BaseAssetId, account.Id) - : Task.CompletedTask; - - await Task.WhenAll(marginEventTask, notificationTask, emailTask); - }); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Stp/ExternalOrderbookService.cs b/src/MarginTrading.Backend.Services/Stp/ExternalOrderbookService.cs new file mode 100644 index 000000000..7d5b5b8e6 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Stp/ExternalOrderbookService.cs @@ -0,0 +1,334 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Common; +using Common.Log; +using Lykke.MarginTrading.OrderBookService.Contracts; +using Lykke.MarginTrading.OrderBookService.Contracts.Models; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orderbooks; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Services.AssetPairs; +using MarginTrading.Backend.Services.Events; +using MarginTrading.Backend.Services.Infrastructure; +using MarginTrading.Common.Extensions; +using MarginTrading.Common.Helpers; +using MarginTrading.Common.Services; +using MarginTrading.SettingsService.Contracts.AssetPair; +using MoreLinq; + +namespace MarginTrading.Backend.Services.Stp +{ + public class ExternalOrderbookService : IExternalOrderbookService + { + private readonly IEventChannel _bestPriceChangeEventChannel; + private readonly IOrderBookProviderApi _orderBookProviderApi; + private readonly IDateService _dateService; + private readonly IConvertService _convertService; + private readonly IAssetPairsCache _assetPairsCache; + private readonly ICqrsSender _cqrsSender; + private readonly IIdentityGenerator _identityGenerator; + private readonly ILog _log; + private readonly MarginTradingSettings _marginTradingSettings; + private readonly IAssetPairDayOffService _assetPairDayOffService; + private readonly IScheduleSettingsCacheService _scheduleSettingsCache; + + public const string EodExternalExchange = "EOD"; + + /// + /// External orderbooks cache (AssetPairId, (Source, Orderbook)) + /// + /// + /// We assume that AssetPairId is unique in LegalEntity + STP mode.
+ /// Note that it is unsafe to even read the inner dictionary without locking. + /// Please use for this purpose. + ///
+ private readonly ReadWriteLockedDictionary> _orderbooks = + new ReadWriteLockedDictionary>(); + + public ExternalOrderbookService( + IEventChannel bestPriceChangeEventChannel, + IOrderBookProviderApi orderBookProviderApi, + IDateService dateService, + IConvertService convertService, + IAssetPairDayOffService assetPairDayOffService, + IScheduleSettingsCacheService scheduleSettingsCache, + IAssetPairsCache assetPairsCache, + ICqrsSender cqrsSender, + IIdentityGenerator identityGenerator, + ILog log, + MarginTradingSettings marginTradingSettings) + { + _bestPriceChangeEventChannel = bestPriceChangeEventChannel; + _orderBookProviderApi = orderBookProviderApi; + _dateService = dateService; + _convertService = convertService; + _assetPairDayOffService = assetPairDayOffService; + _scheduleSettingsCache = scheduleSettingsCache; + _assetPairsCache = assetPairsCache; + _cqrsSender = cqrsSender; + _identityGenerator = identityGenerator; + _log = log; + _marginTradingSettings = marginTradingSettings; + } + + public async Task InitializeAsync() + { + try + { + var orderBooks = (await _orderBookProviderApi.GetOrderBooks()) + .GroupBy(x => x.AssetPairId) + .Select(x => _convertService.Convert( + x.MaxBy(o => o.ReceiveTimestamp))) + .ToList(); + + foreach (var externalOrderBook in orderBooks) + { + SetOrderbook(externalOrderBook); + } + + await _log.WriteInfoAsync(nameof(ExternalOrderbookService), nameof(InitializeAsync), + $"External order books cache initialized with {orderBooks.Count} items from OrderBooks Service"); + } + catch (Exception exception) + { + await _log.WriteWarningAsync(nameof(ExternalOrderbookService), nameof(InitializeAsync), + "Failed to initialize cache from OrderBook Service", exception); + } + } + + public List<(string source, decimal? price)> GetOrderedPricesForExecution(string assetPairId, decimal volume, + bool validateOppositeDirectionVolume) + { + return _orderbooks.TryReadValue(assetPairId, (dataExist, assetPair, orderbooks) + => + { + if (!dataExist) + return null; + + var result = orderbooks.Select(p => (p.Key, + MatchBestPriceForOrderExecution(p.Value, volume, validateOppositeDirectionVolume))) + .Where(p => p.Item2 != null).ToArray(); + + return volume.GetOrderDirection() == OrderDirection.Buy + ? result.OrderBy(tuple => tuple.Item2).ToList() + : result.OrderByDescending(tuple => tuple.Item2).ToList(); + }); + } + + public decimal? GetPriceForPositionClose(string assetPairId, decimal volume, string externalProviderId) + { + decimal? CalculatePriceForClose(Dictionary orderbooks) + => !orderbooks.TryGetValue(externalProviderId, out var orderBook) + ? null + : MatchBestPriceForPositionClose(orderBook, volume); + + return _orderbooks.TryReadValue(assetPairId, (dataExist, assetPair, orderbooks) + => dataExist ? CalculatePriceForClose(orderbooks) : null); + } + + //TODO: understand which orderbook should be used (best price? aggregated?) + public ExternalOrderBook GetOrderBook(string assetPairId) + { + return _orderbooks.TryReadValue(assetPairId, + (exists, assetPair, orderbooks) => orderbooks.Values.FirstOrDefault()); + } + + private static decimal? MatchBestPriceForOrderExecution(ExternalOrderBook externalOrderBook, decimal volume, + bool validateOppositeDirectionVolume) + { + var direction = volume.GetOrderDirection(); + + var price = externalOrderBook.GetMatchedPrice(Math.Abs(volume), direction); + + if (price != null && validateOppositeDirectionVolume) + { + var closePrice = externalOrderBook.GetMatchedPrice(Math.Abs(volume), direction.GetOpositeDirection()); + + //if no liquidity for close, should not use price for open + if (closePrice == null) + return null; + } + + return price; + } + + private static decimal? MatchBestPriceForPositionClose(ExternalOrderBook externalOrderBook, decimal volume) + { + var direction = volume.GetClosePositionOrderDirection(); + + return externalOrderBook.GetMatchedPrice(Math.Abs(volume), direction); + } + + public List GetOrderBooks() + { + return _orderbooks + .Select(x => x.Value.Values.MaxBy(o => o.Timestamp)) + .ToList(); + } + + public void SetOrderbook(ExternalOrderBook orderbook) + { + var isEodOrderbook = orderbook.ExchangeName == EodExternalExchange; + + if (_marginTradingSettings.OrderbookValidation.ValidateInstrumentStatusForEodQuotes && isEodOrderbook || + _marginTradingSettings.OrderbookValidation.ValidateInstrumentStatusForTradingQuotes && !isEodOrderbook) + { + var isDayOff = _assetPairDayOffService.IsDayOff(orderbook.AssetPairId); + + // we should process normal orderbook only if asset is currently tradeable + if (_marginTradingSettings.OrderbookValidation.ValidateInstrumentStatusForTradingQuotes && + isDayOff && + !isEodOrderbook) + { + return; + } + + // and process EOD orderbook only if instrument is currently not tradable + if (_marginTradingSettings.OrderbookValidation.ValidateInstrumentStatusForEodQuotes && + !isDayOff && + isEodOrderbook) + { + //log current schedule for the instrument + var schedule = _scheduleSettingsCache.GetCompiledAssetPairScheduleSettings( + orderbook.AssetPairId, + _dateService.Now(), + TimeSpan.Zero); + + _log.WriteWarning("EOD quotes processing", $"Current schedule: {schedule.ToJson()}", + $"EOD quote for {orderbook.AssetPairId} is skipped, because instrument is within trading hours"); + + return; + } + } + + if (!ValidateOrderbook(orderbook) + || !CheckZeroQuote(orderbook, isEodOrderbook)) + return; + + orderbook.ApplyExchangeIdFromSettings(_marginTradingSettings.DefaultExternalExchangeId); + + var bba = new InstrumentBidAskPair + { + Bid = 0, + Ask = decimal.MaxValue, + Date = _dateService.Now(), + Instrument = orderbook.AssetPairId + }; + + Dictionary UpdateOrderbooksDictionary(string assetPairId, + Dictionary dict) + { + dict[orderbook.ExchangeName] = orderbook; + foreach (var pair in dict.Values.RequiredNotNullOrEmptyCollection(nameof(dict))) + { + // guaranteed to be sorted best first + var bestBid = pair.Bids.First().Price; + var bestAsk = pair.Asks.First().Price; + if (bestBid > bba.Bid) + bba.Bid = bestBid; + + if (bestAsk < bba.Ask) + bba.Ask = bestAsk; + } + + return dict; + } + + _orderbooks.AddOrUpdate(orderbook.AssetPairId, + k => UpdateOrderbooksDictionary(k, new Dictionary()), + UpdateOrderbooksDictionary); + + _bestPriceChangeEventChannel.SendEvent(this, new BestPriceChangeEventArgs(bba, isEodOrderbook)); + } + + //TODO: sort prices of uncomment validation + private bool ValidateOrderbook(ExternalOrderBook orderbook) + { + try + { + orderbook.AssetPairId.RequiredNotNullOrWhiteSpace("orderbook.AssetPairId"); + orderbook.ExchangeName.RequiredNotNullOrWhiteSpace("orderbook.ExchangeName"); + orderbook.RequiredNotNull(nameof(orderbook)); + + orderbook.Bids.RequiredNotNullOrEmpty("orderbook.Bids"); + orderbook.Bids = orderbook.Bids.Where(e => e != null && e.Price > 0 && e.Volume != 0).ToArray(); + //ValidatePricesSorted(orderbook.Bids, false); + + orderbook.Asks.RequiredNotNullOrEmpty("orderbook.Asks"); + orderbook.Asks = orderbook.Asks.Where(e => e != null && e.Price > 0 && e.Volume != 0).ToArray(); + //ValidatePricesSorted(orderbook.Asks, true); + + return true; + } + catch (Exception e) + { + _log.WriteError(nameof(ExternalOrderbookService), orderbook.ToJson(), e); + return false; + } + } + + private void ValidatePricesSorted(IEnumerable volumePrices, bool ascending) + { + decimal? previous = null; + foreach (var current in volumePrices.Select(p => p.Price)) + { + if (previous != null && ascending ? current < previous : current > previous) + { + throw new Exception("Prices should be sorted best first"); + } + + previous = current; + } + } + + private bool CheckZeroQuote(ExternalOrderBook orderbook, bool isEodOrderbook) + { + var isOrderbookValid = orderbook.Asks.Length > 0 && orderbook.Bids.Length > 0;//after validations + + var assetPair = _assetPairsCache.GetAssetPairByIdOrDefault(orderbook.AssetPairId); + if (assetPair == null) + { + return isOrderbookValid; + } + + //EOD quotes should not change asset pair state + if (isEodOrderbook) + return isOrderbookValid; + + if (!isOrderbookValid) + { + if (!assetPair.IsSuspended) + { + assetPair.IsSuspended = true;//todo apply changes to trading engine + _cqrsSender.SendCommandToSettingsService(new SuspendAssetPairCommand + { + AssetPairId = assetPair.Id, + OperationId = _identityGenerator.GenerateGuid(), + }); + } + } + else + { + if (assetPair.IsSuspended) + { + assetPair.IsSuspended = false;//todo apply changes to trading engine + _cqrsSender.SendCommandToSettingsService(new UnsuspendAssetPairCommand + { + AssetPairId = assetPair.Id, + OperationId = _identityGenerator.GenerateGuid(), + }); + } + } + + return isOrderbookValid; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Stp/ExternalOrderbooksList.cs b/src/MarginTrading.Backend.Services/Stp/ExternalOrderbooksList.cs deleted file mode 100644 index 50c9d015d..000000000 --- a/src/MarginTrading.Backend.Services/Stp/ExternalOrderbooksList.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices.ComTypes; -using Common; -using Common.Log; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.MatchedOrders; -using MarginTrading.Backend.Core.Orderbooks; -using MarginTrading.Backend.Services.Events; -using MarginTrading.Common.Extensions; -using MarginTrading.Common.Helpers; -using MarginTrading.Common.Services; - -namespace MarginTrading.Backend.Services.Stp -{ - public class ExternalOrderBooksList - { - private readonly IEventChannel _bestPriceChangeEventChannel; - private readonly IDateService _dateService; - private readonly ILog _log; - - public ExternalOrderBooksList(IEventChannel bestPriceChangeEventChannel, - IDateService dateService, ILog log) - { - _bestPriceChangeEventChannel = bestPriceChangeEventChannel; - _dateService = dateService; - _log = log; - } - - /// - /// External orderbooks cache (AssetPairId, (Source, Orderbook)) - /// - /// - /// We assume that AssetPairId is unique in LegalEntity + STP mode.
- /// Note that it is unsafe to even read the inner dictionary without locking. - /// Please use for this purpose. - ///
- private readonly ReadWriteLockedDictionary> _orderbooks = - new ReadWriteLockedDictionary>(); - - public List<(string source, decimal? price)> GetPricesForOpen(IOrder order) - { - return _orderbooks.TryReadValue(order.Instrument, (dataExist, assetPairId, orderbooks) - => dataExist - ? orderbooks.Select(p => (p.Key, MatchBestPriceForOrder(p.Value, order, true))).ToList() - : null); - } - - public decimal? GetPriceForClose(IOrder order) - { - decimal? CalculatePriceForClose(Dictionary orderbooks) - { - if (!orderbooks.TryGetValue(order.OpenExternalProviderId, out var orderBook)) - { - return null; - } - - return MatchBestPriceForOrder(orderBook, order, false); - } - - return _orderbooks.TryReadValue(order.Instrument, (dataExist, assetPairId, orderbooks) - => dataExist ? CalculatePriceForClose(orderbooks) : null); - } - - //TODO: understand which orderbook should be used (best price? aggregated?) - public ExternalOrderBook GetOrderBook(string assetPairId) - { - return _orderbooks.TryReadValue(assetPairId, - (exists, assetPair, orderbooks) => orderbooks.Values.FirstOrDefault()); - } - - private static decimal? MatchBestPriceForOrder(ExternalOrderBook externalOrderbook, IOrder order, bool isOpening) - { - var direction = isOpening ? order.GetOrderType() : order.GetCloseType(); - var volume = Math.Abs(order.Volume); - - return externalOrderbook.GetMatchedPrice(volume, direction); - } - - public void SetOrderbook(ExternalOrderBook orderbook) - { - if (!ValidateOrderbook(orderbook)) - return; - - var bba = new InstrumentBidAskPair - { - Bid = 0, - Ask = decimal.MaxValue, - Date = _dateService.Now(), - Instrument = orderbook.AssetPairId - }; - - Dictionary UpdateOrderbooksDictionary(string assetPairId, - Dictionary dict) - { - dict[orderbook.ExchangeName] = orderbook; - foreach (var pair in dict.Values.RequiredNotNullOrEmptyCollection(nameof(dict))) - { - // guaranteed to be sorted best first - var bestBid = pair.Bids.First().Price; - var bestAsk = pair.Asks.First().Price; - if (bestBid > bba.Bid) - bba.Bid = bestBid; - - if (bestAsk < bba.Ask) - bba.Ask = bestAsk; - } - - return dict; - } - - _orderbooks.AddOrUpdate(orderbook.AssetPairId, - k => UpdateOrderbooksDictionary(k, new Dictionary()), - UpdateOrderbooksDictionary); - - _bestPriceChangeEventChannel.SendEvent(this, new BestPriceChangeEventArgs(bba)); - } - - private bool ValidateOrderbook(ExternalOrderBook orderbook) - { - try - { - orderbook.AssetPairId.RequiredNotNullOrWhiteSpace("orderbook.AssetPairId"); - orderbook.ExchangeName.RequiredNotNullOrWhiteSpace("orderbook.ExchangeName"); - orderbook.RequiredNotNull(nameof(orderbook)); - - orderbook.Bids.RequiredNotNullOrEmpty("orderbook.Bids"); - orderbook.Bids.RemoveAll(e => e == null || e.Price <= 0 || e.Volume == 0); - ValidatePricesSorted(orderbook.Bids, false); - - orderbook.Asks.RequiredNotNullOrEmpty("orderbook.Asks"); - orderbook.Asks.RemoveAll(e => e == null || e.Price <= 0 || e.Volume == 0); - ValidatePricesSorted(orderbook.Asks, true); - - return true; - } - catch (Exception e) - { - _log.WriteError(nameof(ExternalOrderBooksList), orderbook.ToJson(), e); - return false; - } - } - - private void ValidatePricesSorted(IEnumerable volumePrices, bool ascending) - { - decimal? previous = null; - foreach (var current in volumePrices.Select(p => p.Price)) - { - if (previous != null && ascending ? current < previous : current > previous) - throw new Exception("Prices should be sorted best first"); - - previous = current; - } - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Stp/LightweightExternalOrderbookService.cs b/src/MarginTrading.Backend.Services/Stp/LightweightExternalOrderbookService.cs new file mode 100644 index 000000000..87105ea41 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Stp/LightweightExternalOrderbookService.cs @@ -0,0 +1,261 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Common; +using Common.Log; +using Lykke.MarginTrading.OrderBookService.Contracts; +using Lykke.MarginTrading.OrderBookService.Contracts.Models; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orderbooks; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Services.AssetPairs; +using MarginTrading.Backend.Services.Events; +using MarginTrading.Backend.Services.Infrastructure; +using MarginTrading.Common.Helpers; +using MarginTrading.Common.Services; +using MarginTrading.SettingsService.Contracts.AssetPair; +using MoreLinq; + +namespace MarginTrading.Backend.Services.Stp +{ + public class LightweightExternalOrderbookService : IExternalOrderbookService + { + private readonly IEventChannel _bestPriceChangeEventChannel; + private readonly IOrderBookProviderApi _orderBookProviderApi; + private readonly IDateService _dateService; + private readonly IConvertService _convertService; + private readonly IScheduleSettingsCacheService _scheduleSettingsCache; + private readonly IAssetPairDayOffService _assetPairDayOffService; + private readonly IAssetPairsCache _assetPairsCache; + private readonly ICqrsSender _cqrsSender; + private readonly IIdentityGenerator _identityGenerator; + private readonly ILog _log; + private readonly string _defaultExternalExchangeId; + private readonly OrderbookValidationSettings _orderbookValidation; + + /// + /// External orderbooks cache (AssetPairId, Orderbook) + /// + /// + /// We assume that AssetPairId is unique in LegalEntity + STP mode.
+ /// Please use for this purpose. + ///
+ private readonly ReadWriteLockedDictionary _orderbooks = + new ReadWriteLockedDictionary(); + + public LightweightExternalOrderbookService( + IEventChannel bestPriceChangeEventChannel, + IOrderBookProviderApi orderBookProviderApi, + IDateService dateService, + IConvertService convertService, + IScheduleSettingsCacheService scheduleSettingsCache, + IAssetPairDayOffService assetPairDayOffService, + IAssetPairsCache assetPairsCache, + ICqrsSender cqrsSender, + IIdentityGenerator identityGenerator, + ILog log, + MarginTradingSettings marginTradingSettings) + { + _bestPriceChangeEventChannel = bestPriceChangeEventChannel; + _orderBookProviderApi = orderBookProviderApi; + _dateService = dateService; + _convertService = convertService; + _scheduleSettingsCache = scheduleSettingsCache; + _assetPairDayOffService = assetPairDayOffService; + _assetPairsCache = assetPairsCache; + _cqrsSender = cqrsSender; + _identityGenerator = identityGenerator; + _log = log; + _defaultExternalExchangeId = string.IsNullOrEmpty(marginTradingSettings.DefaultExternalExchangeId) + ? "Default" + : marginTradingSettings.DefaultExternalExchangeId; + _orderbookValidation = marginTradingSettings.OrderbookValidation; + } + + public async Task InitializeAsync() + { + try + { + var orderBooks = (await _orderBookProviderApi.GetOrderBooks()) + .GroupBy(x => x.AssetPairId) + .Select(x => _convertService.Convert( + x.MaxBy(o => o.ReceiveTimestamp))) + .ToList(); + + foreach (var externalOrderBook in orderBooks) + { + SetOrderbook(externalOrderBook); + } + + await _log.WriteInfoAsync(nameof(LightweightExternalOrderbookService), nameof(InitializeAsync), + $"External order books cache initialized with {orderBooks.Count} items from OrderBooks Service"); + } + catch (Exception exception) + { + await _log.WriteWarningAsync(nameof(LightweightExternalOrderbookService), nameof(InitializeAsync), + "Failed to initialize cache from OrderBook Service", exception); + } + } + + public List<(string source, decimal? price)> GetOrderedPricesForExecution(string assetPairId, decimal volume, + bool validateOppositeDirectionVolume) + { + if (!_orderbooks.TryGetValue(assetPairId, out var orderBook)) + { + return null; + } + + var price = MatchBestPriceForOrderExecution(orderBook, volume, validateOppositeDirectionVolume); + + if (price == null) + return null; + + return new List<(string source, decimal? price)> + { + (orderBook.ExchangeName, price) + }; + } + + public decimal? GetPriceForPositionClose(string assetPairId, decimal volume, string externalProviderId) + { + if (!_orderbooks.TryGetValue(assetPairId, out var orderBook)) + { + return null; + } + + return MatchBestPriceForPositionClose(orderBook, volume); + } + + //TODO: understand which orderbook should be used (best price? aggregated?) + public ExternalOrderBook GetOrderBook(string assetPairId) + { + return _orderbooks.TryGetValue(assetPairId, out var orderBook) ? orderBook : null; + } + + private static decimal? MatchBestPriceForOrderExecution(ExternalOrderBook externalOrderBook, decimal volume, + bool validateOppositeDirectionVolume) + { + var direction = volume.GetOrderDirection(); + + var price = externalOrderBook.GetMatchedPrice(volume, direction); + + if (price != null && validateOppositeDirectionVolume) + { + var closePrice = externalOrderBook.GetMatchedPrice(volume, direction.GetOpositeDirection()); + + //if no liquidity for close, should not use price for open + if (closePrice == null) + return null; + } + + return price; + } + + private static decimal? MatchBestPriceForPositionClose(ExternalOrderBook externalOrderBook, decimal volume) + { + var direction = volume.GetClosePositionOrderDirection(); + + return externalOrderBook.GetMatchedPrice(Math.Abs(volume), direction); + } + + public List GetOrderBooks() + { + return _orderbooks + .Select(x => x.Value) + .ToList(); + } + + public void SetOrderbook(ExternalOrderBook orderbook) + { + var isEodOrderbook = orderbook.ExchangeName == ExternalOrderbookService.EodExternalExchange; + + if (_orderbookValidation.ValidateInstrumentStatusForEodQuotes && isEodOrderbook || + _orderbookValidation.ValidateInstrumentStatusForTradingQuotes && !isEodOrderbook) + { + var isDayOff = _assetPairDayOffService.IsDayOff(orderbook.AssetPairId); + + // we should process normal orderbook only if instrument is currently tradable + if (_orderbookValidation.ValidateInstrumentStatusForTradingQuotes && isDayOff && !isEodOrderbook) + { + return; + } + + // and process EOD orderbook only if instrument is currently not tradable + if (_orderbookValidation.ValidateInstrumentStatusForEodQuotes && !isDayOff && isEodOrderbook) + { + //log current schedule for the instrument + var schedule = _scheduleSettingsCache.GetCompiledAssetPairScheduleSettings( + orderbook.AssetPairId, + _dateService.Now(), + TimeSpan.Zero); + + _log.WriteWarning("EOD quotes processing", $"Current schedule: {schedule.ToJson()}", + $"EOD quote for {orderbook.AssetPairId} is skipped, because instrument is within trading hours"); + + return; + } + } + + if (!CheckZeroQuote(orderbook, isEodOrderbook)) + return; + + orderbook.ApplyExchangeIdFromSettings(_defaultExternalExchangeId); + + var bba = orderbook.GetBestPrice(); + + _orderbooks.AddOrUpdate(orderbook.AssetPairId,a => orderbook, (s, book) => orderbook); + + _bestPriceChangeEventChannel.SendEvent(this, new BestPriceChangeEventArgs(bba, isEodOrderbook)); + } + + private bool CheckZeroQuote(ExternalOrderBook orderbook, bool isEodOrderbook) + { + var isOrderbookValid = orderbook.Asks[0].Volume != 0 && orderbook.Bids[0].Volume != 0; + + var assetPair = _assetPairsCache.GetAssetPairByIdOrDefault(orderbook.AssetPairId); + if (assetPair == null) + { + return isOrderbookValid; + } + + //EOD quotes should not change asset pair state + if (isEodOrderbook) + return isOrderbookValid; + + if (!isOrderbookValid) + { + if (!assetPair.IsSuspended) + { + assetPair.IsSuspended = true;//todo apply changes to trading engine + _cqrsSender.SendCommandToSettingsService(new SuspendAssetPairCommand + { + AssetPairId = assetPair.Id, + OperationId = _identityGenerator.GenerateGuid(), + }); + } + } + else + { + if (assetPair.IsSuspended) + { + assetPair.IsSuspended = false;//todo apply changes to trading engine + _cqrsSender.SendCommandToSettingsService(new UnsuspendAssetPairCommand + { + AssetPairId = assetPair.Id, + OperationId = _identityGenerator.GenerateGuid(), + }); + } + } + + return isOrderbookValid; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Stubs/ClientAccountServiceEmptyStub.cs b/src/MarginTrading.Backend.Services/Stubs/ClientAccountServiceEmptyStub.cs new file mode 100644 index 000000000..362ba2202 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Stubs/ClientAccountServiceEmptyStub.cs @@ -0,0 +1,45 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using JetBrains.Annotations; +using Lykke.Service.ClientAccount.Client.Models; +using MarginTrading.Common.Services.Client; + +namespace MarginTrading.Backend.Services.Stubs +{ + [UsedImplicitly] + public class ClientAccountServiceEmptyStub : IClientAccountService + { + public Task GetNotificationId(string clientId) + { + return Task.FromResult(string.Empty); + } + + public Task GetEmail(string clientId) + { + return Task.FromResult(string.Empty); + } + + public Task IsPushEnabled(string clientId) + { + return Task.FromResult(false); + } + + public Task GetMarginEnabledAsync(string clientId) + { + return Task.FromResult(new MarginEnabledSettingsModel + { + Enabled = true, + EnabledLive = true, + TermsOfUseAgreed = true + }); + } + + public Task SetMarginEnabledAsync(string clientId, bool settingsEnabled, bool settingsEnabledLive, + bool settingsTermsOfUseAgreed) + { + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Stubs/EmailSenderLogStub.cs b/src/MarginTrading.Backend.Services/Stubs/EmailSenderLogStub.cs new file mode 100644 index 000000000..da86a84a0 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Stubs/EmailSenderLogStub.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using Common; +using Common.Log; +using Lykke.Service.EmailSender; + +namespace MarginTrading.Backend.Services.Stubs +{ + public class EmailSenderLogStub : IEmailSender + { + private readonly ILog _log; + + public EmailSenderLogStub(ILog log) + { + _log = log; + } + + public void Dispose() + { + + } + + public Task SendAsync(EmailMessage message, EmailAddressee to) + { + return _log.WriteInfoAsync(nameof(EmailSenderLogStub), nameof(SendAsync), message?.ToJson(), + $"Email was send to {to.EmailAddress}"); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Stubs/MtSlackNotificationsSenderLogStub.cs b/src/MarginTrading.Backend.Services/Stubs/MtSlackNotificationsSenderLogStub.cs new file mode 100644 index 000000000..d9c1a7c77 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Stubs/MtSlackNotificationsSenderLogStub.cs @@ -0,0 +1,66 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Text; +using System.Threading.Tasks; +using Common.Log; +using MarginTrading.Common.Enums; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Services.Stubs +{ + public class MtSlackNotificationsSenderLogStub : IMtSlackNotificationsSender + { + private readonly string _appName; + private readonly string _env; + private readonly ILog _log; + + public MtSlackNotificationsSenderLogStub(string appName, string env, ILog log) + { + _appName = appName; + _env = env; + _log = log; + } + + public async Task SendAsync(string type, string sender, string message) + { + if (type.Equals(ChannelTypes.Monitor, StringComparison.InvariantCultureIgnoreCase)) + { + await _log.WriteInfoAsync(sender, type, message); + return; + } + + await _log.WriteInfoAsync(sender, ChannelTypes.MarginTrading, GetSlackMsg(message)); + } + + public async Task SendAsync(DateTime moment, string type, string sender, string message) + { + if (type.Equals(ChannelTypes.Monitor, StringComparison.InvariantCultureIgnoreCase)) + { + await _log.WriteInfoAsync(sender, type, message, moment); + return; + } + + await _log.WriteInfoAsync(sender, ChannelTypes.MarginTrading, GetSlackMsg(message), moment); + } + + public Task SendRawAsync(string type, string sender, string message) + { + return _log.WriteInfoAsync(sender, type, message); + } + + private string GetSlackMsg(string message) + { + var sb = new StringBuilder(); + sb.Append(_appName); + sb.Append(":"); + sb.AppendLine(_env); + sb.AppendLine("\n===================================="); + sb.AppendLine(message); + sb.AppendLine("====================================\n"); + + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/TradingConditions/AccountAssetsCacheService.cs b/src/MarginTrading.Backend.Services/TradingConditions/AccountAssetsCacheService.cs deleted file mode 100644 index 837b6df8f..000000000 --- a/src/MarginTrading.Backend.Services/TradingConditions/AccountAssetsCacheService.cs +++ /dev/null @@ -1,164 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Messages; -using MarginTrading.Backend.Core.TradingConditions; - -namespace MarginTrading.Backend.Services.TradingConditions -{ - public class AccountAssetsCacheService : IAccountAssetsCacheService - { - private Dictionary<(string, string), IAccountAssetPair[]> _accountGroupCache = - new Dictionary<(string, string), IAccountAssetPair[]>(); - private Dictionary<(string, string, string), IAccountAssetPair> _instrumentsCache = - new Dictionary<(string, string, string), IAccountAssetPair>(); - private readonly ReaderWriterLockSlim _lockSlim = new ReaderWriterLockSlim(); - - public IAccountAssetPair GetAccountAsset(string tradingConditionId, string accountAssetId, string instrument) - { - IAccountAssetPair accountAssetPair = null; - - _lockSlim.EnterReadLock(); - try - { - var key = GetInstrumentCacheKey(tradingConditionId, accountAssetId, instrument); - - if (_instrumentsCache.ContainsKey(key)) - accountAssetPair = _instrumentsCache[key]; - } - finally - { - _lockSlim.ExitReadLock(); - } - - if (accountAssetPair == null) - { - throw new Exception(string.Format(MtMessages.AccountAssetForTradingConditionNotFound, - tradingConditionId, accountAssetId, instrument)); - } - - if (accountAssetPair.LeverageMaintenance < 1) - { - throw new Exception(string.Format(MtMessages.LeverageMaintanceIncorrect, tradingConditionId, - accountAssetId, instrument)); - } - - if (accountAssetPair.LeverageInit < 1) - { - throw new Exception(string.Format(MtMessages.LeverageInitIncorrect, tradingConditionId, accountAssetId, - instrument)); - } - - return accountAssetPair; - } - - public IAccountAssetPair GetAccountAssetThrowIfNotFound(string tradingConditionId, - string accountAssetId, string instrument) - { - IAccountAssetPair accountAssetPair = null; - - _lockSlim.EnterReadLock(); - try - { - var key = GetInstrumentCacheKey(tradingConditionId, accountAssetId, instrument); - - if (_instrumentsCache.ContainsKey(key)) - accountAssetPair = _instrumentsCache[key]; - } - finally - { - _lockSlim.ExitReadLock(); - } - - if (accountAssetPair == null) - { - throw new Exception(string.Format(MtMessages.AccountAssetForTradingConditionNotFound, - tradingConditionId, accountAssetId, instrument)); - } - - return accountAssetPair; - } - - public Dictionary GetClientAssets( - IEnumerable accounts) - { - var result = new Dictionary(); - - if (accounts == null) - { - return result; - } - - _lockSlim.EnterReadLock(); - try - { - foreach (var account in accounts) - { - var key = GetAccountGroupCacheKey(account.TradingConditionId, account.BaseAssetId); - - if (!result.ContainsKey(account.BaseAssetId) && _accountGroupCache.ContainsKey(key)) - { - result.Add(account.BaseAssetId, _accountGroupCache[key]); - } - } - } - finally - { - _lockSlim.ExitReadLock(); - } - - return result; - } - - public ICollection GetAccountAssets(string tradingConditionId, string baseAssetId) - { - _lockSlim.EnterReadLock(); - try - { - var key = GetAccountGroupCacheKey(tradingConditionId, baseAssetId); - - if (!_accountGroupCache.TryGetValue(key, out var assets)) - { - return new List(); - } - - return assets; - } - finally - { - _lockSlim.ExitReadLock(); - } - } - - internal void InitAccountAssetsCache(List accountAssets) - { - _lockSlim.EnterWriteLock(); - try - { - _accountGroupCache = accountAssets - .GroupBy(a => GetAccountGroupCacheKey(a.TradingConditionId, a.BaseAssetId)) - .ToDictionary(g => g.Key, g => g.ToArray()); - - _instrumentsCache = accountAssets - .GroupBy(a => GetInstrumentCacheKey(a.TradingConditionId, a.BaseAssetId, a.Instrument)) - .ToDictionary(g => g.Key, g => g.SingleOrDefault()); - } - finally - { - _lockSlim.ExitWriteLock(); - } - } - - private (string, string) GetAccountGroupCacheKey(string tradingCondition, string assetId) - { - return (tradingCondition, assetId); - } - - private (string, string, string) GetInstrumentCacheKey(string tradingCondition, string assetId, string instrument) - { - return (tradingCondition, assetId, instrument); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/TradingConditions/AccountAssetsManager.cs b/src/MarginTrading.Backend.Services/TradingConditions/AccountAssetsManager.cs deleted file mode 100644 index dc9668603..000000000 --- a/src/MarginTrading.Backend.Services/TradingConditions/AccountAssetsManager.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.AzureRepositories.Contract; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Settings; -using MarginTrading.Backend.Core.TradingConditions; - -namespace MarginTrading.Backend.Services.TradingConditions -{ - public class AccountAssetsManager - { - private readonly AccountAssetsCacheService _accountAssetsCacheService; - private readonly IAccountAssetPairsRepository _pairsRepository; - private readonly MarginSettings _settings; - private readonly IClientNotifyService _clientNotifyService; - private readonly IOrderReader _orderReader; - - public AccountAssetsManager( - AccountAssetsCacheService accountAssetsCacheService, - IAccountAssetPairsRepository accountAssetPairsRepository, - MarginSettings settings, - IClientNotifyService clientNotifyService, - IOrderReader orderReader) - { - _accountAssetsCacheService = accountAssetsCacheService; - _pairsRepository = accountAssetPairsRepository; - _settings = settings; - _clientNotifyService = clientNotifyService; - _orderReader = orderReader; - } - - public void Start() - { - UpdateAccountAssetsCache().Wait(); - } - - public async Task UpdateAccountAssetsCache() - { - var accountAssets = (await _pairsRepository.GetAllAsync()).ToList(); - _accountAssetsCacheService.InitAccountAssetsCache(accountAssets); - } - - public async Task> AssignInstruments(string tradingConditionId, - string baseAssetId, string[] instruments) - { - var defaults = _settings.DefaultAccountAssetsSettings ?? new AccountAssetsSettings(); - - var currentInstruments = (await _pairsRepository.GetAllAsync(tradingConditionId, baseAssetId)).ToArray(); - - if (currentInstruments.Any()) - { - var toRemove = currentInstruments.Where(x => !instruments.Contains(x.Instrument)).ToArray(); - - var existingOrderGroups = _orderReader.GetAll() - .Where(o => o.TradingConditionId == tradingConditionId && o.AccountAssetId == baseAssetId) - .GroupBy(o => o.Instrument) - .Where(o => toRemove.Any(i => i.Instrument == o.Key)) - .ToArray(); - - if (existingOrderGroups.Any()) - { - var errorMessage = "Unable to remove following instruments as they have active orders: "; - - foreach (var group in existingOrderGroups) - { - errorMessage += $"{group.Key}({group.Count()} orders) "; - } - - throw new InvalidOperationException(errorMessage); - } - - foreach (var pair in toRemove) - { - await _pairsRepository.Remove(pair.TradingConditionId, pair.BaseAssetId, pair.Instrument); - } - } - - var pairsToAdd = instruments.Where(x => currentInstruments.All(y => y.Instrument != x)); - - var addedPairs = await _pairsRepository.AddAssetPairs(tradingConditionId, baseAssetId, pairsToAdd, defaults); - await UpdateAccountAssetsCache(); - - await _clientNotifyService.NotifyTradingConditionsChanged(tradingConditionId); - - return addedPairs; - } - - public async Task AddOrReplaceAccountAssetAsync(IAccountAssetPair model) - { - await _pairsRepository.AddOrReplaceAsync(model); - await UpdateAccountAssetsCache(); - - await _clientNotifyService.NotifyTradingConditionsChanged(model.TradingConditionId); - - return model; - } - } -} diff --git a/src/MarginTrading.Backend.Services/TradingConditions/AccountGroupCacheService.cs b/src/MarginTrading.Backend.Services/TradingConditions/AccountGroupCacheService.cs deleted file mode 100644 index 6835e57fc..000000000 --- a/src/MarginTrading.Backend.Services/TradingConditions/AccountGroupCacheService.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using MarginTrading.Backend.Core.TradingConditions; - -namespace MarginTrading.Backend.Services.TradingConditions -{ - public class AccountGroupCacheService : IAccountGroupCacheService - { - private List _accountGroups = new List(); - - public IAccountGroup[] GetAllAccountGroups() - { - return _accountGroups.ToArray(); - } - - public IAccountGroup GetAccountGroup(string tradingConditionId, string accountAssetId) - { - return _accountGroups.FirstOrDefault(item => item.TradingConditionId == tradingConditionId && item.BaseAssetId == accountAssetId); - } - - internal void InitAccountGroupsCache(List accountGroups) - { - _accountGroups = accountGroups; - } - } -} diff --git a/src/MarginTrading.Backend.Services/TradingConditions/AccountGroupManager.cs b/src/MarginTrading.Backend.Services/TradingConditions/AccountGroupManager.cs deleted file mode 100644 index 9e2751ab7..000000000 --- a/src/MarginTrading.Backend.Services/TradingConditions/AccountGroupManager.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using Autofac; -using MarginTrading.AzureRepositories.Contract; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Settings; -using MarginTrading.Backend.Core.TradingConditions; - -namespace MarginTrading.Backend.Services.TradingConditions -{ - public class AccountGroupManager : IStartable - { - private readonly AccountGroupCacheService _accountGroupCacheService; - private readonly IAccountGroupRepository _repository; - private readonly MarginSettings _settings; - private readonly IClientNotifyService _clientNotifyService; - - public AccountGroupManager( - AccountGroupCacheService accountGroupCacheService, - IAccountGroupRepository accountGroupRepository, - MarginSettings settings, - IClientNotifyService clientNotifyService) - { - _accountGroupCacheService = accountGroupCacheService; - _repository = accountGroupRepository; - _settings = settings; - _clientNotifyService = clientNotifyService; - } - - public void Start() - { - UpdateAccountGroupCache().Wait(); - } - - public async Task UpdateAccountGroupCache() - { - var accountGroups = (await _repository.GetAllAsync()).ToList(); - _accountGroupCacheService.InitAccountGroupsCache(accountGroups); - } - - public async Task AddAccountGroupsForTradingCondition(string tradingConditionId) - { - foreach (var asset in _settings.BaseAccountAssets) - { - await _repository.AddOrReplaceAsync(new AccountGroup - { - BaseAssetId = asset, - MarginCall = LykkeConstants.DefaultMarginCall, - StopOut = LykkeConstants.DefaultStopOut, - TradingConditionId = tradingConditionId - }); - } - - await UpdateAccountGroupCache(); - } - - public async Task AddOrReplaceAccountGroupAsync(IAccountGroup accountGroup) - { - await _repository.AddOrReplaceAsync(accountGroup); - await UpdateAccountGroupCache(); - - await _clientNotifyService.NotifyTradingConditionsChanged(accountGroup.TradingConditionId); - - return accountGroup; - } - } -} diff --git a/src/MarginTrading.Backend.Services/TradingConditions/IAccountAssetsCacheService.cs b/src/MarginTrading.Backend.Services/TradingConditions/IAccountAssetsCacheService.cs deleted file mode 100644 index 02b222806..000000000 --- a/src/MarginTrading.Backend.Services/TradingConditions/IAccountAssetsCacheService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.TradingConditions; - -namespace MarginTrading.Backend.Services.TradingConditions -{ - public interface IAccountAssetsCacheService - { - IAccountAssetPair GetAccountAsset(string tradingConditionId, string accountAssetId, string instrument); - IAccountAssetPair GetAccountAssetThrowIfNotFound(string tradingConditionId, string accountAssetId, string instrument); - Dictionary GetClientAssets(IEnumerable accounts); - ICollection GetAccountAssets(string tradingConditionId, string accountAssetId); - } -} diff --git a/src/MarginTrading.Backend.Services/TradingConditions/IAccountGroupCacheService.cs b/src/MarginTrading.Backend.Services/TradingConditions/IAccountGroupCacheService.cs deleted file mode 100644 index f0127e6a6..000000000 --- a/src/MarginTrading.Backend.Services/TradingConditions/IAccountGroupCacheService.cs +++ /dev/null @@ -1,11 +0,0 @@ -using JetBrains.Annotations; -using MarginTrading.Backend.Core.TradingConditions; - -namespace MarginTrading.Backend.Services.TradingConditions -{ - public interface IAccountGroupCacheService - { - IAccountGroup[] GetAllAccountGroups(); - [CanBeNull] IAccountGroup GetAccountGroup(string tradingConditionId, string accountAssetId); - } -} diff --git a/src/MarginTrading.Backend.Services/TradingConditions/IOvernightMarginParameterContainer.cs b/src/MarginTrading.Backend.Services/TradingConditions/IOvernightMarginParameterContainer.cs new file mode 100644 index 000000000..80ef9b166 --- /dev/null +++ b/src/MarginTrading.Backend.Services/TradingConditions/IOvernightMarginParameterContainer.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MarginTrading.Backend.Services.TradingConditions +{ + /// + /// Container for the current margin parameter state. + /// + public interface IOvernightMarginParameterContainer + { + /// + /// Get state for the intraday margin parameter. + /// + bool GetOvernightMarginParameterState(); + + /// + /// Set multiplier for the intraday margin parameter to be active at night. + /// + void SetOvernightMarginParameterState(bool isOn); + + /// + /// Get overnight margin parameter values, which depends on state and asset pair's multiplier. + /// + /// + Dictionary<(string, string), decimal> GetOvernightMarginParameterValues(bool onlyNotEqualToOne = false); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/TradingConditions/ITradingConditionsCacheService.cs b/src/MarginTrading.Backend.Services/TradingConditions/ITradingConditionsCacheService.cs index 66640ea7a..744185d8b 100644 --- a/src/MarginTrading.Backend.Services/TradingConditions/ITradingConditionsCacheService.cs +++ b/src/MarginTrading.Backend.Services/TradingConditions/ITradingConditionsCacheService.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; -using JetBrains.Annotations; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using MarginTrading.Backend.Core.TradingConditions; namespace MarginTrading.Backend.Services.TradingConditions diff --git a/src/MarginTrading.Backend.Services/TradingConditions/ITradingConditionsManager.cs b/src/MarginTrading.Backend.Services/TradingConditions/ITradingConditionsManager.cs new file mode 100644 index 000000000..74300a70d --- /dev/null +++ b/src/MarginTrading.Backend.Services/TradingConditions/ITradingConditionsManager.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; + +namespace MarginTrading.Backend.Services.TradingConditions +{ + public interface ITradingConditionsManager + { + Task InitTradingConditionsAsync(); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/TradingConditions/ITradingInstrumentsCacheService.cs b/src/MarginTrading.Backend.Services/TradingConditions/ITradingInstrumentsCacheService.cs new file mode 100644 index 000000000..5f640e9fd --- /dev/null +++ b/src/MarginTrading.Backend.Services/TradingConditions/ITradingInstrumentsCacheService.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using MarginTrading.Backend.Core.TradingConditions; + +namespace MarginTrading.Backend.Services.TradingConditions +{ + public interface ITradingInstrumentsCacheService + { + void InitCache(IEnumerable tradingInstruments); + + void UpdateCache(ITradingInstrument tradingInstrument); + + ITradingInstrument GetTradingInstrument(string tradingConditionId, string instrument); + + (decimal MarginInit, decimal MarginMaintenance) GetMarginRates(ITradingInstrument tradingInstrument, + bool isWarnCheck = false); + + void RemoveFromCache(string tradingConditionId, string instrument); + } +} diff --git a/src/MarginTrading.Backend.Services/TradingConditions/ITradingInstrumentsManager.cs b/src/MarginTrading.Backend.Services/TradingConditions/ITradingInstrumentsManager.cs new file mode 100644 index 000000000..ec82ccc41 --- /dev/null +++ b/src/MarginTrading.Backend.Services/TradingConditions/ITradingInstrumentsManager.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; + +namespace MarginTrading.Backend.Services.TradingConditions +{ + public interface ITradingInstrumentsManager + { + Task UpdateTradingInstrumentsCacheAsync(string id = null); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/TradingConditions/TradingConditionsCacheService.cs b/src/MarginTrading.Backend.Services/TradingConditions/TradingConditionsCacheService.cs index 7db02751f..0d694b753 100644 --- a/src/MarginTrading.Backend.Services/TradingConditions/TradingConditionsCacheService.cs +++ b/src/MarginTrading.Backend.Services/TradingConditions/TradingConditionsCacheService.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -37,7 +40,7 @@ internal void InitTradingConditionsCache(List tradingConditio new ConcurrentDictionary(tradingConditions.ToDictionary(c => c.Id)); } - internal void AddOrUpdateTradingCondition(ITradingCondition tradingCondition) + public void AddOrUpdateTradingCondition(ITradingCondition tradingCondition) { _tradingConditions.AddOrUpdate(tradingCondition.Id, tradingCondition, (s, condition) => tradingCondition); } diff --git a/src/MarginTrading.Backend.Services/TradingConditions/TradingConditionsManager.cs b/src/MarginTrading.Backend.Services/TradingConditions/TradingConditionsManager.cs index 6f16c229a..73b251e2b 100644 --- a/src/MarginTrading.Backend.Services/TradingConditions/TradingConditionsManager.cs +++ b/src/MarginTrading.Backend.Services/TradingConditions/TradingConditionsManager.cs @@ -1,93 +1,59 @@ -using System.Linq; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Linq; using System.Threading.Tasks; using Autofac; using Common.Log; -using MarginTrading.AzureRepositories.Contract; -using MarginTrading.Backend.Core; +using JetBrains.Annotations; using MarginTrading.Backend.Core.TradingConditions; +using MarginTrading.Common.Services; +using MarginTrading.SettingsService.Contracts; +using MarginTrading.SettingsService.Contracts.TradingConditions; namespace MarginTrading.Backend.Services.TradingConditions { - public class TradingConditionsManager : IStartable + [UsedImplicitly] + public class TradingConditionsManager : IStartable, ITradingConditionsManager { - private readonly ITradingConditionRepository _repository; + private readonly ITradingConditionsApi _tradingConditions; private readonly TradingConditionsCacheService _tradingConditionsCacheService; - private readonly IConsole _console; - private readonly AccountGroupManager _accountGroupManager; - private readonly IClientNotifyService _clientNotifyService; + private readonly ILog _log; + private readonly IConvertService _convertService; public TradingConditionsManager( - ITradingConditionRepository repository, + ITradingConditionsApi tradingConditions, TradingConditionsCacheService tradingConditionsCacheService, - IConsole console, - AccountGroupManager accountGroupManager, - IClientNotifyService clientNotifyService) + ILog log, + IConvertService convertService) { - _repository = repository; _tradingConditionsCacheService = tradingConditionsCacheService; - _console = console; - _accountGroupManager = accountGroupManager; - _clientNotifyService = clientNotifyService; + _log = log; + _convertService = convertService; + _tradingConditions = tradingConditions; } public void Start() { - InitTradingConditions().Wait(); + InitTradingConditionsAsync().Wait(); } - public async Task AddOrReplaceTradingConditionAsync(ITradingCondition tradingCondition) + public async Task InitTradingConditionsAsync() { - var allTradingConditions = _tradingConditionsCacheService.GetAllTradingConditions(); - - var defaultTradingCondition = allTradingConditions.FirstOrDefault(item => item.IsDefault); - - if (tradingCondition.IsDefault) - { - if (defaultTradingCondition != null && defaultTradingCondition.Id != tradingCondition.Id) - { - await SetIsDefault(defaultTradingCondition, false); - } - } - else if (defaultTradingCondition?.Id == tradingCondition.Id) - { - var firstNotDefaultCondition = allTradingConditions.FirstOrDefault(item => !item.IsDefault); + await _log.WriteInfoAsync(nameof(InitTradingConditionsAsync), nameof(TradingConditionsManager), + $"Started {nameof(InitTradingConditionsAsync)}"); - if (firstNotDefaultCondition != null) - { - await SetIsDefault(firstNotDefaultCondition, true); - } - } + var tradingConditions = await _tradingConditions.List(); - await _repository.AddOrReplaceAsync(tradingCondition); - - if (!_tradingConditionsCacheService.IsTradingConditionExists(tradingCondition.Id)) + if (tradingConditions != null) { - await _accountGroupManager.AddAccountGroupsForTradingCondition(tradingCondition.Id); + _tradingConditionsCacheService.InitTradingConditionsCache(tradingConditions.Select(t => + (ITradingCondition) _convertService.Convert(t)) + .ToList()); } - - _tradingConditionsCacheService.AddOrUpdateTradingCondition(tradingCondition); - - await _clientNotifyService.NotifyTradingConditionsChanged(tradingCondition.Id); - return tradingCondition; - } - - private async Task SetIsDefault(ITradingCondition tradingCondition, bool isDefault) - { - var existing = TradingCondition.Create(tradingCondition); - existing.IsDefault = isDefault; - await _repository.AddOrReplaceAsync(existing); - } - - private async Task InitTradingConditions() - { - _console.WriteLine($"Started {nameof(InitTradingConditions)}"); - - var tradingConditions = (await _repository.GetAllAsync()).ToList(); - _tradingConditionsCacheService.InitTradingConditionsCache(tradingConditions); - - _console.WriteLine( - $"Finished {nameof(InitTradingConditions)}. Count:{tradingConditions.Count})"); + await _log.WriteInfoAsync(nameof(InitTradingConditionsAsync), nameof(TradingConditionsManager), + $"Finished {nameof(InitTradingConditionsAsync)}. Count:{tradingConditions?.Count ?? 0})"); } } } diff --git a/src/MarginTrading.Backend.Services/TradingConditions/TradingInstrumentsCacheService.cs b/src/MarginTrading.Backend.Services/TradingConditions/TradingInstrumentsCacheService.cs new file mode 100644 index 000000000..c1e5c5bcd --- /dev/null +++ b/src/MarginTrading.Backend.Services/TradingConditions/TradingInstrumentsCacheService.cs @@ -0,0 +1,200 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Common.Log; +using MarginTrading.Backend.Contracts.Events; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Messages; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.TradingConditions; +using MarginTrading.Backend.Services.Infrastructure; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Services.TradingConditions +{ + /// + /// + /// Contains TradingInstruments cache and margin parameter state. + /// + public class TradingInstrumentsCacheService : ITradingInstrumentsCacheService, IOvernightMarginParameterContainer + { + private readonly ICqrsSender _cqrsSender; + private readonly IIdentityGenerator _identityGenerator; + private readonly IDateService _dateService; + private readonly ILog _log; + private readonly IOrderReader _orderReader; + private readonly IAccountsCacheService _accountsCacheService; + + private Dictionary<(string, string), ITradingInstrument> _instrumentsCache = + new Dictionary<(string, string), ITradingInstrument>(); + + private bool _overnightMarginParameterOn; + + private readonly ReaderWriterLockSlim _lockSlim = new ReaderWriterLockSlim(); + + public TradingInstrumentsCacheService( + ICqrsSender cqrsSender, + IIdentityGenerator identityGenerator, + IDateService dateService, + ILog log, + IOrderReader orderReader, + IAccountsCacheService accountsCacheService) + { + _cqrsSender = cqrsSender; + _identityGenerator = identityGenerator; + _dateService = dateService; + _log = log; + _orderReader = orderReader; + _accountsCacheService = accountsCacheService; + } + + public ITradingInstrument GetTradingInstrument(string tradingConditionId, string instrument) + { + ITradingInstrument accountAssetPair = null; + + _lockSlim.EnterReadLock(); + try + { + var key = GetInstrumentCacheKey(tradingConditionId, instrument); + + if (_instrumentsCache.ContainsKey(key)) + accountAssetPair = _instrumentsCache[key]; + } + finally + { + _lockSlim.ExitReadLock(); + } + + if (accountAssetPair == null) + { + throw new Exception(string.Format(MtMessages.AccountAssetForTradingConditionNotFound, + tradingConditionId, instrument)); + } + + if (accountAssetPair.LeverageMaintenance < 1) + { + throw new Exception( + string.Format(MtMessages.LeverageMaintanceIncorrect, tradingConditionId, instrument)); + } + + if (accountAssetPair.LeverageInit < 1) + { + throw new Exception(string.Format(MtMessages.LeverageInitIncorrect, tradingConditionId, + instrument)); + } + + return accountAssetPair; + } + + public (decimal MarginInit, decimal MarginMaintenance) GetMarginRates(ITradingInstrument tradingInstrument, + bool isWarnCheck = false) + { + var parameter = (isWarnCheck || _overnightMarginParameterOn) && + tradingInstrument.OvernightMarginMultiplier > 1 + ? tradingInstrument.OvernightMarginMultiplier + : 1; + + return (parameter / tradingInstrument.LeverageInit, parameter / tradingInstrument.LeverageMaintenance); + } + + public void InitCache(IEnumerable tradingInstruments) + { + _lockSlim.EnterWriteLock(); + try + { + _instrumentsCache = tradingInstruments + .GroupBy(a => GetInstrumentCacheKey(a.TradingConditionId, a.Instrument)) + .ToDictionary(g => g.Key, g => g.SingleOrDefault()); + } + finally + { + _lockSlim.ExitWriteLock(); + } + } + + public void UpdateCache(ITradingInstrument tradingInstrument) + { + _lockSlim.EnterWriteLock(); + try + { + var key = GetInstrumentCacheKey(tradingInstrument.TradingConditionId, tradingInstrument.Instrument); + + _instrumentsCache[key] = tradingInstrument; + } + finally + { + _lockSlim.ExitWriteLock(); + } + } + + public void RemoveFromCache(string tradingConditionId, string instrument) + { + _lockSlim.EnterWriteLock(); + try + { + var key = GetInstrumentCacheKey(tradingConditionId, instrument); + + _instrumentsCache.Remove(key); + } + finally + { + _lockSlim.ExitWriteLock(); + } + } + + public bool GetOvernightMarginParameterState() => _overnightMarginParameterOn; + + public void SetOvernightMarginParameterState(bool isOn) + { + var multiplierChanged = _overnightMarginParameterOn != isOn; + + _overnightMarginParameterOn = isOn; + + if (multiplierChanged) + { + foreach (var position in _orderReader.GetPositions()) + { + position.FplDataShouldBeRecalculated(); + } + + foreach (var account in _accountsCacheService.GetAll().Where(a => a.GetOpenPositionsCount() > 0)) + { + account.CacheNeedsToBeUpdated(); + } + + //send event when the overnight margin parameter is enabled/disabled (margin requirement changes) + _cqrsSender.PublishEvent(new OvernightMarginParameterChangedEvent + { + CorrelationId = _identityGenerator.GenerateGuid(), + EventTimestamp = _dateService.Now(), + CurrentState = _overnightMarginParameterOn, + ParameterValues = GetOvernightMarginParameterValues(true), + }); + } + } + + public Dictionary<(string, string), decimal> GetOvernightMarginParameterValues(bool onlyNotEqualToOne = false) + { + _lockSlim.EnterReadLock(); + try + { + return _instrumentsCache + .Where(x => !onlyNotEqualToOne || x.Value.OvernightMarginMultiplier != 1) + .ToDictionary(x => x.Key, x => _overnightMarginParameterOn + ? x.Value.OvernightMarginMultiplier + : 1); + } + finally + { + _lockSlim.ExitReadLock(); + } + } + + private static (string, string) GetInstrumentCacheKey(string tradingCondition, string instrument) => + (tradingCondition, instrument); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/TradingConditions/TradingInstrumentsManager.cs b/src/MarginTrading.Backend.Services/TradingConditions/TradingInstrumentsManager.cs new file mode 100644 index 000000000..b42cd55e7 --- /dev/null +++ b/src/MarginTrading.Backend.Services/TradingConditions/TradingInstrumentsManager.cs @@ -0,0 +1,85 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Threading.Tasks; +using Common.Log; +using JetBrains.Annotations; +using MarginTrading.Backend.Core.TradingConditions; +using MarginTrading.Common.Services; +using MarginTrading.SettingsService.Contracts; +using MarginTrading.SettingsService.Contracts.TradingConditions; +using Newtonsoft.Json; + +namespace MarginTrading.Backend.Services.TradingConditions +{ + [UsedImplicitly] + public class TradingInstrumentsManager : ITradingInstrumentsManager + { + private readonly ITradingInstrumentsCacheService _tradingInstrumentsCacheService; + private readonly ITradingInstrumentsApi _tradingInstrumentsApi; + private readonly IConvertService _convertService; + private readonly ILog _log; + + public TradingInstrumentsManager( + ITradingInstrumentsCacheService tradingInstrumentsCacheService, + ITradingInstrumentsApi tradingInstrumentsApi, + IConvertService convertService, + ILog log) + { + _tradingInstrumentsCacheService = tradingInstrumentsCacheService; + _tradingInstrumentsApi = tradingInstrumentsApi; + _convertService = convertService; + _log = log; + } + + public void Start() + { + UpdateTradingInstrumentsCacheAsync().Wait(); + } + + public async Task UpdateTradingInstrumentsCacheAsync(string id = null) + { + await _log.WriteInfoAsync(nameof(UpdateTradingInstrumentsCacheAsync), nameof(TradingInstrumentsManager), + $"Started {nameof(UpdateTradingInstrumentsCacheAsync)}"); + + var count = 0; + if (string.IsNullOrEmpty(id)) + { + var instruments = (await _tradingInstrumentsApi.List(string.Empty))? + .Select(i =>_convertService.Convert(i)) + .ToDictionary(x => x.GetKey()); + + if (instruments != null) + { + _tradingInstrumentsCacheService.InitCache(instruments.Values.Select(i => (ITradingInstrument) i) + .ToList()); + + count = instruments.Count; + } + } + else + { + var ids = JsonConvert.DeserializeObject(id); + + var instrumentContract = await _tradingInstrumentsApi.Get(ids.TradingConditionId, ids.Instrument); + + if (instrumentContract != null) + { + var newInstrument = _convertService.Convert(instrumentContract); + + _tradingInstrumentsCacheService.UpdateCache(newInstrument); + + count = 1; + } + else + { + _tradingInstrumentsCacheService.RemoveFromCache(ids.TradingConditionId, ids.Instrument); + } + } + + await _log.WriteInfoAsync(nameof(UpdateTradingInstrumentsCacheAsync), nameof(TradingInstrumentsManager), + $"Finished {nameof(UpdateTradingInstrumentsCacheAsync)} with count: {count}."); + } + } +} diff --git a/src/MarginTrading.Backend.Services/TradingEngine.cs b/src/MarginTrading.Backend.Services/TradingEngine.cs index 073fdcb3e..3947d773f 100644 --- a/src/MarginTrading.Backend.Services/TradingEngine.cs +++ b/src/MarginTrading.Backend.Services/TradingEngine.cs @@ -1,106 +1,122 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Common; using Common.Log; +using JetBrains.Annotations; using Lykke.Common; +using MarginTrading.Backend.Contracts.Activities; using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.Exceptions; -using MarginTrading.Backend.Core.MatchedOrders; using MarginTrading.Backend.Core.MatchingEngines; +using MarginTrading.Backend.Core.Orders; using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Core.Trading; using MarginTrading.Backend.Services.AssetPairs; using MarginTrading.Backend.Services.Events; using MarginTrading.Backend.Services.Infrastructure; -using MarginTrading.Backend.Services.TradingConditions; +using MarginTrading.Backend.Services.Workflow.Liquidation.Commands; +using MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Commands; +using MarginTrading.Common.Extensions; +using MarginTrading.Common.Services; +using MoreLinq; namespace MarginTrading.Backend.Services { - public sealed class TradingEngine : ITradingEngine, IEventConsumer + public sealed class TradingEngine : ITradingEngine, + IEventConsumer, + IEventConsumer { private readonly IEventChannel _marginCallEventChannel; - private readonly IEventChannel _stopoutEventChannel; private readonly IEventChannel _orderPlacedEventChannel; - private readonly IEventChannel _orderClosedEventChannel; + private readonly IEventChannel _orderExecutedEventChannel; private readonly IEventChannel _orderCancelledEventChannel; - private readonly IEventChannel _orderLimitsChangesEventChannel; - private readonly IEventChannel _orderClosingEventChannel; + private readonly IEventChannel _orderChangedEventChannel; + private readonly IEventChannel _orderExecutionStartedEvenChannel; private readonly IEventChannel _orderActivatedEventChannel; private readonly IEventChannel _orderRejectedEventChannel; - private readonly IQuoteCacheService _quoteCashService; - private readonly IAccountUpdateService _accountUpdateService; - private readonly ICommissionService _swapCommissionService; private readonly IValidateOrderService _validateOrderService; - private readonly IEquivalentPricesService _equivalentPricesService; private readonly IAccountsCacheService _accountsCacheService; private readonly OrdersCache _ordersCache; - private readonly IAccountAssetsCacheService _accountAssetsCacheService; private readonly IMatchingEngineRouter _meRouter; private readonly IThreadSwitcher _threadSwitcher; - private readonly IContextFactory _contextFactory; private readonly IAssetPairDayOffService _assetPairDayOffService; private readonly ILog _log; + private readonly IDateService _dateService; + private readonly ICfdCalculatorService _cfdCalculatorService; + private readonly IIdentityGenerator _identityGenerator; + private readonly IAssetPairsCache _assetPairsCache; + private readonly ICqrsSender _cqrsSender; + private readonly IEventChannel _stopOutEventChannel; + private readonly IQuoteCacheService _quoteCacheService; + private readonly MarginTradingSettings _marginTradingSettings; public TradingEngine( IEventChannel marginCallEventChannel, - IEventChannel stopoutEventChannel, IEventChannel orderPlacedEventChannel, - IEventChannel orderClosedEventChannel, + IEventChannel orderClosedEventChannel, IEventChannel orderCancelledEventChannel, - IEventChannel orderLimitsChangesEventChannel, - IEventChannel orderClosingEventChannel, + IEventChannel orderChangedEventChannel, + IEventChannel orderExecutionStartedEventChannel, IEventChannel orderActivatedEventChannel, IEventChannel orderRejectedEventChannel, - IValidateOrderService validateOrderService, - IQuoteCacheService quoteCashService, - IAccountUpdateService accountUpdateService, - ICommissionService swapCommissionService, - IEquivalentPricesService equivalentPricesService, IAccountsCacheService accountsCacheService, OrdersCache ordersCache, - IAccountAssetsCacheService accountAssetsCacheService, IMatchingEngineRouter meRouter, IThreadSwitcher threadSwitcher, - IContextFactory contextFactory, IAssetPairDayOffService assetPairDayOffService, - ILog log) + ILog log, + IDateService dateService, + ICfdCalculatorService cfdCalculatorService, + IIdentityGenerator identityGenerator, + IAssetPairsCache assetPairsCache, + ICqrsSender cqrsSender, + IEventChannel stopOutEventChannel, + IQuoteCacheService quoteCacheService, + MarginTradingSettings marginTradingSettings) { _marginCallEventChannel = marginCallEventChannel; - _stopoutEventChannel = stopoutEventChannel; _orderPlacedEventChannel = orderPlacedEventChannel; - _orderClosedEventChannel = orderClosedEventChannel; + _orderExecutedEventChannel = orderClosedEventChannel; _orderCancelledEventChannel = orderCancelledEventChannel; _orderActivatedEventChannel = orderActivatedEventChannel; - _orderClosingEventChannel = orderClosingEventChannel; - _orderLimitsChangesEventChannel = orderLimitsChangesEventChannel; + _orderExecutionStartedEvenChannel = orderExecutionStartedEventChannel; + _orderChangedEventChannel = orderChangedEventChannel; _orderRejectedEventChannel = orderRejectedEventChannel; - _quoteCashService = quoteCashService; - _accountUpdateService = accountUpdateService; - _swapCommissionService = swapCommissionService; _validateOrderService = validateOrderService; - _equivalentPricesService = equivalentPricesService; _accountsCacheService = accountsCacheService; _ordersCache = ordersCache; - _accountAssetsCacheService = accountAssetsCacheService; _meRouter = meRouter; _threadSwitcher = threadSwitcher; - _contextFactory = contextFactory; _assetPairDayOffService = assetPairDayOffService; _log = log; + _dateService = dateService; + _cfdCalculatorService = cfdCalculatorService; + _identityGenerator = identityGenerator; + _assetPairsCache = assetPairsCache; + _cqrsSender = cqrsSender; + _stopOutEventChannel = stopOutEventChannel; + _quoteCacheService = quoteCacheService; + _marginTradingSettings = marginTradingSettings; } public async Task PlaceOrderAsync(Order order) { + _orderPlacedEventChannel.SendEvent(this, new OrderPlacedEventArgs(order)); + try { - _validateOrderService.Validate(order); - - if (order.ExpectedOpenPrice.HasValue) + if (order.OrderType != OrderType.Market) { - PlacePendingOrder(order); + await PlacePendingOrder(order); return order; } @@ -111,509 +127,931 @@ public async Task PlaceOrderAsync(Order order) RejectOrder(order, ex.RejectReason, ex.Message, ex.Comment); return order; } + catch (Exception ex) + { + RejectOrder(order, OrderRejectReason.TechnicalError, ex.Message); + _log.WriteError(nameof(TradingEngine), nameof(PlaceOrderAsync), ex); + return order; + } } - - private Task PlaceMarketOrderByMatchingEngineAsync(Order order, IMatchingEngineBase matchingEngine) + + private async Task PlaceOrderByMarketPrice(Order order) { - order.OpenOrderbookId = matchingEngine.Id; - order.MatchingEngineMode = matchingEngine.Mode; - - matchingEngine.MatchMarketOrderForOpen(order, matchedOrders => + try { - if (!matchedOrders.Any()) + var me = _meRouter.GetMatchingEngineForExecution(order); + + foreach (var positionId in order.PositionsToBeClosed) { - order.CloseDate = DateTime.UtcNow; - order.Status = OrderStatus.Rejected; - order.RejectReason = OrderRejectReason.NoLiquidity; - order.RejectReasonText = "No orders to match"; - return false; + if (!_ordersCache.Positions.TryGetPositionById(positionId, out var position)) + { + RejectOrder(order, OrderRejectReason.ParentPositionDoesNotExist, positionId); + return order; + } + + if (position.Status != PositionStatus.Active) + { + RejectOrder(order, OrderRejectReason.ParentPositionIsNotActive, positionId); + return order; + } + + position.StartClosing(_dateService.Now(), order.OrderType.GetCloseReason(), order.Originator, ""); } - if (matchedOrders.SummaryVolume < Math.Abs(order.Volume) && order.FillType == OrderFillType.FillOrKill) + return await ExecuteOrderByMatchingEngineAsync(order, me, true); + } + catch (Exception ex) + { + var reason = ex is QuoteNotFoundException + ? OrderRejectReason.NoLiquidity + : OrderRejectReason.TechnicalError; + RejectOrder(order, reason, ex.Message); + _log.WriteError(nameof(TradingEngine), nameof(PlaceOrderByMarketPrice), ex); + return order; + } + } + + private async Task ExecutePendingOrder(Order order) + { + await PlaceOrderByMarketPrice(order); + + if (order.IsExecutionNotStarted) + { + foreach (var positionId in order.PositionsToBeClosed) { - order.CloseDate = DateTime.UtcNow; - order.Status = OrderStatus.Rejected; - order.RejectReason = OrderRejectReason.NoLiquidity; - order.RejectReasonText = "Not fully matched"; - return false; + if (_ordersCache.Positions.TryGetPositionById(positionId, out var position) + && position.Status == PositionStatus.Closing) + { + position.CancelClosing(_dateService.Now()); + } } + } - try + return order; + } + + private async Task PlacePendingOrder(Order order) + { + if (order.IsBasicPendingOrder() || !string.IsNullOrEmpty(order.ParentPositionId)) + { + Position parentPosition = null; + + if (!string.IsNullOrEmpty(order.ParentPositionId)) { - CheckIfWeCanOpenPosition(order, matchedOrders); + parentPosition = _ordersCache.Positions.GetPositionById(order.ParentPositionId); + parentPosition.AddRelatedOrder(order); } - catch (ValidateOrderException e) + + order.Activate(_dateService.Now(), false, parentPosition?.ClosePrice); + _ordersCache.Active.Add(order); + _orderActivatedEventChannel.SendEvent(this, new OrderActivatedEventArgs(order)); + } + else if (!string.IsNullOrEmpty(order.ParentOrderId)) + { + if (_ordersCache.TryGetOrderById(order.ParentOrderId, out var parentOrder)) { - order.CloseDate = DateTime.UtcNow; - order.Status = OrderStatus.Rejected; - order.RejectReason = e.RejectReason; - order.RejectReasonText = e.Message; - return false; + parentOrder.AddRelatedOrder(order); + order.MakeInactive(_dateService.Now()); + _ordersCache.Inactive.Add(order); + return; } - _equivalentPricesService.EnrichOpeningOrder(order); - - MakeOrderActive(order); - - return true; - }); + //may be it was market and now it is position + if (_ordersCache.Positions.TryGetPositionById(order.ParentOrderId, out var parentPosition)) + { + parentPosition.AddRelatedOrder(order); + if (parentPosition.Volume != -order.Volume) + { + order.ChangeVolume(-parentPosition.Volume, _dateService.Now(), OriginatorType.System); + } - if (order.Status == OrderStatus.Rejected) + order.Activate(_dateService.Now(), true, parentPosition.ClosePrice); + _ordersCache.Active.Add(order); + _orderActivatedEventChannel.SendEvent(this, new OrderActivatedEventArgs(order)); + } + else + { + order.MakeInactive(_dateService.Now()); + _ordersCache.Inactive.Add(order); + CancelPendingOrder(order.Id, order.AdditionalInfo, + _identityGenerator.GenerateAlphanumericId(), + $"Parent order closed the position, so {order.OrderType.ToString()} order is cancelled"); + } + } + else { - _orderRejectedEventChannel.SendEvent(this, new OrderRejectedEventArgs(order)); + throw new ValidateOrderException(OrderRejectReason.InvalidParent, "Order parent is not valid"); } - return Task.FromResult(order); + await ExecutePendingOrderIfNeededAsync(order); } - private void RejectOrder(Order order, OrderRejectReason reason, string message, string comment = null) + private async Task ExecuteOrderByMatchingEngineAsync(Order order, IMatchingEngineBase matchingEngine, + bool checkStopout, OrderModality modality = OrderModality.Regular) { - order.CloseDate = DateTime.UtcNow; - order.Status = OrderStatus.Rejected; - order.RejectReason = reason; - order.RejectReasonText = message; - order.Comment = comment; - _orderRejectedEventChannel.SendEvent(this, new OrderRejectedEventArgs(order)); - } - - private Task PlaceOrderByMarketPrice(Order order) - { - try + var now = _dateService.Now(); + + //just in case ) + if (CheckIfOrderIsExpired(order, now)) { - var me = _meRouter.GetMatchingEngineForOpen(order); + return order; + } + + order.StartExecution(_dateService.Now(), matchingEngine.Id); - return PlaceMarketOrderByMatchingEngineAsync(order, me); + _orderExecutionStartedEvenChannel.SendEvent(this, new OrderExecutionStartedEventArgs(order)); + + ChangeOrderVolumeIfNeeded(order); + + var equivalentRate = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.EquivalentAsset, + order.AssetPairId, order.LegalEntity); + var fxRate = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.AccountAssetId, + order.AssetPairId, order.LegalEntity); + + order.SetRates(equivalentRate, fxRate); + + var shouldOpenNewPosition = ShouldOpenNewPosition(order); + + if (modality == OrderModality.Regular && order.Originator != OriginatorType.System) + { + try + { + _validateOrderService.MakePreTradeValidation( + order, + shouldOpenNewPosition, + matchingEngine); + } + catch (ValidateOrderException ex) + { + RejectOrder(order, ex.RejectReason, ex.Message, ex.Comment); + return order; + } } - catch (QuoteNotFoundException ex) + + var matchedOrders = await matchingEngine.MatchOrderAsync(order, shouldOpenNewPosition, modality); + + if (!matchedOrders.Any()) + { + RejectOrder(order, OrderRejectReason.NoLiquidity, "No orders to match", ""); + return order; + } + + if (matchedOrders.SummaryVolume < Math.Abs(order.Volume)) { - RejectOrder(order, OrderRejectReason.NoLiquidity, ex.Message); - return Task.FromResult(order); + if (order.FillType == OrderFillType.FillOrKill) + { + RejectOrder(order, OrderRejectReason.NoLiquidity, "Not fully matched", ""); + return order; + } + else + { + order.PartiallyExecute(_dateService.Now(), matchedOrders); + _ordersCache.InProgress.Add(order); + return order; + } } - catch (Exception ex) + + if (order.Status == OrderStatus.ExecutionStarted) { - RejectOrder(order, OrderRejectReason.TechnicalError, ex.Message); - _log.WriteError(nameof(TradingEngine), nameof(PlaceOrderByMarketPrice), ex); - return Task.FromResult(order); + var accuracy = _assetPairsCache.GetAssetPairByIdOrDefault(order.AssetPairId)?.Accuracy ?? + AssetPairsCache.DefaultAssetPairAccuracy; + + order.Execute(_dateService.Now(), matchedOrders, accuracy); + + _orderExecutedEventChannel.SendEvent(this, new OrderExecutedEventArgs(order)); + + if (checkStopout) + { + CheckStopout(order); + } } + + return order; } - private void MakeOrderActive(Order order) + private bool CheckIfOrderIsExpired(Order order, DateTime now) { - order.OpenDate = DateTime.UtcNow; - order.Status = OrderStatus.Active; + if (order.OrderType != OrderType.Market && + order.Validity.HasValue && + now.Date > order.Validity.Value.Date) + { + order.Expire(now); + _orderCancelledEventChannel.SendEvent(this, + new OrderCancelledEventArgs(order, + new OrderCancelledMetadata {Reason = OrderCancellationReasonContract.Expired})); + return true; + } - var account = _accountsCacheService.Get(order.ClientId, order.AccountId); - _swapCommissionService.SetCommissionRates(account.TradingConditionId, account.BaseAssetId, order); - _ordersCache.ActiveOrders.Add(order); - _orderActivatedEventChannel.SendEvent(this, new OrderActivatedEventArgs(order)); + return false; } - - private void CheckIfWeCanOpenPosition(Order order, MatchedOrderCollection matchedOrders) + + private void ChangeOrderVolumeIfNeeded(Order order) { - var accountAsset = _accountAssetsCacheService.GetAccountAsset(order.TradingConditionId, order.AccountAssetId, order.Instrument); - _validateOrderService.ValidateInstrumentPositionVolume(accountAsset, order); + if (order.PositionsToBeClosed.Any()) + { + var netVolume = 0M; + var rejectReason = default(OrderRejectReason?); + foreach (var positionId in order.PositionsToBeClosed) + { + if (!_ordersCache.Positions.TryGetPositionById(positionId, out var position)) + { + rejectReason = OrderRejectReason.ParentPositionDoesNotExist; + continue; + } - order.MatchedOrders.AddRange(matchedOrders); - order.OpenPrice = Math.Round(order.MatchedOrders.WeightedAveragePrice, order.AssetAccuracy); + if (position.Status != PositionStatus.Closing) + { + rejectReason = OrderRejectReason.TechnicalError; + continue; + } - var defaultMatchingEngine = _meRouter.GetMatchingEngineForClose(order); + netVolume += position.Volume; + } - var closePrice = defaultMatchingEngine.GetPriceForClose(order); + if (netVolume == 0M && rejectReason.HasValue) + { + order.Reject(rejectReason.Value, + rejectReason.Value == OrderRejectReason.ParentPositionDoesNotExist + ? "Related position does not exist" + : "Related position is not in closing state", "", _dateService.Now()); + _orderRejectedEventChannel.SendEvent(this, new OrderRejectedEventArgs(order)); + return; + } - if (!closePrice.HasValue) - { - throw new ValidateOrderException(OrderRejectReason.NoLiquidity, "No orders to match for close"); + // there is no any global lock of positions / orders, that's why it is possible to have concurrency + // in position close process + // since orders, that have not empty PositionsToBeClosed should close positions and not open new ones + // volume of executed order should be equal to position volume, but should have opposite sign + if (order.Volume != -netVolume) + { + var metadata = new OrderChangedMetadata + { + OldValue = order.Volume.ToString("F2"), + UpdatedProperty = OrderChangedProperty.Volume + }; + order.ChangeVolume(-netVolume, _dateService.Now(), order.Originator); + _orderChangedEventChannel.SendEvent(this, new OrderChangedEventArgs(order, metadata)); + } } - - order.UpdateClosePrice(Math.Round(closePrice.Value, order.AssetAccuracy)); - - //TODO: very strange check.. think about it one more time - var guessAccount = _accountUpdateService.GuessAccountWithNewActiveOrder(order); - var guessAccountLevel = guessAccount.GetAccountLevel(); + } + + private void CheckStopout(Order order) + { + var account = _accountsCacheService.Get(order.AccountId); + var accountLevel = account.GetAccountLevel(); - if (guessAccountLevel != AccountLevel.None) + if (accountLevel == AccountLevel.StopOut) { - order.OpenPrice = 0; - order.ClosePrice = 0; - order.MatchedOrders = new MatchedOrderCollection(); + CommitStopOut(account, null); } - - if (guessAccountLevel == AccountLevel.MarginCall) + else if (accountLevel > AccountLevel.None) { - throw new ValidateOrderException(OrderRejectReason.AccountInvalidState, - "Opening the position will lead to account Margin Call level"); + _marginCallEventChannel.SendEvent(this, new MarginCallEventArgs(account, accountLevel)); } + } - if (guessAccountLevel == AccountLevel.StopOUt) + public bool ShouldOpenNewPosition(Order order) + { + var shouldOpenNewPosition = order.ForceOpen; + + if (!order.PositionsToBeClosed.Any() && !shouldOpenNewPosition) { - throw new ValidateOrderException(OrderRejectReason.AccountInvalidState, - "Opening the position will lead to account Stop Out level"); + var existingPositions = + _ordersCache.Positions.GetPositionsByInstrumentAndAccount(order.AssetPairId, order.AccountId); + var netVolume = existingPositions.Where(p => p.Status == PositionStatus.Active).Sum(p => p.Volume); + var newNetVolume = netVolume + order.Volume; + + shouldOpenNewPosition = (Math.Sign(netVolume) != Math.Sign(newNetVolume) && newNetVolume != 0) || + Math.Abs(netVolume) < Math.Abs(newNetVolume); } + + return shouldOpenNewPosition; } - private void PlacePendingOrder(Order order) + private void RejectOrder(Order order, OrderRejectReason reason, string message, string comment = null) { - var me = _meRouter.GetMatchingEngineForOpen(order); - order.MatchingEngineMode = me.Mode; + if (order.OrderType == OrderType.Market + || reason != OrderRejectReason.NoLiquidity + || order.PendingOrderRetriesCount >= _marginTradingSettings.PendingOrderRetriesThreshold) + { + order.Reject(reason, message, comment, _dateService.Now()); - using (_contextFactory.GetWriteSyncContext($"{nameof(TradingEngine)}.{nameof(PlacePendingOrder)}")) - _ordersCache.WaitingForExecutionOrders.Add(order); + _orderRejectedEventChannel.SendEvent(this, new OrderRejectedEventArgs(order)); + } + //TODO: think how to avoid infinite loop + else if (!_ordersCache.TryGetOrderById(order.Id, out _)) // all pending orders should be returned to active state if there is no liquidity + { + order.CancelExecution(_dateService.Now()); + + _ordersCache.Active.Add(order); - _orderPlacedEventChannel.SendEvent(this, new OrderPlacedEventArgs(order)); + var initialAdditionalInfo = order.AdditionalInfo; + //to evade additional OnBehalf fee for this event + order.AdditionalInfo = initialAdditionalInfo.MakeNonOnBehalf(); + + _orderChangedEventChannel.SendEvent(this, + new OrderChangedEventArgs(order, + new OrderChangedMetadata {UpdatedProperty = OrderChangedProperty.None})); + + order.AdditionalInfo = initialAdditionalInfo; + } } #region Orders waiting for execution - - private void ProcessOrdersWaitingForExecution(string instrument) + + private void ProcessOrdersWaitingForExecution(InstrumentBidAskPair quote) { - ProcessPendingOrdersMarginRecalc(instrument); - - var orders = GetPendingOrdersToBeExecuted(instrument).ToArray(); + //TODO: MTC-155 + //ProcessPendingOrdersMarginRecalc(instrument); - if (orders.Length == 0) + var orders = GetPendingOrdersToBeExecuted(quote).GetSortedForExecution(); + + if (!orders.Any()) return; - using (_contextFactory.GetWriteSyncContext($"{nameof(TradingEngine)}.{nameof(ProcessOrdersWaitingForExecution)}")) + foreach (var order in orders) { - foreach (var order in orders) - _ordersCache.WaitingForExecutionOrders.Remove(order); + _threadSwitcher.SwitchThread(async () => + { + await ExecutePendingOrder(order); + }); } - - //TODO: think how to make sure that we don't loose orders - _threadSwitcher.SwitchThread(async () => - { - foreach (var order in orders) - await PlaceOrderByMarketPrice(order); - }); - } - private IEnumerable GetPendingOrdersToBeExecuted(string instrument) + private IEnumerable GetPendingOrdersToBeExecuted(InstrumentBidAskPair quote) { - var pendingOrders = _ordersCache.WaitingForExecutionOrders.GetOrdersByInstrument(instrument) - .OrderBy(item => item.CreateDate); + var pendingOrders = _ordersCache.Active.GetOrdersByInstrument(quote.Instrument); foreach (var order in pendingOrders) { - InstrumentBidAskPair pair; + var price = quote.GetPriceForOrderDirection(order.Direction); - if (_quoteCashService.TryGetQuoteById(order.Instrument, out pair)) + if (order.IsSuitablePriceForPendingOrder(price) && + _validateOrderService.CheckIfPendingOrderExecutionPossible(order.AssetPairId, order.OrderType, + ShouldOpenNewPosition(order))) { - var price = pair.GetPriceForOrderType(order.GetCloseType()); - - if (order.IsSuitablePriceForPendingOrder(price) && - !_assetPairDayOffService.ArePendingOrdersDisabled(order.Instrument)) + if (quote.GetVolumeForOrderDirection(order.Direction) >= Math.Abs(order.Volume)) + { + _ordersCache.Active.Remove(order); yield return order; + } + else //let's validate one more time, considering orderbook depth + { + var me = _meRouter.GetMatchingEngineForExecution(order); + var executionPriceInfo = me.GetBestPriceForOpen(order.AssetPairId, order.Volume); + + if (executionPriceInfo.price.HasValue && order.IsSuitablePriceForPendingOrder(executionPriceInfo.price.Value)) + { + _ordersCache.Active.Remove(order); + yield return order; + } + } } + } } - private void ProcessPendingOrdersMarginRecalc(string instrument) + public void ProcessExpiredOrders(DateTime operationIntervalEnd) { - var pendingOrders = _ordersCache.GetPendingForMarginRecalc(instrument); + var pendingOrders = _ordersCache.Active.GetAllOrders(); + var now = _dateService.Now(); - foreach (var pendingOrder in pendingOrders) + foreach (var order in pendingOrders) { - pendingOrder.UpdatePendingOrderMargin(); + if (order.Validity.HasValue && operationIntervalEnd.Date >= order.Validity.Value.Date) + { + _ordersCache.Active.Remove(order); + order.Expire(now); + _orderCancelledEventChannel.SendEvent( + this, + new OrderCancelledEventArgs( + order, + new OrderCancelledMetadata {Reason = OrderCancellationReasonContract.Expired})); + } } } +// private void ProcessPendingOrdersMarginRecalc(string instrument) +// { +// var pendingOrders = _ordersCache.GetPendingForMarginRecalc(instrument); +// +// foreach (var pendingOrder in pendingOrders) +// { +// pendingOrder.UpdatePendingOrderMargin(); +// } +// } + #endregion - #region Active orders + + #region Positions - private void ProcessOrdersActive(string instrument) + private void UpdatePositionsFxRates(InstrumentBidAskPair quote) { - var stopoutAccounts = UpdateClosePriceAndDetectStopout(instrument).ToArray(); - foreach (var account in stopoutAccounts) - CommitStopout(account); - - foreach (var order in _ordersCache.ActiveOrders.GetOrdersByInstrument(instrument)) + foreach (var position in _ordersCache.GetPositionsByFxAssetPairId(quote.Instrument)) { - if (order.IsStopLoss()) - SetOrderToClosingState(order, OrderCloseReason.StopLoss); - else if (order.IsTakeProfit()) - SetOrderToClosingState(order, OrderCloseReason.TakeProfit); + var fxPrice = _cfdCalculatorService.GetPrice(quote, position.FxToAssetPairDirection, + position.Volume * (position.ClosePrice - position.OpenPrice) > 0); + + position.UpdateCloseFxPrice(fxPrice); } + } - ProcessOrdersClosing(instrument); + private void ProcessPositions(InstrumentBidAskPair quote) + { + var stopoutAccounts = UpdateClosePriceAndDetectStopout(quote); + + foreach (var account in stopoutAccounts) + CommitStopOut(account, quote); } - private IEnumerable UpdateClosePriceAndDetectStopout(string instrument) + private List UpdateClosePriceAndDetectStopout(InstrumentBidAskPair quote) { - var openOrders = _ordersCache.ActiveOrders.GetOrdersByInstrument(instrument) + var positionsByAccounts = _ordersCache.Positions.GetPositionsByInstrument(quote.Instrument) .GroupBy(x => x.AccountId).ToDictionary(x => x.Key, x => x.ToArray()); - foreach (var accountOrders in openOrders) - { - var anyOrder = accountOrders.Value.FirstOrDefault(); - if (null == anyOrder) - continue; + var accountsWithStopout = new List(); - var account = _accountsCacheService.Get(anyOrder.ClientId, anyOrder.AccountId); + foreach (var accountPositions in positionsByAccounts) + { + var account = _accountsCacheService.Get(accountPositions.Key); var oldAccountLevel = account.GetAccountLevel(); - foreach (var order in accountOrders.Value) + foreach (var position in accountPositions.Value) { - var defaultMatchingEngine = _meRouter.GetMatchingEngineForClose(order); + var closeOrderDirection = position.Volume.GetClosePositionOrderDirection(); + var closePrice = quote.GetPriceForOrderDirection(closeOrderDirection); + + if (quote.GetVolumeForOrderDirection(closeOrderDirection) < Math.Abs(position.Volume)) + { + var defaultMatchingEngine = _meRouter.GetMatchingEngineForClose(position.OpenMatchingEngineId); - var closePrice = defaultMatchingEngine.GetPriceForClose(order); + var orderbookPrice = defaultMatchingEngine.GetPriceForClose(position.AssetPairId, position.Volume, + position.ExternalProviderId); - if (closePrice.HasValue) + if (orderbookPrice.HasValue) + closePrice = orderbookPrice.Value; + } + + if (closePrice != 0) { - order.UpdateClosePrice(Math.Round(closePrice.Value, order.AssetAccuracy)); + position.UpdateClosePriceWithoutAccountUpdate(closePrice); + + UpdateTrailingStops(position); } } + + account.CacheNeedsToBeUpdated(); var newAccountLevel = account.GetAccountLevel(); + if (newAccountLevel == AccountLevel.StopOut) + accountsWithStopout.Add(account); + if (oldAccountLevel != newAccountLevel) { - NotifyAccountLevelChanged(account, newAccountLevel); - - if (newAccountLevel == AccountLevel.StopOUt) - yield return account; + _marginCallEventChannel.SendEvent(this, new MarginCallEventArgs(account, newAccountLevel)); } } + + return accountsWithStopout; } - private void CommitStopout(MarginTradingAccount account) + private void UpdateTrailingStops(Position position) { - var pendingOrders = _ordersCache.WaitingForExecutionOrders.GetOrdersByAccountIds(account.Id); + var trailingOrderIds = position.RelatedOrders.Where(o => o.Type == OrderType.TrailingStop) + .Select(o => o.Id); - var cancelledPendingOrders = new List(); - - foreach (var pendingOrder in pendingOrders) + foreach (var trailingOrderId in trailingOrderIds) { - cancelledPendingOrders.Add(pendingOrder); - CancelPendingOrder(pendingOrder.Id, OrderCloseReason.CanceledBySystem, "Stop out"); + if (_ordersCache.TryGetOrderById(trailingOrderId, out var trailingOrder) + && trailingOrder.Price.HasValue) + { + if (trailingOrder.TrailingDistance.HasValue) + { + if (Math.Abs(trailingOrder.Price.Value - position.ClosePrice) > + Math.Abs(trailingOrder.TrailingDistance.Value)) + { + var newPrice = position.ClosePrice + trailingOrder.TrailingDistance.Value; + trailingOrder.ChangePrice(newPrice, + _dateService.Now(), + trailingOrder.Originator, + null, + _identityGenerator.GenerateGuid()); //todo in fact price change correlationId must be used + } + } + else + { + trailingOrder.SetTrailingDistance(position.ClosePrice); + } + } } - - var activeOrders = _ordersCache.ActiveOrders.GetOrdersByAccountIds(account.Id); - - var ordersToClose = new List(); - var newAccountUsedMargin = account.GetUsedMargin(); + } - foreach (var order in activeOrders.OrderBy(o => o.GetTotalFpl())) + private void CommitStopOut(MarginTradingAccount account, InstrumentBidAskPair quote) + { + if (account.IsInLiquidation()) { - if (newAccountUsedMargin <= 0 || - account.GetTotalCapital() / newAccountUsedMargin > account.GetMarginCallLevel()) - break; - - ordersToClose.Add(order); - newAccountUsedMargin -= order.GetMarginMaintenance(); + return; } - if (!ordersToClose.Any() && !cancelledPendingOrders.Any()) - return; + var liquidationType = account.GetUsedMargin() == account.GetCurrentlyUsedMargin() + ? LiquidationType.Normal + : LiquidationType.Mco; - _stopoutEventChannel.SendEvent(this, - new StopOutEventArgs(account, ordersToClose.Concat(cancelledPendingOrders).ToArray())); + _cqrsSender.SendCommandToSelf(new StartLiquidationInternalCommand + { + OperationId = _identityGenerator.GenerateGuid(),//TODO: use quote correlationId + AccountId = account.Id, + CreationTime = _dateService.Now(), + QuoteInfo = quote?.ToJson(), + LiquidationType = liquidationType, + OriginatorType = OriginatorType.System, + }); - foreach (var order in ordersToClose) - SetOrderToClosingState(order, OrderCloseReason.StopOut); + _stopOutEventChannel.SendEvent(this, new StopOutEventArgs(account)); } - private void SetOrderToClosingState(Order order, OrderCloseReason reason) + public async Task<(PositionCloseResult, Order)> ClosePositionsAsync(PositionsCloseData closeData, bool specialLiquidationEnabled) { - order.Status = OrderStatus.Closing; - order.StartClosingDate = DateTime.UtcNow; - order.CloseReason = reason; + var me = closeData.MatchingEngine ?? + _meRouter.GetMatchingEngineForClose(closeData.OpenMatchingEngineId); - _ordersCache.ClosingOrders.Add(order); - _ordersCache.ActiveOrders.Remove(order); - } + var initialParameters = await _validateOrderService.GetOrderInitialParameters(closeData.AssetPairId, + closeData.AccountId); - private void NotifyAccountLevelChanged(MarginTradingAccount account, AccountLevel newAccountLevel) - { - switch (newAccountLevel) + var account = _accountsCacheService.Get(closeData.AccountId); + + var positionIds = new List(); + var now = _dateService.Now(); + var volume = 0M; + + var positions = closeData.Positions; + + if (closeData.Modality != OrderModality.Liquidation) { - case AccountLevel.MarginCall: - _marginCallEventChannel.SendEvent(this, new MarginCallEventArgs(account)); - break; + positions = positions.Where(p => p.Status == PositionStatus.Active).ToList(); + } + + foreach (var position in positions) + { + if (position.TryStartClosing(now, PositionCloseReason.Close, closeData.Originator, "") || + closeData.Modality == OrderModality.Liquidation) + { + positionIds.Add(position.Id); + volume += position.Volume; + } } - } - - private Task CloseActiveOrderByMatchingEngineAsync(Order order, IMatchingEngineBase matchingEngine, OrderCloseReason reason, string comment) - { - order.CloseOrderbookId = matchingEngine.Id; - order.StartClosingDate = DateTime.UtcNow; - order.CloseReason = reason; - order.Comment = comment; - matchingEngine.MatchMarketOrderForClose(order, matchedOrders => + if (!positionIds.Any()) { - if (!matchedOrders.Any()) + if (closeData.Positions.Any(p => p.Status == PositionStatus.Closing)) { - order.CloseRejectReasonText = "No orders to match"; - return false; + return (PositionCloseResult.ClosingIsInProgress, null); } - order.MatchedCloseOrders.AddRange(matchedOrders); - - _equivalentPricesService.EnrichClosingOrder(order); + throw new Exception("No active positions to close"); + } + + var order = new Order(initialParameters.Id, + initialParameters.Code, + closeData.AssetPairId, + -volume, + initialParameters.Now, + initialParameters.Now, + null, + account.Id, + account.TradingConditionId, + account.BaseAssetId, + null, + closeData.EquivalentAsset, + OrderFillType.FillOrKill, + $"Close positions: {string.Join(",", positionIds)}. {closeData.Comment}", + account.LegalEntity, + false, + OrderType.Market, + null, + null, + closeData.Originator, + initialParameters.EquivalentPrice, + initialParameters.FxPrice, + initialParameters.FxAssetPairId, + initialParameters.FxToAssetPairDirection, + OrderStatus.Placed, + closeData.AdditionalInfo, + closeData.CorrelationId, + positionIds, + closeData.ExternalProviderId); + + _orderPlacedEventChannel.SendEvent(this, new OrderPlacedEventArgs(order)); - if (!order.GetIsCloseFullfilled()) + order = await ExecuteOrderByMatchingEngineAsync(order, me, true, closeData.Modality); + + if (order.IsExecutionNotStarted) + { + if (specialLiquidationEnabled && order.RejectReason == OrderRejectReason.NoLiquidity) { - order.Status = OrderStatus.Closing; - _ordersCache.ActiveOrders.Remove(order); - _ordersCache.ClosingOrders.Add(order); - _orderClosingEventChannel.SendEvent(this, new OrderClosingEventArgs(order)); + var command = new StartSpecialLiquidationInternalCommand + { + OperationId = Guid.NewGuid().ToString(), + CreationTime = _dateService.Now(), + AccountId = order.AccountId, + PositionIds = order.PositionsToBeClosed.ToArray(), + AdditionalInfo = order.AdditionalInfo, + OriginatorType = order.Originator + }; + + _cqrsSender.SendCommandToSelf(command); + + return (PositionCloseResult.ClosingStarted, null); } else { - order.Status = OrderStatus.Closed; - order.CloseDate = DateTime.UtcNow; - _ordersCache.ActiveOrders.Remove(order); - _orderClosedEventChannel.SendEvent(this, new OrderClosedEventArgs(order)); + foreach (var position in closeData.Positions) + { + if (position.Status == PositionStatus.Closing) + position.CancelClosing(_dateService.Now()); + } + + _log.WriteWarning(nameof(ClosePositionsAsync), order, + $"Order {order.Id} was not executed. Closing of positions canceled"); + + throw new Exception($"Positions were not closed. Reason: {order.RejectReasonText}"); } + } - return true; - }); - - return Task.FromResult(order); + return (PositionCloseResult.Closed, order); } - public Task CloseActiveOrderAsync(string orderId, OrderCloseReason reason, string comment = null) + [ItemNotNull] + public async Task> ClosePositionsGroupAsync(string accountId, + string assetPairId, PositionDirection? direction, OriginatorType originator, string additionalInfo, string correlationId) { - var order = GetActiveOrderForClose(orderId); + if (string.IsNullOrWhiteSpace(accountId)) + { + throw new ArgumentNullException(nameof(accountId), "AccountId must be set."); + } + + var operationId = string.IsNullOrWhiteSpace(correlationId) + ? _identityGenerator.GenerateGuid() + : correlationId; + + if (string.IsNullOrEmpty(assetPairId))//close all + { + return StartLiquidation(accountId,originator, additionalInfo, operationId); + } + + var result = new Dictionary(); + + var positions = _ordersCache.Positions.GetPositionsByInstrumentAndAccount(assetPairId, accountId); + + var positionGroups = positions + .Where(p => direction == null || p.Direction == direction) + .GroupBy(p => (p.AssetPairId, p.AccountId, p.Direction, p + .OpenMatchingEngineId, p.ExternalProviderId, p.EquivalentAsset)) + .Select(gr => new PositionsCloseData( + gr.ToList(), + gr.Key.AccountId, + gr.Key.AssetPairId, + gr.Key.OpenMatchingEngineId, + gr.Key.ExternalProviderId, + originator, + additionalInfo, + operationId, + gr.Key.EquivalentAsset, + string.Empty)); + + foreach (var positionGroup in positionGroups) + { + try + { + var closeResult = await ClosePositionsAsync(positionGroup, true); - var me = _meRouter.GetMatchingEngineForClose(order); + foreach (var position in positionGroup.Positions) + { + result.Add(position.Id, closeResult); + } + } + catch (Exception ex) + { + await _log.WriteWarningAsync(nameof(ClosePositionsAsync), + positionGroup.ToJson(), + $"Failed to close positions {string.Join(",", positionGroup.Positions.Select(p => p.Id))}", + ex); + + foreach (var position in positionGroup.Positions) + { + result.Add(position.Id, (PositionCloseResult.FailedToClose, null)); + } + } + } - return CloseActiveOrderByMatchingEngineAsync(order, me, reason, comment); + return result; } - public Order CancelPendingOrder(string orderId, OrderCloseReason reason, string comment = null) + private Dictionary StartLiquidation(string accountId, + OriginatorType originator, string additionalInfo, string operationId) { - using (_contextFactory.GetWriteSyncContext($"{nameof(TradingEngine)}.{nameof(CancelPendingOrder)}")) + var result = new Dictionary(); + + var command = new StartLiquidationInternalCommand { - var order = _ordersCache.WaitingForExecutionOrders.GetOrderById(orderId); - CancelWaitingForExecutionOrder(order, reason, comment); - return order; - } - } - #endregion + OperationId = operationId, + CreationTime = _dateService.Now(), + AccountId = accountId, + LiquidationType = LiquidationType.Forced, + OriginatorType = originator, + AdditionalInfo = additionalInfo + }; + _cqrsSender.SendCommandToSelf(command); - private Order GetActiveOrderForClose(string orderId) - { - using (_contextFactory.GetReadSyncContext($"{nameof(TradingEngine)}.{nameof(GetActiveOrderForClose)}")) - return _ordersCache.ActiveOrders.GetOrderById(orderId); + var positions = _ordersCache.Positions.GetPositionsByAccountIds(accountId); + + foreach (var position in positions) + { + switch (position.Status) + { + case PositionStatus.Active: + result.Add(position.Id, (PositionCloseResult.ClosingStarted, null)); + break; + case PositionStatus.Closing: + result.Add(position.Id, (PositionCloseResult.ClosingIsInProgress, null)); + break; + case PositionStatus.Closed: + result.Add(position.Id, (PositionCloseResult.Closed, null)); + break; + default: + throw new InvalidOperationException($"Position state {position.Status.ToString()} is not handled"); + } + } + + return result; } - private void CancelWaitingForExecutionOrder(Order order, OrderCloseReason reason, string comment) + public async Task<(PositionCloseResult, Order)[]> LiquidatePositionsUsingSpecialWorkflowAsync(IMatchingEngineBase me, string[] positionIds, string correlationId, string additionalInfo, OriginatorType originator) { - order.Status = OrderStatus.Closed; - order.CloseDate = DateTime.UtcNow; - order.CloseReason = reason; - order.Comment = comment; + var positionsToClose = _ordersCache.Positions.GetAllPositions() + .Where(x => positionIds.Contains(x.Id)).ToList(); - _ordersCache.WaitingForExecutionOrders.Remove(order); + var positionGroups = positionsToClose + .GroupBy(p => (p.AssetPairId, p.AccountId, p.Direction, p + .OpenMatchingEngineId, p.ExternalProviderId, p.EquivalentAsset)) + .Select(gr => new PositionsCloseData( + gr.ToList(), + gr.Key.AccountId, + gr.Key.AssetPairId, + gr.Key.OpenMatchingEngineId, + gr.Key.ExternalProviderId, + originator, + additionalInfo, + correlationId, + gr.Key.EquivalentAsset, + "Special Liquidation", + me, + OrderModality.Liquidation)); + + var failedPositionIds = new List(); + + var closeOrderList = (await Task.WhenAll(positionGroups + .Select(async group => + { + try + { + return await ClosePositionsAsync(group, false); + } + catch (Exception) + { + failedPositionIds.AddRange(group.Positions.Select(p => p.Id)); + return default; + } + }))).Where(x => x != default).ToArray(); + + if (failedPositionIds.Any()) + { + throw new Exception($"Special liquidation #{correlationId} failed to close these positions: {string.Join(", ", failedPositionIds)}"); + } - _orderCancelledEventChannel.SendEvent(this, new OrderCancelledEventArgs(order)); + return closeOrderList; } - public void ChangeOrderLimits(string orderId, decimal? stopLoss, decimal? takeProfit, decimal? expectedOpenPrice) + public Order CancelPendingOrder(string orderId, string additionalInfo, + string correlationId, string comment = null, OrderCancellationReason reason = OrderCancellationReason.None) { - using (_contextFactory.GetWriteSyncContext($"{nameof(TradingEngine)}.{nameof(ChangeOrderLimits)}")) + var order = _ordersCache.GetOrderById(orderId); + + if (order.Status == OrderStatus.Inactive) + { + _ordersCache.Inactive.Remove(order); + } + else if (order.Status == OrderStatus.Active) + { + _ordersCache.Active.Remove(order); + } + else { - var order = _ordersCache.GetOrderById(orderId); + throw new InvalidOperationException($"Order in state {order.Status} can not be cancelled"); + } - if (order.Status != OrderStatus.WaitingForExecution && expectedOpenPrice > 0) - { - return; - } + order.Cancel(_dateService.Now(), additionalInfo, correlationId); - var quote = _quoteCashService.GetQuote(order.Instrument); - var tp = takeProfit == 0 ? null : takeProfit; - var sl = stopLoss == 0 ? null : stopLoss; - var expOpenPrice = expectedOpenPrice == 0 ? null : expectedOpenPrice; + var metadata = new OrderCancelledMetadata { Reason = reason.ToType() }; + _orderCancelledEventChannel.SendEvent(this, new OrderCancelledEventArgs(order, metadata)); - var accountAsset = _accountAssetsCacheService.GetAccountAsset(order.TradingConditionId, - order.AccountAssetId, order.Instrument); + return order; + } - _validateOrderService.ValidateOrderStops(order.GetOrderType(), quote, - accountAsset.DeltaBid, accountAsset.DeltaAsk, tp, sl, expOpenPrice, order.AssetAccuracy); + #endregion - order.TakeProfit = tp.HasValue ? Math.Round(tp.Value, order.AssetAccuracy) : (decimal?)null; - order.StopLoss = sl.HasValue ? Math.Round(sl.Value, order.AssetAccuracy) : (decimal?)null; - order.ExpectedOpenPrice = expOpenPrice.HasValue ? Math.Round(expOpenPrice.Value, order.AssetAccuracy) : (decimal?)null; - _orderLimitsChangesEventChannel.SendEvent(this, new OrderLimitsChangedEventArgs(order)); - } - } - public bool PingLock() + public async Task ChangeOrderAsync(string orderId, decimal price, DateTime? validity, OriginatorType originator, + string additionalInfo, string correlationId, bool? forceOpen = null) { - using (_contextFactory.GetReadSyncContext($"{nameof(TradingEngine)}.{nameof(PingLock)}")) - { - return true; - } - } + var order = _ordersCache.GetOrderById(orderId); - private void ProcessOrdersClosing(string instrument = null) - { - var closingOrders = string.IsNullOrEmpty(instrument) - ? _ordersCache.ClosingOrders.GetAllOrders() - : _ordersCache.ClosingOrders.GetOrdersByInstrument(instrument); + var assetPair = _validateOrderService.GetAssetPairIfAvailableForTrading(order.AssetPairId, order.OrderType, + order.ForceOpen, false); + price = Math.Round(price, assetPair.Accuracy); - if (closingOrders.Count == 0) - return; + _validateOrderService.ValidateOrderPriceChange(order, price); + _validateOrderService.ValidateValidity(validity, order.OrderType); + _validateOrderService.ValidateForceOpenChange(order, forceOpen); - foreach (var order in closingOrders) + if (order.Price != price) { - var me = _meRouter.GetMatchingEngineForClose(order); + var oldPrice = order.Price; + + order.ChangePrice(price, _dateService.Now(), originator, additionalInfo, correlationId, true); - ProcessOrdersClosingByMatchingEngine(order, me); + var metadata = new OrderChangedMetadata + { + UpdatedProperty = OrderChangedProperty.Price, + OldValue = oldPrice.HasValue ? oldPrice.Value.ToString("F5") : string.Empty + }; + + _orderChangedEventChannel.SendEvent(this, new OrderChangedEventArgs(order, metadata)); } - } - - private void ProcessOrdersClosingByMatchingEngine(Order order, IMatchingEngineBase matchingEngine) - { - order.CloseOrderbookId = matchingEngine.Id; - matchingEngine.MatchMarketOrderForClose(order, matchedOrders => + if (order.Validity != validity) { - if (matchedOrders.Count == 0) + var oldValidity = order.Validity; + + order.ChangeValidity(validity, _dateService.Now(), originator, additionalInfo, correlationId); + + var metadata = new OrderChangedMetadata { - //if order did not started to close yet and for any reason we did not close it now - make active - if (!order.MatchedCloseOrders.Any()) - { - order.Status = OrderStatus.Active; - order.CloseRejectReasonText = "No orders to match"; - - _ordersCache.ActiveOrders.Add(order); - _ordersCache.ClosingOrders.Remove(order); - } + UpdatedProperty = OrderChangedProperty.Validity, + OldValue = oldValidity.HasValue ? oldValidity.Value.ToString("g") : "GTC" + }; + + _orderChangedEventChannel.SendEvent(this, new OrderChangedEventArgs(order, metadata)); + } - return false; - } + if (forceOpen.HasValue && forceOpen.Value != order.ForceOpen) + { + var oldForceOpen = order.ForceOpen; + + order.ChangeForceOpen(forceOpen.Value, _dateService.Now(), originator, additionalInfo, correlationId); - MakeOrderClosed(order, matchedOrders); + var metadata = new OrderChangedMetadata + { + UpdatedProperty = OrderChangedProperty.ForceOpen, + OldValue = oldForceOpen.ToString(), + }; - return true; - }); + _orderChangedEventChannel.SendEvent(this, new OrderChangedEventArgs(order, metadata)); + } + + await ExecutePendingOrderIfNeededAsync(order); } - private void MakeOrderClosed(Order order, MatchedOrderCollection matchedOrders) + private async Task ExecutePendingOrderIfNeededAsync(Order order) { - order.MatchedCloseOrders.AddRange(matchedOrders); - - if (order.GetIsCloseFullfilled()) + if (order.Status == OrderStatus.Active && + _quoteCacheService.TryGetQuoteById(order.AssetPairId, out var pair)) { - order.Status = OrderStatus.Closed; - order.CloseDate = DateTime.UtcNow; - _ordersCache.ClosingOrders.Remove(order); - _orderClosedEventChannel.SendEvent(this, new OrderClosedEventArgs(order)); + var price = pair.GetPriceForOrderDirection(order.Direction); + + if (!_assetPairDayOffService.IsDayOff(order.AssetPairId) //!_assetPairDayOffService.ArePendingOrdersDisabled(order.AssetPairId)) + && order.IsSuitablePriceForPendingOrder(price)) + { + _ordersCache.Active.Remove(order); + await ExecutePendingOrder(order); + } } } - int IEventConsumer.ConsumerRank => 100; + int IEventConsumer.ConsumerRank => 101; void IEventConsumer.ConsumeEvent(object sender, BestPriceChangeEventArgs ea) { - ProcessOrdersClosing(ea.BidAskPair.Instrument); - ProcessOrdersActive(ea.BidAskPair.Instrument); - ProcessOrdersWaitingForExecution(ea.BidAskPair.Instrument); + ProcessPositions(ea.BidAskPair); + ProcessOrdersWaitingForExecution(ea.BidAskPair); + } + + void IEventConsumer.ConsumeEvent(object sender, FxBestPriceChangeEventArgs ea) + { + UpdatePositionsFxRates(ea.BidAskPair); } } } diff --git a/src/MarginTrading.Backend.Services/Workflow/AccountsProjection.cs b/src/MarginTrading.Backend.Services/Workflow/AccountsProjection.cs new file mode 100644 index 000000000..e9ade9e8d --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/AccountsProjection.cs @@ -0,0 +1,223 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Common; +using Common.Log; +using JetBrains.Annotations; +using Lykke.Common.Chaos; +using Lykke.Common.Log; +using MarginTrading.AccountsManagement.Contracts.Events; +using MarginTrading.AccountsManagement.Contracts.Models; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Extensions; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Services.Events; +using MarginTrading.Common.Extensions; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Services.Workflow +{ + /// + /// Listens to s and builds a projection inside of the + /// + /// + public class AccountsProjection + { + private readonly IAccountsCacheService _accountsCacheService; + private readonly IEventChannel _accountBalanceChangedEventChannel; + private readonly IConvertService _convertService; + private readonly IAccountUpdateService _accountUpdateService; + private readonly IDateService _dateService; + private readonly IOperationExecutionInfoRepository _operationExecutionInfoRepository; + private readonly IChaosKitty _chaosKitty; + private readonly OrdersCache _ordersCache; + private readonly ILog _log; + + private const string OperationName = "AccountsProjection"; + + public AccountsProjection( + IAccountsCacheService accountsCacheService, + IEventChannel accountBalanceChangedEventChannel, + IConvertService convertService, + IAccountUpdateService accountUpdateService, + IDateService dateService, + IOperationExecutionInfoRepository operationExecutionInfoRepository, + IChaosKitty chaosKitty, + OrdersCache ordersCache, + ILog log) + { + _accountsCacheService = accountsCacheService; + _accountBalanceChangedEventChannel = accountBalanceChangedEventChannel; + _convertService = convertService; + _accountUpdateService = accountUpdateService; + _dateService = dateService; + _operationExecutionInfoRepository = operationExecutionInfoRepository; + _chaosKitty = chaosKitty; + _ordersCache = ordersCache; + _log = log; + } + + /// + /// CQRS projection impl + /// + [UsedImplicitly] + public async Task Handle(AccountChangedEvent e) + { + var executionInfo = await _operationExecutionInfoRepository.GetOrAddAsync( + operationName: OperationName, + operationId: e.OperationId, + factory: () => new OperationExecutionInfo( + operationName: OperationName, + id: e.OperationId, + lastModified: _dateService.Now(), + data: new OperationData { State = OperationState.Initiated } + )); + + if (executionInfo.Data.SwitchState(OperationState.Initiated, OperationState.Finished)) + { + var updatedAccount = Convert(e.Account); + + switch (e.EventType) + { + case AccountChangedEventTypeContract.Created: + _accountsCacheService.TryAddNew(MarginTradingAccount.Create(updatedAccount)); + break; + case AccountChangedEventTypeContract.Updated: + { + var account = _accountsCacheService.TryGet(e.Account.Id); + if (await ValidateAccount(account, e) + && await _accountsCacheService.UpdateAccountChanges(updatedAccount.Id, + updatedAccount.TradingConditionId, updatedAccount.WithdrawTransferLimit, + updatedAccount.IsDisabled, updatedAccount.IsWithdrawalDisabled, e.ChangeTimestamp)) + { + _accountUpdateService.RemoveLiquidationStateIfNeeded(e.Account.Id, + "Trading conditions changed"); + } + break; + } + case AccountChangedEventTypeContract.BalanceUpdated: + { + if (e.BalanceChange != null) + { + var account = _accountsCacheService.TryGet(e.Account.Id); + if (await ValidateAccount(account, e)) + { + switch (e.BalanceChange.ReasonType) + { + case AccountBalanceChangeReasonTypeContract.Withdraw: + await _accountUpdateService.UnfreezeWithdrawalMargin(updatedAccount.Id, + e.BalanceChange.Id); + break; + case AccountBalanceChangeReasonTypeContract.UnrealizedDailyPnL: + if (_ordersCache.Positions.TryGetPositionById(e.BalanceChange.EventSourceId, + out var position)) + { + position.ChargePnL(e.BalanceChange.Id, e.BalanceChange.ChangeAmount); + } + else + { + _log.WriteWarning("AccountChangedEvent Handler", e.ToJson(), + $"Position [{e.BalanceChange.EventSourceId} was not found]"); + } + + break; + case AccountBalanceChangeReasonTypeContract.RealizedPnL: + await _accountUpdateService.UnfreezeUnconfirmedMargin(e.Account.Id, + e.BalanceChange.EventSourceId); + break; + case AccountBalanceChangeReasonTypeContract.Reset: + foreach (var pos in _ordersCache.Positions.GetPositionsByAccountIds( + e.Account.Id)) + { + _ordersCache.Positions.Remove(pos); + } + + foreach (var order in _ordersCache.Active.GetOrdersByAccountIds(e.Account.Id)) + { + _ordersCache.Active.Remove(order); + } + + foreach (var order in _ordersCache.Inactive.GetOrdersByAccountIds(e.Account.Id)) + { + _ordersCache.Inactive.Remove(order); + } + + foreach (var order in _ordersCache.InProgress.GetOrdersByAccountIds( + e.Account.Id)) + { + _ordersCache.InProgress.Remove(order); + } + + var warnings = _accountsCacheService.Reset(e.Account.Id, e.ChangeTimestamp); + if (!string.IsNullOrEmpty(warnings)) + { + await _log.WriteWarningAsync(nameof(AccountChangedEvent), + nameof(AccountBalanceChangeReasonTypeContract.Reset), + warnings); + } + + await _log.WriteInfoAsync(nameof(AccountChangedEvent), + nameof(AccountBalanceChangeReasonTypeContract.Reset), + $"Account {e.Account.Id} was reset."); + break; + } + + await _accountsCacheService.UpdateAccountBalance(updatedAccount.Id, + e.BalanceChange.Balance, e.ChangeTimestamp); + + _accountUpdateService.RemoveLiquidationStateIfNeeded(e.Account.Id, + "Balance updated"); + + _accountBalanceChangedEventChannel.SendEvent(this, + new AccountBalanceChangedEventArgs(updatedAccount.Id)); + } + } + else + { + _log.WriteWarning("AccountChangedEvent Handler", e.ToJson(), "BalanceChange info is empty"); + } + + break; + } + case AccountChangedEventTypeContract.Deleted: + //account deletion from cache is double-handled by CQRS flow + _accountsCacheService.Remove(e.Account.Id); + break; + + default: + await _log.WriteErrorAsync(nameof(AccountsProjection), nameof(AccountChangedEvent), + e.ToJson(), new Exception("AccountChangedEventTypeContract was in incorrect state")); + break; + } + + _chaosKitty.Meow(e.OperationId); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + private async Task ValidateAccount(IMarginTradingAccount account, AccountChangedEvent e) + { + if (account == null) + { + await _log.WriteWarningAsync(nameof(AccountsProjection), e.ToJson(), + $"Account with id {e.Account.Id} was not found"); + return false; + } + + return true; + } + + private MarginTradingAccount Convert(AccountContract accountContract) + { + return _convertService.Convert(accountContract, + o => o.ConfigureMap(MemberList.Source) + .ForSourceMember(x => x.ModificationTimestamp, c => c.Ignore())); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/AssetPairProjection.cs b/src/MarginTrading.Backend.Services/Workflow/AssetPairProjection.cs new file mode 100644 index 000000000..8d27cabf5 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/AssetPairProjection.cs @@ -0,0 +1,123 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Common.Log; +using JetBrains.Annotations; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.MatchingEngines; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Services.AssetPairs; +using MarginTrading.Common.Extensions; +using MarginTrading.SettingsService.Contracts.AssetPair; + +namespace MarginTrading.Backend.Services.Workflow +{ + /// + /// Listens to s and builds a projection inside of the + /// + /// + [UsedImplicitly] + public class AssetPairProjection + { + private readonly ITradingEngine _tradingEngine; + private readonly IAssetPairsCache _assetPairsCache; + private readonly IOrderReader _orderReader; + private readonly IScheduleSettingsCacheService _scheduleSettingsCacheService; + private readonly ILog _log; + + public AssetPairProjection( + ITradingEngine tradingEngine, + IAssetPairsCache assetPairsCache, + IOrderReader orderReader, + IScheduleSettingsCacheService scheduleSettingsCacheService, + ILog log) + { + _tradingEngine = tradingEngine; + _assetPairsCache = assetPairsCache; + _orderReader = orderReader; + _scheduleSettingsCacheService = scheduleSettingsCacheService; + _log = log; + } + + [UsedImplicitly] + public async Task Handle(AssetPairChangedEvent @event) + { + //deduplication is not required, it's ok if an object is updated multiple times + if (@event.AssetPair?.Id == null) + { + await _log.WriteWarningAsync(nameof(AssetPairProjection), nameof(Handle), + "AssetPairChangedEvent contained no asset pair id"); + return; + } + + if (IsDelete(@event)) + { + CloseAllOrders(); + + ValidatePositions(@event.AssetPair.Id); + + _assetPairsCache.Remove(@event.AssetPair.Id); + } + else + { + if (@event.AssetPair.IsDiscontinued) + { + CloseAllOrders(); + } + + _assetPairsCache.AddOrUpdate(new AssetPair( + id: @event.AssetPair.Id, + name: @event.AssetPair.Name, + baseAssetId: @event.AssetPair.BaseAssetId, + quoteAssetId: @event.AssetPair.QuoteAssetId, + accuracy: @event.AssetPair.Accuracy, + legalEntity: @event.AssetPair.LegalEntity, + basePairId: @event.AssetPair.BasePairId, + matchingEngineMode: @event.AssetPair.MatchingEngineMode.ToType(), + stpMultiplierMarkupBid: @event.AssetPair.StpMultiplierMarkupBid, + stpMultiplierMarkupAsk: @event.AssetPair.StpMultiplierMarkupAsk, + isSuspended: @event.AssetPair.IsSuspended, + isFrozen: @event.AssetPair.IsFrozen, + isDiscontinued: @event.AssetPair.IsDiscontinued + )); + } + + await _scheduleSettingsCacheService.UpdateScheduleSettingsAsync(); + + void CloseAllOrders() + { + try + { + foreach (var order in _orderReader.GetPending().Where(x => x.AssetPairId == @event.AssetPair.Id)) + { + _tradingEngine.CancelPendingOrder(order.Id, null,@event.OperationId, + null, OrderCancellationReason.InstrumentInvalidated); + } + } + catch (Exception exception) + { + _log.WriteError(nameof(AssetPairProjection), nameof(CloseAllOrders), exception); + throw; + } + } + + void ValidatePositions(string assetPairId) + { + var positions = _orderReader.GetPositions(assetPairId); + if (positions.Any()) + { + _log.WriteFatalError(nameof(AssetPairProjection), nameof(ValidatePositions), + new Exception($"{positions.Length} positions are opened for [{assetPairId}], first: [{positions.First().Id}].")); + } + } + } + + private static bool IsDelete(AssetPairChangedEvent @event) + { + return @event.AssetPair.BaseAssetId == null || @event.AssetPair.QuoteAssetId == null; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/DeleteAccountsCommandsHandler.cs b/src/MarginTrading.Backend.Services/Workflow/DeleteAccountsCommandsHandler.cs new file mode 100644 index 000000000..6446d60d0 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/DeleteAccountsCommandsHandler.cs @@ -0,0 +1,240 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Common; +using Common.Log; +using JetBrains.Annotations; +using Lykke.Common.Chaos; +using Lykke.Common.Log; +using Lykke.Cqrs; +using MarginTrading.AccountsManagement.Contracts.Commands; +using MarginTrading.AccountsManagement.Contracts.Events; +using MarginTrading.Backend.Contracts.Activities; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Extensions; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Services.Infrastructure; +using MarginTrading.Backend.Services.Workflow.Liquidation.Commands; +using MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Commands; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Services.Workflow +{ + public class DeleteAccountsCommandsHandler + { + private readonly IOrderReader _orderReader; + private readonly IDateService _dateService; + private readonly IAccountsCacheService _accountsCacheService; + private readonly ITradingEngine _tradingEngine; + private readonly IChaosKitty _chaosKitty; + private readonly IOperationExecutionInfoRepository _operationExecutionInfoRepository; + private readonly ILog _log; + + private const string OperationName = "DeleteAccounts"; + + public DeleteAccountsCommandsHandler( + IOrderReader orderReader, + IDateService dateService, + IAccountsCacheService accountsCacheService, + ITradingEngine tradingEngine, + IChaosKitty chaosKitty, + IOperationExecutionInfoRepository operationExecutionInfoRepository, + ILog log) + { + _orderReader = orderReader; + _dateService = dateService; + _accountsCacheService = accountsCacheService; + _tradingEngine = tradingEngine; + _chaosKitty = chaosKitty; + _operationExecutionInfoRepository = operationExecutionInfoRepository; + _log = log; + } + + /// + /// MT Core need to block trades and withdrawals on accounts. + /// + [UsedImplicitly] + private async Task Handle(BlockAccountsForDeletionCommand command, IEventPublisher publisher) + { + var executionInfo = await _operationExecutionInfoRepository.GetOrAddAsync( + operationName: OperationName, + operationId: command.OperationId, + factory: () => new OperationExecutionInfo( + operationName: OperationName, + id: command.OperationId, + lastModified: _dateService.Now(), + data: new DeleteAccountsOperationData + { + State = OperationState.Initiated + } + )); + + //todo think how to remove state saving from commands handler here and for Withdrawal + if (executionInfo.Data.SwitchState(OperationState.Initiated, OperationState.Started)) + { + var failedAccounts = new Dictionary(); + + foreach (var accountId in command.AccountIds) + { + MarginTradingAccount account = null; + try + { + account = _accountsCacheService.Get(accountId); + } + catch (Exception exception) + { + failedAccounts.Add(accountId, exception.Message); + continue; + } + + var positionsCount = _orderReader.GetPositions().Count(x => x.AccountId == accountId); + if (positionsCount != 0) + { + failedAccounts.Add(accountId, $"Account contain {positionsCount} open positions which must be closed before account deletion."); + continue; + } + + var orders = _orderReader.GetPending().Where(x => x.AccountId == accountId).ToList(); + if (orders.Any()) + { + var (failedToCloseOrderId, failReason) = ((string)null, (string)null); + foreach (var order in orders) + { + try + { + _tradingEngine.CancelPendingOrder(order.Id, order.AdditionalInfo,command.OperationId, + $"{nameof(DeleteAccountsCommandsHandler)}: force close all orders.", + OrderCancellationReason.AccountInactivated); + } + catch (Exception exception) + { + failedToCloseOrderId = order.Id; + failReason = exception.Message; + break; + } + } + + if (failedToCloseOrderId != null) + { + failedAccounts.Add(accountId, $"Failed to close order [{failedToCloseOrderId}]: {failReason}."); + continue; + } + } + + if (account.AccountFpl.WithdrawalFrozenMarginData.Any()) + { + await _log.WriteErrorAsync(nameof(DeleteAccountsCommandsHandler), + nameof(BlockAccountsForDeletionCommand), account.ToJson(), + new Exception("While deleting an account it contained some frozen withdrawal data. Account is deleted.")); + } + + if (account.AccountFpl.UnconfirmedMarginData.Any()) + { + await _log.WriteErrorAsync(nameof(DeleteAccountsCommandsHandler), + nameof(BlockAccountsForDeletionCommand), account.ToJson(), + new Exception("While deleting an account it contained some unconfirmed margin data. Account is deleted.")); + } + + if (account.Balance != 0) + { + await _log.WriteErrorAsync(nameof(DeleteAccountsCommandsHandler), + nameof(BlockAccountsForDeletionCommand), account.ToJson(), + new Exception("While deleting an account it's balance on side of TradingCore was non zero. Account is deleted.")); + } + + if (!await UpdateAccount(account, true, + r => failedAccounts.Add(accountId, r), command.Timestamp)) + { + continue; + } + } + + publisher.PublishEvent(new AccountsBlockedForDeletionEvent( + operationId: command.OperationId, + eventTimestamp: _dateService.Now(), + failedAccountIds: failedAccounts + )); + + _chaosKitty.Meow($"{nameof(BlockAccountsForDeletionCommand)}: " + + "Save_OperationExecutionInfo: " + + $"{command.OperationId}"); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + /// + /// Accounts are marked as deleted on side of Accounts Management => + /// remove successfully deleted accounts from cache on side of MT Core + /// & unblock trades and withdrawals for failed + /// + [UsedImplicitly] + private async Task Handle(MtCoreFinishAccountsDeletionCommand command, IEventPublisher publisher) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: OperationName, + id: command.OperationId + ); + + if (executionInfo == null) + return; + + if (executionInfo.Data.SwitchState(OperationState.Started, OperationState.Finished)) + { + foreach (var failedAccountId in command.FailedAccountIds) + { + try + { + var account = _accountsCacheService.Get(failedAccountId); + + await UpdateAccount(account, false, r => { }, command.Timestamp); + } + catch (Exception exception) + { + await _log.WriteErrorAsync(nameof(DeleteAccountsCommandsHandler), + nameof(MtCoreFinishAccountsDeletionCommand), exception); + } + } + + foreach (var accountId in command.AccountIds) + { + _accountsCacheService.Remove(accountId); + } + + publisher.PublishEvent(new MtCoreDeleteAccountsFinishedEvent(command.OperationId, _dateService.Now())); + + _chaosKitty.Meow($"{nameof(MtCoreFinishAccountsDeletionCommand)}: " + + "Save_OperationExecutionInfo: " + + $"{command.OperationId}"); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + private async Task UpdateAccount(IMarginTradingAccount account, bool toDisablementState, + Action failHandler, DateTime commandTime) + { + try + { + await _accountsCacheService.UpdateAccountChanges(account.Id, account.TradingConditionId, + account.WithdrawTransferLimit, toDisablementState, + toDisablementState, commandTime); + } + catch (Exception exception) + { + await _log.WriteErrorAsync(nameof(DeleteAccountsCommandsHandler), + nameof(DeleteAccountsCommandsHandler), exception.Message, exception); + failHandler(exception.Message); + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/EodCommandsHandler.cs b/src/MarginTrading.Backend.Services/Workflow/EodCommandsHandler.cs new file mode 100644 index 000000000..ceb56651a --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/EodCommandsHandler.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; +using BookKeeper.Client.Workflow.Commands; +using BookKeeper.Client.Workflow.Events; +using Common.Log; +using JetBrains.Annotations; +using Lykke.Common.Chaos; +using Lykke.Cqrs; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Extensions; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Core.Workflow; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Services.Workflow +{ + [UsedImplicitly] + public class EodCommandsHandler + { + private readonly ISnapshotService _snapshotService; + private readonly IDateService _dateService; + private readonly ILog _log; + + public EodCommandsHandler(ISnapshotService snapshotService, IDateService dateService, ILog log) + { + _snapshotService = snapshotService; + _dateService = dateService; + _log = log; + } + + [UsedImplicitly] + private async Task Handle(CreateSnapshotCommand command, IEventPublisher publisher) + { + //deduplication is inside _snapshotService.MakeTradingDataSnapshot + try + { + await _snapshotService.MakeTradingDataSnapshot(command.TradingDay, command.OperationId); + + publisher.PublishEvent(new SnapshotCreatedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + }); + } + catch (Exception exception) + { + await _log.WriteErrorAsync(nameof(EodCommandsHandler), nameof(CreateSnapshotCommand), + exception); + + publisher.PublishEvent(new SnapshotCreationFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + FailReason = exception.Message, + }); + } + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/Liquidation/Commands/FailLiquidationInternalCommand.cs b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Commands/FailLiquidationInternalCommand.cs new file mode 100644 index 000000000..16a7fbaab --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Commands/FailLiquidationInternalCommand.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MarginTrading.Backend.Core; +using MessagePack; + +namespace MarginTrading.Backend.Services.Workflow.Liquidation.Commands +{ + [MessagePackObject] + public class FailLiquidationInternalCommand + { + [Key(0)] + public string OperationId { get; set; } + + [Key(1)] + public DateTime CreationTime { get; set; } + + [Key(2)] + public string Reason { get; set; } + + [Key(3)] + public LiquidationType LiquidationType { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/Liquidation/Commands/FinishLiquidationInternalCommand.cs b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Commands/FinishLiquidationInternalCommand.cs new file mode 100644 index 000000000..8dbb5545b --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Commands/FinishLiquidationInternalCommand.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using MarginTrading.Backend.Core; +using MessagePack; + +namespace MarginTrading.Backend.Services.Workflow.Liquidation.Commands +{ + [MessagePackObject] + public class FinishLiquidationInternalCommand + { + [Key(0)] + public string OperationId { get; set; } + + [Key(1)] + public DateTime CreationTime { get; set; } + + [Key(2)] + public string Reason { get; set; } + + [Key(3)] + public LiquidationType LiquidationType { get; set; } + + [Key(4)] + public List ProcessedPositionIds { get; set; } + + [Key(5)] + public List LiquidatedPositionIds { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/Liquidation/Commands/LiquidatePositionsInternalCommand.cs b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Commands/LiquidatePositionsInternalCommand.cs new file mode 100644 index 000000000..805a549a2 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Commands/LiquidatePositionsInternalCommand.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MarginTrading.Backend.Core.Orders; +using MessagePack; + +namespace MarginTrading.Backend.Services.Workflow.Liquidation.Commands +{ + [MessagePackObject] + public class LiquidatePositionsInternalCommand + { + [Key(0)] public string OperationId { get; set; } + + [Key(1)] public DateTime CreationTime { get; set; } + + [Key(2)] public string AssetPairId { get; set; } + + [Key(3)] public string[] PositionIds { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/Liquidation/Commands/ResumeLiquidationInternalCommand.cs b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Commands/ResumeLiquidationInternalCommand.cs new file mode 100644 index 000000000..2079a09ac --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Commands/ResumeLiquidationInternalCommand.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using MessagePack; + +namespace MarginTrading.Backend.Services.Workflow.Liquidation.Commands +{ + [MessagePackObject] + public class ResumeLiquidationInternalCommand + { + [Key(0)] + public string OperationId { get; set; } + + [Key(1)] + public DateTime CreationTime { get; set; } + + [Key(2)] + public string Comment { get; set; } + + [Key(3)] + public bool IsCausedBySpecialLiquidation { get; set; } + + [Key(4)] + public string CausationOperationId { get; set; } + + [Key(5)] + public List PositionsLiquidatedBySpecialLiquidation { get; set; } + + [Key(6)] + public bool ResumeOnlyFailed { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/Liquidation/Commands/StartLiquidationInternalCommand.cs b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Commands/StartLiquidationInternalCommand.cs new file mode 100644 index 000000000..ed18d3a9d --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Commands/StartLiquidationInternalCommand.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Orders; +using MessagePack; + +namespace MarginTrading.Backend.Services.Workflow.Liquidation.Commands +{ + [MessagePackObject] + public class StartLiquidationInternalCommand + { + [Key(0)] + public string OperationId { get; set; } + + [Key(1)] + public DateTime CreationTime { get; set; } + + [Key(2)] + public string AccountId { get; set; } + + [Key(3)] + public string AssetPairId { get; set; } + + [Key(4)] + public PositionDirection? Direction { get; set; } + + [Key(5)] + public string QuoteInfo { get; set; } + + [Key(6)] + public LiquidationType LiquidationType { get; set; } + + [Key(7)] + public OriginatorType OriginatorType { get; set; } + + [Key(8)] + public string AdditionalInfo { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/Liquidation/Events/LiquidationInfo.cs b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Events/LiquidationInfo.cs new file mode 100644 index 000000000..88e398535 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Events/LiquidationInfo.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MessagePack; + +namespace MarginTrading.Backend.Services.Workflow.Liquidation.Events +{ + [MessagePackObject] + public class LiquidationInfo + { + [Key(0)] + public string PositionId {get; set; } + + [Key(1)] + public bool IsLiquidated { get; set; } + + [Key(2)] + public string Comment { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/Liquidation/Events/LiquidationResumedInternalEvent.cs b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Events/LiquidationResumedInternalEvent.cs new file mode 100644 index 000000000..ca636c397 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Events/LiquidationResumedInternalEvent.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using MessagePack; + +namespace MarginTrading.Backend.Services.Workflow.Liquidation.Events +{ + [MessagePackObject] + public class LiquidationResumedInternalEvent + { + [Key(0)] + public string OperationId { get; set; } + + [Key(1)] + public DateTime CreationTime { get; set; } + + [Key(2)] + public bool IsCausedBySpecialLiquidation { get; set; } + + [Key(3)] + public string Comment { get; set; } + + [Key(4)] + public List PositionsLiquidatedBySpecialLiquidation { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/Liquidation/Events/LiquidationStartedInternalEvent.cs b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Events/LiquidationStartedInternalEvent.cs new file mode 100644 index 000000000..4ccd28bd6 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Events/LiquidationStartedInternalEvent.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MessagePack; + +namespace MarginTrading.Backend.Services.Workflow.Liquidation.Events +{ + [MessagePackObject] + public class LiquidationStartedInternalEvent + { + [Key(0)] + public string OperationId { get; set; } + + [Key(1)] + public DateTime CreationTime { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/Liquidation/Events/NotEnoughLiquidityInternalEvent.cs b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Events/NotEnoughLiquidityInternalEvent.cs new file mode 100644 index 000000000..bf20512ff --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Events/NotEnoughLiquidityInternalEvent.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MessagePack; + +namespace MarginTrading.Backend.Services.Workflow.Liquidation.Events +{ + [MessagePackObject] + public class NotEnoughLiquidityInternalEvent + { + [Key(0)] + public string OperationId { get; set; } + + [Key(1)] + public DateTime CreationTime { get; set; } + + [Key(2)] + public string[] PositionIds { get; set; } + + [Key(3)] + public string Details { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/Liquidation/Events/PositionsLiquidationFinishedInternalEvent.cs b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Events/PositionsLiquidationFinishedInternalEvent.cs new file mode 100644 index 000000000..182df59a6 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/Liquidation/Events/PositionsLiquidationFinishedInternalEvent.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using MessagePack; + +namespace MarginTrading.Backend.Services.Workflow.Liquidation.Events +{ + [MessagePackObject] + public class PositionsLiquidationFinishedInternalEvent + { + [Key(0)] + public string OperationId { get; set; } + + [Key(1)] + public DateTime CreationTime { get; set; } + + [Key(2)] + public List LiquidationInfos { get; set; } + + public PositionsLiquidationFinishedInternalEvent() + { + LiquidationInfos = new List(); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/Liquidation/LiquidationCommandsHandler.cs b/src/MarginTrading.Backend.Services/Workflow/Liquidation/LiquidationCommandsHandler.cs new file mode 100644 index 000000000..1185f5ced --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/Liquidation/LiquidationCommandsHandler.cs @@ -0,0 +1,460 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Common; +using Common.Log; +using JetBrains.Annotations; +using Lykke.Common.Chaos; +using Lykke.Cqrs; +using MarginTrading.Backend.Contracts.Positions; +using MarginTrading.Backend.Contracts.Workflow.Liquidation; +using MarginTrading.Backend.Contracts.Workflow.Liquidation.Events; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.MatchingEngines; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Services.Events; +using MarginTrading.Backend.Services.TradingConditions; +using MarginTrading.Backend.Services.Workflow.Liquidation.Commands; +using MarginTrading.Backend.Services.Workflow.Liquidation.Events; +using MarginTrading.Common.Extensions; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Services.Workflow.Liquidation +{ + [UsedImplicitly] + public class LiquidationCommandsHandler + { + private readonly ITradingInstrumentsCacheService _tradingInstrumentsCacheService; + private readonly IAccountsCacheService _accountsCache; + private readonly IDateService _dateService; + private readonly IOperationExecutionInfoRepository _operationExecutionInfoRepository; + private readonly IChaosKitty _chaosKitty; + private readonly IMatchingEngineRouter _matchingEngineRouter; + private readonly ITradingEngine _tradingEngine; + private readonly OrdersCache _ordersCache; + private readonly ILog _log; + private readonly IAccountUpdateService _accountUpdateService; + private readonly IEventChannel _liquidationEndEventChannel; + + public LiquidationCommandsHandler( + ITradingInstrumentsCacheService tradingInstrumentsCacheService, + IAccountsCacheService accountsCache, + IDateService dateService, + IOperationExecutionInfoRepository operationExecutionInfoRepository, + IChaosKitty chaosKitty, + IMatchingEngineRouter matchingEngineRouter, + ITradingEngine tradingEngine, + OrdersCache ordersCache, + ILog log, + IAccountUpdateService accountUpdateService, + IEventChannel liquidationEndEventChannel) + { + _tradingInstrumentsCacheService = tradingInstrumentsCacheService; + _accountsCache = accountsCache; + _dateService = dateService; + _operationExecutionInfoRepository = operationExecutionInfoRepository; + _chaosKitty = chaosKitty; + _matchingEngineRouter = matchingEngineRouter; + _tradingEngine = tradingEngine; + _ordersCache = ordersCache; + _log = log; + _accountUpdateService = accountUpdateService; + _liquidationEndEventChannel = liquidationEndEventChannel; + } + + [UsedImplicitly] + public async Task Handle(StartLiquidationInternalCommand command, + IEventPublisher publisher) + { + #region Private Methods + + void PublishFailedEvent(string reason) + { + publisher.PublishEvent(new LiquidationFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Reason = reason, + LiquidationType = command.LiquidationType.ToType(), + AccountId = command.AccountId, + AssetPairId = command.AssetPairId, + Direction = command.Direction?.ToType(), + }); + + _liquidationEndEventChannel.SendEvent(this, new LiquidationEndEventArgs + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + AccountId = command.AccountId, + LiquidatedPositionIds = new List(), + FailReason = reason, + }); + } + + #endregion + + #region Validations + + if (string.IsNullOrEmpty(command.AccountId)) + { + PublishFailedEvent("AccountId must be specified"); + return; + } + + if (_accountsCache.TryGet(command.AccountId) == null) + { + PublishFailedEvent( "Account does not exist"); + return; + } + + #endregion + + var executionInfo = await _operationExecutionInfoRepository.GetOrAddAsync( + operationName: LiquidationSaga.OperationName, + operationId: command.OperationId, + factory: () => new OperationExecutionInfo( + operationName: LiquidationSaga.OperationName, + id: command.OperationId, + lastModified: _dateService.Now(), + data: new LiquidationOperationData + { + State = LiquidationOperationState.Initiated, + AccountId = command.AccountId, + AssetPairId = command.AssetPairId, + QuoteInfo = command.QuoteInfo, + Direction = command.Direction, + LiquidatedPositionIds = new List(), + ProcessedPositionIds = new List(), + LiquidationType = command.LiquidationType, + OriginatorType = command.OriginatorType, + AdditionalInfo = command.AdditionalInfo, + } + )); + + if (executionInfo.Data.State == LiquidationOperationState.Initiated) + { + if (!_accountsCache.TryStartLiquidation(command.AccountId, command.OperationId, + out var currentOperationId)) + { + if (currentOperationId != command.OperationId) + { + PublishFailedEvent( + $"Liquidation is already in progress. Initiated by operation : {currentOperationId}"); + return; + } + } + + _chaosKitty.Meow( + $"{nameof(StartLiquidationInternalCommand)}:" + + $"Publish_LiquidationStartedInternalEvent:" + + $"{command.OperationId}"); + + publisher.PublishEvent(new LiquidationStartedInternalEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now() + }); + } + } + + [UsedImplicitly] + public async Task Handle(FailLiquidationInternalCommand command, + IEventPublisher publisher) + { + + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: LiquidationSaga.OperationName, + id: command.OperationId); + + if (executionInfo?.Data == null) + { + return; + } + + _accountUpdateService.RemoveLiquidationStateIfNeeded(executionInfo.Data.AccountId, + $"Liquidation [{command.OperationId}] failed ({command.Reason})", command.OperationId, + executionInfo.Data.LiquidationType); + + _chaosKitty.Meow( + $"{nameof(FailLiquidationInternalCommand)}:" + + $"Publish_LiquidationFailedInternalEvent:" + + $"{command.OperationId}"); + + publisher.PublishEvent(new LiquidationFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Reason = command.Reason, + LiquidationType = command.LiquidationType.ToType(), + AccountId = executionInfo.Data.AccountId, + AssetPairId = executionInfo.Data.AssetPairId, + Direction = executionInfo.Data.Direction?.ToType(), + QuoteInfo = executionInfo.Data.QuoteInfo, + ProcessedPositionIds = executionInfo.Data.ProcessedPositionIds, + LiquidatedPositionIds = executionInfo.Data.LiquidatedPositionIds, + OpenPositionsRemainingOnAccount = _ordersCache.Positions.GetPositionsByAccountIds(executionInfo.Data.AccountId).Count, + CurrentTotalCapital = _accountsCache.Get(executionInfo.Data.AccountId).GetTotalCapital(), + }); + + _liquidationEndEventChannel.SendEvent(this, new LiquidationEndEventArgs + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + AccountId = executionInfo.Data.AccountId, + LiquidatedPositionIds = executionInfo.Data.LiquidatedPositionIds, + FailReason = command.Reason, + }); + } + + [UsedImplicitly] + public async Task Handle(FinishLiquidationInternalCommand command, + IEventPublisher publisher) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: LiquidationSaga.OperationName, + id: command.OperationId); + + if (executionInfo?.Data == null) + { + return; + } + + _accountUpdateService.RemoveLiquidationStateIfNeeded(executionInfo.Data.AccountId, + $"Liquidation [{command.OperationId}] finished ({command.Reason})", command.OperationId, + executionInfo.Data.LiquidationType); + + _chaosKitty.Meow( + $"{nameof(FinishLiquidationInternalCommand)}:" + + $"Publish_LiquidationFinishedInternalEvent:" + + $"{command.OperationId}"); + + publisher.PublishEvent(new LiquidationFinishedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + LiquidationType = command.LiquidationType.ToType(), + AccountId = executionInfo.Data.AccountId, + AssetPairId = executionInfo.Data.AssetPairId, + Direction = executionInfo.Data.Direction?.ToType(), + QuoteInfo = executionInfo.Data.QuoteInfo, + ProcessedPositionIds = command.ProcessedPositionIds, + LiquidatedPositionIds = command.LiquidatedPositionIds, + OpenPositionsRemainingOnAccount = _ordersCache.Positions.GetPositionsByAccountIds(executionInfo.Data.AccountId).Count, + CurrentTotalCapital = _accountsCache.Get(executionInfo.Data.AccountId).GetTotalCapital(), + }); + + _liquidationEndEventChannel.SendEvent(this, new LiquidationEndEventArgs + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + AccountId = executionInfo.Data.AccountId, + LiquidatedPositionIds = command.LiquidatedPositionIds, + }); + } + + [UsedImplicitly] + public async Task Handle(LiquidatePositionsInternalCommand command, IEventPublisher publisher) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: LiquidationSaga.OperationName, + id: command.OperationId); + + if (executionInfo?.Data == null) + { + return; + } + + var positions = _ordersCache.Positions + .GetPositionsByAccountIds(executionInfo.Data.AccountId) + .Where(p => command.PositionIds.Contains(p.Id)) + .ToArray(); + + if (!positions.Any()) + { + publisher.PublishEvent(new PositionsLiquidationFinishedInternalEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + LiquidationInfos = command.PositionIds.Select(p => + new LiquidationInfo + { + PositionId = p, + IsLiquidated = false, + Comment = "Opened position was not found" + }).ToList() + }); + return; + } + + if (!CheckIfNetVolumeCanBeLiquidated(executionInfo.Data.AccountId, command.AssetPairId, positions, + out var details)) + { + publisher.PublishEvent(new NotEnoughLiquidityInternalEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + PositionIds = command.PositionIds, + Details = details + }); + return; + } + + var liquidationInfos = new List(); + + var comment = string.Empty; + + switch (executionInfo.Data.LiquidationType) + { + case LiquidationType.Mco: + comment = "MCO liquidation"; + break; + + case LiquidationType.Normal: + comment = "Liquidation"; + break; + + case LiquidationType.Forced: + comment = "Close positions group"; + break; + } + + var positionGroups = positions + .GroupBy(p => (p.AssetPairId, p.AccountId, p.Direction, p + .OpenMatchingEngineId, p.ExternalProviderId, p.EquivalentAsset)) + .Select(gr => new PositionsCloseData( + gr.ToList(), + gr.Key.AccountId, + gr.Key.AssetPairId, + gr.Key.OpenMatchingEngineId, + gr.Key.ExternalProviderId, + executionInfo.Data.OriginatorType, + executionInfo.Data.AdditionalInfo, + command.OperationId, + gr.Key.EquivalentAsset, + comment)); + + foreach (var positionGroup in positionGroups) + { + try + { + var (result, order) = await _tradingEngine.ClosePositionsAsync(positionGroup, false); + + foreach (var position in positionGroup.Positions) + { + liquidationInfos.Add(new LiquidationInfo + { + PositionId = position.Id, + IsLiquidated = true, + Comment = order != null ? $"Order: {order.Id}" : result.ToString() + }); + } + } + catch (Exception ex) + { + await _log.WriteWarningAsync(nameof(LiquidationCommandsHandler), + nameof(LiquidatePositionsInternalCommand), + $"Failed to close positions {string.Join(",", positionGroup.Positions.Select(p => p.Id))} on liquidation operation #{command.OperationId}", + ex); + + foreach (var position in positionGroup.Positions) + { + liquidationInfos.Add(new LiquidationInfo + { + PositionId = position.Id, + IsLiquidated = false, + Comment = $"Close position failed: {ex.Message}" + }); + } + } + } + + publisher.PublishEvent(new PositionsLiquidationFinishedInternalEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + LiquidationInfos = liquidationInfos + }); + } + + [UsedImplicitly] + public async Task Handle(ResumeLiquidationInternalCommand command, + IEventPublisher publisher) + { + + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: LiquidationSaga.OperationName, + id: command.OperationId); + + if (executionInfo?.Data == null) + { + await _log.WriteWarningAsync(nameof(LiquidationCommandsHandler), + nameof(ResumeLiquidationInternalCommand), + $"Unable to resume liquidation. Execution info was not found. Command: {command.ToJson()}"); + return; + } + + if ((!command.IsCausedBySpecialLiquidation && + (!command.ResumeOnlyFailed || executionInfo.Data.State == LiquidationOperationState.Failed)) || + executionInfo.Data.State == LiquidationOperationState.SpecialLiquidationStarted) + { + publisher.PublishEvent(new LiquidationResumedInternalEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Comment = command.Comment, + IsCausedBySpecialLiquidation = command.IsCausedBySpecialLiquidation, + PositionsLiquidatedBySpecialLiquidation = command.PositionsLiquidatedBySpecialLiquidation + }); + } + else + { + await _log.WriteWarningAsync(nameof(LiquidationCommandsHandler), + nameof(ResumeLiquidationInternalCommand), + $"Unable to resume liquidation in state {executionInfo.Data.State}. Command: {command.ToJson()}"); + } + } + + + #region Private methods + + private bool CheckIfNetVolumeCanBeLiquidated(string accountId, string assetPairId, Position[] positions, + out string details) + { + foreach (var positionsGroup in positions.GroupBy(p => p.Direction)) + { + var netPositionVolume = positionsGroup.Sum(p => p.Volume); + + var account = _accountsCache.Get(accountId); + var tradingInstrument = _tradingInstrumentsCacheService.GetTradingInstrument(account.TradingConditionId, + assetPairId); + + //TODO: discuss and handle situation with different MEs for different positions + //at the current moment all positions has the same asset pair + //and every asset pair can be processed only by one ME + var anyPosition = positionsGroup.First(); + var me = _matchingEngineRouter.GetMatchingEngineForClose(anyPosition.OpenMatchingEngineId); + //the same for externalProvider.. + var externalProvider = anyPosition.ExternalProviderId; + + if (me.GetPriceForClose(assetPairId, netPositionVolume, externalProvider) == null) + { + details = $"Not enough depth of orderbook. Net volume : {netPositionVolume}."; + return false; + } + } + + details = string.Empty; + return true; + } + + #endregion + + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/Liquidation/LiquidationSaga.cs b/src/MarginTrading.Backend.Services/Workflow/Liquidation/LiquidationSaga.cs new file mode 100644 index 000000000..3fa3f187b --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/Liquidation/LiquidationSaga.cs @@ -0,0 +1,348 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Common; +using Common.Log; +using JetBrains.Annotations; +using Lykke.Common.Chaos; +using Lykke.Cqrs; +using MarginTrading.Backend.Contracts.Workflow.Liquidation.Events; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Extensions; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Services.AssetPairs; +using MarginTrading.Backend.Services.Workflow.Liquidation.Commands; +using MarginTrading.Backend.Services.Workflow.Liquidation.Events; +using MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Commands; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Services.Workflow.Liquidation +{ + public class LiquidationSaga + { + private readonly IDateService _dateService; + private readonly IChaosKitty _chaosKitty; + private readonly IOperationExecutionInfoRepository _operationExecutionInfoRepository; + private readonly OrdersCache _ordersCache; + private readonly CqrsContextNamesSettings _cqrsContextNamesSettings; + private readonly ILog _log; + private readonly IAccountsCacheService _accountsCacheService; + private readonly IAssetPairDayOffService _assetPairDayOffService; + + public const string OperationName = "Liquidation"; + + public LiquidationSaga( + IDateService dateService, + IChaosKitty chaosKitty, + IOperationExecutionInfoRepository operationExecutionInfoRepository, + OrdersCache ordersCache, + CqrsContextNamesSettings cqrsContextNamesSettings, + ILog log, + IAccountsCacheService accountsCacheService, + IAssetPairDayOffService assetPairDayOffService) + { + _dateService = dateService; + _chaosKitty = chaosKitty; + _operationExecutionInfoRepository = operationExecutionInfoRepository; + _ordersCache = ordersCache; + _cqrsContextNamesSettings = cqrsContextNamesSettings; + _log = log; + _accountsCacheService = accountsCacheService; + _assetPairDayOffService = assetPairDayOffService; + } + + [UsedImplicitly] + public async Task Handle(LiquidationStartedInternalEvent e, ICommandSender sender) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: OperationName, + id: e.OperationId); + + if (executionInfo?.Data == null) + { + return; + } + + if (executionInfo.Data.SwitchState(LiquidationOperationState.Initiated, + LiquidationOperationState.Started)) + { + + LiquidatePositionsIfAnyAvailable(e.OperationId, executionInfo.Data, sender); + + _chaosKitty.Meow( + $"{nameof(LiquidationStartedInternalEvent)}:" + + $"Save_OperationExecutionInfo:" + + $"{e.OperationId}"); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + [UsedImplicitly] + public async Task Handle(LiquidationFailedEvent e, ICommandSender sender) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: OperationName, + id: e.OperationId); + + if (executionInfo?.Data == null) + { + return; + } + + if (executionInfo.Data.State == LiquidationOperationState.Finished) + { + await _log.WriteWarningAsync(nameof(LiquidationSaga), nameof(LiquidationFailedEvent), + e.ToJson(), $"Unable to set Failed state. Liquidation {e.OperationId} is already finished"); + return; + } + + if (executionInfo.Data.SwitchState(executionInfo.Data.State, LiquidationOperationState.Failed)) + { + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + [UsedImplicitly] + public async Task Handle(LiquidationFinishedEvent e, ICommandSender sender) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: OperationName, + id: e.OperationId); + + if (executionInfo?.Data == null) + { + return; + } + + if (executionInfo.Data.SwitchState(LiquidationOperationState.Started, + LiquidationOperationState.Finished)) + { + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + [UsedImplicitly] + public async Task Handle(PositionsLiquidationFinishedInternalEvent e, ICommandSender sender) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: OperationName, + id: e.OperationId); + + if (executionInfo?.Data == null) + { + return; + } + + if (executionInfo.Data.SwitchState(LiquidationOperationState.Started, + LiquidationOperationState.Started)) + { + executionInfo.Data.ProcessedPositionIds.AddRange(e.LiquidationInfos.Select(i => i.PositionId)); + executionInfo.Data.LiquidatedPositionIds.AddRange(e.LiquidationInfos.Where(i => i.IsLiquidated) + .Select(i => i.PositionId)); + + ContinueOrFinishLiquidation(e.OperationId, executionInfo.Data, sender); + + _chaosKitty.Meow( + $"{nameof(PositionsLiquidationFinishedInternalEvent)}:" + + $"Save_OperationExecutionInfo:" + + $"{e.OperationId}"); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + [UsedImplicitly] + public async Task Handle(NotEnoughLiquidityInternalEvent e, ICommandSender sender) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: OperationName, + id: e.OperationId); + + if (executionInfo?.Data == null) + { + return; + } + + if (executionInfo.Data.SwitchState(LiquidationOperationState.Started, + LiquidationOperationState.SpecialLiquidationStarted)) + { + sender.SendCommand(new StartSpecialLiquidationInternalCommand + { + OperationId = Guid.NewGuid().ToString(), + CreationTime = _dateService.Now(), + AccountId = executionInfo.Data.AccountId, + PositionIds = e.PositionIds, + CausationOperationId = e.OperationId, + AdditionalInfo = executionInfo.Data.AdditionalInfo, + OriginatorType = executionInfo.Data.OriginatorType + }, _cqrsContextNamesSettings.TradingEngine); + + _chaosKitty.Meow( + $"{nameof(PositionsLiquidationFinishedInternalEvent)}:" + + $"Save_OperationExecutionInfo:" + + $"{e.OperationId}"); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + [UsedImplicitly] + public async Task Handle(LiquidationResumedInternalEvent e, ICommandSender sender) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: OperationName, + id: e.OperationId); + + if (executionInfo?.Data == null) + { + return; + } + + var validState = e.IsCausedBySpecialLiquidation + ? LiquidationOperationState.SpecialLiquidationStarted + : executionInfo.Data.State; + + if (executionInfo.Data.SwitchState(validState, LiquidationOperationState.Started)) + { + //if we are trying to resume liquidation, let's clean up processed positions to retry + if (!e.IsCausedBySpecialLiquidation) + { + executionInfo.Data.ProcessedPositionIds = executionInfo.Data.LiquidatedPositionIds; + } + else if (e.PositionsLiquidatedBySpecialLiquidation != null && e + .PositionsLiquidatedBySpecialLiquidation.Any()) + { + executionInfo.Data.LiquidatedPositionIds.AddRange(e.PositionsLiquidatedBySpecialLiquidation); + } + + ContinueOrFinishLiquidation(e.OperationId, executionInfo.Data, sender); + + _chaosKitty.Meow( + $"{nameof(PositionsLiquidationFinishedInternalEvent)}:" + + $"Save_OperationExecutionInfo:" + + $"{e.OperationId}"); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + #region Private methods + + private (string AssetPairId, string[] Positions)? GetLiquidationData( + LiquidationOperationData data) + { + var positionsOnAccount = _ordersCache.Positions.GetPositionsByAccountIds(data.AccountId); + + //group positions and take only not processed, filtered and with open market + var positionGroups = positionsOnAccount + .Where(p => !data.ProcessedPositionIds.Contains(p.Id) && + (string.IsNullOrEmpty(data.AssetPairId) || p.AssetPairId == data.AssetPairId) && + (data.Direction == null || p.Direction == data.Direction)) + .GroupBy(p => p.AssetPairId) + .Where(gr => !_assetPairDayOffService.IsDayOff(gr.Key)) + .ToArray(); + + IGrouping targetPositions = null; + + //take positions from group with max margin used or max initially used margin + targetPositions = positionGroups + .OrderByDescending(gr => gr.Sum(p => Math.Max(p.GetMarginMaintenance(), p.GetInitialMargin()))) + .FirstOrDefault(); + + if (targetPositions == null) + return null; + + return (targetPositions.Key, targetPositions.Select(p => p.Id).ToArray()); + } + + private void LiquidatePositionsIfAnyAvailable(string operationId, + LiquidationOperationData data, ICommandSender sender) + { + var liquidationData = GetLiquidationData(data); + + if (!liquidationData.HasValue || !liquidationData.Value.Positions.Any()) + { + sender.SendCommand(new FailLiquidationInternalCommand + { + OperationId = operationId, + CreationTime = _dateService.Now(), + Reason = "Nothing to liquidate", + LiquidationType = data.LiquidationType, + }, _cqrsContextNamesSettings.TradingEngine); + } + else + { + sender.SendCommand(new LiquidatePositionsInternalCommand + { + OperationId = operationId, + CreationTime = _dateService.Now(), + PositionIds = liquidationData.Value.Positions, + AssetPairId = liquidationData.Value.AssetPairId, + }, _cqrsContextNamesSettings.TradingEngine); + } + } + + private void ContinueOrFinishLiquidation(string operationId, LiquidationOperationData data, ICommandSender sender) + { + void FinishWithReason(string reason) => sender.SendCommand(new FinishLiquidationInternalCommand + { + OperationId = operationId, + CreationTime = _dateService.Now(), + Reason = reason, + LiquidationType = data.LiquidationType, + ProcessedPositionIds = data.ProcessedPositionIds, + LiquidatedPositionIds = data.LiquidatedPositionIds, + }, _cqrsContextNamesSettings.TradingEngine); + + var account = _accountsCacheService.TryGet(data.AccountId); + + if (account == null) + { + sender.SendCommand(new FailLiquidationInternalCommand + { + OperationId = operationId, + CreationTime = _dateService.Now(), + Reason = "Account does not exist", + LiquidationType = data.LiquidationType, + }, _cqrsContextNamesSettings.TradingEngine); + return; + } + + var accountLevel = account.GetAccountLevel(); + + if (data.LiquidationType == LiquidationType.Forced) + { + if (!_ordersCache.Positions.GetPositionsByAccountIds(data.AccountId) + .Any(x => (string.IsNullOrWhiteSpace(data.AssetPairId) || x.AssetPairId == data.AssetPairId) + && (data.Direction == null || x.Direction == data.Direction))) + { + FinishWithReason("All positions are closed"); + } + else + { + LiquidatePositionsIfAnyAvailable(operationId, data, sender); + } + + return; + } + + if (accountLevel < AccountLevel.StopOut) + { + FinishWithReason($"Account margin level is {accountLevel}"); + } + else + { + LiquidatePositionsIfAnyAvailable(operationId, data, sender); + } + } + + #endregion + + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Commands/ExecuteSpecialLiquidationOrdersInternalCommand.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Commands/ExecuteSpecialLiquidationOrdersInternalCommand.cs new file mode 100644 index 000000000..6c11802e5 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Commands/ExecuteSpecialLiquidationOrdersInternalCommand.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MessagePack; + +namespace MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Commands +{ + [MessagePackObject] + public class ExecuteSpecialLiquidationOrdersInternalCommand + { + [Key(0)] + public string OperationId { get; set; } + + [Key(1)] + public DateTime CreationTime { get; set; } + + [Key(3)] + public string Instrument { get; set; } + + [Key(4)] + public decimal Volume { get; set; } + + [Key(5)] + public decimal Price { get; set; } + + [Key(6)] + public string MarketMakerId { get; set; } + + [Key(7)] + public string ExternalOrderId { get; set; } + + [Key(8)] + public DateTime ExternalExecutionTime { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Commands/FailSpecialLiquidationInternalCommand.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Commands/FailSpecialLiquidationInternalCommand.cs new file mode 100644 index 000000000..ce24515ba --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Commands/FailSpecialLiquidationInternalCommand.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MessagePack; + +namespace MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Commands +{ + [MessagePackObject] + public class FailSpecialLiquidationInternalCommand + { + [Key(0)] + public string OperationId { get; set; } + + [Key(1)] + public DateTime CreationTime { get; set; } + + [Key(2)] + public string Reason { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Commands/GetPriceForSpecialLiquidationTimeoutInternalCommand.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Commands/GetPriceForSpecialLiquidationTimeoutInternalCommand.cs new file mode 100644 index 000000000..32a41e54a --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Commands/GetPriceForSpecialLiquidationTimeoutInternalCommand.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MessagePack; + +namespace MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Commands +{ + [MessagePackObject] + public class GetPriceForSpecialLiquidationTimeoutInternalCommand + { + [Key(0)] + public string OperationId { get; set; } + + [Key(1)] + public DateTime CreationTime { get; set; } + + [Key(2)] + public int TimeoutSeconds { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Commands/StartSpecialLiquidationInternalCommand.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Commands/StartSpecialLiquidationInternalCommand.cs new file mode 100644 index 000000000..ccf7f89ca --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Commands/StartSpecialLiquidationInternalCommand.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using JetBrains.Annotations; +using MarginTrading.Backend.Core.Orders; +using MessagePack; + +namespace MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Commands +{ + [MessagePackObject] + public class StartSpecialLiquidationInternalCommand + { + [Key(0)] + public string OperationId { get; set; } + + [Key(1)] + public DateTime CreationTime { get; set; } + + [Key(2)] + public string[] PositionIds { get; set; } + + [CanBeNull] + [Key(3)] + public string AccountId { get; set; } + + [CanBeNull] + [Key(4)] + public string CausationOperationId { get; set; } + + [Key(5)] + public string AdditionalInfo { get; set; } + + [Key(6)] + public OriginatorType OriginatorType { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Events/SpecialLiquidationStartedInternalEvent.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Events/SpecialLiquidationStartedInternalEvent.cs new file mode 100644 index 000000000..f9f400ac2 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Events/SpecialLiquidationStartedInternalEvent.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using JetBrains.Annotations; +using MessagePack; + +namespace MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Events +{ + [MessagePackObject] + public class SpecialLiquidationStartedInternalEvent + { + [Key(0)] + public string OperationId { get; set; } + + [Key(1)] + public DateTime CreationTime { get; set; } + + [Key(2)] + public string Instrument { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationCommandsHandler.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationCommandsHandler.cs new file mode 100644 index 000000000..e737fec63 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationCommandsHandler.cs @@ -0,0 +1,509 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Common; +using Common.Log; +using JetBrains.Annotations; +using Lykke.Common.Chaos; +using Lykke.Cqrs; +using MarginTrading.Backend.Contracts.ExchangeConnector; +using MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Commands; +using MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Events; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Extensions; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Services.AssetPairs; +using MarginTrading.Backend.Services.MatchingEngines; +using MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Commands; +using MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Events; +using MarginTrading.Common.Extensions; +using MarginTrading.Common.Services; +using OrderType = MarginTrading.Backend.Core.Orders.OrderType; + +namespace MarginTrading.Backend.Services.Workflow.SpecialLiquidation +{ + [UsedImplicitly] + public class SpecialLiquidationCommandsHandler + { + private readonly ITradingEngine _tradingEngine; + private readonly IDateService _dateService; + private readonly IOrderReader _orderReader; + private readonly IChaosKitty _chaosKitty; + private readonly IOperationExecutionInfoRepository _operationExecutionInfoRepository; + private readonly ILog _log; + private readonly MarginTradingSettings _marginTradingSettings; + private readonly IAssetPairsCache _assetPairsCache; + private readonly IAssetPairDayOffService _assetPairDayOffService; + private readonly IExchangeConnectorClient _exchangeConnectorClient; + private readonly IIdentityGenerator _identityGenerator; + private readonly IAccountsCacheService _accountsCacheService; + + public SpecialLiquidationCommandsHandler( + ITradingEngine tradingEngine, + IDateService dateService, + IOrderReader orderReader, + IChaosKitty chaosKitty, + IOperationExecutionInfoRepository operationExecutionInfoRepository, + ILog log, + MarginTradingSettings marginTradingSettings, + IAssetPairsCache assetPairsCache, + IAssetPairDayOffService assetPairDayOffService, + IExchangeConnectorClient exchangeConnectorClient, + IIdentityGenerator identityGenerator, + IAccountsCacheService accountsCacheService) + { + _tradingEngine = tradingEngine; + _dateService = dateService; + _orderReader = orderReader; + _chaosKitty = chaosKitty; + _operationExecutionInfoRepository = operationExecutionInfoRepository; + _log = log; + _marginTradingSettings = marginTradingSettings; + _assetPairsCache = assetPairsCache; + _assetPairDayOffService = assetPairDayOffService; + _exchangeConnectorClient = exchangeConnectorClient; + _identityGenerator = identityGenerator; + _accountsCacheService = accountsCacheService; + } + + [UsedImplicitly] + private async Task Handle(StartSpecialLiquidationInternalCommand command, IEventPublisher publisher) + { + if (!_marginTradingSettings.SpecialLiquidation.Enabled) + { + publisher.PublishEvent(new SpecialLiquidationFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Reason = "Special liquidation is disabled in settings", + }); + + return; + } + + //validate the list of positions contain only the same instrument + var positions = _orderReader.GetPositions().Where(x => command.PositionIds.Contains(x.Id)).ToList(); + + if (!string.IsNullOrEmpty(command.AccountId)) + { + if (_accountsCacheService.TryGet(command.AccountId) == null) + { + publisher.PublishEvent(new SpecialLiquidationFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Reason = $"Account {command.AccountId} does not exist", + }); + return; + } + + positions = positions.Where(x => x.AccountId == command.AccountId).ToList(); + } + + if (positions.Select(x => x.AssetPairId).Distinct().Count() > 1) + { + publisher.PublishEvent(new SpecialLiquidationFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Reason = "The list of positions is of different instruments", + }); + + return; + } + + if (!positions.Any()) + { + publisher.PublishEvent(new SpecialLiquidationFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Reason = "No positions to liquidate", + }); + + return; + } + + if (!TryGetExchangeNameFromPositions(positions, out var externalProviderId)) + { + publisher.PublishEvent(new SpecialLiquidationFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Reason = "All requested positions must be open on the same external exchange", + }); + return; + } + + var assetPairId = positions.First().AssetPairId; + if (_assetPairsCache.GetAssetPairById(assetPairId).IsDiscontinued) + { + publisher.PublishEvent(new SpecialLiquidationFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Reason = $"Asset pair {assetPairId} is discontinued", + }); + + return; + } + + var executionInfo = await _operationExecutionInfoRepository.GetOrAddAsync( + operationName: SpecialLiquidationSaga.OperationName, + operationId: command.OperationId, + factory: () => new OperationExecutionInfo( + operationName: SpecialLiquidationSaga.OperationName, + id: command.OperationId, + lastModified: _dateService.Now(), + data: new SpecialLiquidationOperationData + { + State = SpecialLiquidationOperationState.Initiated, + Instrument = assetPairId, + PositionIds = positions.Select(x => x.Id).ToList(), + ExternalProviderId = externalProviderId, + AccountId = command.AccountId, + CausationOperationId = command.CausationOperationId, + AdditionalInfo = command.AdditionalInfo, + OriginatorType = command.OriginatorType + } + )); + + if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.Initiated, SpecialLiquidationOperationState.Started)) + { + publisher.PublishEvent(new SpecialLiquidationStartedInternalEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Instrument = positions.FirstOrDefault()?.AssetPairId, + }); + + _chaosKitty.Meow(command.OperationId); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + [UsedImplicitly] + private async Task Handle(StartSpecialLiquidationCommand command, IEventPublisher publisher) + { + if (!_marginTradingSettings.SpecialLiquidation.Enabled) + { + publisher.PublishEvent(new SpecialLiquidationFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Reason = "Special liquidation is disabled in settings", + }); + + return; + } + + //Validate that market is closed for instrument .. only in ExchangeConnector == Real mode + if (_marginTradingSettings.ExchangeConnector == ExchangeConnectorType.RealExchangeConnector + && !_assetPairDayOffService.IsDayOff(command.Instrument)) + { + publisher.PublishEvent(new SpecialLiquidationFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Reason = $"Asset pair {command.Instrument} market must be disabled to start Special Liquidation", + }); + + return; + } + + if (_assetPairsCache.GetAssetPairById(command.Instrument).IsDiscontinued) + { + publisher.PublishEvent(new SpecialLiquidationFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Reason = $"Asset pair {command.Instrument} is discontinued", + }); + + return; + } + + var openedPositions = _orderReader.GetPositions().Where(x => x.AssetPairId == command.Instrument).ToList(); + + if (!openedPositions.Any()) + { + publisher.PublishEvent(new SpecialLiquidationFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Reason = "No positions to liquidate", + }); + return; + } + + if (!TryGetExchangeNameFromPositions(openedPositions, out var externalProviderId)) + { + publisher.PublishEvent(new SpecialLiquidationFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Reason = "All requested positions must be open on the same external exchange", + }); + return; + } + + var executionInfo = await _operationExecutionInfoRepository.GetOrAddAsync( + operationName: SpecialLiquidationSaga.OperationName, + operationId: command.OperationId, + factory: () => new OperationExecutionInfo( + operationName: SpecialLiquidationSaga.OperationName, + id: command.OperationId, + lastModified: _dateService.Now(), + data: new SpecialLiquidationOperationData + { + State = SpecialLiquidationOperationState.Initiated, + Instrument = command.Instrument, + PositionIds = openedPositions.Select(x => x.Id).ToList(), + ExternalProviderId = externalProviderId, + OriginatorType = OriginatorType.System, + AdditionalInfo = LykkeConstants.LiquidationByCaAdditionalInfo, + } + )); + + if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.Initiated, SpecialLiquidationOperationState.Started)) + { + publisher.PublishEvent(new SpecialLiquidationStartedInternalEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Instrument = command.Instrument, + }); + + _chaosKitty.Meow(command.OperationId); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + [UsedImplicitly] + private async Task Handle(GetPriceForSpecialLiquidationTimeoutInternalCommand command, + IEventPublisher publisher) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: SpecialLiquidationSaga.OperationName, + id: command.OperationId); + + if (executionInfo?.Data != null) + { + if (executionInfo.Data.State > SpecialLiquidationOperationState.PriceRequested) + { + return CommandHandlingResult.Ok(); + } + + if (_dateService.Now() >= command.CreationTime.AddSeconds(command.TimeoutSeconds)) + { + if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.PriceRequested, + SpecialLiquidationOperationState.Failed)) + { + publisher.PublishEvent(new SpecialLiquidationFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Reason = $"Timeout of {command.TimeoutSeconds} seconds from {command.CreationTime:s}", + }); + + _chaosKitty.Meow(command.OperationId); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + + return CommandHandlingResult.Ok(); + } + } + + return CommandHandlingResult.Fail(_marginTradingSettings.SpecialLiquidation.RetryTimeout); + } + + /// + /// Special handler sends API request to close positions + /// + [UsedImplicitly] + private async Task Handle(ExecuteSpecialLiquidationOrderCommand command, IEventPublisher publisher) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: SpecialLiquidationSaga.OperationName, + id: command.OperationId); + + if (executionInfo?.Data == null) + { + return; + } + + if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.PriceReceived, + SpecialLiquidationOperationState.ExternalOrderExecuted)) + { + if (command.Volume == 0) + { + publisher.PublishEvent(new SpecialLiquidationOrderExecutedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + MarketMakerId = "ZeroNetVolume", + ExecutionTime = _dateService.Now(), + OrderId = _identityGenerator.GenerateGuid(), + }); + } + else + { + var operationInfo = new TradeOperationInfo + { + OperationId = executionInfo.Id, + RequestNumber = executionInfo.Data.RequestNumber + }; + + var order = new OrderModel( + tradeType: command.Volume > 0 ? TradeType.Buy : TradeType.Sell, + orderType: OrderType.Market.ToType(), + timeInForce: TimeInForce.FillOrKill, + volume: (double) Math.Abs(command.Volume), + dateTime: _dateService.Now(), + exchangeName: operationInfo.ToJson(), //hack, but ExchangeName is not used and we need this info + // TODO: create a separate field and remove hack (?) + instrument: command.Instrument, + price: (double?) command.Price, + orderId: _identityGenerator.GenerateAlphanumericId(), + modality: TradeRequestModality.Liquidation); + + try + { + var executionResult = await _exchangeConnectorClient.ExecuteOrder(order); + + if (!executionResult.Success) + { + throw new Exception( + $"External order was not executed. Status: {executionResult.ExecutionStatus}. " + + $"Failure: {executionResult.FailureType}"); + } + + publisher.PublishEvent(new SpecialLiquidationOrderExecutedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + MarketMakerId = executionInfo.Data.ExternalProviderId, + ExecutionTime = executionResult.Time, + OrderId = executionResult.ExchangeOrderId, + }); + } + catch (Exception exception) + { + publisher.PublishEvent(new SpecialLiquidationOrderExecutionFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Reason = exception.Message + }); + await _log.WriteWarningAsync(nameof(SpecialLiquidationCommandsHandler), + nameof(ExecuteSpecialLiquidationOrderCommand), + $"Failed to execute the order: {order.ToJson()}", + exception); + } + } + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + [UsedImplicitly] + private async Task Handle(ExecuteSpecialLiquidationOrdersInternalCommand command, IEventPublisher publisher) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: SpecialLiquidationSaga.OperationName, + id: command.OperationId); + + if (executionInfo?.Data == null) + { + return; + } + + if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.InternalOrderExecutionStarted, + SpecialLiquidationOperationState.InternalOrdersExecuted)) + { + try + { + //close positions with the quotes from gavel + await _tradingEngine.LiquidatePositionsUsingSpecialWorkflowAsync( + me: new SpecialLiquidationMatchingEngine(command.Price, command.MarketMakerId, + command.ExternalOrderId, command.ExternalExecutionTime), + positionIds: executionInfo.Data.PositionIds.ToArray(), + correlationId: command.OperationId, + executionInfo.Data.AdditionalInfo, + executionInfo.Data.OriginatorType); + + _chaosKitty.Meow(command.OperationId); + + publisher.PublishEvent(new SpecialLiquidationFinishedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + }); + } + catch (Exception ex) + { + publisher.PublishEvent(new SpecialLiquidationFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Reason = ex.Message, + }); + } + + _chaosKitty.Meow(command.OperationId); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + [UsedImplicitly] + private async Task Handle(FailSpecialLiquidationInternalCommand command, IEventPublisher publisher) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: SpecialLiquidationSaga.OperationName, + id: command.OperationId); + + if (executionInfo?.Data == null) + { + return; + } + + if (executionInfo.Data.SwitchState(executionInfo.Data.State,//from any state + SpecialLiquidationOperationState.OnTheWayToFail)) + { + publisher.PublishEvent(new SpecialLiquidationFailedEvent + { + OperationId = command.OperationId, + CreationTime = _dateService.Now(), + Reason = command.Reason, + }); + + _chaosKitty.Meow(command.OperationId); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + private bool TryGetExchangeNameFromPositions(IEnumerable positions, out string externalProviderId) + { + var externalProviderIds = positions.Select(x => x.ExternalProviderId).Distinct().ToList(); + if (externalProviderIds.Count != 1) + { + externalProviderId = null; + return false; + } + + externalProviderId = externalProviderIds.Single(); + return true; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs new file mode 100644 index 000000000..e5fb52e7f --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs @@ -0,0 +1,323 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Lykke.Common.Chaos; +using Lykke.Cqrs; +using MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Commands; +using MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Events; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Extensions; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Services.Workflow.Liquidation.Commands; +using MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Commands; +using MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Events; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Services.Workflow.SpecialLiquidation +{ + [UsedImplicitly] + public class SpecialLiquidationSaga + { + private readonly IDateService _dateService; + private readonly IChaosKitty _chaosKitty; + private readonly IOperationExecutionInfoRepository _operationExecutionInfoRepository; + private readonly IOrderReader _orderReader; + private readonly ISpecialLiquidationService _specialLiquidationService; + + private readonly MarginTradingSettings _marginTradingSettings; + private readonly CqrsContextNamesSettings _cqrsContextNamesSettings; + + public const string OperationName = "SpecialLiquidation"; + + public SpecialLiquidationSaga( + IDateService dateService, + IChaosKitty chaosKitty, + IOperationExecutionInfoRepository operationExecutionInfoRepository, + IOrderReader orderReader, + ISpecialLiquidationService specialLiquidationService, + MarginTradingSettings marginTradingSettings, + CqrsContextNamesSettings cqrsContextNamesSettings) + { + _dateService = dateService; + _chaosKitty = chaosKitty; + _operationExecutionInfoRepository = operationExecutionInfoRepository; + _orderReader = orderReader; + _specialLiquidationService = specialLiquidationService; + _marginTradingSettings = marginTradingSettings; + _cqrsContextNamesSettings = cqrsContextNamesSettings; + } + + [UsedImplicitly] + private async Task Handle(SpecialLiquidationStartedInternalEvent e, ICommandSender sender) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: OperationName, + id: e.OperationId); + + if (executionInfo?.Data == null) + { + return; + } + + if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.Started, + SpecialLiquidationOperationState.PriceRequested)) + { + var positionsVolume = GetNetPositionCloseVolume(executionInfo.Data.PositionIds, executionInfo.Data.AccountId); + + //special command is sent instantly for timeout control.. it is retried until timeout occurs + sender.SendCommand(new GetPriceForSpecialLiquidationTimeoutInternalCommand + { + OperationId = e.OperationId, + CreationTime = _dateService.Now(), + TimeoutSeconds = _marginTradingSettings.SpecialLiquidation.PriceRequestTimeoutSec, + }, _cqrsContextNamesSettings.TradingEngine); + + executionInfo.Data.Instrument = e.Instrument; + executionInfo.Data.Volume = positionsVolume; + + if (_marginTradingSettings.ExchangeConnector == ExchangeConnectorType.RealExchangeConnector) + { + var requestNumber = 1; + + //send it to the Gavel + sender.SendCommand(new GetPriceForSpecialLiquidationCommand + { + OperationId = e.OperationId, + CreationTime = _dateService.Now(), + Instrument = e.Instrument, + Volume = positionsVolume != 0 ? positionsVolume : 1,//hack, requested by the bank + RequestNumber = requestNumber, + }, _cqrsContextNamesSettings.Gavel); + + executionInfo.Data.RequestNumber = requestNumber; + } + else + { + _specialLiquidationService.FakeGetPriceForSpecialLiquidation(e.OperationId, e.Instrument, + positionsVolume); + } + + _chaosKitty.Meow(e.OperationId); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + [UsedImplicitly] + private async Task Handle(PriceForSpecialLiquidationCalculatedEvent e, ICommandSender sender) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: OperationName, + id: e.OperationId); + + if (executionInfo?.Data == null) + return; + + if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.PriceRequested, + SpecialLiquidationOperationState.PriceReceived)) + { + //validate that volume didn't changed to peek either to execute order or request the price again + var currentVolume = GetNetPositionCloseVolume(executionInfo.Data.PositionIds, executionInfo.Data.AccountId); + if (currentVolume != 0 && currentVolume != executionInfo.Data.Volume) + { + var requestNumber = executionInfo.Data.RequestNumber + 1; + + sender.SendCommand(new GetPriceForSpecialLiquidationCommand + { + OperationId = e.OperationId, + CreationTime = _dateService.Now(), + Instrument = executionInfo.Data.Instrument, + Volume = currentVolume, + RequestNumber = requestNumber, + AccountId = executionInfo.Data.AccountId, + }, _cqrsContextNamesSettings.Gavel); + + executionInfo.Data.Volume = currentVolume; + executionInfo.Data.RequestNumber = requestNumber; + + await _operationExecutionInfoRepository.Save(executionInfo); + + return;//wait for the new price + } + + executionInfo.Data.Price = e.Price; + + //execute order in Gavel by API + sender.SendCommand(new ExecuteSpecialLiquidationOrderCommand + { + OperationId = e.OperationId, + CreationTime = _dateService.Now(), + Instrument = executionInfo.Data.Instrument, + Volume = executionInfo.Data.Volume, + Price = e.Price, + }, _cqrsContextNamesSettings.TradingEngine); + + _chaosKitty.Meow(e.OperationId); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + [UsedImplicitly] + private async Task Handle(PriceForSpecialLiquidationCalculationFailedEvent e, ICommandSender sender) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: OperationName, + id: e.OperationId); + + if (executionInfo?.Data == null) + return; + + if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.PriceRequested, + SpecialLiquidationOperationState.OnTheWayToFail)) + { + sender.SendCommand(new FailSpecialLiquidationInternalCommand + { + OperationId = e.OperationId, + CreationTime = _dateService.Now(), + Reason = e.Reason + }, _cqrsContextNamesSettings.TradingEngine); + + _chaosKitty.Meow(e.OperationId); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + [UsedImplicitly] + private async Task Handle(SpecialLiquidationOrderExecutedEvent e, ICommandSender sender) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: OperationName, + id: e.OperationId); + + if (executionInfo?.Data == null) + return; + + if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.ExternalOrderExecuted, + SpecialLiquidationOperationState.InternalOrderExecutionStarted)) + { + sender.SendCommand(new ExecuteSpecialLiquidationOrdersInternalCommand + { + OperationId = e.OperationId, + CreationTime = _dateService.Now(), + Instrument = executionInfo.Data.Instrument, + Volume = executionInfo.Data.Volume, + Price = executionInfo.Data.Price, + MarketMakerId = e.MarketMakerId, + ExternalOrderId = e.OrderId, + ExternalExecutionTime = e.ExecutionTime, + }, _cqrsContextNamesSettings.TradingEngine); + + _chaosKitty.Meow(e.OperationId); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + [UsedImplicitly] + private async Task Handle(SpecialLiquidationOrderExecutionFailedEvent e, ICommandSender sender) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: OperationName, + id: e.OperationId); + + if (executionInfo?.Data == null) + return; + + if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.ExternalOrderExecuted, + SpecialLiquidationOperationState.OnTheWayToFail)) + { + sender.SendCommand(new FailSpecialLiquidationInternalCommand + { + OperationId = e.OperationId, + CreationTime = _dateService.Now(), + Reason = e.Reason + }, _cqrsContextNamesSettings.TradingEngine); + + _chaosKitty.Meow(e.OperationId); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + [UsedImplicitly] + private async Task Handle(SpecialLiquidationFinishedEvent e, ICommandSender sender) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: OperationName, + id: e.OperationId); + + if (executionInfo?.Data == null) + return; + + if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.InternalOrdersExecuted, + SpecialLiquidationOperationState.Finished)) + { + if (!string.IsNullOrEmpty(executionInfo.Data.CausationOperationId)) + { + sender.SendCommand(new ResumeLiquidationInternalCommand + { + OperationId = executionInfo.Data.CausationOperationId, + CreationTime = _dateService.Now(), + Comment = $"Resume after special liquidation {executionInfo.Id} finished", + IsCausedBySpecialLiquidation = true, + CausationOperationId = executionInfo.Id, + PositionsLiquidatedBySpecialLiquidation = executionInfo.Data.PositionIds + }, _cqrsContextNamesSettings.TradingEngine); + } + + _chaosKitty.Meow(e.OperationId); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + [UsedImplicitly] + private async Task Handle(SpecialLiquidationFailedEvent e, ICommandSender sender) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: OperationName, + id: e.OperationId); + + if (executionInfo?.Data == null) + return; + + if (executionInfo.Data.SwitchState(executionInfo.Data.State,//from any state + SpecialLiquidationOperationState.Failed)) + { + if (!string.IsNullOrEmpty(executionInfo.Data.CausationOperationId)) + { + sender.SendCommand(new ResumeLiquidationInternalCommand + { + OperationId = executionInfo.Data.CausationOperationId, + CreationTime = _dateService.Now(), + Comment = $"Resume after special liquidation {executionInfo.Id} failed. Reason: {e.Reason}", + IsCausedBySpecialLiquidation = true, + CausationOperationId = executionInfo.Id + }, _cqrsContextNamesSettings.TradingEngine); + } + + _chaosKitty.Meow(e.OperationId); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + private decimal GetNetPositionCloseVolume(ICollection positionIds, string accountId) + { + var netPositionVolume = _orderReader.GetPositions() + .Where(x => positionIds.Contains(x.Id) + && (string.IsNullOrEmpty(accountId) || x.AccountId == accountId)) + .Sum(x => x.Volume); + + return -netPositionVolume; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/WithdrawalCommandsHandler.cs b/src/MarginTrading.Backend.Services/Workflow/WithdrawalCommandsHandler.cs new file mode 100644 index 000000000..b9ac4ff47 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/WithdrawalCommandsHandler.cs @@ -0,0 +1,128 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Lykke.Common.Chaos; +using Lykke.Cqrs; +using MarginTrading.AccountsManagement.Contracts.Commands; +using MarginTrading.AccountsManagement.Contracts.Events; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Extensions; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Common.Services; + +namespace MarginTrading.Backend.Services.Workflow +{ + public class WithdrawalCommandsHandler + { + private readonly IDateService _dateService; + private readonly IAccountsCacheService _accountsCacheService; + private readonly IAccountUpdateService _accountUpdateService; + private readonly IChaosKitty _chaosKitty; + private readonly IOperationExecutionInfoRepository _operationExecutionInfoRepository; + private const string OperationName = "FreezeAmountForWithdrawal"; + + public WithdrawalCommandsHandler( + IDateService dateService, + IAccountsCacheService accountsCacheService, + IAccountUpdateService accountUpdateService, + IChaosKitty chaosKitty, + IOperationExecutionInfoRepository operationExecutionInfoRepository) + { + _dateService = dateService; + _accountsCacheService = accountsCacheService; + _accountUpdateService = accountUpdateService; + _chaosKitty = chaosKitty; + _operationExecutionInfoRepository = operationExecutionInfoRepository; + } + + /// + /// Freeze the the amount in the margin. + /// + [UsedImplicitly] + private async Task Handle(FreezeAmountForWithdrawalCommand command, IEventPublisher publisher) + { + var executionInfo = await _operationExecutionInfoRepository.GetOrAddAsync( + operationName: OperationName, + operationId: command.OperationId, + factory: () => new OperationExecutionInfo( + operationName: OperationName, + id: command.OperationId, + lastModified: _dateService.Now(), + data: new WithdrawalFreezeOperationData + { + State = OperationState.Initiated, + AccountId = command.AccountId, + Amount = command.Amount, + } + )); + + MarginTradingAccount account = null; + try + { + account = _accountsCacheService.Get(command.AccountId); + } + catch + { + publisher.PublishEvent(new AmountForWithdrawalFreezeFailedEvent(command.OperationId, _dateService.Now(), + command.AccountId, command.Amount, $"Failed to get account {command.AccountId}")); + return; + } + + if (executionInfo.Data.SwitchState(OperationState.Initiated, OperationState.Started)) + { + if (account.GetFreeMargin() >= command.Amount) + { + await _accountUpdateService.FreezeWithdrawalMargin(command.AccountId, command.OperationId, + command.Amount); + + _chaosKitty.Meow(command.OperationId); + + publisher.PublishEvent(new AmountForWithdrawalFrozenEvent(command.OperationId, _dateService.Now(), + command.AccountId, command.Amount, command.Reason)); + } + else + { + publisher.PublishEvent(new AmountForWithdrawalFreezeFailedEvent(command.OperationId, + _dateService.Now(), + command.AccountId, command.Amount, "Not enough free margin")); + } + + _chaosKitty.Meow(command.OperationId); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + + /// + /// Withdrawal failed => margin must be unfrozen. + /// + /// Errors are not handled => if error occurs event will be retried + [UsedImplicitly] + private async Task Handle(UnfreezeMarginOnFailWithdrawalCommand command, IEventPublisher publisher) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + operationName: OperationName, + id: command.OperationId + ); + + if (executionInfo == null) + return; + + if (executionInfo.Data.SwitchState(OperationState.Started, OperationState.Finished)) + { + await _accountUpdateService.UnfreezeWithdrawalMargin(executionInfo.Data.AccountId, command.OperationId); + + publisher.PublishEvent(new UnfreezeMarginOnFailSucceededWithdrawalEvent(command.OperationId, + _dateService.Now(), executionInfo.Data.AccountId, executionInfo.Data.Amount)); + + _chaosKitty.Meow(command.OperationId); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.TestClient/MarginTrading.Backend.TestClient.csproj b/src/MarginTrading.Backend.TestClient/MarginTrading.Backend.TestClient.csproj index 89c29d020..2300e33e1 100644 --- a/src/MarginTrading.Backend.TestClient/MarginTrading.Backend.TestClient.csproj +++ b/src/MarginTrading.Backend.TestClient/MarginTrading.Backend.TestClient.csproj @@ -1,19 +1,23 @@  Exe - netcoreapp2.0 + netcoreapp2.2 latest - 1.0.0 + 1.16.29 + + + 1701;1702;1705;CA2007;0612;0618;1591 - - - - - + + + + + + \ No newline at end of file diff --git a/src/MarginTrading.Backend.TestClient/Program.cs b/src/MarginTrading.Backend.TestClient/Program.cs index b8dbeb97b..fba886ff6 100644 --- a/src/MarginTrading.Backend.TestClient/Program.cs +++ b/src/MarginTrading.Backend.TestClient/Program.cs @@ -1,19 +1,16 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Linq; using System.Threading.Tasks; using AsyncFriendlyStackTrace; -using Autofac; -using Autofac.Extensions.DependencyInjection; -using FluentAssertions; using Lykke.HttpClientGenerator; using Lykke.HttpClientGenerator.Retries; -using MarginTrading.Backend.Contracts.AccountAssetPair; -using MarginTrading.Backend.Contracts.AssetPairSettings; -using MarginTrading.Backend.Contracts.Client; -using MarginTrading.Backend.Contracts.DataReaderClient; -using MarginTrading.Backend.Contracts.DayOffSettings; -using MarginTrading.Common.Extensions; -using Microsoft.Extensions.DependencyInjection; +using MarginTrading.Backend.Contracts; +using MarginTrading.Backend.Contracts.Orders; +using MarginTrading.Backend.Contracts.Positions; +using MarginTrading.Backend.Contracts.Prices; using Newtonsoft.Json; using Refit; @@ -57,243 +54,59 @@ private static string TryDeserializeToString(string str) // NOTE: Use Demo instances for tests private static async Task Run() { - var services = new ServiceCollection(); - var builder = new ContainerBuilder(); var retryStrategy = new LinearRetryStrategy(TimeSpan.FromSeconds(10), 50); - services.RegisterMtBackendClient(HttpClientGenerator.BuildForUrl("http://localhost:5000") - .WithApiKey("margintrading").WithRetriesStrategy(retryStrategy).Create()); - services.RegisterMtDataReaderClient(HttpClientGenerator.BuildForUrl("http://localhost:5008") - .WithApiKey("margintrading").WithRetriesStrategy(retryStrategy).Create()); - builder.Populate(services); - var container = builder.Build(); - var backendClient = container.Resolve(); - - await backendClient.ScheduleSettings.ListExclusions().Dump(); - var excl = await backendClient.ScheduleSettings.CreateExclusion(new DayOffExclusionInputContract - { - AssetPairRegex = "lol", - Start = DateTime.Now.AddDays(-1), - End = DateTime.Now.Date, - IsTradeEnabled = false, - }).Dump(); - var id = excl.Id; - var ex = await backendClient.ScheduleSettings.GetExclusion(id).Dump(); - ex.AssetPairRegex = "^btc"; - await backendClient.ScheduleSettings.UpdateExclusion(id, ex).Dump(); - await backendClient.ScheduleSettings.GetExclusion(id).Dump(); - await backendClient.ScheduleSettings.ListCompiledExclusions().Dump(); - await backendClient.ScheduleSettings.DeleteExclusion(id).Dump(); - await backendClient.ScheduleSettings.GetExclusion(id).Dump(); - - var s = await backendClient.ScheduleSettings.GetSchedule().Dump(); - s.AssetPairsWithoutDayOff.Add("BTCRABBIT"); - await backendClient.ScheduleSettings.SetSchedule(s).Dump(); - s.AssetPairsWithoutDayOff.Remove("BTCRABBIT"); - await backendClient.ScheduleSettings.SetSchedule(s).Dump(); - - var assetPairSettingsInputContract = new AssetPairContract - { - Id = "BTCUSD.test", - BasePairId = "BTCUSD", - LegalEntity = "LYKKETEST", - StpMultiplierMarkupBid = 0.9m, - StpMultiplierMarkupAsk = 1.1m, - MatchingEngineMode = MatchingEngineModeContract.MarketMaker, - Name = "BTCUSD.test name", - Accuracy = 123, - BaseAssetId = "BTC", - QuoteAssetId = "USD", - }; - - await backendClient.AssetPairsEdit.Delete("BTCUSD.test").Dump(); - var result = await backendClient.AssetPairsEdit.Insert("BTCUSD.test", assetPairSettingsInputContract) - .Dump(); - CheckAssetPair(result, assetPairSettingsInputContract); - - assetPairSettingsInputContract.MatchingEngineMode = MatchingEngineModeContract.Stp; - var result2 = await backendClient.AssetPairsEdit.Update("BTCUSD.test", assetPairSettingsInputContract) - .Dump(); - CheckAssetPair(result2, assetPairSettingsInputContract); - - - var dataReaderClient = container.Resolve(); - - var list = await dataReaderClient.AssetPairsRead.List().Dump(); - var ours = list.First(e => e.Id == "BTCUSD.test"); - CheckAssetPair(ours, assetPairSettingsInputContract); - - var get = await dataReaderClient.AssetPairsRead.Get("BTCUSD.test").Dump(); - CheckAssetPair(get, assetPairSettingsInputContract); - - var nonexistentGet = await dataReaderClient.AssetPairsRead.Get("nonexistent").Dump(); - nonexistentGet.RequiredEqualsTo(null, nameof(nonexistentGet)); - - var getByMode = await dataReaderClient.AssetPairsRead.List("LYKKETEST", MatchingEngineModeContract.Stp) - .Dump(); - var ours2 = getByMode.First(e => e.Id == "BTCUSD.test"); - CheckAssetPair(ours2, assetPairSettingsInputContract); - - var getByOtherMode = await dataReaderClient.AssetPairsRead - .List("LYKKETEST", MatchingEngineModeContract.MarketMaker).Dump(); - getByOtherMode.Count(e => e.Id == "BTCUSD.test").RequiredEqualsTo(0, "getByOtherMode.Count"); - - var result3 = await backendClient.AssetPairsEdit.Delete("BTCUSD.test").Dump(); - CheckAssetPair(result3, assetPairSettingsInputContract); - - var nonexistentDelete = await backendClient.AssetPairsEdit.Delete("nonexistent").Dump(); - nonexistentDelete.RequiredEqualsTo(null, nameof(nonexistentDelete)); - - #region TradeMonitoring - - var assetSumary = await dataReaderClient.TradeMonitoringRead.AssetSummaryList().Dump(); - - var openPositions = await dataReaderClient.TradeMonitoringRead.OpenPositions().Dump(); - var openPosition = openPositions.FirstOrDefault(); - if (openPosition != null) - { - string clientId = openPosition.ClientId; - var openPositionsByClient = - await dataReaderClient.TradeMonitoringRead.OpenPositionsByClient(clientId).Dump(); - } - - var openPositionsByDate = await dataReaderClient.TradeMonitoringRead - .OpenPositionsByDate(DateTime.UtcNow.AddDays(-30), DateTime.UtcNow).Dump(); - var openPositionsByVolume = await dataReaderClient.TradeMonitoringRead.OpenPositionsByVolume(100).Dump(); - - var pendingOrders = await dataReaderClient.TradeMonitoringRead.PendingOrders().Dump(); - var pendingOrder = pendingOrders.FirstOrDefault(); - if (pendingOrder != null) - { - string clientId = pendingOrder.ClientId; - var pendingOrdersByClient = - await dataReaderClient.TradeMonitoringRead.PendingOrdersByClient(clientId).Dump(); - } - - var pendingOrdersByDate = await dataReaderClient.TradeMonitoringRead - .PendingOrdersByDate(DateTime.UtcNow.AddDays(-30), DateTime.UtcNow).Dump(); - var pendingOrdersByVolume = await dataReaderClient.TradeMonitoringRead.PendingOrdersByVolume(100).Dump(); + var generator = HttpClientGenerator.BuildForUrl("http://localhost:5000").WithApiKey("margintrading") + .WithRetriesStrategy(retryStrategy).Create(); - var orderBooksByInstrument = await dataReaderClient.TradeMonitoringRead.OrderBooksByInstrument("BTCUSD"); + await CheckMarginParametersAsync(generator); + + //await CheckAccountsAsync(generator); + //await CheckOrdersAsync(generator); + //await CheckPricesAsync(generator); - #endregion - - var accountAssetPairs = await dataReaderClient.AccountAssetPairsRead - .List() - .Dump(); - var firstAccountAssetPair = accountAssetPairs.First(); - var secondAccountAssetPair = await dataReaderClient.AccountAssetPairsRead - .Get(firstAccountAssetPair.TradingConditionId, firstAccountAssetPair.BaseAssetId, - firstAccountAssetPair.Instrument) - .Dump(); - firstAccountAssetPair.Should().BeEquivalentTo(secondAccountAssetPair); - - var accountAssetPairsGetByTradingCondition = await dataReaderClient.AccountAssetPairsRead - .Get(firstAccountAssetPair.TradingConditionId, firstAccountAssetPair.BaseAssetId) - .Dump(); - foreach (var accountAssetPair in accountAssetPairsGetByTradingCondition) - { - var item = accountAssetPairs - .Single(x => x.TradingConditionId == accountAssetPair.TradingConditionId - && x.BaseAssetId == accountAssetPair.BaseAssetId - && x.Instrument == accountAssetPair.Instrument); - item.Should().BeEquivalentTo(accountAssetPair); - } - - firstAccountAssetPair.OvernightSwapLong = 0.1m; - var updatedAccountAssetPair = await backendClient.TradingConditionsEdit - .InsertOrUpdateAccountAsset(firstAccountAssetPair) - .Dump(); - updatedAccountAssetPair.Result.Should().BeEquivalentTo(firstAccountAssetPair); - - var tc = await backendClient.TradingConditionsEdit.InsertOrUpdate( - new Contracts.TradingConditions.TradingConditionContract - { - Id = "LYKKETEST", - LegalEntity = "LYKKEVA", - IsDefault = false, - Name = "Test Trading Condition", - }).Dump(); - tc.Result.Id.RequiredEqualsTo("LYKKETEST", "tc.Result.Id"); - - var ag = await backendClient.TradingConditionsEdit.InsertOrUpdateAccountGroup( - new Contracts.TradingConditions.AccountGroupContract - { - BaseAssetId = "BTC", - TradingConditionId = tc.Result.Id, - DepositTransferLimit = 0.1m, - ProfitWithdrawalLimit = 0.2m, - MarginCall = 0.3m, - StopOut = 0.4m - }) - .Dump(); - ag.Result.StopOut.RequiredEqualsTo(0.4m, "ag.Result.StopOut"); - - var aa = await backendClient.TradingConditionsEdit.InsertOrUpdateAccountAsset(new AccountAssetPairContract - { - Instrument = "TSTLKK", - BaseAssetId = "BTC", - TradingConditionId = tc.Result.Id - }) - .Dump(); - aa.Result.Instrument.RequiredEqualsTo("TSTLKK", "aa.Result.Instrument"); - - var ai = await backendClient.TradingConditionsEdit.AssignInstruments( - new Contracts.TradingConditions.AssignInstrumentsContract - { - BaseAssetId = "BTC", - TradingConditionId = tc.Result.Id, - Instruments = new string[] {"TSTLKK"} - }) - .Dump(); - - ai.IsOk.RequiredEqualsTo(true, "ai.IsOk"); - - var tclist = await dataReaderClient.TradingConditionsRead.List().Dump(); - await dataReaderClient.TradingConditionsRead.Get(tclist.First().Id).Dump(); + Console.WriteLine("Successfuly finished"); + } - var manualCharge = await backendClient.AccountsBalance.ChargeManually( - new Contracts.AccountBalance.AccountChargeManuallyRequest - { - ClientId = "232b3b04-7479-44e7-a6b3-ac131d8e6ccd", - AccountId = "d_f4c745f19c834145bcf2d6b5f1a871f3", - Amount = 1, - Reason = "API TEST" - }) - .Dump(); + private static async Task CheckPricesAsync(HttpClientGenerator generator) + { + var api = generator.Generate(); + await api.GetBestAsync(new InitPricesBackendRequest {AssetIds = new[] {"EURUSD"}}).Dump(); + } + + private static async Task CheckOrdersAsync(HttpClientGenerator generator) + { + var api = generator.Generate(); + var orders = await api.ListAsync().Dump(); - var accountGroups = await dataReaderClient.AccountGroups.List().Dump(); - var accountGroup1 = accountGroups.FirstOrDefault(); - if (accountGroup1 != null) + if (orders.Any()) { - var accountGroup = - (await dataReaderClient.AccountGroups.GetByBaseAsset(accountGroup1.TradingConditionId, - accountGroup1.BaseAssetId)).Dump(); - accountGroup.Should().BeEquivalentTo(accountGroup1); + await api.CancelAsync(orders.First().Id, new OrderCancelRequest() {AdditionalInfo = "test"}); } + + } - var assetPairs = await dataReaderClient.Dictionaries.AssetPairs().Dump(); - var matchingEngines = await dataReaderClient.Dictionaries.MatchingEngines().Dump(); - var orderTypes = await dataReaderClient.Dictionaries.OrderTypes().Dump(); - - - var routes = await dataReaderClient.Routes.List().Dump(); - var route1 = routes.FirstOrDefault(); - if (route1 != null) + private static async Task CheckPositionsAsync(HttpClientGenerator generator) + { + var api = generator.Generate(); + var positions = await api.ListAsync().Dump(); + var anyPosition = positions.FirstOrDefault(); + if (anyPosition != null) { - var route = await dataReaderClient.Routes.GetById(route1.Id).Dump(); - route.Should().BeEquivalentTo(route1); + await api.CloseAsync(anyPosition.Id, + new PositionCloseRequest {Comment = "111", Originator = OriginatorTypeContract.Investor}).Dump(); } - - var isEnabled = - await dataReaderClient.Settings.IsMarginTradingEnabled("232b3b04-7479-44e7-a6b3-ac131d8e6ccd"); - - Console.WriteLine("Successfuly finished"); } - private static void CheckAssetPair(AssetPairContract actual, - AssetPairContract expected) + private static async Task CheckAccountsAsync(HttpClientGenerator generator) + { + var api = generator.Generate(); + await api.GetAllAccountStats().Dump(); + } + + private static async Task CheckMarginParametersAsync(HttpClientGenerator generator) { - actual.Should().BeEquivalentTo(expected); + var api = generator.Generate(); + await api.GetOvernightMarginParameterValues(null).Dump(); } public static T Dump(this T o) diff --git a/src/MarginTrading.Backend/Application.cs b/src/MarginTrading.Backend/Application.cs index b4b7866b2..7babc0f0b 100644 --- a/src/MarginTrading.Backend/Application.cs +++ b/src/MarginTrading.Backend/Application.cs @@ -1,29 +1,29 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Threading.Tasks; using Common.Log; -using FluentScheduler; -using Lykke.RabbitMqBroker.Subscriber; using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.MarketMakerFeed; using MarginTrading.Backend.Core.MatchingEngines; using MarginTrading.Backend.Core.Orderbooks; +using MarginTrading.Backend.Core.Services; using MarginTrading.Backend.Core.Settings; using MarginTrading.Backend.Services; -using MarginTrading.Backend.Scheduling; -using MarginTrading.Backend.Services.Helpers; +using MarginTrading.Backend.Services.AssetPairs; +using MarginTrading.Backend.Services.Assets; using MarginTrading.Backend.Services.Infrastructure; using MarginTrading.Backend.Services.MatchingEngines; using MarginTrading.Backend.Services.Notifications; -using MarginTrading.Backend.Services.Services; using MarginTrading.Backend.Services.Stp; -using MarginTrading.Common.Extensions; +using MarginTrading.Backend.Services.TradingConditions; using MarginTrading.Common.RabbitMq; using MarginTrading.Common.Services; using MarginTrading.OrderbookAggregator.Contracts.Messages; -using Newtonsoft.Json; +using MarginTrading.SettingsService.Contracts.AssetPair; +using MarginTrading.SettingsService.Contracts.Enums; +using MarginTrading.SettingsService.Contracts.Messages; #pragma warning disable 1591 @@ -32,33 +32,46 @@ namespace MarginTrading.Backend public sealed class Application { private readonly IRabbitMqNotifyService _rabbitMqNotifyService; - private readonly IConsole _consoleWriter; private readonly MarketMakerService _marketMakerService; private readonly ILog _logger; - private readonly MarginSettings _marginSettings; + private readonly MarginTradingSettings _marginSettings; private readonly IMaintenanceModeService _maintenanceModeService; private readonly IRabbitMqService _rabbitMqService; - private readonly MatchingEngineRoutesManager _matchingEngineRoutesManager; + private readonly IMatchingEngineRoutesManager _matchingEngineRoutesManager; private readonly IMigrationService _migrationService; private readonly IConvertService _convertService; - private readonly ExternalOrderBooksList _externalOrderBooksList; + private readonly IFxRateCacheService _fxRateCacheService; + private readonly IExternalOrderbookService _externalOrderbookService; + private readonly IAssetsManager _assetsManager; + private readonly IAssetPairsManager _assetPairsManager; + private readonly ITradingInstrumentsManager _tradingInstrumentsManager; + private readonly ITradingConditionsManager _tradingConditionsManager; + private readonly IScheduleSettingsCacheService _scheduleSettingsCacheService; + private readonly IOvernightMarginService _overnightMarginService; + private readonly IScheduleControlService _scheduleControlService; private const string ServiceName = "MarginTrading.Backend"; public Application( IRabbitMqNotifyService rabbitMqNotifyService, - IConsole consoleWriter, MarketMakerService marketMakerService, - ILog logger, - MarginSettings marginSettings, + ILog logger, + MarginTradingSettings marginSettings, IMaintenanceModeService maintenanceModeService, IRabbitMqService rabbitMqService, MatchingEngineRoutesManager matchingEngineRoutesManager, IMigrationService migrationService, IConvertService convertService, - ExternalOrderBooksList externalOrderBooksList) + IFxRateCacheService fxRateCacheService, + IExternalOrderbookService externalOrderbookService, + IAssetsManager assetsManager, + IAssetPairsManager assetPairsManager, + ITradingInstrumentsManager tradingInstrumentsManager, + ITradingConditionsManager tradingConditionsManager, + IScheduleSettingsCacheService scheduleSettingsCacheService, + IOvernightMarginService overnightMarginService, + IScheduleControlService scheduleControlService) { _rabbitMqNotifyService = rabbitMqNotifyService; - _consoleWriter = consoleWriter; _marketMakerService = marketMakerService; _logger = logger; _marginSettings = marginSettings; @@ -67,21 +80,60 @@ public Application( _matchingEngineRoutesManager = matchingEngineRoutesManager; _migrationService = migrationService; _convertService = convertService; - _externalOrderBooksList = externalOrderBooksList; + _fxRateCacheService = fxRateCacheService; + _externalOrderbookService = externalOrderbookService; + _assetsManager = assetsManager; + _assetPairsManager = assetPairsManager; + _tradingInstrumentsManager = tradingInstrumentsManager; + _tradingConditionsManager = tradingConditionsManager; + _scheduleSettingsCacheService = scheduleSettingsCacheService; + _overnightMarginService = overnightMarginService; + _scheduleControlService = scheduleControlService; } public async Task StartApplicationAsync() { - _consoleWriter.WriteLine($"Starting {ServiceName}"); - await _logger.WriteInfoAsync(ServiceName, null, null, "Starting..."); + await _logger.WriteInfoAsync(nameof(StartApplicationAsync), nameof(Application), $"Starting {ServiceName}"); + + if (_marginSettings.MarketMakerRabbitMqSettings == null && + _marginSettings.StpAggregatorRabbitMqSettings == null) + { + throw new Exception("Both MM and STP connections are not configured. Can not start service."); + } try { await _migrationService.InvokeAll(); - - _rabbitMqService.Subscribe( - _marginSettings.MarketMakerRabbitMqSettings, false, HandleNewOrdersMessage, - _rabbitMqService.GetJsonDeserializer()); + + if (_marginSettings.MarketMakerRabbitMqSettings != null) + { + _rabbitMqService.Subscribe( + _marginSettings.MarketMakerRabbitMqSettings, false, HandleNewOrdersMessage, + _rabbitMqService.GetJsonDeserializer()); + } + else + { + _logger.WriteInfo(ServiceName, nameof(StartApplicationAsync), + "MarketMakerRabbitMqSettings is not configured"); + } + + if (_marginSettings.FxRateRabbitMqSettings != null) + { + _rabbitMqService.Subscribe(_marginSettings.FxRateRabbitMqSettings, false, + _fxRateCacheService.SetQuote, _rabbitMqService.GetMsgPackDeserializer()); + } + + if (_marginSettings.StpAggregatorRabbitMqSettings != null) + { + _rabbitMqService.Subscribe(_marginSettings.StpAggregatorRabbitMqSettings, + false, HandleStpOrderbook, + _rabbitMqService.GetMsgPackDeserializer()); + } + else + { + _logger.WriteInfo(ServiceName, nameof(StartApplicationAsync), + "StpAggregatorRabbitMqSettings is not configured"); + } if (_marginSettings.RisksRabbitMqSettings != null) { @@ -89,31 +141,24 @@ public async Task StartApplicationAsync() true, _matchingEngineRoutesManager.HandleRiskManagerCommand, _rabbitMqService.GetJsonDeserializer()); } - else if (_marginSettings.IsLive) + else { - _logger.WriteWarning(ServiceName, nameof(StartApplicationAsync), + _logger.WriteInfo(ServiceName, nameof(StartApplicationAsync), "RisksRabbitMqSettings is not configured"); } - - // Demo server works only in MM mode - if (_marginSettings.IsLive) + + var settingsChanged = new RabbitMqSettings { - _rabbitMqService.Subscribe(_marginSettings.StpAggregatorRabbitMqSettings - .RequiredNotNull(nameof(_marginSettings.StpAggregatorRabbitMqSettings)), false, - HandleStpOrderbook, - _rabbitMqService.GetMsgPackDeserializer()); - } + ConnectionString = _marginSettings.MtRabbitMqConnString, + ExchangeName = _marginSettings.RabbitMqQueues.SettingsChanged.ExchangeName + }; - var settingsCalcTime = (_marginSettings.OvernightSwapCalculationTime.Hours, - _marginSettings.OvernightSwapCalculationTime.Minutes); - var registry = new Registry(); - registry.Schedule().ToRunEvery(0).Days().At(settingsCalcTime.Hours, settingsCalcTime.Minutes); - JobManager.Initialize(registry); - JobManager.JobException += info => _logger.WriteError(ServiceName, nameof(JobManager), info.Exception); + _rabbitMqService.Subscribe(settingsChanged, + true, HandleChangeSettingsMessage, + _rabbitMqService.GetJsonDeserializer()); } catch (Exception ex) { - _consoleWriter.WriteLine($"{ServiceName} error: {ex.Message}"); await _logger.WriteErrorAsync(ServiceName, "Application.RunAsync", null, ex); } } @@ -121,18 +166,14 @@ public async Task StartApplicationAsync() private Task HandleStpOrderbook(ExternalExchangeOrderbookMessage message) { var orderbook = _convertService.Convert(message); - _externalOrderBooksList.SetOrderbook(orderbook); + _externalOrderbookService.SetOrderbook(orderbook); return Task.CompletedTask; } public void StopApplication() { _maintenanceModeService.SetMode(true); - _consoleWriter.WriteLine($"Maintenance mode enabled for {ServiceName}"); - _consoleWriter.WriteLine($"Closing {ServiceName}"); - _logger.WriteInfoAsync(ServiceName, null, null, "Closing...").Wait(); - _rabbitMqNotifyService.Stop(); - _consoleWriter.WriteLine($"Closed {ServiceName}"); + _logger.WriteInfoAsync(ServiceName, null, null, "Application is shutting down").Wait(); } private Task HandleNewOrdersMessage(MarketMakerOrderCommandsBatchMessage feedData) @@ -140,5 +181,44 @@ private Task HandleNewOrdersMessage(MarketMakerOrderCommandsBatchMessage feedDat _marketMakerService.ProcessOrderCommands(feedData); return Task.CompletedTask; } + + private async Task HandleChangeSettingsMessage(SettingsChangedEvent message) + { + switch (message.SettingsType) + { + case SettingsTypeContract.Asset: + await _assetsManager.UpdateCacheAsync(); + break; + + case SettingsTypeContract.AssetPair: + //AssetPair change handled in AssetPairProjection + break; + + case SettingsTypeContract.TradingCondition: + await _tradingConditionsManager.InitTradingConditionsAsync(); + break; + + case SettingsTypeContract.TradingInstrument: + await _tradingInstrumentsManager.UpdateTradingInstrumentsCacheAsync(message.ChangedEntityId); + break; + + case SettingsTypeContract.TradingRoute: + await _matchingEngineRoutesManager.UpdateRoutesCacheAsync(); + break; + + case SettingsTypeContract.ScheduleSettings: + await _scheduleSettingsCacheService.UpdateAllSettingsAsync(); + _overnightMarginService.ScheduleNext(); + _scheduleControlService.ScheduleNext(); + break; + + case SettingsTypeContract.Market: + break; + case SettingsTypeContract.ServiceMaintenance: + break; + default: + throw new NotImplementedException($"Type {message.SettingsType} is not supported"); + } + } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend/Attributes/SkipMarginTradingEnabledCheckAttribute.cs b/src/MarginTrading.Backend/Attributes/SkipMarginTradingEnabledCheckAttribute.cs deleted file mode 100644 index 88e05794d..000000000 --- a/src/MarginTrading.Backend/Attributes/SkipMarginTradingEnabledCheckAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using MarginTrading.Backend.Filters; - -namespace MarginTrading.Backend.Attributes -{ - /// - /// Indicates that filter should not check if current type of margin trading (live or demo) is enabled for clientId. - /// Used for marking actions which accept a clientId but can be called even if particular type of trading is disabled for client. - /// - [AttributeUsage(AttributeTargets.Method)] - public class SkipMarginTradingEnabledCheckAttribute: Attribute - { - } -} diff --git a/src/MarginTrading.Backend/Connected Services/Application Insights/ConnectedService.json b/src/MarginTrading.Backend/Connected Services/Application Insights/ConnectedService.json deleted file mode 100644 index f4580e22d..000000000 --- a/src/MarginTrading.Backend/Connected Services/Application Insights/ConnectedService.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "ProviderId": "Microsoft.ApplicationInsights.ConnectedService.ConnectedServiceProvider", - "Version": "8.6.404.2", - "GettingStartedDocument": { - "Uri": "https://go.microsoft.com/fwlink/?LinkID=798432" - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend/Controllers/AccountProfileController.cs b/src/MarginTrading.Backend/Controllers/AccountProfileController.cs deleted file mode 100644 index 56164825a..000000000 --- a/src/MarginTrading.Backend/Controllers/AccountProfileController.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Mappers; -using MarginTrading.Backend.Core.Settings; -using MarginTrading.Backend.Services; -using MarginTrading.Common.Middleware; -using MarginTrading.Contract.BackendContracts; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.Backend.Controllers -{ - /// - /// - /// - [Route("api/accountprofile")] - [MiddlewareFilter(typeof(RequestLoggingPipeline))] - public class AccountProfileController : Controller - { - private readonly IMarginTradingAccountHistoryRepository _accountsHistoryRepository; - private readonly IMarginTradingOrdersHistoryRepository _ordersHistoryRepository; - private readonly IAccountsCacheService _accountsCacheService; - private readonly OrdersCache _ordersCache; - private readonly MarginSettings _settings; - - public AccountProfileController( - IMarginTradingAccountHistoryRepository accountsHistoryRepository, - IMarginTradingOrdersHistoryRepository ordersHistoryRepository, - IAccountsCacheService accountsCacheService, - OrdersCache ordersCache, - MarginSettings settings) - { - _accountsHistoryRepository = accountsHistoryRepository; - _ordersHistoryRepository = ordersHistoryRepository; - _accountsCacheService = accountsCacheService; - _ordersCache = ordersCache; - _settings = settings; - } - - /// - /// Returns margin account by id - /// - /// - /// - /// - [HttpGet] - [Route("account/{clientId}/{accountId}")] - public MarginTradingAccountBackendContract GetAccount(string clientId, string accountId) - { - return _accountsCacheService.Get(clientId, accountId).ToFullBackendContract(_settings.IsLive); - } - - /// - /// Returns account open positions by account id - /// - /// - /// - [HttpGet] - [Route("openPositions/{accountId}")] - public IEnumerable GetAccountOrders(string accountId) - { - return _ordersCache.ActiveOrders.GetOrdersByAccountIds(accountId).Select(item => item.ToBaseContract()); - } - - /// - /// Returns account history by account id - /// - /// - /// - /// - [HttpGet] - [Route("history/{clientId}/{accountId}")] - public async Task GetAccountHistory(string clientId, string accountId) - { - var now = DateTime.UtcNow; - - var accounts = (await _accountsHistoryRepository.GetAsync(new[] { accountId }, now.AddYears(-1), now)) - .Where(item => item.Type != AccountHistoryType.OrderClosed) - .OrderByDescending(item => item.Date); - - var historyOrders = (await _ordersHistoryRepository.GetHistoryAsync(clientId, new[] { accountId }, now.AddYears(-1), now)) - .Where(item => item.Status != OrderStatus.Rejected); - - var openPositions = _ordersCache.ActiveOrders.GetOrdersByAccountIds(accountId); - - return BackendContractFactory.CreateAccountHistoryBackendResponse(accounts, openPositions, historyOrders); - } - } -} diff --git a/src/MarginTrading.Backend/Controllers/AccountsBalanceController.cs b/src/MarginTrading.Backend/Controllers/AccountsBalanceController.cs deleted file mode 100644 index 78db06325..000000000 --- a/src/MarginTrading.Backend/Controllers/AccountsBalanceController.cs +++ /dev/null @@ -1,202 +0,0 @@ -using System; -using System.Threading.Tasks; -using Common; -using Common.Log; -using MarginTrading.Backend.Contracts; -using MarginTrading.Backend.Contracts.AccountBalance; -using MarginTrading.Backend.Contracts.Common; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Settings; -using MarginTrading.Backend.Services; -using MarginTrading.Common.Middleware; -using MarginTrading.Common.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.Backend.Controllers -{ - [Authorize] - [Route("api/[controller]")] - [MiddlewareFilter(typeof(RequestLoggingPipeline))] - public class AccountsBalanceController : Controller, IAccountsBalanceApi - { - private readonly IMarginTradingOperationsLogService _operationsLogService; - private readonly ILog _log; - private readonly MarginSettings _marginSettings; - private readonly IAccountsCacheService _accountsCacheService; - private readonly AccountManager _accountManager; - - public AccountsBalanceController( - MarginSettings marginSettings, - IMarginTradingOperationsLogService operationsLogService, - ILog log, - IAccountsCacheService accountsCacheService, - AccountManager accountManager) - { - _marginSettings = marginSettings; - _operationsLogService = operationsLogService; - _log = log; - _accountsCacheService = accountsCacheService; - _accountManager = accountManager; - } - - - [Route("deposit")] - [HttpPost] - [ProducesResponseType(typeof(BackendResponse), 200)] - public async Task> AccountDeposit([FromBody]AccountDepositWithdrawRequest request) - { - var account = _accountsCacheService.Get(request.ClientId, request.AccountId); - - var changeTransferLimit = _marginSettings.IsLive && - request.PaymentType == PaymentType.Transfer && - !IsCrypto(account.BaseAssetId); - - try - { - var transactionId = await _accountManager.UpdateBalanceAsync(account, Math.Abs(request.Amount), - AccountHistoryType.Deposit, "Account deposit", request.TransactionId, changeTransferLimit); - - _operationsLogService.AddLog($"account deposit {request.PaymentType}", request.ClientId, request.AccountId, request.ToJson(), true.ToJson()); - - return BackendResponse.Ok( - new AccountDepositWithdrawResponse {TransactionId = transactionId}); - } - catch (Exception e) - { - await _log.WriteErrorAsync(nameof(AccountsBalanceController), "AccountDeposit", request?.ToJson(), e); - return BackendResponse.Error(e.Message); - } - } - - [Route("withdraw")] - [HttpPost] - [ProducesResponseType(typeof(BackendResponse), 200)] - public async Task> AccountWithdraw([FromBody]AccountDepositWithdrawRequest request) - { - var account = _accountsCacheService.Get(request.ClientId, request.AccountId); - var freeMargin = account.GetFreeMargin(); - - if (freeMargin < Math.Abs(request.Amount)) - return BackendResponse.Error( - "Requested withdrawal amount is more than free margin"); - - var changeTransferLimit = _marginSettings.IsLive && - request.PaymentType == PaymentType.Transfer && - !IsCrypto(account.BaseAssetId); - - try - { - var transactionId = await _accountManager.UpdateBalanceAsync(account, -Math.Abs(request.Amount), - AccountHistoryType.Withdraw, "Account withdraw", null, changeTransferLimit); - - _operationsLogService.AddLog($"account withdraw {request.PaymentType}", request.ClientId, request.AccountId, request.ToJson(), true.ToJson()); - - return BackendResponse.Ok( - new AccountDepositWithdrawResponse {TransactionId = transactionId}); - - } - catch (Exception e) - { - await _log.WriteErrorAsync(nameof(AccountsBalanceController), "AccountWithdraw", request?.ToJson(), e); - return BackendResponse.Error(e.Message); - } - } - - /// - /// Manually charge client's account. Amount is absolute, i.e. negative value goes for charging. - /// - /// - /// - [Route("chargeManually")] - [HttpPost] - [ProducesResponseType(typeof(BackendResponse), 200)] - public async Task> ChargeManually([FromBody]AccountChargeManuallyRequest request) - { - if (string.IsNullOrEmpty(request?.Reason?.Trim())) - { - return BackendResponse.Error("Reason must be set."); - } - - if (request.Amount == 0) - { - return BackendResponse.Error("Amount must be set."); - } - - var account = _accountsCacheService.Get(request.ClientId, request.AccountId); - - try - { - var transactionId = await _accountManager.UpdateBalanceAsync(account, request.Amount, - AccountHistoryType.Manual, request.Reason, auditLog: request.ToJson()); - - _operationsLogService.AddLog("account charge manually", request.ClientId, request.AccountId, - request.ToJson(), true.ToJson()); - - return BackendResponse.Ok( - new AccountChargeManuallyResponse {TransactionId = transactionId}); - - } - catch (Exception e) - { - await _log.WriteErrorAsync(nameof(AccountsBalanceController), "ChargeManually", request?.ToJson(), e); - return BackendResponse.Error(e.Message); - } - } - - [Route("reset")] - [HttpPost] - [ProducesResponseType(typeof(BackendResponse), 200)] - public async Task> AccountResetDemo([FromBody]AccounResetRequest request) - { - if (_marginSettings.IsLive) - return BackendResponse.Error("Account reset is available only for DEMO accounts"); - - var transactionId = await _accountManager.ResetAccountAsync(request.ClientId, request.AccountId); - - _operationsLogService.AddLog("account reset", request.ClientId, request.AccountId, request.ToJson(), true.ToJson()); - - return BackendResponse.Ok( - new AccountResetResponse {TransactionId = transactionId}); - } - - #region Obsolete - - [Route("~/api/backoffice/marginTradingAccounts/deposit")] - [HttpPost] - [ProducesResponseType(typeof(bool), 200)] - [Obsolete] - public async Task AccountDepositOld([FromBody] AccountDepositWithdrawRequest request) - { - return (await AccountDeposit(request)).IsOk; - } - - [Route("~/api/backoffice/marginTradingAccounts/withdraw")] - [HttpPost] - [ProducesResponseType(typeof(bool), 200)] - [Obsolete] - public async Task AccountWithdrawOld([FromBody] AccountDepositWithdrawRequest request) - { - return (await AccountWithdraw(request)).IsOk; - } - - [Route("~/api/backoffice/marginTradingAccounts/reset")] - [HttpPost] - [ProducesResponseType(typeof(bool), 200)] - [Obsolete] - public async Task AccountResetDemoOld([FromBody] AccounResetRequest request) - { - return (await AccountResetDemo(request)).IsOk; - } - - #endregion - - private bool IsCrypto(string baseAssetId) - { - return baseAssetId == LykkeConstants.BitcoinAssetId - || baseAssetId == LykkeConstants.LykkeAssetId - || baseAssetId == LykkeConstants.EthAssetId - || baseAssetId == LykkeConstants.SolarAssetId; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend/Controllers/AccountsController.cs b/src/MarginTrading.Backend/Controllers/AccountsController.cs new file mode 100644 index 000000000..62bde43cb --- /dev/null +++ b/src/MarginTrading.Backend/Controllers/AccountsController.cs @@ -0,0 +1,198 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MarginTrading.Backend.Contracts.Account; +using MarginTrading.Backend.Contracts.Common; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Mappers; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Services; +using MarginTrading.Backend.Services.Infrastructure; +using MarginTrading.Backend.Services.Mappers; +using MarginTrading.Backend.Services.TradingConditions; +using MarginTrading.Backend.Services.Workflow.Liquidation.Commands; +using MarginTrading.Common.Extensions; +using MarginTrading.Common.Middleware; +using MarginTrading.Common.Services; +using MarginTrading.Contract.BackendContracts; +using MarginTrading.Contract.BackendContracts.AccountsManagement; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using IAccountsApi = MarginTrading.Backend.Contracts.IAccountsApi; + +namespace MarginTrading.Backend.Controllers +{ + [Authorize] + [Route("api/accounts")] + [MiddlewareFilter(typeof(RequestLoggingPipeline))] + public class AccountsController : Controller, IAccountsApi + { + private readonly IAccountsCacheService _accountsCacheService; + private readonly IDateService _dateService; + private readonly AccountManager _accountManager; + private readonly IOrderReader _orderReader; + private readonly TradingConditionsCacheService _tradingConditionsCache; + private readonly ICqrsSender _cqrsSender; + + public AccountsController(IAccountsCacheService accountsCacheService, + IDateService dateService, + AccountManager accountManager, + IOrderReader orderReader, + TradingConditionsCacheService tradingConditionsCache, + ICqrsSender cqrsSender) + { + _accountsCacheService = accountsCacheService; + _dateService = dateService; + _accountManager = accountManager; + _orderReader = orderReader; + _tradingConditionsCache = tradingConditionsCache; + _cqrsSender = cqrsSender; + } + + /// + /// Returns all account stats + /// + [HttpGet] + [Route("stats")] + public Task> GetAllAccountStats() + { + var stats = _accountsCacheService.GetAll(); + + return Task.FromResult(stats.Select(x => x.ConvertToContract()).ToList()); + } + + /// + /// Returns all accounts stats, optionally paginated. Both skip and take must be set or unset. + /// + [HttpGet] + [Route("stats/by-pages")] + public Task> GetAllAccountStatsByPages( + int? skip = null, int? take = null) + { + if ((skip.HasValue && !take.HasValue) || (!skip.HasValue && take.HasValue)) + { + throw new ArgumentOutOfRangeException(nameof(skip), "Both skip and take must be set or unset"); + } + + if (take.HasValue && (take <= 0 || skip < 0)) + { + throw new ArgumentOutOfRangeException(nameof(skip), "Skip must be >= 0, take must be > 0"); + } + + var stats = _accountsCacheService.GetAllByPages(skip, take); + + return Task.FromResult(Convert(stats)); + } + + /// + /// Get accounts depending on active/open orders and positions for particular assets. + /// + /// List of account ids + [HttpPost("/api/accounts")] + public Task> GetAllAccountIdsFiltered([FromBody] ActiveAccountsRequest request) + { + if (request.IsAndClauseApplied != null + && (request.ActiveOrderAssetPairIds == null || request.ActivePositionAssetPairIds == null)) + { + throw new ArgumentException("isAndClauseApplied might be set only if both filters are set", + nameof(request.IsAndClauseApplied)); + } + + List accountIdsByOrders = null; + if (request.ActiveOrderAssetPairIds != null) + { + accountIdsByOrders = _orderReader.GetPending() + .Where(x => request.ActiveOrderAssetPairIds.Count == 0 + || request.ActiveOrderAssetPairIds.Contains(x.AssetPairId)) + .Select(x => x.AccountId) + .Distinct() + .ToList(); + } + + List accountIdsByPositions = null; + if (request.ActivePositionAssetPairIds != null) + { + accountIdsByPositions = _orderReader.GetPositions() + .Where(x => request.ActivePositionAssetPairIds.Count == 0 + || request.ActivePositionAssetPairIds.Contains(x.AssetPairId)) + .Select(x => x.AccountId) + .Distinct() + .ToList(); + } + + if (accountIdsByOrders == null && accountIdsByPositions != null) + { + return Task.FromResult(accountIdsByPositions.OrderBy(x => x).ToList()); + } + + if (accountIdsByOrders != null && accountIdsByPositions == null) + { + return Task.FromResult(accountIdsByOrders.OrderBy(x => x).ToList()); + } + + if (accountIdsByOrders == null && accountIdsByPositions == null) + { + return Task.FromResult(_accountsCacheService.GetAll().Select(x => x.Id).OrderBy(x => x).ToList()); + } + + if (request.IsAndClauseApplied ?? false) + { + return Task.FromResult(accountIdsByOrders.Intersect(accountIdsByPositions) + .OrderBy(x => x).ToList()); + } + + return Task.FromResult(accountIdsByOrders.Concat(accountIdsByPositions) + .Distinct().OrderBy(x => x).ToList()); + } + + /// + /// Returns stats of selected account + /// + /// + [HttpGet] + [Route("stats/{accountId}")] + public Task GetAccountStats(string accountId) + { + var stats = _accountsCacheService.Get(accountId); + + return Task.FromResult(stats.ConvertToContract()); + } + + [HttpPost, Route("resume-liquidation/{accountId}")] + public Task ResumeLiquidation(string accountId, string comment) + { + var account = _accountsCacheService.Get(accountId); + + var liquidation = account.LiquidationOperationId; + + if (string.IsNullOrEmpty(liquidation)) + { + throw new InvalidOperationException("Account is not in liquidation state"); + } + + _cqrsSender.SendCommandToSelf(new ResumeLiquidationInternalCommand + { + OperationId = liquidation, + CreationTime = DateTime.UtcNow, + IsCausedBySpecialLiquidation = false, + Comment = comment + }); + + return Task.CompletedTask; + } + + private PaginatedResponseContract Convert(PaginatedResponse accounts) + { + return new PaginatedResponseContract( + contents: accounts.Contents.Select(x => x.ConvertToContract()).ToList(), + start: accounts.Start, + size: accounts.Size, + totalSize: accounts.TotalSize + ); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend/Controllers/AccountsManagementController.cs b/src/MarginTrading.Backend/Controllers/AccountsManagementController.cs deleted file mode 100644 index afe6e988b..000000000 --- a/src/MarginTrading.Backend/Controllers/AccountsManagementController.cs +++ /dev/null @@ -1,208 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Mappers; -using MarginTrading.Backend.Services; -using MarginTrading.Backend.Services.TradingConditions; -using MarginTrading.Common.Extensions; -using MarginTrading.Common.Middleware; -using MarginTrading.Common.Services; -using MarginTrading.Contract.BackendContracts; -using MarginTrading.Contract.BackendContracts.AccountsManagement; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Swashbuckle.SwaggerGen.Annotations; - -namespace MarginTrading.Backend.Controllers -{ - [Authorize] - [Route("api/[controller]")] - [MiddlewareFilter(typeof(RequestLoggingPipeline))] - public class AccountsManagementController : Controller - { - private readonly IAccountsCacheService _accountsCacheService; - private readonly IDateService _dateService; - private readonly AccountManager _accountManager; - private readonly AccountGroupCacheService _accountGroupCacheService; - private readonly ITradingConditionsCacheService _tradingConditionsCacheService; - - public AccountsManagementController(IAccountsCacheService accountsCacheService, - IDateService dateService, - AccountManager accountManager, - AccountGroupCacheService accountGroupCacheService, - ITradingConditionsCacheService tradingConditionsCacheService) - { - _accountsCacheService = accountsCacheService; - _dateService = dateService; - _accountManager = accountManager; - _accountGroupCacheService = accountGroupCacheService; - _tradingConditionsCacheService = tradingConditionsCacheService; - } - - - /// - /// Get all accounts where (balance + pnl) / Used margin less or equal than threshold value - /// - /// Minimal margin usege level - [ProducesResponseType(typeof(AccountsMarginLevelResponse), 200)] - [Route("marginLevels/{threshold:decimal}")] - [HttpGet] - public AccountsMarginLevelResponse GetAccountsMarginLevels(decimal threshold) - { - var accounts = _accountsCacheService.GetAll() - .Select(a => - new AccountsMarginLevelContract - { - AccountId = a.Id, - ClientId = a.ClientId, - TradingConditionId = a.TradingConditionId, - BaseAssetId = a.BaseAssetId, - Balance = a.Balance, - MarginLevel = a.GetMarginUsageLevel(), - OpenedPositionsCount = a.GetOpenPositionsCount(), - UsedMargin = a.GetUsedMargin(), - TotalBalance = a.GetTotalCapital() - }) - .Where(a => a.MarginLevel <= threshold) - .ToArray(); - - return new AccountsMarginLevelResponse - { - DateTime = _dateService.Now(), - Levels = accounts - }; - } - - /// - /// Close positions for accounts - /// - [ProducesResponseType(typeof(CloseAccountPositionsResponse), 200)] - [Route("closePositions")] - [HttpPost] - public async Task CloseAccountPositions([FromBody] CloseAccountPositionsRequest request) - { - request.RequiredNotNull(nameof(request)); - - var accounts = request.IgnoreMarginLevel - ? null - : _accountsCacheService.GetAll().ToDictionary(a => a.Id); - - var result = new CloseAccountPositionsResponse() - { - Results = new List() - }; - - foreach (var accountId in request.AccountIds) - { - if (!request.IgnoreMarginLevel) - { - var account = accounts[accountId]; - var accountGroup = - _accountGroupCacheService.GetAccountGroup(account.TradingConditionId, account.BaseAssetId); - var accountMarginUsageLevel = account.GetMarginUsageLevel(); - - if (accountMarginUsageLevel > accountGroup.MarginCall) - { - result.Results.Add(new CloseAccountPositionsResult - { - AccountId = accountId, - ClosedPositions = new OrderFullContract[0], - ErrorMessage = - $"Account margin usage level [{accountMarginUsageLevel}] is greater then margin call level [{accountGroup.MarginCall}]" - }); - - continue; - } - } - - var closedOrders = await _accountManager.CloseAccountOrders(accountId); - - result.Results.Add(new CloseAccountPositionsResult - { - AccountId = accountId, - ClosedPositions = closedOrders.Select(o => - { - var orderUpdateType = o.Status == OrderStatus.Closing - ? OrderUpdateType.Closing - : OrderUpdateType.Close; - return o.ToFullContract(orderUpdateType); - }).ToArray() - }); - } - - return result; - } - - /// - /// Sets trading condition for account - /// - /// Returns changed account - [HttpPost] - [Route("tradingCondition")] - [SwaggerOperation("SetTradingCondition")] - public async Task> SetTradingCondition( - [FromBody] SetTradingConditionModel model) - { - if (!_tradingConditionsCacheService.IsTradingConditionExists(model.TradingConditionId)) - { - return MtBackendResponse.Error( - $"No trading condition {model.TradingConditionId} found in cache"); - } - - var tradingCondition = _tradingConditionsCacheService.GetTradingCondition(model.TradingConditionId); - - var account = - await _accountManager.SetTradingCondition(model.ClientId, model.AccountId, model.TradingConditionId); - if (account == null) - { - return MtBackendResponse.Error( - $"Account for client [{model.ClientId}] with id [{model.AccountId}] was not found"); - } - - if (tradingCondition.LegalEntity != account.LegalEntity) - { - return MtBackendResponse.Error( - $"Account for client [{model.ClientId}] with id [{model.AccountId}] has LegalEntity " + - $"[{account.LegalEntity}], but trading condition wit id [{tradingCondition.Id}] has " + - $"LegalEntity [{tradingCondition.LegalEntity}]"); - } - - return MtBackendResponse.Ok(account.ToBackendContract()); - } - - /// - /// Create accounts with requested base asset for all users - /// that already have accounts with requested trading condition - /// - [HttpPost] - [Route("accountGroup/init")] - [SwaggerOperation("InitAccountGroup")] - public async Task>> InitAccountGroup( - [FromBody] InitAccountGroupRequest request) - { - var tradingCondition = _tradingConditionsCacheService.GetTradingCondition(request.TradingConditionId); - - if (tradingCondition == null) - { - return MtBackendResponse>.Error( - $"No trading condition {request.TradingConditionId} found in cache"); - } - - var accountGroup = - _accountGroupCacheService.GetAccountGroup(request.TradingConditionId, request.BaseAssetId); - - if (accountGroup == null) - { - return MtBackendResponse>.Error( - $"No account group {request.TradingConditionId}_{request.BaseAssetId} found in cache"); - } - - var newAccounts = await _accountManager.CreateAccounts(request.TradingConditionId, request.BaseAssetId); - - return MtBackendResponse>.Ok( - newAccounts.Select(a => a.ToBackendContract())); - } - - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend/Controllers/AssetPairsController.cs b/src/MarginTrading.Backend/Controllers/AssetPairsController.cs deleted file mode 100644 index 914747b01..000000000 --- a/src/MarginTrading.Backend/Controllers/AssetPairsController.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Threading.Tasks; -using MarginTrading.Backend.Contracts; -using MarginTrading.Backend.Contracts.AssetPairSettings; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Services.AssetPairs; -using MarginTrading.Common.Middleware; -using MarginTrading.Common.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.Backend.Controllers -{ - [Route("api/[controller]"), Authorize, MiddlewareFilter(typeof(RequestLoggingPipeline))] - public class AssetPairsController : Controller, IAssetPairsEditingApi - { - private readonly IConvertService _convertService; - private readonly IAssetPairsManager _assetPairsManager; - - public AssetPairsController(IConvertService convertService, IAssetPairsManager assetPairsManager) - { - _convertService = convertService; - _assetPairsManager = assetPairsManager; - } - - /// - /// Insert new pair - /// - [HttpPost, Route("{assetPairId}")] - public async Task Insert(string assetPairId, - [FromBody] AssetPairInputContract settings) - { - return Convert(await _assetPairsManager.InsertAssetPair(Convert(assetPairId, settings))); - } - - /// - /// Update existing pair - /// - [HttpPut, Route("{assetPairId}")] - public async Task Update(string assetPairId, - [FromBody] AssetPairInputContract settings) - { - return Convert(await _assetPairsManager.UpdateAssetPair(Convert(assetPairId, settings))); - } - - /// - /// Delete existing pair - /// - [HttpDelete, Route("{assetPairId}")] - public async Task Delete(string assetPairId) - { - return Convert(await _assetPairsManager.DeleteAssetPair(assetPairId)); - } - - private IAssetPair Convert(string assetPairId, AssetPairInputContract settings) - { - return _convertService.ConvertWithConstructorArgs( - settings, new {id = assetPairId}); - } - - private AssetPairContract Convert(IAssetPair settings) - { - return _convertService.Convert(settings); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend/Controllers/BackOfficeController.cs b/src/MarginTrading.Backend/Controllers/BackOfficeController.cs deleted file mode 100644 index 21e37550a..000000000 --- a/src/MarginTrading.Backend/Controllers/BackOfficeController.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.Backend.Attributes; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Mappers; -using MarginTrading.Backend.Core.MatchingEngines; -using MarginTrading.Backend.Models; -using MarginTrading.Backend.Services; -using MarginTrading.Backend.Services.MatchingEngines; -using MarginTrading.Common.Middleware; -using MarginTrading.Contract.BackendContracts; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.Backend.Controllers -{ - [Authorize] - [Route("api/backoffice")] - [MiddlewareFilter(typeof(RequestLoggingPipeline))] - public class BackOfficeController : Controller - { - private readonly IAccountsCacheService _accountsCacheService; - private readonly AccountManager _accountManager; - private readonly MatchingEngineRoutesManager _routesManager; - private readonly IOrderReader _ordersReader; - private readonly IMarginTradingEnablingService _marginTradingEnablingService; - - public BackOfficeController( - IAccountsCacheService accountsCacheService, - AccountManager accountManager, - MatchingEngineRoutesManager routesManager, - IOrderReader ordersReader, - IMarginTradingEnablingService marginTradingEnablingService) - { - _accountsCacheService = accountsCacheService; - - _accountManager = accountManager; - _routesManager = routesManager; - _ordersReader = ordersReader; - _marginTradingEnablingService = marginTradingEnablingService; - } - - - #region Monitoring - - /// - /// Returns summary asset info - /// - /// - /// VolumeLong is a sum of long positions volume - /// - /// VolumeShort is a sum of short positions volume - /// - /// PnL is a sum of all positions PnL - /// - /// Header "api-key" is required - /// - /// Returns summary info by assets - [HttpGet] - [Route("assetsInfo")] - [ProducesResponseType(typeof(List), 200)] - public List GetAssetsInfo() - { - var result = new List(); - var orders = _ordersReader.GetAll().ToList(); - - foreach (var order in orders) - { - var assetInfo = result.FirstOrDefault(item => item.AssetPairId == order.Instrument); - - if (assetInfo == null) - { - result.Add(new SummaryAssetInfo - { - AssetPairId = order.Instrument, - PnL = order.GetFpl(), - VolumeLong = order.GetOrderType() == OrderDirection.Buy ? order.GetMatchedVolume() : 0, - VolumeShort = order.GetOrderType() == OrderDirection.Sell ? order.GetMatchedVolume() : 0 - }); - } - else - { - assetInfo.PnL += order.GetFpl(); - - if (order.GetOrderType() == OrderDirection.Buy) - { - assetInfo.VolumeLong += order.GetMatchedVolume(); - } - else - { - assetInfo.VolumeShort += order.GetMatchedVolume(); - } - } - } - - return result; - } - - /// - /// Returns list of opened positions - /// - /// - /// Returns list of opened positions with matched volume greater or equal provided "volume" parameter - /// - /// Header "api-key" is required - /// - /// Returns opened positions - [HttpGet] - [Route("positionsByVolume")] - [ProducesResponseType(typeof(List), 200)] - public List GetPositionsByVolume([FromQuery]decimal volume) - { - var result = new List(); - var orders = _ordersReader.GetActive(); - - foreach (var order in orders) - { - if (order.GetMatchedVolume() >= volume) - { - result.Add(order.ToBaseContract()); - } - } - - return result; - } - - /// - /// Returns list of pending orders - /// - /// - /// Returns list of pending orders with volume greater or equal provided "volume" parameter - /// - /// Header "api-key" is required - /// - /// Returns pending orders - [HttpGet] - [Route("pendingOrdersByVolume")] - [ProducesResponseType(typeof(List), 200)] - public List GetPendingOrdersByVolume([FromQuery]decimal volume) - { - var result = new List(); - var orders = _ordersReader.GetPending(); - - foreach (var order in orders) - { - if (Math.Abs(order.Volume) >= volume) - { - result.Add(order.ToBaseContract()); - } - } - - return result; - } - - #endregion - - - #region Accounts - - [HttpGet] - [Route("marginTradingAccounts/getall/{clientId}")] - [ProducesResponseType(typeof(List), 200)] - public IActionResult GetAllMarginTradingAccounts(string clientId) - { - var accounts = _accountsCacheService.GetAll(clientId); - return Ok(accounts); - } - - [HttpPost] - [Route("marginTradingAccounts/delete/{clientId}/{accountId}")] - public async Task DeleteMarginTradingAccount(string clientId, string accountId) - { - await _accountManager.DeleteAccountAsync(clientId, accountId); - return Ok(); - } - - [HttpPost] - [Route("marginTradingAccounts/init")] - public async Task InitMarginTradingAccounts([FromBody]InitAccountsRequest request) - { - var accounts = _accountsCacheService.GetAll(request.ClientId); - - if (accounts.Any()) - { - return new InitAccountsResponse { Status = CreateAccountStatus.Available }; - } - - if (string.IsNullOrEmpty(request.TradingConditionsId)) - { - return new InitAccountsResponse - { - Status = CreateAccountStatus.Error, - Message = "Can't create accounts - no trading condition passed" - }; - } - - await _accountManager.CreateDefaultAccounts(request.ClientId, request.TradingConditionsId); - - return new InitAccountsResponse { Status = CreateAccountStatus.Created}; - } - - [HttpPost] - [Route("marginTradingAccounts/add")] - public async Task AddMarginTradingAccount([FromBody]MarginTradingAccount account) - { - await _accountManager.AddAccountAsync(account.ClientId, account.BaseAssetId, account.TradingConditionId); - return Ok(); - } - - #endregion - - - #region Matching engine routes - - [HttpGet] - [Route("routes")] - [ProducesResponseType(typeof(List), 200)] - public IActionResult GetAllRoutes() - { - var routes = _routesManager.GetRoutes(); - return Ok(routes); - } - - [HttpGet] - [Route("routes/{id}")] - [ProducesResponseType(typeof(MatchingEngineRoute), 200)] - public IActionResult GetRoute(string id) - { - var route = _routesManager.GetRouteById(id); - return Ok(route); - } - - [HttpPost] - [Route("routes")] - public async Task AddRoute([FromBody]NewMatchingEngineRouteRequest request) - { - var newRoute = DomainObjectsFactory.CreateRoute(request); - await _routesManager.AddOrReplaceRouteAsync(newRoute); - return Ok(newRoute); - } - - [HttpPut] - [Route("routes/{id}")] - public async Task EditRoute(string id, [FromBody]NewMatchingEngineRouteRequest request) - { - var existingRoute = _routesManager.GetRouteById(id); - if (existingRoute != null) - { - var route = DomainObjectsFactory.CreateRoute(request, id); - await _routesManager.AddOrReplaceRouteAsync(route); - return Ok(_routesManager); - } - else - throw new Exception("MatchingEngine Route not found"); - } - - [HttpDelete] - [Route("routes/{id}")] - public async Task DeleteRoute(string id) - { - await _routesManager.DeleteRouteAsync(id); - return Ok(); - } - - #endregion - - - #region Settings - - [HttpPost] - [Route("settings/enabled/{clientId}")] - [SkipMarginTradingEnabledCheck] - public async Task SetMarginTradingIsEnabled(string clientId, [FromBody]bool enabled) - { - await _marginTradingEnablingService.SetMarginTradingEnabled(clientId, enabled); - return Ok(); - } - - #endregion - - } -} diff --git a/src/MarginTrading.Backend/Controllers/IsAliveController.cs b/src/MarginTrading.Backend/Controllers/IsAliveController.cs index c1c9b98f9..4e5874539 100644 --- a/src/MarginTrading.Backend/Controllers/IsAliveController.cs +++ b/src/MarginTrading.Backend/Controllers/IsAliveController.cs @@ -1,5 +1,6 @@ -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.MatchingEngines; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using MarginTrading.Backend.Core.Settings; using MarginTrading.Common.Services; using MarginTrading.Contract.BackendContracts; @@ -10,35 +11,25 @@ namespace MarginTrading.Backend.Controllers [Route("api/[controller]")] public class IsAliveController : Controller { - private readonly IMarketMakerMatchingEngine _matchingEngine; - private readonly ITradingEngine _tradingEngine; - private readonly MarginSettings _settings; + private readonly MarginTradingSettings _settings; private readonly IDateService _dateService; public IsAliveController( - IMarketMakerMatchingEngine matchingEngine, - ITradingEngine tradingEngine, - MarginSettings settings, + MarginTradingSettings settings, IDateService dateService) { - _matchingEngine = matchingEngine; - _tradingEngine = tradingEngine; _settings = settings; _dateService = dateService; } + [HttpGet] public IsAliveResponse Get() { - var matchingEngineAlive = _matchingEngine.PingLock(); - var tradingEngineAlive = _tradingEngine.PingLock(); - return new IsAliveResponse { - MatchingEngineAlive = matchingEngineAlive, - TradingEngineAlive = tradingEngineAlive, Version = Microsoft.Extensions.PlatformAbstractions.PlatformServices.Default.Application.ApplicationVersion, - Env = _settings.IsLive ? "Live" : "Demo", + Env = _settings.Env, ServerTime = _dateService.Now() }; } diff --git a/src/MarginTrading.Backend/Controllers/MtController.cs b/src/MarginTrading.Backend/Controllers/MtController.cs deleted file mode 100644 index 96b44fd1b..000000000 --- a/src/MarginTrading.Backend/Controllers/MtController.cs +++ /dev/null @@ -1,480 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Common; -using Common.Log; -using MarginTrading.Backend.Attributes; -using MarginTrading.Backend.Contracts; -using MarginTrading.Backend.Contracts.Common; -using MarginTrading.Backend.Contracts.Trading; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Exceptions; -using MarginTrading.Backend.Core.Mappers; -using MarginTrading.Backend.Core.MatchingEngines; -using MarginTrading.Backend.Core.Repositories; -using MarginTrading.Backend.Core.Settings; -using MarginTrading.Backend.Services; -using MarginTrading.Backend.Services.AssetPairs; -using MarginTrading.Backend.Services.TradingConditions; -using MarginTrading.Common.Middleware; -using MarginTrading.Common.Services; -using MarginTrading.Contract.BackendContracts; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.Backend.Controllers -{ - [Authorize] - [Route("api/mt")] - public class MtController : Controller, ITradingApi - { - private readonly IMarginTradingAccountHistoryRepository _accountsHistoryRepository; - private readonly IMarginTradingOrdersHistoryRepository _ordersHistoryRepository; - private readonly IMicrographCacheService _micrographCacheService; - private readonly IAccountAssetsCacheService _accountAssetsCacheService; - private readonly IAssetPairsCache _assetPairsCache; - private readonly IMarketMakerMatchingEngine _matchingEngine; - private readonly ITradingEngine _tradingEngine; - private readonly IAccountsCacheService _accountsCacheService; - private readonly IMarginTradingOperationsLogService _operationsLogService; - private readonly IConsole _consoleWriter; - private readonly OrdersCache _ordersCache; - private readonly MarginSettings _marginSettings; - private readonly AccountManager _accountManager; - private readonly IAssetPairDayOffService _assetDayOffService; - private readonly IQuoteCacheService _quoteCacheService; - private readonly IIdentityGenerator _identityGenerator; - - public MtController( - IMarginTradingAccountHistoryRepository accountsHistoryRepository, - IMarginTradingOrdersHistoryRepository ordersHistoryRepository, - IMicrographCacheService micrographCacheService, - IAccountAssetsCacheService accountAssetsCacheService, - IAssetPairsCache assetPairsCache, - IMarketMakerMatchingEngine matchingEngine, - ITradingEngine tradingEngine, - IAccountsCacheService accountsCacheService, - IMarginTradingOperationsLogService operationsLogService, - IConsole consoleWriter, - OrdersCache ordersCache, - MarginSettings marginSettings, - AccountManager accountManager, - IAssetPairDayOffService assetDayOffService, - IQuoteCacheService quoteCacheService, - IIdentityGenerator identityGenerator) - { - _accountsHistoryRepository = accountsHistoryRepository; - _ordersHistoryRepository = ordersHistoryRepository; - _micrographCacheService = micrographCacheService; - _accountAssetsCacheService = accountAssetsCacheService; - _assetPairsCache = assetPairsCache; - _matchingEngine = matchingEngine; - _tradingEngine = tradingEngine; - _accountsCacheService = accountsCacheService; - _operationsLogService = operationsLogService; - _consoleWriter = consoleWriter; - _ordersCache = ordersCache; - _marginSettings = marginSettings; - _accountManager = accountManager; - _assetDayOffService = assetDayOffService; - _quoteCacheService = quoteCacheService; - _identityGenerator = identityGenerator; - } - - #region Init data - - [Route("init.data")] - [HttpPost] - public async Task InitData([FromBody]ClientIdBackendRequest request) - { - if (string.IsNullOrWhiteSpace(request?.ClientId)) - { - throw new ArgumentNullException(nameof(ClientIdBackendRequest.ClientId)); - } - - var accounts = _accountsCacheService.GetAll(request.ClientId).ToArray(); - - if (accounts.Length == 0 && !_marginSettings.IsLive) - { - accounts = await _accountManager.CreateDefaultAccounts(request.ClientId); - } - - if (accounts.Length == 0) - return InitDataBackendResponse.CreateEmpty(); - - var assets = _accountAssetsCacheService.GetClientAssets(accounts); - - var result = BackendContractFactory.CreateInitDataBackendResponse(accounts, assets, _marginSettings.IsLive); - result.IsLive = _marginSettings.IsLive; - - return result; - } - - /// - /// uses in BoxOptions app only - /// - /// - [Route("init.chartdata")] - [HttpPost] - public InitChartDataBackendResponse InitChardData() - { - var chartData = _micrographCacheService.GetGraphData(); - return BackendContractFactory.CreateInitChartDataBackendResponse(chartData); - } - - [Route("init.accounts")] - [HttpPost] - public MarginTradingAccountBackendContract[] InitAccounts([FromBody]ClientIdBackendRequest request) - { - var accounts = _accountsCacheService.GetAll(request.ClientId).ToArray(); - - var result = accounts.Select(item => item.ToFullBackendContract(_marginSettings.IsLive)).ToArray(); - - return result; - } - - [Route("init.accountinstruments")] - [HttpPost] - public InitAccountInstrumentsBackendResponse AccountInstruments([FromBody]ClientIdBackendRequest request) - { - var accounts = _accountsCacheService.GetAll(request.ClientId).ToArray(); - - if (accounts.Length == 0) - return InitAccountInstrumentsBackendResponse.CreateEmpty(); - - var accountAssets = _accountAssetsCacheService.GetClientAssets(accounts); - var result = BackendContractFactory.CreateInitAccountInstrumentsBackendResponse(accountAssets); - - return result; - } - - [Route("init.graph")] - [HttpPost] - [SkipMarginTradingEnabledCheck] - public InitChartDataBackendResponse InitGraph([FromBody]InitChartDataBackendRequest request) - { - var chartData = _micrographCacheService.GetGraphData(); - - if (request?.AssetIds?.Length > 0) - { - chartData = chartData.Where(d => request.AssetIds.Contains(d.Key)) - .ToDictionary(k => k.Key, v => v.Value); - } - - return BackendContractFactory.CreateInitChartDataBackendResponse(chartData); - } - - [Route("init.availableassets")] - [HttpPost] - public string[] InitAvailableAssets([FromBody]ClientIdBackendRequest request) - { - return GetAvailableAssets(request.ClientId).ToArray(); - } - - [Route("init.assets")] - [HttpPost] - public AssetPairBackendContract[] InitAssets([FromBody]ClientIdBackendRequest request) - { - var availableAssets = GetAvailableAssets(request.ClientId).ToHashSet(); - - var instruments = _assetPairsCache.GetAll(); - - return instruments.Where(a => availableAssets.Contains(a.Id)) - .Select(item => item.ToBackendContract()).ToArray(); - } - - private IEnumerable GetAvailableAssets(string clientId) - { - var result = new List(); - - var accounts = _accountsCacheService.GetAll(clientId); - - foreach (var account in accounts) - { - result.AddRange(_accountAssetsCacheService - .GetAccountAssets(account.TradingConditionId, account.BaseAssetId).Select(a => a.Instrument)); - } - - return result.Distinct(); - } - - [Route("init.prices")] - [HttpPost] - [SkipMarginTradingEnabledCheck] - public Dictionary InitPrices([FromBody]InitPricesBackendRequest request) - { - IEnumerable> allQuotes = _quoteCacheService.GetAllQuotes(); - - if (request.AssetIds != null && request.AssetIds.Any()) - { - allQuotes = allQuotes.Where(q => request.AssetIds.Contains(q.Key)); - } - - return allQuotes.ToDictionary(q => q.Key, q => q.Value.ToBackendContract()); - } - - #endregion - - #region Account - - [Route("account.history")] - [HttpPost] - public async Task GetAccountHistory([FromBody]AccountHistoryBackendRequest request) - { - var clientAccountIds = string.IsNullOrEmpty(request.AccountId) - ? _accountsCacheService.GetAll(request.ClientId).Select(item => item.Id).ToArray() - : new[] { request.AccountId }; - - var accounts = (await _accountsHistoryRepository.GetAsync(clientAccountIds, request.From, request.To)) - .Where(item => item.Type != AccountHistoryType.OrderClosed); - - var orders = (await _ordersHistoryRepository.GetHistoryAsync(request.ClientId, clientAccountIds, request.From, request.To)) - .Where(item => item.Status != OrderStatus.Rejected); - - var openPositions = _ordersCache.ActiveOrders.GetOrdersByAccountIds(clientAccountIds).ToList(); - - var result = BackendContractFactory.CreateAccountHistoryBackendResponse(accounts, openPositions, orders); - - return result; - } - - [Route("account.history.new")] - [HttpPost] - public async Task GetHistory([FromBody]AccountHistoryBackendRequest request) - { - var clientAccountIds = string.IsNullOrEmpty(request.AccountId) - ? _accountsCacheService.GetAll(request.ClientId).Select(item => item.Id).ToArray() - : new[] { request.AccountId }; - - var accounts = (await _accountsHistoryRepository.GetAsync(clientAccountIds, request.From, request.To)) - .Where(item => item.Type != AccountHistoryType.OrderClosed); - - var openOrders = _ordersCache.ActiveOrders.GetOrdersByAccountIds(clientAccountIds); - - var historyOrders = (await _ordersHistoryRepository.GetHistoryAsync(request.ClientId, clientAccountIds, request.From, request.To)) - .Where(item => item.Status != OrderStatus.Rejected); - - var result = BackendContractFactory.CreateAccountNewHistoryBackendResponse(accounts, openOrders, historyOrders); - - return result; - } - - #endregion - - #region Order - - [Route("order.place")] - [MiddlewareFilter(typeof(RequestLoggingPipeline))] - [HttpPost] - public async Task PlaceOrder([FromBody]OpenOrderBackendRequest request) - { - var code = await _identityGenerator.GenerateIdAsync(nameof(Order)); - - var order = new Order - { - Id = Guid.NewGuid().ToString("N"), - Code = code, - CreateDate = DateTime.UtcNow, - ClientId = request.ClientId, - AccountId = request.Order.AccountId, - Instrument = request.Order.Instrument, - Volume = request.Order.Volume, - ExpectedOpenPrice = request.Order.ExpectedOpenPrice, - TakeProfit = request.Order.TakeProfit, - StopLoss = request.Order.StopLoss - }; - - var placedOrder = await _tradingEngine.PlaceOrderAsync(order); - - var result = BackendContractFactory.CreateOpenOrderBackendResponse(placedOrder); - - _consoleWriter.WriteLine($"action order.place for clientId = {request.ClientId}"); - _operationsLogService.AddLog("action order.place", request.ClientId, request.Order.AccountId, request.ToJson(), result.ToJson()); - - return result; - } - - [Route("order.close")] - [MiddlewareFilter(typeof(RequestLoggingPipeline))] - [HttpPost] - public async Task> CloseOrder([FromBody] CloseOrderBackendRequest request) - { - if (!_ordersCache.ActiveOrders.TryGetOrderById(request.OrderId, out var order)) - { - return BackendResponse.Error("Order not found"); - } - - if (_assetDayOffService.IsDayOff(order.Instrument)) - { - return BackendResponse.Error("Trades for instrument are not available"); - } - - if (order.ClientId != request.ClientId || order.AccountId != request.AccountId) - { - return BackendResponse.Error("Order is not available for user"); - } - - if (request.IsForcedByBroker && string.IsNullOrEmpty(request.Comment)) - { - return BackendResponse.Error("For operation forced by broker, comment is mandatory"); - } - - var reason = request.IsForcedByBroker ? OrderCloseReason.ClosedByBroker : OrderCloseReason.Close; - - order = await _tradingEngine.CloseActiveOrderAsync(request.OrderId, reason, request.Comment); - - var result = new BackendResponse - { - Result = order.Status == OrderStatus.Closed || order.Status == OrderStatus.Closing, - Message = order.CloseRejectReasonText - }; - - _consoleWriter.WriteLine( - $"action order.close for clientId = {request.ClientId}, orderId = {request.OrderId}"); - _operationsLogService.AddLog("action order.close", request.ClientId, order.AccountId, request.ToJson(), - result.ToJson()); - - return result; - } - - [Route("order.cancel")] - [MiddlewareFilter(typeof(RequestLoggingPipeline))] - [HttpPost] - public async Task> CancelOrder([FromBody] CloseOrderBackendRequest request) - { - if (!_ordersCache.WaitingForExecutionOrders.TryGetOrderById(request.OrderId, out var order)) - { - return BackendResponse.Error("Order not found"); - } - - if (_assetDayOffService.IsDayOff(order.Instrument)) - { - return BackendResponse.Error("Trades for instrument are not available"); - } - - if (order.ClientId != request.ClientId || order.AccountId != request.AccountId) - { - - return BackendResponse.Error("Order is not available for user"); - } - - if (request.IsForcedByBroker && string.IsNullOrEmpty(request.Comment)) - { - return BackendResponse.Error("For operation forced by broker, comment is mandatory"); - } - - var reason = request.IsForcedByBroker ? OrderCloseReason.CanceledByBroker : OrderCloseReason.Canceled; - - _tradingEngine.CancelPendingOrder(order.Id, reason, request.Comment); - - var result = new BackendResponse {Result = true}; - - _consoleWriter.WriteLine( - $"action order.cancel for clientId = {request.ClientId}, orderId = {request.OrderId}"); - _operationsLogService.AddLog("action order.cancel", request.ClientId, order.AccountId, request.ToJson(), - result.ToJson()); - - return result; - } - - [Route("order.list")] - [HttpPost] - public OrderBackendContract[] GetOpenPositions([FromBody]ClientIdBackendRequest request) - { - var accountIds = _accountsCacheService.GetAll(request.ClientId).Select(item => item.Id).ToArray(); - - var positions = _ordersCache.ActiveOrders.GetOrdersByAccountIds(accountIds).Select(item => item.ToBackendContract()).ToList(); - var orders = _ordersCache.WaitingForExecutionOrders.GetOrdersByAccountIds(accountIds).Select(item => item.ToBackendContract()).ToList(); - - positions.AddRange(orders); - var result = positions.ToArray(); - - return result; - } - - [Route("order.account.list")] - [HttpPost] - public OrderBackendContract[] GetAccountOpenPositions([FromBody]AccountClientIdBackendRequest request) - { - var account = _accountsCacheService.Get(request.ClientId, request.AccountId); - - var positions = _ordersCache.ActiveOrders.GetOrdersByAccountIds(account.Id).Select(item => item.ToBackendContract()).ToList(); - var orders = _ordersCache.WaitingForExecutionOrders.GetOrdersByAccountIds(account.Id).Select(item => item.ToBackendContract()).ToList(); - - positions.AddRange(orders); - var result = positions.ToArray(); - - return result; - } - - [Route("order.positions")] - [HttpPost] - public ClientOrdersBackendResponse GetClientOrders([FromBody]ClientIdBackendRequest request) - { - var accountIds = _accountsCacheService.GetAll(request.ClientId).Select(item => item.Id).ToArray(); - - var positions = _ordersCache.ActiveOrders.GetOrdersByAccountIds(accountIds).ToList(); - var orders = _ordersCache.WaitingForExecutionOrders.GetOrdersByAccountIds(accountIds).ToList(); - - var result = BackendContractFactory.CreateClientOrdersBackendResponse(positions, orders); - - return result; - } - - [Route("order.changeLimits")] - [MiddlewareFilter(typeof(RequestLoggingPipeline))] - [HttpPost] - public MtBackendResponse ChangeOrderLimits([FromBody]ChangeOrderLimitsBackendRequest request) - { - if (!_ordersCache.TryGetOrderById(request.OrderId, out var order)) - { - return new MtBackendResponse {Message = "Order not found"}; - } - - if (_assetDayOffService.IsDayOff(order.Instrument)) - { - return new MtBackendResponse { Message = "Trades for instrument are not available" }; - } - - try - { - _tradingEngine.ChangeOrderLimits(request.OrderId, request.StopLoss, request.TakeProfit, - request.ExpectedOpenPrice); - } - catch (ValidateOrderException ex) - { - return new MtBackendResponse {Result = false, Message = ex.Message}; - } - - var result = new MtBackendResponse {Result = true}; - - _consoleWriter.WriteLine($"action order.changeLimits for clientId = {request.ClientId}, orderId = {request.OrderId}"); - _operationsLogService.AddLog("action order.changeLimits", request.ClientId, order.AccountId, request.ToJson(), result.ToJson()); - - return result; - } - - #endregion - - #region Orderbook - - [Route("orderbooks")] - [HttpPost] - public OrderbooksBackendResponse GetOrderBooks([FromBody] OrderbooksBackendRequest request) - { - //TODO: handle different MEs - return BackendContractFactory.CreateOrderbooksBackendResponse(_matchingEngine.GetOrderBook(request.Instrument)); - } - - #endregion - - [Route("ping")] - [HttpPost] - public MtBackendResponse Ping() - { - return new MtBackendResponse { Result = $"[{DateTime.UtcNow:u}] Ping!" }; - } - - } -} diff --git a/src/MarginTrading.Backend/Controllers/OrdersController.cs b/src/MarginTrading.Backend/Controllers/OrdersController.cs new file mode 100644 index 000000000..23f2a19cc --- /dev/null +++ b/src/MarginTrading.Backend/Controllers/OrdersController.cs @@ -0,0 +1,337 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Common; +using Common.Log; +using MarginTrading.Backend.Contracts; +using MarginTrading.Backend.Contracts.Common; +using MarginTrading.Backend.Contracts.Events; +using MarginTrading.Backend.Contracts.Orders; +using MarginTrading.Backend.Contracts.TradeMonitoring; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Exceptions; +using MarginTrading.Backend.Core.Helpers; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Trading; +using MarginTrading.Backend.Filters; +using MarginTrading.Backend.Services; +using MarginTrading.Backend.Services.AssetPairs; +using MarginTrading.Backend.Services.Infrastructure; +using MarginTrading.Backend.Services.Mappers; +using MarginTrading.Common.Extensions; +using MarginTrading.Common.Middleware; +using MarginTrading.Common.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace MarginTrading.Backend.Controllers +{ + [Authorize] + [Route("api/orders")] + public class OrdersController : Controller, IOrdersApi + { + private readonly IAssetPairsCache _assetPairsCache; + private readonly ITradingEngine _tradingEngine; + private readonly IAccountsCacheService _accountsCacheService; + private readonly IOperationsLogService _operationsLogService; + private readonly ILog _log; + private readonly OrdersCache _ordersCache; + private readonly IAssetPairDayOffService _assetDayOffService; + private readonly IDateService _dateService; + private readonly IValidateOrderService _validateOrderService; + private readonly IIdentityGenerator _identityGenerator; + private readonly ICqrsSender _cqrsSender; + + public OrdersController(IAssetPairsCache assetPairsCache, + ITradingEngine tradingEngine, + IAccountsCacheService accountsCacheService, + IOperationsLogService operationsLogService, + ILog log, + OrdersCache ordersCache, + IAssetPairDayOffService assetDayOffService, + IDateService dateService, + IValidateOrderService validateOrderService, + IIdentityGenerator identityGenerator, + ICqrsSender cqrsSender) + { + _assetPairsCache = assetPairsCache; + _tradingEngine = tradingEngine; + _accountsCacheService = accountsCacheService; + _operationsLogService = operationsLogService; + _log = log; + _ordersCache = ordersCache; + _assetDayOffService = assetDayOffService; + _dateService = dateService; + _validateOrderService = validateOrderService; + _identityGenerator = identityGenerator; + _cqrsSender = cqrsSender; + } + + /// + /// Place new order + /// + /// Order model + /// Order Id + [Route("")] + [MiddlewareFilter(typeof(RequestLoggingPipeline))] + [ServiceFilter(typeof(MarginTradingEnabledFilter))] + [HttpPost] + public async Task PlaceAsync([FromBody] OrderPlaceRequest request) + { + var (baseOrder, relatedOrders) = (default(Order), default(List)); + try + { + (baseOrder, relatedOrders) = await _validateOrderService.ValidateRequestAndCreateOrders(request); + } + catch (ValidateOrderException exception) + { + _cqrsSender.PublishEvent(new OrderPlacementRejectedEvent + { + CorrelationId = request.CorrelationId ?? _identityGenerator.GenerateGuid(), + EventTimestamp = _dateService.Now(), + OrderPlaceRequest = request, + RejectReason = exception.RejectReason.ToType(), + RejectReasonText = exception.Message, + }); + throw; + } + + var placedOrder = await _tradingEngine.PlaceOrderAsync(baseOrder); + + _operationsLogService.AddLog("action order.place", request.AccountId, request.ToJson(), + placedOrder.ToJson()); + + if (placedOrder.Status == OrderStatus.Rejected) + { + throw new Exception($"Order {placedOrder.Id} from account {placedOrder.AccountId} for instrument {placedOrder.AssetPairId} is rejected: {placedOrder.RejectReason} ({placedOrder.RejectReasonText}). Comment: {placedOrder.Comment}."); + } + + foreach (var order in relatedOrders) + { + var placedRelatedOrder = await _tradingEngine.PlaceOrderAsync(order); + + _operationsLogService.AddLog("action related.order.place", request.AccountId, request.ToJson(), + placedRelatedOrder.ToJson()); + } + + return placedOrder.Id; + } + + /// + /// Cancel existing order + /// + /// Id of order to cancel + /// Additional cancellation info + [Route("{orderId}")] + [MiddlewareFilter(typeof(RequestLoggingPipeline))] + [ServiceFilter(typeof(MarginTradingEnabledFilter))] + [HttpDelete] + public Task CancelAsync(string orderId, [FromBody] OrderCancelRequest request = null) + { + if (!_ordersCache.TryGetOrderById(orderId, out var order)) + throw new InvalidOperationException("Order not found"); + + var correlationId = string.IsNullOrWhiteSpace(request?.CorrelationId) + ? _identityGenerator.GenerateGuid() + : request.CorrelationId; + + var reason = request?.Originator == OriginatorTypeContract.System + ? OrderCancellationReason.CorporateAction + : OrderCancellationReason.None; + + var canceledOrder = _tradingEngine.CancelPendingOrder(order.Id, request?.AdditionalInfo, + correlationId, request?.Comment, reason); + + _operationsLogService.AddLog("action order.cancel", order.AccountId, request?.ToJson(), + canceledOrder.ToJson()); + + return Task.CompletedTask; + } + + /// + /// Close group of orders by accountId, assetPairId and direction. + /// + /// Mandatory + /// Optional + /// Optional + /// Optional, should orders, linked to positions, to be canceled + /// Optional + /// Dictionary of failed to close orderIds with exception message + /// + /// + [Route("close-group")]//todo: to be deleted + [Route("cancel-group")] + [MiddlewareFilter(typeof(RequestLoggingPipeline))] + [ServiceFilter(typeof(MarginTradingEnabledFilter))] + [HttpDelete] + public async Task> CancelGroupAsync([FromQuery] string accountId, + [FromQuery] string assetPairId = null, + [FromQuery] OrderDirectionContract? direction = null, + [FromQuery] bool includeLinkedToPositions = false, + [FromBody] OrderCancelRequest request = null) + { + accountId.RequiredNotNullOrWhiteSpace(nameof(accountId)); + + var failedOrderIds = new Dictionary(); + + foreach (var order in _ordersCache.GetPending() + .Where(x => x.AccountId == accountId + && (string.IsNullOrEmpty(assetPairId) || x.AssetPairId == assetPairId) + && (direction == null || x.Direction == direction.ToType()) + && (includeLinkedToPositions || string.IsNullOrEmpty(x.ParentPositionId)))) + { + try + { + await CancelAsync(order.Id, request); + } + catch (Exception exception) + { + await _log.WriteWarningAsync(nameof(OrdersController), nameof(CancelGroupAsync), + "Failed to cancel order [{order.Id}]", exception); + failedOrderIds.Add(order.Id, exception.Message); + } + } + + return failedOrderIds; + } + + /// + /// Change existing order + /// + /// Id of order to change + /// Values to change + /// + [Route("{orderId}")] + [MiddlewareFilter(typeof(RequestLoggingPipeline))] + [ServiceFilter(typeof(MarginTradingEnabledFilter))] + [HttpPut] + public async Task ChangeAsync(string orderId, [FromBody] OrderChangeRequest request) + { + if (!_ordersCache.TryGetOrderById(orderId, out var order)) + { + throw new InvalidOperationException("Order not found"); + } + + try + { + var originator = GetOriginator(request.Originator); + + var correlationId = string.IsNullOrWhiteSpace(request.CorrelationId) + ? _identityGenerator.GenerateGuid() + : request.CorrelationId; + + await _tradingEngine.ChangeOrderAsync(order.Id, request.Price, request.Validity, originator, + request.AdditionalInfo, correlationId, request.ForceOpen); + } + catch (ValidateOrderException ex) + { + throw new InvalidOperationException(ex.Message); + } + + _operationsLogService.AddLog("action order.changeLimits", order.AccountId, + new { orderId = orderId, request = request.ToJson() }.ToJson(), ""); + } + + /// + /// Get order by id + /// + [HttpGet, Route("{orderId}")] + public Task GetAsync(string orderId) + { + return _ordersCache.TryGetOrderById(orderId, out var order) + ? Task.FromResult(order.ConvertToContract(_ordersCache)) + : Task.FromResult(null); + } + + /// + /// Get open orders with optional filtering + /// + [HttpGet, Route("")] + public Task> ListAsync([FromQuery] string accountId = null, + [FromQuery] string assetPairId = null, [FromQuery] string parentPositionId = null, + [FromQuery] string parentOrderId = null) + { + // do not call get by account, it's slower for single account + IEnumerable orders = _ordersCache.GetAllOrders(); + + if (!string.IsNullOrWhiteSpace(accountId)) + orders = orders.Where(o => o.AccountId == accountId); + + if (!string.IsNullOrWhiteSpace(assetPairId)) + orders = orders.Where(o => o.AssetPairId == assetPairId); + + if (!string.IsNullOrWhiteSpace(parentPositionId)) + orders = orders.Where(o => o.ParentPositionId == parentPositionId); + + if (!string.IsNullOrWhiteSpace(parentOrderId)) + orders = orders.Where(o => o.ParentOrderId == parentOrderId); + + return Task.FromResult(orders.Select(o => o.ConvertToContract(_ordersCache)).ToList()); + } + + /// + /// Get open orders with optional filtering and pagination. Sorted descending by default. + /// + [HttpGet, Route("by-pages")] + public Task> ListAsyncByPages( + [FromQuery] string accountId = null, + [FromQuery] string assetPairId = null, [FromQuery] string parentPositionId = null, + [FromQuery] string parentOrderId = null, + [FromQuery] int? skip = null, [FromQuery] int? take = null, + [FromQuery] string order = LykkeConstants.DescendingOrder) + { + if ((skip.HasValue && !take.HasValue) || (!skip.HasValue && take.HasValue)) + { + throw new ArgumentOutOfRangeException(nameof(skip), "Both skip and take must be set or unset"); + } + + if (take.HasValue && (take <= 0 || skip < 0)) + { + throw new ArgumentOutOfRangeException(nameof(skip), "Skip must be >= 0, take must be > 0"); + } + + var orders = _ordersCache.GetAllOrders().AsEnumerable(); + + if (!string.IsNullOrWhiteSpace(accountId)) + orders = orders.Where(o => o.AccountId == accountId); + + if (!string.IsNullOrWhiteSpace(assetPairId)) + orders = orders.Where(o => o.AssetPairId == assetPairId); + + if (!string.IsNullOrWhiteSpace(parentPositionId)) + orders = orders.Where(o => o.ParentPositionId == parentPositionId); + + if (!string.IsNullOrWhiteSpace(parentOrderId)) + orders = orders.Where(o => o.ParentOrderId == parentOrderId); + + var orderList = (order == LykkeConstants.AscendingOrder + ? orders.OrderBy(x => x.Created) + : orders.OrderByDescending(x => x.Created)) + .ToList(); + var filtered = (take == null ? orderList : orderList.Skip(skip.Value)) + .Take(PaginationHelper.GetTake(take)).ToList(); + + return Task.FromResult(new PaginatedResponseContract( + contents: filtered.Select(o => o.ConvertToContract(_ordersCache)).ToList(), + start: skip ?? 0, + size: filtered.Count, + totalSize: orderList.Count + )); + } + + private OriginatorType GetOriginator(OriginatorTypeContract? originator) + { + if (originator == null || originator.Value == default(OriginatorTypeContract)) + { + return OriginatorType.Investor; + } + + return originator.ToType(); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend/Controllers/OvernightSwapController.cs b/src/MarginTrading.Backend/Controllers/OvernightSwapController.cs deleted file mode 100644 index 99c5d4a90..000000000 --- a/src/MarginTrading.Backend/Controllers/OvernightSwapController.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Repositories; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.Backend.Controllers -{ - [Authorize] - [Route("api/overnightswap")] - public class OvernightSwapController : Controller - { - private readonly IOvernightSwapHistoryRepository _overnightSwapHistoryRepository; - - public OvernightSwapController(IOvernightSwapHistoryRepository overnightSwapHistoryRepository) - { - _overnightSwapHistoryRepository = overnightSwapHistoryRepository; - } - - [Route("history")] - [ProducesResponseType(typeof(IEnumerable), 200)] - [ProducesResponseType(400)] - [HttpPost] - public async Task GetOvernightSwapHistory([FromQuery] DateTime from, [FromQuery] DateTime to) - { - var data = await _overnightSwapHistoryRepository.GetAsync(from, to); - - return Ok(data); - } - - /// - /// Invoke recalculation of account/instrument/direction order packages that were not calculated successfully last time. - /// - /// - [Route("recalc.failed.orders")] - [ProducesResponseType(200)] - [ProducesResponseType(400)] - [HttpPost] - public IActionResult RecalculateFailedOrders() - { - MtServiceLocator.OvernightSwapService.CalculateAndChargeSwaps(); - - return Ok(); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend/Controllers/PositionsController.cs b/src/MarginTrading.Backend/Controllers/PositionsController.cs new file mode 100644 index 000000000..8fae7ce62 --- /dev/null +++ b/src/MarginTrading.Backend/Controllers/PositionsController.cs @@ -0,0 +1,257 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Common; +using Common.Log; +using JetBrains.Annotations; +using MarginTrading.Backend.Contracts; +using MarginTrading.Backend.Contracts.Common; +using MarginTrading.Backend.Contracts.Orders; +using MarginTrading.Backend.Contracts.Positions; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Helpers; +using MarginTrading.Backend.Core.MatchingEngines; +using MarginTrading.Backend.Core.Orders; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Core.Trading; +using MarginTrading.Backend.Filters; +using MarginTrading.Backend.Services; +using MarginTrading.Backend.Services.AssetPairs; +using MarginTrading.Backend.Services.Infrastructure; +using MarginTrading.Backend.Services.Mappers; +using MarginTrading.Backend.Services.Workflow.Liquidation.Commands; +using MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Commands; +using MarginTrading.Common.Extensions; +using MarginTrading.Common.Middleware; +using MarginTrading.Common.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using MoreLinq; + +namespace MarginTrading.Backend.Controllers +{ + [Authorize] + [Route("api/positions")] + public class PositionsController : Controller, IPositionsApi + { + private readonly ITradingEngine _tradingEngine; + private readonly IOperationsLogService _operationsLogService; + private readonly ILog _log; + private readonly OrdersCache _ordersCache; + private readonly IAssetPairDayOffService _assetDayOffService; + private readonly IIdentityGenerator _identityGenerator; + private readonly ICqrsSender _cqrsSender; + private readonly IDateService _dateService; + + public PositionsController( + ITradingEngine tradingEngine, + IOperationsLogService operationsLogService, + ILog log, + OrdersCache ordersCache, + IAssetPairDayOffService assetDayOffService, + IIdentityGenerator identityGenerator, + ICqrsSender cqrsSender, + IDateService dateService) + { + _tradingEngine = tradingEngine; + _operationsLogService = operationsLogService; + _log = log; + _ordersCache = ordersCache; + _assetDayOffService = assetDayOffService; + _identityGenerator = identityGenerator; + _cqrsSender = cqrsSender; + _dateService = dateService; + } + + /// + /// Close opened position + /// + /// Id of position + /// Additional info for close + [Route("{positionId}")] + [MiddlewareFilter(typeof(RequestLoggingPipeline))] + [ServiceFilter(typeof(MarginTradingEnabledFilter))] + [HttpDelete] + public async Task CloseAsync([CanBeNull] [FromRoute] string positionId, + [FromBody] PositionCloseRequest request = null) + { + if (!_ordersCache.Positions.TryGetPositionById(positionId, out var position)) + { + throw new InvalidOperationException("Position not found"); + } + + ValidateDayOff(position.AssetPairId); + + var originator = GetOriginator(request?.Originator); + + var correlationId = request?.CorrelationId ?? _identityGenerator.GenerateGuid(); + + var closeResult = await _tradingEngine.ClosePositionsAsync( + new PositionsCloseData( + new List {position}, + position.AccountId, + position.AssetPairId, + position.OpenMatchingEngineId, + position.ExternalProviderId, + originator, + request?.AdditionalInfo, + correlationId, + position.EquivalentAsset), true); + + _operationsLogService.AddLog("action order.close", position.AccountId, request?.ToJson(), + closeResult.ToJson()); + + return new PositionCloseResponse + { + PositionId = positionId, + OrderId = closeResult.order?.Id, + Result = closeResult.result.ToType() + }; + } + + /// + /// Close group of opened positions by accountId, assetPairId and direction. + /// AccountId must be passed. Method signature allow nulls for backward compatibility. + /// + /// Optional + /// Mandatory + /// Optional + /// Optional + /// + /// + /// + [Route("close-group")] + [MiddlewareFilter(typeof(RequestLoggingPipeline))] + [ServiceFilter(typeof(MarginTradingEnabledFilter))] + [HttpDelete] + public async Task CloseGroupAsync([FromQuery] string assetPairId = null, + [FromQuery] string accountId = null, [FromQuery] PositionDirectionContract? direction = null, [FromBody] PositionCloseRequest request = null) + { + var originator = GetOriginator(request?.Originator); + + var closeResult = await _tradingEngine.ClosePositionsGroupAsync(accountId, assetPairId, direction?.ToType(), originator, request?.AdditionalInfo, request?.CorrelationId); + + _operationsLogService.AddLog("Position liquidation started", string.Empty, + $"instrument = [{assetPairId}], account = [{accountId}], direction = [{direction}], request = [{request.ToJson()}]", + closeResult?.ToJson()); + + return new PositionsGroupCloseResponse + { + Responses = closeResult.Select(r => new PositionCloseResponse + { + PositionId = r.Key, + Result = r.Value.Item1.ToType(), + OrderId = r.Value.Item2?.Id + }).ToArray() + }; + } + + /// + /// Get a position by id + /// + [HttpGet, Route("{positionId}")] + public Task GetAsync(string positionId) + { + if (!_ordersCache.Positions.TryGetPositionById(positionId, out var position)) + return null; + + return Task.FromResult(position.ConvertToContract(_ordersCache)); + } + + /// + /// Get open positions + /// + [HttpGet, Route("")] + public Task> ListAsync([FromQuery]string accountId = null, + [FromQuery] string assetPairId = null) + { + var positions = _ordersCache.Positions.GetAllPositions().AsEnumerable(); + + if (!string.IsNullOrWhiteSpace(accountId)) + positions = positions.Where(o => o.AccountId == accountId); + + if (!string.IsNullOrWhiteSpace(assetPairId)) + positions = positions.Where(o => o.AssetPairId == assetPairId); + + return Task.FromResult(positions.Select(x => x.ConvertToContract(_ordersCache)).ToList()); + } + + /// + /// Get positions with optional filtering and pagination + /// + [HttpGet, Route("by-pages")] + public Task> ListAsyncByPages(string accountId = null, + string assetPairId = null, int? skip = null, int? take = null) + { + if ((skip.HasValue && !take.HasValue) || (!skip.HasValue && take.HasValue)) + { + throw new ArgumentOutOfRangeException(nameof(skip), "Both skip and take must be set or unset"); + } + + if (take.HasValue && (take <= 0 || skip < 0)) + { + throw new ArgumentOutOfRangeException(nameof(skip), "Skip must be >= 0, take must be > 0"); + } + + var positions = _ordersCache.Positions.GetAllPositions().AsEnumerable(); + + if (!string.IsNullOrWhiteSpace(accountId)) + positions = positions.Where(o => o.AccountId == accountId); + + if (!string.IsNullOrWhiteSpace(assetPairId)) + positions = positions.Where(o => o.AssetPairId == assetPairId); + + var positionList = positions.OrderByDescending(x => x.OpenDate).ToList(); + var filtered = (take == null ? positionList : positionList.Skip(skip.Value)) + .Take(PaginationHelper.GetTake(take)).ToList(); + + return Task.FromResult(new PaginatedResponseContract( + contents: filtered.Select(x => x.ConvertToContract(_ordersCache)).ToList(), + start: skip ?? 0, + size: filtered.Count, + totalSize: positionList.Count + )); + } + + /// + /// FOR TEST PURPOSES ONLY! + /// + [HttpPost, Route("special-liquidation")] + public void StartSpecialLiquidation(string[] positionIds, [CanBeNull] string accountId) + { + _cqrsSender.SendCommandToSelf(new StartSpecialLiquidationInternalCommand + { + OperationId = _identityGenerator.GenerateGuid(), + CreationTime = _dateService.Now(), + PositionIds = positionIds, + AccountId = accountId, + }); + } + + private OriginatorType GetOriginator(OriginatorTypeContract? originator) + { + if (originator == null || originator.Value == default) + { + return OriginatorType.Investor; + } + + return originator.ToType(); + } + + private void ValidateDayOff(params string[] assetPairIds) + { + foreach (var instrument in assetPairIds) + { + if (_assetDayOffService.IsDayOff(instrument)) + { + throw new InvalidOperationException($"Trades for {instrument} are not available"); + } + } + } + } +} diff --git a/src/MarginTrading.Backend/Controllers/PricesController.cs b/src/MarginTrading.Backend/Controllers/PricesController.cs new file mode 100644 index 000000000..c31747728 --- /dev/null +++ b/src/MarginTrading.Backend/Controllers/PricesController.cs @@ -0,0 +1,126 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MarginTrading.Backend.Contracts; +using MarginTrading.Backend.Contracts.Prices; +using MarginTrading.Backend.Contracts.Snow.Prices; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Services; +using MarginTrading.Backend.Services.Mappers; +using MarginTrading.Contract.BackendContracts; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace MarginTrading.Backend.Controllers +{ + /// + /// + /// Provides data about prices + /// + [Authorize] + [Route("api/prices")] + public class PricesController : Controller, IPricesApi + { + private readonly IQuoteCacheService _quoteCacheService; + private readonly IFxRateCacheService _fxRateCacheService; + private readonly OrdersCache _ordersCache; + + public PricesController( + IQuoteCacheService quoteCacheService, + IFxRateCacheService fxRateCacheService, + OrdersCache ordersCache) + { + _quoteCacheService = quoteCacheService; + _fxRateCacheService = fxRateCacheService; + _ordersCache = ordersCache; + } + + /// + /// Get current best prices + /// + /// + /// Post because the query string will be too long otherwise + /// + [Route("best")] + [HttpPost] + public Task> GetBestAsync( + [FromBody] InitPricesBackendRequest request) + { + IEnumerable> allQuotes = _quoteCacheService.GetAllQuotes(); + + if (request.AssetIds != null && request.AssetIds.Any()) + allQuotes = allQuotes.Where(q => request.AssetIds.Contains(q.Key)); + + return Task.FromResult(allQuotes.ToDictionary(q => q.Key, q => q.Value.ConvertToContract())); + } + + /// + /// Get current fx best prices + /// + /// + /// Post because the query string will be too long otherwise + /// + [Route("bestFx")] + [HttpPost] + public Task> GetBestFxAsync( + [FromBody] InitPricesBackendRequest request) + { + IEnumerable> allQuotes = _fxRateCacheService.GetAllQuotes(); + + if (request.AssetIds != null && request.AssetIds.Any()) + allQuotes = allQuotes.Where(q => request.AssetIds.Contains(q.Key)); + + return Task.FromResult(allQuotes.ToDictionary(q => q.Key, q => q.Value.ConvertToContract())); + } + + [HttpDelete] + [Route("best/{assetPairId}")] + public MtBackendResponse RemoveFromBestPriceCache(string assetPairId) + { + var positions = _ordersCache.Positions.GetPositionsByInstrument(assetPairId).ToList(); + if (positions.Any()) + { + return MtBackendResponse.Error( + $"Cannot delete [{assetPairId}] best price because there are {positions.Count} opened positions."); + } + + var orders = _ordersCache.Active.GetOrdersByInstrument(assetPairId).ToList(); + if (orders.Any()) + { + return MtBackendResponse.Error( + $"Cannot delete [{assetPairId}] best price because there are {orders.Count} active orders."); + } + + _quoteCacheService.RemoveQuote(assetPairId); + + return MtBackendResponse.Ok(true); + } + + [HttpDelete] + [Route("bestFx/{assetPairId}")] + public MtBackendResponse RemoveFromBestFxPriceCache(string assetPairId) + { + var positions = _ordersCache.Positions.GetPositionsByFxInstrument(assetPairId).ToList(); + if (positions.Any()) + { + return MtBackendResponse.Error( + $"Cannot delete [{assetPairId}] best FX price because there are {positions.Count} opened positions."); + } + + var orders = _ordersCache.Active.GetOrdersByFxInstrument(assetPairId).ToList(); + if (orders.Any()) + { + return MtBackendResponse.Error( + $"Cannot delete [{assetPairId}] best FX price because there are {orders.Count} active orders."); + } + + _fxRateCacheService.RemoveQuote(assetPairId); + + return MtBackendResponse.Ok(true); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend/Controllers/ReportController.cs b/src/MarginTrading.Backend/Controllers/ReportController.cs new file mode 100644 index 000000000..270570dc7 --- /dev/null +++ b/src/MarginTrading.Backend/Controllers/ReportController.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Threading.Tasks; +using MarginTrading.Backend.Contracts; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Services; +using MarginTrading.Common.Middleware; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace MarginTrading.Backend.Controllers +{ + [Authorize] + [Route("api/reports")] + public class ReportController : Controller, IReportApi + { + private readonly IReportService _reportService; + + public ReportController( + IReportService reportService) + { + _reportService = reportService; + } + + /// + /// Populates the data needed for report building to the storage: open positions and account fpl + /// + /// Returns 200 on success, exception otherwise + [HttpPost("dump-data")] + [MiddlewareFilter(typeof(RequestLoggingPipeline))] + public async Task DumpReportData() + { + await _reportService.DumpReportData(); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend/Controllers/RisksController.cs b/src/MarginTrading.Backend/Controllers/RisksController.cs deleted file mode 100644 index 3e973de65..000000000 --- a/src/MarginTrading.Backend/Controllers/RisksController.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Mappers; -using MarginTrading.Backend.Core.Settings; -using MarginTrading.Common.Middleware; -using MarginTrading.Contract.BackendContracts; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.Backend.Controllers -{ - [Authorize] - [Route("api/risks")] - [MiddlewareFilter(typeof(RequestLoggingPipeline))] - public class RisksController : Controller - { - private readonly IAssetPairsCache _instrumentsCache; - private readonly IAccountsCacheService _accountsCacheService; - private readonly MarginSettings _marginSettings; - private readonly IMarginTradingAccountHistoryRepository _accountHistoryRepository; - - public RisksController(IAssetPairsCache instrumentsCache, - IAccountsCacheService accountsCacheService, - MarginSettings marginSettings, - IMarginTradingAccountHistoryRepository accountHistoryRepository) - { - _instrumentsCache = instrumentsCache; - _accountsCacheService = accountsCacheService; - _marginSettings = marginSettings; - _accountHistoryRepository = accountHistoryRepository; - } - - [Route("assets")] - [HttpGet] - public AssetPairBackendContract[] GetAllAssets() - { - var instruments = _instrumentsCache.GetAll(); - return instruments.Select(item => item.ToBackendContract()).ToArray(); - } - - [Route("accounts")] - [HttpGet] - public MarginTradingAccountBackendContract[] GetAllAccounts() - { - var accounts = _accountsCacheService.GetAll(); - return accounts.Select(item => item.ToFullBackendContract(_marginSettings.IsLive)).ToArray(); - } - - [Route("accounts/history")] - [HttpGet] - public async Task> GetAllAccountsHistory( - [FromQuery] string accountId = null, [FromQuery] DateTime? from = null, [FromQuery] DateTime? to = null) - { - var accountIds = accountId != null - ? new[] {accountId} - : _accountsCacheService.GetAll().Select(item => item.Id).ToArray(); - - var history = await _accountHistoryRepository.GetAsync(accountIds, from, to); - - return history.GroupBy(i => i.AccountId) - .ToDictionary(g => g.Key, g => g.Select(i => i.ToBackendContract()).ToArray()); - } - } -} diff --git a/src/MarginTrading.Backend/Controllers/ScheduleSettingsController.cs b/src/MarginTrading.Backend/Controllers/ScheduleSettingsController.cs deleted file mode 100644 index 88c3e1f5e..000000000 --- a/src/MarginTrading.Backend/Controllers/ScheduleSettingsController.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading.Tasks; -using JetBrains.Annotations; -using MarginTrading.Backend.Contracts; -using MarginTrading.Backend.Contracts.DayOffSettings; -using MarginTrading.Backend.Core.DayOffSettings; -using MarginTrading.Backend.Services.AssetPairs; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.Backend.Controllers -{ - [Route("api/[controller]")] - public class ScheduleSettingsController : Controller, IScheduleSettingsApi - { - private readonly IDayOffSettingsService _dayOffSettingsService; - - public ScheduleSettingsController(IDayOffSettingsService dayOffSettingsService) - { - _dayOffSettingsService = dayOffSettingsService; - } - - /// - /// Get all exclusions - /// - [HttpGet] - [Route("Exclusions")] - public Task> ListExclusions() - { - return Task.FromResult>(_dayOffSettingsService.GetExclusions().Values.Select(Convert).ToList()); - } - - /// - /// Get exclusion by id - /// - [HttpGet] - [CanBeNull] - [Route("Exclusions/{id}")] - public Task GetExclusion(Guid id) - { - return Task.FromResult(Convert(_dayOffSettingsService.GetExclusion(id))); - } - - /// - /// Get all compiled exclusions - /// - [HttpGet] - [Route("Exclusions/Compiled")] - public Task ListCompiledExclusions() - { - return Task.FromResult(Convert(_dayOffSettingsService.GetCompiledExclusions())); - } - - /// - /// Create exclusion - /// - [HttpPost] - [Route("Exclusions")] - public Task CreateExclusion([FromBody] DayOffExclusionInputContract contract) - { - return Task.FromResult(Convert(_dayOffSettingsService.CreateExclusion(Convert(Guid.NewGuid(), contract)))); - } - - /// - /// Update exclusion - /// - [HttpPut] - [Route("Exclusions/{id}")] - public Task UpdateExclusion(Guid id, [FromBody] DayOffExclusionInputContract contract) - { - return Task.FromResult(Convert(_dayOffSettingsService.UpdateExclusion(Convert(id, contract)))); - } - - /// - /// Delete exclusion - /// - [HttpDelete] - [Route("Exclusions/{id}")] - public Task DeleteExclusion(Guid id) - { - _dayOffSettingsService.DeleteExclusion(id); - return Task.FromResult(Ok()); - } - - [HttpGet] - public Task GetSchedule() - { - return Task.FromResult(Convert(_dayOffSettingsService.GetScheduleSettings())); - } - - [HttpPut] - public Task SetSchedule([FromBody] ScheduleSettingsContract scheduleSettingsContract) - { - return Task.FromResult(Convert(_dayOffSettingsService.SetScheduleSettings(Convert(scheduleSettingsContract)))); - } - - [ContractAnnotation("shedule:null => null")] - private static ScheduleSettingsContract Convert([CanBeNull] ScheduleSettings shedule) - { - if (shedule == null) - { - return null; - } - - return new ScheduleSettingsContract - { - AssetPairsWithoutDayOff = shedule.AssetPairsWithoutDayOff, - DayOffEndDay = shedule.DayOffEndDay, - DayOffEndTime = shedule.DayOffEndTime, - DayOffStartDay = shedule.DayOffStartDay, - DayOffStartTime = shedule.DayOffStartTime, - PendingOrdersCutOff = shedule.PendingOrdersCutOff, - }; - } - - private static ScheduleSettings Convert(ScheduleSettingsContract shedule) - { - return new ScheduleSettings( - dayOffStartDay: shedule.DayOffStartDay, - dayOffStartTime: shedule.DayOffStartTime, - dayOffEndDay: shedule.DayOffEndDay, - dayOffEndTime: shedule.DayOffEndTime, - assetPairsWithoutDayOff: shedule.AssetPairsWithoutDayOff, - pendingOrdersCutOff: shedule.PendingOrdersCutOff); - } - - [ContractAnnotation("dayOffExclusion:null => null")] - private static DayOffExclusionContract Convert([CanBeNull] DayOffExclusion dayOffExclusion) - { - if (dayOffExclusion == null) return null; - - return new DayOffExclusionContract - { - Id = dayOffExclusion.Id, - AssetPairRegex = dayOffExclusion.AssetPairRegex, - Start = dayOffExclusion.Start, - End = dayOffExclusion.End, - IsTradeEnabled = dayOffExclusion.IsTradeEnabled, - }; - } - - private static DayOffExclusion Convert(Guid id, DayOffExclusionInputContract dayOffExclusion) - { - if (dayOffExclusion == null) throw new ArgumentNullException(nameof(dayOffExclusion)); - return new DayOffExclusion(id, dayOffExclusion.AssetPairRegex, dayOffExclusion.Start, - dayOffExclusion.End, dayOffExclusion.IsTradeEnabled); - } - - private static CompiledExclusionsListContract Convert(ImmutableDictionary> compiledExclusions) - { - var converted = compiledExclusions.SelectMany(p => p.Value.Select(e => - (e.IsTradeEnabled, Exclusion: new CompiledExclusionContract - { - Id = e.Id, - AssetPairId = p.Key, - AssetPairRegex = e.AssetPairRegex, - Start = e.Start, - End = e.End, - }))) - .ToLookup(t => t.IsTradeEnabled, t => t.Exclusion); - - return new CompiledExclusionsListContract - { - TradesDisabled = converted[false].OrderBy(e => e.AssetPairId).ToList(), - TradesEnabled = converted[true].OrderBy(e => e.AssetPairId).ToList(), - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend/Controllers/ServiceController.cs b/src/MarginTrading.Backend/Controllers/ServiceController.cs index d66592ffe..19fc289d8 100644 --- a/src/MarginTrading.Backend/Controllers/ServiceController.cs +++ b/src/MarginTrading.Backend/Controllers/ServiceController.cs @@ -1,7 +1,15 @@ -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Services.Infrastructure; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MarginTrading.Backend.Contracts; +using MarginTrading.Backend.Core.Repositories; +using MarginTrading.Backend.Core.Services; +using MarginTrading.Backend.Services.TradingConditions; using MarginTrading.Common.Middleware; -using MarginTrading.Contract.BackendContracts; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -10,43 +18,70 @@ namespace MarginTrading.Backend.Controllers [Authorize] [Route("api/service")] [MiddlewareFilter(typeof(RequestLoggingPipeline))] - public class ServiceController : Controller + public class ServiceController : Controller, IServiceApi { - private readonly IMaintenanceModeService _maintenanceModeService; - private readonly IQuoteCacheService _quoteCacheService; + private readonly IOvernightMarginParameterContainer _overnightMarginParameterContainer; + private readonly IIdentityGenerator _identityGenerator; + private readonly ISnapshotService _snapshotService; - public ServiceController(IMaintenanceModeService maintenanceModeService, - IQuoteCacheService quoteCacheService) + public ServiceController( + IOvernightMarginParameterContainer overnightMarginParameterContainer, + IIdentityGenerator identityGenerator, + ISnapshotService snapshotService) { - _maintenanceModeService = maintenanceModeService; - _quoteCacheService = quoteCacheService; + _overnightMarginParameterContainer = overnightMarginParameterContainer; + _identityGenerator = identityGenerator; + _snapshotService = snapshotService; } - [HttpPost] - [Route(LykkeConstants.MaintenanceModeRoute)] - public MtBackendResponse SetMaintenanceMode([FromBody]bool enabled) + /// + /// Save snapshot of orders, positions, account stats, best fx prices, best trading prices for current moment. + /// Throws an error in case if trading is not stopped. + /// + /// Snapshot statistics. + [HttpPost("make-trading-data-snapshot")] + public async Task MakeTradingDataSnapshot([FromQuery] DateTime tradingDay, [FromQuery] string correlationId = null) { - _maintenanceModeService.SetMode(enabled); - - return MtBackendResponse.Ok(enabled); + if (tradingDay == default) + { + throw new Exception($"{nameof(tradingDay)} must be set"); + } + + if (string.IsNullOrWhiteSpace(correlationId)) + { + correlationId = _identityGenerator.GenerateGuid(); + } + + return await _snapshotService.MakeTradingDataSnapshot(tradingDay, correlationId); } - [HttpGet] - [Route(LykkeConstants.MaintenanceModeRoute)] - public MtBackendResponse GetMaintenanceMode() + /// + /// Get current state of overnight margin parameter. + /// + [HttpGet("current-overnight-margin-parameter")] + public Task GetOvernightMarginParameterCurrentState() { - var result = _maintenanceModeService.CheckIsEnabled(); - - return MtBackendResponse.Ok(result); + return Task.FromResult(_overnightMarginParameterContainer.GetOvernightMarginParameterState()); } - [HttpDelete] - [Route("bestprice/{assetPair}")] - public MtBackendResponse ClearBestBriceCache(string assetPair) + /// + /// Get current margin parameter values for instruments (all / filtered by IDs). + /// + /// + /// Dictionary with key = asset pair ID and value = (Dictionary with key = trading condition ID and value = multiplier) + /// + [HttpGet("overnight-margin-parameter")] + public Task>> GetOvernightMarginParameterValues( + [FromQuery] string[] instruments = null) { - _quoteCacheService.RemoveQuote(assetPair); + var result = _overnightMarginParameterContainer.GetOvernightMarginParameterValues() + .Where(x => instruments == null || !instruments.Any() || instruments.Contains(x.Key.Item2)) + .GroupBy(x => x.Key.Item2) + .OrderBy(x => x.Any(p => p.Value > 1) ? 0 : 1) + .ThenBy(x => x.Key) + .ToDictionary(x => x.Key, x => x.ToDictionary(p => p.Key.Item1, p => p.Value)); - return MtBackendResponse.Ok(true); + return Task.FromResult(result); } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend/Controllers/TradingConditionsController.cs b/src/MarginTrading.Backend/Controllers/TradingConditionsController.cs deleted file mode 100644 index bbbc63043..000000000 --- a/src/MarginTrading.Backend/Controllers/TradingConditionsController.cs +++ /dev/null @@ -1,129 +0,0 @@ -using JetBrains.Annotations; -using MarginTrading.Backend.Contracts; -using MarginTrading.Backend.Contracts.AccountAssetPair; -using MarginTrading.Backend.Contracts.Common; -using MarginTrading.Backend.Contracts.TradingConditions; -using MarginTrading.Backend.Core.TradingConditions; -using MarginTrading.Backend.Services.TradingConditions; -using MarginTrading.Common.Middleware; -using MarginTrading.Common.Services; -using MarginTrading.Contract.BackendContracts.AccountsManagement; -using MarginTrading.Contract.BackendContracts.TradingConditions; -using MarginTrading.Contract.BackendContracts; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Swashbuckle.SwaggerGen.Annotations; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.Backend.Core.Mappers; - -namespace MarginTrading.Backend.Controllers -{ - [Authorize] - [Route("api/tradingConditions")] - [MiddlewareFilter(typeof(RequestLoggingPipeline))] - public class TradingConditionsController : Controller - { - private readonly TradingConditionsManager _tradingConditionsManager; - private readonly AccountGroupManager _accountGroupManager; - private readonly AccountAssetsManager _accountAssetsManager; - private readonly ITradingConditionsCacheService _tradingConditionsCacheService; - private readonly IConvertService _convertService; - - public TradingConditionsController(TradingConditionsManager tradingConditionsManager, - AccountGroupManager accountGroupManager, - AccountAssetsManager accountAssetsManager, - ITradingConditionsCacheService tradingConditionsCacheService, - IConvertService convertService) - { - _tradingConditionsManager = tradingConditionsManager; - _accountGroupManager = accountGroupManager; - _accountAssetsManager = accountAssetsManager; - _tradingConditionsCacheService = tradingConditionsCacheService; - _convertService = convertService; - } - - [HttpPost] - [Route("")] - [SwaggerOperation("AddOrReplaceTradingCondition")] - public async Task> AddOrReplaceTradingCondition( - [FromBody] TradingConditionModel model) - { - if (string.IsNullOrWhiteSpace(model.Id)) - return MtBackendResponse.Error("Id cannot be empty"); - - if (string.IsNullOrWhiteSpace(model.Name)) - return MtBackendResponse.Error("Name cannot be empty"); - - if (string.IsNullOrWhiteSpace(model.LegalEntity)) - return MtBackendResponse.Error("LegalEntity cannot be empty"); - - if (_tradingConditionsCacheService.IsTradingConditionExists(model.Id)) - { - var existingCondition = _tradingConditionsCacheService.GetTradingCondition(model.Id); - - if (existingCondition.LegalEntity != model.LegalEntity) - { - return MtBackendResponse.Error("LegalEntity cannot be changed"); - } - } - - var tradingCondition = model.ToDomainContract(); - - tradingCondition = await _tradingConditionsManager.AddOrReplaceTradingConditionAsync(tradingCondition); - - return MtBackendResponse.Ok(tradingCondition?.ToBackendContract()); - } - - [HttpPost] - [Route("accountGroups")] - [SwaggerOperation("AddOrReplaceAccountGroup")] - public async Task> AddOrReplaceAccountGroup( - [FromBody] AccountGroupModel model) - { - var accountGroup = await _accountGroupManager.AddOrReplaceAccountGroupAsync(model.ToDomainContract()); - - return MtBackendResponse.Ok(accountGroup.ToBackendContract()); - } - - [HttpPost] - [Route("accountAssets/assignInstruments")] - [SwaggerOperation("AssignInstruments")] - public async Task>> AssignInstruments( - [FromBody] AssignInstrumentsRequest model) - { - try - { - var assetPairs = await _accountAssetsManager.AssignInstruments(model.TradingConditionId, model.BaseAssetId, - model.Instruments); - - return MtBackendResponse>.Ok( - assetPairs.Select(a => a.ToBackendContract())); - } - catch (Exception e) - { - return MtBackendResponse>.Error(e.Message); - } - } - - [HttpPost] - [Route("accountAssets")] - [SwaggerOperation("AddOrReplaceAccountAsset")] - public async Task> InsertOrUpdateAccountAsset([FromBody]AccountAssetPairContract model) - { - var assetPair = await _accountAssetsManager.AddOrReplaceAccountAssetAsync(Convert(model)); - return BackendResponse.Ok(Convert(assetPair)); - } - - private AccountAssetPairContract Convert(IAccountAssetPair accountAssetPair) - { - return _convertService.Convert(accountAssetPair); - } - private IAccountAssetPair Convert(AccountAssetPairContract accountAssetPair) - { - return _convertService.Convert(accountAssetPair); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend/Controllers/TradingScheduleController.cs b/src/MarginTrading.Backend/Controllers/TradingScheduleController.cs new file mode 100644 index 000000000..5ec7f35f3 --- /dev/null +++ b/src/MarginTrading.Backend/Controllers/TradingScheduleController.cs @@ -0,0 +1,105 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MarginTrading.Backend.Contracts; +using MarginTrading.Backend.Contracts.TradingSchedule; +using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Mappers; +using MarginTrading.Backend.Filters; +using MarginTrading.Backend.Services.AssetPairs; +using MarginTrading.Common.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace MarginTrading.Backend.Controllers +{ + /// + /// Api to retrieve compiled trading schedule - for cache initialization only. + /// + [Authorize] + [Route("api/trading-schedule")] + public class TradingScheduleController : Controller, ITradingScheduleApi + { + private readonly IDateService _dateService; + private readonly IScheduleSettingsCacheService _scheduleSettingsCacheService; + private readonly IAssetPairDayOffService _assetPairDayOffService; + private readonly IAssetPairsCache _assetPairsCache; + + public TradingScheduleController( + IDateService dateService, + IScheduleSettingsCacheService scheduleSettingsCacheService, + IAssetPairDayOffService assetPairDayOffService, + IAssetPairsCache assetPairsCache) + { + _dateService = dateService; + _scheduleSettingsCacheService = scheduleSettingsCacheService; + _assetPairDayOffService = assetPairDayOffService; + _assetPairsCache = assetPairsCache; + } + + /// + /// Get current compiled trading schedule for each asset pair in a form of the list of time intervals. + /// Cache is invalidated and recalculated after 00:00:00.000 each day on request. + /// + [HttpGet("compiled")] + [HttpGet("asset-pairs-compiled")] + public Task>> CompiledTradingSchedule() + { + return Task.FromResult( + _scheduleSettingsCacheService.GetCompiledAssetPairScheduleSettings() + .ToDictionary(x => x.Key, x => x.Value.Select(ti => ti.ToRabbitMqContract()).ToList())); + } + + /// + /// Get current compiled trading schedule for each market. + /// + [HttpGet("markets-compiled")] + public Task>> CompiledMarketTradingSchedule() + { + return Task.FromResult(_scheduleSettingsCacheService.GetMarketsTradingSchedule() + .ToDictionary(x => x.Key, x => x.Value.Select(m => m.ToRabbitMqContract()).ToList())); + } + + /// + /// Get current instrument's trading status. + /// Do not use this endpoint from FrontEnd! + /// + [HttpGet("is-enabled/{assetPairId}")] + public Task IsInstrumentEnabled(string assetPairId) + { + _assetPairsCache.GetAssetPairById(assetPairId); + + var isEnabled = !_assetPairDayOffService.IsDayOff(assetPairId); + + return Task.FromResult(isEnabled); + } + + /// + /// Get current trading status of all instruments. + /// Do not use this endpoint from FrontEnd! + /// + [HttpGet("are-enabled")] + public Task> AreInstrumentsEnabled() + { + var assetPairIds = _assetPairsCache.GetAllIds(); + + var areEnabled = assetPairIds.ToDictionary(x => x, x => !_assetPairDayOffService.IsDayOff(x)); + + return Task.FromResult(areEnabled); + } + + /// + /// Get current markets state: trading enabled or disabled. + /// + [HttpGet("markets-state")] + public Task> MarketsState() + { + return Task.FromResult(_scheduleSettingsCacheService.GetMarketState() + .ToDictionary(x => x.Key, x => x.Value.IsEnabled)); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend/Dockerfile b/src/MarginTrading.Backend/Dockerfile index a53d715ed..5cc7dad6c 100644 --- a/src/MarginTrading.Backend/Dockerfile +++ b/src/MarginTrading.Backend/Dockerfile @@ -1,4 +1,4 @@ -FROM microsoft/aspnetcore:2.0 +FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 WORKDIR /app COPY . . ENTRYPOINT ["dotnet", "MarginTrading.Backend.dll"] diff --git a/src/MarginTrading.Backend/Email/EmailService.cs b/src/MarginTrading.Backend/Email/EmailService.cs index 814f22a48..f740613ed 100644 --- a/src/MarginTrading.Backend/Email/EmailService.cs +++ b/src/MarginTrading.Backend/Email/EmailService.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; using Lykke.Service.EmailSender; using MarginTrading.Backend.Core; @@ -50,22 +53,5 @@ await _emailSender.SendAsync( EmailAddress = email }); } - - public async Task SendOvernightSwapEmailAsync(string email, OvernightSwapNotification overnightSwapNotification) - { - var message = - _templateGenerator.Generate("OvernightSwap", overnightSwapNotification); - - await _emailSender.SendAsync( - new EmailMessage - { - HtmlBody = message, - Subject = "Overnight Swap" - }, - new EmailAddressee - { - EmailAddress = email - }); - } } } diff --git a/src/MarginTrading.Backend/Email/ITemplateGenerator.cs b/src/MarginTrading.Backend/Email/ITemplateGenerator.cs index 15519b12d..850121e2f 100644 --- a/src/MarginTrading.Backend/Email/ITemplateGenerator.cs +++ b/src/MarginTrading.Backend/Email/ITemplateGenerator.cs @@ -1,4 +1,7 @@ -#pragma warning disable 1591 +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +#pragma warning disable 1591 namespace MarginTrading.Backend.Email { diff --git a/src/MarginTrading.Backend/Email/MustacheTemplateGenerator.cs b/src/MarginTrading.Backend/Email/MustacheTemplateGenerator.cs index 8849e8078..d19bd866d 100644 --- a/src/MarginTrading.Backend/Email/MustacheTemplateGenerator.cs +++ b/src/MarginTrading.Backend/Email/MustacheTemplateGenerator.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.IO; using Microsoft.AspNetCore.Hosting; @@ -25,7 +28,6 @@ public string Generate(string templateName, T model) try { - //TODO: get template from blob return Nustache.Core.Render.FileToString(path, model); } catch (InvalidCastException) diff --git a/src/MarginTrading.Backend/Filters/MarginTradingEnabledFilter.cs b/src/MarginTrading.Backend/Filters/MarginTradingEnabledFilter.cs index 510e439b5..02d197416 100644 --- a/src/MarginTrading.Backend/Filters/MarginTradingEnabledFilter.cs +++ b/src/MarginTrading.Backend/Filters/MarginTradingEnabledFilter.cs @@ -1,142 +1,139 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; -using Common; using Common.Log; using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Rocks.Caching; -using MarginTrading.Backend.Attributes; -using MarginTrading.Backend.Core.Settings; -using MarginTrading.Common.Services.Settings; -using MarginTrading.Common.Settings; +using MarginTrading.Backend.Services.Services; namespace MarginTrading.Backend.Filters { /// /// Restricts access to actions for clients which are not allowed to use current type of margin trading (live/demo). - /// Skips validation if current action method is marked with . - /// If ClientId is not found in the action parameters - does nothing. + /// If AccountId is not found in the action parameters - does nothing. /// - public class MarginTradingEnabledFilter: ActionFilterAttribute + public class MarginTradingEnabledFilter : ActionFilterAttribute { - private readonly MarginSettings _marginSettings; private readonly IMarginTradingSettingsCacheService _marginTradingSettingsCacheService; private readonly ICacheProvider _cacheProvider; - private readonly ILog _log; public MarginTradingEnabledFilter( - MarginSettings marginSettings, IMarginTradingSettingsCacheService marginTradingSettingsCacheService, - ICacheProvider cacheProvider, - ILog log) + ICacheProvider cacheProvider) { - _marginSettings = marginSettings; _marginTradingSettingsCacheService = marginTradingSettingsCacheService; _cacheProvider = cacheProvider; - _log = log; } /// public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { - await ValidateMarginTradingEnabledAsync(context); + ValidateMarginTradingEnabled(context); await base.OnActionExecutionAsync(context, next); } /// public override void OnActionExecuting(ActionExecutingContext context) { - // this won't be called frequently as we have most actions async - ValidateMarginTradingEnabledAsync(context).Wait(); + ValidateMarginTradingEnabled(context); } /// /// Performs a validation if current type of margin trading is enabled globally and for the particular client /// (which is extracted from the action parameters). - /// Skips validation if current action method is marked with . /// /// /// Using this type of margin trading is restricted for client or - /// a controller action has more then one ClientId in its parameters. + /// a controller action has more then one AccountId in its parameters. /// - private async Task ValidateMarginTradingEnabledAsync(ActionExecutingContext context) + private void ValidateMarginTradingEnabled(ActionExecutingContext context) { - var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; - if (controllerActionDescriptor == null) - { + if (!(context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)) return; - } - var cacheKey = CacheKeyBuilder.Create(nameof(MarginTradingEnabledFilter), nameof(GetSingleClientIdGetter), controllerActionDescriptor.DisplayName); - var clientIdGetter = _cacheProvider.Get(cacheKey, () => new CachableResult(GetSingleClientIdGetter(controllerActionDescriptor), CachingParameters.InfiniteCache)); - if (clientIdGetter != null) + var cacheKey = CacheKeyBuilder.Create(nameof(MarginTradingEnabledFilter), nameof(GetSingleAccountIdGetter), + controllerActionDescriptor.DisplayName); + var accountIdGetter = _cacheProvider.Get(cacheKey, + () => new CachableResult(GetSingleAccountIdGetter(controllerActionDescriptor), + CachingParameters.InfiniteCache)); + if (accountIdGetter != null) { - var clientId = clientIdGetter(context.ActionArguments); - if (string.IsNullOrWhiteSpace(clientId)) + var accountId = accountIdGetter(context.ActionArguments); + if (!string.IsNullOrWhiteSpace(accountId)) { - await _log.WriteWarningAsync(nameof(MarginTradingEnabledFilter), nameof(ValidateMarginTradingEnabledAsync), context.ActionDescriptor.DisplayName, "ClientId is null but is expected. No validation will be performed"); - } - else if (!await _marginTradingSettingsCacheService.IsMarginTradingEnabled(clientId, _marginSettings.IsLive)) - { - throw new InvalidOperationException("Using this type of margin trading is restricted for client " + clientId); + var isAccEnabled = _marginTradingSettingsCacheService.IsMarginTradingEnabledByAccountId(accountId); + if (isAccEnabled == null) + { + throw new InvalidOperationException($"Account {accountId} does not exist"); + } + + if (!(bool) isAccEnabled) + { + throw new InvalidOperationException( + "Using this type of margin trading is restricted for account id " + accountId); + } } } } /// - /// Finds single clientId getter func for current action. + /// Finds single accountId getter func for current action. /// If none found returns null. /// If the action is marked to skip the MarginTradingEnabled check also returns null. /// [CanBeNull] - private static ClientIdGetter GetSingleClientIdGetter(ControllerActionDescriptor controllerActionDescriptor) + private static AccountIdGetter GetSingleAccountIdGetter(ControllerActionDescriptor controllerActionDescriptor) { - if (controllerActionDescriptor.MethodInfo.GetCustomAttribute() != null) - { - return null; - } - - var clientIdGetters = GetClientIdGetters(controllerActionDescriptor.Parameters).ToList(); - switch (clientIdGetters.Count) + var accountIdGetters = GetAccountIdGetters(controllerActionDescriptor.Parameters).ToList(); + switch (accountIdGetters.Count) { case 0: return null; case 1: - return clientIdGetters[0]; + return accountIdGetters[0]; default: - throw new InvalidOperationException("A controller action cannot have more then one ClientId in its parameters"); + throw new InvalidOperationException( + "A controller action cannot have more then one AccountId in its parameters"); } } /// - /// Searches the controller's actions parameters for the presence of ClientId - /// and returns a func to get the ClientId value from ActionArguments for each of found ClientIds parameters + /// Searches the controller's actions parameters for the presence of AccountId + /// and returns a func to get the AccountId value from ActionArguments for each of found AccountIds parameters /// - private static IEnumerable GetClientIdGetters(IList parameterDescriptors) + private static IEnumerable GetAccountIdGetters( + IEnumerable parameterDescriptors) { foreach (var parameterDescriptor in parameterDescriptors) { - if (string.Compare(parameterDescriptor.Name, "ClientId", StringComparison.OrdinalIgnoreCase) == 0 - && parameterDescriptor.ParameterType == typeof(string)) + if (string.Compare(parameterDescriptor.Name, "AccountId", StringComparison.OrdinalIgnoreCase) == 0 && + parameterDescriptor.ParameterType == typeof(string)) { - yield return d => (string) d[parameterDescriptor.Name]; + yield return d => d.TryGetValue(parameterDescriptor.Name, out var arg) ? (string) arg : null; } else { - var clientIdPropertyInfo = parameterDescriptor.ParameterType.GetProperty("ClientId", typeof(string)); - if (clientIdPropertyInfo != null) + var accountIdPropertyInfo = + parameterDescriptor.ParameterType.GetProperty("AccountId", typeof(string)); + if (accountIdPropertyInfo != null) { - yield return d => (string) ((dynamic) d[parameterDescriptor.Name])?.ClientId; + yield return d => + d.TryGetValue(parameterDescriptor.Name, out var arg) + ? (string) ((dynamic) arg)?.AccountId + : null; } } } } - private delegate string ClientIdGetter(IDictionary actionArguments); + private delegate string AccountIdGetter(IDictionary actionArguments); } -} +} \ No newline at end of file diff --git a/src/MarginTrading.Backend/Infrastructure/ApiKeyHeaderOperationFilter.cs b/src/MarginTrading.Backend/Infrastructure/ApiKeyHeaderOperationFilter.cs index 174bcf7b5..722f9e0c1 100644 --- a/src/MarginTrading.Backend/Infrastructure/ApiKeyHeaderOperationFilter.cs +++ b/src/MarginTrading.Backend/Infrastructure/ApiKeyHeaderOperationFilter.cs @@ -1,8 +1,11 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc.Authorization; -using Swashbuckle.Swagger.Model; -using Swashbuckle.SwaggerGen.Generator; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; namespace MarginTrading.Backend.Infrastructure { diff --git a/src/MarginTrading.Backend/Infrastructure/LykkeErrorResponse.cs b/src/MarginTrading.Backend/Infrastructure/LykkeErrorResponse.cs new file mode 100644 index 000000000..11ca598c0 --- /dev/null +++ b/src/MarginTrading.Backend/Infrastructure/LykkeErrorResponse.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; + +namespace MarginTrading.Backend.Infrastructure +{ + [UsedImplicitly] + public class LykkeErrorResponse + { + public string ErrorMessage { get; set; } + + public override string ToString() => ErrorMessage; + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend/MarginTrading.Backend.csproj b/src/MarginTrading.Backend/MarginTrading.Backend.csproj index c99b43893..a010751ab 100644 --- a/src/MarginTrading.Backend/MarginTrading.Backend.csproj +++ b/src/MarginTrading.Backend/MarginTrading.Backend.csproj @@ -1,7 +1,6 @@  - netcoreapp2.0 - $(NoWarn);1591 + netcoreapp2.2 true MarginTrading.Backend Exe @@ -9,8 +8,13 @@ false false false - 1.0.1 - 7.1 + 1.16.29 + 7.3 + + + 1701;1702;1705;CA2007;0612;0618;1591 + F7428201-B7F7-4571-9ABD-20DDECF71A33 + MarginTrading.Backend @@ -28,47 +32,68 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + PreserveNewest + + + PreserveNewest + + + + + + + + diff --git a/src/MarginTrading.Backend/Middleware/GlobalErrorHandlerMiddleware.cs b/src/MarginTrading.Backend/Middleware/GlobalErrorHandlerMiddleware.cs index 5d8e31894..4344d4bc4 100644 --- a/src/MarginTrading.Backend/Middleware/GlobalErrorHandlerMiddleware.cs +++ b/src/MarginTrading.Backend/Middleware/GlobalErrorHandlerMiddleware.cs @@ -1,8 +1,12 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.IO; using System.Threading.Tasks; using Common; using Common.Log; +using JetBrains.Annotations; using MarginTrading.Common.Extensions; using MarginTrading.Common.Helpers; using MarginTrading.Contract.BackendContracts; @@ -21,6 +25,7 @@ public GlobalErrorHandlerMiddleware(RequestDelegate next, ILog log) _next = next; } + [UsedImplicitly] public async Task Invoke(HttpContext context) { try @@ -54,7 +59,7 @@ private async Task SendError(HttpContext ctx, string errorMessage) { ctx.Response.ContentType = "application/json"; ctx.Response.StatusCode = 500; - var response = new MtBackendResponse() {Result = "Technical problems", Message = errorMessage}; + var response = new MtBackendResponse {Result = "Technical problems", ErrorMessage = errorMessage}; await ctx.Response.WriteAsync(response.ToJson()); } } diff --git a/src/MarginTrading.Backend/Middleware/KeyAuthHandler.cs b/src/MarginTrading.Backend/Middleware/KeyAuthHandler.cs index 00db3e51f..afb974e90 100644 --- a/src/MarginTrading.Backend/Middleware/KeyAuthHandler.cs +++ b/src/MarginTrading.Backend/Middleware/KeyAuthHandler.cs @@ -1,4 +1,7 @@ -using System.Linq; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Linq; using System.Security.Claims; using System.Text.Encodings.Web; using System.Threading.Tasks; diff --git a/src/MarginTrading.Backend/Middleware/KeyAuthOptions.cs b/src/MarginTrading.Backend/Middleware/KeyAuthOptions.cs index 2e4fbb71b..6aa57878b 100644 --- a/src/MarginTrading.Backend/Middleware/KeyAuthOptions.cs +++ b/src/MarginTrading.Backend/Middleware/KeyAuthOptions.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Authentication; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Microsoft.AspNetCore.Authentication; namespace MarginTrading.Backend.Middleware { diff --git a/src/MarginTrading.Backend/Middleware/MaintenanceModeMiddleware.cs b/src/MarginTrading.Backend/Middleware/MaintenanceModeMiddleware.cs index f7e420f8f..40a85a587 100644 --- a/src/MarginTrading.Backend/Middleware/MaintenanceModeMiddleware.cs +++ b/src/MarginTrading.Backend/Middleware/MaintenanceModeMiddleware.cs @@ -1,7 +1,11 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Threading.Tasks; using Common; using Common.Log; +using JetBrains.Annotations; using MarginTrading.Backend.Core; using MarginTrading.Backend.Services.Infrastructure; using MarginTrading.Contract.BackendContracts; @@ -24,6 +28,7 @@ public MaintenanceModeMiddleware(RequestDelegate next, _log = log; } + [UsedImplicitly] public async Task Invoke(HttpContext context) { var isMaintenanceMode = false; @@ -45,7 +50,7 @@ public async Task Invoke(HttpContext context) { context.Response.ContentType = "application/json"; context.Response.StatusCode = 503; - var response = new MtBackendResponse { Message = "Maintenance Mode" }; + var response = new MtBackendResponse { ErrorMessage = "Maintenance Mode" }; await context.Response.WriteAsync(response.ToJson()); } } diff --git a/src/MarginTrading.Backend/Middleware/Validator/IApiKeyValidator.cs b/src/MarginTrading.Backend/Middleware/Validator/IApiKeyValidator.cs index e5cf5498d..e223faf41 100644 --- a/src/MarginTrading.Backend/Middleware/Validator/IApiKeyValidator.cs +++ b/src/MarginTrading.Backend/Middleware/Validator/IApiKeyValidator.cs @@ -1,4 +1,5 @@ - +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. using MarginTrading.Backend.Core.Settings; @@ -13,9 +14,9 @@ public interface IApiKeyValidator public class ApiKeyValidator : IApiKeyValidator { - private readonly MarginSettings _settings; + private readonly MarginTradingSettings _settings; - public ApiKeyValidator(MarginSettings settings) + public ApiKeyValidator(MarginTradingSettings settings) { _settings = settings; } diff --git a/src/MarginTrading.Backend/Models/BackofficeModels.cs b/src/MarginTrading.Backend/Models/BackofficeModels.cs deleted file mode 100644 index 83034e356..000000000 --- a/src/MarginTrading.Backend/Models/BackofficeModels.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -#pragma warning disable 1591 - -namespace MarginTrading.Backend.Models -{ - public class SummaryAssetInfo - { - public string AssetPairId { get; set; } - public decimal VolumeLong { get; set; } - public decimal VolumeShort { get; set; } - public decimal PnL { get; set; } - } - - public class CreateMarginTradingAccountModel - { - [Required] - public string ClientId { get; set; } - [Required] - public string AssetId { get; set; } - [Required] - public string TradingConditionId { get; set; } - } - - public class InitAccountsRequest - { - public string ClientId { get; set; } - public string TradingConditionsId { get; set; } - } - - public class InitAccountsResponse - { - public CreateAccountStatus Status { get; set; } - public string Message { get; set; } - } - - public enum CreateAccountStatus - { - Available, - Created, - Error - } -} diff --git a/src/MarginTrading.Backend/Models/OrderBookModel.cs b/src/MarginTrading.Backend/Models/OrderBookModel.cs deleted file mode 100644 index 15aa83347..000000000 --- a/src/MarginTrading.Backend/Models/OrderBookModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; -using MarginTrading.Backend.Core; - -namespace MarginTrading.Backend.Models -{ - public class OrderBookModel - { - public string Instrument { get; set; } - public List Buy { get; set; } = new List(); - public List Sell { get; set; } = new List(); - } -} diff --git a/src/MarginTrading.Backend/Modules/BackendMigrationsModule.cs b/src/MarginTrading.Backend/Modules/BackendMigrationsModule.cs index 1d371bc05..a3bf6ae96 100644 --- a/src/MarginTrading.Backend/Modules/BackendMigrationsModule.cs +++ b/src/MarginTrading.Backend/Modules/BackendMigrationsModule.cs @@ -1,7 +1,9 @@ -using System.Linq; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Linq; using System.Reflection; using Autofac; -using MarginTrading.Backend.Services; using MarginTrading.Backend.Services.Migrations; using Module = Autofac.Module; diff --git a/src/MarginTrading.Backend/Modules/BackendRepositoriesModule.cs b/src/MarginTrading.Backend/Modules/BackendRepositoriesModule.cs index c66b4cefe..aeda3fbc7 100644 --- a/src/MarginTrading.Backend/Modules/BackendRepositoriesModule.cs +++ b/src/MarginTrading.Backend/Modules/BackendRepositoriesModule.cs @@ -1,10 +1,11 @@ -using Autofac; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Autofac; using AzureStorage.Tables; -using AzureStorage.Tables.Templates.Index; using Common.Log; using Lykke.SettingsReader; using MarginTrading.AzureRepositories; -using MarginTrading.AzureRepositories.Contract; using MarginTrading.AzureRepositories.Entities; using MarginTrading.AzureRepositories.Logs; using MarginTrading.Backend.Core; @@ -13,103 +14,173 @@ using MarginTrading.Backend.Core.Settings; using MarginTrading.Backend.Services.Infrastructure; using MarginTrading.Backend.Services.MatchingEngines; -using MarginTrading.Common.RabbitMq; using MarginTrading.Common.Services; +using MarginTrading.SqlRepositories.Repositories; +using MarginTrading.SqlRepositories; +using Microsoft.Extensions.Internal; +using OperationLogEntity = MarginTrading.AzureRepositories.OperationLogEntity; namespace MarginTrading.Backend.Modules { - public class BackendRepositoriesModule : Module - { - private readonly IReloadingManager _settings; - private readonly ILog _log; - - public BackendRepositoriesModule(IReloadingManager settings, ILog log) - { - _settings = settings; - _log = log; - } - - protected override void Load(ContainerBuilder builder) - { - builder.RegisterInstance(_log) - .As() - .SingleInstance(); - - builder.Register(ctx => - new MarginTradingOperationsLogRepository( - AzureTableStorage.Create(_settings.Nested(s => s.Db.LogsConnString), - "MarginTradingBackendOperationsLog", _log)) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateAccountsRepository(_settings.Nested(s => s.Db.MarginTradingConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateOrdersHistoryRepository(_settings.Nested(s => s.Db.HistoryConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateAccountHistoryRepository(_settings.Nested(s => s.Db.HistoryConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateMatchingEngineRoutesRepository(_settings.Nested(s => s.Db.MarginTradingConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateTradingConditionsRepository(_settings.Nested(s => s.Db.MarginTradingConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateAccountGroupRepository(_settings.Nested(s => s.Db.MarginTradingConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateAccountAssetsRepository(_settings.Nested(s => s.Db.MarginTradingConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateBlobRepository(_settings.Nested(s => s.Db.StateConnString)) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateRiskSystemCommandsLogRepository(_settings.Nested(s => s.Db.LogsConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateOvernightSwapStateRepository(_settings.Nested(s => s.Db.StateConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateOvernightSwapHistoryRepository(_settings.Nested(s => s.Db.HistoryConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateDayOffSettingsRepository(_settings.Nested(s => s.Db.MarginTradingConnString)) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateAssetPairSettingsRepository( - _settings.Nested(s => s.Db.MarginTradingConnString), _log, ctx.Resolve()) - ).SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder.Register(c => - { - var settings = c.Resolve>(); - - return settings.CurrentValue.UseAzureIdentityGenerator - ? (IIdentityGenerator) new AzureIdentityGenerator( - AzureTableStorage.Create(settings.Nested(s => s.Db.MarginTradingConnString), - "Identity", _log)) - : (IIdentityGenerator) new FakeIdentityGenerator(); - }) - .As() - .SingleInstance(); - } - } -} + public class BackendRepositoriesModule : Module + { + private readonly IReloadingManager _settings; + private readonly ILog _log; + private const string OperationsLogName = "MarginTradingBackendOperationsLog"; + + public BackendRepositoriesModule(IReloadingManager settings, ILog log) + { + _settings = settings; + _log = log; + } + + protected override void Load(ContainerBuilder builder) + { + builder.RegisterInstance(_log).As().SingleInstance(); + + if (_settings.CurrentValue.Db.StorageMode == StorageMode.Azure) + { + builder.Register(ctx => _settings.CurrentValue.UseSerilog + ? (IOperationsLogRepository) new SerilogOperationsLogRepository(_log) + : new OperationsLogRepository(AzureTableStorage.Create( + _settings.Nested(s => s.Db.LogsConnString), OperationsLogName, _log))) + .SingleInstance(); + + builder.Register(ctx => + AzureRepoFactories.MarginTrading.CreateBlobRepository( + _settings.Nested(s => s.Db.StateConnString))) + .SingleInstance(); + + builder.Register(c => + { + var settings = c.Resolve>(); + + return settings.CurrentValue.UseDbIdentityGenerator + ? (IIdentityGenerator) new AzureIdentityGenerator( + AzureTableStorage.Create(settings.Nested(s => s.Db.MarginTradingConnString), + "Identity", _log)) + : (IIdentityGenerator) new SimpleIdentityGenerator(); + }).As().SingleInstance(); + + builder.RegisterType() + .As() + .WithParameter(new NamedParameter("connectionStringManager", + _settings.Nested(x => x.Db.MarginTradingConnString))) + .SingleInstance(); + + builder.RegisterType() + .As() + .WithParameter(new NamedParameter("connectionStringManager", + _settings.Nested(x => x.Db.MarginTradingConnString))) + .SingleInstance(); + + builder.RegisterType() + .As() + .WithParameter(new NamedParameter("connectionStringManager", + _settings.Nested(x => x.Db.MarginTradingConnString))) + .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); + } + else if (_settings.CurrentValue.Db.StorageMode == StorageMode.SqlServer) + { + builder.Register(ctx => _settings.CurrentValue.UseSerilog + ? (IOperationsLogRepository) new SerilogOperationsLogRepository(_log) + : new SqlOperationsLogRepository(ctx.Resolve(), + OperationsLogName, _settings.CurrentValue.Db.LogsConnString)) + .SingleInstance(); + + builder.Register(ctx => + new SqlBlobRepository(_settings.CurrentValue.Db.StateConnString)) + .SingleInstance(); + + builder.Register(c => c.Resolve>().CurrentValue + .UseDbIdentityGenerator + ? (IIdentityGenerator) new SqlIdentityGenerator() + : (IIdentityGenerator) new SimpleIdentityGenerator()) + .As() + .SingleInstance(); + + builder.RegisterType() + .As() + .WithParameter(new NamedParameter("connectionString", + _settings.CurrentValue.Db.SqlConnectionString)) + .SingleInstance(); + + builder.RegisterType() + .As() + .WithParameter(new NamedParameter("connectionString", + _settings.CurrentValue.Db.SqlConnectionString)) + .SingleInstance(); + + builder.RegisterType() + .As() + .WithParameter(new NamedParameter("connectionString", + _settings.CurrentValue.Db.SqlConnectionString)) + .SingleInstance(); + + builder.RegisterType() + .As() + .WithParameter(new NamedParameter("connectionString", + _settings.CurrentValue.Db.OrdersHistorySqlConnectionString)) + .WithParameter(new NamedParameter("tableName", + _settings.CurrentValue.Db.OrdersHistoryTableName)) + .WithParameter(new NamedParameter("getLastSnapshotTimeoutS", + _settings.CurrentValue.Db.QueryTimeouts.GetLastSnapshotTimeoutS)) + .SingleInstance(); + + builder.RegisterType() + .As() + .WithParameter(new NamedParameter("connectionString", + _settings.CurrentValue.Db.PositionsHistorySqlConnectionString)) + .WithParameter(new NamedParameter("tableName", + _settings.CurrentValue.Db.PositionsHistoryTableName)) + .WithParameter(new NamedParameter("getLastSnapshotTimeoutS", + _settings.CurrentValue.Db.QueryTimeouts.GetLastSnapshotTimeoutS)) + .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); + } + + builder.RegisterType().As().SingleInstance(); + + builder.Register(c => + { + var settings = c.Resolve>(); + + return settings.CurrentValue.UseDbIdentityGenerator + ? (IIdentityGenerator) new AzureIdentityGenerator( + AzureTableStorage.Create(settings.Nested(s => s.Db.MarginTradingConnString), + "Identity", _log)) + : (IIdentityGenerator) new SimpleIdentityGenerator(); + }).As().SingleInstance(); + + //SQL PLACE + builder.RegisterType() + .As() + .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); + + + // builder.Register(ctx => +// AzureRepoFactories.MarginTrading.CreateDayOffSettingsRepository( +// _settings.Nested(s => s.Db.MarginTradingConnString))).SingleInstance(); + +// builder.Register(ctx => +// AzureRepoFactories.MarginTrading.CreateOrdersByIdRepository( +// _settings.Nested(s => s.Db.MarginTradingConnString), _log, ctx.Resolve())) +// .SingleInstance(); + + // builder.Register(ctx => +// AzureRepoFactories.MarginTrading.CreateRiskSystemCommandsLogRepository( +// _settings.Nested(s => s.Db.LogsConnString), _log)).SingleInstance(); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend/Modules/BackendServicesModule.cs b/src/MarginTrading.Backend/Modules/BackendServicesModule.cs index 0ffe6678b..fbc398195 100644 --- a/src/MarginTrading.Backend/Modules/BackendServicesModule.cs +++ b/src/MarginTrading.Backend/Modules/BackendServicesModule.cs @@ -1,37 +1,42 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.IO; using Autofac; using Common; using Common.Log; +using Lykke.Common.Chaos; using Lykke.RabbitMqBroker.Publisher; using Lykke.RabbitMqBroker.Subscriber; using MarginTrading.Backend.Email; using MarginTrading.Backend.Middleware.Validator; using MarginTrading.Common.RabbitMq; using Microsoft.AspNetCore.Hosting; -using Lykke.Service.EmailSender; using MarginTrading.Backend.Core; +using MarginTrading.Backend.Core.Services; using MarginTrading.Backend.Core.Settings; using MarginTrading.Backend.Services; using MarginTrading.Backend.Services.Events; using MarginTrading.Backend.Services.EventsConsumers; using MarginTrading.Backend.Services.Infrastructure; using MarginTrading.Backend.Services.Quotes; +using MarginTrading.Backend.Services.RabbitMq; +using MarginTrading.Backend.Services.Services; using MarginTrading.Backend.Services.Settings; using MarginTrading.Common.Services; -using MarginTrading.Common.Services.Client; namespace MarginTrading.Backend.Modules { public class BackendServicesModule : Module { private readonly MtBackendSettings _mtSettings; - private readonly MarginSettings _settings; + private readonly MarginTradingSettings _settings; private readonly IHostingEnvironment _environment; private readonly ILog _log; - public BackendServicesModule(MtBackendSettings mtSettings, MarginSettings settings, IHostingEnvironment environment, ILog log) + public BackendServicesModule(MtBackendSettings mtSettings, MarginTradingSettings settings, IHostingEnvironment environment, ILog log) { _mtSettings = mtSettings; _settings = settings; @@ -58,18 +63,21 @@ protected override void Load(ContainerBuilder builder) new MustacheTemplateGenerator(_environment, Path.Combine("Email","Templates")) ).SingleInstance(); - builder.Register(ctx => - new EmailSenderClient(_mtSettings.EmailSender.ServiceUrl, _log) - ).SingleInstance(); - var consoleWriter = new ConsoleLWriter(Console.WriteLine); builder.RegisterInstance(consoleWriter) .As() .SingleInstance(); - builder.RegisterType() - .As() + if (_settings.WriteOperationLog && _settings.UseSerilog) + { + _log.WriteWarning(nameof(BackendServicesModule), nameof(Load), + $"Operations log will not be written, because {nameof(_settings.UseSerilog)} is enabled."); + } + + builder.RegisterType() + .As() + .WithParameter(new TypedParameter(typeof(bool), _settings.WriteOperationLog && !_settings.UseSerilog)) .SingleInstance(); builder.RegisterType() @@ -81,10 +89,6 @@ protected override void Load(ContainerBuilder builder) .SingleInstance() .OnActivated(args => args.Instance.StartApplicationAsync().Wait()); - builder.RegisterType() - .As() - .SingleInstance(); - builder.RegisterType() .As() .SingleInstance(); @@ -92,7 +96,7 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType() .As>() .As>() - .As>() + .As>() .As>() .SingleInstance(); @@ -104,6 +108,17 @@ protected override void Load(ContainerBuilder builder) .As() .SingleInstance(); + builder.RegisterType() + .As() + .PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies) + .SingleInstance(); + + builder.RegisterChaosKitty(_settings.ChaosKitty); + + builder.RegisterType() + .As() + .SingleInstance(); + RegisterPublishers(builder, consoleWriter); } @@ -111,18 +126,13 @@ private void RegisterPublishers(ContainerBuilder builder, IConsole consoleWriter { var publishers = new List { - _settings.RabbitMqQueues.AccountHistory.ExchangeName, _settings.RabbitMqQueues.OrderHistory.ExchangeName, - _settings.RabbitMqQueues.OrderRejected.ExchangeName, _settings.RabbitMqQueues.OrderbookPrices.ExchangeName, - _settings.RabbitMqQueues.OrderChanged.ExchangeName, - _settings.RabbitMqQueues.AccountChanged.ExchangeName, - _settings.RabbitMqQueues.AccountStopout.ExchangeName, - _settings.RabbitMqQueues.UserUpdates.ExchangeName, _settings.RabbitMqQueues.AccountMarginEvents.ExchangeName, _settings.RabbitMqQueues.AccountStats.ExchangeName, _settings.RabbitMqQueues.Trades.ExchangeName, - _settings.RabbitMqQueues.ExternalOrder.ExchangeName + _settings.RabbitMqQueues.PositionHistory.ExchangeName, + _settings.RabbitMqQueues.ExternalOrder.ExchangeName, }; var bytesSerializer = new BytesStringSerializer(); diff --git a/src/MarginTrading.Backend/Modules/BackendSettingsModule.cs b/src/MarginTrading.Backend/Modules/BackendSettingsModule.cs index 5b2fb042a..c86966973 100644 --- a/src/MarginTrading.Backend/Modules/BackendSettingsModule.cs +++ b/src/MarginTrading.Backend/Modules/BackendSettingsModule.cs @@ -1,4 +1,7 @@ -using Autofac; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Autofac; using Lykke.SettingsReader; using MarginTrading.Backend.Core.Settings; using MarginTrading.Backend.Services.Settings; @@ -7,22 +10,23 @@ namespace MarginTrading.Backend.Modules { public class BackendSettingsModule : Module { - private readonly MtBackendSettings _mtSettings; - private readonly IReloadingManager _settings; + private readonly IReloadingManager _settings; - public BackendSettingsModule(MtBackendSettings mtSettings, IReloadingManager settings) + public BackendSettingsModule(IReloadingManager settings) { - _mtSettings = mtSettings; _settings = settings; } protected override void Load(ContainerBuilder builder) { - builder.RegisterInstance(_mtSettings.EmailSender).SingleInstance(); - builder.RegisterInstance(_mtSettings.Jobs).SingleInstance(); - builder.RegisterInstance(_settings).SingleInstance(); - builder.RegisterInstance(_settings.CurrentValue).SingleInstance(); - builder.RegisterInstance(_settings.CurrentValue.RequestLoggerSettings).SingleInstance(); + builder.RegisterInstance(_settings.Nested(s => s.MtBackend)).SingleInstance(); + builder.RegisterInstance(_settings.CurrentValue.MtBackend).SingleInstance(); + builder.RegisterInstance(_settings.CurrentValue.MtStpExchangeConnectorClient).SingleInstance(); + builder.RegisterInstance(_settings.CurrentValue.MtBackend.RequestLoggerSettings).SingleInstance(); + builder.RegisterInstance(_settings.CurrentValue.MtBackend.SpecialLiquidation).SingleInstance(); + builder.RegisterInstance(_settings.CurrentValue.RiskInformingSettings ?? + new RiskInformingSettings {Data = new RiskInformingParams[0]}).SingleInstance(); + builder.RegisterInstance(_settings.CurrentValue.MtBackend.OvernightMargin).SingleInstance(); } } } diff --git a/src/MarginTrading.Backend/Modules/ExternalServicesModule.cs b/src/MarginTrading.Backend/Modules/ExternalServicesModule.cs new file mode 100644 index 000000000..77d874d9e --- /dev/null +++ b/src/MarginTrading.Backend/Modules/ExternalServicesModule.cs @@ -0,0 +1,169 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using Autofac; +using Common.Log; +using Lykke.HttpClientGenerator; +using Lykke.HttpClientGenerator.Retries; +using Lykke.MarginTrading.OrderBookService.Contracts; +using Lykke.Service.ClientAccount.Client; +using Lykke.Service.EmailSender; +using Lykke.SettingsReader; +using Lykke.Snow.Common.Startup; +using MarginTrading.AccountsManagement.Contracts; +using MarginTrading.Backend.Contracts.ExchangeConnector; +using MarginTrading.Backend.Core.Settings; +using MarginTrading.Backend.Infrastructure; +using MarginTrading.Backend.Services.FakeExchangeConnector; +using MarginTrading.Backend.Services.Settings; +using MarginTrading.Backend.Services.Stubs; +using MarginTrading.Common.Services.Client; +using MarginTrading.SettingsService.Contracts; + +namespace MarginTrading.Backend.Modules +{ + public class ExternalServicesModule : Module + { + private readonly IReloadingManager _settings; + + public ExternalServicesModule(IReloadingManager settings) + { + _settings = settings; + } + + protected override void Load(ContainerBuilder builder) + { + if (_settings.CurrentValue.MtBackend.ExchangeConnector == ExchangeConnectorType.RealExchangeConnector) + { + var gavelClientGenerator = HttpClientGenerator + .BuildForUrl(_settings.CurrentValue.MtStpExchangeConnectorClient.ServiceUrl) + .WithServiceName( + $"Gavel [{_settings.CurrentValue.MtStpExchangeConnectorClient.ServiceUrl}]") + .WithApiKey(_settings.CurrentValue.MtStpExchangeConnectorClient.ApiKey) + .WithoutRetries() + .Create(); + + builder.RegisterInstance(gavelClientGenerator.Generate()) + .As() + .SingleInstance(); + } + if (_settings.CurrentValue.MtBackend.ExchangeConnector == ExchangeConnectorType.FakeExchangeConnector) + { + builder.RegisterType() + .As() + .SingleInstance(); + } + + #region Client Account Service + + if (_settings.CurrentValue.ClientAccountServiceClient != null) + { + builder.RegisterLykkeServiceClient(_settings.CurrentValue.ClientAccountServiceClient.ServiceUrl); + + builder.RegisterType() + .As() + .SingleInstance(); + } + else + { + builder.RegisterType() + .As() + .SingleInstance(); + } + + #endregion + + #region Email Sender + + if (_settings.CurrentValue.EmailSender != null) + { + builder.Register(ctx => + new EmailSenderClient(_settings.CurrentValue.EmailSender.ServiceUrl, ctx.Resolve()) + ).SingleInstance(); + } + else + { + builder.RegisterType().As().SingleInstance(); + } + + #endregion + + #region MT Settings + + var settingsClientGeneratorBuilder = HttpClientGenerator + .BuildForUrl(_settings.CurrentValue.SettingsServiceClient.ServiceUrl) + .WithServiceName( + $"MT Settings [{_settings.CurrentValue.SettingsServiceClient.ServiceUrl}]") + .WithRetriesStrategy(new LinearRetryStrategy(TimeSpan.FromMilliseconds(300), 3)); + + if (!string.IsNullOrWhiteSpace(_settings.CurrentValue.SettingsServiceClient.ApiKey)) + { + settingsClientGeneratorBuilder = settingsClientGeneratorBuilder + .WithApiKey(_settings.CurrentValue.SettingsServiceClient.ApiKey); + } + + var settingsClientGenerator = settingsClientGeneratorBuilder.Create(); + + builder.RegisterInstance(settingsClientGenerator.Generate()) + .As().SingleInstance(); + + builder.RegisterInstance(settingsClientGenerator.Generate()) + .As().SingleInstance(); + + builder.RegisterInstance(settingsClientGenerator.Generate()) + .As().SingleInstance(); + + builder.RegisterInstance(settingsClientGenerator.Generate()) + .As().SingleInstance(); + + builder.RegisterInstance(settingsClientGenerator.Generate()) + .As().SingleInstance(); + + builder.RegisterInstance(settingsClientGenerator.Generate()) + .As().SingleInstance(); + + builder.RegisterInstance(settingsClientGenerator.Generate()) + .As().SingleInstance(); + + #endregion + + #region MT Accounts Management + + var accountsClientGeneratorBuilder = HttpClientGenerator + .BuildForUrl(_settings.CurrentValue.AccountsManagementServiceClient.ServiceUrl) + .WithServiceName( + $"MT Account Management [{_settings.CurrentValue.AccountsManagementServiceClient.ServiceUrl}]"); + + if (!string.IsNullOrWhiteSpace(_settings.CurrentValue.AccountsManagementServiceClient.ApiKey)) + { + accountsClientGeneratorBuilder = accountsClientGeneratorBuilder + .WithApiKey(_settings.CurrentValue.AccountsManagementServiceClient.ApiKey); + } + + builder.RegisterInstance(accountsClientGeneratorBuilder.Create().Generate()) + .As().SingleInstance(); + + #endregion + + #region OrderBook Service + + var orderBookServiceClientGeneratorBuilder = HttpClientGenerator + .BuildForUrl(_settings.CurrentValue.OrderBookServiceClient.ServiceUrl) + .WithServiceName( + $"MT OrderBook Service [{_settings.CurrentValue.OrderBookServiceClient.ServiceUrl}]"); + + if (!string.IsNullOrWhiteSpace(_settings.CurrentValue.OrderBookServiceClient.ApiKey)) + { + orderBookServiceClientGeneratorBuilder = orderBookServiceClientGeneratorBuilder + .WithApiKey(_settings.CurrentValue.OrderBookServiceClient.ApiKey); + } + + builder.RegisterInstance(orderBookServiceClientGeneratorBuilder.Create().Generate()) + .As() + .SingleInstance(); + + #endregion OrderBook Service + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend/Program.cs b/src/MarginTrading.Backend/Program.cs index b9f6981a1..5ad1c1d76 100644 --- a/src/MarginTrading.Backend/Program.cs +++ b/src/MarginTrading.Backend/Program.cs @@ -1,44 +1,73 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.IO; using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; using MarginTrading.Common.Services; +using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.PlatformAbstractions; #pragma warning disable 1591 namespace MarginTrading.Backend { + [UsedImplicitly] public class Program { - public static void Main(string[] args) + internal static IWebHost Host { get; private set; } + + public static async Task Main(string[] args) { - var restartAttempsLeft = 5; + AppDomain.CurrentDomain.UnhandledException += (sender, e) => + { + LogLocator.CommonLog?.WriteFatalErrorAsync("UnhandledException", nameof(AppDomain), (Exception)e.ExceptionObject); + }; + + Console.WriteLine($@"{PlatformServices.Default.Application.ApplicationName} version {PlatformServices.Default.Application.ApplicationVersion}"); + + var restartAttemptsLeft = int.TryParse(Environment.GetEnvironmentVariable("RESTART_ATTEMPTS_NUMBER"), + out var restartAttemptsFromEnv) + ? restartAttemptsFromEnv + : int.MaxValue; + var restartAttemptsInterval = int.TryParse(Environment.GetEnvironmentVariable("RESTART_ATTEMPTS_INTERVAL_MS"), + out var restartAttemptsIntervalFromEnv) + ? restartAttemptsIntervalFromEnv + : 10000; - while (restartAttempsLeft > 0) + while (restartAttemptsLeft > 0) { try { - var host = new WebHostBuilder() - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseUrls("http://*:5000") + var configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", optional: true) + .AddUserSecrets() + .AddEnvironmentVariables() + .Build(); + + Host = WebHost.CreateDefaultBuilder() + .UseConfiguration(configuration) .UseStartup() .UseApplicationInsights() .Build(); - host.Run(); - - restartAttempsLeft = 0; + await Host.RunAsync(); } catch (Exception e) { - Console.WriteLine($"Error: {e.Message}{Environment.NewLine}{e.StackTrace}{Environment.NewLine}Restarting..."); + Console.WriteLine($@"Error: {e.Message}{Environment.NewLine}{e.StackTrace}{Environment.NewLine}Restarting..."); LogLocator.CommonLog?.WriteFatalErrorAsync( - "MT Backend", "Restart host", $"Attempts left: {restartAttempsLeft}", e); - restartAttempsLeft--; - Thread.Sleep(10000); + "MT Backend", "Restart host", $"Attempts left: {restartAttemptsLeft}", e); + restartAttemptsLeft--; + Thread.Sleep(restartAttemptsInterval); } } + + Console.WriteLine(@"Terminated"); } } } diff --git a/src/MarginTrading.Backend/Properties/AssemblyInfo.cs b/src/MarginTrading.Backend/Properties/AssemblyInfo.cs index fb7e96c51..1a1b424a2 100644 --- a/src/MarginTrading.Backend/Properties/AssemblyInfo.cs +++ b/src/MarginTrading.Backend/Properties/AssemblyInfo.cs @@ -1,5 +1,7 @@ -using System.Reflection; -using System.Runtime.CompilerServices; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Reflection; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/src/MarginTrading.Backend/Scheduling/OvernightSwapJob.cs b/src/MarginTrading.Backend/Scheduling/OvernightSwapJob.cs deleted file mode 100644 index 6fd092dfa..000000000 --- a/src/MarginTrading.Backend/Scheduling/OvernightSwapJob.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using FluentScheduler; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Services; - -namespace MarginTrading.Backend.Scheduling -{ - /// - /// Overnight swaps calculation job. - /// Take into account, that scheduler might fire the job with delay of 100ms. - /// - public class OvernightSwapJob : IJob, IDisposable - { - - public OvernightSwapJob() - { - } - - public void Execute() - { - MtServiceLocator.OvernightSwapService.CalculateAndChargeSwaps(); - } - - public void Dispose() - { - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Backend/Startup.cs b/src/MarginTrading.Backend/Startup.cs index 778157e17..5179f921f 100644 --- a/src/MarginTrading.Backend/Startup.cs +++ b/src/MarginTrading.Backend/Startup.cs @@ -1,39 +1,57 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Autofac; using Autofac.Extensions.DependencyInjection; using Common.Log; +using FluentScheduler; +using JetBrains.Annotations; using Lykke.AzureQueueIntegration; -using Lykke.Common.ApiLibrary.Middleware; +using Lykke.Common; using Lykke.Common.ApiLibrary.Swagger; using Lykke.Logs; +using Lykke.Logs.MsSql; +using Lykke.Logs.MsSql.Repositories; +using Lykke.Logs.Serilog; using Lykke.SettingsReader; using Lykke.SlackNotification.AzureQueue; using Lykke.SlackNotifications; +using Lykke.Snow.Common.Startup.Hosting; +using Lykke.Snow.Common.Startup.Log; +using MarginTrading.AzureRepositories; using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.MatchingEngines; +using MarginTrading.Backend.Core.Services; using MarginTrading.Backend.Core.Settings; using MarginTrading.Backend.Filters; using MarginTrading.Backend.Infrastructure; using MarginTrading.Backend.Middleware; using MarginTrading.Backend.Modules; using MarginTrading.Backend.Services; +using MarginTrading.Backend.Services.AssetPairs; +using MarginTrading.Backend.Services.Caches; using MarginTrading.Backend.Services.Infrastructure; -using MarginTrading.Backend.Services.MatchingEngines; using MarginTrading.Backend.Services.Modules; using MarginTrading.Backend.Services.Quotes; +using MarginTrading.Backend.Services.Scheduling; using MarginTrading.Backend.Services.Settings; +using MarginTrading.Backend.Services.Stp; +using MarginTrading.Backend.Services.Stubs; using MarginTrading.Backend.Services.TradingConditions; using MarginTrading.Common.Extensions; -using MarginTrading.Common.Json; using MarginTrading.Common.Modules; using MarginTrading.Common.Services; -using Microsoft.ApplicationInsights.Extensibility; +using MarginTrading.SqlRepositories; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; +using StackExchange.Redis; using GlobalErrorHandlerMiddleware = MarginTrading.Backend.Middleware.GlobalErrorHandlerMiddleware; using LogLevel = Microsoft.Extensions.Logging.LogLevel; @@ -51,7 +69,7 @@ public Startup(IHostingEnvironment env) { Configuration = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) - .AddDevJson(env) + .AddSerilogJson(env) .AddEnvironmentVariables() .Build(); @@ -60,86 +78,87 @@ public Startup(IHostingEnvironment env) public IServiceProvider ConfigureServices(IServiceCollection services) { - var loggerFactory = new LoggerFactory() - .AddConsole(LogLevel.Error) - .AddDebug(LogLevel.Error); - - services.AddSingleton(loggerFactory); - services.AddLogging(); services.AddSingleton(Configuration); - services.AddMvc(options => options.Filters.Add(typeof(MarginTradingEnabledFilter))) - .AddJsonOptions( - options => { options.SerializerSettings.Converters = SerializerSettings.GetDefaultConverters(); }); + services.AddMvc() + .AddJsonOptions(options => + { + options.SerializerSettings.ContractResolver = new DefaultContractResolver(); + options.SerializerSettings.Converters.Add(new StringEnumConverter()); + }); + services.AddScoped(); services.AddAuthentication(KeyAuthOptions.AuthenticationScheme) .AddScheme(KeyAuthOptions.AuthenticationScheme, "", options => { }); - var isLive = Configuration.IsLive(); - services.AddSwaggerGen(options => { - options.DefaultLykkeConfiguration("v1", $"MarginTrading_Api_{Configuration.ServerType()}"); + options.DefaultLykkeConfiguration("v1", $"MarginTradingEngine_Api_{Configuration.ServerType()}"); options.OperationFilter(); }); var builder = new ContainerBuilder(); - var envSuffix = !string.IsNullOrEmpty(Configuration["Env"]) ? "." + Configuration["Env"] : ""; - var mtSettings = Configuration.LoadSettings() + var mtSettings = Configuration.LoadSettings( + throwExceptionOnCheckError: !Configuration.NotThrowExceptionsOnServiceValidation()) .Nested(s => { - var inner = isLive ? s.MtBackend.MarginTradingLive : s.MtBackend.MarginTradingDemo; - inner.IsLive = isLive; - inner.Env = Configuration.ServerType() + envSuffix; + s.MtBackend.Env = Configuration.ServerType(); return s; }); - var settings = - mtSettings.Nested(s => isLive ? s.MtBackend.MarginTradingLive : s.MtBackend.MarginTradingDemo); - var riskInformingSettings = - mtSettings.Nested(s => isLive ? s.RiskInformingSettings : s.RiskInformingSettingsDemo); - - Console.WriteLine($"IsLive: {settings.CurrentValue.IsLive}"); + SetupLoggers(Configuration, services, mtSettings); - SetupLoggers(services, mtSettings, settings); + var deduplicationService = RunHealthChecks(mtSettings.CurrentValue.MtBackend); + builder.RegisterInstance(deduplicationService).AsSelf().As().SingleInstance(); - RegisterModules(builder, mtSettings, settings, Environment, riskInformingSettings); + RegisterModules(builder, mtSettings, Environment); builder.Populate(services); - + ApplicationContainer = builder.Build(); MtServiceLocator.FplService = ApplicationContainer.Resolve(); MtServiceLocator.AccountUpdateService = ApplicationContainer.Resolve(); MtServiceLocator.AccountsCacheService = ApplicationContainer.Resolve(); MtServiceLocator.SwapCommissionService = ApplicationContainer.Resolve(); - MtServiceLocator.OvernightSwapService = ApplicationContainer.Resolve(); + + ApplicationContainer.Resolve() + .UpdateAllSettingsAsync().GetAwaiter().GetResult(); + + InitializeJobs(); return new AutofacServiceProvider(ApplicationContainer); } - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, - IApplicationLifetime appLifetime) + [UsedImplicitly] + public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime appLifetime) { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + app.UseMiddleware(); app.UseMiddleware(); app.UseAuthentication(); app.UseMvc(); - app.UseSwagger(); - app.UseSwaggerUi(); + app.UseSwagger(c => + { + c.PreSerializeFilters.Add((swagger, httpReq) => swagger.Host = httpReq.Host.Value); + }); + app.UseSwaggerUI(a => a.SwaggerEndpoint("/swagger/v1/swagger.json", "Trading Engine API Swagger")); appLifetime.ApplicationStopped.Register(() => ApplicationContainer.Dispose()); - - var application = app.ApplicationServices.GetService(); - var settings = app.ApplicationServices.GetService(); + var application = app.ApplicationServices.GetService(); - appLifetime.ApplicationStarted.Register(() => + appLifetime.ApplicationStarted.Register(async () => { - if (!string.IsNullOrEmpty(settings.ApplicationInsightsKey)) - { - TelemetryConfiguration.Active.InstrumentationKey = settings.ApplicationInsightsKey; - } + await Program.Host.WriteLogsAsync(Environment, LogLocator.CommonLog); LogLocator.CommonLog?.WriteMonitorAsync("", "", $"{Configuration.ServerType()} Started"); }); @@ -152,60 +171,146 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF ); } - private void RegisterModules(ContainerBuilder builder, IReloadingManager mtSettings, - IReloadingManager settings, IHostingEnvironment environment, - IReloadingManager riskInformingSettings) + private static void RegisterModules(ContainerBuilder builder, IReloadingManager mtSettings, + IHostingEnvironment environment) { + var settings = mtSettings.Nested(x => x.MtBackend); + builder.RegisterModule(new BaseServicesModule(mtSettings.CurrentValue, LogLocator.CommonLog)); - builder.RegisterModule(new BackendSettingsModule(mtSettings.CurrentValue, settings)); + builder.RegisterModule(new BackendSettingsModule(mtSettings)); builder.RegisterModule(new BackendRepositoriesModule(settings, LogLocator.CommonLog)); builder.RegisterModule(new EventModule()); builder.RegisterModule(new CacheModule()); builder.RegisterModule(new ManagersModule()); - builder.RegisterModule(new ServicesModule(riskInformingSettings)); + builder.RegisterModule(new ServicesModule()); builder.RegisterModule(new BackendServicesModule(mtSettings.CurrentValue, settings.CurrentValue, environment, LogLocator.CommonLog)); builder.RegisterModule(new MarginTradingCommonModule()); builder.RegisterModule(new ExternalServicesModule(mtSettings)); builder.RegisterModule(new BackendMigrationsModule()); + builder.RegisterModule(new CqrsModule(settings.CurrentValue.Cqrs, LogLocator.CommonLog, settings.CurrentValue)); - builder.RegisterBuildCallback(c => c.Resolve()); - builder.RegisterBuildCallback(c => c.Resolve()); - builder.RegisterBuildCallback(c => c.Resolve()); - builder.RegisterBuildCallback(c => c.Resolve()); - builder.RegisterBuildCallback(c => c.Resolve()); // note the order here is important! - builder.RegisterBuildCallback(c => c.Resolve()); - builder.RegisterBuildCallback(c => c.Resolve()); - builder.RegisterBuildCallback(c => c.Resolve()); + builder.RegisterBuildCallback(async c => + { + // note the order here is important! + c.Resolve(); + c.Resolve(); + await c.Resolve().InitializeAsync(); + c.Resolve().Start(); + c.Resolve(); + c.Resolve(); + c.Resolve(); + c.Resolve(); + }); } - private static void SetupLoggers(IServiceCollection services, IReloadingManager mtSettings, - IReloadingManager settings) + private static void SetupLoggers(IConfiguration configuration, IServiceCollection services, + IReloadingManager mtSettings) { + var settings = mtSettings.Nested(x => x.MtBackend); + const string requestsLogName = "MarginTradingBackendRequestsLog"; + const string logName = "MarginTradingBackendLog"; var consoleLogger = new LogToConsole(); - var azureQueue = new AzureQueueSettings + #region Logs settings validation + + if (!settings.CurrentValue.UseSerilog && string.IsNullOrWhiteSpace(settings.CurrentValue.Db.LogsConnString)) + { + throw new Exception("Either UseSerilog must be true or LogsConnString must be set"); + } + + #endregion Logs settings validation + + #region Slack registration + + IMtSlackNotificationsSender slackService = null; + + if (mtSettings.CurrentValue.SlackNotifications != null) + { + var azureQueue = new AzureQueueSettings + { + ConnectionString = mtSettings.CurrentValue.SlackNotifications.AzureQueue.ConnectionString, + QueueName = mtSettings.CurrentValue.SlackNotifications.AzureQueue.QueueName + }; + + var commonSlackService = + services.UseSlackNotificationsSenderViaAzureQueue(azureQueue, consoleLogger); + + slackService = + new MtSlackNotificationsSender(commonSlackService, "MT Backend", settings.CurrentValue.Env); + } + + #endregion Slack registration + + if (settings.CurrentValue.UseSerilog) { - ConnectionString = mtSettings.CurrentValue.SlackNotifications.AzureQueue.ConnectionString, - QueueName = mtSettings.CurrentValue.SlackNotifications.AzureQueue.QueueName - }; + LogLocator.RequestsLog = LogLocator.CommonLog = new SerilogLogger(typeof(Startup).Assembly, configuration); + } + else if (settings.CurrentValue.Db.StorageMode == StorageMode.SqlServer) + { + LogLocator.RequestsLog = new AggregateLogger( + new LogToSql(new SqlLogRepository(requestsLogName, + settings.CurrentValue.Db.LogsConnString)), + new LogToConsole()); + + LogLocator.CommonLog = new AggregateLogger( + new LogToSql(new SqlLogRepository(logName, + settings.CurrentValue.Db.LogsConnString)), + new LogToConsole()); + } + else if (settings.CurrentValue.Db.StorageMode == StorageMode.Azure) + { + if (slackService == null) + { + slackService = + new MtSlackNotificationsSenderLogStub("MT Backend", settings.CurrentValue.Env, consoleLogger); + } + + LogLocator.RequestsLog = services.UseLogToAzureStorage(settings.Nested(s => s.Db.LogsConnString), + slackService, requestsLogName, consoleLogger); - var commonSlackService = - services.UseSlackNotificationsSenderViaAzureQueue(azureQueue, consoleLogger); + LogLocator.CommonLog = services.UseLogToAzureStorage(settings.Nested(s => s.Db.LogsConnString), + slackService, logName, consoleLogger); + } - var slackService = - new MtSlackNotificationsSender(commonSlackService, "MT Backend", settings.CurrentValue.Env); + if (slackService == null) + { + slackService = + new MtSlackNotificationsSenderLogStub("MT Backend", settings.CurrentValue.Env, LogLocator.CommonLog); + } services.AddSingleton(slackService); services.AddSingleton(slackService); - // Order of logs registration is important - UseLogToAzureStorage() registers ILog in container. - // Last registration wins. - LogLocator.RequestsLog = services.UseLogToAzureStorage(settings.Nested(s => s.Db.LogsConnString), - slackService, "MarginTradingBackendRequestsLog", consoleLogger); + services.AddSingleton(x => new WebHostLoggerFactory(LogLocator.CommonLog)); + } + + /// + /// Initialize scheduled jobs. Each job will start in time with dispersion of 100ms. + /// + private void InitializeJobs() + { + JobManager.UseUtcTime(); + JobManager.Initialize(); + + JobManager.AddJob(() => ApplicationContainer.Resolve().Execute(), + s => s.NonReentrant().ToRunEvery(1).Days().At(0, 0)); + + ApplicationContainer.Resolve().ScheduleNext(); + ApplicationContainer.Resolve().ScheduleNext(); + } + + private StartupDeduplicationService RunHealthChecks(MarginTradingSettings marginTradingSettings) + { + var deduplicationService = new StartupDeduplicationService(Environment, LogLocator.CommonLog, + marginTradingSettings); + deduplicationService + .HoldLock(); + + new StartupQueuesCheckerService(marginTradingSettings) + .Check(); - LogLocator.CommonLog = services.UseLogToAzureStorage(settings.Nested(s => s.Db.LogsConnString), - slackService, "MarginTradingBackendLog", consoleLogger); + return deduplicationService; } } } \ No newline at end of file diff --git a/src/MarginTrading.Backend/appsettings.Serilog.json b/src/MarginTrading.Backend/appsettings.Serilog.json new file mode 100644 index 000000000..bcad4dcbd --- /dev/null +++ b/src/MarginTrading.Backend/appsettings.Serilog.json @@ -0,0 +1,35 @@ +{ + "serilog": { + "Using": [ "Serilog.Sinks.File", "Serilog.Sinks.Async" ], + "minimumLevel": { + "default": "Debug" + }, + "writeTo": [ + { + "Name": "Async", + "Args": { + "configure": [ + { + "Name": "Console", + "Args": { + "outputTemplate": "[{Timestamp:u}] [{Level:u3}] [{Component}:{Process}:{Context}] - {info} {Message:lj} {NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "outputTemplate": "[{Timestamp:u}] [{Level:u3}] [{Component}:{Process}:{Context}] - {info} {Message:lj} {NewLine}{Exception}", + "path": "logs/MTCore/BackendLog.log", + "rollingInterval": "Day" + } + } + ] + } + } + ], + "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId", "WithDemystifiedStackTraces" ], + "Properties": { + "Application": "MTCore" + } + } +} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Application.cs b/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Application.cs deleted file mode 100644 index d402e71e4..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Application.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Common.Log; -using Lykke.SlackNotifications; -using MarginTrading.AccountHistoryBroker.Repositories; -using MarginTrading.AccountHistoryBroker.Repositories.Models; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Mappers; -using MarginTrading.BrokerBase; -using MarginTrading.BrokerBase.Settings; -using MarginTrading.Contract.BackendContracts; -using System.Threading.Tasks; - -namespace MarginTrading.AccountHistoryBroker -{ - internal class Application : BrokerApplicationBase - { - private readonly IMarginTradingAccountHistoryRepository _accountHistoryRepository; - private readonly IAccountTransactionsReportsRepository _accountTransactionsReportsRepository; - private readonly Settings _settings; - - public Application(IMarginTradingAccountHistoryRepository accountHistoryRepository, ILog logger, - Settings settings, CurrentApplicationInfo applicationInfo, - IAccountTransactionsReportsRepository accountTransactionsReportsRepository, - ISlackNotificationsSender slackNotificationsSender) - : base(logger, slackNotificationsSender, applicationInfo) - { - _accountHistoryRepository = accountHistoryRepository; - _settings = settings; - _accountTransactionsReportsRepository = accountTransactionsReportsRepository; - } - - protected override BrokerSettingsBase Settings => _settings; - protected override string ExchangeName => _settings.RabbitMqQueues.AccountHistory.ExchangeName; - - protected override Task HandleMessage(AccountHistoryBackendContract accountHistoryContract) - { - var accountHistory = accountHistoryContract.ToAccountHistoryContract(); - var accountTransactionReport = new AccountTransactionsReport - { - AccountId = accountHistoryContract.AccountId, - ClientId = accountHistoryContract.ClientId, - Comment = accountHistoryContract.Comment, - Id = accountHistoryContract.Id, - Amount = (double) accountHistoryContract.Amount, - Balance = (double) accountHistoryContract.Balance, - Date = accountHistoryContract.Date, - Type = accountHistoryContract.Type.ToString(), - WithdrawTransferLimit = (double) accountHistoryContract.WithdrawTransferLimit, - PositionId = accountHistoryContract.OrderId, - LegalEntity = accountHistory.LegalEntity, - AuditLog = accountHistoryContract.AuditLog - }; - - return Task.WhenAll( - _accountHistoryRepository.AddAsync(accountHistory), - _accountTransactionsReportsRepository.InsertOrReplaceAsync(accountTransactionReport)); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Dockerfile b/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Dockerfile deleted file mode 100644 index fd248b670..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM microsoft/aspnetcore:2.0 -WORKDIR /app -COPY . . -ENTRYPOINT ["dotnet", "MarginTrading.AccountHistoryBroker.dll"] diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/MarginTrading.AccountHistoryBroker.csproj b/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/MarginTrading.AccountHistoryBroker.csproj deleted file mode 100644 index ce87da7f5..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/MarginTrading.AccountHistoryBroker.csproj +++ /dev/null @@ -1,45 +0,0 @@ - - - netcoreapp2.0 - MarginTrading.AccountHistoryBroker - Exe - MarginTrading.AccountHistoryBroker - false - false - false - 1.0.1 - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Program.cs b/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Program.cs deleted file mode 100644 index f077c3447..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MarginTrading.BrokerBase; - -namespace MarginTrading.AccountHistoryBroker -{ - public class Program : WebAppProgramBase - { - public static void Main(string[] args) - { - RunOnPort(5011); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Properties/AssemblyInfo.cs b/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Properties/AssemblyInfo.cs deleted file mode 100644 index bc724708d..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -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: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Lykke")] -[assembly: AssemblyProduct("MarginTrading.AccountHistoryBroker")] -[assembly: AssemblyTrademark("")] - -// 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("12173ef3-07da-4679-8ebb-bb4396a1864f")] diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/AzureRepositories/AccountTransactionsReportsEntity.cs b/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/AzureRepositories/AccountTransactionsReportsEntity.cs deleted file mode 100644 index c8a592a8f..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/AzureRepositories/AccountTransactionsReportsEntity.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using MarginTrading.AccountHistoryBroker.Repositories.Models; -using Microsoft.WindowsAzure.Storage.Table; - -namespace MarginTrading.AccountHistoryBroker.Repositories.AzureRepositories -{ - internal class AccountTransactionsReportsEntity : TableEntity, IAccountTransactionsReport - { - public string Id - { - get => RowKey; - set => RowKey = value; - } - - public string AccountId - { - get => PartitionKey; - set => PartitionKey = value; - } - - public double Amount { get; set; } - public double Balance { get; set; } - public string ClientId { get; set; } - public string Comment { get; set; } - public DateTime Date { get; set; } - public string Type { get; set; } - public double WithdrawTransferLimit { get; set; } - public string PositionId { get; set; } - public string LegalEntity { get; set; } - public string AuditLog { get; set; } - - public static AccountTransactionsReportsEntity Create(IAccountTransactionsReport src) - { - return new AccountTransactionsReportsEntity - { - Id = src.Id, - Date = src.Date, - AccountId = src.AccountId, - ClientId = src.ClientId, - Amount = src.Amount, - Balance = src.Balance, - WithdrawTransferLimit = src.WithdrawTransferLimit, - Comment = src.Comment, - Type = src.Type, - PositionId = src.PositionId, - LegalEntity = src.LegalEntity, - AuditLog = src.AuditLog - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/AzureRepositories/AccountTransactionsRepository.cs b/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/AzureRepositories/AccountTransactionsRepository.cs deleted file mode 100644 index 1ed9d71e5..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/AzureRepositories/AccountTransactionsRepository.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Threading.Tasks; -using AzureStorage; -using AzureStorage.Tables; -using Common.Log; -using Lykke.SettingsReader; -using MarginTrading.AccountHistoryBroker.Repositories.Models; - -namespace MarginTrading.AccountHistoryBroker.Repositories.AzureRepositories -{ - internal class AccountTransactionsReportsRepository : IAccountTransactionsReportsRepository - { - private readonly INoSQLTableStorage _tableStorage; - - public AccountTransactionsReportsRepository(IReloadingManager settings, ILog log) - { - _tableStorage = AzureTableStorage.Create(settings.Nested(s => s.Db.ReportsConnString), - "MarginTradingAccountTransactionsReports", log); - } - - public Task InsertOrReplaceAsync(IAccountTransactionsReport entity) - { - return _tableStorage.InsertOrReplaceAsync(AccountTransactionsReportsEntity.Create(entity)); - } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/IAccountTransactionsReportsRepository.cs b/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/IAccountTransactionsReportsRepository.cs deleted file mode 100644 index 05cd129e7..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/IAccountTransactionsReportsRepository.cs +++ /dev/null @@ -1,10 +0,0 @@ -using MarginTrading.AccountHistoryBroker.Repositories.Models; -using System.Threading.Tasks; - -namespace MarginTrading.AccountHistoryBroker.Repositories -{ - public interface IAccountTransactionsReportsRepository - { - Task InsertOrReplaceAsync(IAccountTransactionsReport entity); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/Models/AccountTransactionsReport.cs b/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/Models/AccountTransactionsReport.cs deleted file mode 100644 index 0428c8dc9..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/Models/AccountTransactionsReport.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace MarginTrading.AccountHistoryBroker.Repositories.Models -{ - public class AccountTransactionsReport : IAccountTransactionsReport - { - public string AccountId { get; set; } - public double Amount { get; set; } - public double Balance { get; set; } - public string ClientId { get; set; } - public string Comment { get; set; } - public DateTime Date { get; set; } - public string Id { get; set; } - public string PositionId { get; set; } - public string Type { get; set; } - public double WithdrawTransferLimit { get; set; } - public string LegalEntity { get; set; } - public string AuditLog { get; set; } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/Models/IAccountTransactionsReport.cs b/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/Models/IAccountTransactionsReport.cs deleted file mode 100644 index 1b15eaba7..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/Models/IAccountTransactionsReport.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace MarginTrading.AccountHistoryBroker.Repositories.Models -{ - public interface IAccountTransactionsReport - { - string AccountId { get; } - double Amount { get; } - double Balance { get; } - string ClientId { get; } - string Comment { get; } - DateTime Date { get; } - string Id { get; } - string PositionId { get; } - string Type { get; } - double WithdrawTransferLimit { get; } - string LegalEntity { get; } - string AuditLog { get; } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/RepositoryAggregator.cs b/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/RepositoryAggregator.cs deleted file mode 100644 index 7f04a1fab..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/RepositoryAggregator.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using MarginTrading.AccountHistoryBroker.Repositories.Models; - -namespace MarginTrading.AccountHistoryBroker.Repositories -{ - internal class RepositoryAggregator : IAccountTransactionsReportsRepository - { - private readonly List _repositories; - - public RepositoryAggregator(IEnumerable repositories) - { - _repositories = new List(); - _repositories.AddRange(repositories); - } - - public async Task InsertOrReplaceAsync(IAccountTransactionsReport report) - { - foreach (var item in _repositories) - { - await item.InsertOrReplaceAsync(report); - } - } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/SqlRepositories/AccountTransactionsReportsSqlRepository.cs b/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/SqlRepositories/AccountTransactionsReportsSqlRepository.cs deleted file mode 100644 index 2588dfc29..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Repositories/SqlRepositories/AccountTransactionsReportsSqlRepository.cs +++ /dev/null @@ -1,82 +0,0 @@ -using Common; -using Common.Log; -using Dapper; -using MarginTrading.AccountHistoryBroker.Repositories.Models; -using MarginTrading.BrokerBase; -using System; -using System.Data.SqlClient; -using System.Threading.Tasks; - -namespace MarginTrading.AccountHistoryBroker.Repositories.SqlRepositories -{ - public class AccountTransactionsReportsSqlRepository : IAccountTransactionsReportsRepository - { - private const string TableName = "MarginTradingAccountTransactionsReports"; - private const string CreateTableScript = "CREATE TABLE [{0}](" + - "[Id] [nvarchar](64) NOT NULL, " + - "[Date] [datetime] NOT NULL," + - "[ClientId] [nvarchar] (64) NOT NULL, " + - "[AccountId] [nvarchar] (64) NOT NULL, " + - "[PositionId] [text] NULL, " + - "[Amount] float NOT NULL, " + - "[Balance] float NOT NULL, " + - "[Type] [nvarchar] (50) NOT NULL, " + - "[Comment] [text] NOT NULL, " + - "[WithdrawTransferLimit] float NOT NULL, " + - "[AuditLog] [text] NULL, " + - "[LegalEntity] [nvarchar] (64) NULL, " + - "CONSTRAINT[PK_{0}] PRIMARY KEY CLUSTERED ([Id] ASC)" + - ");"; - - private readonly Settings _settings; - private readonly ILog _log; - - public AccountTransactionsReportsSqlRepository(Settings settings, ILog log) - { - _log = log; - _settings = settings; - using (var conn = new SqlConnection(_settings.Db.ReportsSqlConnString)) - { - try { conn.CreateTableIfDoesntExists(CreateTableScript, TableName); } - catch (Exception ex) - { - _log?.WriteErrorAsync("AccountTransactionsReportsSqlRepository", "CreateTableIfDoesntExists", null, ex); - throw; - } - } - } - - public async Task InsertOrReplaceAsync(IAccountTransactionsReport entity) - { - using (var conn = new SqlConnection(_settings.Db.ReportsSqlConnString)) - { - var res = conn.ExecuteScalar($"select Id from {TableName} where Id = '{entity.Id}'"); - string query; - if (res == null) - { - query = $"insert into {TableName} " + - "(Id, Date, AccountId, ClientId, Amount, Balance, WithdrawTransferLimit, Comment, Type, PositionId, LegalEntity, AuditLog) " + - " values " + - "(@Id ,@Date, @AccountId, @ClientId, @Amount, @Balance, @WithdrawTransferLimit, @Comment, @Type, @PositionId, @LegalEntity, @AuditLog)"; - } - else - { - query = $"update {TableName} set " + - "Date=@Date, AccountId=@AccountId, ClientId=@ClientId, Amount=@Amount, Balance=@Balance, " + - "WithdrawTransferLimit=@WithdrawTransferLimit, Comment=@Comment, Type=@Type, " + - "PositionId = @PositionId, LegalEntity = @LegalEntity, AuditLog = @AuditLog" + - " where Id=@Id"; - } - try { await conn.ExecuteAsync(query, entity); } - catch (Exception ex) - { - var msg = $"Error {ex.Message} \n" + - "Entity : \n" + - entity.ToJson(); - await _log?.WriteWarningAsync("AccountTransactionsReportsSqlRepository", "InsertOrReplaceAsync", null, msg); - throw new Exception(msg); - } - } - } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Settings.cs b/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Settings.cs deleted file mode 100644 index c1987925f..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Settings.cs +++ /dev/null @@ -1,23 +0,0 @@ -using MarginTrading.BrokerBase.Settings; -using MarginTrading.Common.RabbitMq; - -namespace MarginTrading.AccountHistoryBroker -{ - public class Settings : BrokerSettingsBase - { - public Db Db { get; set; } - public RabbitMqQueues RabbitMqQueues { get; set; } - } - - public class Db - { - public string HistoryConnString { get; set; } - public string ReportsConnString { get; set; } - public string ReportsSqlConnString { get; set; } - } - - public class RabbitMqQueues - { - public RabbitMqQueueInfo AccountHistory { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Startup.cs b/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Startup.cs deleted file mode 100644 index 55e50ef41..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountHistoryBroker/Startup.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Autofac; -using Common.Log; -using Lykke.SettingsReader; -using MarginTrading.AccountHistoryBroker.Repositories; -using MarginTrading.AccountHistoryBroker.Repositories.AzureRepositories; -using MarginTrading.AccountHistoryBroker.Repositories.SqlRepositories; -using MarginTrading.AzureRepositories; -using MarginTrading.Backend.Core; -using MarginTrading.BrokerBase; -using MarginTrading.BrokerBase.Settings; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; - -namespace MarginTrading.AccountHistoryBroker -{ - public class Startup : BrokerStartupBase, Settings> - { - protected override string ApplicationName => "MarginTradingAccountHistoryBroker"; - - public Startup(IHostingEnvironment env) : base(env) - { - } - - - protected override void RegisterCustomServices(IServiceCollection services, ContainerBuilder builder, IReloadingManager settings, ILog log, bool isLive) - { - builder.RegisterType().As().SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateAccountHistoryRepository(settings.Nested(s => s.Db.HistoryConnString), log) - ).SingleInstance(); - - builder.RegisterInstance(new RepositoryAggregator(new IAccountTransactionsReportsRepository[] - { - new AccountTransactionsReportsSqlRepository(settings.CurrentValue, log), - new AccountTransactionsReportsRepository(settings, log) - })) - .As(); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Application.cs b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Application.cs index 0d1a53822..1e41e669c 100644 --- a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Application.cs +++ b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Application.cs @@ -1,57 +1,61 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; using Common.Log; +using Lykke.MarginTrading.BrokerBase.Settings; using Lykke.SlackNotifications; -using MarginTrading.BrokerBase; -using MarginTrading.BrokerBase.Settings; -using MarginTrading.Contract.RabbitMqMessageModels; +using Lykke.MarginTrading.BrokerBase; using MarginTrading.AccountMarginEventsBroker.Repositories; using MarginTrading.AccountMarginEventsBroker.Repositories.Models; +using MarginTrading.Backend.Contracts.Events; namespace MarginTrading.AccountMarginEventsBroker { - internal class Application : BrokerApplicationBase + internal class Application : BrokerApplicationBase { - private readonly IAccountMarginEventsReportsRepository _accountMarginEventsReportsRepository; + private readonly IAccountMarginEventsRepository _accountMarginEventsRepository; private readonly Settings _settings; public Application(ILog logger, Settings settings, CurrentApplicationInfo applicationInfo, - IAccountMarginEventsReportsRepository accountMarginEventsReportsRepository, + IAccountMarginEventsRepository accountMarginEventsRepository, ISlackNotificationsSender slackNotificationsSender) : base(logger, slackNotificationsSender, applicationInfo) { _settings = settings; - _accountMarginEventsReportsRepository = accountMarginEventsReportsRepository; + _accountMarginEventsRepository = accountMarginEventsRepository; } protected override BrokerSettingsBase Settings => _settings; protected override string ExchangeName => _settings.RabbitMqQueues.AccountMarginEvents.ExchangeName; + public override string RoutingKey => null; - protected override Task HandleMessage(AccountMarginEventMessage message) + protected override Task HandleMessage(MarginEventMessage message) { - return _accountMarginEventsReportsRepository.InsertOrReplaceAsync(new AccountMarginEventReport + return _accountMarginEventsRepository.InsertOrReplaceAsync(new AccountMarginEvent { EventId = message.EventId, EventTime = message.EventTime, - IsEventStopout = message.IsEventStopout, + IsEventStopout = message.EventType == MarginEventTypeContract.Stopout, + EventType = message.EventType, - ClientId = message.ClientId, AccountId = message.AccountId, TradingConditionId = message.TradingConditionId, BaseAssetId = message.BaseAssetId, - Balance = (double) message.Balance, - WithdrawTransferLimit = (double) message.WithdrawTransferLimit, + Balance = message.Balance, + WithdrawTransferLimit = message.WithdrawTransferLimit, - MarginCall = (double) message.MarginCall, - StopOut = (double) message.StopOut, - TotalCapital = (double) message.TotalCapital, - FreeMargin = (double) message.FreeMargin, - MarginAvailable = (double) message.MarginAvailable, - UsedMargin = (double) message.UsedMargin, - MarginInit = (double) message.MarginInit, - PnL = (double) message.PnL, - OpenPositionsCount = (double) message.OpenPositionsCount, - MarginUsageLevel = (double) message.MarginUsageLevel + MarginCall = message.MarginCall1Level, + StopOut = message.StopOutLevel, + TotalCapital = message.TotalCapital, + FreeMargin = message.FreeMargin, + MarginAvailable = message.MarginAvailable, + UsedMargin = message.UsedMargin, + MarginInit = message.MarginInit, + PnL = message.PnL, + OpenPositionsCount = message.OpenPositionsCount, + MarginUsageLevel = message.MarginUsageLevel, }); } } diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Dockerfile b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Dockerfile index a92f18f12..8bb3bf8d6 100644 --- a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Dockerfile +++ b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Dockerfile @@ -1,4 +1,4 @@ -FROM microsoft/aspnetcore:2.0 +FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 WORKDIR /app COPY . . ENTRYPOINT ["dotnet", "MarginTrading.AccountMarginEventsBroker.dll"] diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/MarginTrading.AccountMarginEventsBroker.csproj b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/MarginTrading.AccountMarginEventsBroker.csproj index 852353b7d..8b6c9c341 100644 --- a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/MarginTrading.AccountMarginEventsBroker.csproj +++ b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/MarginTrading.AccountMarginEventsBroker.csproj @@ -1,38 +1,55 @@  Exe - netcoreapp2.0 - 1.0.1 + netcoreapp2.2 + 1.16.29 + 7.3 + + + 1701;1702;1705;CA2007;0612;0618;1591 - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - + + + + - PreserveNewest + + + + + + + + + PreserveNewest + PreserveNewest + + \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Program.cs b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Program.cs index c800a3c49..80dd9a280 100644 --- a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Program.cs +++ b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Program.cs @@ -1,7 +1,12 @@ -using MarginTrading.BrokerBase; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; +using Lykke.MarginTrading.BrokerBase; namespace MarginTrading.AccountMarginEventsBroker { + [UsedImplicitly] public class Program : WebAppProgramBase { public static void Main(string[] args) diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/AzureRepositories/AccountMarginEventReportEntity.cs b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/AzureRepositories/AccountMarginEventEntity.cs similarity index 59% rename from src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/AzureRepositories/AccountMarginEventReportEntity.cs rename to src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/AzureRepositories/AccountMarginEventEntity.cs index d47a10e49..0a0525956 100644 --- a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/AzureRepositories/AccountMarginEventReportEntity.cs +++ b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/AzureRepositories/AccountMarginEventEntity.cs @@ -1,10 +1,15 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using Lykke.AzureStorage.Tables; using MarginTrading.AccountMarginEventsBroker.Repositories.Models; +using MarginTrading.Backend.Contracts.Events; using Microsoft.WindowsAzure.Storage.Table; namespace MarginTrading.AccountMarginEventsBroker.Repositories.AzureRepositories { - internal class AccountMarginEventReportEntity : TableEntity, IAccountMarginEventReport + internal class AccountMarginEventEntity : AzureTableEntity, IAccountMarginEvent { public string EventId { @@ -20,38 +25,38 @@ public string AccountId public DateTime EventTime { get; set; } public bool IsEventStopout { get; set; } + public MarginEventTypeContract EventType { get; set; } - public string ClientId { get; set; } public string TradingConditionId { get; set; } public string BaseAssetId { get; set; } - public double Balance { get; set; } - public double WithdrawTransferLimit { get; set; } + public decimal Balance { get; set; } + public decimal WithdrawTransferLimit { get; set; } - public double MarginCall { get; set; } - public double StopOut { get; set; } - public double TotalCapital { get; set; } - public double FreeMargin { get; set; } - public double MarginAvailable { get; set; } - public double UsedMargin { get; set; } - public double MarginInit { get; set; } - public double PnL { get; set; } - public double OpenPositionsCount { get; set; } - public double MarginUsageLevel { get; set; } + public decimal MarginCall { get; set; } + public decimal StopOut { get; set; } + public decimal TotalCapital { get; set; } + public decimal FreeMargin { get; set; } + public decimal MarginAvailable { get; set; } + public decimal UsedMargin { get; set; } + public decimal MarginInit { get; set; } + public decimal PnL { get; set; } + public decimal OpenPositionsCount { get; set; } + public decimal MarginUsageLevel { get; set; } public string Id => EventId; - public static AccountMarginEventReportEntity Create(IAccountMarginEventReport src) + public static AccountMarginEventEntity Create(IAccountMarginEvent src) { - return new AccountMarginEventReportEntity + return new AccountMarginEventEntity { AccountId = src.AccountId, Balance = src.Balance, BaseAssetId = src.BaseAssetId, - ClientId = src.ClientId, EventId = src.EventId, EventTime = src.EventTime, FreeMargin = src.FreeMargin, IsEventStopout = src.IsEventStopout, + EventType = src.EventType, MarginAvailable = src.MarginAvailable, MarginCall = src.MarginCall, MarginInit = src.MarginInit, diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/AzureRepositories/AccountMarginEventsReportsRepository.cs b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/AzureRepositories/AccountMarginEventsReportsRepository.cs deleted file mode 100644 index ea57871f2..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/AzureRepositories/AccountMarginEventsReportsRepository.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Threading.Tasks; -using AzureStorage; -using AzureStorage.Tables; -using Common.Log; -using Lykke.SettingsReader; -using MarginTrading.AccountMarginEventsBroker.Repositories.Models; - -namespace MarginTrading.AccountMarginEventsBroker.Repositories.AzureRepositories -{ - internal class AccountMarginEventsReportsRepository : IAccountMarginEventsReportsRepository - { - private readonly INoSQLTableStorage _tableStorage; - - public AccountMarginEventsReportsRepository(IReloadingManager settings, ILog log) - { - _tableStorage = AzureTableStorage.Create(settings.Nested(s => s.Db.ReportsConnString), - "AccountMarginEventsReports", log); - } - - public Task InsertOrReplaceAsync(IAccountMarginEventReport entity) - { - return _tableStorage.InsertOrReplaceAsync(AccountMarginEventReportEntity.Create(entity)); - } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/AzureRepositories/AccountMarginEventsRepository.cs b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/AzureRepositories/AccountMarginEventsRepository.cs new file mode 100644 index 000000000..fbf35f42b --- /dev/null +++ b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/AzureRepositories/AccountMarginEventsRepository.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using AzureStorage; +using AzureStorage.Tables; +using Common.Log; +using Lykke.SettingsReader; +using MarginTrading.AccountMarginEventsBroker.Repositories.Models; + +namespace MarginTrading.AccountMarginEventsBroker.Repositories.AzureRepositories +{ + internal class AccountMarginEventsRepository : IAccountMarginEventsRepository + { + private readonly INoSQLTableStorage _tableStorage; + + public AccountMarginEventsRepository(IReloadingManager settings, ILog log) + { + _tableStorage = AzureTableStorage.Create(settings.Nested(s => s.Db.ConnString), + "AccountMarginEvents", log); + } + + public Task InsertOrReplaceAsync(IAccountMarginEvent entity) + { + return _tableStorage.InsertOrReplaceAsync(AccountMarginEventEntity.Create(entity)); + } + } +} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/IAccountMarginEventsReportsRepository.cs b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/IAccountMarginEventsReportsRepository.cs deleted file mode 100644 index bdaf1ce3e..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/IAccountMarginEventsReportsRepository.cs +++ /dev/null @@ -1,10 +0,0 @@ -using MarginTrading.AccountMarginEventsBroker.Repositories.Models; -using System.Threading.Tasks; - -namespace MarginTrading.AccountMarginEventsBroker.Repositories -{ - internal interface IAccountMarginEventsReportsRepository - { - Task InsertOrReplaceAsync(IAccountMarginEventReport report); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/IAccountMarginEventsRepository.cs b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/IAccountMarginEventsRepository.cs new file mode 100644 index 000000000..94b661bfa --- /dev/null +++ b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/IAccountMarginEventsRepository.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.AccountMarginEventsBroker.Repositories.Models; +using System.Threading.Tasks; + +namespace MarginTrading.AccountMarginEventsBroker.Repositories +{ + internal interface IAccountMarginEventsRepository + { + Task InsertOrReplaceAsync(IAccountMarginEvent report); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Contract/RabbitMqMessageModels/AccountMarginEventMessage.cs b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/Models/AccountMarginEvent.cs similarity index 69% rename from src/MarginTrading.Contract/RabbitMqMessageModels/AccountMarginEventMessage.cs rename to src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/Models/AccountMarginEvent.cs index 744ffca13..d044f3db8 100644 --- a/src/MarginTrading.Contract/RabbitMqMessageModels/AccountMarginEventMessage.cs +++ b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/Models/AccountMarginEvent.cs @@ -1,29 +1,32 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. -namespace MarginTrading.Contract.RabbitMqMessageModels +using System; +using MarginTrading.Backend.Contracts.Events; + +namespace MarginTrading.AccountMarginEventsBroker.Repositories.Models { - public class AccountMarginEventMessage + internal class AccountMarginEvent : IAccountMarginEvent { + public string Id => EventId; + public string AccountId { get; set; } + public decimal Balance { get; set; } + public string BaseAssetId { get; set; } public string EventId { get; set; } public DateTime EventTime { get; set; } + public decimal FreeMargin { get; set; } public bool IsEventStopout { get; set; } - - public string AccountId { get; set; } - public string ClientId { get; set; } - public string TradingConditionId { get; set; } - public string BaseAssetId { get; set; } - public decimal Balance { get; set; } - public decimal WithdrawTransferLimit { get; set; } - + public MarginEventTypeContract EventType { get; set; } + public decimal MarginAvailable { get; set; } public decimal MarginCall { get; set; } + public decimal MarginInit { get; set; } + public decimal MarginUsageLevel { get; set; } + public decimal OpenPositionsCount { get; set; } + public decimal PnL { get; set; } public decimal StopOut { get; set; } public decimal TotalCapital { get; set; } - public decimal FreeMargin { get; set; } - public decimal MarginAvailable { get; set; } + public string TradingConditionId { get; set; } public decimal UsedMargin { get; set; } - public decimal MarginInit { get; set; } - public decimal PnL { get; set; } - public decimal OpenPositionsCount { get; set; } - public decimal MarginUsageLevel { get; set; } + public decimal WithdrawTransferLimit { get; set; } } } diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/Models/AccountMarginEventReport.cs b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/Models/AccountMarginEventReport.cs deleted file mode 100644 index 2eaecdeb5..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/Models/AccountMarginEventReport.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace MarginTrading.AccountMarginEventsBroker.Repositories.Models -{ - internal class AccountMarginEventReport : IAccountMarginEventReport - { - public string Id => EventId; - public string AccountId { get; set; } - public double Balance { get; set; } - public string BaseAssetId { get; set; } - public string ClientId { get; set; } - public string EventId { get; set; } - public DateTime EventTime { get; set; } - public double FreeMargin { get; set; } - public bool IsEventStopout { get; set; } - public double MarginAvailable { get; set; } - public double MarginCall { get; set; } - public double MarginInit { get; set; } - public double MarginUsageLevel { get; set; } - public double OpenPositionsCount { get; set; } - public double PnL { get; set; } - public double StopOut { get; set; } - public double TotalCapital { get; set; } - public string TradingConditionId { get; set; } - public double UsedMargin { get; set; } - public double WithdrawTransferLimit { get; set; } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/Models/IAccountMarginEvent.cs b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/Models/IAccountMarginEvent.cs new file mode 100644 index 000000000..022319d23 --- /dev/null +++ b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/Models/IAccountMarginEvent.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MarginTrading.Backend.Contracts.Events; + +namespace MarginTrading.AccountMarginEventsBroker.Repositories.Models +{ + internal interface IAccountMarginEvent + { + string Id { get; } + string AccountId { get; } + decimal Balance { get; } + string BaseAssetId { get; } + string EventId { get; } + DateTime EventTime { get; } + decimal FreeMargin { get; } + bool IsEventStopout { get; } + MarginEventTypeContract EventType { get; } + decimal MarginAvailable { get; } + decimal MarginCall { get; } + decimal MarginInit { get; } + decimal MarginUsageLevel { get; } + decimal OpenPositionsCount { get; } + decimal PnL { get; } + decimal StopOut { get; } + decimal TotalCapital { get; } + string TradingConditionId { get; } + decimal UsedMargin { get; } + decimal WithdrawTransferLimit { get; } + } +} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/Models/IAccountMarginEventReport.cs b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/Models/IAccountMarginEventReport.cs deleted file mode 100644 index 06287ee04..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/Models/IAccountMarginEventReport.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace MarginTrading.AccountMarginEventsBroker.Repositories.Models -{ - internal interface IAccountMarginEventReport - { - string Id { get; } - string AccountId { get; } - double Balance { get; } - string BaseAssetId { get; } - string ClientId { get; } - string EventId { get; } - DateTime EventTime { get; } - double FreeMargin { get; } - bool IsEventStopout { get; } - double MarginAvailable { get; } - double MarginCall { get; } - double MarginInit { get; } - double MarginUsageLevel { get; } - double OpenPositionsCount { get; } - double PnL { get; } - double StopOut { get; } - double TotalCapital { get; } - string TradingConditionId { get; } - double UsedMargin { get; } - double WithdrawTransferLimit { get; } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/RepositoryAggregator.cs b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/RepositoryAggregator.cs deleted file mode 100644 index 3e720fe3e..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/RepositoryAggregator.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using MarginTrading.AccountMarginEventsBroker.Repositories.Models; - -namespace MarginTrading.AccountMarginEventsBroker.Repositories -{ - internal class RepositoryAggregator : IAccountMarginEventsReportsRepository - { - private readonly List _repositories; - - public RepositoryAggregator(IEnumerable repositories) - { - _repositories = new List(); - _repositories.AddRange(repositories); - } - - public async Task InsertOrReplaceAsync(IAccountMarginEventReport report) - { - foreach (var item in _repositories) - { - await item.InsertOrReplaceAsync(report); - } - } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/SqlRepositories/AccountMarginEventEntity.cs b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/SqlRepositories/AccountMarginEventEntity.cs new file mode 100644 index 000000000..f4dbded2e --- /dev/null +++ b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/SqlRepositories/AccountMarginEventEntity.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using MarginTrading.AccountMarginEventsBroker.Repositories.Models; +using MarginTrading.Backend.Contracts.Events; + +namespace MarginTrading.AccountMarginEventsBroker.Repositories.SqlRepositories +{ + internal class AccountMarginEventEntity : IAccountMarginEvent + { + public string Id { get; set; } + public string AccountId { get; set; } + public decimal Balance { get; set; } + public string BaseAssetId { get; set; } + public string EventId { get; set; } + public DateTime EventTime { get; set; } + public decimal FreeMargin { get; set; } + public bool IsEventStopout { get; set; } + public string EventType { get; set; } + MarginEventTypeContract IAccountMarginEvent.EventType => Enum.Parse(EventType); + public decimal MarginAvailable { get; set; } + public decimal MarginCall { get; set; } + public decimal MarginInit { get; set; } + public decimal MarginUsageLevel { get; set; } + public decimal OpenPositionsCount { get; set; } + public decimal PnL { get; set; } + public decimal StopOut { get; set; } + public decimal TotalCapital { get; set; } + public string TradingConditionId { get; set; } + public decimal UsedMargin { get; set; } + public decimal WithdrawTransferLimit { get; set; } + + public static AccountMarginEventEntity Create(IAccountMarginEvent src) + { + return new AccountMarginEventEntity + { + Id = src.Id, + AccountId = src.AccountId, + Balance = src.Balance, + BaseAssetId = src.BaseAssetId, + EventId = src.EventId, + EventTime = src.EventTime, + FreeMargin = src.FreeMargin, + IsEventStopout = src.IsEventStopout, + EventType = src.EventType.ToString(), + MarginAvailable = src.MarginAvailable, + MarginCall = src.MarginCall, + MarginInit = src.MarginInit, + MarginUsageLevel = src.MarginUsageLevel, + OpenPositionsCount = src.OpenPositionsCount, + PnL = src.PnL, + StopOut = src.StopOut, + TotalCapital = src.TotalCapital, + TradingConditionId = src.TradingConditionId, + UsedMargin = src.UsedMargin, + WithdrawTransferLimit = src.WithdrawTransferLimit + }; + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/SqlRepositories/AccountMarginEventsReportsSqlRepository.cs b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/SqlRepositories/AccountMarginEventsReportsSqlRepository.cs deleted file mode 100644 index 9611b25e6..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/SqlRepositories/AccountMarginEventsReportsSqlRepository.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Common; -using Common.Log; -using Dapper; -using MarginTrading.AccountMarginEventsBroker.Repositories.Models; -using MarginTrading.BrokerBase; -using System; -using System.Data.SqlClient; -using System.Threading.Tasks; - -namespace MarginTrading.AccountMarginEventsBroker.Repositories.SqlRepositories -{ - internal class AccountMarginEventsReportsSqlRepository : IAccountMarginEventsReportsRepository - { - private const string TableName = "AccountMarginEventsReports"; - private const string CreateTableScript = "CREATE TABLE [{0}] (" + - "[Id] [nvarchar](64) NOT NULL, " + - "[EventId] [nvarchar](64) NOT NULL, " + - "[ClientId] [nvarchar] (64) NOT NULL, " + - "[AccountId] [nvarchar] (64) NOT NULL, " + - "[TradingConditionId] [nvarchar] (64) NOT NULL, " + - "[Balance] float NOT NULL, " + - "[BaseAssetId] [nvarchar] (64) NOT NULL, " + - "[EventTime] [datetime] NOT NULL, " + - "[FreeMargin] float NOT NULL, " + - "[IsEventStopout] [bit] NOT NULL, " + - "[MarginAvailable] float NOT NULL, " + - "[MarginCall] float NOT NULL, " + - "[MarginInit] float NOT NULL, " + - "[MarginUsageLevel] float NOT NULL, " + - "[OpenPositionsCount] float NOT NULL, " + - "[PnL] float NOT NULL, " + - "[StopOut] float NOT NULL, " + - "[TotalCapital] float NOT NULL, " + - "[UsedMargin] float NOT NULL, " + - "[WithdrawTransferLimit] float NOT NULL, " + - "CONSTRAINT[PK_{0}] PRIMARY KEY CLUSTERED ([Id] ASC)" + - ");"; - - private readonly Settings _settings; - private readonly ILog _log; - - public AccountMarginEventsReportsSqlRepository(Settings settings, ILog log) - { - _log = log; - _settings = settings; - using (var conn = new SqlConnection(_settings.Db.ReportsSqlConnString)) - { - try { conn.CreateTableIfDoesntExists(CreateTableScript, TableName); } - catch (Exception ex) - { - _log?.WriteErrorAsync("AccountMarginEventsReportsSqlRepository", "CreateTableIfDoesntExists", null, ex); - throw; - } - } - } - - public async Task InsertOrReplaceAsync(IAccountMarginEventReport report) - { - - using (var conn = new SqlConnection(_settings.Db.ReportsSqlConnString)) - { - var res = conn.ExecuteScalar($"select Id from {TableName} where Id = '{report.Id}'"); - string query; - if (res == null) - { - query = $"insert into {TableName} " + - "(Id, EventId, ClientId, AccountId, TradingConditionId, Balance, BaseAssetId, EventTime, FreeMargin, IsEventStopout, MarginAvailable, " + - "MarginCall, MarginInit, MarginUsageLevel, OpenPositionsCount, PnL, StopOut, TotalCapital, UsedMargin, WithdrawTransferLimit)" + - " values " + - "(@Id, @EventId, @ClientId, @AccountId, @TradingConditionId, @Balance, @BaseAssetId, @EventTime, @FreeMargin, @IsEventStopout, @MarginAvailable, " + - "@MarginCall, @MarginInit, @MarginUsageLevel, @OpenPositionsCount, @PnL, @StopOut, @TotalCapital, @UsedMargin, @WithdrawTransferLimit)"; - } - else - { - query = $"update {TableName} set " + - "EventId=@EventId, ClientId=@ClientId, AccountId=@AccountId, TradingConditionId=@TradingConditionId, Balance=@Balance, " + - "BaseAssetId=@BaseAssetId, EventTime=@EventTime, FreeMargin=@FreeMargin, IsEventStopout=@IsEventStopout, MarginAvailable=@MarginAvailable, " + - "MarginCall=@MarginCall, MarginInit=@MarginInit, MarginUsageLevel=@MarginUsageLevel, OpenPositionsCount=@OpenPositionsCount, " + - "PnL=@PnL, StopOut=@StopOut, TotalCapital=@TotalCapital, UsedMargin=@UsedMargin, WithdrawTransferLimit=@WithdrawTransferLimit " + - " where Id=@Id"; - } - - try { await conn.ExecuteAsync(query, report); } - catch (Exception ex) - { - var msg = $"Error {ex.Message} \n" + - "Entity : \n" + - report.ToJson(); - await _log?.WriteWarningAsync("AccountMarginEventsReportsSqlRepository", "InsertOrReplaceAsync", - null, msg); - throw new Exception(msg); - } - } - } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/SqlRepositories/AccountMarginEventsSqlRepository.cs b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/SqlRepositories/AccountMarginEventsSqlRepository.cs new file mode 100644 index 000000000..b7557879f --- /dev/null +++ b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Repositories/SqlRepositories/AccountMarginEventsSqlRepository.cs @@ -0,0 +1,90 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Common; +using Common.Log; +using Dapper; +using MarginTrading.AccountMarginEventsBroker.Repositories.Models; +using System; +using System.Data.SqlClient; +using System.Linq; +using System.Threading.Tasks; +using Lykke.MarginTrading.BrokerBase.Extensions; + +namespace MarginTrading.AccountMarginEventsBroker.Repositories.SqlRepositories +{ + internal class AccountMarginEventsSqlRepository : IAccountMarginEventsRepository + { + private const string TableName = "AccountMarginEvents"; + private const string CreateTableScript = "CREATE TABLE [{0}] (" + + "[Id] [nvarchar](64) NOT NULL, " + + "[EventId] [nvarchar](64) NOT NULL, " + + "[AccountId] [nvarchar] (64) NOT NULL, " + + "[TradingConditionId] [nvarchar] (64) NOT NULL, " + + "[Balance] float NOT NULL, " + + "[BaseAssetId] [nvarchar] (64) NOT NULL, " + + "[EventTime] [datetime] NOT NULL, " + + "[FreeMargin] float NOT NULL, " + + "[IsEventStopout] [bit] NOT NULL, " + + "[EventType] [nvarchar] (64) NULL, " + + "[MarginAvailable] float NOT NULL, " + + "[MarginCall] float NOT NULL, " + + "[MarginInit] float NOT NULL, " + + "[MarginUsageLevel] float NOT NULL, " + + "[OpenPositionsCount] float NOT NULL, " + + "[PnL] float NOT NULL, " + + "[StopOut] float NOT NULL, " + + "[TotalCapital] float NOT NULL, " + + "[UsedMargin] float NOT NULL, " + + "[WithdrawTransferLimit] float NOT NULL, " + + "CONSTRAINT[PK_{0}] PRIMARY KEY CLUSTERED ([Id] ASC)" + + ");"; + + private readonly Settings _settings; + private readonly ILog _log; + + private static readonly string GetColumns = + string.Join(",", typeof(IAccountMarginEvent).GetProperties().Select(x => x.Name)); + + private static readonly string GetFields = + string.Join(",", typeof(IAccountMarginEvent).GetProperties().Select(x => "@" + x.Name)); + + public AccountMarginEventsSqlRepository(Settings settings, ILog log) + { + _log = log; + _settings = settings; + using (var conn = new SqlConnection(_settings.Db.ConnString)) + { + try { conn.CreateTableIfDoesntExists(CreateTableScript, TableName); } + catch (Exception ex) + { + _log?.WriteErrorAsync(nameof(AccountMarginEventsSqlRepository), "CreateTableIfDoesntExists", null, ex); + throw; + } + } + } + + public async Task InsertOrReplaceAsync(IAccountMarginEvent report) + { + + using (var conn = new SqlConnection(_settings.Db.ConnString)) + { + try + { + await conn.ExecuteAsync( + $"insert into {TableName} ({GetColumns}) values ({GetFields})", + AccountMarginEventEntity.Create(report)); + } + catch (Exception ex) + { + var msg = $"Error {ex.Message} \n" + + $"Entity <{nameof(IAccountMarginEvent)}>: \n" + + report.ToJson(); + await _log?.WriteWarningAsync(nameof(AccountMarginEventsSqlRepository), "InsertOrReplaceAsync", + null, msg); + throw new Exception(msg); + } + } + } + } +} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Settings.cs b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Settings.cs index 2bf94a42c..ae12723b8 100644 --- a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Settings.cs +++ b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Settings.cs @@ -1,20 +1,31 @@ -using MarginTrading.BrokerBase.Settings; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; +using Lykke.MarginTrading.BrokerBase.Models; +using Lykke.MarginTrading.BrokerBase.Settings; +using Lykke.SettingsReader.Attributes; using MarginTrading.Common.RabbitMq; namespace MarginTrading.AccountMarginEventsBroker { + [UsedImplicitly] public class Settings : BrokerSettingsBase { public Db Db { get; set; } + public RabbitMqQueues RabbitMqQueues { get; set; } } + [UsedImplicitly] public class Db { - public string ReportsConnString { get; set; } - public string ReportsSqlConnString { get; set; } + public StorageMode StorageMode { get; set; } + + public string ConnString { get; set; } } + [UsedImplicitly] public class RabbitMqQueues { public RabbitMqQueueInfo AccountMarginEvents { get; set; } diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Startup.cs b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Startup.cs index 62b56d762..48f031138 100644 --- a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Startup.cs +++ b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/Startup.cs @@ -1,35 +1,45 @@ -using Autofac; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Autofac; using Common.Log; +using JetBrains.Annotations; using Lykke.SettingsReader; using MarginTrading.AccountMarginEventsBroker.Repositories; using MarginTrading.AccountMarginEventsBroker.Repositories.AzureRepositories; using MarginTrading.AccountMarginEventsBroker.Repositories.SqlRepositories; -using MarginTrading.BrokerBase; -using MarginTrading.BrokerBase.Settings; +using Lykke.MarginTrading.BrokerBase; +using Lykke.MarginTrading.BrokerBase.Models; +using Lykke.MarginTrading.BrokerBase.Settings; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; namespace MarginTrading.AccountMarginEventsBroker { + [UsedImplicitly] public class Startup : BrokerStartupBase, Settings> { - protected override string ApplicationName => "MarginTradingAccountMarginEventsBroker"; + protected override string ApplicationName => "AccountMarginEventsBroker"; public Startup(IHostingEnvironment env) : base(env) { } - - - protected override void RegisterCustomServices(IServiceCollection services, ContainerBuilder builder, IReloadingManager settings, ILog log, bool isLive) + + protected override void RegisterCustomServices(IServiceCollection services, ContainerBuilder builder, IReloadingManager settings, + ILog log) { builder.RegisterType().As().SingleInstance(); - - builder.RegisterInstance(new RepositoryAggregator(new IAccountMarginEventsReportsRepository[] + + if (settings.CurrentValue.Db.StorageMode == StorageMode.Azure) + { + builder.RegisterInstance(new AccountMarginEventsRepository(settings, log)) + .As(); + } + else if (settings.CurrentValue.Db.StorageMode == StorageMode.SqlServer) { - new AccountMarginEventsReportsSqlRepository(settings.CurrentValue, log), - new AccountMarginEventsReportsRepository(settings, log) - })) - .As(); + builder.RegisterInstance(new AccountMarginEventsSqlRepository(settings.CurrentValue, log)) + .As(); + } } } } \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/appsettings.Serilog.json b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/appsettings.Serilog.json new file mode 100644 index 000000000..c48345c99 --- /dev/null +++ b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/appsettings.Serilog.json @@ -0,0 +1,35 @@ +{ + "serilog": { + "Using": [ "Serilog.Sinks.File", "Serilog.Sinks.Async" ], + "minimumLevel": { + "default": "Debug" + }, + "writeTo": [ + { + "Name": "Async", + "Args": { + "configure": [ + { + "Name": "Console", + "Args": { + "outputTemplate": "[{Timestamp:u}] [{Level:u3}] [{Component}:{Process}:{Context}] - {info} {Message:lj} {NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "outputTemplate": "[{Timestamp:u}] [{Level:u3}] [{Component}:{Process}:{Context}] - {info} {Message:lj} {NewLine}{Exception}", + "path": "logs/MTCore/AccountMarginEventsBrokerLog.log", + "rollingInterval": "Day" + } + } + ] + } + } + ], + "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId", "WithDemystifiedStackTraces" ], + "Properties": { + "Application": "AccountMarginEventsBroker" + } + } +} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/AccountReportsApplication.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/AccountReportsApplication.cs deleted file mode 100644 index c0b54edf8..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/AccountReportsApplication.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Common.Log; -using Lykke.SlackNotifications; -using MarginTrading.AccountReportsBroker.Repositories; -using MarginTrading.AccountReportsBroker.Repositories.Models; -using MarginTrading.BrokerBase; -using MarginTrading.BrokerBase.Settings; -using MarginTrading.Contract.RabbitMqMessageModels; -using System; -using System.Threading.Tasks; - -namespace MarginTrading.AccountReportsBroker -{ - internal class AccountReportsApplication : BrokerApplicationBase - { - private readonly IAccountsReportsRepository _accountsReportsRepository; - private readonly Settings _settings; - - public AccountReportsApplication(ILog logger, - Settings settings, CurrentApplicationInfo applicationInfo, - IAccountsReportsRepository accountsReportsRepository, - ISlackNotificationsSender slackNotificationsSender) - : base(logger, slackNotificationsSender, applicationInfo) - { - _settings = settings; - _accountsReportsRepository = accountsReportsRepository; - } - - protected override BrokerSettingsBase Settings => _settings; - protected override string ExchangeName => _settings.RabbitMqQueues.AccountChanged.ExchangeName; - - protected override Task HandleMessage(AccountChangedMessage message) - { - if (message.EventType != AccountEventTypeEnum.Created || message.Account == null) - { - return Task.CompletedTask; - } - - var report = new AccountsReport - { - Id = message.Account.Id, - Date = DateTime.UtcNow, - TakerAccountId = message.Account.Id, - TakerCounterpartyId = message.Account.ClientId, - BaseAssetId = message.Account.BaseAssetId, - IsLive = _settings.IsLive, - LegalEntity = message.Account.LegalEntity, - }; - - return _accountsReportsRepository.InsertOrReplaceAsync(report); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/AccountStatAzureReportsApplication.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/AccountStatAzureReportsApplication.cs deleted file mode 100644 index f15fe06ce..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/AccountStatAzureReportsApplication.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Common.Log; -using Lykke.SlackNotifications; -using MarginTrading.AccountReportsBroker.Repositories; -using MarginTrading.AzureRepositories; -using MarginTrading.BrokerBase.Settings; - -namespace MarginTrading.AccountReportsBroker -{ - internal class AccountStatAzureReportsApplication : AccountStatReportsApplication - { - public AccountStatAzureReportsApplication(ILog logger, Settings settings, - CurrentApplicationInfo applicationInfo, IAccountsStatsReportsRepository accountsStatsReportsRepository, - IMarginTradingAccountStatsRepository statsRepository, ISlackNotificationsSender slackNotificationsSender) : - base(logger, settings, applicationInfo, accountsStatsReportsRepository, statsRepository, - slackNotificationsSender) - { - } - - protected override string QueuePostfix => ".AzureTables"; - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/AccountStatReportsApplication.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/AccountStatReportsApplication.cs deleted file mode 100644 index 25b6af992..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/AccountStatReportsApplication.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Common.Log; -using Lykke.SlackNotifications; -using MarginTrading.AccountReportsBroker.Repositories; -using MarginTrading.AccountReportsBroker.Repositories.Models; -using MarginTrading.AzureRepositories; -using MarginTrading.BrokerBase; -using MarginTrading.BrokerBase.Settings; -using MarginTrading.Contract.RabbitMqMessageModels; -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace MarginTrading.AccountReportsBroker -{ - internal abstract class AccountStatReportsApplication : BrokerApplicationBase - { - private readonly IAccountsStatsReportsRepository _accountsStatsReportsRepository; - private readonly IMarginTradingAccountStatsRepository _statsRepository; - private readonly Settings _settings; - - - protected AccountStatReportsApplication(ILog logger, - Settings settings, CurrentApplicationInfo applicationInfo, - IAccountsStatsReportsRepository accountsStatsReportsRepository, - IMarginTradingAccountStatsRepository statsRepository, - ISlackNotificationsSender slackNotificationsSender) - : base(logger, slackNotificationsSender, applicationInfo) - { - _settings = settings; - _accountsStatsReportsRepository = accountsStatsReportsRepository; - _statsRepository = statsRepository; - } - - protected override BrokerSettingsBase Settings => _settings; - protected override string ExchangeName => _settings.RabbitMqQueues.AccountStats.ExchangeName; - - protected override Task HandleMessage(AccountStatsUpdateMessage message) - { - var accountsStatsReports = message.Accounts?.Select(a => - new AccountsStatReport - { - Id = a.AccountId, - Date = DateTime.UtcNow, - AccountId = a.AccountId, - Balance = (double) a.Balance, - BaseAssetId = a.BaseAssetId, - ClientId = a.ClientId, - IsLive = a.IsLive, - FreeMargin = (double) a.FreeMargin, - MarginAvailable = (double) a.MarginAvailable, - MarginCall = (double) a.MarginCallLevel, - MarginInit = (double) a.MarginInit, - MarginUsageLevel = (double) a.MarginUsageLevel, - OpenPositionsCount = (double) a.OpenPositionsCount, - PnL = (double) a.PnL, - StopOut = (double) a.StopOutLevel, - TotalCapital = (double) a.TotalCapital, - TradingConditionId = a.TradingConditionId, - UsedMargin = (double) a.UsedMargin, - WithdrawTransferLimit = (double) a.WithdrawTransferLimit, - LegalEntity = a.LegalEntity, - }); - - var accountStats = message.Accounts?.Select(a => new MarginTradingAccountStatsEntity - { - AccountId = a.AccountId, - BaseAssetId = a.BaseAssetId, - MarginCall = (double) a.MarginCallLevel, - StopOut = (double) a.StopOutLevel, - TotalCapital = (double) a.TotalCapital, - FreeMargin = (double) a.FreeMargin, - MarginAvailable = (double) a.MarginAvailable, - UsedMargin = (double) a.UsedMargin, - MarginInit = (double) a.MarginInit, - PnL = (double) a.PnL, - OpenPositionsCount = (double) a.OpenPositionsCount, - MarginUsageLevel = (double) a.MarginUsageLevel, - LegalEntity = a.LegalEntity, - }); - - return Task.WhenAll( - _accountsStatsReportsRepository.InsertOrReplaceBatchAsync(accountsStatsReports), - _statsRepository.InsertOrReplaceBatchAsync(accountStats) - ); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/AccountStatSqlReportsApplication.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/AccountStatSqlReportsApplication.cs deleted file mode 100644 index 173ef710b..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/AccountStatSqlReportsApplication.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Common.Log; -using Lykke.SlackNotifications; -using MarginTrading.AccountReportsBroker.Repositories; -using MarginTrading.AzureRepositories; -using MarginTrading.BrokerBase.Settings; - -namespace MarginTrading.AccountReportsBroker -{ - internal class AccountStatSqlReportsApplication : AccountStatReportsApplication - { - public AccountStatSqlReportsApplication(ILog logger, Settings settings, - CurrentApplicationInfo applicationInfo, IAccountsStatsReportsRepository accountsStatsReportsRepository, - IMarginTradingAccountStatsRepository statsRepository, ISlackNotificationsSender slackNotificationsSender) : - base(logger, settings, applicationInfo, accountsStatsReportsRepository, statsRepository, - slackNotificationsSender) - { - } - - protected override string QueuePostfix => ".Sql"; - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Dockerfile b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Dockerfile deleted file mode 100644 index d3d4dbe0d..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM microsoft/aspnetcore:2.0 -WORKDIR /app -COPY . . -ENTRYPOINT ["dotnet", "MarginTrading.AccountReportsBroker.dll"] diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/MarginTrading.AccountReportsBroker.csproj b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/MarginTrading.AccountReportsBroker.csproj deleted file mode 100644 index caf61b296..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/MarginTrading.AccountReportsBroker.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - Exe - netcoreapp2.0 - 1.0.1 - - - - - - - PreserveNewest - - - - - - - - \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Program.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Program.cs deleted file mode 100644 index 542b036cd..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MarginTrading.BrokerBase; - -namespace MarginTrading.AccountReportsBroker -{ - public class Program: WebAppProgramBase - { - public static void Main(string[] args) - { - RunOnPort(5012); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/AccountsReportsRepositoryAggregator.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/AccountsReportsRepositoryAggregator.cs deleted file mode 100644 index 5fbd3f4f5..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/AccountsReportsRepositoryAggregator.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using MarginTrading.AccountReportsBroker.Repositories.Models; - -namespace MarginTrading.AccountReportsBroker.Repositories -{ - internal class AccountsReportsRepositoryAggregator: IAccountsReportsRepository - { - private readonly List _repositories; - - public AccountsReportsRepositoryAggregator(IEnumerable repositories) - { - _repositories = new List(); - _repositories.AddRange(repositories); - } - - public async Task InsertOrReplaceAsync(IAccountsReport report) - { - foreach (var item in _repositories) - { - await item.InsertOrReplaceAsync(report); - } - } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/AzureRepositories/AccountsReportsRepository.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/AzureRepositories/AccountsReportsRepository.cs deleted file mode 100644 index ed087d8ec..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/AzureRepositories/AccountsReportsRepository.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Threading.Tasks; -using AzureStorage; -using AzureStorage.Tables; -using Common.Log; -using Lykke.SettingsReader; -using MarginTrading.AccountReportsBroker.Repositories.AzureRepositories.Entities; -using MarginTrading.AccountReportsBroker.Repositories.Models; - -namespace MarginTrading.AccountReportsBroker.Repositories.AzureRepositories -{ - public class AccountsReportsRepository : IAccountsReportsRepository - { - private readonly INoSQLTableStorage _tableStorage; - - public AccountsReportsRepository(IReloadingManager settings, ILog log) - { - _tableStorage = AzureTableStorage.Create(settings.Nested(s => s.Db.ReportsConnString), - "ClientAccountsReports", log); - } - - public Task InsertOrReplaceAsync(IAccountsReport report) - { - return _tableStorage.InsertOrReplaceAsync(AccountsReportEntity.Create(report)); - } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/AzureRepositories/AccountsStatsReportsRepository.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/AzureRepositories/AccountsStatsReportsRepository.cs deleted file mode 100644 index 3e8d23c65..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/AzureRepositories/AccountsStatsReportsRepository.cs +++ /dev/null @@ -1,31 +0,0 @@ -using AzureStorage; -using AzureStorage.Tables; -using Common.Log; -using MarginTrading.AccountReportsBroker.Repositories.AzureRepositories.Entities; -using MarginTrading.AccountReportsBroker.Repositories.Models; -using MarginTrading.AzureRepositories.Helpers; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Lykke.SettingsReader; - -namespace MarginTrading.AccountReportsBroker.Repositories.AzureRepositories -{ - public class AccountsStatsReportsRepository : IAccountsStatsReportsRepository - { - private readonly INoSQLTableStorage _tableStorage; - - public AccountsStatsReportsRepository(IReloadingManager settings, ILog log) - { - _tableStorage = AzureTableStorage.Create(settings.Nested(s => s.Db.ReportsConnString), - "ClientAccountsStatusReports", log); - } - - public Task InsertOrReplaceBatchAsync(IEnumerable stats) - { - var tasks = BatchEntityInsertHelper.MakeBatchesByPartitionKey(stats.Select(m => AccountsStatReportEntity.Create(m))) - .Select(b => _tableStorage.InsertOrReplaceBatchAsync(b)); - return Task.WhenAll(tasks); - } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/AzureRepositories/Entities/AccountsReportEntity.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/AzureRepositories/Entities/AccountsReportEntity.cs deleted file mode 100644 index d3f957400..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/AzureRepositories/Entities/AccountsReportEntity.cs +++ /dev/null @@ -1,36 +0,0 @@ -using MarginTrading.AccountReportsBroker.Repositories.Models; -using Microsoft.WindowsAzure.Storage.Table; - -namespace MarginTrading.AccountReportsBroker.Repositories.AzureRepositories.Entities -{ - public class AccountsReportEntity : TableEntity - { - public string TakerCounterpartyId - { - get => PartitionKey; - set => PartitionKey = value; - } - - public string TakerAccountId - { - get => RowKey; - set => RowKey = value; - } - - public string BaseAssetId { get; set; } - public bool IsLive { get; set; } - public string LegalEntity { get; set; } - - public static AccountsReportEntity Create(IAccountsReport src) - { - return new AccountsReportEntity - { - TakerCounterpartyId = src.TakerCounterpartyId, - TakerAccountId = src.TakerAccountId, - BaseAssetId = src.BaseAssetId, - IsLive = src.IsLive, - LegalEntity = src.LegalEntity, - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/AzureRepositories/Entities/AccountsStatReportEntity.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/AzureRepositories/Entities/AccountsStatReportEntity.cs deleted file mode 100644 index 4443b6ba7..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/AzureRepositories/Entities/AccountsStatReportEntity.cs +++ /dev/null @@ -1,62 +0,0 @@ -using MarginTrading.AccountReportsBroker.Repositories.Models; -using Microsoft.WindowsAzure.Storage.Table; - -namespace MarginTrading.AccountReportsBroker.Repositories.AzureRepositories.Entities -{ - public class AccountsStatReportEntity : TableEntity - { - public string BaseAssetId - { - get => PartitionKey; - set => PartitionKey = value; - } - - public string AccountId - { - get => RowKey; - set => RowKey = value; - } - - public string ClientId { get; set; } - public string TradingConditionId { get; set; } - public double Balance { get; set; } - public double WithdrawTransferLimit { get; set; } - public double MarginCall { get; set; } - public double StopOut { get; set; } - public double TotalCapital { get; set; } - public double FreeMargin { get; set; } - public double MarginAvailable { get; set; } - public double UsedMargin { get; set; } - public double MarginInit { get; set; } - public double PnL { get; set; } - public double OpenPositionsCount { get; set; } - public double MarginUsageLevel { get; set; } - public bool IsLive { get; set; } - public string LegalEntity { get; set; } - - public static AccountsStatReportEntity Create(IAccountsStatReport s) - { - return new AccountsStatReportEntity - { - BaseAssetId = s.BaseAssetId, - AccountId = s.AccountId, - ClientId = s.ClientId, - TradingConditionId = s.TradingConditionId, - Balance = s.Balance, - WithdrawTransferLimit = s.WithdrawTransferLimit, - MarginCall = s.MarginCall, - StopOut = s.StopOut, - TotalCapital = s.TotalCapital, - FreeMargin = s.FreeMargin, - MarginAvailable = s.MarginAvailable, - UsedMargin = s.UsedMargin, - MarginInit = s.MarginInit, - PnL = s.PnL, - OpenPositionsCount = s.OpenPositionsCount, - MarginUsageLevel = s.MarginUsageLevel, - IsLive = s.IsLive, - LegalEntity = s.LegalEntity, - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/IAccountsReportsRepository.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/IAccountsReportsRepository.cs deleted file mode 100644 index 08b267cec..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/IAccountsReportsRepository.cs +++ /dev/null @@ -1,10 +0,0 @@ -using MarginTrading.AccountReportsBroker.Repositories.Models; -using System.Threading.Tasks; - -namespace MarginTrading.AccountReportsBroker.Repositories -{ - public interface IAccountsReportsRepository - { - Task InsertOrReplaceAsync(IAccountsReport report); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/IAccountsStatsReportsRepository.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/IAccountsStatsReportsRepository.cs deleted file mode 100644 index dea635e68..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/IAccountsStatsReportsRepository.cs +++ /dev/null @@ -1,11 +0,0 @@ -using MarginTrading.AccountReportsBroker.Repositories.Models; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace MarginTrading.AccountReportsBroker.Repositories -{ - public interface IAccountsStatsReportsRepository - { - Task InsertOrReplaceBatchAsync(IEnumerable stats); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/Models/AccountsReport.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/Models/AccountsReport.cs deleted file mode 100644 index c19c0fe53..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/Models/AccountsReport.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace MarginTrading.AccountReportsBroker.Repositories.Models -{ - public class AccountsReport : IAccountsReport - { - public string Id { get; set; } - public DateTime Date { get; set; } - public string TakerCounterpartyId { get; set; } - public string TakerAccountId { get; set; } - public string BaseAssetId { get; set; } - public bool IsLive { get; set; } - public string LegalEntity { get; set; } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/Models/AccountsStatReport.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/Models/AccountsStatReport.cs deleted file mode 100644 index 0ee39bd98..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/Models/AccountsStatReport.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace MarginTrading.AccountReportsBroker.Repositories.Models -{ - public class AccountsStatReport : IAccountsStatReport - { - public string Id { get; set; } - public DateTime Date { get; set; } - public string BaseAssetId { get; set; } - public string AccountId { get; set; } - public string ClientId { get; set; } - public string TradingConditionId { get; set; } - public double Balance { get; set; } - public double WithdrawTransferLimit { get; set; } - public double MarginCall { get; set; } - public double StopOut { get; set; } - public double TotalCapital { get; set; } - public double FreeMargin { get; set; } - public double MarginAvailable { get; set; } - public double UsedMargin { get; set; } - public double MarginInit { get; set; } - public double PnL { get; set; } - public double OpenPositionsCount { get; set; } - public double MarginUsageLevel { get; set; } - public bool IsLive { get; set; } - public string LegalEntity { get; set; } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/Models/IAccountsReport.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/Models/IAccountsReport.cs deleted file mode 100644 index 079827d66..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/Models/IAccountsReport.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace MarginTrading.AccountReportsBroker.Repositories.Models -{ - public interface IAccountsReport - { - string Id { get; } - DateTime Date { get; } - string TakerCounterpartyId { get; } - string TakerAccountId { get; } - string BaseAssetId { get; } - bool IsLive { get; } - string LegalEntity { get; } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/Models/IAccountsStatReport.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/Models/IAccountsStatReport.cs deleted file mode 100644 index 09bfee43b..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/Models/IAccountsStatReport.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace MarginTrading.AccountReportsBroker.Repositories.Models -{ - public interface IAccountsStatReport - { - string Id { get; } - DateTime Date { get; } - string BaseAssetId { get; } - string AccountId { get; } - string ClientId { get; } - string TradingConditionId { get; } - double Balance { get; } - double WithdrawTransferLimit { get; } - double MarginCall { get; } - double StopOut { get; } - double TotalCapital { get; } - double FreeMargin { get; } - double MarginAvailable { get; } - double UsedMargin { get; } - double MarginInit { get; } - double PnL { get; } - double OpenPositionsCount { get; } - double MarginUsageLevel { get; } - bool IsLive { get; } - string LegalEntity { get; } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/SqlRepositories/AccountsReportsSqlRepository.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/SqlRepositories/AccountsReportsSqlRepository.cs deleted file mode 100644 index 571769b70..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/SqlRepositories/AccountsReportsSqlRepository.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Common; -using Common.Log; -using Dapper; -using MarginTrading.AccountReportsBroker.Repositories.Models; -using MarginTrading.BrokerBase; -using System; -using System.Data.SqlClient; -using System.Threading.Tasks; - -namespace MarginTrading.AccountReportsBroker.Repositories.SqlRepositories -{ - public class AccountsReportsSqlRepository : IAccountsReportsRepository - { - private const string TableName = "ClientAccountsReports"; - private const string CreateTableScript = "CREATE TABLE [{0}](" + - "[Id] [nvarchar](64) NOT NULL, " + - "[Date] [datetime] NOT NULL, " + - "[TakerCounterpartyId] [nvarchar] (64) NOT NULL, " + - "[TakerAccountId] [nvarchar] (64) NOT NULL, " + - "[BaseAssetId] [nvarchar] (64) NOT NULL, " + - "[IsLive] [bit] NOT NULL, " + - "[LegalEntity] [nvarchar] (64) NULL, " + - "CONSTRAINT[PK_{0}] PRIMARY KEY CLUSTERED ([Id] ASC)" + - ");"; - - private readonly Settings _settings; - private readonly ILog _log; - - public AccountsReportsSqlRepository(Settings settings, ILog log) - { - _log = log; - _settings = settings; - using (var conn = new SqlConnection(_settings.Db.ReportsSqlConnString)) - { - try { conn.CreateTableIfDoesntExists(CreateTableScript, TableName); } - catch (Exception ex) - { - _log?.WriteErrorAsync("AccountsReportsSqlRepository", "CreateTableIfDoesntExists", null, ex); - throw; - } - } - } - - public async Task InsertOrReplaceAsync(IAccountsReport report) - { - using (var conn = new SqlConnection(_settings.Db.ReportsSqlConnString)) - { - var res = conn.ExecuteScalar($"select Id from {TableName} where Id = '{report.Id}'"); - string query; - if (res == null) - { - query = $"insert into {TableName} " + - "(Id, Date, TakerCounterpartyId, TakerAccountId, BaseAssetId, IsLive, LegalEntity) " + - " values " + - "(@Id, @Date, @TakerCounterpartyId, @TakerAccountId, @BaseAssetId, @IsLive, @LegalEntity)"; - } - else - { - query = $"update {TableName} set " + - "Date=@Date, TakerCounterpartyId=@TakerCounterpartyId, TakerAccountId=@TakerAccountId, " + - "BaseAssetId=@BaseAssetId, IsLive=@IsLive, LegalEntity=@LegalEntity " + - " where Id=@Id"; - } - try { await conn.ExecuteAsync(query, report); } - catch (Exception ex) - { - var msg = $"Error {ex.Message} \n" + - "Entity : \n" + - report.ToJson(); - await _log?.WriteWarningAsync("AccountsReportsSqlRepository", "InsertOrReplaceAsync", null, msg); - throw new Exception(msg);; - } - } - } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/SqlRepositories/AccountsStatsReportsSqlRepository.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/SqlRepositories/AccountsStatsReportsSqlRepository.cs deleted file mode 100644 index 32e2c8284..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Repositories/SqlRepositories/AccountsStatsReportsSqlRepository.cs +++ /dev/null @@ -1,160 +0,0 @@ -using Common.Log; -using Dapper; -using MarginTrading.AccountReportsBroker.Repositories.Models; -using MarginTrading.BrokerBase; -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Threading.Tasks; - -namespace MarginTrading.AccountReportsBroker.Repositories.SqlRepositories -{ - public class AccountsStatsReportsSqlRepository : IAccountsStatsReportsRepository - { - private const string TableName = "ClientAccountsStatusReports"; - private const string CreateTableScript = "CREATE TABLE [{0}](" + - "[Id] [nvarchar](64) NOT NULL, " + - "[Date] [datetime] NOT NULL, " + - "[BaseAssetId] [nvarchar](64) NOT NULL, " + - "[AccountId] [nvarchar] (64) NOT NULL, " + - "[ClientId] [nvarchar] (64) NOT NULL, " + - "[TradingConditionId] [nvarchar] (64) NOT NULL, " + - "[Balance] float NOT NULL, " + - "[WithdrawTransferLimit] float NOT NULL, " + - "[MarginCall] float NOT NULL, " + - "[StopOut] float NOT NULL, " + - "[TotalCapital] float NOT NULL, " + - "[FreeMargin] float NOT NULL, " + - "[MarginAvailable] float NOT NULL, " + - "[UsedMargin] float NOT NULL, " + - "[MarginInit] float NOT NULL, " + - "[PnL] float NOT NULL, " + - "[OpenPositionsCount] float NOT NULL, " + - "[MarginUsageLevel] float NOT NULL, " + - "[IsLive] [bit] NOT NULL, " + - "[LegalEntity] [nvarchar] (64) NULL); "; - - private const string CreatePkScript = "ALTER TABLE {0} ADD CONSTRAINT[PK_{0}] PRIMARY KEY CLUSTERED ([Id] ASC);"; - - private readonly Settings _settings; - private readonly ILog _log; - - public AccountsStatsReportsSqlRepository(Settings settings, ILog log) - { - _log = log; - _settings = settings; - using (var conn = new SqlConnection(_settings.Db.ReportsSqlConnString)) - { - try { conn.CreateTableIfDoesntExists(CreateTableScript + CreatePkScript, TableName); } - catch (Exception ex) - { - _log?.WriteErrorAsync("AccountsStatsReportsSqlRepository", "CreateTableIfDoesntExists", null, ex); - throw; - } - } - } - - public async Task InsertOrReplaceBatchAsync(IEnumerable stats) - { - const string tempValuesTable = "#tmpValues"; - - using (var conn = new SqlConnection(_settings.Db.ReportsSqlConnString)) - { - try - { - await conn.OpenAsync(); - - var tran = conn.BeginTransaction(); - - var createTempTableQuery = string.Format(CreateTableScript, tempValuesTable); - await conn.ExecuteAsync(createTempTableQuery, transaction: tran); - - var tempInsertQuery = GetInsertQuery(tempValuesTable); - - //insert values into temp table - await conn.ExecuteAsync(tempInsertQuery, stats, transaction: tran); - - var mergeQuery = GetMergeQuery(TableName, tempValuesTable); - - //merge values from temp to report table - await conn.ExecuteAsync(mergeQuery, transaction: tran); - - tran.Commit(); - } - catch (SqlException ex) - { - await _log.WriteWarningAsync("AccountsStatsReportsSqlRepository", "InsertOrReplaceBatchAsync", null, - ex.ToString()); - } - catch (Exception ex) - { - await _log.WriteWarningAsync("AccountsStatsReportsSqlRepository", "InsertOrReplaceBatchAsync", null, - ex.ToString()); - throw; - } - } - } - - private string GetInsertQuery(string tableName) - { - return $"INSERT INTO {tableName} " + - @"(Id, - Date, - BaseAssetId, - AccountId, - ClientId, - TradingConditionId, - Balance, - WithdrawTransferLimit, - MarginCall, - StopOut, - TotalCapital, - FreeMargin, - MarginAvailable, - UsedMargin, - MarginInit, - PnL, - OpenPositionsCount, - MarginUsageLevel, - IsLive, - LegalEntity) - VALUES - (@Id, - @Date, - @BaseAssetId, - @AccountId, - @ClientId, - @TradingConditionId, - @Balance, - @WithdrawTransferLimit, - @MarginCall, - @StopOut, - @TotalCapital, - @FreeMargin, - @MarginAvailable, - @UsedMargin, - @MarginInit, - @PnL, - @OpenPositionsCount, - @MarginUsageLevel, - @IsLive, - @LegalEntity)"; - } - - private string GetMergeQuery(string targetTableName, string sourceTableName) - { - return - "SET NOCOUNT ON; " + - $"MERGE {targetTableName} AS target " + - $"USING (SELECT * from {sourceTableName}) AS source " + - @"(Id, Date, BaseAssetId, AccountId, ClientId, TradingConditionId, Balance, WithdrawTransferLimit, MarginCall, StopOut, TotalCapital, FreeMargin, MarginAvailable, UsedMargin, MarginInit, PnL, OpenPositionsCount, MarginUsageLevel, IsLive, LegalEntity) - ON (target.Id = source.Id) - WHEN MATCHED THEN - UPDATE SET Date=source.Date, BaseAssetId=source.BaseAssetId, AccountId=source.AccountId, ClientId=source.ClientId, TradingConditionId=source.TradingConditionId, Balance=source.Balance, WithdrawTransferLimit=source.WithdrawTransferLimit, MarginCall=source.MarginCall, StopOut=source.StopOut, TotalCapital=source.TotalCapital, FreeMargin=source.FreeMargin, MarginAvailable=source.MarginAvailable, UsedMargin=source.UsedMargin, MarginInit=source.MarginInit, PnL=source.PnL, OpenPositionsCount=source.OpenPositionsCount, MarginUsageLevel=source.MarginUsageLevel, IsLive=source.IsLive, LegalEntity=source.LegalEntity - WHEN NOT MATCHED THEN - INSERT (Id, Date, BaseAssetId, AccountId, ClientId, TradingConditionId, Balance, WithdrawTransferLimit, MarginCall, StopOut, TotalCapital, FreeMargin, MarginAvailable, UsedMargin, MarginInit, PnL, OpenPositionsCount, MarginUsageLevel, IsLive, LegalEntity) - VALUES (source.Id, source.Date, source.BaseAssetId, source.AccountId, source.ClientId, source.TradingConditionId, source.Balance, source.WithdrawTransferLimit, source.MarginCall, source.StopOut, source.TotalCapital, source.FreeMargin, source.MarginAvailable, source.UsedMargin, source.MarginInit, source.PnL, source.OpenPositionsCount, source.MarginUsageLevel, source.IsLive, source.LegalEntity);"; - - } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Settings.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Settings.cs deleted file mode 100644 index b27c61a9b..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Settings.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Lykke.SettingsReader.Attributes; -using MarginTrading.BrokerBase.Settings; -using MarginTrading.Common.RabbitMq; - -namespace MarginTrading.AccountReportsBroker -{ - public class Settings : BrokerSettingsBase - { - public Db Db { get; set; } - public RabbitMqQueues RabbitMqQueues { get; set; } - [Optional] - public ReportTarget ReportTarget { get; set; } - } - - public class Db - { - public string ReportsConnString { get; set; } - public string HistoryConnString { get; set; } - public string ReportsSqlConnString { get; set; } - } - - public class RabbitMqQueues - { - public RabbitMqQueueInfo AccountStats { get; set; } - public RabbitMqQueueInfo AccountChanged { get; set; } - } - - public enum ReportTarget - { - All, - Azure, - Sql - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Startup.cs b/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Startup.cs deleted file mode 100644 index f003be52f..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.AccountReportsBroker/Startup.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using Autofac; -using Autofac.Core; -using Common.Log; -using Lykke.SettingsReader; -using MarginTrading.AccountReportsBroker.Repositories.AzureRepositories; -using MarginTrading.AccountReportsBroker.Repositories; -using MarginTrading.AccountReportsBroker.Repositories.SqlRepositories; -using MarginTrading.AzureRepositories; -using MarginTrading.BrokerBase; -using MarginTrading.BrokerBase.Settings; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace MarginTrading.AccountReportsBroker -{ - public class Startup : BrokerStartupBase, Settings> - { - protected override string ApplicationName => "MarginTradingAccountReportsBroker"; - - public Startup(IHostingEnvironment env) : base(env) - { - } - - protected override void SetSettingValues(Settings source, IConfigurationRoot configuration) - { - base.SetSettingValues(source, configuration); - - source.ReportTarget = GetReportTarget(configuration); - } - - protected override void RegisterCustomServices(IServiceCollection services, ContainerBuilder builder, IReloadingManager settings, ILog log, bool isLive) - { - var settingsValue = settings.CurrentValue; - - if (settingsValue.ReportTarget == ReportTarget.All || settingsValue.ReportTarget == ReportTarget.Azure) - { - builder.RegisterType() - .As() - .WithParameter( - new ResolvedParameter( - (pi, ctx) => pi.ParameterType == typeof(IAccountsStatsReportsRepository), - (pi, ctx) => new AccountsStatsReportsRepository(settings, log))) - .SingleInstance(); - } - - if (settingsValue.ReportTarget == ReportTarget.All || settingsValue.ReportTarget == ReportTarget.Sql) - { - builder.RegisterType() - .As() - .WithParameter( - new ResolvedParameter( - (pi, ctx) => pi.ParameterType == typeof(IAccountsStatsReportsRepository), - (pi, ctx) => new AccountsStatsReportsSqlRepository(settings.CurrentValue, log))) - .SingleInstance(); - } - - builder.RegisterType().As().SingleInstance(); - - builder.RegisterInstance(new AccountsReportsRepositoryAggregator(new IAccountsReportsRepository[] - { - new AccountsReportsSqlRepository(settings.CurrentValue, log), - new AccountsReportsRepository(settings, log) - })) - .As(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateAccountStatsRepository(settings.Nested(s => s.Db.HistoryConnString), log) - ).SingleInstance(); - } - - private static ReportTarget GetReportTarget(IConfigurationRoot configuration) - { - return Enum.TryParse(configuration["ReportTarget"], out ReportTarget result) - ? result - : ReportTarget.All; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/BrokerApplicationBase.cs b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/BrokerApplicationBase.cs index 590313665..388372f13 100644 --- a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/BrokerApplicationBase.cs +++ b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/BrokerApplicationBase.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Threading.Tasks; using Common.Log; using Lykke.RabbitMqBroker; diff --git a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/BrokerStartupBase.cs b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/BrokerStartupBase.cs index 83dfd1f58..1172d9cc5 100644 --- a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/BrokerStartupBase.cs +++ b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/BrokerStartupBase.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.IO; using Autofac; using Autofac.Extensions.DependencyInjection; @@ -53,7 +56,7 @@ public IServiceProvider ConfigureServices(IServiceCollection services) services.AddSingleton(Configuration); services.AddMvc(); - var isLive = Configuration.IsLive(); + var isLive = true; var applicationSettings = Configuration.LoadSettings() .Nested(s => { diff --git a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Controllers/IsAliveController.cs b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Controllers/IsAliveController.cs index bad9566bc..11e7d2f43 100644 --- a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Controllers/IsAliveController.cs +++ b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Controllers/IsAliveController.cs @@ -1,4 +1,7 @@ -using MarginTrading.BrokerBase.Settings; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.BrokerBase.Settings; using Microsoft.AspNetCore.Mvc; namespace MarginTrading.BrokerBase.Controllers diff --git a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Extensions.cs b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Extensions.cs index 518b3cc0d..b5c9f992d 100644 --- a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Extensions.cs +++ b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Extensions.cs @@ -1,4 +1,7 @@ -using Dapper; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Dapper; using System.Data; using System.Data.SqlClient; diff --git a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/IBrokerApplication.cs b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/IBrokerApplication.cs index e1b9d0900..b37e6b7bd 100644 --- a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/IBrokerApplication.cs +++ b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/IBrokerApplication.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.BrokerBase +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.BrokerBase { public interface IBrokerApplication { diff --git a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/MarginTrading.BrokerBase.csproj b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/MarginTrading.BrokerBase.csproj index 1cd411bdb..aee8e768c 100644 --- a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/MarginTrading.BrokerBase.csproj +++ b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/MarginTrading.BrokerBase.csproj @@ -1,24 +1,28 @@  - netcoreapp2.0 - 1.0.1 + netcoreapp2.1 + 1.16.29 + 7.3 + + + 1701;1702;1705;CA2007;0612;0618;1591 - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/BrokerSettingsBase.cs b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/BrokerSettingsBase.cs index edcc6c582..19872b0a7 100644 --- a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/BrokerSettingsBase.cs +++ b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/BrokerSettingsBase.cs @@ -1,4 +1,7 @@ -using Lykke.SettingsReader.Attributes; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Lykke.SettingsReader.Attributes; namespace MarginTrading.BrokerBase.Settings { diff --git a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/BrokerSettingsRoot.cs b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/BrokerSettingsRoot.cs index 384fd173a..26cb06117 100644 --- a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/BrokerSettingsRoot.cs +++ b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/BrokerSettingsRoot.cs @@ -1,4 +1,6 @@ - +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + namespace MarginTrading.BrokerBase.Settings { public class BrokerSettingsRoot diff --git a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/BrokersLogsSettings.cs b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/BrokersLogsSettings.cs index e93b145af..7f322f72c 100644 --- a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/BrokersLogsSettings.cs +++ b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/BrokersLogsSettings.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.BrokerBase.Settings +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.BrokerBase.Settings { public class BrokersLogsSettings { diff --git a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/CurrentApplicationInfo.cs b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/CurrentApplicationInfo.cs index 61d4e883c..121a507b7 100644 --- a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/CurrentApplicationInfo.cs +++ b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/CurrentApplicationInfo.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.BrokerBase.Settings +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.BrokerBase.Settings { public class CurrentApplicationInfo { diff --git a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/DefaultBrokerApplicationSettings.cs b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/DefaultBrokerApplicationSettings.cs index 6b36b48c0..50e8d99a9 100644 --- a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/DefaultBrokerApplicationSettings.cs +++ b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/DefaultBrokerApplicationSettings.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.BrokerBase.Settings +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.BrokerBase.Settings { public class DefaultBrokerApplicationSettings: IBrokerApplicationSettings where TBrokerSettings: BrokerSettingsBase diff --git a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/IBrokerApplicationSettings.cs b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/IBrokerApplicationSettings.cs index 114b091d8..4ff84e22f 100644 --- a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/IBrokerApplicationSettings.cs +++ b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/IBrokerApplicationSettings.cs @@ -1,4 +1,7 @@ -using Lykke.SettingsReader.Attributes; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Lykke.SettingsReader.Attributes; using JetBrains.Annotations; namespace MarginTrading.BrokerBase.Settings diff --git a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/SlackNotificationSettings.cs b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/SlackNotificationSettings.cs index bccdcd39e..0707f55e7 100644 --- a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/SlackNotificationSettings.cs +++ b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/Settings/SlackNotificationSettings.cs @@ -1,4 +1,7 @@ -using Lykke.AzureQueueIntegration; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Lykke.AzureQueueIntegration; namespace MarginTrading.BrokerBase.Settings { diff --git a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/WebAppProgramBase.cs b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/WebAppProgramBase.cs index 1fab4aab9..3709c92c3 100644 --- a/src/MarginTrading.Brokers/MarginTrading.BrokerBase/WebAppProgramBase.cs +++ b/src/MarginTrading.Brokers/MarginTrading.BrokerBase/WebAppProgramBase.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.IO; using System.Threading; using Microsoft.AspNetCore.Hosting; diff --git a/src/MarginTrading.Brokers/MarginTrading.Brokers.sln b/src/MarginTrading.Brokers/MarginTrading.Brokers.sln deleted file mode 100644 index 878e0b3a5..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.Brokers.sln +++ /dev/null @@ -1,73 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26430.12 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Brokers", "Brokers", "{F30DE7CA-51CD-47C5-85EB-7156FA9D87E2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.AccountHistoryBroker", "MarginTrading.AccountHistoryBroker\MarginTrading.AccountHistoryBroker.csproj", "{36CEACA8-B152-4DDD-AC80-B9BEDBE6DD05}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1. Infrastructure", "1. Infrastructure", "{6861CCE5-4A2E-423B-9FAC-6FD3F82B6D87}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2. Repositories", "2. Repositories", "{AB04641B-CDE2-475F-84BF-D42AA9CD0045}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.OrderHistoryBroker", "MarginTrading.OrderHistoryBroker\MarginTrading.OrderHistoryBroker.csproj", "{D9475450-FB95-480B-8D77-D9D5B2EAC265}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.OrderRejectedBroker", "MarginTrading.OrderRejectedBroker\MarginTrading.OrderRejectedBroker.csproj", "{5595E80D-6703-4EC1-A232-FFBD8F0A1C95}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.Common", "..\MarginTrading.Common\MarginTrading.Common.csproj", "{F15AA5FA-BD7C-43AE-8849-4BB7702F4DDA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.Core", "..\MarginTrading.Core\MarginTrading.Core.csproj", "{F7AAEB5E-0F81-4F2D-9965-7678EC805153}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.Services", "..\MarginTrading.Services\MarginTrading.Services.csproj", "{B346C9E3-B161-4026-91D7-1A316918AD36}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarginTrading.AzureRepositories", "..\MarginTrading.AzureRepositories\MarginTrading.AzureRepositories.csproj", "{291399B7-4D6E-4B68-8D94-1D7FA98A0EFC}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {36CEACA8-B152-4DDD-AC80-B9BEDBE6DD05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {36CEACA8-B152-4DDD-AC80-B9BEDBE6DD05}.Debug|Any CPU.Build.0 = Debug|Any CPU - {36CEACA8-B152-4DDD-AC80-B9BEDBE6DD05}.Release|Any CPU.ActiveCfg = Release|Any CPU - {36CEACA8-B152-4DDD-AC80-B9BEDBE6DD05}.Release|Any CPU.Build.0 = Release|Any CPU - {D9475450-FB95-480B-8D77-D9D5B2EAC265}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D9475450-FB95-480B-8D77-D9D5B2EAC265}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D9475450-FB95-480B-8D77-D9D5B2EAC265}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D9475450-FB95-480B-8D77-D9D5B2EAC265}.Release|Any CPU.Build.0 = Release|Any CPU - {5595E80D-6703-4EC1-A232-FFBD8F0A1C95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5595E80D-6703-4EC1-A232-FFBD8F0A1C95}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5595E80D-6703-4EC1-A232-FFBD8F0A1C95}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5595E80D-6703-4EC1-A232-FFBD8F0A1C95}.Release|Any CPU.Build.0 = Release|Any CPU - {F15AA5FA-BD7C-43AE-8849-4BB7702F4DDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F15AA5FA-BD7C-43AE-8849-4BB7702F4DDA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F15AA5FA-BD7C-43AE-8849-4BB7702F4DDA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F15AA5FA-BD7C-43AE-8849-4BB7702F4DDA}.Release|Any CPU.Build.0 = Release|Any CPU - {F7AAEB5E-0F81-4F2D-9965-7678EC805153}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F7AAEB5E-0F81-4F2D-9965-7678EC805153}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F7AAEB5E-0F81-4F2D-9965-7678EC805153}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F7AAEB5E-0F81-4F2D-9965-7678EC805153}.Release|Any CPU.Build.0 = Release|Any CPU - {B346C9E3-B161-4026-91D7-1A316918AD36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B346C9E3-B161-4026-91D7-1A316918AD36}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B346C9E3-B161-4026-91D7-1A316918AD36}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B346C9E3-B161-4026-91D7-1A316918AD36}.Release|Any CPU.Build.0 = Release|Any CPU - {291399B7-4D6E-4B68-8D94-1D7FA98A0EFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {291399B7-4D6E-4B68-8D94-1D7FA98A0EFC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {291399B7-4D6E-4B68-8D94-1D7FA98A0EFC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {291399B7-4D6E-4B68-8D94-1D7FA98A0EFC}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {36CEACA8-B152-4DDD-AC80-B9BEDBE6DD05} = {F30DE7CA-51CD-47C5-85EB-7156FA9D87E2} - {D9475450-FB95-480B-8D77-D9D5B2EAC265} = {F30DE7CA-51CD-47C5-85EB-7156FA9D87E2} - {5595E80D-6703-4EC1-A232-FFBD8F0A1C95} = {F30DE7CA-51CD-47C5-85EB-7156FA9D87E2} - {F15AA5FA-BD7C-43AE-8849-4BB7702F4DDA} = {6861CCE5-4A2E-423B-9FAC-6FD3F82B6D87} - {F7AAEB5E-0F81-4F2D-9965-7678EC805153} = {6861CCE5-4A2E-423B-9FAC-6FD3F82B6D87} - {B346C9E3-B161-4026-91D7-1A316918AD36} = {6861CCE5-4A2E-423B-9FAC-6FD3F82B6D87} - {291399B7-4D6E-4B68-8D94-1D7FA98A0EFC} = {AB04641B-CDE2-475F-84BF-D42AA9CD0045} - EndGlobalSection -EndGlobal diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Application.cs b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Application.cs index 94933645b..393e4a011 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Application.cs +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Application.cs @@ -1,17 +1,18 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; using Common.Log; using Lykke.SlackNotifications; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Mappers; +using MarginTrading.Backend.Contracts.ExchangeConnector; using MarginTrading.BrokerBase; using MarginTrading.BrokerBase.Settings; -using MarginTrading.Contract.BackendContracts; using MarginTrading.ExternalOrderBroker.Models; using MarginTrading.ExternalOrderBroker.Repositories; namespace MarginTrading.ExternalOrderBroker { - public class Application : BrokerApplicationBase + public class Application : BrokerApplicationBase { private readonly IExternalOrderReportRepository _externalOrderReportRepository; private readonly Settings.AppSettings _appSettings; @@ -30,7 +31,7 @@ public Application(IExternalOrderReportRepository externalOrderReportRepository, protected override BrokerSettingsBase Settings => _appSettings; protected override string ExchangeName => _appSettings.RabbitMqQueues.ExternalOrder.ExchangeName; - protected override Task HandleMessage(Lykke.Service.ExchangeConnector.Client.Models.ExecutionReport order) + protected override Task HandleMessage(ExecutionReport order) { var externalOrder = ExternalOrderReport.Create(order); return _externalOrderReportRepository.InsertOrReplaceAsync(externalOrder); diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Dockerfile b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Dockerfile index 49d5e6a0e..4991fb903 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Dockerfile +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Dockerfile @@ -1,4 +1,4 @@ -FROM microsoft/aspnetcore:2.0 +FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 WORKDIR /app COPY . . ENTRYPOINT ["dotnet", "MarginTrading.ExternalOrderBroker.dll"] diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/MarginTrading.ExternalOrderBroker.csproj b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/MarginTrading.ExternalOrderBroker.csproj index 8cc622ec3..95277f220 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/MarginTrading.ExternalOrderBroker.csproj +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/MarginTrading.ExternalOrderBroker.csproj @@ -1,13 +1,17 @@  - netcoreapp2.0 + netcoreapp2.2 MarginTrading.ExternalOrderBroker Exe MarginTrading.ExternalOrderBroker false false false - 1.0.1 + 1.16.29 + 7.3 + + + 1701;1702;1705;CA2007;0612;0618;1591 @@ -15,25 +19,24 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - + + + diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Models/ExecutionStatusReport.cs b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Models/ExecutionStatusReport.cs index 2a76387d5..c2258fe2f 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Models/ExecutionStatusReport.cs +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Models/ExecutionStatusReport.cs @@ -1,4 +1,7 @@ -using System.Runtime.Serialization; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Runtime.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Converters; diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Models/ExternalOrderReport.cs b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Models/ExternalOrderReport.cs index c574fdbd8..920dbb68d 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Models/ExternalOrderReport.cs +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Models/ExternalOrderReport.cs @@ -1,4 +1,9 @@ -namespace MarginTrading.ExternalOrderBroker.Models +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Backend.Contracts.ExchangeConnector; + +namespace MarginTrading.ExternalOrderBroker.Models { public class ExternalOrderReport : IExternalOrderReport { @@ -39,7 +44,7 @@ public override string ToString() + "Message: " + this.Message; } - public static ExternalOrderReport Create(Lykke.Service.ExchangeConnector.Client.Models.ExecutionReport externalContract) + public static ExternalOrderReport Create(ExecutionReport externalContract) { return new ExternalOrderReport { @@ -51,7 +56,7 @@ public static ExternalOrderReport Create(Lykke.Service.ExchangeConnector.Client. Time = externalContract.Time, Price = externalContract.Price, Volume = externalContract.Volume * - (externalContract.Type == Lykke.Service.ExchangeConnector.Client.Models.TradeType.Buy ? 1 : -1), + (externalContract.Type == TradeType.Buy ? 1 : -1), Fee = externalContract.Fee, Id = externalContract.ExchangeOrderId, Status = externalContract.ExecutionStatus.ToString(), diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Models/IExternalOrderReport.cs b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Models/IExternalOrderReport.cs index 78c78ba3e..ce7e82b54 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Models/IExternalOrderReport.cs +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Models/IExternalOrderReport.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.ExternalOrderBroker.Models +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.ExternalOrderBroker.Models { public interface IExternalOrderReport { diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Models/TradeTypeReport.cs b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Models/TradeTypeReport.cs index 2c7754310..157659439 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Models/TradeTypeReport.cs +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Models/TradeTypeReport.cs @@ -1,4 +1,7 @@ -using System.Runtime.Serialization; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Runtime.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Converters; diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Program.cs b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Program.cs index 124800201..c3b98c6a5 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Program.cs +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Program.cs @@ -1,4 +1,7 @@ -using MarginTrading.BrokerBase; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.BrokerBase; namespace MarginTrading.ExternalOrderBroker { diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Properties/AssemblyInfo.cs b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Properties/AssemblyInfo.cs index d3c81f9a2..212530e60 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Properties/AssemblyInfo.cs +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Properties/AssemblyInfo.cs @@ -1,5 +1,7 @@ -using System.Reflection; -using System.Runtime.CompilerServices; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Reflection; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/Azure/ExternalOrderReportAzureRepository.cs b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/Azure/ExternalOrderReportAzureRepository.cs index fdb7aba54..213010f60 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/Azure/ExternalOrderReportAzureRepository.cs +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/Azure/ExternalOrderReportAzureRepository.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; using AzureStorage; using AzureStorage.Tables; using Common.Log; diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/Azure/ExternalOrderReportEntity.cs b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/Azure/ExternalOrderReportEntity.cs index d4eeed9f6..36d9b0f48 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/Azure/ExternalOrderReportEntity.cs +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/Azure/ExternalOrderReportEntity.cs @@ -1,4 +1,7 @@ -using MarginTrading.ExternalOrderBroker.Models; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.ExternalOrderBroker.Models; using Microsoft.WindowsAzure.Storage.Table; namespace MarginTrading.ExternalOrderBroker.Repositories.Azure diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/IExternalOrderReportRepository.cs b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/IExternalOrderReportRepository.cs index d6c0d4f94..d22d19caa 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/IExternalOrderReportRepository.cs +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/IExternalOrderReportRepository.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; using MarginTrading.ExternalOrderBroker.Models; namespace MarginTrading.ExternalOrderBroker.Repositories diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/ReportRepositoryAggregator.cs b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/ReportRepositoryAggregator.cs index 078e00f55..60dc68896 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/ReportRepositoryAggregator.cs +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/ReportRepositoryAggregator.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using MarginTrading.ExternalOrderBroker.Models; diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/Sql/ExternalOrderReportSqlRepository.cs b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/Sql/ExternalOrderReportSqlRepository.cs index 21cefa63d..dd14f3f1a 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/Sql/ExternalOrderReportSqlRepository.cs +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Repositories/Sql/ExternalOrderReportSqlRepository.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Data.SqlClient; using System.Linq; using System.Threading.Tasks; diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Settings/AppSettings.cs b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Settings/AppSettings.cs index 3e9ebbc0e..931946c6d 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Settings/AppSettings.cs +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Settings/AppSettings.cs @@ -1,5 +1,7 @@ -using MarginTrading.BrokerBase.Settings; -using MarginTrading.Common.RabbitMq; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.BrokerBase.Settings; namespace MarginTrading.ExternalOrderBroker.Settings { diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Settings/DbSettings.cs b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Settings/DbSettings.cs index 2db1d0625..f32f418ab 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Settings/DbSettings.cs +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Settings/DbSettings.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.ExternalOrderBroker.Settings +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.ExternalOrderBroker.Settings { public class DbSettings { diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Settings/RabbitMqQueuesSettings.cs b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Settings/RabbitMqQueuesSettings.cs index df4754acf..1f3a87e82 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Settings/RabbitMqQueuesSettings.cs +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Settings/RabbitMqQueuesSettings.cs @@ -1,4 +1,7 @@ -using MarginTrading.Common.RabbitMq; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Common.RabbitMq; namespace MarginTrading.ExternalOrderBroker.Settings { diff --git a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Startup.cs b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Startup.cs index 995e7988b..5bdb5a869 100644 --- a/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Startup.cs +++ b/src/MarginTrading.Brokers/MarginTrading.ExternalOrderBroker/Startup.cs @@ -1,8 +1,9 @@ -using Autofac; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Autofac; using Common.Log; using Lykke.SettingsReader; -using MarginTrading.AzureRepositories; -using MarginTrading.Backend.Core; using MarginTrading.BrokerBase; using MarginTrading.BrokerBase.Settings; using MarginTrading.ExternalOrderBroker.Repositories; diff --git a/src/MarginTrading.Brokers/MarginTrading.MigrateApp/Application.cs b/src/MarginTrading.Brokers/MarginTrading.MigrateApp/Application.cs deleted file mode 100644 index 712fb8c56..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.MigrateApp/Application.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using AzureStorage.Tables; -using Common.Log; -using Lykke.SettingsReader; -using Lykke.SlackNotifications; -using MarginTrading.AccountReportsBroker.Repositories.AzureRepositories.Entities; -using MarginTrading.AzureRepositories; -using MarginTrading.Backend.Core; -using MarginTrading.BrokerBase; -using MarginTrading.BrokerBase.Settings; -using MarginTrading.Contract.RabbitMqMessageModels; -using MoreLinq; - -namespace MarginTrading.MigrateApp -{ - internal class Application : BrokerApplicationBase - { - private readonly Settings _settings; - private readonly IReloadingManager _reloadingManager; - - public Application(ILog logger, Settings settings, - CurrentApplicationInfo applicationInfo, - ISlackNotificationsSender slackNotificationsSender, - IReloadingManager reloadingManager) - : base(logger, slackNotificationsSender, applicationInfo) - { - _settings = settings; - _reloadingManager = reloadingManager; - } - - protected override BrokerSettingsBase Settings => _settings; - protected override string ExchangeName => "Fake"; - - protected override Task HandleMessage(BidAskPairRabbitMqContract message) - { - throw new NotSupportedException(); - } - - public override void Run() - { - WriteInfoToLogAndSlack("Starting MigrateApp"); - - try - { - Task.WaitAll( - Task.Run(ProcessOrders) - ); - WriteInfoToLogAndSlack("MigrateApp finished"); - } - catch (Exception ex) - { - _logger.WriteErrorAsync(ApplicationInfo.ApplicationFullName, "Application.RunAsync", null, ex) - .GetAwaiter() - .GetResult(); - } - } - - private async Task ProcessOrders() - { - var repository = AzureTableStorage.Create( - _reloadingManager.Nested(s => s.Db.MarginTradingConnString), "MarginTradingOrdersHistory", _logger); - (await repository.GetDataAsync()) - .Where(a => string.IsNullOrWhiteSpace(a.OrderUpdateType) && a.Status != "Closed") - .GroupBy(a => a.PartitionKey) - .SelectMany(g => g.Batch(500)) - .ForEach(batch => - { - repository.DeleteAsync(batch).GetAwaiter().GetResult(); - Console.WriteLine($"Deleted {batch.Count()} rows"); - }); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.MigrateApp/Dockerfile b/src/MarginTrading.Brokers/MarginTrading.MigrateApp/Dockerfile deleted file mode 100644 index a9940445d..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.MigrateApp/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM microsoft/aspnetcore:2.0 -WORKDIR /app -COPY . . -ENTRYPOINT ["dotnet", "MarginTrading.MigrateApp.dll"] diff --git a/src/MarginTrading.Brokers/MarginTrading.MigrateApp/MarginTrading.MigrateApp.csproj b/src/MarginTrading.Brokers/MarginTrading.MigrateApp/MarginTrading.MigrateApp.csproj deleted file mode 100644 index 074e51809..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.MigrateApp/MarginTrading.MigrateApp.csproj +++ /dev/null @@ -1,41 +0,0 @@ - - - Exe - netcoreapp2.0 - 1.0.1 - - - - - - - - - - - - - - - - - - - - - - - - - - {9595DED9-58C9-4732-87B1-891AFC2282B1} - MarginTrading.AccountReportsBroker - - - - - - PreserveNewest - - - \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.MigrateApp/Program.cs b/src/MarginTrading.Brokers/MarginTrading.MigrateApp/Program.cs deleted file mode 100644 index dec5b7a06..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.MigrateApp/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MarginTrading.BrokerBase; - -namespace MarginTrading.MigrateApp -{ - public class Program : WebAppProgramBase - { - public static void Main(string[] args) - { - RunOnPort(5016); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.MigrateApp/Settings.cs b/src/MarginTrading.Brokers/MarginTrading.MigrateApp/Settings.cs deleted file mode 100644 index 0c7aaff9d..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.MigrateApp/Settings.cs +++ /dev/null @@ -1,23 +0,0 @@ -using MarginTrading.BrokerBase.Settings; -using MarginTrading.Common.RabbitMq; - -namespace MarginTrading.MigrateApp -{ - public class Settings : BrokerSettingsBase - { - public Db Db { get; set; } - public RabbitMqQueues RabbitMqQueues { get; set; } - } - - public class Db - { - public string HistoryConnString { get; set; } - public string MarginTradingConnString { get; set; } - public string ReportsConnString { get; set; } - } - - public class RabbitMqQueues - { - public RabbitMqQueueInfo OrderbookPrices { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.MigrateApp/Startup.cs b/src/MarginTrading.Brokers/MarginTrading.MigrateApp/Startup.cs deleted file mode 100644 index 02a8d96e2..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.MigrateApp/Startup.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Autofac; -using Common.Log; -using Lykke.SettingsReader; -using MarginTrading.AzureRepositories; -using MarginTrading.AzureRepositories.Contract; -using MarginTrading.Backend.Core; -using MarginTrading.BrokerBase; -using MarginTrading.BrokerBase.Settings; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; - -namespace MarginTrading.MigrateApp -{ - public class Startup : BrokerStartupBase, Settings> - { - protected override string ApplicationName => "MarginTradingMigrateApp"; - - public Startup(IHostingEnvironment env) : base(env) - { - } - - protected override void RegisterCustomServices(IServiceCollection services, ContainerBuilder builder, - IReloadingManager settings, ILog log, bool isLive) - { - builder.RegisterType().As().SingleInstance(); - builder.RegisterInstance(settings).SingleInstance(); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Application.cs b/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Application.cs deleted file mode 100644 index 7af0c1937..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Application.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Threading.Tasks; -using Common.Log; -using Lykke.SlackNotifications; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Mappers; -using MarginTrading.BrokerBase; -using MarginTrading.BrokerBase.Settings; -using MarginTrading.Contract.BackendContracts; - -namespace MarginTrading.OrderHistoryBroker -{ - public class Application : BrokerApplicationBase - { - private readonly IMarginTradingOrdersHistoryRepository[] _ordersHistoryRepositories; - private readonly Settings _settings; - - public Application(IMarginTradingOrdersHistoryRepository[] ordersHistoryRepositories, ILog logger, - Settings settings, CurrentApplicationInfo applicationInfo, - ISlackNotificationsSender slackNotificationsSender) : base(logger, slackNotificationsSender, - applicationInfo) - { - _ordersHistoryRepositories = ordersHistoryRepositories; - _settings = settings; - } - - protected override BrokerSettingsBase Settings => _settings; - protected override string ExchangeName => _settings.RabbitMqQueues.OrderHistory.ExchangeName; - - protected override async Task HandleMessage(OrderFullContract order) - { - var orderHistory = order.ToOrderHistoryDomain(); - - foreach (var ordersHistoryRepository in _ordersHistoryRepositories) - { - await ordersHistoryRepository.AddAsync(orderHistory); - } - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Dockerfile b/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Dockerfile deleted file mode 100644 index 75d3b2030..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM microsoft/aspnetcore:2.0 -WORKDIR /app -COPY . . -ENTRYPOINT ["dotnet", "MarginTrading.OrderHistoryBroker.dll"] diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/MarginTrading.OrderHistoryBroker.csproj b/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/MarginTrading.OrderHistoryBroker.csproj deleted file mode 100644 index 2304b8340..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/MarginTrading.OrderHistoryBroker.csproj +++ /dev/null @@ -1,44 +0,0 @@ - - - netcoreapp2.0 - MarginTrading.OrderHistoryBroker - Exe - MarginTrading.OrderHistoryBroker - false - false - false - 1.0.1 - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Program.cs b/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Program.cs deleted file mode 100644 index 2b6e7f540..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MarginTrading.BrokerBase; - -namespace MarginTrading.OrderHistoryBroker -{ - public class Program: WebAppProgramBase - { - public static void Main(string[] args) - { - RunOnPort(5013); - } - } -} diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Properties/AssemblyInfo.cs b/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Properties/AssemblyInfo.cs deleted file mode 100644 index 0643b0995..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -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: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Lykke")] -[assembly: AssemblyProduct("MarginTrading.OrderHistoryBroker")] -[assembly: AssemblyTrademark("")] - -// 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("797db257-15c9-4699-aaef-b206b92c0cc6")] diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Repositories/SqlRepositories/MarginTradingOrdersHistorySqlRepository.cs b/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Repositories/SqlRepositories/MarginTradingOrdersHistorySqlRepository.cs deleted file mode 100644 index a0c9a24b4..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Repositories/SqlRepositories/MarginTradingOrdersHistorySqlRepository.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Threading.Tasks; -using Common; -using Common.Log; -using Dapper; -using MarginTrading.AzureRepositories; -using MarginTrading.Backend.Core; -using MarginTrading.BrokerBase; - -namespace MarginTrading.OrderHistoryBroker.Repositories.SqlRepositories -{ - public class MarginTradingOrdersHistorySqlRepository : IMarginTradingOrdersHistoryRepository - { - private const string TableName = "OrdersChangeHistory"; - - private const string CreateTableScript = "CREATE TABLE [{0}](" + - @"[OID] [int] NOT NULL IDENTITY (1,1) PRIMARY KEY, -[Id] [nvarchar](64) NOT NULL, -[Code] [bigint] NULL, -[ClientId] [nvarchar] (64) NOT NULL, -[TradingConditionId] [nvarchar] (64) NOT NULL, -[AccountAssetId] [nvarchar] (64) NULL, -[Instrument] [nvarchar] (64) NOT NULL, -[Type] [nvarchar] (64) NOT NULL, -[CreateDate] [datetime] NOT NULL, -[OpenDate] [datetime] NULL, -[CloseDate] [datetime] NULL, -[ExpectedOpenPrice] [float] NULL, -[OpenPrice] [float] NULL, -[ClosePrice] [float] NULL, -[QuoteRate] [float] NULL, -[Volume] [float] NULL, -[TakeProfit] [float] NULL, -[StopLoss] [float] NULL, -[CommissionLot] [float] NULL, -[OpenCommission] [float] NULL, -[CloseCommission] [float] NULL, -[SwapCommission] [float] NULL, -[EquivalentAsset] [nvarchar] (64) NULL, -[OpenPriceEquivalent] [float] NULL, -[ClosePriceEquivalent] [float] NULL, -[StartClosingDate] [datetime] NULL, -[Status] [nvarchar] (64) NULL, -[CloseReason] [nvarchar] (64) NULL, -[FillType] [nvarchar] (64) NULL, -[RejectReason] [nvarchar] (64) NULL, -[RejectReasonText] [nvarchar] (255) NULL, -[Comment] [nvarchar] (255) NULL, -[MatchedVolume] [float] NULL, -[MatchedCloseVolume] [float] NULL, -[Fpl] [float] NULL, -[PnL] [float] NULL, -[InterestRateSwap] [float] NULL, -[MarginInit] [float] NULL, -[MarginMaintenance] [float] NULL, -[OrderUpdateType] [nvarchar] (64) NULL, -[OpenExternalOrderId] [nvarchar] (64) NULL, -[OpenExternalProviderId] [nvarchar] (64) NULL, -[CloseExternalOrderId] [nvarchar] (64) NULL, -[CloseExternalProviderId] [nvarchar] (64) NULL, -[MatchingEngineMode] [nvarchar] (64) NULL, -[LegalEntity] [nvarchar] (64) NULL);"; - - private readonly Settings _settings; - private readonly ILog _log; - - public MarginTradingOrdersHistorySqlRepository(Settings settings, ILog log) - { - _settings = settings; - _log = log; - - using (var conn = new SqlConnection(_settings.Db.ReportsSqlConnString)) - { - try { conn.CreateTableIfDoesntExists(CreateTableScript, TableName); } - catch (Exception ex) - { - _log?.WriteErrorAsync("OrdersChangeHistory", "CreateTableIfDoesntExists", null, ex); - throw; - } - } - } - - public async Task AddAsync(IOrderHistory order) - { - using (var conn = new SqlConnection(_settings.Db.ReportsSqlConnString)) - { - var query = $"insert into {TableName} " + - @"(Id, Code, ClientId, TradingConditionId, AccountAssetId, Instrument, Type, CreateDate, OpenDate, - CloseDate, ExpectedOpenPrice, OpenPrice, ClosePrice, QuoteRate, Volume, TakeProfit, - StopLoss, CommissionLot, OpenCommission, CloseCommission, SwapCommission, StartClosingDate, - Status, CloseReason, FillType, RejectReason, RejectReasonText, Comment, MatchedVolume, - MatchedCloseVolume, Fpl, PnL, InterestRateSwap, MarginInit, MarginMaintenance, - OrderUpdateType, OpenExternalOrderId, OpenExternalProviderId, CloseExternalOrderId, - CloseExternalProviderId, MatchingEngineMode, LegalEntity) - values - (@Id, @Code, @ClientId, @TradingConditionId, @AccountAssetId, @Instrument, @Type, @CreateDate, @OpenDate, - @CloseDate, @ExpectedOpenPrice, @OpenPrice, @ClosePrice, @QuoteRate, @Volume, @TakeProfit, - @StopLoss, @CommissionLot, @OpenCommission, @CloseCommission, @SwapCommission, @StartClosingDate, - @Status, @CloseReason, @FillType, @RejectReason, @RejectReasonText, @Comment, @MatchedVolume, - @MatchedCloseVolume, @Fpl, @PnL, @InterestRateSwap, @MarginInit, @MarginMaintenance, - @OrderUpdateType, @OpenExternalOrderId, @OpenExternalProviderId, @CloseExternalOrderId, - @CloseExternalProviderId, @MatchingEngineMode, @LegalEntity)"; - - try - { - var entity = MarginTradingOrderHistoryEntity.Create(order); - await conn.ExecuteAsync(query, entity); - } - catch (Exception ex) - { - var msg = $"Error {ex.Message} \n" + - "Entity : \n" + - order.ToJson(); - - _log?.WriteWarning("AccountTransactionsReportsSqlRepository", "InsertOrReplaceAsync", msg); - - throw new Exception(msg); - } - } - } - - public Task> GetHistoryAsync() - { - throw new NotImplementedException(); - } - - public Task> GetHistoryAsync(string clientId, string[] accountIds, DateTime? @from, DateTime? to) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Settings.cs b/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Settings.cs deleted file mode 100644 index 9a12a3dcf..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Settings.cs +++ /dev/null @@ -1,22 +0,0 @@ -using MarginTrading.BrokerBase.Settings; -using MarginTrading.Common.RabbitMq; - -namespace MarginTrading.OrderHistoryBroker -{ - public class Settings : BrokerSettingsBase - { - public Db Db { get; set; } - public RabbitMqQueues RabbitMqQueues { get; set; } - } - - public class Db - { - public string HistoryConnString { get; set; } - public string ReportsSqlConnString { get; set; } - } - - public class RabbitMqQueues - { - public RabbitMqQueueInfo OrderHistory { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Startup.cs b/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Startup.cs deleted file mode 100644 index 44de090ca..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.OrderHistoryBroker/Startup.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Autofac; -using Common.Log; -using Lykke.SettingsReader; -using MarginTrading.AzureRepositories; -using MarginTrading.Backend.Core; -using MarginTrading.BrokerBase; -using MarginTrading.BrokerBase.Settings; -using MarginTrading.OrderHistoryBroker.Repositories.SqlRepositories; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; - -namespace MarginTrading.OrderHistoryBroker -{ - public class Startup : BrokerStartupBase, Settings> - { - public Startup(IHostingEnvironment env) : base(env) - { - } - - protected override string ApplicationName => "MarginTradingOrderHistoryBroker"; - - protected override void RegisterCustomServices(IServiceCollection services, ContainerBuilder builder, IReloadingManager settings, ILog log, bool isLive) - { - builder.RegisterType().As().SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateOrdersHistoryRepository(settings.Nested(s => s.Db.HistoryConnString), log) - ).SingleInstance(); - builder.Register(ctx => - new MarginTradingOrdersHistorySqlRepository(settings.CurrentValue, log) - ).SingleInstance(); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Application.cs b/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Application.cs deleted file mode 100644 index 3d11b6074..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Application.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Threading.Tasks; -using Common.Log; -using Lykke.SlackNotifications; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Mappers; -using MarginTrading.BrokerBase; -using MarginTrading.BrokerBase.Settings; -using MarginTrading.Contract.BackendContracts; - -namespace MarginTrading.OrderRejectedBroker -{ - public class Application : BrokerApplicationBase - { - private readonly IMarginTradingOrdersRejectedRepository _ordersRejectedRepository; - private readonly Settings _settings; - - public Application(IMarginTradingOrdersRejectedRepository ordersRejectedRepository, - ILog logger, Settings settings, CurrentApplicationInfo applicationInfo, - ISlackNotificationsSender slackNotificationsSender) - : base(logger, slackNotificationsSender, applicationInfo) - { - _ordersRejectedRepository = ordersRejectedRepository; - _settings = settings; - } - - protected override BrokerSettingsBase Settings => _settings; - protected override string ExchangeName => _settings.RabbitMqQueues.OrderRejected.ExchangeName; - - protected override Task HandleMessage(OrderFullContract order) - { - var orderHistory = order.ToOrderHistoryDomain(); - return _ordersRejectedRepository.AddAsync(orderHistory); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Dockerfile b/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Dockerfile deleted file mode 100644 index 9ff333c34..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM microsoft/aspnetcore:2.0 -WORKDIR /app -COPY . . -ENTRYPOINT ["dotnet", "MarginTrading.OrderRejectedBroker.dll"] diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/MarginTrading.OrderRejectedBroker.csproj b/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/MarginTrading.OrderRejectedBroker.csproj deleted file mode 100644 index 68fa6f302..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/MarginTrading.OrderRejectedBroker.csproj +++ /dev/null @@ -1,44 +0,0 @@ - - - netcoreapp2.0 - MarginTrading.OrderRejectedBroker - Exe - MarginTrading.OrderRejectedBroker - false - false - false - 1.0.1 - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Program.cs b/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Program.cs deleted file mode 100644 index 1016c1af4..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MarginTrading.BrokerBase; - -namespace MarginTrading.OrderRejectedBroker -{ - public class Program : WebAppProgramBase - { - public static void Main(string[] args) - { - RunOnPort(5014); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Properties/AssemblyInfo.cs b/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Properties/AssemblyInfo.cs deleted file mode 100644 index 55787d887..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -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: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Lykke")] -[assembly: AssemblyProduct("MarginTrading.OrderRejectedBroker")] -[assembly: AssemblyTrademark("")] - -// 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("79a17e52-3f6f-49d9-972c-0787850fb7f4")] diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Settings.cs b/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Settings.cs deleted file mode 100644 index 4134c64e7..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Settings.cs +++ /dev/null @@ -1,21 +0,0 @@ -using MarginTrading.BrokerBase.Settings; -using MarginTrading.Common.RabbitMq; - -namespace MarginTrading.OrderRejectedBroker -{ - public class Settings : BrokerSettingsBase - { - public Db Db { get; set; } - public RabbitMqQueues RabbitMqQueues { get; set; } - } - - public class Db - { - public string HistoryConnString { get; set; } - } - - public class RabbitMqQueues - { - public RabbitMqQueueInfo OrderRejected { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Startup.cs b/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Startup.cs deleted file mode 100644 index 3783c9ec3..000000000 --- a/src/MarginTrading.Brokers/MarginTrading.OrderRejectedBroker/Startup.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Autofac; -using Common.Log; -using Lykke.SettingsReader; -using MarginTrading.AzureRepositories; -using MarginTrading.Backend.Core; -using MarginTrading.BrokerBase; -using MarginTrading.BrokerBase.Settings; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; - -namespace MarginTrading.OrderRejectedBroker -{ - public class Startup : BrokerStartupBase, Settings> - { - public Startup(IHostingEnvironment env) : base(env) - { - } - - protected override string ApplicationName => "MarginTradingOrderRejectedBroker"; - - protected override void RegisterCustomServices(IServiceCollection services, ContainerBuilder builder, IReloadingManager settings, ILog log, bool isLive) - { - builder.RegisterType().As().SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateOrdersRejectedRepository(settings.Nested(s => s.Db.HistoryConnString), log) - ).SingleInstance(); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Application.cs b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Application.cs index 6279c7c7d..a526c48e3 100644 --- a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Application.cs +++ b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Application.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; using Common.Log; using Lykke.SlackNotifications; using MarginTrading.BrokerBase; diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Dockerfile b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Dockerfile index 87482b8bb..f13e853dd 100644 --- a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Dockerfile +++ b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Dockerfile @@ -1,4 +1,4 @@ -FROM microsoft/aspnetcore:2.0 +FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 WORKDIR /app COPY . . ENTRYPOINT ["dotnet", "MarginTrading.OrderbookBestPricesBroker.dll"] diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/MarginTrading.OrderbookBestPricesBroker.csproj b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/MarginTrading.OrderbookBestPricesBroker.csproj index c429428fa..ca95dcb9d 100644 --- a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/MarginTrading.OrderbookBestPricesBroker.csproj +++ b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/MarginTrading.OrderbookBestPricesBroker.csproj @@ -1,29 +1,33 @@  Exe - netcoreapp2.0 - 1.0.1 + netcoreapp2.2 + 1.16.29 + 7.3 + + + 1701;1702;1705;CA2007;0612;0618;1591 - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - + + + diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Program.cs b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Program.cs index 416085c2e..c00592a8b 100644 --- a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Program.cs +++ b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Program.cs @@ -1,4 +1,7 @@ -using MarginTrading.BrokerBase; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.BrokerBase; namespace MarginTrading.OrderbookBestPricesBroker { diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Repositories/IOrderbookBestPricesRepository.cs b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Repositories/IOrderbookBestPricesRepository.cs index 8679d01dc..19b97a134 100644 --- a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Repositories/IOrderbookBestPricesRepository.cs +++ b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Repositories/IOrderbookBestPricesRepository.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Threading.Tasks; namespace MarginTrading.OrderbookBestPricesBroker.Repositories diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Repositories/OrderbookBestPricesHistoryEntity.cs b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Repositories/OrderbookBestPricesHistoryEntity.cs index 8f5de54b0..c222f61f5 100644 --- a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Repositories/OrderbookBestPricesHistoryEntity.cs +++ b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Repositories/OrderbookBestPricesHistoryEntity.cs @@ -1,4 +1,7 @@ -using Lykke.AzureStorage.Tables; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Lykke.AzureStorage.Tables; namespace MarginTrading.OrderbookBestPricesBroker.Repositories { diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Repositories/OrderbookBestPricesRepository.cs b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Repositories/OrderbookBestPricesRepository.cs index 39aa3089f..8b283989d 100644 --- a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Repositories/OrderbookBestPricesRepository.cs +++ b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Repositories/OrderbookBestPricesRepository.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Threading.Tasks; using AzureStorage; using AzureStorage.Tables; diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Settings.cs b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Settings.cs index 0af006147..27b636fbb 100644 --- a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Settings.cs +++ b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Settings.cs @@ -1,4 +1,7 @@ -using MarginTrading.BrokerBase.Settings; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.BrokerBase.Settings; using MarginTrading.Common.RabbitMq; namespace MarginTrading.OrderbookBestPricesBroker diff --git a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Startup.cs b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Startup.cs index 8db635811..6f8df2298 100644 --- a/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Startup.cs +++ b/src/MarginTrading.Brokers/MarginTrading.OrderbookBestPricesBroker/Startup.cs @@ -1,4 +1,7 @@ -using Autofac; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Autofac; using Common.Log; using Lykke.SettingsReader; using MarginTrading.BrokerBase; diff --git a/src/MarginTrading.Client/Bot/BotClient.cs b/src/MarginTrading.Client/Bot/BotClient.cs deleted file mode 100644 index 4ac543c11..000000000 --- a/src/MarginTrading.Client/Bot/BotClient.cs +++ /dev/null @@ -1,608 +0,0 @@ -using Flurl.Http; -using MarginTrading.Client.JsonResults; -using MarginTrading.Client.Settings; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Common; -using MarginTrading.Client.EventArgs; -using MarginTrading.Client.Wamp; -using MarginTrading.Contract.BackendContracts; -using MarginTrading.Contract.ClientContracts; -using WampSharp.V2; -using WampSharp.V2.Client; - -namespace MarginTrading.Client.Bot -{ - class BotClient : IDisposable - { - public event EventHandler LogEvent; - - #region vars - private readonly TestBotUserSettings _settings; - - private Timer _transactionTimer; - private Timer _connectTimer; - private bool _isDisposing; - private string _authorizationAddress; - private IDisposable _notificationSubscription; - private readonly Random _random; - private string _serverAddress; - private IWampChannel _channel; - private IWampRealmProxy _realmProxy; - private string _token; - private IRpcMtFrontend _service; - private string _notificationId; - - private readonly Dictionary _priceSubscription; - private readonly Dictionary _subscriptionHistory; - - #endregion - - #region Properties - public int Id => _settings.Number; - public string Email => _settings.Email; - public string Password => _settings.Password; - - public int ActionScriptInterval { get; private set; } - public int TransactionFrequencyMin { get; private set; } - public int TransactionFrequencyMax { get; private set; } - public bool Initialized { get; private set; } - #endregion - - public BotClient(TestBotUserSettings settings) - { - _settings = settings; - Initialized = false; - _priceSubscription = new Dictionary(); - _subscriptionHistory = new Dictionary(); - _random = new Random(); - } - - #region Methods - private void Connect() - { - var factory = new DefaultWampChannelFactory(); - _channel = factory.CreateJsonChannel(_serverAddress, "mtcrossbar"); - var tries = 0; - while (!_channel.RealmProxy.Monitor.IsConnected) - { - try - { - tries++; - LogInfo($"Trying to connect to server {_serverAddress }..."); - _channel.Open().Wait(); - } - catch (Exception ex) - { - LogError(ex); - if (tries > 5) - throw; - LogInfo("Retrying in 5 sec..."); - - } - } - LogInfo($"Connected to server {_serverAddress}"); - - _realmProxy = _channel.RealmProxy; - _service = _realmProxy.Services.GetCalleeProxy(); - - // Subscribe Notifications - _notificationSubscription = _realmProxy.Services.GetSubject($"user.updates.{_notificationId}").Subscribe(NotificationReceived); - } - private void Disconnect() - { - LogInfo($"Disconnecting from server {_serverAddress}"); - _channel.Close(); - } - public async Task Initialize(string serverAddress, string authorizationAddress, int actionScriptInterval, int transactionFrequencyMin, int transactionFrequencyMax) - { - - LogInfo($"Initializing bot {_settings.Number} ({_settings.Email}). AquireTokenData..."); - _serverAddress = serverAddress; - _authorizationAddress = authorizationAddress; - ActionScriptInterval = actionScriptInterval; - TransactionFrequencyMin = transactionFrequencyMin; - TransactionFrequencyMax = transactionFrequencyMax; - - try - { - var res = await AquireTokenData(); - _token = res.token; - _notificationId = res.notificationsId; - Initialized = true; - } - catch (Exception ex) - { - LogError(ex); - throw; - } - - Connect(); - } - public void Reconnect() - { - Disconnect(); - Thread.Sleep(2000); - Connect(); - } - - public void IsAlive() - { - try - { - var data = _service.IsAlive(); - LogInfo($"IsAlive:{data.ToJson()}"); - } - catch (Exception ex) - { - LogError(ex); - } - } - public async Task InitData() - { - try - { - var res = new OperationResult - { - Operation = "InitData", - StartDate = DateTime.UtcNow - }; - var initData = await _service.InitData(_token); - res.EndDate = DateTime.UtcNow; - res.Result = initData; - LogInfo($";{res.Duration};InitData: Assets={initData.Assets.Length} Prices={initData.Prices.Count}"); - - return res; - } - catch (Exception ex) - { - LogError(ex); - return null; - } - } - public async Task InitAccounts() - { - try - { - var res = new OperationResult - { - Operation = "InitAccounts", - StartDate = DateTime.UtcNow - }; - var initAccounts = await _service.InitAccounts(_token); - res.EndDate = DateTime.UtcNow; - res.Result = initAccounts; - LogInfo($";{res.Duration};InitAccounts: Demo={initAccounts.Demo.Length} Live={initAccounts.Live.Length}"); - return res; - } - catch (Exception ex) - { - LogError(ex); - return null; - } - } - - public async Task InitGraph() - { - try - { - var res = new OperationResult - { - Operation = "InitGraph", - StartDate = DateTime.UtcNow - }; - var chartData = await _service.InitGraph(); - res.EndDate = DateTime.UtcNow; - res.Result = chartData; - LogInfo($";{res.Duration};InitGraph: ChartData={chartData.ChartData.Count}"); - return res; - } - catch (Exception ex) - { - LogError(ex); - return null; - } - - } - public async Task GetAccountHistory() - { - try - { - var res = new OperationResult - { - Operation = "GetAccountHistory", - StartDate = DateTime.UtcNow - }; - - var request = new AccountHistoryRpcClientRequest - { - Token = _token - }; - var result = await _service.GetAccountHistory(request.ToJson()); - res.EndDate = DateTime.UtcNow; - res.Result = result; - LogInfo($";{res.Duration};GetAccountHistory: Accounts={result.Account.Length}, OpenPositions={result.OpenPositions.Length}, PositionsHistory={result.PositionsHistory.Length}"); - return res; - } - catch (Exception ex) - { - LogError(ex); - return null; - } - } - - public async Task GetHistory() - { - var res = new OperationResult - { - Operation = "GetHistory", - StartDate = DateTime.UtcNow - }; - - var request = new AccountHistoryRpcClientRequest - { - Token = _token - }; - var result = await _service.GetHistory(request.ToJson()); - res.EndDate = DateTime.UtcNow; - res.Result = result; - LogInfo($";{res.Duration};GetHistory: Items={result.Length}"); - return res; - } - public async Task GetOpenPositionsFromDemo() - { - var res = new OperationResult - { - Operation = "GetOpenPositions", - StartDate = DateTime.UtcNow - }; - - var result = await _service.GetOpenPositions(_token); - res.EndDate = DateTime.UtcNow; - res.Result = result.Demo; - LogInfo($";{res.Duration};GetOpenPositionsFromDemo: Items={result.Demo.Length}"); - return res; - } - public async Task GetAccountOpenPositions(string accountId) - { - var res = new OperationResult - { - Operation = "GetAccountOpenPositions", - StartDate = DateTime.UtcNow - }; - var request = new AccountTokenClientRequest - { - Token = _token, - AccountId = accountId - }; - var result = await _service.GetAccountOpenPositions(request.ToJson()); - res.EndDate = DateTime.UtcNow; - res.Result = result; - LogInfo($";{res.Duration};GetAccountOpenPositions: OpenPositions={result.Length}"); - return res; - } - public async Task GetClientOrders() - { - var res = new OperationResult - { - Operation = "GetClientOrders", - StartDate = DateTime.UtcNow - }; - - var result = await _service.GetClientOrders(_token); - res.EndDate = DateTime.UtcNow; - res.Result = result; - LogInfo($";{res.Duration};GetClientOrders: Orders={result.Demo.Orders.Length}, Positions={result.Demo.Positions.Length}"); - return res; - } - - public async Task> PlaceOrders(string accountId, string instrument, int numOrders) - { - var operations = new List(); - for (var i = 0; i < numOrders; i++) - { - try - { - var request = new OpenOrderRpcClientRequest - { - Token = _token, - Order = new NewOrderClientContract - { - AccountId = accountId, - FillType = OrderFillTypeContract.FillOrKill, - Instrument = instrument, - Volume = 1 - } - }; - LogInfo($"Placing order {i+1}/{numOrders}: [{instrument}]"); - - var res = new OperationResult - { - Operation = "PlaceOrders", - StartDate = DateTime.UtcNow - }; - var order = await _service.PlaceOrder(request.ToJson()); - res.EndDate = DateTime.UtcNow; - res.Result = order.Result; - operations.Add(res); - - LogInfo(order.Result.Status == 3 - ? $";{res.Duration};Order rejected: {order.Result.RejectReason} -> {order.Result.RejectReasonText}" - : $";{res.Duration};Order placed: {order.Result.Id} -> Status={order.Result.Status}"); - } - catch (Exception ex) - { - LogError(ex); - } - // Sleep TransactionFrequency - Thread.Sleep(GetRandomTransactionInterval()); - } - return operations; - } - public async Task> PlacePendingOrders(string accountId, string instrument, int numOrders, decimal currentBid) - { - - var operations = new List(); - for (var i = 0; i < numOrders; i++) - { - try - { - var request = new OpenOrderRpcClientRequest - { - Token = _token, - Order = new NewOrderClientContract - { - AccountId = accountId, - FillType = OrderFillTypeContract.FillOrKill, - Instrument = instrument, - Volume = 1, - ExpectedOpenPrice = currentBid * 0.9m - } - }; - - LogInfo($"Placing order {i + 1}/{numOrders}: [{instrument}]"); - var res = new OperationResult - { - Operation = "PlacePendingOrders", - StartDate = DateTime.UtcNow - }; - var order = await _service.PlaceOrder(request.ToJson()); - res.EndDate = DateTime.UtcNow; - res.Result = order.Result; - operations.Add(res); - - LogInfo(order.Result.Status == 3 - ? $";{res.Duration};Order rejected: {order.Result.RejectReason} -> {order.Result.RejectReasonText}" - : $";{res.Duration};Order placed: {order.Result.Id} -> Status={order.Result.Status}"); - } - catch (Exception ex) - { - LogError(ex); - } - // Sleep TransactionFrequency - Thread.Sleep(GetRandomTransactionInterval()); - } - return operations; - } - public async Task> CloseOrders(string accountId, string instrument, int numOrders) - { - var operations = new List(); - var resGetOpenPositions = await GetOpenPositionsFromDemo(); - operations.Add(resGetOpenPositions); - - var orders = ((OrderClientContract[])resGetOpenPositions.Result) - .Where(x => x.AccountId == accountId && x.Instrument == instrument).ToList(); - var processed = 0; - foreach (var order in orders) - { - if (processed >= numOrders) - break; - try - { - LogInfo($"Closing order {processed + 1}/{numOrders}: [{instrument}] Id={order.Id} Fpl={order.Fpl}"); - var res = new OperationResult - { - Operation = "CloseOrders", - StartDate = DateTime.UtcNow - }; - var request = new CloseOrderRpcClientRequest - { - OrderId = order.Id, - AccountId = order.AccountId, - Token = _token - }; - var orderClosed = await _service.CloseOrder(request.ToJson()); - res.EndDate = DateTime.UtcNow; - res.Result = orderClosed; - operations.Add(res); - - LogInfo(orderClosed.Result - ? $";{res.Duration};Order Closed Id={order.Id}" - : $";{res.Duration};Order Close Failed Id={order.Id} Message:{orderClosed.Message}"); - } - catch (Exception ex) - { - LogError(ex); - } - processed++; - // Sleep TransactionFrequency - Thread.Sleep(GetRandomTransactionInterval()); - } - - if (processed < numOrders) - LogWarning($"Not enough orders to close requested amount {numOrders}"); - - return operations; - } - public async Task> CancelOrders(string accountId, string instrument, int numOrders) - { - var operations = new List(); - var resGetOpenPositions = await GetOpenPositionsFromDemo(); - operations.Add(resGetOpenPositions); - - var orders = ((OrderClientContract[])resGetOpenPositions.Result) - .Where(x => x.AccountId == accountId && x.Instrument == instrument && x.Status==0).ToList(); - var processed = 0; - foreach (var order in orders) - { - if (processed >= numOrders) - break; - try - { - LogInfo($"Canceling order {processed + 1}/{numOrders}: [{instrument}] Id={order.Id} Fpl={order.Fpl}"); - var res = new OperationResult - { - Operation = "CancelOrders", - StartDate = DateTime.UtcNow - }; - var request = new CloseOrderRpcClientRequest - { - OrderId = order.Id, - AccountId = order.AccountId, - Token = _token - }; - var ordercanceled = await _service.CancelOrder(request.ToJson()); - res.EndDate = DateTime.UtcNow; - res.Result = ordercanceled; - operations.Add(res); - - LogInfo(ordercanceled.Result - ? $";{res.Duration};Order Canceled Id={order.Id}" - : $";{res.Duration};Order Cancel Failed Id={order.Id} Message:{ordercanceled.Message}"); - } - catch (Exception ex) - { - LogError(ex); - } - processed++; - // Sleep TransactionFrequency - Thread.Sleep(GetRandomTransactionInterval()); - } - - if (processed < numOrders) - LogWarning($"Not enough orders to close requested amount {numOrders}"); - - return operations; - } - - public void SubscribePrice(string instrument) - { - var topicName = !string.IsNullOrEmpty(instrument) ? $"prices.update.{instrument}" : "prices.update"; - var subscription = _realmProxy.Services.GetSubject(topicName) - .Subscribe(PriceReceived); - - _priceSubscription.Add(instrument, subscription); - LogInfo($"SubscribePrice: Instrument={instrument}"); - - //subscription.Dispose(); - } - public void UnsubscribePrice(string instrument) - { - var subscription = _priceSubscription[instrument]; - subscription?.Dispose(); - - if (_subscriptionHistory.ContainsKey(instrument)) - { - var received = _subscriptionHistory[instrument]; - LogInfo($"UnsubscribePrice: Instrument={instrument}. Entries received:{received}"); - _subscriptionHistory.Remove(instrument); - } - } - - private async Task<(string token, string notificationsId)> AquireTokenData() - { - var address = $"{_authorizationAddress}/Auth"; - var result = await address.PostJsonAsync(new - { - _settings.Email, - _settings.Password - }) - .ReceiveJson(); - if (result.Error != null) - throw new Exception(result.Error.Message); - - return (token: result.Result.Token, notificationsId: result.Result.NotificationsId); - } - - private int GetRandomTransactionInterval() - { - return _random.Next(TransactionFrequencyMin, TransactionFrequencyMax); - } - - public void Dispose() - { - if (_isDisposing) - return; - _isDisposing = true; - - _notificationSubscription?.Dispose(); - _channel?.Close(); - - if (_transactionTimer != null) - { - _transactionTimer.Dispose(); - _transactionTimer = null; - } - - if (_connectTimer != null) - { - _connectTimer.Dispose(); - _connectTimer = null; - } - } - - - #region CallBacks - private void PriceReceived(InstrumentBidAskPairContract price) - { - if (!_subscriptionHistory.ContainsKey(price.Id)) - _subscriptionHistory.Add(price.Id, 0); - - var received = _subscriptionHistory[price.Id]; - _subscriptionHistory[price.Id] = received + 1; - - //LogInfo($"Price received:{price.Instrument} Ask/Bid:{price.Ask}/{price.Bid}"); - } - private void NotificationReceived(NotifyResponse info) - { - if (info.Account != null) - LogInfo($"Notification received: Account changed={info.Account.Id} Balance:{info.Account.Balance}"); - - if (info.Order != null) - LogInfo($"Notification received: Order changed={info.Order.Id} Open:{info.Order.OpenDate} Close:{info.Order.CloseDate} Fpl:{info.Order.Fpl}"); - - if (info.AccountStopout != null) - LogInfo($"Notification received: Account stopout={info.AccountStopout.AccountId}"); - - if (info.UserUpdate != null) - LogInfo($"Notification received: User update={info.UserUpdate.UpdateAccountAssetPairs}, accounts = {info.UserUpdate.UpdateAccounts}"); - - } - #endregion - - #region Logging - private void LogInfo(string message) - { - OnLog(new LogEventArgs(DateTime.UtcNow, $"Bot:[{_settings.Number}]", "info", $"Thread[{ Thread.CurrentThread.ManagedThreadId.ToString() }] {message}", null)); - } - private void LogWarning(string message) - { - OnLog(new LogEventArgs(DateTime.UtcNow, $"Bot:[{_settings.Number}]", "warning", $"Thread[{ Thread.CurrentThread.ManagedThreadId.ToString() }] {message}", null)); - } - private void LogError(Exception error) - { - OnLog(new LogEventArgs(DateTime.UtcNow, $"Bot:[{_settings.Number}]", "error", $"Thread[{ Thread.CurrentThread.ManagedThreadId.ToString() }] {error.Message}", error)); - } - private void OnLog(LogEventArgs e) - { - LogEvent?.Invoke(this, e); - } - #endregion - - #endregion - } - -} diff --git a/src/MarginTrading.Client/Bot/BotConsole.cs b/src/MarginTrading.Client/Bot/BotConsole.cs deleted file mode 100644 index 67106d3c2..000000000 --- a/src/MarginTrading.Client/Bot/BotConsole.cs +++ /dev/null @@ -1,353 +0,0 @@ -using Flurl.Http; -using MarginTrading.Client.Settings; -using Microsoft.Extensions.Configuration; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.Client.EventArgs; - -namespace MarginTrading.Client.Bot -{ - static class BotConsole - { - #region Vars - private static BotHost _botHost; - private static string _sessionLogFile; - private static bool _isAutoRun; - private static MtTradingBotSettings _settings; - private static MtTradingBotTestSettings _testSettings; - - private static readonly object LogLock = new object(); - private static Queue _logQueue; - - private static bool _isRunningTests; - #endregion - - private static void LoadSettings(string testScriptFile) - { - var config = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.dev.json", true, true) - .AddEnvironmentVariables() - .Build(); - - var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); - // If it's Development environment load local settings file, else load settings from Lykke.Settings - var mtSettings = (env == "Development") ? - config.Get() : - Lykke.SettingsReader.SettingsProcessor.Process(config["SettingsUrl"].GetStringAsync().Result); - - _settings = mtSettings.MtTradingBot; - - string script; - if (!string.IsNullOrEmpty(_settings.TestFile)) - script = _settings.TestFile; - else if (!string.IsNullOrEmpty(testScriptFile)) - script = testScriptFile; - else - throw new Exception("Invalid configuration file"); - - var testFile = new FileInfo(Path.Combine(Directory.GetCurrentDirectory(), script)); - if (!testFile.Exists) - throw new FileNotFoundException($"Script file not found: {testFile.Name}"); - - config = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.dev.json", true, true) - .AddJsonFile(script, true, true) - .AddEnvironmentVariables() - .Build(); - - _testSettings = config.Get(); - - if (string.IsNullOrEmpty(_settings?.MTAuthorizationAddress) || string.IsNullOrEmpty(_settings.MTServerAddress)) - throw new Exception("Invalid configuration file"); - LogInfo("LoadSettings", $"Configuration Loaded: {script}"); - - } - internal static void StartBotHost(string configFile, bool autorun) - { - _isAutoRun = autorun; - _sessionLogFile = Path.Combine(Path.GetTempPath(), "MTLOG_"+ DateTime.UtcNow.ToString("yyyyMMdd_HHmm") + ".log"); - - LoadSettings(configFile); - if (!string.IsNullOrEmpty(_settings.TestFile)) - _isAutoRun = true; - - _botHost = new BotHost(); - _botHost.LogEvent += Bot_LogEvent; - _botHost.TestFinished += BotHost_TestFinished; - _botHost.Start(_settings, _testSettings); - - LogInfo("BotConsole.StartBot", $"Log session file: {_sessionLogFile}"); - if (_isAutoRun) - Run(); - string input; - do - { - input = Console.ReadLine(); - switch (input) - { - case "exit": - break; - case "bots": - ShowBots(); - break; - case "help": - ShowHelp(); - break; - case "isalive": - IsAlive(); - break; - case "initdata": - InitData(); - break; - case "initaccounts": - InitAccounts(); - break; - case "run": - Run(); - break; - case "appinfo": - AppInfo(); - break; - case "verifyemail": - VerifyEmail(); - break; - case "register": - RegisterUser(); - break; - } - - } while (input != "exit"); - _botHost.Stop(); - _botHost.LogEvent -= Bot_LogEvent; - _botHost.TestFinished -= BotHost_TestFinished; - FlushLog(); - } - private static string RequestBot() - { - Console.Write("\tSelect bot [(#)bot number / (a)all]: "); - var input = Console.ReadLine(); - if (input == "all" || input == "a") - return "all"; - - if (int.TryParse(input, out var botId)) - { - - var bot = _botHost.Bots.FirstOrDefault(x => x.Id == botId); - if (bot == null) - return null; - else - return input; - - - } - else - return null; - } - - #region Console Commands - private static void Run() - { - if (_isRunningTests) - LogInfo("Run", "Test is already running. Cannot run again"); - _isRunningTests = true; - _botHost.RunScript(); - } - - private static async void AppInfo() - { - try - { - await MtUserHelper.ApplicationInfo(_settings.MTAuthorizationAddress); - } - catch (Exception ex) - { - LogError("MtUserHelper.EmailVerification", ex); - } - } - private static async void VerifyEmail() - { - try - { - await MtUserHelper.EmailVerification("nem@dev.com", _settings.MTAuthorizationAddress); - } - catch (Exception ex) - { - LogError("MtUserHelper.EmailVerification", ex); - } - } - private static async void RegisterUser() - { - Console.Write("Email: "); - var email = Console.ReadLine(); - try - { - await MtUserHelper.EmailVerification(email, _settings.MTAuthorizationAddress); - } - catch (Exception ex) - { - LogError("MtUserHelper.EmailVerification", ex); - return; - } - - Console.Write("Password: "); - var pass = Console.ReadLine(); - - await MtUserHelper.Registration(email, pass, _settings.MTAuthorizationAddress); - } - private static void ShowBots() - { - Console.WriteLine(" ===== Bots ===== "); - foreach (var bot in _botHost.Bots) - { - Console.WriteLine(" Bot Id: {0} > {1}", bot.Id, bot.Email); - } - Console.WriteLine(" ===== ==== ===== "); - } - private static void ShowHelp() - { - Console.WriteLine(" ===== HELP ===== "); - Console.WriteLine(" bots - Show active bots "); - Console.WriteLine(" isalive - Perform IsAlive call for 1 or all bots "); - Console.WriteLine(" initdata - Perform InitData call for 1 or all bots "); - Console.WriteLine(" initaccounts - Perform InitAccounts call for 1 or all bots "); - Console.WriteLine(" run - Run actions script "); - Console.WriteLine(" exit - Stops bot application "); - Console.WriteLine(" ===== ==== ===== "); - } - private static void IsAlive() - { - var botid = RequestBot(); - if (botid == null) - Console.WriteLine("Invalid bot id"); - else if (botid == "all") - { - foreach (var bot in _botHost.Bots) - { - Task.Run(() => bot.IsAlive()); - } - } - else - { - var bot = _botHost.Bots.FirstOrDefault(x => x.Id.ToString() == botid); - bot?.IsAlive(); - } - } - private static void InitData() - { - var botid = RequestBot(); - switch (botid) - { - case null: - Console.WriteLine("Invalid bot id"); - break; - case "all": - foreach (var b in _botHost.Bots) - { - Task.Run(() => b.InitData()); - } - break; - default: - var bot = _botHost.Bots.FirstOrDefault(x => x.Id.ToString() == botid); - bot?.InitData(); - break; - } - } - private static void InitAccounts() - { - var botid = RequestBot(); - if (botid == null) - Console.WriteLine("Invalid bot id"); - else if (botid == "all") - { - foreach (var bot in _botHost.Bots) - { - Task.Run(() => bot.InitAccounts()); - } - } - else - { - var bot = _botHost.Bots.FirstOrDefault(x => x.Id.ToString() == botid); - bot?.InitAccounts(); - } - } - #endregion - - #region Logging - private static void LogInfo(string origin, string message) - { - Log(new LogEventArgs(DateTime.UtcNow, origin, "info", message, null)); - } - - private static void LogError(string origin, Exception error) - { - Log(new LogEventArgs(DateTime.UtcNow, origin, "error", error.Message, error)); - } - - private static void Log(LogEventArgs e) - { - LogEventArgs[] currentLogBuffer = null; - - lock (LogLock) - { - if (_logQueue == null) - _logQueue = new Queue(); - - _logQueue.Enqueue(e); - if (_logQueue.Count >= 64) - { - currentLogBuffer = _logQueue.ToArray(); - _logQueue.Clear(); - } - } - - var msg = $"{e.Date:HH:mm:ss.fff};{e.Origin};{e.Type};{e.Message}"; - if (e.Exception != null) - msg += $"\n\r{e.Exception.GetBaseException().Message}\n\rSTACK:[{e.Exception.GetBaseException().StackTrace}]"; - Console.WriteLine(msg); - - if (currentLogBuffer != null) - FlushLog(currentLogBuffer); - } - private static void FlushLog() - { - LogEventArgs[] buffer; - lock (LogLock) - { - buffer = _logQueue.ToArray(); - _logQueue.Clear(); - } - FlushLog(buffer); - } - private static void FlushLog(LogEventArgs[] logBuffer) - { - using (var fs = new FileStream(_sessionLogFile, FileMode.Append, FileAccess.Write)) - using (var sw = new StreamWriter(fs)) - { - foreach (var logitem in logBuffer) - { - sw.WriteLine("{0};{1};{2};{3}", logitem.Date.ToString("HH:mm:ss.fff"), logitem.Origin, logitem.Type, logitem.Message); - if (logitem.Exception != null) - sw.WriteLine($"{logitem.Exception.GetBaseException().Message}\n\rSTACK:[{logitem.Exception.GetBaseException().StackTrace}]"); - } - } - } - #endregion - - #region Event Handlers - private static void BotHost_TestFinished(object sender, System.EventArgs e) - { - FlushLog(); - _isRunningTests = false; - } - private static void Bot_LogEvent(object sender, LogEventArgs e) - { - Log(e); - } - #endregion - } -} diff --git a/src/MarginTrading.Client/Bot/BotHost.cs b/src/MarginTrading.Client/Bot/BotHost.cs deleted file mode 100644 index dbc527862..000000000 --- a/src/MarginTrading.Client/Bot/BotHost.cs +++ /dev/null @@ -1,253 +0,0 @@ -using MarginTrading.Client.Settings; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.Client.EventArgs; - -namespace MarginTrading.Client.Bot -{ - class BotHost - { - public event EventHandler LogEvent; - public event EventHandler TestFinished; - - #region vars - private MtTradingBotSettings _settings; - private MtTradingBotTestSettings _testSettings; - - private List _runningTests; - private List _finishedTests; - #endregion - - #region Properties - public List Bots { get; private set; } - #endregion - - #region Methods - public void Start(MtTradingBotSettings settings, MtTradingBotTestSettings testSettings) - { - _settings = settings; - _testSettings = testSettings; - - // Create Bots - try { CreateBots(); } - catch (Exception ex01) - { - LogError("CreateEnvironment", ex01); - throw; - } - - // Initialize Bots - Task.Run(async () => await StartBots()) - .Wait(); - } - public void Stop() - { - if (Bots != null && Bots.Count > 0) - { - foreach (var bot in Bots) - { - bot.Dispose(); - } - } - } - - private void CreateBots() - { - - Bots = new List(); - - if (string.IsNullOrEmpty(_settings.UsersFile)) - { - if (_settings.NumberOfUsers > _testSettings.Users.Length) - throw new IndexOutOfRangeException("User array does not have enough users for requested NumberOfUsers "); - - // Load Users Json Array - var userSettings = _testSettings.Users.OrderBy(x => x.Number).ToArray(); - for (var i = 0; i < _settings.NumberOfUsers; i++) - { - var bot = new BotClient(userSettings[i]); - bot.LogEvent += Bot_LogEvent; - Bots.Add(bot); - } - } - else - { - // Load Users from CSV File - var file = new FileInfo(_settings.UsersFile); - if (!file.Exists) - throw new FileNotFoundException(file.FullName); - - using (var fs = new FileStream(file.FullName, FileMode.Open, FileAccess.Read)) - using (var sr = new StreamReader(fs)) - { - //Read Header - sr.ReadLine(); - var ctr = 0; - do - { - if (ctr >= _settings.NumberOfUsers) - break; - // read csv line - var line = sr.ReadLine(); - var values = line.Split(';'); - var email = values[0]; - var pass = values[1]; - - var bot = new BotClient(new TestBotUserSettings() { Number = ++ctr, Email = email, Password = pass }); - bot.LogEvent += Bot_LogEvent; - Bots.Add(bot); - - } while (!sr.EndOfStream); - } - } - } - private async Task StartBots() - { - foreach (var bot in Bots) - { - var tryCount = 0; - while (true) - { - if (tryCount >= 3) - break; - tryCount++; - - var createUser = false; - try - { - await bot.Initialize(_settings.MTServerAddress, _settings.MTAuthorizationAddress, _settings.ActionScriptInterval, _settings.TransactionFrequencyMin, _settings.TransactionFrequencyMax); - break; - } - catch (Exception ex) - { - if (ex.Message == "Invalid username or password") - createUser = true; - } - if (createUser) - { - LogInfo("StartBots", $"Creating user: {bot.Email}"); - try - { - await MtUserHelper.Registration(bot.Email, bot.Password, _settings.MTAuthorizationAddress); - LogInfo("StartBots", $"User {bot.Email} created successfully"); - System.Threading.Thread.Sleep(1000); - } - catch (Exception ex01) - { - LogError("MtUserHelper.Registration", ex01); - break; - } - } - } - - } - } - - public void RunScript() - { - _runningTests = new List(); - _finishedTests = new List(); - foreach (var bot in Bots) - { - if (bot.Initialized) - { - var test = new BotTest(bot, _testSettings.Actions); - test.LogEvent += Test_LogEvent; - test.TestFinished += Test_TestFinished; - _runningTests.Add(test); - LogInfo($"Bot:[{bot.Id}]", $"Starting test for Bot: {bot.Email}"); - test.RunScriptAsync(); - } - else - { - LogWarning($"Bot:[{bot.Id}]", $"Bot initialization failed: {bot.Email}. Not running tests"); - } - } - } - - private void PrintSummary() - { - LogInfo("BotHost", ";BOT;OPERATION;COUNT;AVERAGE"); - - var totalOperations = new List(); - foreach (var item in _finishedTests) - { - totalOperations.AddRange(item.Operations); - var grouped = item.Operations.GroupBy(x => x.Operation); - foreach (var group in grouped) - { - var line = $";{item.Bot.Id};{group.Key};{group.Count()};{group.Average(x => x.Duration.TotalSeconds)}"; - LogInfo("BotHost", line); - } - } - - LogInfo("BotHost", " === Total Averages === "); - LogInfo("BotHost", "OPERATION;COUNT;AVERAGE"); - var totalgrouped = totalOperations.GroupBy(x => x.Operation); - foreach (var group in totalgrouped) - { - var line = $";{group.Key};{group.Count()};{group.Average(x => x.Duration.TotalSeconds)}"; - LogInfo("BotHost", line); - } - LogInfo("BotHost", " === Total Averages === "); - } - - private void LogInfo(string origin, string message) - { - OnLog(this, new LogEventArgs(DateTime.UtcNow, origin, "info", message, null)); - } - private void LogWarning(string origin, string message) - { - OnLog(this, new LogEventArgs(DateTime.UtcNow, origin, "warning", message, null)); - } - private void LogError(string origin, Exception error) - { - OnLog(this, new LogEventArgs(DateTime.UtcNow, origin, "error", error.Message, error)); - } - #endregion - - #region Event Raising - private void OnTestFinished(object sender, System.EventArgs e) - { - TestFinished?.Invoke(sender, e); - } - private void OnLog(object sender, LogEventArgs e) - { - LogEvent?.Invoke(sender, e); - } - #endregion - - #region Event Handlers - private void Test_LogEvent(object sender, LogEventArgs e) - { - OnLog(sender, e); - } - private void Bot_LogEvent(object sender, LogEventArgs e) - { - OnLog(sender, e); - } - private void Test_TestFinished(object sender, System.EventArgs e) - { - var sdr = sender as BotTest; - LogInfo($"Bot:[{sdr?.Bot.Id}]", $"Test Finished! Bot: {sdr?.Bot.Email}"); - _runningTests.Remove(sdr); - _finishedTests.Add(sdr); - if (_runningTests.Count == 0) - { - PrintSummary(); - OnTestFinished(this, new System.EventArgs()); - } - } - #endregion - - } - - - - - - -} diff --git a/src/MarginTrading.Client/Bot/BotTest.cs b/src/MarginTrading.Client/Bot/BotTest.cs deleted file mode 100644 index 3f70ecd63..000000000 --- a/src/MarginTrading.Client/Bot/BotTest.cs +++ /dev/null @@ -1,310 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MarginTrading.Client.EventArgs; -using MarginTrading.Contract.ClientContracts; - -#pragma warning disable 4014 - -namespace MarginTrading.Client.Bot -{ - class BotTest - { - public event EventHandler LogEvent; - public event EventHandler TestFinished; - - readonly List _operations; - private string[] _processedScript; - - int _currentAction; - readonly Timer _actionTimer; - - private InitDataLiveDemoClientResponse _initData; - private InitChartDataClientResponse _initGraph; - - public BotClient Bot { get; } - public string[] Actions { get; } - public bool IsFinished{ get; private set; } - - public List Operations => _operations; - - public BotTest(BotClient bot, string[] actions) - { - IsFinished = false; - Bot = bot; - Actions = actions; - _actionTimer = new Timer(NextActionTimerCall, null, -1, -1); - _operations = new List(); - } - - public void RunScriptAsync() - { - var t = new Thread(RunScript); - t.Start(); - } - private void RunScript() - { - _currentAction = 0; - if (Actions == null || Actions.Length < 1) - throw new ArgumentException("Actions"); - - // Pre-process script - var pscript = new List(); - for (var i = 0; i < Actions.Length; i++) - { - if (Actions[i].ToLower().StartsWith("repeat")) - { - //do repeat stuff - if (Actions[i].Split(' ').Length <= 1) - continue; - - var rcount = Actions[i].Split(' ')[1]; - if (rcount.ToLower() == "all") - { - var repeated = new List(); - for (var j = 0; j < i; j++) - { - repeated.Add(Actions[j]); - } - - var repeatAllCount = 1; - if (Actions[i].Split(' ').Length > 2) - int.TryParse(Actions[i].Split(' ')[2], out repeatAllCount); - - for (var k = 0; k < repeatAllCount; k++) - { - pscript.AddRange(repeated); - } - } - else - { - int.TryParse(rcount, out var repeatCount); - var pos = -repeatCount; - var repeatActions = new List(); - while (pos < 0) - { - repeatActions.Add(Actions[i + pos]); - pos++; - } - pscript.AddRange(repeatActions); - } - } - else - pscript.Add(Actions[i]); - } - _processedScript = pscript.ToArray(); - - Execute(_processedScript[_currentAction]); - } - - private async Task Execute(string action, bool restartTimer = true) - { - // do action - LogInfo($"Action: {action}"); - - var command = action.Split(' ')[0].ToLower(); - - switch (command) - { - case "initdata": - var resinitdata = await Bot.InitData(); - _initData = (InitDataLiveDemoClientResponse)resinitdata.Result; - _operations.Add(resinitdata); - break; - case "initaccounts": - var resinitaccounts = await Bot.InitAccounts(); - _operations.Add(resinitaccounts); - break; - case "initgraph": - var resinitGraph = await Bot.InitGraph(); - _initGraph = (InitChartDataClientResponse)resinitGraph.Result; - _operations.Add(resinitGraph); - foreach (var graphRow in _initGraph.ChartData) - { - LogInfo($"ChartRow: {graphRow.Key}:{graphRow.Value}"); - } - - break; - case "subscribe": - var subscribeInstrument = action.Split(' ')[1].ToUpper(); - Bot.SubscribePrice(subscribeInstrument); - break; - case "unsubscribe": - var unsubscribeInstrument = action.Split(' ')[1].ToUpper(); - Bot.UnsubscribePrice(unsubscribeInstrument); - break; - case "placeorder": - #region placeorder - if (_initData == null) - { - LogInfo("PlaceOrder Failed. InitData not performed, please call InitData before placing orders"); - } - else - { - var placeOrderInstrument = action.Split(' ')[1].ToUpper(); - int placeOrderCount; - if (action.Split(' ').Length > 2) - { - var orderCount = action.Split(' ')[2].ToUpper(); - if (!int.TryParse(orderCount, out placeOrderCount)) - placeOrderCount = 1; - } - else - placeOrderCount = 1; - - var result = await Bot.PlaceOrders(_initData.Demo.Accounts[0].Id, placeOrderInstrument, placeOrderCount); - _operations.AddRange(result); - } - #endregion - break; - case "closeorder": - #region closeorder - if (_initData == null) - { - LogInfo("CloseOrder Failed. InitData not performed, please call InitData before closing orders"); - } - else - { - var closeOrderInstrument = action.Split(' ')[1].ToUpper(); - int closeOrderCount; - if (action.Split(' ').Length > 2) - { - var orderCount = action.Split(' ')[2].ToUpper(); - if (!int.TryParse(orderCount, out closeOrderCount)) - closeOrderCount = 1; - } - else - closeOrderCount = 1; - - var result = await Bot.CloseOrders(_initData.Demo.Accounts[0].Id, closeOrderInstrument, closeOrderCount); - _operations.AddRange(result); - } - #endregion - break; - case "cancelorder": - #region cancelorder - if (_initData == null) - { - LogInfo("CancelOrder Failed. InitData not performed, please call InitData before canceling orders"); - } - else - { - var cancelOrderInstrument = action.Split(' ')[1].ToUpper(); - int cancelOrderCount; - if (action.Split(' ').Length > 2) - { - var orderCount = action.Split(' ')[2].ToUpper(); - if (!int.TryParse(orderCount, out cancelOrderCount)) - cancelOrderCount = 1; - } - else - cancelOrderCount = 1; - - var result = await Bot.CancelOrders(_initData.Demo.Accounts[0].Id, cancelOrderInstrument, cancelOrderCount); - _operations.AddRange(result); - } - #endregion - break; - case "gethistory": - var resgethistory = await Bot.GetHistory(); - _operations.Add(resgethistory); - break; - case "getaccounthistory": - var resgetaccounthistory = await Bot.GetAccountHistory(); - _operations.Add(resgetaccounthistory); - break; - case "getaccountopenpositions": - if (_initData == null) - { - LogInfo("GetAccountOpenPositions Failed. InitData not performed, please call InitData before placing orders"); - } - else - { - var result = await Bot.GetAccountOpenPositions(_initData.Demo.Accounts[0].Id); - _operations.Add(result); - } - break; - case "getclientorders": - var getClientOrdersResult = await Bot.GetClientOrders(); - _operations.Add(getClientOrdersResult); - break; - case "reconnect": - Bot.Reconnect(); - LogInfo("Reconnected..."); - break; - case "placependingorder": - #region placependingorder - if (_initData == null) - { - LogInfo("PlaceOrder Failed. InitData not performed, please call InitData before placing orders"); - } - else - { - var placeOrderInstrument = action.Split(' ')[1].ToUpper(); - int placeOrderCount; - if (action.Split(' ').Length > 2) - { - var orderCount = action.Split(' ')[2].ToUpper(); - if (!int.TryParse(orderCount, out placeOrderCount)) - placeOrderCount = 1; - } - else - placeOrderCount = 1; - var currentBid = _initData.Prices[placeOrderInstrument].Bid; - var result = await Bot.PlacePendingOrders(_initData.Demo.Accounts[0].Id, placeOrderInstrument, placeOrderCount, currentBid); - _operations.AddRange(result); - } - #endregion - break; - } - - - // Wait for next action - if (restartTimer) - _actionTimer.Change(Bot.ActionScriptInterval, 0); - } - private void NextActionTimerCall(object status) - { - _actionTimer.Change(-1, -1); - _currentAction++; - if (_currentAction >= _processedScript.Length) - { - IsFinished = true; - PrintTestOperations(); - //script finished - OnTestFinished(new System.EventArgs()); } - else - { - Execute(_processedScript[_currentAction]); - } - } - - private void PrintTestOperations() - { - var distinct = _operations.GroupBy(x => x.Operation); - Console.WriteLine(distinct); - LogInfo($" == Test Finished for Bot {Bot.Id} =="); - foreach (var group in distinct) - { - LogInfo($"{group.Key}=>Count:{group.Count()} Average Time:{group.Average(x => x.Duration.TotalSeconds)}"); - } - LogInfo(" == =="); - } - - private void LogInfo(string message) - { - OnLog(new LogEventArgs(DateTime.UtcNow, $"Bot:[{Bot.Id}]", "info", $"Thread[{ Thread.CurrentThread.ManagedThreadId.ToString() }] {message}", null)); - } - - private void OnLog(LogEventArgs e) - { - LogEvent?.Invoke(this, e); - } - private void OnTestFinished(System.EventArgs e) - { - TestFinished?.Invoke(this, e); - } - } -} diff --git a/src/MarginTrading.Client/Bot/MtUserHelper.cs b/src/MarginTrading.Client/Bot/MtUserHelper.cs deleted file mode 100644 index 2a56b62cb..000000000 --- a/src/MarginTrading.Client/Bot/MtUserHelper.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Flurl.Http; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; - -namespace MarginTrading.Client.Bot -{ - internal static class MtUserHelper - { - public static async Task ApplicationInfo(string apiAddress) - { - var address = $"{apiAddress}/ApplicationInfo"; - - var result = await address.GetJsonAsync(); - if (result.Error != null) - throw new Exception(result.Error.Message); - } - public static async Task EmailVerification(string email, string apiAddress) - { - var address = $"{apiAddress}/EmailVerification"; - var Email = email; - - var result = await address.PostJsonAsync( - new - { - Email - }).ReceiveJson(); - if (result.Error != null) - throw new Exception(result.Error.Message); - } - public static async Task Registration(string email, string password, string apiAddress) - { - var address = $"{apiAddress}/Registration"; - - var ClientInfo = "MT Test Bot"; - var ContactPhone = ""; - var Email = email; - var FullName = ""; - var Hint = "MtBotHint"; - var Password = HashPass(password); - var result = await address.PostJsonAsync( - new - { - ClientInfo, - ContactPhone, - Email, - FullName, - Hint, - Password - }).ReceiveJson(); - if (result.Error != null) - throw new Exception(result.Error.Message); - - } - - private static string HashPass(string password) - { - using (var sha256 = System.Security.Cryptography.SHA256.Create()) - { - // Send text to hash. - var hashedBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(password)); - - // Get the hashed string. - var hash = BitConverter.ToString(hashedBytes).Replace("-", "").ToLower(); - - // Print the string. - Console.WriteLine(hash); - return hash; - } - } - } -} diff --git a/src/MarginTrading.Client/Bot/OperationResult.cs b/src/MarginTrading.Client/Bot/OperationResult.cs deleted file mode 100644 index 618d53b47..000000000 --- a/src/MarginTrading.Client/Bot/OperationResult.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace MarginTrading.Client.Bot -{ - public class OperationResult - { - public DateTime StartDate { get; set; } - public DateTime EndDate { get; set; } - public string Operation { get; set; } - public object Result { get; set; } - - public TimeSpan Duration => EndDate - StartDate; - } -} diff --git a/src/MarginTrading.Client/ClientEnv.cs b/src/MarginTrading.Client/ClientEnv.cs deleted file mode 100644 index e809a1ec6..000000000 --- a/src/MarginTrading.Client/ClientEnv.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MarginTrading.Client -{ - public enum ClientEnv - { - Local, - Dev, - Test, - Prod - } -} diff --git a/src/MarginTrading.Client/Dockerfile b/src/MarginTrading.Client/Dockerfile deleted file mode 100644 index 0b4077057..000000000 --- a/src/MarginTrading.Client/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM microsoft/aspnetcore:2.0 -WORKDIR /app -COPY . . -ENTRYPOINT ["dotnet", "MarginTrading.Client.dll"] diff --git a/src/MarginTrading.Client/EventArgs/LogEventArgs.cs b/src/MarginTrading.Client/EventArgs/LogEventArgs.cs deleted file mode 100644 index ff4bc2cef..000000000 --- a/src/MarginTrading.Client/EventArgs/LogEventArgs.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace MarginTrading.Client.EventArgs -{ - public class LogEventArgs : System.EventArgs - { - public LogEventArgs(DateTime date, string origin, string type, string message, Exception exception) - { - Date = date; - Origin = origin; - Type = type; - Message = message; - Exception = exception; - } - - public DateTime Date { get; } - public string Origin { get; } - public string Type { get; } - public string Message { get; } - public Exception Exception { get; } - } -} diff --git a/src/MarginTrading.Client/JsonResults/ApiAuthResult.cs b/src/MarginTrading.Client/JsonResults/ApiAuthResult.cs deleted file mode 100644 index 7c288f941..000000000 --- a/src/MarginTrading.Client/JsonResults/ApiAuthResult.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Newtonsoft.Json; - -namespace MarginTrading.Client.JsonResults -{ - class ApiAuthResult - { - [JsonProperty("Result")] - public AuthResult Result { get; set; } - [JsonProperty("Error")] - public AuthError Error { get; set; } - } -} diff --git a/src/MarginTrading.Client/JsonResults/AuthError.cs b/src/MarginTrading.Client/JsonResults/AuthError.cs deleted file mode 100644 index c1377fa54..000000000 --- a/src/MarginTrading.Client/JsonResults/AuthError.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Newtonsoft.Json; - -namespace MarginTrading.Client.JsonResults -{ - class AuthError - { - [JsonProperty("Code")] - public int Code { get; set; } - [JsonProperty("Field ")] - public object Field { get; set; } - [JsonProperty("Message")] - public string Message { get; set; } - } -} diff --git a/src/MarginTrading.Client/JsonResults/AuthResult.cs b/src/MarginTrading.Client/JsonResults/AuthResult.cs deleted file mode 100644 index 90e6035be..000000000 --- a/src/MarginTrading.Client/JsonResults/AuthResult.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Newtonsoft.Json; - -namespace MarginTrading.Client.JsonResults -{ - class AuthResult - { - - [JsonProperty("KycStatus")] - public string KycStatus { get; set; } - [JsonProperty("PinIsEntered ")] - public bool PinIsEntered { get; set; } - [JsonProperty("Token")] - public string Token { get; set; } - [JsonProperty("NotificationsId")] - public string NotificationsId { get; set; } - } -} diff --git a/src/MarginTrading.Client/MarginTrading.Client.csproj b/src/MarginTrading.Client/MarginTrading.Client.csproj deleted file mode 100644 index 2dd333f7d..000000000 --- a/src/MarginTrading.Client/MarginTrading.Client.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - Exe - netcoreapp2.0 - 1.0.1 - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/MarginTrading.Client/MtClient.cs b/src/MarginTrading.Client/MtClient.cs deleted file mode 100644 index 82c138831..000000000 --- a/src/MarginTrading.Client/MtClient.cs +++ /dev/null @@ -1,320 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Common; -using MarginTrading.Client.Wamp; -using MarginTrading.Contract.BackendContracts; -using MarginTrading.Contract.ClientContracts; -using WampSharp.V2; -using WampSharp.V2.Client; -using Microsoft.Extensions.Configuration; - -namespace MarginTrading.Client -{ - public class MtClient - { - private string _token; - private string _notificationId; - private string _serverAddress; - private IWampRealmProxy _realmProxy; - private IRpcMtFrontend _service; - private IWampChannel _channel; - - public void Connect(ClientEnv env) - { - SetEnv(env); - var factory = new DefaultWampChannelFactory(); - _channel = factory.CreateJsonChannel(_serverAddress, "mtcrossbar"); - - while (!_channel.RealmProxy.Monitor.IsConnected) - { - try - { - Console.WriteLine($"Trying to connect to server {_serverAddress}..."); - _channel.Open().Wait(); - } - catch - { - Console.WriteLine("Retrying in 5 sec..."); - Thread.Sleep(5000); - } - } - Console.WriteLine($"Connected to server {_serverAddress}"); - - _realmProxy = _channel.RealmProxy; - _service = _realmProxy.Services.GetCalleeProxy(); - } - - public void Close() - { - _channel.Close(); - } - - public void SetEnv(ClientEnv env) - { - var config = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", true, true) - .AddJsonFile($"appsettings.{env.ToString().ToLower()}.json", true, true) - .Build(); - - _token = config["token"]; - _notificationId = config["notificationId"]; - _serverAddress = config["serverAddress"]; - } - - public void IsAlive() - { - var data = _service.IsAlive(); - Console.WriteLine(data.ToJson()); - } - - public async Task InitData() - { - var data = await _service.InitData(_token); - Console.WriteLine(data.ToJson()); - } - - public async Task InitAccounts() - { - var data = await _service.InitAccounts(_token); - Console.WriteLine(data.ToJson()); - } - - public async Task AccountInstruments() - { - var data = await _service.AccountInstruments(_token); - Console.WriteLine(data.ToJson()); - } - - public async Task InitGraph() - { - var data = await _service.InitGraph(); - Console.WriteLine(data.ChartData.Count); - } - - public async Task GetAccountHistory() - { - var request = new AccountHistoryRpcClientRequest - { - Token = _token - }; - - var result = await _service.GetAccountHistory(request.ToJson()); - } - - public async Task GetHistory() - { - var request = new AccountHistoryRpcClientRequest - { - Token = _token - }; - - var result = await _service.GetHistory(request.ToJson()); - } - - public async Task PlaceOrder() - { - var subscription = _realmProxy.Services.GetSubject($"user.updates.{_notificationId}") - .Subscribe(info => - { - if (info.Account != null) - Console.WriteLine("Account changed"); - - if (info.Order != null) - Console.WriteLine("Order changed"); - - if (info.AccountStopout != null) - Console.WriteLine("Account stopout"); - - if (info.UserUpdate != null) - Console.WriteLine($"User update: accountAssetPairs = {info.UserUpdate.UpdateAccountAssetPairs}, accounts = {info.UserUpdate.UpdateAccounts}"); - }); - - var data = await _service.InitData(_token); - - try - { - var request = new OpenOrderRpcClientRequest - { - Token = _token, - Order = new NewOrderClientContract - { - AccountId = data.Demo.Accounts[0].Id, - FillType = OrderFillTypeContract.FillOrKill, - Instrument = "BTCUSD", - Volume = 1 - } - }; - - var order = await _service.PlaceOrder(request.ToJson()); - - if (order.Result.Status == 3) - { - Console.WriteLine($"Order rejected: {order.Result.RejectReason} -> {order.Result.RejectReasonText}"); - } - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - - Console.WriteLine("Press enter"); - Console.ReadLine(); - subscription.Dispose(); - } - - public async Task CloseOrder(bool closeAnyFpl) - { - var subscription = _realmProxy.Services.GetSubject($"user.updates.{_notificationId}") - .Subscribe(info => - { - if (info.Order != null) - { - Console.WriteLine($"Order pnl: {info.Order.Fpl}"); - } - }); - - while (true) - { - var orders = await _service.GetOpenPositions(_token); - - if (orders.Demo.Any(item => item.Fpl > 0) || closeAnyFpl) - { - var order = orders.Demo.First(); - - var request = new CloseOrderRpcClientRequest - { - OrderId = order.Id, - AccountId = order.AccountId, - Token = _token - }; - - var result = await _service.CloseOrder(request.ToJson()); - break; - } - - Thread.Sleep(200); - } - - Console.WriteLine("Press enter"); - Console.ReadLine(); - subscription.Dispose(); - } - - public async Task CancelOrder() - { - var subscription = _realmProxy.Services.GetSubject($"user.updates.{_notificationId}") - .Subscribe(info => - { - if (info.Order != null) - { - Console.WriteLine($"Order status: {info.Order.Status}"); - } - }); - - while (true) - { - var orders = await _service.GetOpenPositions(_token); - - if (orders.Demo.Any()) - { - var order = orders.Demo.First(); - - var request = new CloseOrderRpcClientRequest - { - OrderId = order.Id, - Token = _token - }; - - var result = _service.CancelOrder(request.ToJson()); - break; - } - - Thread.Sleep(200); - } - - Console.WriteLine("Press enter"); - Console.ReadLine(); - subscription.Dispose(); - } - - public async Task GetOpenPositions() - { - var result = await _service.GetOpenPositions(_token); - } - - public async Task GetAccountOpenPositions() - { - var data = await _service.InitData(_token); - - var request = new AccountTokenClientRequest - { - Token = _token, - AccountId = data.Demo.Accounts[0].Id - }; - - var result = await _service.GetAccountOpenPositions(request.ToJson()); - } - - public async Task GetClientOrders() - { - var result = await _service.GetClientOrders(_token); - } - - public async Task ChangeOrderLimits() - { - var request = new ChangeOrderLimitsRpcClientRequest - { - Token = _token, - OrderId = "" - }; - - var result = await _service.ChangeOrderLimits(request.ToJson()); - Console.WriteLine($"result = {result.Result}, message = {result.Message}"); - } - - public void Prices(string instrument = null) - { - var topicName = !string.IsNullOrEmpty(instrument) ? $"prices.update.{instrument}" : "prices.update"; - var subscription = _realmProxy.Services.GetSubject(topicName) - .Subscribe(info => - { - Console.WriteLine($"{info.Id} {info.Bid}/{info.Ask}"); - }); - - - Console.ReadLine(); - subscription.Dispose(); - } - - public void Trades() - { - var subscription = _realmProxy.Services.GetSubject("trades") - .Subscribe(info => - { - Console.WriteLine($"{info.ToJson()}"); - }); - - - Console.ReadLine(); - subscription.Dispose(); - } - - public void UserUpdates() - { - var subscription = _realmProxy.Services.GetSubject($"user.updates.{_notificationId}") - .Subscribe(info => - { - if (info.UserUpdate != null) - Console.WriteLine($"assets = {info.UserUpdate.UpdateAccountAssetPairs}, accounts = {info.UserUpdate.UpdateAccounts}"); - }); - - - Console.ReadLine(); - subscription.Dispose(); - } - } -} diff --git a/src/MarginTrading.Client/Program.cs b/src/MarginTrading.Client/Program.cs deleted file mode 100644 index d6b8aaf8a..000000000 --- a/src/MarginTrading.Client/Program.cs +++ /dev/null @@ -1,78 +0,0 @@ -using MarginTrading.Client.Bot; -using System; - -namespace MarginTrading.Client -{ - public class Program - { - public static void Main(string[] args) - { - var testBot = false; - string testBotSettingsFile = null; - var autorun = false; - for (var i = 0; i< args.Length; i++) - { - - if (args[i].ToLower() == "-h" || args[i].ToLower() == "--help") - { - Console.WriteLine("-h\t--help Show this help"); - Console.WriteLine("-b\t--bot Run test bot. Usage: -b [json configuration file]"); - Console.ReadKey(); - return; - } - if (args[i].ToLower() == "-a" || args[i].ToLower() == "--autorun") - { - autorun = true; - } - if (args[i].ToLower() == "-b" || args[i].ToLower() == "--bot") - { - testBot = true; - if (args.Length > i + 1 && !args[i + 1].StartsWith('-')) - { - try { testBotSettingsFile = args[++i]; } - catch { testBotSettingsFile = ""; } - } - } - } - if (testBot) - { - BotConsole.StartBotHost(testBotSettingsFile, autorun); - } - else - { - for (var i = 0; i < 1; i++) - { - var client = new MtClient(); - - client.Connect(ClientEnv.Dev); - - client.IsAlive(); - //client.InitData().Wait(); - //client.InitAccounts(); - //client.AccountInstruments(); - //client.InitGraph().Wait(); - - //client.AccountDeposit().Wait(); - //client.AccountWithdraw(); - //client.SetActiveAccount(); - //client.GetAccountHistory(); - //client.GetHistory(); - - //client.PlaceOrder().Wait(); - //client.CloseOrder(true).Wait(); - //client.CancelOrder(); - //client.GetAccountOpenPositions().Wait(); - //client.GetOpenPositions().Wait(); - //client.GetClientOrders(); - //client.ChangeOrderLimits(); - - client.Prices("BTCUSD"); - //client.UserUpdates(); - client.Trades(); - - client.Close(); - } - } - } - } -} diff --git a/src/MarginTrading.Client/Settings/MtTradingBotSettings.cs b/src/MarginTrading.Client/Settings/MtTradingBotSettings.cs deleted file mode 100644 index 4a6d6a7f1..000000000 --- a/src/MarginTrading.Client/Settings/MtTradingBotSettings.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MarginTrading.Client.Settings -{ - class MtTradingBotSettings - { - public string MTServerAddress { get; set; } - public string MTAuthorizationAddress { get; set; } - public int NumberOfUsers { get; set; } - public int ActionScriptInterval { get; set; } - public int TransactionFrequencyMin { get; set; } - public int TransactionFrequencyMax { get; set; } - public string UsersFile { get; set; } = null; - public string TestFile { get; set; } = null; - } -} diff --git a/src/MarginTrading.Client/Settings/MtTradingBotTestSettings.cs b/src/MarginTrading.Client/Settings/MtTradingBotTestSettings.cs deleted file mode 100644 index 456262371..000000000 --- a/src/MarginTrading.Client/Settings/MtTradingBotTestSettings.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MarginTrading.Client.Settings -{ - class MtTradingBotTestSettings - { - public TestBotUserSettings[] Users { get; set; } - public string[] Actions { get; set; } - } -} diff --git a/src/MarginTrading.Client/Settings/TestBotUserSettings.cs b/src/MarginTrading.Client/Settings/TestBotUserSettings.cs deleted file mode 100644 index 413a1d422..000000000 --- a/src/MarginTrading.Client/Settings/TestBotUserSettings.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MarginTrading.Client.Settings -{ - class TestBotUserSettings - { - public int Number { get; set; } - public string Email { get; set; } - public string Password { get; set; } - } -} diff --git a/src/MarginTrading.Client/Settings/TradingBotSettings.cs b/src/MarginTrading.Client/Settings/TradingBotSettings.cs deleted file mode 100644 index 5dba4e0b8..000000000 --- a/src/MarginTrading.Client/Settings/TradingBotSettings.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MarginTrading.Client.Settings -{ - class TradingBotSettings - { - public MtTradingBotSettings MtTradingBot { get; set; } - } -} diff --git a/src/MarginTrading.Client/Wamp/IRpcMtFrontend.cs b/src/MarginTrading.Client/Wamp/IRpcMtFrontend.cs deleted file mode 100644 index 8e52ea819..000000000 --- a/src/MarginTrading.Client/Wamp/IRpcMtFrontend.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Threading.Tasks; -using MarginTrading.Contract.ClientContracts; -using WampSharp.V2.Rpc; - -namespace MarginTrading.Client.Wamp -{ - public interface IRpcMtFrontend - { - [WampProcedure("is.alive")] - IsAliveResponse IsAlive(); - - [WampProcedure("init.data")] - Task InitData(string token); - - [WampProcedure("init.accounts")] - Task InitAccounts(string token); - - [WampProcedure("init.accountinstruments")] - Task AccountInstruments(string token = null); - - [WampProcedure("init.graph")] - Task InitGraph(string token = null, string[] assetIds = null); - - [WampProcedure("account.history")] - Task GetAccountHistory(string requestJson); - - [WampProcedure("account.history.new")] - Task GetHistory(string requestJson); - - [WampProcedure("order.place")] - Task> PlaceOrder(string requestJson); - - [WampProcedure("order.close")] - Task> CloseOrder(string requestJson); - - [WampProcedure("order.cancel")] - Task> CancelOrder(string requestJson); - - [WampProcedure("order.list")] - Task GetOpenPositions(string token); - - [WampProcedure("order.account.list")] - Task GetAccountOpenPositions(string requestJson); - - [WampProcedure("order.positions")] - Task GetClientOrders(string token); - - [WampProcedure("order.changeLimits")] - Task> ChangeOrderLimits(string requestJson); - - [WampProcedure("orderbooks")] - Task GetOrderBook(string instrument); - } -} diff --git a/src/MarginTrading.Client/Wamp/RealmNames.cs b/src/MarginTrading.Client/Wamp/RealmNames.cs deleted file mode 100644 index ec722d9cf..000000000 --- a/src/MarginTrading.Client/Wamp/RealmNames.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MarginTrading.Client.Wamp -{ - public static class RealmNames - { - public const string FrontEnd = "mtcrossbar"; - } -} diff --git a/src/MarginTrading.Client/bottest01.json b/src/MarginTrading.Client/bottest01.json deleted file mode 100644 index 94ee3ef2c..000000000 --- a/src/MarginTrading.Client/bottest01.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "Actions": [ - "InitData", - "InitAccounts", - "InitGraph", - "Subscribe BTCUSD", - "GetAccountHistory", - "PlaceOrder BTCUSD 10", - "CloseOrder BTCUSD 10", - "PlacePendingOrder BTCUSD 10", - "PlaceOrder BTCUSD 10", - "Reconnect", - "InitData", - "GetClientOrders", - "CancelOrder BTCUSD 10", - "CloseOrder BTCUSD 10", - "GetAccountOpenPositions", - "Unsubscribe BTCUSD" - ], - "Users": [ - { - "Number": 1, - "Email": "nuno@dev.com", - "Password": "123456" - } - ] -} - diff --git a/src/MarginTrading.Client/bottest02.json b/src/MarginTrading.Client/bottest02.json deleted file mode 100644 index 1009fe2d4..000000000 --- a/src/MarginTrading.Client/bottest02.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "Actions": [ - "InitData", - "InitAccounts", - "InitGraph", - "Subscribe BTCUSD", - "GetAccountHistory", - "PlaceOrder BTCUSD 100", - "CloseOrder BTCUSD 100", - "PlacePendingOrder BTCUSD 10", - "PlaceOrder BTCUSD 100", - "Reconnect", - "InitData", - "GetClientOrders", - "CancelOrder BTCUSD 10", - "CloseOrder BTCUSD 100", - "GetAccountOpenPositions", - "Unsubscribe BTCUSD" - ], - "Users": [ - { - "Number": 1, - "Email": "nuno@dev.com", - "Password": "123456" - } - ] -} - diff --git a/src/MarginTrading.Client/bottest03.json b/src/MarginTrading.Client/bottest03.json deleted file mode 100644 index cd55b8a2c..000000000 --- a/src/MarginTrading.Client/bottest03.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "Actions": [ - "InitData", - "InitAccounts", - "InitGraph", - "PlaceOrder BTCUSD 1", - "Reconnect", - "InitData", - "CloseOrder BTCUSD 1" - ], - "Users": [ - { - "Number": 1, - "Email": "nuno@dev.com", - "Password": "123456" - } - ] -} - diff --git a/src/MarginTrading.Client/bottest04.json b/src/MarginTrading.Client/bottest04.json deleted file mode 100644 index ed2c1695e..000000000 --- a/src/MarginTrading.Client/bottest04.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Actions": [ - "InitData", - "PlaceOrder BTCUSD 5", - "CloseOrder BTCUSD 5" - ], - "Users": [ - { - "Number": 1, - "Email": "nuno@dev.com", - "Password": "123456" - } - ] -} - diff --git a/src/MarginTrading.Client/devUsers.csv b/src/MarginTrading.Client/devUsers.csv deleted file mode 100644 index 6d3a3d6e0..000000000 --- a/src/MarginTrading.Client/devUsers.csv +++ /dev/null @@ -1,1001 +0,0 @@ -Email;Password -bot0001@dev.com;P123456P -bot0002@dev.com;P123456P -bot0003@dev.com;P123456P -bot0004@dev.com;P123456P -bot0005@dev.com;P123456P -bot0006@dev.com;P123456P -bot0007@dev.com;P123456P -bot0008@dev.com;P123456P -bot0009@dev.com;P123456P -bot0010@dev.com;P123456P -bot0011@dev.com;P123456P -bot0012@dev.com;P123456P -bot0013@dev.com;P123456P -bot0014@dev.com;P123456P -bot0015@dev.com;P123456P -bot0016@dev.com;P123456P -bot0017@dev.com;P123456P -bot0018@dev.com;P123456P -bot0019@dev.com;P123456P -bot0020@dev.com;P123456P -bot0021@dev.com;P123456P -bot0022@dev.com;P123456P -bot0023@dev.com;P123456P -bot0024@dev.com;P123456P -bot0025@dev.com;P123456P -bot0026@dev.com;P123456P -bot0027@dev.com;P123456P -bot0028@dev.com;P123456P -bot0029@dev.com;P123456P -bot0030@dev.com;P123456P -bot0031@dev.com;P123456P -bot0032@dev.com;P123456P -bot0033@dev.com;P123456P -bot0034@dev.com;P123456P -bot0035@dev.com;P123456P -bot0036@dev.com;P123456P -bot0037@dev.com;P123456P -bot0038@dev.com;P123456P -bot0039@dev.com;P123456P -bot0040@dev.com;P123456P -bot0041@dev.com;P123456P -bot0042@dev.com;P123456P -bot0043@dev.com;P123456P -bot0044@dev.com;P123456P -bot0045@dev.com;P123456P -bot0046@dev.com;P123456P -bot0047@dev.com;P123456P -bot0048@dev.com;P123456P -bot0049@dev.com;P123456P -bot0050@dev.com;P123456P -bot0051@dev.com;P123456P -bot0052@dev.com;P123456P -bot0053@dev.com;P123456P -bot0054@dev.com;P123456P -bot0055@dev.com;P123456P -bot0056@dev.com;P123456P -bot0057@dev.com;P123456P -bot0058@dev.com;P123456P -bot0059@dev.com;P123456P -bot0060@dev.com;P123456P -bot0061@dev.com;P123456P -bot0062@dev.com;P123456P -bot0063@dev.com;P123456P -bot0064@dev.com;P123456P -bot0065@dev.com;P123456P -bot0066@dev.com;P123456P -bot0067@dev.com;P123456P -bot0068@dev.com;P123456P -bot0069@dev.com;P123456P -bot0070@dev.com;P123456P -bot0071@dev.com;P123456P -bot0072@dev.com;P123456P -bot0073@dev.com;P123456P -bot0074@dev.com;P123456P -bot0075@dev.com;P123456P -bot0076@dev.com;P123456P -bot0077@dev.com;P123456P -bot0078@dev.com;P123456P -bot0079@dev.com;P123456P -bot0080@dev.com;P123456P -bot0081@dev.com;P123456P -bot0082@dev.com;P123456P -bot0083@dev.com;P123456P -bot0084@dev.com;P123456P -bot0085@dev.com;P123456P -bot0086@dev.com;P123456P -bot0087@dev.com;P123456P -bot0088@dev.com;P123456P -bot0089@dev.com;P123456P -bot0090@dev.com;P123456P -bot0091@dev.com;P123456P -bot0092@dev.com;P123456P -bot0093@dev.com;P123456P -bot0094@dev.com;P123456P -bot0095@dev.com;P123456P -bot0096@dev.com;P123456P -bot0097@dev.com;P123456P -bot0098@dev.com;P123456P -bot0099@dev.com;P123456P -bot0100@dev.com;P123456P -bot0101@dev.com;P123456P -bot0102@dev.com;P123456P -bot0103@dev.com;P123456P -bot0104@dev.com;P123456P -bot0105@dev.com;P123456P -bot0106@dev.com;P123456P -bot0107@dev.com;P123456P -bot0108@dev.com;P123456P -bot0109@dev.com;P123456P -bot0110@dev.com;P123456P -bot0111@dev.com;P123456P -bot0112@dev.com;P123456P -bot0113@dev.com;P123456P -bot0114@dev.com;P123456P -bot0115@dev.com;P123456P -bot0116@dev.com;P123456P -bot0117@dev.com;P123456P -bot0118@dev.com;P123456P -bot0119@dev.com;P123456P -bot0120@dev.com;P123456P -bot0121@dev.com;P123456P -bot0122@dev.com;P123456P -bot0123@dev.com;P123456P -bot0124@dev.com;P123456P -bot0125@dev.com;P123456P -bot0126@dev.com;P123456P -bot0127@dev.com;P123456P -bot0128@dev.com;P123456P -bot0129@dev.com;P123456P -bot0130@dev.com;P123456P -bot0131@dev.com;P123456P -bot0132@dev.com;P123456P -bot0133@dev.com;P123456P -bot0134@dev.com;P123456P -bot0135@dev.com;P123456P -bot0136@dev.com;P123456P -bot0137@dev.com;P123456P -bot0138@dev.com;P123456P -bot0139@dev.com;P123456P -bot0140@dev.com;P123456P -bot0141@dev.com;P123456P -bot0142@dev.com;P123456P -bot0143@dev.com;P123456P -bot0144@dev.com;P123456P -bot0145@dev.com;P123456P -bot0146@dev.com;P123456P -bot0147@dev.com;P123456P -bot0148@dev.com;P123456P -bot0149@dev.com;P123456P -bot0150@dev.com;P123456P -bot0151@dev.com;P123456P -bot0152@dev.com;P123456P -bot0153@dev.com;P123456P -bot0154@dev.com;P123456P -bot0155@dev.com;P123456P -bot0156@dev.com;P123456P -bot0157@dev.com;P123456P -bot0158@dev.com;P123456P -bot0159@dev.com;P123456P -bot0160@dev.com;P123456P -bot0161@dev.com;P123456P -bot0162@dev.com;P123456P -bot0163@dev.com;P123456P -bot0164@dev.com;P123456P -bot0165@dev.com;P123456P -bot0166@dev.com;P123456P -bot0167@dev.com;P123456P -bot0168@dev.com;P123456P -bot0169@dev.com;P123456P -bot0170@dev.com;P123456P -bot0171@dev.com;P123456P -bot0172@dev.com;P123456P -bot0173@dev.com;P123456P -bot0174@dev.com;P123456P -bot0175@dev.com;P123456P -bot0176@dev.com;P123456P -bot0177@dev.com;P123456P -bot0178@dev.com;P123456P -bot0179@dev.com;P123456P -bot0180@dev.com;P123456P -bot0181@dev.com;P123456P -bot0182@dev.com;P123456P -bot0183@dev.com;P123456P -bot0184@dev.com;P123456P -bot0185@dev.com;P123456P -bot0186@dev.com;P123456P -bot0187@dev.com;P123456P -bot0188@dev.com;P123456P -bot0189@dev.com;P123456P -bot0190@dev.com;P123456P -bot0191@dev.com;P123456P -bot0192@dev.com;P123456P -bot0193@dev.com;P123456P -bot0194@dev.com;P123456P -bot0195@dev.com;P123456P -bot0196@dev.com;P123456P -bot0197@dev.com;P123456P -bot0198@dev.com;P123456P -bot0199@dev.com;P123456P -bot0200@dev.com;P123456P -bot0201@dev.com;P123456P -bot0202@dev.com;P123456P -bot0203@dev.com;P123456P -bot0204@dev.com;P123456P -bot0205@dev.com;P123456P -bot0206@dev.com;P123456P -bot0207@dev.com;P123456P -bot0208@dev.com;P123456P -bot0209@dev.com;P123456P -bot0210@dev.com;P123456P -bot0211@dev.com;P123456P -bot0212@dev.com;P123456P -bot0213@dev.com;P123456P -bot0214@dev.com;P123456P -bot0215@dev.com;P123456P -bot0216@dev.com;P123456P -bot0217@dev.com;P123456P -bot0218@dev.com;P123456P -bot0219@dev.com;P123456P -bot0220@dev.com;P123456P -bot0221@dev.com;P123456P -bot0222@dev.com;P123456P -bot0223@dev.com;P123456P -bot0224@dev.com;P123456P -bot0225@dev.com;P123456P -bot0226@dev.com;P123456P -bot0227@dev.com;P123456P -bot0228@dev.com;P123456P -bot0229@dev.com;P123456P -bot0230@dev.com;P123456P -bot0231@dev.com;P123456P -bot0232@dev.com;P123456P -bot0233@dev.com;P123456P -bot0234@dev.com;P123456P -bot0235@dev.com;P123456P -bot0236@dev.com;P123456P -bot0237@dev.com;P123456P -bot0238@dev.com;P123456P -bot0239@dev.com;P123456P -bot0240@dev.com;P123456P -bot0241@dev.com;P123456P -bot0242@dev.com;P123456P -bot0243@dev.com;P123456P -bot0244@dev.com;P123456P -bot0245@dev.com;P123456P -bot0246@dev.com;P123456P -bot0247@dev.com;P123456P -bot0248@dev.com;P123456P -bot0249@dev.com;P123456P -bot0250@dev.com;P123456P -bot0251@dev.com;P123456P -bot0252@dev.com;P123456P -bot0253@dev.com;P123456P -bot0254@dev.com;P123456P -bot0255@dev.com;P123456P -bot0256@dev.com;P123456P -bot0257@dev.com;P123456P -bot0258@dev.com;P123456P -bot0259@dev.com;P123456P -bot0260@dev.com;P123456P -bot0261@dev.com;P123456P -bot0262@dev.com;P123456P -bot0263@dev.com;P123456P -bot0264@dev.com;P123456P -bot0265@dev.com;P123456P -bot0266@dev.com;P123456P -bot0267@dev.com;P123456P -bot0268@dev.com;P123456P -bot0269@dev.com;P123456P -bot0270@dev.com;P123456P -bot0271@dev.com;P123456P -bot0272@dev.com;P123456P -bot0273@dev.com;P123456P -bot0274@dev.com;P123456P -bot0275@dev.com;P123456P -bot0276@dev.com;P123456P -bot0277@dev.com;P123456P -bot0278@dev.com;P123456P -bot0279@dev.com;P123456P -bot0280@dev.com;P123456P -bot0281@dev.com;P123456P -bot0282@dev.com;P123456P -bot0283@dev.com;P123456P -bot0284@dev.com;P123456P -bot0285@dev.com;P123456P -bot0286@dev.com;P123456P -bot0287@dev.com;P123456P -bot0288@dev.com;P123456P -bot0289@dev.com;P123456P -bot0290@dev.com;P123456P -bot0291@dev.com;P123456P -bot0292@dev.com;P123456P -bot0293@dev.com;P123456P -bot0294@dev.com;P123456P -bot0295@dev.com;P123456P -bot0296@dev.com;P123456P -bot0297@dev.com;P123456P -bot0298@dev.com;P123456P -bot0299@dev.com;P123456P -bot0300@dev.com;P123456P -bot0301@dev.com;P123456P -bot0302@dev.com;P123456P -bot0303@dev.com;P123456P -bot0304@dev.com;P123456P -bot0305@dev.com;P123456P -bot0306@dev.com;P123456P -bot0307@dev.com;P123456P -bot0308@dev.com;P123456P -bot0309@dev.com;P123456P -bot0310@dev.com;P123456P -bot0311@dev.com;P123456P -bot0312@dev.com;P123456P -bot0313@dev.com;P123456P -bot0314@dev.com;P123456P -bot0315@dev.com;P123456P -bot0316@dev.com;P123456P -bot0317@dev.com;P123456P -bot0318@dev.com;P123456P -bot0319@dev.com;P123456P -bot0320@dev.com;P123456P -bot0321@dev.com;P123456P -bot0322@dev.com;P123456P -bot0323@dev.com;P123456P -bot0324@dev.com;P123456P -bot0325@dev.com;P123456P -bot0326@dev.com;P123456P -bot0327@dev.com;P123456P -bot0328@dev.com;P123456P -bot0329@dev.com;P123456P -bot0330@dev.com;P123456P -bot0331@dev.com;P123456P -bot0332@dev.com;P123456P -bot0333@dev.com;P123456P -bot0334@dev.com;P123456P -bot0335@dev.com;P123456P -bot0336@dev.com;P123456P -bot0337@dev.com;P123456P -bot0338@dev.com;P123456P -bot0339@dev.com;P123456P -bot0340@dev.com;P123456P -bot0341@dev.com;P123456P -bot0342@dev.com;P123456P -bot0343@dev.com;P123456P -bot0344@dev.com;P123456P -bot0345@dev.com;P123456P -bot0346@dev.com;P123456P -bot0347@dev.com;P123456P -bot0348@dev.com;P123456P -bot0349@dev.com;P123456P -bot0350@dev.com;P123456P -bot0351@dev.com;P123456P -bot0352@dev.com;P123456P -bot0353@dev.com;P123456P -bot0354@dev.com;P123456P -bot0355@dev.com;P123456P -bot0356@dev.com;P123456P -bot0357@dev.com;P123456P -bot0358@dev.com;P123456P -bot0359@dev.com;P123456P -bot0360@dev.com;P123456P -bot0361@dev.com;P123456P -bot0362@dev.com;P123456P -bot0363@dev.com;P123456P -bot0364@dev.com;P123456P -bot0365@dev.com;P123456P -bot0366@dev.com;P123456P -bot0367@dev.com;P123456P -bot0368@dev.com;P123456P -bot0369@dev.com;P123456P -bot0370@dev.com;P123456P -bot0371@dev.com;P123456P -bot0372@dev.com;P123456P -bot0373@dev.com;P123456P -bot0374@dev.com;P123456P -bot0375@dev.com;P123456P -bot0376@dev.com;P123456P -bot0377@dev.com;P123456P -bot0378@dev.com;P123456P -bot0379@dev.com;P123456P -bot0380@dev.com;P123456P -bot0381@dev.com;P123456P -bot0382@dev.com;P123456P -bot0383@dev.com;P123456P -bot0384@dev.com;P123456P -bot0385@dev.com;P123456P -bot0386@dev.com;P123456P -bot0387@dev.com;P123456P -bot0388@dev.com;P123456P -bot0389@dev.com;P123456P -bot0390@dev.com;P123456P -bot0391@dev.com;P123456P -bot0392@dev.com;P123456P -bot0393@dev.com;P123456P -bot0394@dev.com;P123456P -bot0395@dev.com;P123456P -bot0396@dev.com;P123456P -bot0397@dev.com;P123456P -bot0398@dev.com;P123456P -bot0399@dev.com;P123456P -bot0400@dev.com;P123456P -bot0401@dev.com;P123456P -bot0402@dev.com;P123456P -bot0403@dev.com;P123456P -bot0404@dev.com;P123456P -bot0405@dev.com;P123456P -bot0406@dev.com;P123456P -bot0407@dev.com;P123456P -bot0408@dev.com;P123456P -bot0409@dev.com;P123456P -bot0410@dev.com;P123456P -bot0411@dev.com;P123456P -bot0412@dev.com;P123456P -bot0413@dev.com;P123456P -bot0414@dev.com;P123456P -bot0415@dev.com;P123456P -bot0416@dev.com;P123456P -bot0417@dev.com;P123456P -bot0418@dev.com;P123456P -bot0419@dev.com;P123456P -bot0420@dev.com;P123456P -bot0421@dev.com;P123456P -bot0422@dev.com;P123456P -bot0423@dev.com;P123456P -bot0424@dev.com;P123456P -bot0425@dev.com;P123456P -bot0426@dev.com;P123456P -bot0427@dev.com;P123456P -bot0428@dev.com;P123456P -bot0429@dev.com;P123456P -bot0430@dev.com;P123456P -bot0431@dev.com;P123456P -bot0432@dev.com;P123456P -bot0433@dev.com;P123456P -bot0434@dev.com;P123456P -bot0435@dev.com;P123456P -bot0436@dev.com;P123456P -bot0437@dev.com;P123456P -bot0438@dev.com;P123456P -bot0439@dev.com;P123456P -bot0440@dev.com;P123456P -bot0441@dev.com;P123456P -bot0442@dev.com;P123456P -bot0443@dev.com;P123456P -bot0444@dev.com;P123456P -bot0445@dev.com;P123456P -bot0446@dev.com;P123456P -bot0447@dev.com;P123456P -bot0448@dev.com;P123456P -bot0449@dev.com;P123456P -bot0450@dev.com;P123456P -bot0451@dev.com;P123456P -bot0452@dev.com;P123456P -bot0453@dev.com;P123456P -bot0454@dev.com;P123456P -bot0455@dev.com;P123456P -bot0456@dev.com;P123456P -bot0457@dev.com;P123456P -bot0458@dev.com;P123456P -bot0459@dev.com;P123456P -bot0460@dev.com;P123456P -bot0461@dev.com;P123456P -bot0462@dev.com;P123456P -bot0463@dev.com;P123456P -bot0464@dev.com;P123456P -bot0465@dev.com;P123456P -bot0466@dev.com;P123456P -bot0467@dev.com;P123456P -bot0468@dev.com;P123456P -bot0469@dev.com;P123456P -bot0470@dev.com;P123456P -bot0471@dev.com;P123456P -bot0472@dev.com;P123456P -bot0473@dev.com;P123456P -bot0474@dev.com;P123456P -bot0475@dev.com;P123456P -bot0476@dev.com;P123456P -bot0477@dev.com;P123456P -bot0478@dev.com;P123456P -bot0479@dev.com;P123456P -bot0480@dev.com;P123456P -bot0481@dev.com;P123456P -bot0482@dev.com;P123456P -bot0483@dev.com;P123456P -bot0484@dev.com;P123456P -bot0485@dev.com;P123456P -bot0486@dev.com;P123456P -bot0487@dev.com;P123456P -bot0488@dev.com;P123456P -bot0489@dev.com;P123456P -bot0490@dev.com;P123456P -bot0491@dev.com;P123456P -bot0492@dev.com;P123456P -bot0493@dev.com;P123456P -bot0494@dev.com;P123456P -bot0495@dev.com;P123456P -bot0496@dev.com;P123456P -bot0497@dev.com;P123456P -bot0498@dev.com;P123456P -bot0499@dev.com;P123456P -bot0500@dev.com;P123456P -bot0501@dev.com;P123456P -bot0502@dev.com;P123456P -bot0503@dev.com;P123456P -bot0504@dev.com;P123456P -bot0505@dev.com;P123456P -bot0506@dev.com;P123456P -bot0507@dev.com;P123456P -bot0508@dev.com;P123456P -bot0509@dev.com;P123456P -bot0510@dev.com;P123456P -bot0511@dev.com;P123456P -bot0512@dev.com;P123456P -bot0513@dev.com;P123456P -bot0514@dev.com;P123456P -bot0515@dev.com;P123456P -bot0516@dev.com;P123456P -bot0517@dev.com;P123456P -bot0518@dev.com;P123456P -bot0519@dev.com;P123456P -bot0520@dev.com;P123456P -bot0521@dev.com;P123456P -bot0522@dev.com;P123456P -bot0523@dev.com;P123456P -bot0524@dev.com;P123456P -bot0525@dev.com;P123456P -bot0526@dev.com;P123456P -bot0527@dev.com;P123456P -bot0528@dev.com;P123456P -bot0529@dev.com;P123456P -bot0530@dev.com;P123456P -bot0531@dev.com;P123456P -bot0532@dev.com;P123456P -bot0533@dev.com;P123456P -bot0534@dev.com;P123456P -bot0535@dev.com;P123456P -bot0536@dev.com;P123456P -bot0537@dev.com;P123456P -bot0538@dev.com;P123456P -bot0539@dev.com;P123456P -bot0540@dev.com;P123456P -bot0541@dev.com;P123456P -bot0542@dev.com;P123456P -bot0543@dev.com;P123456P -bot0544@dev.com;P123456P -bot0545@dev.com;P123456P -bot0546@dev.com;P123456P -bot0547@dev.com;P123456P -bot0548@dev.com;P123456P -bot0549@dev.com;P123456P -bot0550@dev.com;P123456P -bot0551@dev.com;P123456P -bot0552@dev.com;P123456P -bot0553@dev.com;P123456P -bot0554@dev.com;P123456P -bot0555@dev.com;P123456P -bot0556@dev.com;P123456P -bot0557@dev.com;P123456P -bot0558@dev.com;P123456P -bot0559@dev.com;P123456P -bot0560@dev.com;P123456P -bot0561@dev.com;P123456P -bot0562@dev.com;P123456P -bot0563@dev.com;P123456P -bot0564@dev.com;P123456P -bot0565@dev.com;P123456P -bot0566@dev.com;P123456P -bot0567@dev.com;P123456P -bot0568@dev.com;P123456P -bot0569@dev.com;P123456P -bot0570@dev.com;P123456P -bot0571@dev.com;P123456P -bot0572@dev.com;P123456P -bot0573@dev.com;P123456P -bot0574@dev.com;P123456P -bot0575@dev.com;P123456P -bot0576@dev.com;P123456P -bot0577@dev.com;P123456P -bot0578@dev.com;P123456P -bot0579@dev.com;P123456P -bot0580@dev.com;P123456P -bot0581@dev.com;P123456P -bot0582@dev.com;P123456P -bot0583@dev.com;P123456P -bot0584@dev.com;P123456P -bot0585@dev.com;P123456P -bot0586@dev.com;P123456P -bot0587@dev.com;P123456P -bot0588@dev.com;P123456P -bot0589@dev.com;P123456P -bot0590@dev.com;P123456P -bot0591@dev.com;P123456P -bot0592@dev.com;P123456P -bot0593@dev.com;P123456P -bot0594@dev.com;P123456P -bot0595@dev.com;P123456P -bot0596@dev.com;P123456P -bot0597@dev.com;P123456P -bot0598@dev.com;P123456P -bot0599@dev.com;P123456P -bot0600@dev.com;P123456P -bot0601@dev.com;P123456P -bot0602@dev.com;P123456P -bot0603@dev.com;P123456P -bot0604@dev.com;P123456P -bot0605@dev.com;P123456P -bot0606@dev.com;P123456P -bot0607@dev.com;P123456P -bot0608@dev.com;P123456P -bot0609@dev.com;P123456P -bot0610@dev.com;P123456P -bot0611@dev.com;P123456P -bot0612@dev.com;P123456P -bot0613@dev.com;P123456P -bot0614@dev.com;P123456P -bot0615@dev.com;P123456P -bot0616@dev.com;P123456P -bot0617@dev.com;P123456P -bot0618@dev.com;P123456P -bot0619@dev.com;P123456P -bot0620@dev.com;P123456P -bot0621@dev.com;P123456P -bot0622@dev.com;P123456P -bot0623@dev.com;P123456P -bot0624@dev.com;P123456P -bot0625@dev.com;P123456P -bot0626@dev.com;P123456P -bot0627@dev.com;P123456P -bot0628@dev.com;P123456P -bot0629@dev.com;P123456P -bot0630@dev.com;P123456P -bot0631@dev.com;P123456P -bot0632@dev.com;P123456P -bot0633@dev.com;P123456P -bot0634@dev.com;P123456P -bot0635@dev.com;P123456P -bot0636@dev.com;P123456P -bot0637@dev.com;P123456P -bot0638@dev.com;P123456P -bot0639@dev.com;P123456P -bot0640@dev.com;P123456P -bot0641@dev.com;P123456P -bot0642@dev.com;P123456P -bot0643@dev.com;P123456P -bot0644@dev.com;P123456P -bot0645@dev.com;P123456P -bot0646@dev.com;P123456P -bot0647@dev.com;P123456P -bot0648@dev.com;P123456P -bot0649@dev.com;P123456P -bot0650@dev.com;P123456P -bot0651@dev.com;P123456P -bot0652@dev.com;P123456P -bot0653@dev.com;P123456P -bot0654@dev.com;P123456P -bot0655@dev.com;P123456P -bot0656@dev.com;P123456P -bot0657@dev.com;P123456P -bot0658@dev.com;P123456P -bot0659@dev.com;P123456P -bot0660@dev.com;P123456P -bot0661@dev.com;P123456P -bot0662@dev.com;P123456P -bot0663@dev.com;P123456P -bot0664@dev.com;P123456P -bot0665@dev.com;P123456P -bot0666@dev.com;P123456P -bot0667@dev.com;P123456P -bot0668@dev.com;P123456P -bot0669@dev.com;P123456P -bot0670@dev.com;P123456P -bot0671@dev.com;P123456P -bot0672@dev.com;P123456P -bot0673@dev.com;P123456P -bot0674@dev.com;P123456P -bot0675@dev.com;P123456P -bot0676@dev.com;P123456P -bot0677@dev.com;P123456P -bot0678@dev.com;P123456P -bot0679@dev.com;P123456P -bot0680@dev.com;P123456P -bot0681@dev.com;P123456P -bot0682@dev.com;P123456P -bot0683@dev.com;P123456P -bot0684@dev.com;P123456P -bot0685@dev.com;P123456P -bot0686@dev.com;P123456P -bot0687@dev.com;P123456P -bot0688@dev.com;P123456P -bot0689@dev.com;P123456P -bot0690@dev.com;P123456P -bot0691@dev.com;P123456P -bot0692@dev.com;P123456P -bot0693@dev.com;P123456P -bot0694@dev.com;P123456P -bot0695@dev.com;P123456P -bot0696@dev.com;P123456P -bot0697@dev.com;P123456P -bot0698@dev.com;P123456P -bot0699@dev.com;P123456P -bot0700@dev.com;P123456P -bot0701@dev.com;P123456P -bot0702@dev.com;P123456P -bot0703@dev.com;P123456P -bot0704@dev.com;P123456P -bot0705@dev.com;P123456P -bot0706@dev.com;P123456P -bot0707@dev.com;P123456P -bot0708@dev.com;P123456P -bot0709@dev.com;P123456P -bot0710@dev.com;P123456P -bot0711@dev.com;P123456P -bot0712@dev.com;P123456P -bot0713@dev.com;P123456P -bot0714@dev.com;P123456P -bot0715@dev.com;P123456P -bot0716@dev.com;P123456P -bot0717@dev.com;P123456P -bot0718@dev.com;P123456P -bot0719@dev.com;P123456P -bot0720@dev.com;P123456P -bot0721@dev.com;P123456P -bot0722@dev.com;P123456P -bot0723@dev.com;P123456P -bot0724@dev.com;P123456P -bot0725@dev.com;P123456P -bot0726@dev.com;P123456P -bot0727@dev.com;P123456P -bot0728@dev.com;P123456P -bot0729@dev.com;P123456P -bot0730@dev.com;P123456P -bot0731@dev.com;P123456P -bot0732@dev.com;P123456P -bot0733@dev.com;P123456P -bot0734@dev.com;P123456P -bot0735@dev.com;P123456P -bot0736@dev.com;P123456P -bot0737@dev.com;P123456P -bot0738@dev.com;P123456P -bot0739@dev.com;P123456P -bot0740@dev.com;P123456P -bot0741@dev.com;P123456P -bot0742@dev.com;P123456P -bot0743@dev.com;P123456P -bot0744@dev.com;P123456P -bot0745@dev.com;P123456P -bot0746@dev.com;P123456P -bot0747@dev.com;P123456P -bot0748@dev.com;P123456P -bot0749@dev.com;P123456P -bot0750@dev.com;P123456P -bot0751@dev.com;P123456P -bot0752@dev.com;P123456P -bot0753@dev.com;P123456P -bot0754@dev.com;P123456P -bot0755@dev.com;P123456P -bot0756@dev.com;P123456P -bot0757@dev.com;P123456P -bot0758@dev.com;P123456P -bot0759@dev.com;P123456P -bot0760@dev.com;P123456P -bot0761@dev.com;P123456P -bot0762@dev.com;P123456P -bot0763@dev.com;P123456P -bot0764@dev.com;P123456P -bot0765@dev.com;P123456P -bot0766@dev.com;P123456P -bot0767@dev.com;P123456P -bot0768@dev.com;P123456P -bot0769@dev.com;P123456P -bot0770@dev.com;P123456P -bot0771@dev.com;P123456P -bot0772@dev.com;P123456P -bot0773@dev.com;P123456P -bot0774@dev.com;P123456P -bot0775@dev.com;P123456P -bot0776@dev.com;P123456P -bot0777@dev.com;P123456P -bot0778@dev.com;P123456P -bot0779@dev.com;P123456P -bot0780@dev.com;P123456P -bot0781@dev.com;P123456P -bot0782@dev.com;P123456P -bot0783@dev.com;P123456P -bot0784@dev.com;P123456P -bot0785@dev.com;P123456P -bot0786@dev.com;P123456P -bot0787@dev.com;P123456P -bot0788@dev.com;P123456P -bot0789@dev.com;P123456P -bot0790@dev.com;P123456P -bot0791@dev.com;P123456P -bot0792@dev.com;P123456P -bot0793@dev.com;P123456P -bot0794@dev.com;P123456P -bot0795@dev.com;P123456P -bot0796@dev.com;P123456P -bot0797@dev.com;P123456P -bot0798@dev.com;P123456P -bot0799@dev.com;P123456P -bot0800@dev.com;P123456P -bot0801@dev.com;P123456P -bot0802@dev.com;P123456P -bot0803@dev.com;P123456P -bot0804@dev.com;P123456P -bot0805@dev.com;P123456P -bot0806@dev.com;P123456P -bot0807@dev.com;P123456P -bot0808@dev.com;P123456P -bot0809@dev.com;P123456P -bot0810@dev.com;P123456P -bot0811@dev.com;P123456P -bot0812@dev.com;P123456P -bot0813@dev.com;P123456P -bot0814@dev.com;P123456P -bot0815@dev.com;P123456P -bot0816@dev.com;P123456P -bot0817@dev.com;P123456P -bot0818@dev.com;P123456P -bot0819@dev.com;P123456P -bot0820@dev.com;P123456P -bot0821@dev.com;P123456P -bot0822@dev.com;P123456P -bot0823@dev.com;P123456P -bot0824@dev.com;P123456P -bot0825@dev.com;P123456P -bot0826@dev.com;P123456P -bot0827@dev.com;P123456P -bot0828@dev.com;P123456P -bot0829@dev.com;P123456P -bot0830@dev.com;P123456P -bot0831@dev.com;P123456P -bot0832@dev.com;P123456P -bot0833@dev.com;P123456P -bot0834@dev.com;P123456P -bot0835@dev.com;P123456P -bot0836@dev.com;P123456P -bot0837@dev.com;P123456P -bot0838@dev.com;P123456P -bot0839@dev.com;P123456P -bot0840@dev.com;P123456P -bot0841@dev.com;P123456P -bot0842@dev.com;P123456P -bot0843@dev.com;P123456P -bot0844@dev.com;P123456P -bot0845@dev.com;P123456P -bot0846@dev.com;P123456P -bot0847@dev.com;P123456P -bot0848@dev.com;P123456P -bot0849@dev.com;P123456P -bot0850@dev.com;P123456P -bot0851@dev.com;P123456P -bot0852@dev.com;P123456P -bot0853@dev.com;P123456P -bot0854@dev.com;P123456P -bot0855@dev.com;P123456P -bot0856@dev.com;P123456P -bot0857@dev.com;P123456P -bot0858@dev.com;P123456P -bot0859@dev.com;P123456P -bot0860@dev.com;P123456P -bot0861@dev.com;P123456P -bot0862@dev.com;P123456P -bot0863@dev.com;P123456P -bot0864@dev.com;P123456P -bot0865@dev.com;P123456P -bot0866@dev.com;P123456P -bot0867@dev.com;P123456P -bot0868@dev.com;P123456P -bot0869@dev.com;P123456P -bot0870@dev.com;P123456P -bot0871@dev.com;P123456P -bot0872@dev.com;P123456P -bot0873@dev.com;P123456P -bot0874@dev.com;P123456P -bot0875@dev.com;P123456P -bot0876@dev.com;P123456P -bot0877@dev.com;P123456P -bot0878@dev.com;P123456P -bot0879@dev.com;P123456P -bot0880@dev.com;P123456P -bot0881@dev.com;P123456P -bot0882@dev.com;P123456P -bot0883@dev.com;P123456P -bot0884@dev.com;P123456P -bot0885@dev.com;P123456P -bot0886@dev.com;P123456P -bot0887@dev.com;P123456P -bot0888@dev.com;P123456P -bot0889@dev.com;P123456P -bot0890@dev.com;P123456P -bot0891@dev.com;P123456P -bot0892@dev.com;P123456P -bot0893@dev.com;P123456P -bot0894@dev.com;P123456P -bot0895@dev.com;P123456P -bot0896@dev.com;P123456P -bot0897@dev.com;P123456P -bot0898@dev.com;P123456P -bot0899@dev.com;P123456P -bot0900@dev.com;P123456P -bot0901@dev.com;P123456P -bot0902@dev.com;P123456P -bot0903@dev.com;P123456P -bot0904@dev.com;P123456P -bot0905@dev.com;P123456P -bot0906@dev.com;P123456P -bot0907@dev.com;P123456P -bot0908@dev.com;P123456P -bot0909@dev.com;P123456P -bot0910@dev.com;P123456P -bot0911@dev.com;P123456P -bot0912@dev.com;P123456P -bot0913@dev.com;P123456P -bot0914@dev.com;P123456P -bot0915@dev.com;P123456P -bot0916@dev.com;P123456P -bot0917@dev.com;P123456P -bot0918@dev.com;P123456P -bot0919@dev.com;P123456P -bot0920@dev.com;P123456P -bot0921@dev.com;P123456P -bot0922@dev.com;P123456P -bot0923@dev.com;P123456P -bot0924@dev.com;P123456P -bot0925@dev.com;P123456P -bot0926@dev.com;P123456P -bot0927@dev.com;P123456P -bot0928@dev.com;P123456P -bot0929@dev.com;P123456P -bot0930@dev.com;P123456P -bot0931@dev.com;P123456P -bot0932@dev.com;P123456P -bot0933@dev.com;P123456P -bot0934@dev.com;P123456P -bot0935@dev.com;P123456P -bot0936@dev.com;P123456P -bot0937@dev.com;P123456P -bot0938@dev.com;P123456P -bot0939@dev.com;P123456P -bot0940@dev.com;P123456P -bot0941@dev.com;P123456P -bot0942@dev.com;P123456P -bot0943@dev.com;P123456P -bot0944@dev.com;P123456P -bot0945@dev.com;P123456P -bot0946@dev.com;P123456P -bot0947@dev.com;P123456P -bot0948@dev.com;P123456P -bot0949@dev.com;P123456P -bot0950@dev.com;P123456P -bot0951@dev.com;P123456P -bot0952@dev.com;P123456P -bot0953@dev.com;P123456P -bot0954@dev.com;P123456P -bot0955@dev.com;P123456P -bot0956@dev.com;P123456P -bot0957@dev.com;P123456P -bot0958@dev.com;P123456P -bot0959@dev.com;P123456P -bot0960@dev.com;P123456P -bot0961@dev.com;P123456P -bot0962@dev.com;P123456P -bot0963@dev.com;P123456P -bot0964@dev.com;P123456P -bot0965@dev.com;P123456P -bot0966@dev.com;P123456P -bot0967@dev.com;P123456P -bot0968@dev.com;P123456P -bot0969@dev.com;P123456P -bot0970@dev.com;P123456P -bot0971@dev.com;P123456P -bot0972@dev.com;P123456P -bot0973@dev.com;P123456P -bot0974@dev.com;P123456P -bot0975@dev.com;P123456P -bot0976@dev.com;P123456P -bot0977@dev.com;P123456P -bot0978@dev.com;P123456P -bot0979@dev.com;P123456P -bot0980@dev.com;P123456P -bot0981@dev.com;P123456P -bot0982@dev.com;P123456P -bot0983@dev.com;P123456P -bot0984@dev.com;P123456P -bot0985@dev.com;P123456P -bot0986@dev.com;P123456P -bot0987@dev.com;P123456P -bot0988@dev.com;P123456P -bot0989@dev.com;P123456P -bot0990@dev.com;P123456P -bot0991@dev.com;P123456P -bot0992@dev.com;P123456P -bot0993@dev.com;P123456P -bot0994@dev.com;P123456P -bot0995@dev.com;P123456P -bot0996@dev.com;P123456P -bot0997@dev.com;P123456P -bot0998@dev.com;P123456P -bot0999@dev.com;P123456P -bot1000@dev.com;P123456P \ No newline at end of file diff --git a/src/MarginTrading.Client/docker-compose.yml b/src/MarginTrading.Client/docker-compose.yml deleted file mode 100644 index fe358a0ef..000000000 --- a/src/MarginTrading.Client/docker-compose.yml +++ /dev/null @@ -1,16 +0,0 @@ -version: '2' -services: - margintradingclient: - image: lykkedev/margintradingclient - container_name: margintradingclient - command: -b - environment: - - SettingsUrl=${SettingsUrl} - networks: - mynet: - aliases: - - margintradingclient - -networks: - mynet: - driver: bridge diff --git a/src/MarginTrading.Client/prodUsers.csv b/src/MarginTrading.Client/prodUsers.csv deleted file mode 100644 index 6d3a3d6e0..000000000 --- a/src/MarginTrading.Client/prodUsers.csv +++ /dev/null @@ -1,1001 +0,0 @@ -Email;Password -bot0001@dev.com;P123456P -bot0002@dev.com;P123456P -bot0003@dev.com;P123456P -bot0004@dev.com;P123456P -bot0005@dev.com;P123456P -bot0006@dev.com;P123456P -bot0007@dev.com;P123456P -bot0008@dev.com;P123456P -bot0009@dev.com;P123456P -bot0010@dev.com;P123456P -bot0011@dev.com;P123456P -bot0012@dev.com;P123456P -bot0013@dev.com;P123456P -bot0014@dev.com;P123456P -bot0015@dev.com;P123456P -bot0016@dev.com;P123456P -bot0017@dev.com;P123456P -bot0018@dev.com;P123456P -bot0019@dev.com;P123456P -bot0020@dev.com;P123456P -bot0021@dev.com;P123456P -bot0022@dev.com;P123456P -bot0023@dev.com;P123456P -bot0024@dev.com;P123456P -bot0025@dev.com;P123456P -bot0026@dev.com;P123456P -bot0027@dev.com;P123456P -bot0028@dev.com;P123456P -bot0029@dev.com;P123456P -bot0030@dev.com;P123456P -bot0031@dev.com;P123456P -bot0032@dev.com;P123456P -bot0033@dev.com;P123456P -bot0034@dev.com;P123456P -bot0035@dev.com;P123456P -bot0036@dev.com;P123456P -bot0037@dev.com;P123456P -bot0038@dev.com;P123456P -bot0039@dev.com;P123456P -bot0040@dev.com;P123456P -bot0041@dev.com;P123456P -bot0042@dev.com;P123456P -bot0043@dev.com;P123456P -bot0044@dev.com;P123456P -bot0045@dev.com;P123456P -bot0046@dev.com;P123456P -bot0047@dev.com;P123456P -bot0048@dev.com;P123456P -bot0049@dev.com;P123456P -bot0050@dev.com;P123456P -bot0051@dev.com;P123456P -bot0052@dev.com;P123456P -bot0053@dev.com;P123456P -bot0054@dev.com;P123456P -bot0055@dev.com;P123456P -bot0056@dev.com;P123456P -bot0057@dev.com;P123456P -bot0058@dev.com;P123456P -bot0059@dev.com;P123456P -bot0060@dev.com;P123456P -bot0061@dev.com;P123456P -bot0062@dev.com;P123456P -bot0063@dev.com;P123456P -bot0064@dev.com;P123456P -bot0065@dev.com;P123456P -bot0066@dev.com;P123456P -bot0067@dev.com;P123456P -bot0068@dev.com;P123456P -bot0069@dev.com;P123456P -bot0070@dev.com;P123456P -bot0071@dev.com;P123456P -bot0072@dev.com;P123456P -bot0073@dev.com;P123456P -bot0074@dev.com;P123456P -bot0075@dev.com;P123456P -bot0076@dev.com;P123456P -bot0077@dev.com;P123456P -bot0078@dev.com;P123456P -bot0079@dev.com;P123456P -bot0080@dev.com;P123456P -bot0081@dev.com;P123456P -bot0082@dev.com;P123456P -bot0083@dev.com;P123456P -bot0084@dev.com;P123456P -bot0085@dev.com;P123456P -bot0086@dev.com;P123456P -bot0087@dev.com;P123456P -bot0088@dev.com;P123456P -bot0089@dev.com;P123456P -bot0090@dev.com;P123456P -bot0091@dev.com;P123456P -bot0092@dev.com;P123456P -bot0093@dev.com;P123456P -bot0094@dev.com;P123456P -bot0095@dev.com;P123456P -bot0096@dev.com;P123456P -bot0097@dev.com;P123456P -bot0098@dev.com;P123456P -bot0099@dev.com;P123456P -bot0100@dev.com;P123456P -bot0101@dev.com;P123456P -bot0102@dev.com;P123456P -bot0103@dev.com;P123456P -bot0104@dev.com;P123456P -bot0105@dev.com;P123456P -bot0106@dev.com;P123456P -bot0107@dev.com;P123456P -bot0108@dev.com;P123456P -bot0109@dev.com;P123456P -bot0110@dev.com;P123456P -bot0111@dev.com;P123456P -bot0112@dev.com;P123456P -bot0113@dev.com;P123456P -bot0114@dev.com;P123456P -bot0115@dev.com;P123456P -bot0116@dev.com;P123456P -bot0117@dev.com;P123456P -bot0118@dev.com;P123456P -bot0119@dev.com;P123456P -bot0120@dev.com;P123456P -bot0121@dev.com;P123456P -bot0122@dev.com;P123456P -bot0123@dev.com;P123456P -bot0124@dev.com;P123456P -bot0125@dev.com;P123456P -bot0126@dev.com;P123456P -bot0127@dev.com;P123456P -bot0128@dev.com;P123456P -bot0129@dev.com;P123456P -bot0130@dev.com;P123456P -bot0131@dev.com;P123456P -bot0132@dev.com;P123456P -bot0133@dev.com;P123456P -bot0134@dev.com;P123456P -bot0135@dev.com;P123456P -bot0136@dev.com;P123456P -bot0137@dev.com;P123456P -bot0138@dev.com;P123456P -bot0139@dev.com;P123456P -bot0140@dev.com;P123456P -bot0141@dev.com;P123456P -bot0142@dev.com;P123456P -bot0143@dev.com;P123456P -bot0144@dev.com;P123456P -bot0145@dev.com;P123456P -bot0146@dev.com;P123456P -bot0147@dev.com;P123456P -bot0148@dev.com;P123456P -bot0149@dev.com;P123456P -bot0150@dev.com;P123456P -bot0151@dev.com;P123456P -bot0152@dev.com;P123456P -bot0153@dev.com;P123456P -bot0154@dev.com;P123456P -bot0155@dev.com;P123456P -bot0156@dev.com;P123456P -bot0157@dev.com;P123456P -bot0158@dev.com;P123456P -bot0159@dev.com;P123456P -bot0160@dev.com;P123456P -bot0161@dev.com;P123456P -bot0162@dev.com;P123456P -bot0163@dev.com;P123456P -bot0164@dev.com;P123456P -bot0165@dev.com;P123456P -bot0166@dev.com;P123456P -bot0167@dev.com;P123456P -bot0168@dev.com;P123456P -bot0169@dev.com;P123456P -bot0170@dev.com;P123456P -bot0171@dev.com;P123456P -bot0172@dev.com;P123456P -bot0173@dev.com;P123456P -bot0174@dev.com;P123456P -bot0175@dev.com;P123456P -bot0176@dev.com;P123456P -bot0177@dev.com;P123456P -bot0178@dev.com;P123456P -bot0179@dev.com;P123456P -bot0180@dev.com;P123456P -bot0181@dev.com;P123456P -bot0182@dev.com;P123456P -bot0183@dev.com;P123456P -bot0184@dev.com;P123456P -bot0185@dev.com;P123456P -bot0186@dev.com;P123456P -bot0187@dev.com;P123456P -bot0188@dev.com;P123456P -bot0189@dev.com;P123456P -bot0190@dev.com;P123456P -bot0191@dev.com;P123456P -bot0192@dev.com;P123456P -bot0193@dev.com;P123456P -bot0194@dev.com;P123456P -bot0195@dev.com;P123456P -bot0196@dev.com;P123456P -bot0197@dev.com;P123456P -bot0198@dev.com;P123456P -bot0199@dev.com;P123456P -bot0200@dev.com;P123456P -bot0201@dev.com;P123456P -bot0202@dev.com;P123456P -bot0203@dev.com;P123456P -bot0204@dev.com;P123456P -bot0205@dev.com;P123456P -bot0206@dev.com;P123456P -bot0207@dev.com;P123456P -bot0208@dev.com;P123456P -bot0209@dev.com;P123456P -bot0210@dev.com;P123456P -bot0211@dev.com;P123456P -bot0212@dev.com;P123456P -bot0213@dev.com;P123456P -bot0214@dev.com;P123456P -bot0215@dev.com;P123456P -bot0216@dev.com;P123456P -bot0217@dev.com;P123456P -bot0218@dev.com;P123456P -bot0219@dev.com;P123456P -bot0220@dev.com;P123456P -bot0221@dev.com;P123456P -bot0222@dev.com;P123456P -bot0223@dev.com;P123456P -bot0224@dev.com;P123456P -bot0225@dev.com;P123456P -bot0226@dev.com;P123456P -bot0227@dev.com;P123456P -bot0228@dev.com;P123456P -bot0229@dev.com;P123456P -bot0230@dev.com;P123456P -bot0231@dev.com;P123456P -bot0232@dev.com;P123456P -bot0233@dev.com;P123456P -bot0234@dev.com;P123456P -bot0235@dev.com;P123456P -bot0236@dev.com;P123456P -bot0237@dev.com;P123456P -bot0238@dev.com;P123456P -bot0239@dev.com;P123456P -bot0240@dev.com;P123456P -bot0241@dev.com;P123456P -bot0242@dev.com;P123456P -bot0243@dev.com;P123456P -bot0244@dev.com;P123456P -bot0245@dev.com;P123456P -bot0246@dev.com;P123456P -bot0247@dev.com;P123456P -bot0248@dev.com;P123456P -bot0249@dev.com;P123456P -bot0250@dev.com;P123456P -bot0251@dev.com;P123456P -bot0252@dev.com;P123456P -bot0253@dev.com;P123456P -bot0254@dev.com;P123456P -bot0255@dev.com;P123456P -bot0256@dev.com;P123456P -bot0257@dev.com;P123456P -bot0258@dev.com;P123456P -bot0259@dev.com;P123456P -bot0260@dev.com;P123456P -bot0261@dev.com;P123456P -bot0262@dev.com;P123456P -bot0263@dev.com;P123456P -bot0264@dev.com;P123456P -bot0265@dev.com;P123456P -bot0266@dev.com;P123456P -bot0267@dev.com;P123456P -bot0268@dev.com;P123456P -bot0269@dev.com;P123456P -bot0270@dev.com;P123456P -bot0271@dev.com;P123456P -bot0272@dev.com;P123456P -bot0273@dev.com;P123456P -bot0274@dev.com;P123456P -bot0275@dev.com;P123456P -bot0276@dev.com;P123456P -bot0277@dev.com;P123456P -bot0278@dev.com;P123456P -bot0279@dev.com;P123456P -bot0280@dev.com;P123456P -bot0281@dev.com;P123456P -bot0282@dev.com;P123456P -bot0283@dev.com;P123456P -bot0284@dev.com;P123456P -bot0285@dev.com;P123456P -bot0286@dev.com;P123456P -bot0287@dev.com;P123456P -bot0288@dev.com;P123456P -bot0289@dev.com;P123456P -bot0290@dev.com;P123456P -bot0291@dev.com;P123456P -bot0292@dev.com;P123456P -bot0293@dev.com;P123456P -bot0294@dev.com;P123456P -bot0295@dev.com;P123456P -bot0296@dev.com;P123456P -bot0297@dev.com;P123456P -bot0298@dev.com;P123456P -bot0299@dev.com;P123456P -bot0300@dev.com;P123456P -bot0301@dev.com;P123456P -bot0302@dev.com;P123456P -bot0303@dev.com;P123456P -bot0304@dev.com;P123456P -bot0305@dev.com;P123456P -bot0306@dev.com;P123456P -bot0307@dev.com;P123456P -bot0308@dev.com;P123456P -bot0309@dev.com;P123456P -bot0310@dev.com;P123456P -bot0311@dev.com;P123456P -bot0312@dev.com;P123456P -bot0313@dev.com;P123456P -bot0314@dev.com;P123456P -bot0315@dev.com;P123456P -bot0316@dev.com;P123456P -bot0317@dev.com;P123456P -bot0318@dev.com;P123456P -bot0319@dev.com;P123456P -bot0320@dev.com;P123456P -bot0321@dev.com;P123456P -bot0322@dev.com;P123456P -bot0323@dev.com;P123456P -bot0324@dev.com;P123456P -bot0325@dev.com;P123456P -bot0326@dev.com;P123456P -bot0327@dev.com;P123456P -bot0328@dev.com;P123456P -bot0329@dev.com;P123456P -bot0330@dev.com;P123456P -bot0331@dev.com;P123456P -bot0332@dev.com;P123456P -bot0333@dev.com;P123456P -bot0334@dev.com;P123456P -bot0335@dev.com;P123456P -bot0336@dev.com;P123456P -bot0337@dev.com;P123456P -bot0338@dev.com;P123456P -bot0339@dev.com;P123456P -bot0340@dev.com;P123456P -bot0341@dev.com;P123456P -bot0342@dev.com;P123456P -bot0343@dev.com;P123456P -bot0344@dev.com;P123456P -bot0345@dev.com;P123456P -bot0346@dev.com;P123456P -bot0347@dev.com;P123456P -bot0348@dev.com;P123456P -bot0349@dev.com;P123456P -bot0350@dev.com;P123456P -bot0351@dev.com;P123456P -bot0352@dev.com;P123456P -bot0353@dev.com;P123456P -bot0354@dev.com;P123456P -bot0355@dev.com;P123456P -bot0356@dev.com;P123456P -bot0357@dev.com;P123456P -bot0358@dev.com;P123456P -bot0359@dev.com;P123456P -bot0360@dev.com;P123456P -bot0361@dev.com;P123456P -bot0362@dev.com;P123456P -bot0363@dev.com;P123456P -bot0364@dev.com;P123456P -bot0365@dev.com;P123456P -bot0366@dev.com;P123456P -bot0367@dev.com;P123456P -bot0368@dev.com;P123456P -bot0369@dev.com;P123456P -bot0370@dev.com;P123456P -bot0371@dev.com;P123456P -bot0372@dev.com;P123456P -bot0373@dev.com;P123456P -bot0374@dev.com;P123456P -bot0375@dev.com;P123456P -bot0376@dev.com;P123456P -bot0377@dev.com;P123456P -bot0378@dev.com;P123456P -bot0379@dev.com;P123456P -bot0380@dev.com;P123456P -bot0381@dev.com;P123456P -bot0382@dev.com;P123456P -bot0383@dev.com;P123456P -bot0384@dev.com;P123456P -bot0385@dev.com;P123456P -bot0386@dev.com;P123456P -bot0387@dev.com;P123456P -bot0388@dev.com;P123456P -bot0389@dev.com;P123456P -bot0390@dev.com;P123456P -bot0391@dev.com;P123456P -bot0392@dev.com;P123456P -bot0393@dev.com;P123456P -bot0394@dev.com;P123456P -bot0395@dev.com;P123456P -bot0396@dev.com;P123456P -bot0397@dev.com;P123456P -bot0398@dev.com;P123456P -bot0399@dev.com;P123456P -bot0400@dev.com;P123456P -bot0401@dev.com;P123456P -bot0402@dev.com;P123456P -bot0403@dev.com;P123456P -bot0404@dev.com;P123456P -bot0405@dev.com;P123456P -bot0406@dev.com;P123456P -bot0407@dev.com;P123456P -bot0408@dev.com;P123456P -bot0409@dev.com;P123456P -bot0410@dev.com;P123456P -bot0411@dev.com;P123456P -bot0412@dev.com;P123456P -bot0413@dev.com;P123456P -bot0414@dev.com;P123456P -bot0415@dev.com;P123456P -bot0416@dev.com;P123456P -bot0417@dev.com;P123456P -bot0418@dev.com;P123456P -bot0419@dev.com;P123456P -bot0420@dev.com;P123456P -bot0421@dev.com;P123456P -bot0422@dev.com;P123456P -bot0423@dev.com;P123456P -bot0424@dev.com;P123456P -bot0425@dev.com;P123456P -bot0426@dev.com;P123456P -bot0427@dev.com;P123456P -bot0428@dev.com;P123456P -bot0429@dev.com;P123456P -bot0430@dev.com;P123456P -bot0431@dev.com;P123456P -bot0432@dev.com;P123456P -bot0433@dev.com;P123456P -bot0434@dev.com;P123456P -bot0435@dev.com;P123456P -bot0436@dev.com;P123456P -bot0437@dev.com;P123456P -bot0438@dev.com;P123456P -bot0439@dev.com;P123456P -bot0440@dev.com;P123456P -bot0441@dev.com;P123456P -bot0442@dev.com;P123456P -bot0443@dev.com;P123456P -bot0444@dev.com;P123456P -bot0445@dev.com;P123456P -bot0446@dev.com;P123456P -bot0447@dev.com;P123456P -bot0448@dev.com;P123456P -bot0449@dev.com;P123456P -bot0450@dev.com;P123456P -bot0451@dev.com;P123456P -bot0452@dev.com;P123456P -bot0453@dev.com;P123456P -bot0454@dev.com;P123456P -bot0455@dev.com;P123456P -bot0456@dev.com;P123456P -bot0457@dev.com;P123456P -bot0458@dev.com;P123456P -bot0459@dev.com;P123456P -bot0460@dev.com;P123456P -bot0461@dev.com;P123456P -bot0462@dev.com;P123456P -bot0463@dev.com;P123456P -bot0464@dev.com;P123456P -bot0465@dev.com;P123456P -bot0466@dev.com;P123456P -bot0467@dev.com;P123456P -bot0468@dev.com;P123456P -bot0469@dev.com;P123456P -bot0470@dev.com;P123456P -bot0471@dev.com;P123456P -bot0472@dev.com;P123456P -bot0473@dev.com;P123456P -bot0474@dev.com;P123456P -bot0475@dev.com;P123456P -bot0476@dev.com;P123456P -bot0477@dev.com;P123456P -bot0478@dev.com;P123456P -bot0479@dev.com;P123456P -bot0480@dev.com;P123456P -bot0481@dev.com;P123456P -bot0482@dev.com;P123456P -bot0483@dev.com;P123456P -bot0484@dev.com;P123456P -bot0485@dev.com;P123456P -bot0486@dev.com;P123456P -bot0487@dev.com;P123456P -bot0488@dev.com;P123456P -bot0489@dev.com;P123456P -bot0490@dev.com;P123456P -bot0491@dev.com;P123456P -bot0492@dev.com;P123456P -bot0493@dev.com;P123456P -bot0494@dev.com;P123456P -bot0495@dev.com;P123456P -bot0496@dev.com;P123456P -bot0497@dev.com;P123456P -bot0498@dev.com;P123456P -bot0499@dev.com;P123456P -bot0500@dev.com;P123456P -bot0501@dev.com;P123456P -bot0502@dev.com;P123456P -bot0503@dev.com;P123456P -bot0504@dev.com;P123456P -bot0505@dev.com;P123456P -bot0506@dev.com;P123456P -bot0507@dev.com;P123456P -bot0508@dev.com;P123456P -bot0509@dev.com;P123456P -bot0510@dev.com;P123456P -bot0511@dev.com;P123456P -bot0512@dev.com;P123456P -bot0513@dev.com;P123456P -bot0514@dev.com;P123456P -bot0515@dev.com;P123456P -bot0516@dev.com;P123456P -bot0517@dev.com;P123456P -bot0518@dev.com;P123456P -bot0519@dev.com;P123456P -bot0520@dev.com;P123456P -bot0521@dev.com;P123456P -bot0522@dev.com;P123456P -bot0523@dev.com;P123456P -bot0524@dev.com;P123456P -bot0525@dev.com;P123456P -bot0526@dev.com;P123456P -bot0527@dev.com;P123456P -bot0528@dev.com;P123456P -bot0529@dev.com;P123456P -bot0530@dev.com;P123456P -bot0531@dev.com;P123456P -bot0532@dev.com;P123456P -bot0533@dev.com;P123456P -bot0534@dev.com;P123456P -bot0535@dev.com;P123456P -bot0536@dev.com;P123456P -bot0537@dev.com;P123456P -bot0538@dev.com;P123456P -bot0539@dev.com;P123456P -bot0540@dev.com;P123456P -bot0541@dev.com;P123456P -bot0542@dev.com;P123456P -bot0543@dev.com;P123456P -bot0544@dev.com;P123456P -bot0545@dev.com;P123456P -bot0546@dev.com;P123456P -bot0547@dev.com;P123456P -bot0548@dev.com;P123456P -bot0549@dev.com;P123456P -bot0550@dev.com;P123456P -bot0551@dev.com;P123456P -bot0552@dev.com;P123456P -bot0553@dev.com;P123456P -bot0554@dev.com;P123456P -bot0555@dev.com;P123456P -bot0556@dev.com;P123456P -bot0557@dev.com;P123456P -bot0558@dev.com;P123456P -bot0559@dev.com;P123456P -bot0560@dev.com;P123456P -bot0561@dev.com;P123456P -bot0562@dev.com;P123456P -bot0563@dev.com;P123456P -bot0564@dev.com;P123456P -bot0565@dev.com;P123456P -bot0566@dev.com;P123456P -bot0567@dev.com;P123456P -bot0568@dev.com;P123456P -bot0569@dev.com;P123456P -bot0570@dev.com;P123456P -bot0571@dev.com;P123456P -bot0572@dev.com;P123456P -bot0573@dev.com;P123456P -bot0574@dev.com;P123456P -bot0575@dev.com;P123456P -bot0576@dev.com;P123456P -bot0577@dev.com;P123456P -bot0578@dev.com;P123456P -bot0579@dev.com;P123456P -bot0580@dev.com;P123456P -bot0581@dev.com;P123456P -bot0582@dev.com;P123456P -bot0583@dev.com;P123456P -bot0584@dev.com;P123456P -bot0585@dev.com;P123456P -bot0586@dev.com;P123456P -bot0587@dev.com;P123456P -bot0588@dev.com;P123456P -bot0589@dev.com;P123456P -bot0590@dev.com;P123456P -bot0591@dev.com;P123456P -bot0592@dev.com;P123456P -bot0593@dev.com;P123456P -bot0594@dev.com;P123456P -bot0595@dev.com;P123456P -bot0596@dev.com;P123456P -bot0597@dev.com;P123456P -bot0598@dev.com;P123456P -bot0599@dev.com;P123456P -bot0600@dev.com;P123456P -bot0601@dev.com;P123456P -bot0602@dev.com;P123456P -bot0603@dev.com;P123456P -bot0604@dev.com;P123456P -bot0605@dev.com;P123456P -bot0606@dev.com;P123456P -bot0607@dev.com;P123456P -bot0608@dev.com;P123456P -bot0609@dev.com;P123456P -bot0610@dev.com;P123456P -bot0611@dev.com;P123456P -bot0612@dev.com;P123456P -bot0613@dev.com;P123456P -bot0614@dev.com;P123456P -bot0615@dev.com;P123456P -bot0616@dev.com;P123456P -bot0617@dev.com;P123456P -bot0618@dev.com;P123456P -bot0619@dev.com;P123456P -bot0620@dev.com;P123456P -bot0621@dev.com;P123456P -bot0622@dev.com;P123456P -bot0623@dev.com;P123456P -bot0624@dev.com;P123456P -bot0625@dev.com;P123456P -bot0626@dev.com;P123456P -bot0627@dev.com;P123456P -bot0628@dev.com;P123456P -bot0629@dev.com;P123456P -bot0630@dev.com;P123456P -bot0631@dev.com;P123456P -bot0632@dev.com;P123456P -bot0633@dev.com;P123456P -bot0634@dev.com;P123456P -bot0635@dev.com;P123456P -bot0636@dev.com;P123456P -bot0637@dev.com;P123456P -bot0638@dev.com;P123456P -bot0639@dev.com;P123456P -bot0640@dev.com;P123456P -bot0641@dev.com;P123456P -bot0642@dev.com;P123456P -bot0643@dev.com;P123456P -bot0644@dev.com;P123456P -bot0645@dev.com;P123456P -bot0646@dev.com;P123456P -bot0647@dev.com;P123456P -bot0648@dev.com;P123456P -bot0649@dev.com;P123456P -bot0650@dev.com;P123456P -bot0651@dev.com;P123456P -bot0652@dev.com;P123456P -bot0653@dev.com;P123456P -bot0654@dev.com;P123456P -bot0655@dev.com;P123456P -bot0656@dev.com;P123456P -bot0657@dev.com;P123456P -bot0658@dev.com;P123456P -bot0659@dev.com;P123456P -bot0660@dev.com;P123456P -bot0661@dev.com;P123456P -bot0662@dev.com;P123456P -bot0663@dev.com;P123456P -bot0664@dev.com;P123456P -bot0665@dev.com;P123456P -bot0666@dev.com;P123456P -bot0667@dev.com;P123456P -bot0668@dev.com;P123456P -bot0669@dev.com;P123456P -bot0670@dev.com;P123456P -bot0671@dev.com;P123456P -bot0672@dev.com;P123456P -bot0673@dev.com;P123456P -bot0674@dev.com;P123456P -bot0675@dev.com;P123456P -bot0676@dev.com;P123456P -bot0677@dev.com;P123456P -bot0678@dev.com;P123456P -bot0679@dev.com;P123456P -bot0680@dev.com;P123456P -bot0681@dev.com;P123456P -bot0682@dev.com;P123456P -bot0683@dev.com;P123456P -bot0684@dev.com;P123456P -bot0685@dev.com;P123456P -bot0686@dev.com;P123456P -bot0687@dev.com;P123456P -bot0688@dev.com;P123456P -bot0689@dev.com;P123456P -bot0690@dev.com;P123456P -bot0691@dev.com;P123456P -bot0692@dev.com;P123456P -bot0693@dev.com;P123456P -bot0694@dev.com;P123456P -bot0695@dev.com;P123456P -bot0696@dev.com;P123456P -bot0697@dev.com;P123456P -bot0698@dev.com;P123456P -bot0699@dev.com;P123456P -bot0700@dev.com;P123456P -bot0701@dev.com;P123456P -bot0702@dev.com;P123456P -bot0703@dev.com;P123456P -bot0704@dev.com;P123456P -bot0705@dev.com;P123456P -bot0706@dev.com;P123456P -bot0707@dev.com;P123456P -bot0708@dev.com;P123456P -bot0709@dev.com;P123456P -bot0710@dev.com;P123456P -bot0711@dev.com;P123456P -bot0712@dev.com;P123456P -bot0713@dev.com;P123456P -bot0714@dev.com;P123456P -bot0715@dev.com;P123456P -bot0716@dev.com;P123456P -bot0717@dev.com;P123456P -bot0718@dev.com;P123456P -bot0719@dev.com;P123456P -bot0720@dev.com;P123456P -bot0721@dev.com;P123456P -bot0722@dev.com;P123456P -bot0723@dev.com;P123456P -bot0724@dev.com;P123456P -bot0725@dev.com;P123456P -bot0726@dev.com;P123456P -bot0727@dev.com;P123456P -bot0728@dev.com;P123456P -bot0729@dev.com;P123456P -bot0730@dev.com;P123456P -bot0731@dev.com;P123456P -bot0732@dev.com;P123456P -bot0733@dev.com;P123456P -bot0734@dev.com;P123456P -bot0735@dev.com;P123456P -bot0736@dev.com;P123456P -bot0737@dev.com;P123456P -bot0738@dev.com;P123456P -bot0739@dev.com;P123456P -bot0740@dev.com;P123456P -bot0741@dev.com;P123456P -bot0742@dev.com;P123456P -bot0743@dev.com;P123456P -bot0744@dev.com;P123456P -bot0745@dev.com;P123456P -bot0746@dev.com;P123456P -bot0747@dev.com;P123456P -bot0748@dev.com;P123456P -bot0749@dev.com;P123456P -bot0750@dev.com;P123456P -bot0751@dev.com;P123456P -bot0752@dev.com;P123456P -bot0753@dev.com;P123456P -bot0754@dev.com;P123456P -bot0755@dev.com;P123456P -bot0756@dev.com;P123456P -bot0757@dev.com;P123456P -bot0758@dev.com;P123456P -bot0759@dev.com;P123456P -bot0760@dev.com;P123456P -bot0761@dev.com;P123456P -bot0762@dev.com;P123456P -bot0763@dev.com;P123456P -bot0764@dev.com;P123456P -bot0765@dev.com;P123456P -bot0766@dev.com;P123456P -bot0767@dev.com;P123456P -bot0768@dev.com;P123456P -bot0769@dev.com;P123456P -bot0770@dev.com;P123456P -bot0771@dev.com;P123456P -bot0772@dev.com;P123456P -bot0773@dev.com;P123456P -bot0774@dev.com;P123456P -bot0775@dev.com;P123456P -bot0776@dev.com;P123456P -bot0777@dev.com;P123456P -bot0778@dev.com;P123456P -bot0779@dev.com;P123456P -bot0780@dev.com;P123456P -bot0781@dev.com;P123456P -bot0782@dev.com;P123456P -bot0783@dev.com;P123456P -bot0784@dev.com;P123456P -bot0785@dev.com;P123456P -bot0786@dev.com;P123456P -bot0787@dev.com;P123456P -bot0788@dev.com;P123456P -bot0789@dev.com;P123456P -bot0790@dev.com;P123456P -bot0791@dev.com;P123456P -bot0792@dev.com;P123456P -bot0793@dev.com;P123456P -bot0794@dev.com;P123456P -bot0795@dev.com;P123456P -bot0796@dev.com;P123456P -bot0797@dev.com;P123456P -bot0798@dev.com;P123456P -bot0799@dev.com;P123456P -bot0800@dev.com;P123456P -bot0801@dev.com;P123456P -bot0802@dev.com;P123456P -bot0803@dev.com;P123456P -bot0804@dev.com;P123456P -bot0805@dev.com;P123456P -bot0806@dev.com;P123456P -bot0807@dev.com;P123456P -bot0808@dev.com;P123456P -bot0809@dev.com;P123456P -bot0810@dev.com;P123456P -bot0811@dev.com;P123456P -bot0812@dev.com;P123456P -bot0813@dev.com;P123456P -bot0814@dev.com;P123456P -bot0815@dev.com;P123456P -bot0816@dev.com;P123456P -bot0817@dev.com;P123456P -bot0818@dev.com;P123456P -bot0819@dev.com;P123456P -bot0820@dev.com;P123456P -bot0821@dev.com;P123456P -bot0822@dev.com;P123456P -bot0823@dev.com;P123456P -bot0824@dev.com;P123456P -bot0825@dev.com;P123456P -bot0826@dev.com;P123456P -bot0827@dev.com;P123456P -bot0828@dev.com;P123456P -bot0829@dev.com;P123456P -bot0830@dev.com;P123456P -bot0831@dev.com;P123456P -bot0832@dev.com;P123456P -bot0833@dev.com;P123456P -bot0834@dev.com;P123456P -bot0835@dev.com;P123456P -bot0836@dev.com;P123456P -bot0837@dev.com;P123456P -bot0838@dev.com;P123456P -bot0839@dev.com;P123456P -bot0840@dev.com;P123456P -bot0841@dev.com;P123456P -bot0842@dev.com;P123456P -bot0843@dev.com;P123456P -bot0844@dev.com;P123456P -bot0845@dev.com;P123456P -bot0846@dev.com;P123456P -bot0847@dev.com;P123456P -bot0848@dev.com;P123456P -bot0849@dev.com;P123456P -bot0850@dev.com;P123456P -bot0851@dev.com;P123456P -bot0852@dev.com;P123456P -bot0853@dev.com;P123456P -bot0854@dev.com;P123456P -bot0855@dev.com;P123456P -bot0856@dev.com;P123456P -bot0857@dev.com;P123456P -bot0858@dev.com;P123456P -bot0859@dev.com;P123456P -bot0860@dev.com;P123456P -bot0861@dev.com;P123456P -bot0862@dev.com;P123456P -bot0863@dev.com;P123456P -bot0864@dev.com;P123456P -bot0865@dev.com;P123456P -bot0866@dev.com;P123456P -bot0867@dev.com;P123456P -bot0868@dev.com;P123456P -bot0869@dev.com;P123456P -bot0870@dev.com;P123456P -bot0871@dev.com;P123456P -bot0872@dev.com;P123456P -bot0873@dev.com;P123456P -bot0874@dev.com;P123456P -bot0875@dev.com;P123456P -bot0876@dev.com;P123456P -bot0877@dev.com;P123456P -bot0878@dev.com;P123456P -bot0879@dev.com;P123456P -bot0880@dev.com;P123456P -bot0881@dev.com;P123456P -bot0882@dev.com;P123456P -bot0883@dev.com;P123456P -bot0884@dev.com;P123456P -bot0885@dev.com;P123456P -bot0886@dev.com;P123456P -bot0887@dev.com;P123456P -bot0888@dev.com;P123456P -bot0889@dev.com;P123456P -bot0890@dev.com;P123456P -bot0891@dev.com;P123456P -bot0892@dev.com;P123456P -bot0893@dev.com;P123456P -bot0894@dev.com;P123456P -bot0895@dev.com;P123456P -bot0896@dev.com;P123456P -bot0897@dev.com;P123456P -bot0898@dev.com;P123456P -bot0899@dev.com;P123456P -bot0900@dev.com;P123456P -bot0901@dev.com;P123456P -bot0902@dev.com;P123456P -bot0903@dev.com;P123456P -bot0904@dev.com;P123456P -bot0905@dev.com;P123456P -bot0906@dev.com;P123456P -bot0907@dev.com;P123456P -bot0908@dev.com;P123456P -bot0909@dev.com;P123456P -bot0910@dev.com;P123456P -bot0911@dev.com;P123456P -bot0912@dev.com;P123456P -bot0913@dev.com;P123456P -bot0914@dev.com;P123456P -bot0915@dev.com;P123456P -bot0916@dev.com;P123456P -bot0917@dev.com;P123456P -bot0918@dev.com;P123456P -bot0919@dev.com;P123456P -bot0920@dev.com;P123456P -bot0921@dev.com;P123456P -bot0922@dev.com;P123456P -bot0923@dev.com;P123456P -bot0924@dev.com;P123456P -bot0925@dev.com;P123456P -bot0926@dev.com;P123456P -bot0927@dev.com;P123456P -bot0928@dev.com;P123456P -bot0929@dev.com;P123456P -bot0930@dev.com;P123456P -bot0931@dev.com;P123456P -bot0932@dev.com;P123456P -bot0933@dev.com;P123456P -bot0934@dev.com;P123456P -bot0935@dev.com;P123456P -bot0936@dev.com;P123456P -bot0937@dev.com;P123456P -bot0938@dev.com;P123456P -bot0939@dev.com;P123456P -bot0940@dev.com;P123456P -bot0941@dev.com;P123456P -bot0942@dev.com;P123456P -bot0943@dev.com;P123456P -bot0944@dev.com;P123456P -bot0945@dev.com;P123456P -bot0946@dev.com;P123456P -bot0947@dev.com;P123456P -bot0948@dev.com;P123456P -bot0949@dev.com;P123456P -bot0950@dev.com;P123456P -bot0951@dev.com;P123456P -bot0952@dev.com;P123456P -bot0953@dev.com;P123456P -bot0954@dev.com;P123456P -bot0955@dev.com;P123456P -bot0956@dev.com;P123456P -bot0957@dev.com;P123456P -bot0958@dev.com;P123456P -bot0959@dev.com;P123456P -bot0960@dev.com;P123456P -bot0961@dev.com;P123456P -bot0962@dev.com;P123456P -bot0963@dev.com;P123456P -bot0964@dev.com;P123456P -bot0965@dev.com;P123456P -bot0966@dev.com;P123456P -bot0967@dev.com;P123456P -bot0968@dev.com;P123456P -bot0969@dev.com;P123456P -bot0970@dev.com;P123456P -bot0971@dev.com;P123456P -bot0972@dev.com;P123456P -bot0973@dev.com;P123456P -bot0974@dev.com;P123456P -bot0975@dev.com;P123456P -bot0976@dev.com;P123456P -bot0977@dev.com;P123456P -bot0978@dev.com;P123456P -bot0979@dev.com;P123456P -bot0980@dev.com;P123456P -bot0981@dev.com;P123456P -bot0982@dev.com;P123456P -bot0983@dev.com;P123456P -bot0984@dev.com;P123456P -bot0985@dev.com;P123456P -bot0986@dev.com;P123456P -bot0987@dev.com;P123456P -bot0988@dev.com;P123456P -bot0989@dev.com;P123456P -bot0990@dev.com;P123456P -bot0991@dev.com;P123456P -bot0992@dev.com;P123456P -bot0993@dev.com;P123456P -bot0994@dev.com;P123456P -bot0995@dev.com;P123456P -bot0996@dev.com;P123456P -bot0997@dev.com;P123456P -bot0998@dev.com;P123456P -bot0999@dev.com;P123456P -bot1000@dev.com;P123456P \ No newline at end of file diff --git a/src/MarginTrading.Client/testUsers.csv b/src/MarginTrading.Client/testUsers.csv deleted file mode 100644 index 6d3a3d6e0..000000000 --- a/src/MarginTrading.Client/testUsers.csv +++ /dev/null @@ -1,1001 +0,0 @@ -Email;Password -bot0001@dev.com;P123456P -bot0002@dev.com;P123456P -bot0003@dev.com;P123456P -bot0004@dev.com;P123456P -bot0005@dev.com;P123456P -bot0006@dev.com;P123456P -bot0007@dev.com;P123456P -bot0008@dev.com;P123456P -bot0009@dev.com;P123456P -bot0010@dev.com;P123456P -bot0011@dev.com;P123456P -bot0012@dev.com;P123456P -bot0013@dev.com;P123456P -bot0014@dev.com;P123456P -bot0015@dev.com;P123456P -bot0016@dev.com;P123456P -bot0017@dev.com;P123456P -bot0018@dev.com;P123456P -bot0019@dev.com;P123456P -bot0020@dev.com;P123456P -bot0021@dev.com;P123456P -bot0022@dev.com;P123456P -bot0023@dev.com;P123456P -bot0024@dev.com;P123456P -bot0025@dev.com;P123456P -bot0026@dev.com;P123456P -bot0027@dev.com;P123456P -bot0028@dev.com;P123456P -bot0029@dev.com;P123456P -bot0030@dev.com;P123456P -bot0031@dev.com;P123456P -bot0032@dev.com;P123456P -bot0033@dev.com;P123456P -bot0034@dev.com;P123456P -bot0035@dev.com;P123456P -bot0036@dev.com;P123456P -bot0037@dev.com;P123456P -bot0038@dev.com;P123456P -bot0039@dev.com;P123456P -bot0040@dev.com;P123456P -bot0041@dev.com;P123456P -bot0042@dev.com;P123456P -bot0043@dev.com;P123456P -bot0044@dev.com;P123456P -bot0045@dev.com;P123456P -bot0046@dev.com;P123456P -bot0047@dev.com;P123456P -bot0048@dev.com;P123456P -bot0049@dev.com;P123456P -bot0050@dev.com;P123456P -bot0051@dev.com;P123456P -bot0052@dev.com;P123456P -bot0053@dev.com;P123456P -bot0054@dev.com;P123456P -bot0055@dev.com;P123456P -bot0056@dev.com;P123456P -bot0057@dev.com;P123456P -bot0058@dev.com;P123456P -bot0059@dev.com;P123456P -bot0060@dev.com;P123456P -bot0061@dev.com;P123456P -bot0062@dev.com;P123456P -bot0063@dev.com;P123456P -bot0064@dev.com;P123456P -bot0065@dev.com;P123456P -bot0066@dev.com;P123456P -bot0067@dev.com;P123456P -bot0068@dev.com;P123456P -bot0069@dev.com;P123456P -bot0070@dev.com;P123456P -bot0071@dev.com;P123456P -bot0072@dev.com;P123456P -bot0073@dev.com;P123456P -bot0074@dev.com;P123456P -bot0075@dev.com;P123456P -bot0076@dev.com;P123456P -bot0077@dev.com;P123456P -bot0078@dev.com;P123456P -bot0079@dev.com;P123456P -bot0080@dev.com;P123456P -bot0081@dev.com;P123456P -bot0082@dev.com;P123456P -bot0083@dev.com;P123456P -bot0084@dev.com;P123456P -bot0085@dev.com;P123456P -bot0086@dev.com;P123456P -bot0087@dev.com;P123456P -bot0088@dev.com;P123456P -bot0089@dev.com;P123456P -bot0090@dev.com;P123456P -bot0091@dev.com;P123456P -bot0092@dev.com;P123456P -bot0093@dev.com;P123456P -bot0094@dev.com;P123456P -bot0095@dev.com;P123456P -bot0096@dev.com;P123456P -bot0097@dev.com;P123456P -bot0098@dev.com;P123456P -bot0099@dev.com;P123456P -bot0100@dev.com;P123456P -bot0101@dev.com;P123456P -bot0102@dev.com;P123456P -bot0103@dev.com;P123456P -bot0104@dev.com;P123456P -bot0105@dev.com;P123456P -bot0106@dev.com;P123456P -bot0107@dev.com;P123456P -bot0108@dev.com;P123456P -bot0109@dev.com;P123456P -bot0110@dev.com;P123456P -bot0111@dev.com;P123456P -bot0112@dev.com;P123456P -bot0113@dev.com;P123456P -bot0114@dev.com;P123456P -bot0115@dev.com;P123456P -bot0116@dev.com;P123456P -bot0117@dev.com;P123456P -bot0118@dev.com;P123456P -bot0119@dev.com;P123456P -bot0120@dev.com;P123456P -bot0121@dev.com;P123456P -bot0122@dev.com;P123456P -bot0123@dev.com;P123456P -bot0124@dev.com;P123456P -bot0125@dev.com;P123456P -bot0126@dev.com;P123456P -bot0127@dev.com;P123456P -bot0128@dev.com;P123456P -bot0129@dev.com;P123456P -bot0130@dev.com;P123456P -bot0131@dev.com;P123456P -bot0132@dev.com;P123456P -bot0133@dev.com;P123456P -bot0134@dev.com;P123456P -bot0135@dev.com;P123456P -bot0136@dev.com;P123456P -bot0137@dev.com;P123456P -bot0138@dev.com;P123456P -bot0139@dev.com;P123456P -bot0140@dev.com;P123456P -bot0141@dev.com;P123456P -bot0142@dev.com;P123456P -bot0143@dev.com;P123456P -bot0144@dev.com;P123456P -bot0145@dev.com;P123456P -bot0146@dev.com;P123456P -bot0147@dev.com;P123456P -bot0148@dev.com;P123456P -bot0149@dev.com;P123456P -bot0150@dev.com;P123456P -bot0151@dev.com;P123456P -bot0152@dev.com;P123456P -bot0153@dev.com;P123456P -bot0154@dev.com;P123456P -bot0155@dev.com;P123456P -bot0156@dev.com;P123456P -bot0157@dev.com;P123456P -bot0158@dev.com;P123456P -bot0159@dev.com;P123456P -bot0160@dev.com;P123456P -bot0161@dev.com;P123456P -bot0162@dev.com;P123456P -bot0163@dev.com;P123456P -bot0164@dev.com;P123456P -bot0165@dev.com;P123456P -bot0166@dev.com;P123456P -bot0167@dev.com;P123456P -bot0168@dev.com;P123456P -bot0169@dev.com;P123456P -bot0170@dev.com;P123456P -bot0171@dev.com;P123456P -bot0172@dev.com;P123456P -bot0173@dev.com;P123456P -bot0174@dev.com;P123456P -bot0175@dev.com;P123456P -bot0176@dev.com;P123456P -bot0177@dev.com;P123456P -bot0178@dev.com;P123456P -bot0179@dev.com;P123456P -bot0180@dev.com;P123456P -bot0181@dev.com;P123456P -bot0182@dev.com;P123456P -bot0183@dev.com;P123456P -bot0184@dev.com;P123456P -bot0185@dev.com;P123456P -bot0186@dev.com;P123456P -bot0187@dev.com;P123456P -bot0188@dev.com;P123456P -bot0189@dev.com;P123456P -bot0190@dev.com;P123456P -bot0191@dev.com;P123456P -bot0192@dev.com;P123456P -bot0193@dev.com;P123456P -bot0194@dev.com;P123456P -bot0195@dev.com;P123456P -bot0196@dev.com;P123456P -bot0197@dev.com;P123456P -bot0198@dev.com;P123456P -bot0199@dev.com;P123456P -bot0200@dev.com;P123456P -bot0201@dev.com;P123456P -bot0202@dev.com;P123456P -bot0203@dev.com;P123456P -bot0204@dev.com;P123456P -bot0205@dev.com;P123456P -bot0206@dev.com;P123456P -bot0207@dev.com;P123456P -bot0208@dev.com;P123456P -bot0209@dev.com;P123456P -bot0210@dev.com;P123456P -bot0211@dev.com;P123456P -bot0212@dev.com;P123456P -bot0213@dev.com;P123456P -bot0214@dev.com;P123456P -bot0215@dev.com;P123456P -bot0216@dev.com;P123456P -bot0217@dev.com;P123456P -bot0218@dev.com;P123456P -bot0219@dev.com;P123456P -bot0220@dev.com;P123456P -bot0221@dev.com;P123456P -bot0222@dev.com;P123456P -bot0223@dev.com;P123456P -bot0224@dev.com;P123456P -bot0225@dev.com;P123456P -bot0226@dev.com;P123456P -bot0227@dev.com;P123456P -bot0228@dev.com;P123456P -bot0229@dev.com;P123456P -bot0230@dev.com;P123456P -bot0231@dev.com;P123456P -bot0232@dev.com;P123456P -bot0233@dev.com;P123456P -bot0234@dev.com;P123456P -bot0235@dev.com;P123456P -bot0236@dev.com;P123456P -bot0237@dev.com;P123456P -bot0238@dev.com;P123456P -bot0239@dev.com;P123456P -bot0240@dev.com;P123456P -bot0241@dev.com;P123456P -bot0242@dev.com;P123456P -bot0243@dev.com;P123456P -bot0244@dev.com;P123456P -bot0245@dev.com;P123456P -bot0246@dev.com;P123456P -bot0247@dev.com;P123456P -bot0248@dev.com;P123456P -bot0249@dev.com;P123456P -bot0250@dev.com;P123456P -bot0251@dev.com;P123456P -bot0252@dev.com;P123456P -bot0253@dev.com;P123456P -bot0254@dev.com;P123456P -bot0255@dev.com;P123456P -bot0256@dev.com;P123456P -bot0257@dev.com;P123456P -bot0258@dev.com;P123456P -bot0259@dev.com;P123456P -bot0260@dev.com;P123456P -bot0261@dev.com;P123456P -bot0262@dev.com;P123456P -bot0263@dev.com;P123456P -bot0264@dev.com;P123456P -bot0265@dev.com;P123456P -bot0266@dev.com;P123456P -bot0267@dev.com;P123456P -bot0268@dev.com;P123456P -bot0269@dev.com;P123456P -bot0270@dev.com;P123456P -bot0271@dev.com;P123456P -bot0272@dev.com;P123456P -bot0273@dev.com;P123456P -bot0274@dev.com;P123456P -bot0275@dev.com;P123456P -bot0276@dev.com;P123456P -bot0277@dev.com;P123456P -bot0278@dev.com;P123456P -bot0279@dev.com;P123456P -bot0280@dev.com;P123456P -bot0281@dev.com;P123456P -bot0282@dev.com;P123456P -bot0283@dev.com;P123456P -bot0284@dev.com;P123456P -bot0285@dev.com;P123456P -bot0286@dev.com;P123456P -bot0287@dev.com;P123456P -bot0288@dev.com;P123456P -bot0289@dev.com;P123456P -bot0290@dev.com;P123456P -bot0291@dev.com;P123456P -bot0292@dev.com;P123456P -bot0293@dev.com;P123456P -bot0294@dev.com;P123456P -bot0295@dev.com;P123456P -bot0296@dev.com;P123456P -bot0297@dev.com;P123456P -bot0298@dev.com;P123456P -bot0299@dev.com;P123456P -bot0300@dev.com;P123456P -bot0301@dev.com;P123456P -bot0302@dev.com;P123456P -bot0303@dev.com;P123456P -bot0304@dev.com;P123456P -bot0305@dev.com;P123456P -bot0306@dev.com;P123456P -bot0307@dev.com;P123456P -bot0308@dev.com;P123456P -bot0309@dev.com;P123456P -bot0310@dev.com;P123456P -bot0311@dev.com;P123456P -bot0312@dev.com;P123456P -bot0313@dev.com;P123456P -bot0314@dev.com;P123456P -bot0315@dev.com;P123456P -bot0316@dev.com;P123456P -bot0317@dev.com;P123456P -bot0318@dev.com;P123456P -bot0319@dev.com;P123456P -bot0320@dev.com;P123456P -bot0321@dev.com;P123456P -bot0322@dev.com;P123456P -bot0323@dev.com;P123456P -bot0324@dev.com;P123456P -bot0325@dev.com;P123456P -bot0326@dev.com;P123456P -bot0327@dev.com;P123456P -bot0328@dev.com;P123456P -bot0329@dev.com;P123456P -bot0330@dev.com;P123456P -bot0331@dev.com;P123456P -bot0332@dev.com;P123456P -bot0333@dev.com;P123456P -bot0334@dev.com;P123456P -bot0335@dev.com;P123456P -bot0336@dev.com;P123456P -bot0337@dev.com;P123456P -bot0338@dev.com;P123456P -bot0339@dev.com;P123456P -bot0340@dev.com;P123456P -bot0341@dev.com;P123456P -bot0342@dev.com;P123456P -bot0343@dev.com;P123456P -bot0344@dev.com;P123456P -bot0345@dev.com;P123456P -bot0346@dev.com;P123456P -bot0347@dev.com;P123456P -bot0348@dev.com;P123456P -bot0349@dev.com;P123456P -bot0350@dev.com;P123456P -bot0351@dev.com;P123456P -bot0352@dev.com;P123456P -bot0353@dev.com;P123456P -bot0354@dev.com;P123456P -bot0355@dev.com;P123456P -bot0356@dev.com;P123456P -bot0357@dev.com;P123456P -bot0358@dev.com;P123456P -bot0359@dev.com;P123456P -bot0360@dev.com;P123456P -bot0361@dev.com;P123456P -bot0362@dev.com;P123456P -bot0363@dev.com;P123456P -bot0364@dev.com;P123456P -bot0365@dev.com;P123456P -bot0366@dev.com;P123456P -bot0367@dev.com;P123456P -bot0368@dev.com;P123456P -bot0369@dev.com;P123456P -bot0370@dev.com;P123456P -bot0371@dev.com;P123456P -bot0372@dev.com;P123456P -bot0373@dev.com;P123456P -bot0374@dev.com;P123456P -bot0375@dev.com;P123456P -bot0376@dev.com;P123456P -bot0377@dev.com;P123456P -bot0378@dev.com;P123456P -bot0379@dev.com;P123456P -bot0380@dev.com;P123456P -bot0381@dev.com;P123456P -bot0382@dev.com;P123456P -bot0383@dev.com;P123456P -bot0384@dev.com;P123456P -bot0385@dev.com;P123456P -bot0386@dev.com;P123456P -bot0387@dev.com;P123456P -bot0388@dev.com;P123456P -bot0389@dev.com;P123456P -bot0390@dev.com;P123456P -bot0391@dev.com;P123456P -bot0392@dev.com;P123456P -bot0393@dev.com;P123456P -bot0394@dev.com;P123456P -bot0395@dev.com;P123456P -bot0396@dev.com;P123456P -bot0397@dev.com;P123456P -bot0398@dev.com;P123456P -bot0399@dev.com;P123456P -bot0400@dev.com;P123456P -bot0401@dev.com;P123456P -bot0402@dev.com;P123456P -bot0403@dev.com;P123456P -bot0404@dev.com;P123456P -bot0405@dev.com;P123456P -bot0406@dev.com;P123456P -bot0407@dev.com;P123456P -bot0408@dev.com;P123456P -bot0409@dev.com;P123456P -bot0410@dev.com;P123456P -bot0411@dev.com;P123456P -bot0412@dev.com;P123456P -bot0413@dev.com;P123456P -bot0414@dev.com;P123456P -bot0415@dev.com;P123456P -bot0416@dev.com;P123456P -bot0417@dev.com;P123456P -bot0418@dev.com;P123456P -bot0419@dev.com;P123456P -bot0420@dev.com;P123456P -bot0421@dev.com;P123456P -bot0422@dev.com;P123456P -bot0423@dev.com;P123456P -bot0424@dev.com;P123456P -bot0425@dev.com;P123456P -bot0426@dev.com;P123456P -bot0427@dev.com;P123456P -bot0428@dev.com;P123456P -bot0429@dev.com;P123456P -bot0430@dev.com;P123456P -bot0431@dev.com;P123456P -bot0432@dev.com;P123456P -bot0433@dev.com;P123456P -bot0434@dev.com;P123456P -bot0435@dev.com;P123456P -bot0436@dev.com;P123456P -bot0437@dev.com;P123456P -bot0438@dev.com;P123456P -bot0439@dev.com;P123456P -bot0440@dev.com;P123456P -bot0441@dev.com;P123456P -bot0442@dev.com;P123456P -bot0443@dev.com;P123456P -bot0444@dev.com;P123456P -bot0445@dev.com;P123456P -bot0446@dev.com;P123456P -bot0447@dev.com;P123456P -bot0448@dev.com;P123456P -bot0449@dev.com;P123456P -bot0450@dev.com;P123456P -bot0451@dev.com;P123456P -bot0452@dev.com;P123456P -bot0453@dev.com;P123456P -bot0454@dev.com;P123456P -bot0455@dev.com;P123456P -bot0456@dev.com;P123456P -bot0457@dev.com;P123456P -bot0458@dev.com;P123456P -bot0459@dev.com;P123456P -bot0460@dev.com;P123456P -bot0461@dev.com;P123456P -bot0462@dev.com;P123456P -bot0463@dev.com;P123456P -bot0464@dev.com;P123456P -bot0465@dev.com;P123456P -bot0466@dev.com;P123456P -bot0467@dev.com;P123456P -bot0468@dev.com;P123456P -bot0469@dev.com;P123456P -bot0470@dev.com;P123456P -bot0471@dev.com;P123456P -bot0472@dev.com;P123456P -bot0473@dev.com;P123456P -bot0474@dev.com;P123456P -bot0475@dev.com;P123456P -bot0476@dev.com;P123456P -bot0477@dev.com;P123456P -bot0478@dev.com;P123456P -bot0479@dev.com;P123456P -bot0480@dev.com;P123456P -bot0481@dev.com;P123456P -bot0482@dev.com;P123456P -bot0483@dev.com;P123456P -bot0484@dev.com;P123456P -bot0485@dev.com;P123456P -bot0486@dev.com;P123456P -bot0487@dev.com;P123456P -bot0488@dev.com;P123456P -bot0489@dev.com;P123456P -bot0490@dev.com;P123456P -bot0491@dev.com;P123456P -bot0492@dev.com;P123456P -bot0493@dev.com;P123456P -bot0494@dev.com;P123456P -bot0495@dev.com;P123456P -bot0496@dev.com;P123456P -bot0497@dev.com;P123456P -bot0498@dev.com;P123456P -bot0499@dev.com;P123456P -bot0500@dev.com;P123456P -bot0501@dev.com;P123456P -bot0502@dev.com;P123456P -bot0503@dev.com;P123456P -bot0504@dev.com;P123456P -bot0505@dev.com;P123456P -bot0506@dev.com;P123456P -bot0507@dev.com;P123456P -bot0508@dev.com;P123456P -bot0509@dev.com;P123456P -bot0510@dev.com;P123456P -bot0511@dev.com;P123456P -bot0512@dev.com;P123456P -bot0513@dev.com;P123456P -bot0514@dev.com;P123456P -bot0515@dev.com;P123456P -bot0516@dev.com;P123456P -bot0517@dev.com;P123456P -bot0518@dev.com;P123456P -bot0519@dev.com;P123456P -bot0520@dev.com;P123456P -bot0521@dev.com;P123456P -bot0522@dev.com;P123456P -bot0523@dev.com;P123456P -bot0524@dev.com;P123456P -bot0525@dev.com;P123456P -bot0526@dev.com;P123456P -bot0527@dev.com;P123456P -bot0528@dev.com;P123456P -bot0529@dev.com;P123456P -bot0530@dev.com;P123456P -bot0531@dev.com;P123456P -bot0532@dev.com;P123456P -bot0533@dev.com;P123456P -bot0534@dev.com;P123456P -bot0535@dev.com;P123456P -bot0536@dev.com;P123456P -bot0537@dev.com;P123456P -bot0538@dev.com;P123456P -bot0539@dev.com;P123456P -bot0540@dev.com;P123456P -bot0541@dev.com;P123456P -bot0542@dev.com;P123456P -bot0543@dev.com;P123456P -bot0544@dev.com;P123456P -bot0545@dev.com;P123456P -bot0546@dev.com;P123456P -bot0547@dev.com;P123456P -bot0548@dev.com;P123456P -bot0549@dev.com;P123456P -bot0550@dev.com;P123456P -bot0551@dev.com;P123456P -bot0552@dev.com;P123456P -bot0553@dev.com;P123456P -bot0554@dev.com;P123456P -bot0555@dev.com;P123456P -bot0556@dev.com;P123456P -bot0557@dev.com;P123456P -bot0558@dev.com;P123456P -bot0559@dev.com;P123456P -bot0560@dev.com;P123456P -bot0561@dev.com;P123456P -bot0562@dev.com;P123456P -bot0563@dev.com;P123456P -bot0564@dev.com;P123456P -bot0565@dev.com;P123456P -bot0566@dev.com;P123456P -bot0567@dev.com;P123456P -bot0568@dev.com;P123456P -bot0569@dev.com;P123456P -bot0570@dev.com;P123456P -bot0571@dev.com;P123456P -bot0572@dev.com;P123456P -bot0573@dev.com;P123456P -bot0574@dev.com;P123456P -bot0575@dev.com;P123456P -bot0576@dev.com;P123456P -bot0577@dev.com;P123456P -bot0578@dev.com;P123456P -bot0579@dev.com;P123456P -bot0580@dev.com;P123456P -bot0581@dev.com;P123456P -bot0582@dev.com;P123456P -bot0583@dev.com;P123456P -bot0584@dev.com;P123456P -bot0585@dev.com;P123456P -bot0586@dev.com;P123456P -bot0587@dev.com;P123456P -bot0588@dev.com;P123456P -bot0589@dev.com;P123456P -bot0590@dev.com;P123456P -bot0591@dev.com;P123456P -bot0592@dev.com;P123456P -bot0593@dev.com;P123456P -bot0594@dev.com;P123456P -bot0595@dev.com;P123456P -bot0596@dev.com;P123456P -bot0597@dev.com;P123456P -bot0598@dev.com;P123456P -bot0599@dev.com;P123456P -bot0600@dev.com;P123456P -bot0601@dev.com;P123456P -bot0602@dev.com;P123456P -bot0603@dev.com;P123456P -bot0604@dev.com;P123456P -bot0605@dev.com;P123456P -bot0606@dev.com;P123456P -bot0607@dev.com;P123456P -bot0608@dev.com;P123456P -bot0609@dev.com;P123456P -bot0610@dev.com;P123456P -bot0611@dev.com;P123456P -bot0612@dev.com;P123456P -bot0613@dev.com;P123456P -bot0614@dev.com;P123456P -bot0615@dev.com;P123456P -bot0616@dev.com;P123456P -bot0617@dev.com;P123456P -bot0618@dev.com;P123456P -bot0619@dev.com;P123456P -bot0620@dev.com;P123456P -bot0621@dev.com;P123456P -bot0622@dev.com;P123456P -bot0623@dev.com;P123456P -bot0624@dev.com;P123456P -bot0625@dev.com;P123456P -bot0626@dev.com;P123456P -bot0627@dev.com;P123456P -bot0628@dev.com;P123456P -bot0629@dev.com;P123456P -bot0630@dev.com;P123456P -bot0631@dev.com;P123456P -bot0632@dev.com;P123456P -bot0633@dev.com;P123456P -bot0634@dev.com;P123456P -bot0635@dev.com;P123456P -bot0636@dev.com;P123456P -bot0637@dev.com;P123456P -bot0638@dev.com;P123456P -bot0639@dev.com;P123456P -bot0640@dev.com;P123456P -bot0641@dev.com;P123456P -bot0642@dev.com;P123456P -bot0643@dev.com;P123456P -bot0644@dev.com;P123456P -bot0645@dev.com;P123456P -bot0646@dev.com;P123456P -bot0647@dev.com;P123456P -bot0648@dev.com;P123456P -bot0649@dev.com;P123456P -bot0650@dev.com;P123456P -bot0651@dev.com;P123456P -bot0652@dev.com;P123456P -bot0653@dev.com;P123456P -bot0654@dev.com;P123456P -bot0655@dev.com;P123456P -bot0656@dev.com;P123456P -bot0657@dev.com;P123456P -bot0658@dev.com;P123456P -bot0659@dev.com;P123456P -bot0660@dev.com;P123456P -bot0661@dev.com;P123456P -bot0662@dev.com;P123456P -bot0663@dev.com;P123456P -bot0664@dev.com;P123456P -bot0665@dev.com;P123456P -bot0666@dev.com;P123456P -bot0667@dev.com;P123456P -bot0668@dev.com;P123456P -bot0669@dev.com;P123456P -bot0670@dev.com;P123456P -bot0671@dev.com;P123456P -bot0672@dev.com;P123456P -bot0673@dev.com;P123456P -bot0674@dev.com;P123456P -bot0675@dev.com;P123456P -bot0676@dev.com;P123456P -bot0677@dev.com;P123456P -bot0678@dev.com;P123456P -bot0679@dev.com;P123456P -bot0680@dev.com;P123456P -bot0681@dev.com;P123456P -bot0682@dev.com;P123456P -bot0683@dev.com;P123456P -bot0684@dev.com;P123456P -bot0685@dev.com;P123456P -bot0686@dev.com;P123456P -bot0687@dev.com;P123456P -bot0688@dev.com;P123456P -bot0689@dev.com;P123456P -bot0690@dev.com;P123456P -bot0691@dev.com;P123456P -bot0692@dev.com;P123456P -bot0693@dev.com;P123456P -bot0694@dev.com;P123456P -bot0695@dev.com;P123456P -bot0696@dev.com;P123456P -bot0697@dev.com;P123456P -bot0698@dev.com;P123456P -bot0699@dev.com;P123456P -bot0700@dev.com;P123456P -bot0701@dev.com;P123456P -bot0702@dev.com;P123456P -bot0703@dev.com;P123456P -bot0704@dev.com;P123456P -bot0705@dev.com;P123456P -bot0706@dev.com;P123456P -bot0707@dev.com;P123456P -bot0708@dev.com;P123456P -bot0709@dev.com;P123456P -bot0710@dev.com;P123456P -bot0711@dev.com;P123456P -bot0712@dev.com;P123456P -bot0713@dev.com;P123456P -bot0714@dev.com;P123456P -bot0715@dev.com;P123456P -bot0716@dev.com;P123456P -bot0717@dev.com;P123456P -bot0718@dev.com;P123456P -bot0719@dev.com;P123456P -bot0720@dev.com;P123456P -bot0721@dev.com;P123456P -bot0722@dev.com;P123456P -bot0723@dev.com;P123456P -bot0724@dev.com;P123456P -bot0725@dev.com;P123456P -bot0726@dev.com;P123456P -bot0727@dev.com;P123456P -bot0728@dev.com;P123456P -bot0729@dev.com;P123456P -bot0730@dev.com;P123456P -bot0731@dev.com;P123456P -bot0732@dev.com;P123456P -bot0733@dev.com;P123456P -bot0734@dev.com;P123456P -bot0735@dev.com;P123456P -bot0736@dev.com;P123456P -bot0737@dev.com;P123456P -bot0738@dev.com;P123456P -bot0739@dev.com;P123456P -bot0740@dev.com;P123456P -bot0741@dev.com;P123456P -bot0742@dev.com;P123456P -bot0743@dev.com;P123456P -bot0744@dev.com;P123456P -bot0745@dev.com;P123456P -bot0746@dev.com;P123456P -bot0747@dev.com;P123456P -bot0748@dev.com;P123456P -bot0749@dev.com;P123456P -bot0750@dev.com;P123456P -bot0751@dev.com;P123456P -bot0752@dev.com;P123456P -bot0753@dev.com;P123456P -bot0754@dev.com;P123456P -bot0755@dev.com;P123456P -bot0756@dev.com;P123456P -bot0757@dev.com;P123456P -bot0758@dev.com;P123456P -bot0759@dev.com;P123456P -bot0760@dev.com;P123456P -bot0761@dev.com;P123456P -bot0762@dev.com;P123456P -bot0763@dev.com;P123456P -bot0764@dev.com;P123456P -bot0765@dev.com;P123456P -bot0766@dev.com;P123456P -bot0767@dev.com;P123456P -bot0768@dev.com;P123456P -bot0769@dev.com;P123456P -bot0770@dev.com;P123456P -bot0771@dev.com;P123456P -bot0772@dev.com;P123456P -bot0773@dev.com;P123456P -bot0774@dev.com;P123456P -bot0775@dev.com;P123456P -bot0776@dev.com;P123456P -bot0777@dev.com;P123456P -bot0778@dev.com;P123456P -bot0779@dev.com;P123456P -bot0780@dev.com;P123456P -bot0781@dev.com;P123456P -bot0782@dev.com;P123456P -bot0783@dev.com;P123456P -bot0784@dev.com;P123456P -bot0785@dev.com;P123456P -bot0786@dev.com;P123456P -bot0787@dev.com;P123456P -bot0788@dev.com;P123456P -bot0789@dev.com;P123456P -bot0790@dev.com;P123456P -bot0791@dev.com;P123456P -bot0792@dev.com;P123456P -bot0793@dev.com;P123456P -bot0794@dev.com;P123456P -bot0795@dev.com;P123456P -bot0796@dev.com;P123456P -bot0797@dev.com;P123456P -bot0798@dev.com;P123456P -bot0799@dev.com;P123456P -bot0800@dev.com;P123456P -bot0801@dev.com;P123456P -bot0802@dev.com;P123456P -bot0803@dev.com;P123456P -bot0804@dev.com;P123456P -bot0805@dev.com;P123456P -bot0806@dev.com;P123456P -bot0807@dev.com;P123456P -bot0808@dev.com;P123456P -bot0809@dev.com;P123456P -bot0810@dev.com;P123456P -bot0811@dev.com;P123456P -bot0812@dev.com;P123456P -bot0813@dev.com;P123456P -bot0814@dev.com;P123456P -bot0815@dev.com;P123456P -bot0816@dev.com;P123456P -bot0817@dev.com;P123456P -bot0818@dev.com;P123456P -bot0819@dev.com;P123456P -bot0820@dev.com;P123456P -bot0821@dev.com;P123456P -bot0822@dev.com;P123456P -bot0823@dev.com;P123456P -bot0824@dev.com;P123456P -bot0825@dev.com;P123456P -bot0826@dev.com;P123456P -bot0827@dev.com;P123456P -bot0828@dev.com;P123456P -bot0829@dev.com;P123456P -bot0830@dev.com;P123456P -bot0831@dev.com;P123456P -bot0832@dev.com;P123456P -bot0833@dev.com;P123456P -bot0834@dev.com;P123456P -bot0835@dev.com;P123456P -bot0836@dev.com;P123456P -bot0837@dev.com;P123456P -bot0838@dev.com;P123456P -bot0839@dev.com;P123456P -bot0840@dev.com;P123456P -bot0841@dev.com;P123456P -bot0842@dev.com;P123456P -bot0843@dev.com;P123456P -bot0844@dev.com;P123456P -bot0845@dev.com;P123456P -bot0846@dev.com;P123456P -bot0847@dev.com;P123456P -bot0848@dev.com;P123456P -bot0849@dev.com;P123456P -bot0850@dev.com;P123456P -bot0851@dev.com;P123456P -bot0852@dev.com;P123456P -bot0853@dev.com;P123456P -bot0854@dev.com;P123456P -bot0855@dev.com;P123456P -bot0856@dev.com;P123456P -bot0857@dev.com;P123456P -bot0858@dev.com;P123456P -bot0859@dev.com;P123456P -bot0860@dev.com;P123456P -bot0861@dev.com;P123456P -bot0862@dev.com;P123456P -bot0863@dev.com;P123456P -bot0864@dev.com;P123456P -bot0865@dev.com;P123456P -bot0866@dev.com;P123456P -bot0867@dev.com;P123456P -bot0868@dev.com;P123456P -bot0869@dev.com;P123456P -bot0870@dev.com;P123456P -bot0871@dev.com;P123456P -bot0872@dev.com;P123456P -bot0873@dev.com;P123456P -bot0874@dev.com;P123456P -bot0875@dev.com;P123456P -bot0876@dev.com;P123456P -bot0877@dev.com;P123456P -bot0878@dev.com;P123456P -bot0879@dev.com;P123456P -bot0880@dev.com;P123456P -bot0881@dev.com;P123456P -bot0882@dev.com;P123456P -bot0883@dev.com;P123456P -bot0884@dev.com;P123456P -bot0885@dev.com;P123456P -bot0886@dev.com;P123456P -bot0887@dev.com;P123456P -bot0888@dev.com;P123456P -bot0889@dev.com;P123456P -bot0890@dev.com;P123456P -bot0891@dev.com;P123456P -bot0892@dev.com;P123456P -bot0893@dev.com;P123456P -bot0894@dev.com;P123456P -bot0895@dev.com;P123456P -bot0896@dev.com;P123456P -bot0897@dev.com;P123456P -bot0898@dev.com;P123456P -bot0899@dev.com;P123456P -bot0900@dev.com;P123456P -bot0901@dev.com;P123456P -bot0902@dev.com;P123456P -bot0903@dev.com;P123456P -bot0904@dev.com;P123456P -bot0905@dev.com;P123456P -bot0906@dev.com;P123456P -bot0907@dev.com;P123456P -bot0908@dev.com;P123456P -bot0909@dev.com;P123456P -bot0910@dev.com;P123456P -bot0911@dev.com;P123456P -bot0912@dev.com;P123456P -bot0913@dev.com;P123456P -bot0914@dev.com;P123456P -bot0915@dev.com;P123456P -bot0916@dev.com;P123456P -bot0917@dev.com;P123456P -bot0918@dev.com;P123456P -bot0919@dev.com;P123456P -bot0920@dev.com;P123456P -bot0921@dev.com;P123456P -bot0922@dev.com;P123456P -bot0923@dev.com;P123456P -bot0924@dev.com;P123456P -bot0925@dev.com;P123456P -bot0926@dev.com;P123456P -bot0927@dev.com;P123456P -bot0928@dev.com;P123456P -bot0929@dev.com;P123456P -bot0930@dev.com;P123456P -bot0931@dev.com;P123456P -bot0932@dev.com;P123456P -bot0933@dev.com;P123456P -bot0934@dev.com;P123456P -bot0935@dev.com;P123456P -bot0936@dev.com;P123456P -bot0937@dev.com;P123456P -bot0938@dev.com;P123456P -bot0939@dev.com;P123456P -bot0940@dev.com;P123456P -bot0941@dev.com;P123456P -bot0942@dev.com;P123456P -bot0943@dev.com;P123456P -bot0944@dev.com;P123456P -bot0945@dev.com;P123456P -bot0946@dev.com;P123456P -bot0947@dev.com;P123456P -bot0948@dev.com;P123456P -bot0949@dev.com;P123456P -bot0950@dev.com;P123456P -bot0951@dev.com;P123456P -bot0952@dev.com;P123456P -bot0953@dev.com;P123456P -bot0954@dev.com;P123456P -bot0955@dev.com;P123456P -bot0956@dev.com;P123456P -bot0957@dev.com;P123456P -bot0958@dev.com;P123456P -bot0959@dev.com;P123456P -bot0960@dev.com;P123456P -bot0961@dev.com;P123456P -bot0962@dev.com;P123456P -bot0963@dev.com;P123456P -bot0964@dev.com;P123456P -bot0965@dev.com;P123456P -bot0966@dev.com;P123456P -bot0967@dev.com;P123456P -bot0968@dev.com;P123456P -bot0969@dev.com;P123456P -bot0970@dev.com;P123456P -bot0971@dev.com;P123456P -bot0972@dev.com;P123456P -bot0973@dev.com;P123456P -bot0974@dev.com;P123456P -bot0975@dev.com;P123456P -bot0976@dev.com;P123456P -bot0977@dev.com;P123456P -bot0978@dev.com;P123456P -bot0979@dev.com;P123456P -bot0980@dev.com;P123456P -bot0981@dev.com;P123456P -bot0982@dev.com;P123456P -bot0983@dev.com;P123456P -bot0984@dev.com;P123456P -bot0985@dev.com;P123456P -bot0986@dev.com;P123456P -bot0987@dev.com;P123456P -bot0988@dev.com;P123456P -bot0989@dev.com;P123456P -bot0990@dev.com;P123456P -bot0991@dev.com;P123456P -bot0992@dev.com;P123456P -bot0993@dev.com;P123456P -bot0994@dev.com;P123456P -bot0995@dev.com;P123456P -bot0996@dev.com;P123456P -bot0997@dev.com;P123456P -bot0998@dev.com;P123456P -bot0999@dev.com;P123456P -bot1000@dev.com;P123456P \ No newline at end of file diff --git a/src/MarginTrading.Common/Documentation/DocMeAttribute.cs b/src/MarginTrading.Common/Documentation/DocMeAttribute.cs index 712b6287e..ead4da5d4 100644 --- a/src/MarginTrading.Common/Documentation/DocMeAttribute.cs +++ b/src/MarginTrading.Common/Documentation/DocMeAttribute.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Common.Documentation { diff --git a/src/MarginTrading.Common/Documentation/MethodDocInfo.cs b/src/MarginTrading.Common/Documentation/MethodDocInfo.cs index 05f7f1960..ce7adb700 100644 --- a/src/MarginTrading.Common/Documentation/MethodDocInfo.cs +++ b/src/MarginTrading.Common/Documentation/MethodDocInfo.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Common.Documentation { diff --git a/src/MarginTrading.Common/Documentation/TypeDocGenerator.cs b/src/MarginTrading.Common/Documentation/TypeDocGenerator.cs index e96d9c850..68e9d7316 100644 --- a/src/MarginTrading.Common/Documentation/TypeDocGenerator.cs +++ b/src/MarginTrading.Common/Documentation/TypeDocGenerator.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; diff --git a/src/MarginTrading.Common/Enums/ChannelTypes.cs b/src/MarginTrading.Common/Enums/ChannelTypes.cs index 3f44c267c..7757de3d0 100644 --- a/src/MarginTrading.Common/Enums/ChannelTypes.cs +++ b/src/MarginTrading.Common/Enums/ChannelTypes.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Common.Enums +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Common.Enums { public static class ChannelTypes { diff --git a/src/MarginTrading.Common/Extensions/ActionExtensions.cs b/src/MarginTrading.Common/Extensions/ActionExtensions.cs index 3444b261e..8ed2d2200 100644 --- a/src/MarginTrading.Common/Extensions/ActionExtensions.cs +++ b/src/MarginTrading.Common/Extensions/ActionExtensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; using JetBrains.Annotations; diff --git a/src/MarginTrading.Common/Extensions/ConfigurationBuilderExtensions.cs b/src/MarginTrading.Common/Extensions/ConfigurationBuilderExtensions.cs index d675fa91f..ccc127cf2 100644 --- a/src/MarginTrading.Common/Extensions/ConfigurationBuilderExtensions.cs +++ b/src/MarginTrading.Common/Extensions/ConfigurationBuilderExtensions.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using System.IO; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; diff --git a/src/MarginTrading.Common/Extensions/ConfigurationRootExtensions.cs b/src/MarginTrading.Common/Extensions/ConfigurationRootExtensions.cs index eb7d4ee19..21e407a84 100644 --- a/src/MarginTrading.Common/Extensions/ConfigurationRootExtensions.cs +++ b/src/MarginTrading.Common/Extensions/ConfigurationRootExtensions.cs @@ -1,18 +1,22 @@ -using Microsoft.Extensions.Configuration; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Microsoft.Extensions.Configuration; namespace MarginTrading.Common.Extensions { public static class ConfigurationRootExtensions { - public static bool IsLive(this IConfigurationRoot configuration) + public static bool NotThrowExceptionsOnServiceValidation(this IConfigurationRoot configuration) { - return !string.IsNullOrEmpty(configuration["IsLive"]) && - bool.TryParse(configuration["IsLive"], out var isLive) && isLive; + return !string.IsNullOrEmpty(configuration["NOT_THROW_EXCEPTIONS_ON_SERVICES_VALIDATION"]) && + bool.TryParse(configuration["NOT_THROW_EXCEPTIONS_ON_SERVICES_VALIDATION"], + out var trowExceptionsOnInvalidService) && trowExceptionsOnInvalidService; } public static string ServerType(this IConfigurationRoot configuration) { - return configuration.IsLive() ? "Live" : "Demo"; + return configuration["Env"]; } } } diff --git a/src/MarginTrading.Common/Extensions/EnumExtensions.cs b/src/MarginTrading.Common/Extensions/EnumExtensions.cs index 839525717..db8e1cd05 100644 --- a/src/MarginTrading.Common/Extensions/EnumExtensions.cs +++ b/src/MarginTrading.Common/Extensions/EnumExtensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Common.Extensions { diff --git a/src/MarginTrading.Common/Extensions/EnumerableExtensions.cs b/src/MarginTrading.Common/Extensions/EnumerableExtensions.cs new file mode 100644 index 000000000..e58586874 --- /dev/null +++ b/src/MarginTrading.Common/Extensions/EnumerableExtensions.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace MarginTrading.Common.Extensions +{ + public static class EnumerableExtensions + { + public static bool IsNullOrEmpty(this IEnumerable @this) + => @this == null || !@this.Any(); + } +} \ No newline at end of file diff --git a/src/MarginTrading.Common/Extensions/HttpContextExtensions.cs b/src/MarginTrading.Common/Extensions/HttpContextExtensions.cs index 60e5fafee..8271db188 100644 --- a/src/MarginTrading.Common/Extensions/HttpContextExtensions.cs +++ b/src/MarginTrading.Common/Extensions/HttpContextExtensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using Microsoft.AspNetCore.Http; namespace MarginTrading.Common.Extensions diff --git a/src/MarginTrading.Common/Extensions/TypeExtensions.cs b/src/MarginTrading.Common/Extensions/TypeExtensions.cs index ba46ad9b5..84721bae9 100644 --- a/src/MarginTrading.Common/Extensions/TypeExtensions.cs +++ b/src/MarginTrading.Common/Extensions/TypeExtensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Reflection; using System.Text; diff --git a/src/MarginTrading.Common/Extensions/ValidationExtensions.cs b/src/MarginTrading.Common/Extensions/ValidationExtensions.cs index c96512ea9..fd8a0c6ca 100644 --- a/src/MarginTrading.Common/Extensions/ValidationExtensions.cs +++ b/src/MarginTrading.Common/Extensions/ValidationExtensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections; using System.Collections.Generic; using System.Linq; diff --git a/src/MarginTrading.Common/Helpers/ReadWriteLockedDictionary.cs b/src/MarginTrading.Common/Helpers/ReadWriteLockedDictionary.cs index 27b6260f0..adcf7d509 100644 --- a/src/MarginTrading.Common/Helpers/ReadWriteLockedDictionary.cs +++ b/src/MarginTrading.Common/Helpers/ReadWriteLockedDictionary.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; diff --git a/src/MarginTrading.Common/Helpers/ReaderWriterLockHelper.cs b/src/MarginTrading.Common/Helpers/ReaderWriterLockHelper.cs index 441541e00..6dca3ff43 100644 --- a/src/MarginTrading.Common/Helpers/ReaderWriterLockHelper.cs +++ b/src/MarginTrading.Common/Helpers/ReaderWriterLockHelper.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Threading; namespace MarginTrading.Common.Helpers diff --git a/src/MarginTrading.Common/Helpers/StreamHelpers.cs b/src/MarginTrading.Common/Helpers/StreamHelpers.cs index e03fad7ef..bab942b12 100644 --- a/src/MarginTrading.Common/Helpers/StreamHelpers.cs +++ b/src/MarginTrading.Common/Helpers/StreamHelpers.cs @@ -1,4 +1,7 @@ -using System.IO; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.IO; using System.Threading.Tasks; namespace MarginTrading.Common.Helpers diff --git a/src/MarginTrading.Common/Helpers/UserAgentTelemetryInitializer.cs b/src/MarginTrading.Common/Helpers/UserAgentTelemetryInitializer.cs index 46e4cf056..8e18128c0 100644 --- a/src/MarginTrading.Common/Helpers/UserAgentTelemetryInitializer.cs +++ b/src/MarginTrading.Common/Helpers/UserAgentTelemetryInitializer.cs @@ -1,4 +1,7 @@ -using Microsoft.ApplicationInsights.Channel; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Microsoft.ApplicationInsights.Channel; using Microsoft.ApplicationInsights.Extensibility; using Microsoft.AspNetCore.Http; using Microsoft.Net.Http.Headers; diff --git a/src/MarginTrading.Common/Json/SerializerSettings.cs b/src/MarginTrading.Common/Json/SerializerSettings.cs index c28ac7ac4..e9a555c75 100644 --- a/src/MarginTrading.Common/Json/SerializerSettings.cs +++ b/src/MarginTrading.Common/Json/SerializerSettings.cs @@ -1,4 +1,7 @@ -using Newtonsoft.Json; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; using Newtonsoft.Json.Converters; namespace MarginTrading.Common.Json diff --git a/src/MarginTrading.Common/MarginTrading.Common.csproj b/src/MarginTrading.Common/MarginTrading.Common.csproj index abe4c3c4c..095108f90 100644 --- a/src/MarginTrading.Common/MarginTrading.Common.csproj +++ b/src/MarginTrading.Common/MarginTrading.Common.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 MarginTrading.Common @@ -6,28 +6,32 @@ false false false - 1.0.1 + 1.16.29 + 7.3 + + + 1701;1702;1705;CA2007;0612;0618;1591 - - - - - - + + + + + + - - - - - - + + + + + + - - - - + + + + \ No newline at end of file diff --git a/src/MarginTrading.Common/Middleware/RequestLoggingPipeline.cs b/src/MarginTrading.Common/Middleware/RequestLoggingPipeline.cs index b6fa13ab3..cf12e42b7 100644 --- a/src/MarginTrading.Common/Middleware/RequestLoggingPipeline.cs +++ b/src/MarginTrading.Common/Middleware/RequestLoggingPipeline.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Builder; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Microsoft.AspNetCore.Builder; namespace MarginTrading.Common.Middleware { diff --git a/src/MarginTrading.Common/Middleware/RequestsLoggingMiddleware.cs b/src/MarginTrading.Common/Middleware/RequestsLoggingMiddleware.cs index bcc8810c4..0c764288e 100644 --- a/src/MarginTrading.Common/Middleware/RequestsLoggingMiddleware.cs +++ b/src/MarginTrading.Common/Middleware/RequestsLoggingMiddleware.cs @@ -1,10 +1,14 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.IO; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using Common; using Common.Log; +using JetBrains.Annotations; using MarginTrading.Common.Helpers; using MarginTrading.Common.Services; using MarginTrading.Common.Settings; @@ -12,6 +16,7 @@ namespace MarginTrading.Common.Middleware { + [UsedImplicitly] public class RequestsLoggingMiddleware { private readonly RequestDelegate _next; @@ -20,7 +25,7 @@ public class RequestsLoggingMiddleware private readonly ILog _requestsLog; private const int MaxStorageFieldLength = 2000; - private readonly string[] _personalDataHeaders = {"Authorization"}; + private readonly string[] _personalDataHeaders = {"Authorization", "api-key"}; public RequestsLoggingMiddleware(RequestDelegate next, RequestLoggerSettings settings, ILog log) { @@ -30,6 +35,7 @@ public RequestsLoggingMiddleware(RequestDelegate next, RequestLoggerSettings set _requestsLog = LogLocator.RequestsLog; } + [UsedImplicitly] public async Task Invoke(HttpContext context) { var requestContext = @@ -59,7 +65,8 @@ public async Task Invoke(HttpContext context) info = info.Substring(0, MaxStorageFieldLength); } - await _requestsLog.WriteInfoAsync("MIDDLEWARE", "RequestsLoggingMiddleware", requestContext, info); + await _requestsLog.WriteInfoAsync("MIDDLEWARE", "RequestsLoggingMiddleware", requestContext, + info); } } } diff --git a/src/MarginTrading.Common/Modules/MarginTradingCommonModule.cs b/src/MarginTrading.Common/Modules/MarginTradingCommonModule.cs index 5aca18bfb..bf16baffa 100644 --- a/src/MarginTrading.Common/Modules/MarginTradingCommonModule.cs +++ b/src/MarginTrading.Common/Modules/MarginTradingCommonModule.cs @@ -1,4 +1,7 @@ -using Autofac; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Autofac; using Autofac.Extensions.DependencyInjection; using MarginTrading.Common.Helpers; using MarginTrading.Common.Services; diff --git a/src/MarginTrading.Common/Properties/AssemblyInfo.cs b/src/MarginTrading.Common/Properties/AssemblyInfo.cs index ad8ce3994..6cc569d3b 100644 --- a/src/MarginTrading.Common/Properties/AssemblyInfo.cs +++ b/src/MarginTrading.Common/Properties/AssemblyInfo.cs @@ -1,4 +1,7 @@ -using System.Reflection; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; diff --git a/src/MarginTrading.Common/RabbitMq/BytesStringSerializer.cs b/src/MarginTrading.Common/RabbitMq/BytesStringSerializer.cs index 70f0e0f18..73dd2b620 100644 --- a/src/MarginTrading.Common/RabbitMq/BytesStringSerializer.cs +++ b/src/MarginTrading.Common/RabbitMq/BytesStringSerializer.cs @@ -1,4 +1,7 @@ -using System.Text; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Text; using Lykke.RabbitMqBroker.Publisher; namespace MarginTrading.Common.RabbitMq diff --git a/src/MarginTrading.Common/RabbitMq/DeserializerWithErrorLogging.cs b/src/MarginTrading.Common/RabbitMq/DeserializerWithErrorLogging.cs index d2d4be67e..853ea2e87 100644 --- a/src/MarginTrading.Common/RabbitMq/DeserializerWithErrorLogging.cs +++ b/src/MarginTrading.Common/RabbitMq/DeserializerWithErrorLogging.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Text; using Common.Log; using Lykke.RabbitMqBroker.Subscriber; diff --git a/src/MarginTrading.Common/RabbitMq/IRabbitMqService.cs b/src/MarginTrading.Common/RabbitMq/IRabbitMqService.cs index ed7c5050b..b565fb7bf 100644 --- a/src/MarginTrading.Common/RabbitMq/IRabbitMqService.cs +++ b/src/MarginTrading.Common/RabbitMq/IRabbitMqService.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Threading.Tasks; using Common; using Lykke.RabbitMqBroker.Publisher; @@ -8,7 +11,7 @@ namespace MarginTrading.Common.RabbitMq { public interface IRabbitMqService { - IMessageProducer GetProducer(RabbitMqSettings settings, bool isDurable, + IMessageProducer GetProducer(RabbitMqSettings settings, IRabbitMqSerializer serializer); void Subscribe(RabbitMqSettings settings, bool isDurable, Func handler, diff --git a/src/MarginTrading.Common/RabbitMq/QueueHelper.cs b/src/MarginTrading.Common/RabbitMq/QueueHelper.cs index a7feb779a..1d208a8a3 100644 --- a/src/MarginTrading.Common/RabbitMq/QueueHelper.cs +++ b/src/MarginTrading.Common/RabbitMq/QueueHelper.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.PlatformAbstractions; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Microsoft.Extensions.PlatformAbstractions; namespace MarginTrading.Common.RabbitMq { diff --git a/src/MarginTrading.Common/RabbitMq/RabbitMqQueueInfo.cs b/src/MarginTrading.Common/RabbitMq/RabbitMqQueueInfo.cs index 625fc7a4a..b81da7415 100644 --- a/src/MarginTrading.Common/RabbitMq/RabbitMqQueueInfo.cs +++ b/src/MarginTrading.Common/RabbitMq/RabbitMqQueueInfo.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Common.RabbitMq +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Common.RabbitMq { public class RabbitMqQueueInfo { diff --git a/src/MarginTrading.Common/RabbitMq/RabbitMqService.cs b/src/MarginTrading.Common/RabbitMq/RabbitMqService.cs index d115d1949..4aeebaa71 100644 --- a/src/MarginTrading.Common/RabbitMq/RabbitMqService.cs +++ b/src/MarginTrading.Common/RabbitMq/RabbitMqService.cs @@ -1,7 +1,10 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Text; +using System.Linq; using System.Threading.Tasks; using AzureStorage.Blob; using Common; @@ -12,8 +15,7 @@ using Lykke.RabbitMqBroker.Publisher; using Lykke.RabbitMqBroker.Subscriber; using Lykke.SettingsReader; -using Microsoft.Extensions.PlatformAbstractions; -using Newtonsoft.Json; +using RabbitMQ.Client; namespace MarginTrading.Common.RabbitMq { @@ -21,35 +23,55 @@ public class RabbitMqService : IRabbitMqService, IDisposable { private readonly ILog _logger; private readonly string _env; + private readonly IPublishingQueueRepository _publishingQueueRepository; private readonly IConsole _consoleWriter; - private readonly ConcurrentDictionary _subscribers = - new ConcurrentDictionary(new SubscriptionSettingsEqualityComparer()); + private readonly ConcurrentDictionary<(RabbitMqSubscriptionSettings, int), IStopable> _subscribers = + new ConcurrentDictionary<(RabbitMqSubscriptionSettings, int), IStopable>(new SubscriptionSettingsWithNumberEqualityComparer()); private readonly ConcurrentDictionary> _producers = new ConcurrentDictionary>( new SubscriptionSettingsEqualityComparer()); - [ItemCanBeNull] private readonly Lazy _queueRepository; + //[ItemCanBeNull] private readonly Lazy _queueRepository; - public RabbitMqService(ILog logger, IConsole consoleWriter, - [CanBeNull] IReloadingManager queueRepositoryConnectionString, string env) + public RabbitMqService(ILog logger, + IConsole consoleWriter, + [CanBeNull] IReloadingManager queueRepositoryConnectionString, + string env, + IPublishingQueueRepository publishingQueueRepository) { _logger = logger; _env = env; + _publishingQueueRepository = publishingQueueRepository; _consoleWriter = consoleWriter; - _queueRepository = new Lazy(() => - { - if (string.IsNullOrWhiteSpace(queueRepositoryConnectionString?.CurrentValue)) - { - _logger.WriteWarning(nameof(RabbitMqService), "", - "QueueRepositoryConnectionString is not configured"); - return null; - } + //_queueRepository = new Lazy(() => +// { +// if (string.IsNullOrWhiteSpace(queueRepositoryConnectionString?.CurrentValue)) +// { +// _logger.WriteWarning(nameof(RabbitMqService), "", +// "QueueRepositoryConnectionString is not configured"); +// return null; +// } + + //var blob = AzureBlobStorage.Create(queueRepositoryConnectionString); + //return new MessagePackBlobPublishingQueueRepository(blob); + //}); + } - var blob = AzureBlobStorage.Create(queueRepositoryConnectionString); - return new MessagePackBlobPublishingQueueRepository(blob); - }); + /// + /// Returns the number of messages in ready to be delivered to consumers. + /// This method assumes the queue exists. If it doesn't, an exception is thrown. + /// + public static uint GetMessageCount(string connectionString, string queueName) + { + var factory = new ConnectionFactory { Uri = connectionString }; + + using (var connection = factory.CreateConnection()) + using (var channel = connection.CreateModel()) + { + return channel.MessageCount(queueName); + } } public void Dispose() @@ -81,14 +103,14 @@ public IMessageDeserializer GetMsgPackDeserializer() } public IMessageProducer GetProducer(RabbitMqSettings settings, - bool isDurable, IRabbitMqSerializer serializer) + IRabbitMqSerializer serializer) { // on-the fly connection strings switch is not supported currently for rabbitMq var subscriptionSettings = new RabbitMqSubscriptionSettings { ConnectionString = settings.ConnectionString, ExchangeName = settings.ExchangeName, - IsDurable = isDurable, + IsDurable = settings.IsDurable, }; return (IMessageProducer) _producers.GetOrAdd(subscriptionSettings, CreateProducer).Value; @@ -101,8 +123,8 @@ Lazy CreateProducer(RabbitMqSubscriptionSettings s) { var publisher = new RabbitMqPublisher(s); - if (isDurable && _queueRepository.Value != null) - publisher.SetQueueRepository(_queueRepository.Value); + if (s.IsDurable && _publishingQueueRepository != null) + publisher.SetQueueRepository(_publishingQueueRepository); else publisher.DisableInMemoryQueuePersistence(); @@ -115,33 +137,39 @@ Lazy CreateProducer(RabbitMqSubscriptionSettings s) } } + public void Subscribe(RabbitMqSettings settings, bool isDurable, Func handler, IMessageDeserializer deserializer) { - var subscriptionSettings = new RabbitMqSubscriptionSettings + var consumerCount = settings.ConsumerCount == 0 ? 1 : settings.ConsumerCount; + + foreach (var consumerNumber in Enumerable.Range(1, consumerCount)) { - ConnectionString = settings.ConnectionString, - QueueName = QueueHelper.BuildQueueName(settings.ExchangeName, _env), - ExchangeName = settings.ExchangeName, - IsDurable = isDurable, - }; - - var rabbitMqSubscriber = new RabbitMqSubscriber(subscriptionSettings, - new DefaultErrorHandlingStrategy(_logger, subscriptionSettings)) - .SetMessageDeserializer(deserializer) - .Subscribe(handler) - .SetLogger(_logger) - .SetConsole(_consoleWriter); + var subscriptionSettings = new RabbitMqSubscriptionSettings + { + ConnectionString = settings.ConnectionString, + QueueName = QueueHelper.BuildQueueName(settings.ExchangeName, _env), + ExchangeName = settings.ExchangeName, + IsDurable = isDurable, + }; + + var rabbitMqSubscriber = new RabbitMqSubscriber(subscriptionSettings, + new DefaultErrorHandlingStrategy(_logger, subscriptionSettings)) + .SetMessageDeserializer(deserializer) + .Subscribe(handler) + .SetLogger(_logger) + .SetConsole(_consoleWriter); + + if (!_subscribers.TryAdd((subscriptionSettings, consumerNumber), rabbitMqSubscriber)) + { + throw new InvalidOperationException( + $"A subscriber number {consumerNumber} for queue {subscriptionSettings.QueueName} was already initialized"); + } - if (!_subscribers.TryAdd(subscriptionSettings, rabbitMqSubscriber)) - { - throw new InvalidOperationException( - $"A subscriber for queue {subscriptionSettings.QueueName} was already initialized"); + rabbitMqSubscriber.Start(); } - - rabbitMqSubscriber.Start(); } - + /// /// ReSharper auto-generated /// @@ -166,5 +194,31 @@ public int GetHashCode(RabbitMqSubscriptionSettings obj) } } } + + /// + /// ReSharper auto-generated + /// + private sealed class SubscriptionSettingsWithNumberEqualityComparer : IEqualityComparer<(RabbitMqSubscriptionSettings, int)> + { + public bool Equals((RabbitMqSubscriptionSettings, int) x, (RabbitMqSubscriptionSettings, int) y) + { + if (ReferenceEquals(x.Item1, y.Item1) && x.Item2 == y.Item2) return true; + if (ReferenceEquals(x.Item1, null)) return false; + if (ReferenceEquals(y.Item1, null)) return false; + if (x.Item1.GetType() != y.Item1.GetType()) return false; + return string.Equals(x.Item1.ConnectionString, y.Item1.ConnectionString) + && string.Equals(x.Item1.ExchangeName, y.Item1.ExchangeName) + && x.Item2 == y.Item2; + } + + public int GetHashCode((RabbitMqSubscriptionSettings, int) obj) + { + unchecked + { + return ((obj.Item1.ConnectionString != null ? obj.Item1.ConnectionString.GetHashCode() : 0) * 397) ^ + (obj.Item1.ExchangeName != null ? obj.Item1.ExchangeName.GetHashCode() : 0); + } + } + } } } \ No newline at end of file diff --git a/src/MarginTrading.Common/RabbitMq/RabbitMqSettings.cs b/src/MarginTrading.Common/RabbitMq/RabbitMqSettings.cs index 13e80acc1..cb7242629 100644 --- a/src/MarginTrading.Common/RabbitMq/RabbitMqSettings.cs +++ b/src/MarginTrading.Common/RabbitMq/RabbitMqSettings.cs @@ -1,4 +1,8 @@ -using Lykke.SettingsReader.Attributes; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; +using Lykke.SettingsReader.Attributes; namespace MarginTrading.Common.RabbitMq { @@ -7,5 +11,12 @@ public class RabbitMqSettings [AmqpCheck] public string ConnectionString { get; set; } public string ExchangeName { get; set; } + + /// By default = 1 + [Optional] + public int ConsumerCount { get; set; } = 1; + + [Optional] + public bool IsDurable { get; set; } = true; } } \ No newline at end of file diff --git a/src/MarginTrading.Common/Services/Client/ClientAccountService.cs b/src/MarginTrading.Common/Services/Client/ClientAccountService.cs index 53814d9ad..a114d6b86 100644 --- a/src/MarginTrading.Common/Services/Client/ClientAccountService.cs +++ b/src/MarginTrading.Common/Services/Client/ClientAccountService.cs @@ -1,6 +1,10 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Threading.Tasks; using Lykke.Service.ClientAccount.Client; +using Lykke.Service.ClientAccount.Client.Models; namespace MarginTrading.Common.Services.Client { @@ -38,5 +42,17 @@ public async Task IsPushEnabled(string clientId) return pushSettings != null && pushSettings.Enabled; } + + public Task GetMarginEnabledAsync(string clientId) + { + return _clientAccountsClient.GetMarginEnabledAsync(clientId); + } + + public Task SetMarginEnabledAsync(string clientId, bool settingsEnabled, bool settingsEnabledLive, + bool settingsTermsOfUseAgreed) + { + return _clientAccountsClient.SetMarginEnabledAsync(clientId, settingsEnabled, settingsEnabledLive, + settingsTermsOfUseAgreed); + } } } diff --git a/src/MarginTrading.Common/Services/Client/IClientAccountService.cs b/src/MarginTrading.Common/Services/Client/IClientAccountService.cs index 475fa0b73..5cfe49864 100644 --- a/src/MarginTrading.Common/Services/Client/IClientAccountService.cs +++ b/src/MarginTrading.Common/Services/Client/IClientAccountService.cs @@ -1,4 +1,8 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using Lykke.Service.ClientAccount.Client.Models; namespace MarginTrading.Common.Services.Client { @@ -7,5 +11,7 @@ public interface IClientAccountService Task GetNotificationId(string clientId); Task GetEmail(string clientId); Task IsPushEnabled(string clientId); + Task GetMarginEnabledAsync(string clientId); + Task SetMarginEnabledAsync(string clientId, bool settingsEnabled, bool settingsEnabledLive, bool settingsTermsOfUseAgreed); } } diff --git a/src/MarginTrading.Common/Services/ConvertService.cs b/src/MarginTrading.Common/Services/ConvertService.cs index 70ff3940b..d77511310 100644 --- a/src/MarginTrading.Common/Services/ConvertService.cs +++ b/src/MarginTrading.Common/Services/ConvertService.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Concurrent; using System.Collections.Generic; using AutoMapper; @@ -53,6 +56,11 @@ public TResult Convert(TSource source) return _mapper.Map(source); } + public TResult Convert(object source) + { + return _mapper.Map(source); + } + /// /// Get the properties and values of an object using an existing opimized implementation /// diff --git a/src/MarginTrading.Common/Services/DateService.cs b/src/MarginTrading.Common/Services/DateService.cs index 5df82e062..b9cc91710 100644 --- a/src/MarginTrading.Common/Services/DateService.cs +++ b/src/MarginTrading.Common/Services/DateService.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Common.Services { diff --git a/src/MarginTrading.Common/Services/IConvertService.cs b/src/MarginTrading.Common/Services/IConvertService.cs index b29a0d64e..964e7fd45 100644 --- a/src/MarginTrading.Common/Services/IConvertService.cs +++ b/src/MarginTrading.Common/Services/IConvertService.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using AutoMapper; namespace MarginTrading.Common.Services @@ -15,5 +18,7 @@ public interface IConvertService /// Note that this method is slower than others. ///
TResult ConvertWithConstructorArgs(TSource source, object argumentsObject); + + TResult Convert(object source); } } \ No newline at end of file diff --git a/src/MarginTrading.Common/Services/IDateService.cs b/src/MarginTrading.Common/Services/IDateService.cs index 4b598c0b7..7bd146102 100644 --- a/src/MarginTrading.Common/Services/IDateService.cs +++ b/src/MarginTrading.Common/Services/IDateService.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Common.Services { diff --git a/src/MarginTrading.Common/Services/IMarginTradingOperationsLogRepository.cs b/src/MarginTrading.Common/Services/IMarginTradingOperationsLogRepository.cs index 091868439..576cc26ac 100644 --- a/src/MarginTrading.Common/Services/IMarginTradingOperationsLogRepository.cs +++ b/src/MarginTrading.Common/Services/IMarginTradingOperationsLogRepository.cs @@ -1,11 +1,13 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; namespace MarginTrading.Common.Services { public interface IOperationLog { string Name { get; } - string ClientId { get; } string AccountId { get; } string Input { get; } string Data { get; } @@ -14,13 +16,12 @@ public interface IOperationLog public class OperationLog : IOperationLog { public string Name { get; set; } - public string ClientId { get; set; } public string AccountId { get; set; } public string Input { get; set; } public string Data { get; set; } } - public interface IMarginTradingOperationsLogRepository + public interface IOperationsLogRepository { Task AddLogAsync(IOperationLog logEntity); } diff --git a/src/MarginTrading.Common/Services/IMarginTradingOperationsLogService.cs b/src/MarginTrading.Common/Services/IMarginTradingOperationsLogService.cs deleted file mode 100644 index ed8ea107b..000000000 --- a/src/MarginTrading.Common/Services/IMarginTradingOperationsLogService.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MarginTrading.Common.Services -{ - public interface IMarginTradingOperationsLogService - { - void AddLog(string name, string clientId, string accountId, string input, string data); - } -} diff --git a/src/MarginTrading.Common/Services/IMtSlackNotificationsSender.cs b/src/MarginTrading.Common/Services/IMtSlackNotificationsSender.cs index ecc7f4013..b5e7d50f4 100644 --- a/src/MarginTrading.Common/Services/IMtSlackNotificationsSender.cs +++ b/src/MarginTrading.Common/Services/IMtSlackNotificationsSender.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; using Lykke.SlackNotifications; namespace MarginTrading.Common.Services diff --git a/src/MarginTrading.Common/Services/LogLocator.cs b/src/MarginTrading.Common/Services/LogLocator.cs index b8c202d86..eec658502 100644 --- a/src/MarginTrading.Common/Services/LogLocator.cs +++ b/src/MarginTrading.Common/Services/LogLocator.cs @@ -1,4 +1,7 @@ -using Common.Log; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Common.Log; namespace MarginTrading.Common.Services { diff --git a/src/MarginTrading.Common/Services/MarginTradingOperationsLogService.cs b/src/MarginTrading.Common/Services/MarginTradingOperationsLogService.cs index 90bad69e3..4cd162c62 100644 --- a/src/MarginTrading.Common/Services/MarginTradingOperationsLogService.cs +++ b/src/MarginTrading.Common/Services/MarginTradingOperationsLogService.cs @@ -1,27 +1,40 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using Common.Log; +using JetBrains.Annotations; using Lykke.Common; namespace MarginTrading.Common.Services { - public sealed class MarginTradingOperationsLogService : IMarginTradingOperationsLogService + [UsedImplicitly] + public sealed class OperationsLogService : IOperationsLogService { - private readonly IMarginTradingOperationsLogRepository _operationsLogRepository; + private readonly IOperationsLogRepository _operationsLogRepository; private readonly IThreadSwitcher _threadSwitcher; private readonly ILog _log; + private readonly bool _writeOperationLog; - public MarginTradingOperationsLogService( - IMarginTradingOperationsLogRepository operationsLogRepository, + public OperationsLogService( + IOperationsLogRepository operationsLogRepository, IThreadSwitcher threadSwitcher, - ILog log) + ILog log, + bool writeOperationLog) { _operationsLogRepository = operationsLogRepository; _threadSwitcher = threadSwitcher; _log = log; + _writeOperationLog = writeOperationLog; } - public void AddLog(string name, string clientId, string accountId, string input, string data) + public void AddLog(string name, string accountId, string input, string data) { + if (!_writeOperationLog) + { + return; + } + _threadSwitcher.SwitchThread(async () => { try @@ -30,7 +43,6 @@ public void AddLog(string name, string clientId, string accountId, string input, { Name = name, AccountId = accountId, - ClientId = clientId, Data = data, Input = input }; @@ -39,7 +51,7 @@ public void AddLog(string name, string clientId, string accountId, string input, } catch (Exception ex) { - _log.WriteErrorAsync(nameof(MarginTradingOperationsLogService), nameof(AddLog), $"{name}, clientId = {clientId}, accountId = {accountId}", ex).Wait(); + _log.WriteErrorAsync(nameof(OperationsLogService), nameof(AddLog), $"{name}, accountId = {accountId}", ex).Wait(); } }); } diff --git a/src/MarginTrading.Common/Services/MtSlackNotificationsSender.cs b/src/MarginTrading.Common/Services/MtSlackNotificationsSender.cs index 9e1d23677..05ffa8dce 100644 --- a/src/MarginTrading.Common/Services/MtSlackNotificationsSender.cs +++ b/src/MarginTrading.Common/Services/MtSlackNotificationsSender.cs @@ -1,7 +1,9 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.Text; using System.Threading.Tasks; -using Lykke.Logs; using Lykke.SlackNotifications; using MarginTrading.Common.Enums; @@ -31,6 +33,17 @@ public async Task SendAsync(string type, string sender, string message) await _sender.SendAsync(ChannelTypes.MarginTrading, sender, GetSlackMsg(message)); } + public async Task SendAsync(DateTime moment, string type, string sender, string message) + { + if (type.Equals(ChannelTypes.Monitor, StringComparison.InvariantCultureIgnoreCase)) + { + await _sender.SendAsync(moment, type, sender, message); + return; + } + + await _sender.SendAsync(moment, ChannelTypes.MarginTrading, sender, GetSlackMsg(message)); + } + public Task SendRawAsync(string type, string sender, string message) { return _sender.SendAsync(type, sender, message); diff --git a/src/MarginTrading.Common/Services/OperationsLogService.cs b/src/MarginTrading.Common/Services/OperationsLogService.cs new file mode 100644 index 000000000..9386dd8e4 --- /dev/null +++ b/src/MarginTrading.Common/Services/OperationsLogService.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Common.Services +{ + public interface IOperationsLogService + { + void AddLog(string name, string accountId, string input, string data); + } +} diff --git a/src/MarginTrading.Common/Services/Settings/EnabledMarginTradingTypes.cs b/src/MarginTrading.Common/Services/Settings/EnabledMarginTradingTypes.cs index 35efbf9f8..30662ddd2 100644 --- a/src/MarginTrading.Common/Services/Settings/EnabledMarginTradingTypes.cs +++ b/src/MarginTrading.Common/Services/Settings/EnabledMarginTradingTypes.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Common.Services.Settings +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Common.Services.Settings { /// /// Contains information about what types of margin trading are available for particular client diff --git a/src/MarginTrading.Common/Services/Settings/MarginTradingEnabledChangedMessage.cs b/src/MarginTrading.Common/Services/Settings/MarginTradingEnabledChangedMessage.cs index 1991dec03..2fba7155e 100644 --- a/src/MarginTrading.Common/Services/Settings/MarginTradingEnabledChangedMessage.cs +++ b/src/MarginTrading.Common/Services/Settings/MarginTradingEnabledChangedMessage.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Common.Services.Settings +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Common.Services.Settings { public class MarginTradingEnabledChangedMessage { diff --git a/src/MarginTrading.Common/Services/Telemetry/ITelemetryPublisher.cs b/src/MarginTrading.Common/Services/Telemetry/ITelemetryPublisher.cs index bce89cf3c..6aca010b0 100644 --- a/src/MarginTrading.Common/Services/Telemetry/ITelemetryPublisher.cs +++ b/src/MarginTrading.Common/Services/Telemetry/ITelemetryPublisher.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; namespace MarginTrading.Common.Services.Telemetry { diff --git a/src/MarginTrading.Common/Services/Telemetry/TelemetryConstants.cs b/src/MarginTrading.Common/Services/Telemetry/TelemetryConstants.cs index dfcd951fa..d5a3b7f90 100644 --- a/src/MarginTrading.Common/Services/Telemetry/TelemetryConstants.cs +++ b/src/MarginTrading.Common/Services/Telemetry/TelemetryConstants.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Common.Services.Telemetry +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Common.Services.Telemetry { public static class TelemetryConstants { diff --git a/src/MarginTrading.Common/Services/Telemetry/TelemetryPublisher.cs b/src/MarginTrading.Common/Services/Telemetry/TelemetryPublisher.cs index 37628340d..4d53bc609 100644 --- a/src/MarginTrading.Common/Services/Telemetry/TelemetryPublisher.cs +++ b/src/MarginTrading.Common/Services/Telemetry/TelemetryPublisher.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using Microsoft.ApplicationInsights; namespace MarginTrading.Common.Services.Telemetry diff --git a/src/MarginTrading.Common/Settings/ClientAccountServiceSettings.cs b/src/MarginTrading.Common/Settings/ClientAccountServiceSettings.cs index 980bac52a..6e60c2ec7 100644 --- a/src/MarginTrading.Common/Settings/ClientAccountServiceSettings.cs +++ b/src/MarginTrading.Common/Settings/ClientAccountServiceSettings.cs @@ -1,7 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; using Lykke.SettingsReader.Attributes; namespace MarginTrading.Common.Settings { + [UsedImplicitly] public class ClientAccountServiceSettings { [HttpCheck("/api/isalive")] diff --git a/src/MarginTrading.Common/Settings/ExchangeConnectorServiceClient.cs b/src/MarginTrading.Common/Settings/ExchangeConnectorServiceClient.cs new file mode 100644 index 000000000..4662d3010 --- /dev/null +++ b/src/MarginTrading.Common/Settings/ExchangeConnectorServiceClient.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; +using Lykke.SettingsReader.Attributes; + +namespace MarginTrading.Common.Settings +{ + [UsedImplicitly] + public class ExchangeConnectorServiceClient + { + [HttpCheck("/api/isalive")] + public string ServiceUrl { get; set; } + + public string ApiKey { get; set; } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Common/Settings/RequestLoggerSettings.cs b/src/MarginTrading.Common/Settings/RequestLoggerSettings.cs index 8d61a1292..f27b2410f 100644 --- a/src/MarginTrading.Common/Settings/RequestLoggerSettings.cs +++ b/src/MarginTrading.Common/Settings/RequestLoggerSettings.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Common.Settings +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Common.Settings { public class RequestLoggerSettings { diff --git a/src/MarginTrading.Common/Settings/SlackNotificationSettings.cs b/src/MarginTrading.Common/Settings/SlackNotificationSettings.cs index f82a528c2..5e7e96c18 100644 --- a/src/MarginTrading.Common/Settings/SlackNotificationSettings.cs +++ b/src/MarginTrading.Common/Settings/SlackNotificationSettings.cs @@ -1,4 +1,7 @@ -using Lykke.SettingsReader.Attributes; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using Lykke.SettingsReader.Attributes; namespace MarginTrading.Common.Settings { diff --git a/src/MarginTrading.Contract/BackendContracts/AccountClientIdBackendRequest.cs b/src/MarginTrading.Contract/BackendContracts/AccountClientIdBackendRequest.cs deleted file mode 100644 index 8e9602c9d..000000000 --- a/src/MarginTrading.Contract/BackendContracts/AccountClientIdBackendRequest.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MarginTrading.Contract.BackendContracts -{ - public class AccountClientIdBackendRequest - { - public string AccountId { get; set; } - public string ClientId { get; set; } - } -} diff --git a/src/MarginTrading.Contract/BackendContracts/AccountHistoryBackendContract.cs b/src/MarginTrading.Contract/BackendContracts/AccountHistoryBackendContract.cs index abf4c88c0..5fc390859 100644 --- a/src/MarginTrading.Contract/BackendContracts/AccountHistoryBackendContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/AccountHistoryBackendContract.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Contract.BackendContracts { diff --git a/src/MarginTrading.Contract/BackendContracts/AccountHistoryBackendRequest.cs b/src/MarginTrading.Contract/BackendContracts/AccountHistoryBackendRequest.cs index 6de387704..ce2fdaa95 100644 --- a/src/MarginTrading.Contract/BackendContracts/AccountHistoryBackendRequest.cs +++ b/src/MarginTrading.Contract/BackendContracts/AccountHistoryBackendRequest.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Contract.BackendContracts { diff --git a/src/MarginTrading.Contract/BackendContracts/AccountHistoryBackendResponse.cs b/src/MarginTrading.Contract/BackendContracts/AccountHistoryBackendResponse.cs index c6fe04b17..2ae967449 100644 --- a/src/MarginTrading.Contract/BackendContracts/AccountHistoryBackendResponse.cs +++ b/src/MarginTrading.Contract/BackendContracts/AccountHistoryBackendResponse.cs @@ -1,5 +1,5 @@ -using System.Collections.Generic; -using System.Linq; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. namespace MarginTrading.Contract.BackendContracts { diff --git a/src/MarginTrading.Contract/BackendContracts/AccountHistoryItemBackend.cs b/src/MarginTrading.Contract/BackendContracts/AccountHistoryItemBackend.cs index 0e5307010..859e0247d 100644 --- a/src/MarginTrading.Contract/BackendContracts/AccountHistoryItemBackend.cs +++ b/src/MarginTrading.Contract/BackendContracts/AccountHistoryItemBackend.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using JetBrains.Annotations; namespace MarginTrading.Contract.BackendContracts diff --git a/src/MarginTrading.Contract/BackendContracts/AccountHistoryTypeContract.cs b/src/MarginTrading.Contract/BackendContracts/AccountHistoryTypeContract.cs index 3381f2349..b2c558dd5 100644 --- a/src/MarginTrading.Contract/BackendContracts/AccountHistoryTypeContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/AccountHistoryTypeContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public enum AccountHistoryTypeContract { diff --git a/src/MarginTrading.Contract/BackendContracts/AccountNewHistoryBackendResponse.cs b/src/MarginTrading.Contract/BackendContracts/AccountNewHistoryBackendResponse.cs index 5ab7fc04a..ca2f78840 100644 --- a/src/MarginTrading.Contract/BackendContracts/AccountNewHistoryBackendResponse.cs +++ b/src/MarginTrading.Contract/BackendContracts/AccountNewHistoryBackendResponse.cs @@ -1,5 +1,5 @@ -using System.Collections.Generic; -using System.Linq; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. namespace MarginTrading.Contract.BackendContracts { diff --git a/src/MarginTrading.Contract/BackendContracts/AccountsManagement/AccountsMarginLevelContract.cs b/src/MarginTrading.Contract/BackendContracts/AccountsManagement/AccountsMarginLevelContract.cs index 7ad8dff46..3224508b9 100644 --- a/src/MarginTrading.Contract/BackendContracts/AccountsManagement/AccountsMarginLevelContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/AccountsManagement/AccountsMarginLevelContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts.AccountsManagement +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts.AccountsManagement { public class AccountsMarginLevelContract { diff --git a/src/MarginTrading.Contract/BackendContracts/AccountsManagement/AccountsMarginLevelResponse.cs b/src/MarginTrading.Contract/BackendContracts/AccountsManagement/AccountsMarginLevelResponse.cs index 872e93249..b9d2adae6 100644 --- a/src/MarginTrading.Contract/BackendContracts/AccountsManagement/AccountsMarginLevelResponse.cs +++ b/src/MarginTrading.Contract/BackendContracts/AccountsManagement/AccountsMarginLevelResponse.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Contract.BackendContracts.AccountsManagement { diff --git a/src/MarginTrading.Contract/BackendContracts/AccountsManagement/CloseAccountPositionsRequest.cs b/src/MarginTrading.Contract/BackendContracts/AccountsManagement/CloseAccountPositionsRequest.cs index f3240d3c3..2df01272c 100644 --- a/src/MarginTrading.Contract/BackendContracts/AccountsManagement/CloseAccountPositionsRequest.cs +++ b/src/MarginTrading.Contract/BackendContracts/AccountsManagement/CloseAccountPositionsRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts.AccountsManagement +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts.AccountsManagement { public class CloseAccountPositionsRequest { diff --git a/src/MarginTrading.Contract/BackendContracts/AccountsManagement/CloseAccountPositionsResponse.cs b/src/MarginTrading.Contract/BackendContracts/AccountsManagement/CloseAccountPositionsResponse.cs index fa07e36db..65552be1c 100644 --- a/src/MarginTrading.Contract/BackendContracts/AccountsManagement/CloseAccountPositionsResponse.cs +++ b/src/MarginTrading.Contract/BackendContracts/AccountsManagement/CloseAccountPositionsResponse.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; namespace MarginTrading.Contract.BackendContracts.AccountsManagement { diff --git a/src/MarginTrading.Contract/BackendContracts/AccountsManagement/CloseAccountPositionsResult.cs b/src/MarginTrading.Contract/BackendContracts/AccountsManagement/CloseAccountPositionsResult.cs index d66480e34..9a93c6a9a 100644 --- a/src/MarginTrading.Contract/BackendContracts/AccountsManagement/CloseAccountPositionsResult.cs +++ b/src/MarginTrading.Contract/BackendContracts/AccountsManagement/CloseAccountPositionsResult.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts.AccountsManagement +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts.AccountsManagement { public class CloseAccountPositionsResult { diff --git a/src/MarginTrading.Contract/BackendContracts/AccountsManagement/InitAccountGroupRequest.cs b/src/MarginTrading.Contract/BackendContracts/AccountsManagement/InitAccountGroupRequest.cs index 9679bccad..1bc391a7f 100644 --- a/src/MarginTrading.Contract/BackendContracts/AccountsManagement/InitAccountGroupRequest.cs +++ b/src/MarginTrading.Contract/BackendContracts/AccountsManagement/InitAccountGroupRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts.AccountsManagement +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts.AccountsManagement { public class InitAccountGroupRequest { diff --git a/src/MarginTrading.Contract/BackendContracts/AccountsManagement/MarginTradingAccountModel.cs b/src/MarginTrading.Contract/BackendContracts/AccountsManagement/MarginTradingAccountModel.cs index 11ff78638..329811cb1 100644 --- a/src/MarginTrading.Contract/BackendContracts/AccountsManagement/MarginTradingAccountModel.cs +++ b/src/MarginTrading.Contract/BackendContracts/AccountsManagement/MarginTradingAccountModel.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts.AccountsManagement +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts.AccountsManagement { public class MarginTradingAccountModel { diff --git a/src/MarginTrading.Contract/BackendContracts/AccountsManagement/SetTradingConditionModel.cs b/src/MarginTrading.Contract/BackendContracts/AccountsManagement/SetTradingConditionModel.cs index e3f2ae790..740c5ce9d 100644 --- a/src/MarginTrading.Contract/BackendContracts/AccountsManagement/SetTradingConditionModel.cs +++ b/src/MarginTrading.Contract/BackendContracts/AccountsManagement/SetTradingConditionModel.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts.AccountsManagement +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts.AccountsManagement { public class SetTradingConditionModel { diff --git a/src/MarginTrading.Contract/BackendContracts/AggregatedOrderBookItemBackendContract.cs b/src/MarginTrading.Contract/BackendContracts/AggregatedOrderBookItemBackendContract.cs index dbbc914cd..55f078d3c 100644 --- a/src/MarginTrading.Contract/BackendContracts/AggregatedOrderBookItemBackendContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/AggregatedOrderBookItemBackendContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public class AggregatedOrderBookItemBackendContract { diff --git a/src/MarginTrading.Contract/BackendContracts/AggregatedOrderbookBackendResponse.cs b/src/MarginTrading.Contract/BackendContracts/AggregatedOrderbookBackendResponse.cs index 9999b2fd6..7c11ddeaf 100644 --- a/src/MarginTrading.Contract/BackendContracts/AggregatedOrderbookBackendResponse.cs +++ b/src/MarginTrading.Contract/BackendContracts/AggregatedOrderbookBackendResponse.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public class AggregatedOrderbookBackendResponse { diff --git a/src/MarginTrading.Contract/BackendContracts/AssetPairBackendContract.cs b/src/MarginTrading.Contract/BackendContracts/AssetPairBackendContract.cs index 4a438c9f6..53889cabe 100644 --- a/src/MarginTrading.Contract/BackendContracts/AssetPairBackendContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/AssetPairBackendContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public class AssetPairBackendContract { diff --git a/src/MarginTrading.Contract/BackendContracts/ChangeOrderLimitsBackendRequest.cs b/src/MarginTrading.Contract/BackendContracts/ChangeOrderLimitsBackendRequest.cs index 93309aa97..fb6c3b2cc 100644 --- a/src/MarginTrading.Contract/BackendContracts/ChangeOrderLimitsBackendRequest.cs +++ b/src/MarginTrading.Contract/BackendContracts/ChangeOrderLimitsBackendRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public class ChangeOrderLimitsBackendRequest { diff --git a/src/MarginTrading.Contract/BackendContracts/ClientIdBackendRequest.cs b/src/MarginTrading.Contract/BackendContracts/ClientIdBackendRequest.cs deleted file mode 100644 index 2a851bc83..000000000 --- a/src/MarginTrading.Contract/BackendContracts/ClientIdBackendRequest.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MarginTrading.Contract.BackendContracts -{ - public class ClientIdBackendRequest - { - public string ClientId { get; set; } - } -} diff --git a/src/MarginTrading.Contract/BackendContracts/ClientOrdersBackendResponse.cs b/src/MarginTrading.Contract/BackendContracts/ClientOrdersBackendResponse.cs index 53b74a588..d27b94e4d 100644 --- a/src/MarginTrading.Contract/BackendContracts/ClientOrdersBackendResponse.cs +++ b/src/MarginTrading.Contract/BackendContracts/ClientOrdersBackendResponse.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public class ClientOrdersBackendResponse { diff --git a/src/MarginTrading.Contract/BackendContracts/GraphBidAskPairBackendContract.cs b/src/MarginTrading.Contract/BackendContracts/GraphBidAskPairBackendContract.cs index 117abca55..45ae23446 100644 --- a/src/MarginTrading.Contract/BackendContracts/GraphBidAskPairBackendContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/GraphBidAskPairBackendContract.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Contract.BackendContracts { diff --git a/src/MarginTrading.Contract/BackendContracts/InitAccountInstrumentsBackendResponse.cs b/src/MarginTrading.Contract/BackendContracts/InitAccountInstrumentsBackendResponse.cs index b86042cf4..89d2ed735 100644 --- a/src/MarginTrading.Contract/BackendContracts/InitAccountInstrumentsBackendResponse.cs +++ b/src/MarginTrading.Contract/BackendContracts/InitAccountInstrumentsBackendResponse.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; -using System.Linq; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using MarginTrading.Contract.BackendContracts.TradingConditions; namespace MarginTrading.Contract.BackendContracts diff --git a/src/MarginTrading.Contract/BackendContracts/InitChartDataBackendRequest.cs b/src/MarginTrading.Contract/BackendContracts/InitChartDataBackendRequest.cs deleted file mode 100644 index d2d513669..000000000 --- a/src/MarginTrading.Contract/BackendContracts/InitChartDataBackendRequest.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MarginTrading.Contract.BackendContracts -{ - public class InitChartDataBackendRequest : ClientIdBackendRequest - { - public string[] AssetIds { get; set; } - } -} diff --git a/src/MarginTrading.Contract/BackendContracts/InitChartDataBackendResponse.cs b/src/MarginTrading.Contract/BackendContracts/InitChartDataBackendResponse.cs deleted file mode 100644 index 07f1445ba..000000000 --- a/src/MarginTrading.Contract/BackendContracts/InitChartDataBackendResponse.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace MarginTrading.Contract.BackendContracts -{ - public class InitChartDataBackendResponse - { - public Dictionary ChartData { get; set; } - } -} diff --git a/src/MarginTrading.Contract/BackendContracts/InitDataBackendResponse.cs b/src/MarginTrading.Contract/BackendContracts/InitDataBackendResponse.cs deleted file mode 100644 index 6b34b9861..000000000 --- a/src/MarginTrading.Contract/BackendContracts/InitDataBackendResponse.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MarginTrading.Contract.BackendContracts.TradingConditions; - -namespace MarginTrading.Contract.BackendContracts -{ - public class InitDataBackendResponse - { - public MarginTradingAccountBackendContract[] Accounts { get; set; } - public Dictionary AccountAssetPairs { get; set; } - public bool IsLive { get; set; } - - public static InitDataBackendResponse CreateEmpty() - { - return new InitDataBackendResponse - { - Accounts = Array.Empty(), - AccountAssetPairs = new Dictionary(), - }; - } - } -} diff --git a/src/MarginTrading.Contract/BackendContracts/InitPricesBackendRequest.cs b/src/MarginTrading.Contract/BackendContracts/InitPricesBackendRequest.cs deleted file mode 100644 index 57101d372..000000000 --- a/src/MarginTrading.Contract/BackendContracts/InitPricesBackendRequest.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MarginTrading.Contract.BackendContracts -{ - public class InitPricesBackendRequest : ClientIdBackendRequest - { - public string[] AssetIds { get; set; } - } -} diff --git a/src/MarginTrading.Contract/BackendContracts/InstrumentBackendRequest.cs b/src/MarginTrading.Contract/BackendContracts/InstrumentBackendRequest.cs index f86d1a821..4c195d920 100644 --- a/src/MarginTrading.Contract/BackendContracts/InstrumentBackendRequest.cs +++ b/src/MarginTrading.Contract/BackendContracts/InstrumentBackendRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public class InstrumentBackendRequest { diff --git a/src/MarginTrading.Contract/BackendContracts/InstrumentBidAskPairContract.cs b/src/MarginTrading.Contract/BackendContracts/InstrumentBidAskPairContract.cs index a8e94d976..3c578e41d 100644 --- a/src/MarginTrading.Contract/BackendContracts/InstrumentBidAskPairContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/InstrumentBidAskPairContract.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Contract.BackendContracts { diff --git a/src/MarginTrading.Contract/BackendContracts/IsAliveResponse.cs b/src/MarginTrading.Contract/BackendContracts/IsAliveResponse.cs index 8510df285..65b398d32 100644 --- a/src/MarginTrading.Contract/BackendContracts/IsAliveResponse.cs +++ b/src/MarginTrading.Contract/BackendContracts/IsAliveResponse.cs @@ -1,11 +1,12 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Contract.BackendContracts { public class IsAliveResponse { - public bool MatchingEngineAlive { get; set; } - public bool TradingEngineAlive { get; set; } public string Version { get; set; } public string Env { get; set; } public DateTime ServerTime { get; set; } diff --git a/src/MarginTrading.Contract/BackendContracts/LimitOrderBackendContract.cs b/src/MarginTrading.Contract/BackendContracts/LimitOrderBackendContract.cs index d0add0642..542ce9ea0 100644 --- a/src/MarginTrading.Contract/BackendContracts/LimitOrderBackendContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/LimitOrderBackendContract.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Contract.BackendContracts { diff --git a/src/MarginTrading.Contract/BackendContracts/MarginTradingAccountBackendContract.cs b/src/MarginTrading.Contract/BackendContracts/MarginTradingAccountBackendContract.cs index a50e60785..d326bf11c 100644 --- a/src/MarginTrading.Contract/BackendContracts/MarginTradingAccountBackendContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/MarginTradingAccountBackendContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public class MarginTradingAccountBackendContract { diff --git a/src/MarginTrading.Contract/BackendContracts/MatchedOrderBackendContract.cs b/src/MarginTrading.Contract/BackendContracts/MatchedOrderBackendContract.cs index d46cb2e38..c230750b3 100644 --- a/src/MarginTrading.Contract/BackendContracts/MatchedOrderBackendContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/MatchedOrderBackendContract.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Contract.BackendContracts { diff --git a/src/MarginTrading.Contract/BackendContracts/MatchingEngineModeContract.cs b/src/MarginTrading.Contract/BackendContracts/MatchingEngineModeContract.cs index 232998885..cc459ca27 100644 --- a/src/MarginTrading.Contract/BackendContracts/MatchingEngineModeContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/MatchingEngineModeContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public enum MatchingEngineModeContract { diff --git a/src/MarginTrading.Contract/BackendContracts/MtBackendResponse.cs b/src/MarginTrading.Contract/BackendContracts/MtBackendResponse.cs index b6716c720..7d83fc3bc 100644 --- a/src/MarginTrading.Contract/BackendContracts/MtBackendResponse.cs +++ b/src/MarginTrading.Contract/BackendContracts/MtBackendResponse.cs @@ -1,9 +1,12 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public class MtBackendResponse { public T Result { get; set; } - public string Message { get; set; } + public string ErrorMessage { get; set; } public static MtBackendResponse Ok(T result) { @@ -17,7 +20,7 @@ public static MtBackendResponse Error(string message) { return new MtBackendResponse { - Message = message + ErrorMessage = message }; } } diff --git a/src/MarginTrading.Contract/BackendContracts/NewMatchingEngineRouteRequest.cs b/src/MarginTrading.Contract/BackendContracts/NewMatchingEngineRouteRequest.cs index 9094b850b..841a5f22e 100644 --- a/src/MarginTrading.Contract/BackendContracts/NewMatchingEngineRouteRequest.cs +++ b/src/MarginTrading.Contract/BackendContracts/NewMatchingEngineRouteRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public class NewMatchingEngineRouteRequest { diff --git a/src/MarginTrading.Contract/BackendContracts/NewOrderBackendContract.cs b/src/MarginTrading.Contract/BackendContracts/NewOrderBackendContract.cs index 481861478..60729c5cc 100644 --- a/src/MarginTrading.Contract/BackendContracts/NewOrderBackendContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/NewOrderBackendContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public class NewOrderBackendContract { diff --git a/src/MarginTrading.Contract/BackendContracts/OpenOrderBackendRequest.cs b/src/MarginTrading.Contract/BackendContracts/OpenOrderBackendRequest.cs index 5504fccf4..44f6b800b 100644 --- a/src/MarginTrading.Contract/BackendContracts/OpenOrderBackendRequest.cs +++ b/src/MarginTrading.Contract/BackendContracts/OpenOrderBackendRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public class OpenOrderBackendRequest { diff --git a/src/MarginTrading.Contract/BackendContracts/OpenOrderBackendResponse.cs b/src/MarginTrading.Contract/BackendContracts/OpenOrderBackendResponse.cs index 9269201ee..a6590f69d 100644 --- a/src/MarginTrading.Contract/BackendContracts/OpenOrderBackendResponse.cs +++ b/src/MarginTrading.Contract/BackendContracts/OpenOrderBackendResponse.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public class OpenOrderBackendResponse { diff --git a/src/MarginTrading.Contract/BackendContracts/OrderBackendContract.cs b/src/MarginTrading.Contract/BackendContracts/OrderBackendContract.cs index 797d5f110..7b8f9b384 100644 --- a/src/MarginTrading.Contract/BackendContracts/OrderBackendContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/OrderBackendContract.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using Newtonsoft.Json; using Newtonsoft.Json.Converters; diff --git a/src/MarginTrading.Contract/BackendContracts/OrderBookBackendContract.cs b/src/MarginTrading.Contract/BackendContracts/OrderBookBackendContract.cs index 88e805d6c..dc26e2b8b 100644 --- a/src/MarginTrading.Contract/BackendContracts/OrderBookBackendContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/OrderBookBackendContract.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; namespace MarginTrading.Contract.BackendContracts { diff --git a/src/MarginTrading.Contract/BackendContracts/OrderCloseReasonContract.cs b/src/MarginTrading.Contract/BackendContracts/OrderCloseReasonContract.cs index adda9b57a..7c4296b5b 100644 --- a/src/MarginTrading.Contract/BackendContracts/OrderCloseReasonContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/OrderCloseReasonContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public enum OrderCloseReasonContract { diff --git a/src/MarginTrading.Contract/BackendContracts/OrderContract.cs b/src/MarginTrading.Contract/BackendContracts/OrderContract.cs index 401fe0339..7f9508ddb 100644 --- a/src/MarginTrading.Contract/BackendContracts/OrderContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/OrderContract.cs @@ -1,3 +1,6 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Newtonsoft.Json; @@ -9,7 +12,6 @@ public class OrderContract { public string Id { get; set; } public long Code { get; set; } - public string ClientId { get; set; } public string AccountId { get; set; } public string AccountAssetId { get; set; } public string Instrument { get; set; } @@ -51,5 +53,10 @@ public class OrderContract [JsonConverter(typeof(StringEnumConverter))] public MatchingEngineModeContract MatchingEngineMode { get; set; } + + /// + /// Number of pending order retries passed + /// + public int PendingOrderRetriesCount { get; set; } } } \ No newline at end of file diff --git a/src/MarginTrading.Contract/BackendContracts/OrderDirectionContract.cs b/src/MarginTrading.Contract/BackendContracts/OrderDirectionContract.cs index 3d642c8fd..26b8f0ceb 100644 --- a/src/MarginTrading.Contract/BackendContracts/OrderDirectionContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/OrderDirectionContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public enum OrderDirectionContract { diff --git a/src/MarginTrading.Contract/BackendContracts/OrderFillTypeContract.cs b/src/MarginTrading.Contract/BackendContracts/OrderFillTypeContract.cs index 81d25d90b..15a97502c 100644 --- a/src/MarginTrading.Contract/BackendContracts/OrderFillTypeContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/OrderFillTypeContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public enum OrderFillTypeContract { diff --git a/src/MarginTrading.Contract/BackendContracts/OrderFullContract.cs b/src/MarginTrading.Contract/BackendContracts/OrderFullContract.cs index 0070972cb..a4644fbec 100644 --- a/src/MarginTrading.Contract/BackendContracts/OrderFullContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/OrderFullContract.cs @@ -1,3 +1,6 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + using System; namespace MarginTrading.Contract.BackendContracts @@ -14,10 +17,12 @@ public class OrderFullContract : OrderContract public decimal MarginRate { get; set; } public decimal MarginInit { get; set; } public decimal MarginMaintenance { get; set; } + public DateTime UpdateTimestamp { get; set; } /// /// Business operation type which caused last change /// public OrderUpdateTypeContract OrderUpdateType { get; set; } + } } diff --git a/src/MarginTrading.Contract/BackendContracts/OrderHistoryBackendContract.cs b/src/MarginTrading.Contract/BackendContracts/OrderHistoryBackendContract.cs index 94502ecb5..26f86d84c 100644 --- a/src/MarginTrading.Contract/BackendContracts/OrderHistoryBackendContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/OrderHistoryBackendContract.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using Newtonsoft.Json; using Newtonsoft.Json.Converters; diff --git a/src/MarginTrading.Contract/BackendContracts/OrderRejectReasonContract.cs b/src/MarginTrading.Contract/BackendContracts/OrderRejectReasonContract.cs index cd03acd94..86605ec2a 100644 --- a/src/MarginTrading.Contract/BackendContracts/OrderRejectReasonContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/OrderRejectReasonContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public enum OrderRejectReasonContract { @@ -13,7 +16,15 @@ public enum OrderRejectReasonContract InvalidStoploss, InvalidInstrument, InvalidAccount, + InvalidParent, TradingConditionError, - TechnicalError + InvalidValidity, + TechnicalError, + ParentPositionDoesNotExist, + ParentPositionIsNotActive, + ShortPositionsDisabled, + MaxPositionLimit, + MinOrderSizeLimit, + MaxOrderSizeLimit, } } \ No newline at end of file diff --git a/src/MarginTrading.Contract/BackendContracts/OrderStatusContract.cs b/src/MarginTrading.Contract/BackendContracts/OrderStatusContract.cs index f536c4df7..cb465133f 100644 --- a/src/MarginTrading.Contract/BackendContracts/OrderStatusContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/OrderStatusContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public enum OrderStatusContract { diff --git a/src/MarginTrading.Contract/BackendContracts/OrderUpdateTypeContract.cs b/src/MarginTrading.Contract/BackendContracts/OrderUpdateTypeContract.cs index 4a40fe48e..0cdef2021 100644 --- a/src/MarginTrading.Contract/BackendContracts/OrderUpdateTypeContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/OrderUpdateTypeContract.cs @@ -1,4 +1,7 @@ -using JetBrains.Annotations; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using JetBrains.Annotations; namespace MarginTrading.Contract.BackendContracts { diff --git a/src/MarginTrading.Contract/BackendContracts/OrderbooksBackendRequest.cs b/src/MarginTrading.Contract/BackendContracts/OrderbooksBackendRequest.cs index 889e6fd9a..653dceab5 100644 --- a/src/MarginTrading.Contract/BackendContracts/OrderbooksBackendRequest.cs +++ b/src/MarginTrading.Contract/BackendContracts/OrderbooksBackendRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public class OrderbooksBackendRequest { diff --git a/src/MarginTrading.Contract/BackendContracts/OrderbooksBackendResponse.cs b/src/MarginTrading.Contract/BackendContracts/OrderbooksBackendResponse.cs index 8bb916685..bb2b9a99f 100644 --- a/src/MarginTrading.Contract/BackendContracts/OrderbooksBackendResponse.cs +++ b/src/MarginTrading.Contract/BackendContracts/OrderbooksBackendResponse.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public class OrderbooksBackendResponse { diff --git a/src/MarginTrading.Contract/BackendContracts/SetActiveAccountBackendRequest.cs b/src/MarginTrading.Contract/BackendContracts/SetActiveAccountBackendRequest.cs index 9350dcf6d..7c8310b94 100644 --- a/src/MarginTrading.Contract/BackendContracts/SetActiveAccountBackendRequest.cs +++ b/src/MarginTrading.Contract/BackendContracts/SetActiveAccountBackendRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts { public class SetActiveAccountBackendRequest { diff --git a/src/MarginTrading.Contract/BackendContracts/TradingConditions/AccountAssetPairBackendContract.cs b/src/MarginTrading.Contract/BackendContracts/TradingConditions/AccountAssetPairBackendContract.cs index 63ed56f84..fd9818b03 100644 --- a/src/MarginTrading.Contract/BackendContracts/TradingConditions/AccountAssetPairBackendContract.cs +++ b/src/MarginTrading.Contract/BackendContracts/TradingConditions/AccountAssetPairBackendContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts.TradingConditions +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts.TradingConditions { public class AccountAssetPairModel { diff --git a/src/MarginTrading.Contract/BackendContracts/TradingConditions/AccountGroupModel.cs b/src/MarginTrading.Contract/BackendContracts/TradingConditions/AccountGroupModel.cs index b2562b81a..84d2f27ca 100644 --- a/src/MarginTrading.Contract/BackendContracts/TradingConditions/AccountGroupModel.cs +++ b/src/MarginTrading.Contract/BackendContracts/TradingConditions/AccountGroupModel.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts.TradingConditions +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts.TradingConditions { public class AccountGroupModel { diff --git a/src/MarginTrading.Contract/BackendContracts/TradingConditions/AssignInstrumentsRequest.cs b/src/MarginTrading.Contract/BackendContracts/TradingConditions/AssignInstrumentsRequest.cs index d816867e1..9573de82a 100644 --- a/src/MarginTrading.Contract/BackendContracts/TradingConditions/AssignInstrumentsRequest.cs +++ b/src/MarginTrading.Contract/BackendContracts/TradingConditions/AssignInstrumentsRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts.TradingConditions +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts.TradingConditions { public class AssignInstrumentsRequest { diff --git a/src/MarginTrading.Contract/BackendContracts/TradingConditions/TradingConditionModel.cs b/src/MarginTrading.Contract/BackendContracts/TradingConditions/TradingConditionModel.cs index 5e4eaaa78..f3737c8e9 100644 --- a/src/MarginTrading.Contract/BackendContracts/TradingConditions/TradingConditionModel.cs +++ b/src/MarginTrading.Contract/BackendContracts/TradingConditions/TradingConditionModel.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.BackendContracts.TradingConditions +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.BackendContracts.TradingConditions { public class TradingConditionModel { diff --git a/src/MarginTrading.Contract/ClientContracts/AccountAssetPairClientContract.cs b/src/MarginTrading.Contract/ClientContracts/AccountAssetPairClientContract.cs index 3d2c71da6..b4859f782 100644 --- a/src/MarginTrading.Contract/ClientContracts/AccountAssetPairClientContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/AccountAssetPairClientContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class AccountAssetPairClientContract { diff --git a/src/MarginTrading.Contract/ClientContracts/AccountHistoryClientContract.cs b/src/MarginTrading.Contract/ClientContracts/AccountHistoryClientContract.cs index 280a981db..86d76c53f 100644 --- a/src/MarginTrading.Contract/ClientContracts/AccountHistoryClientContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/AccountHistoryClientContract.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using MarginTrading.Contract.BackendContracts; namespace MarginTrading.Contract.ClientContracts diff --git a/src/MarginTrading.Contract/ClientContracts/AccountHistoryClientResponse.cs b/src/MarginTrading.Contract/ClientContracts/AccountHistoryClientResponse.cs index 9fa279d24..a08262e93 100644 --- a/src/MarginTrading.Contract/ClientContracts/AccountHistoryClientResponse.cs +++ b/src/MarginTrading.Contract/ClientContracts/AccountHistoryClientResponse.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class AccountHistoryClientResponse { diff --git a/src/MarginTrading.Contract/ClientContracts/AccountHistoryFiltersClientRequest.cs b/src/MarginTrading.Contract/ClientContracts/AccountHistoryFiltersClientRequest.cs index 4b9d15a1b..0cffd3743 100644 --- a/src/MarginTrading.Contract/ClientContracts/AccountHistoryFiltersClientRequest.cs +++ b/src/MarginTrading.Contract/ClientContracts/AccountHistoryFiltersClientRequest.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Contract.ClientContracts { diff --git a/src/MarginTrading.Contract/ClientContracts/AccountHistoryItemClient.cs b/src/MarginTrading.Contract/ClientContracts/AccountHistoryItemClient.cs index 8a6c21ace..649206702 100644 --- a/src/MarginTrading.Contract/ClientContracts/AccountHistoryItemClient.cs +++ b/src/MarginTrading.Contract/ClientContracts/AccountHistoryItemClient.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Contract.ClientContracts { diff --git a/src/MarginTrading.Contract/ClientContracts/AccountHistoryRpcClientRequest.cs b/src/MarginTrading.Contract/ClientContracts/AccountHistoryRpcClientRequest.cs index 653929d8f..e10c7331c 100644 --- a/src/MarginTrading.Contract/ClientContracts/AccountHistoryRpcClientRequest.cs +++ b/src/MarginTrading.Contract/ClientContracts/AccountHistoryRpcClientRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class AccountHistoryRpcClientRequest : AccountHistoryFiltersClientRequest { diff --git a/src/MarginTrading.Contract/ClientContracts/AccountStopoutBackendContract.cs b/src/MarginTrading.Contract/ClientContracts/AccountStopoutBackendContract.cs index 95b6e4968..671f3a94e 100644 --- a/src/MarginTrading.Contract/ClientContracts/AccountStopoutBackendContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/AccountStopoutBackendContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class AccountStopoutBackendContract { diff --git a/src/MarginTrading.Contract/ClientContracts/AccountStopoutClientContract.cs b/src/MarginTrading.Contract/ClientContracts/AccountStopoutClientContract.cs index 703c593c7..ee305bae9 100644 --- a/src/MarginTrading.Contract/ClientContracts/AccountStopoutClientContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/AccountStopoutClientContract.cs @@ -1,4 +1,7 @@ -using System.ComponentModel; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; namespace MarginTrading.Contract.ClientContracts { diff --git a/src/MarginTrading.Contract/ClientContracts/AccountTokenClientRequest.cs b/src/MarginTrading.Contract/ClientContracts/AccountTokenClientRequest.cs index 68226a1b9..46ef060eb 100644 --- a/src/MarginTrading.Contract/ClientContracts/AccountTokenClientRequest.cs +++ b/src/MarginTrading.Contract/ClientContracts/AccountTokenClientRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class AccountTokenClientRequest { diff --git a/src/MarginTrading.Contract/ClientContracts/AggregatedOrderBookItemClientContract.cs b/src/MarginTrading.Contract/ClientContracts/AggregatedOrderBookItemClientContract.cs index ddfd22d0d..3d98574e7 100644 --- a/src/MarginTrading.Contract/ClientContracts/AggregatedOrderBookItemClientContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/AggregatedOrderBookItemClientContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class AggregatedOrderBookItemClientContract { diff --git a/src/MarginTrading.Contract/ClientContracts/AggregatedOrderbookClientContract.cs b/src/MarginTrading.Contract/ClientContracts/AggregatedOrderbookClientContract.cs index bc672caa8..8c752e131 100644 --- a/src/MarginTrading.Contract/ClientContracts/AggregatedOrderbookClientContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/AggregatedOrderbookClientContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class AggregatedOrderbookClientContract { diff --git a/src/MarginTrading.Contract/ClientContracts/AggregatedOrderbookLiveDemoClientContract.cs b/src/MarginTrading.Contract/ClientContracts/AggregatedOrderbookLiveDemoClientContract.cs index 18b3b7cee..a157bd4e3 100644 --- a/src/MarginTrading.Contract/ClientContracts/AggregatedOrderbookLiveDemoClientContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/AggregatedOrderbookLiveDemoClientContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class AggregatedOrderbookLiveDemoClientContract { diff --git a/src/MarginTrading.Contract/ClientContracts/AssetPairClientContract.cs b/src/MarginTrading.Contract/ClientContracts/AssetPairClientContract.cs index 3b3b5b8b6..3f9c52a77 100644 --- a/src/MarginTrading.Contract/ClientContracts/AssetPairClientContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/AssetPairClientContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class AssetPairClientContract { diff --git a/src/MarginTrading.Contract/ClientContracts/BidAskClientContract.cs b/src/MarginTrading.Contract/ClientContracts/BidAskClientContract.cs index 885a47a44..d3ee64f45 100644 --- a/src/MarginTrading.Contract/ClientContracts/BidAskClientContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/BidAskClientContract.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Contract.ClientContracts { diff --git a/src/MarginTrading.Contract/ClientContracts/ChangeOrderLimitsClientRequest.cs b/src/MarginTrading.Contract/ClientContracts/ChangeOrderLimitsClientRequest.cs index 3be43cded..30d277c0d 100644 --- a/src/MarginTrading.Contract/ClientContracts/ChangeOrderLimitsClientRequest.cs +++ b/src/MarginTrading.Contract/ClientContracts/ChangeOrderLimitsClientRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class ChangeOrderLimitsClientRequest { diff --git a/src/MarginTrading.Contract/ClientContracts/ChangeOrderLimitsRpcClientRequest.cs b/src/MarginTrading.Contract/ClientContracts/ChangeOrderLimitsRpcClientRequest.cs index a7e2da67d..7fcc96098 100644 --- a/src/MarginTrading.Contract/ClientContracts/ChangeOrderLimitsRpcClientRequest.cs +++ b/src/MarginTrading.Contract/ClientContracts/ChangeOrderLimitsRpcClientRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class ChangeOrderLimitsRpcClientRequest : ChangeOrderLimitsClientRequest { diff --git a/src/MarginTrading.Contract/ClientContracts/ClientOrdersClientResponse.cs b/src/MarginTrading.Contract/ClientContracts/ClientOrdersClientResponse.cs index a2479ec8e..98df47549 100644 --- a/src/MarginTrading.Contract/ClientContracts/ClientOrdersClientResponse.cs +++ b/src/MarginTrading.Contract/ClientContracts/ClientOrdersClientResponse.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class ClientOrdersClientResponse { diff --git a/src/MarginTrading.Contract/ClientContracts/ClientOrdersLiveDemoClientResponse.cs b/src/MarginTrading.Contract/ClientContracts/ClientOrdersLiveDemoClientResponse.cs index 8c069a65e..5b5206a25 100644 --- a/src/MarginTrading.Contract/ClientContracts/ClientOrdersLiveDemoClientResponse.cs +++ b/src/MarginTrading.Contract/ClientContracts/ClientOrdersLiveDemoClientResponse.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class ClientOrdersLiveDemoClientResponse { diff --git a/src/MarginTrading.Contract/ClientContracts/ClientPositionsLiveDemoClientResponse.cs b/src/MarginTrading.Contract/ClientContracts/ClientPositionsLiveDemoClientResponse.cs index ca2b71348..64b518f54 100644 --- a/src/MarginTrading.Contract/ClientContracts/ClientPositionsLiveDemoClientResponse.cs +++ b/src/MarginTrading.Contract/ClientContracts/ClientPositionsLiveDemoClientResponse.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class ClientPositionsLiveDemoClientResponse { diff --git a/src/MarginTrading.Contract/ClientContracts/CloseOrderClientRequest.cs b/src/MarginTrading.Contract/ClientContracts/CloseOrderClientRequest.cs index fc486fb0c..5b2bed5c9 100644 --- a/src/MarginTrading.Contract/ClientContracts/CloseOrderClientRequest.cs +++ b/src/MarginTrading.Contract/ClientContracts/CloseOrderClientRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class CloseOrderClientRequest { diff --git a/src/MarginTrading.Contract/ClientContracts/CloseOrderRpcClientRequest.cs b/src/MarginTrading.Contract/ClientContracts/CloseOrderRpcClientRequest.cs index d77c3a139..7ce0d0931 100644 --- a/src/MarginTrading.Contract/ClientContracts/CloseOrderRpcClientRequest.cs +++ b/src/MarginTrading.Contract/ClientContracts/CloseOrderRpcClientRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class CloseOrderRpcClientRequest : CloseOrderClientRequest { diff --git a/src/MarginTrading.Contract/ClientContracts/DepositWithdrawClientRequest.cs b/src/MarginTrading.Contract/ClientContracts/DepositWithdrawClientRequest.cs index 1404fc4f9..6c0bfba12 100644 --- a/src/MarginTrading.Contract/ClientContracts/DepositWithdrawClientRequest.cs +++ b/src/MarginTrading.Contract/ClientContracts/DepositWithdrawClientRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class DepositWithdrawClientRequest { diff --git a/src/MarginTrading.Contract/ClientContracts/GraphBidAskPairClientContract.cs b/src/MarginTrading.Contract/ClientContracts/GraphBidAskPairClientContract.cs index 0fb178289..bc2c9dc8d 100644 --- a/src/MarginTrading.Contract/ClientContracts/GraphBidAskPairClientContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/GraphBidAskPairClientContract.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Contract.ClientContracts { diff --git a/src/MarginTrading.Contract/ClientContracts/InitAccountInstrumentsClientResponse.cs b/src/MarginTrading.Contract/ClientContracts/InitAccountInstrumentsClientResponse.cs index da1721081..3765e3f39 100644 --- a/src/MarginTrading.Contract/ClientContracts/InitAccountInstrumentsClientResponse.cs +++ b/src/MarginTrading.Contract/ClientContracts/InitAccountInstrumentsClientResponse.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; namespace MarginTrading.Contract.ClientContracts { diff --git a/src/MarginTrading.Contract/ClientContracts/InitAccountInstrumentsLiveDemoClientResponse.cs b/src/MarginTrading.Contract/ClientContracts/InitAccountInstrumentsLiveDemoClientResponse.cs index 3cb2a61d4..60853d9fd 100644 --- a/src/MarginTrading.Contract/ClientContracts/InitAccountInstrumentsLiveDemoClientResponse.cs +++ b/src/MarginTrading.Contract/ClientContracts/InitAccountInstrumentsLiveDemoClientResponse.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class InitAccountInstrumentsLiveDemoClientResponse { diff --git a/src/MarginTrading.Contract/ClientContracts/InitAccountsLiveDemoClientResponse.cs b/src/MarginTrading.Contract/ClientContracts/InitAccountsLiveDemoClientResponse.cs index ecb812512..fd14e4d6e 100644 --- a/src/MarginTrading.Contract/ClientContracts/InitAccountsLiveDemoClientResponse.cs +++ b/src/MarginTrading.Contract/ClientContracts/InitAccountsLiveDemoClientResponse.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class InitAccountsLiveDemoClientResponse { diff --git a/src/MarginTrading.Contract/ClientContracts/InitChartDataClientRequest.cs b/src/MarginTrading.Contract/ClientContracts/InitChartDataClientRequest.cs index 8063ab24c..143a73e55 100644 --- a/src/MarginTrading.Contract/ClientContracts/InitChartDataClientRequest.cs +++ b/src/MarginTrading.Contract/ClientContracts/InitChartDataClientRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class InitChartDataClientRequest { diff --git a/src/MarginTrading.Contract/ClientContracts/InitChartDataClientResponse.cs b/src/MarginTrading.Contract/ClientContracts/InitChartDataClientResponse.cs index 16091f5d4..07a576229 100644 --- a/src/MarginTrading.Contract/ClientContracts/InitChartDataClientResponse.cs +++ b/src/MarginTrading.Contract/ClientContracts/InitChartDataClientResponse.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; namespace MarginTrading.Contract.ClientContracts { diff --git a/src/MarginTrading.Contract/ClientContracts/InitDataClientResponse.cs b/src/MarginTrading.Contract/ClientContracts/InitDataClientResponse.cs index 3ee7e6a59..8a0eff12b 100644 --- a/src/MarginTrading.Contract/ClientContracts/InitDataClientResponse.cs +++ b/src/MarginTrading.Contract/ClientContracts/InitDataClientResponse.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; namespace MarginTrading.Contract.ClientContracts { diff --git a/src/MarginTrading.Contract/ClientContracts/InitDataLiveDemoClientResponse.cs b/src/MarginTrading.Contract/ClientContracts/InitDataLiveDemoClientResponse.cs index 3c79600a1..8c0976145 100644 --- a/src/MarginTrading.Contract/ClientContracts/InitDataLiveDemoClientResponse.cs +++ b/src/MarginTrading.Contract/ClientContracts/InitDataLiveDemoClientResponse.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; namespace MarginTrading.Contract.ClientContracts { diff --git a/src/MarginTrading.Contract/ClientContracts/InitPricesFilteredRequest.cs b/src/MarginTrading.Contract/ClientContracts/InitPricesFilteredRequest.cs index 9d681dcde..df0ae1f8c 100644 --- a/src/MarginTrading.Contract/ClientContracts/InitPricesFilteredRequest.cs +++ b/src/MarginTrading.Contract/ClientContracts/InitPricesFilteredRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class InitPricesFilteredRequest { diff --git a/src/MarginTrading.Contract/ClientContracts/InitPricesResponse.cs b/src/MarginTrading.Contract/ClientContracts/InitPricesResponse.cs index f2794a414..d6bed3cb7 100644 --- a/src/MarginTrading.Contract/ClientContracts/InitPricesResponse.cs +++ b/src/MarginTrading.Contract/ClientContracts/InitPricesResponse.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Contract.ClientContracts { diff --git a/src/MarginTrading.Contract/ClientContracts/IsAliveExtendedResponse.cs b/src/MarginTrading.Contract/ClientContracts/IsAliveExtendedResponse.cs index 6413cfd41..ea23fe1df 100644 --- a/src/MarginTrading.Contract/ClientContracts/IsAliveExtendedResponse.cs +++ b/src/MarginTrading.Contract/ClientContracts/IsAliveExtendedResponse.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class IsAliveExtendedResponse : IsAliveResponse { diff --git a/src/MarginTrading.Contract/ClientContracts/IsAliveResponse.cs b/src/MarginTrading.Contract/ClientContracts/IsAliveResponse.cs index 4aab7ac80..9fb8eedd0 100644 --- a/src/MarginTrading.Contract/ClientContracts/IsAliveResponse.cs +++ b/src/MarginTrading.Contract/ClientContracts/IsAliveResponse.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; namespace MarginTrading.Contract.ClientContracts { diff --git a/src/MarginTrading.Contract/ClientContracts/MarginTradingAccountClientContract.cs b/src/MarginTrading.Contract/ClientContracts/MarginTradingAccountClientContract.cs index d1f142236..0b636a5a2 100644 --- a/src/MarginTrading.Contract/ClientContracts/MarginTradingAccountClientContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/MarginTradingAccountClientContract.cs @@ -1,4 +1,7 @@ -using System.ComponentModel; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; namespace MarginTrading.Contract.ClientContracts { diff --git a/src/MarginTrading.Contract/ClientContracts/MtClientResponse.cs b/src/MarginTrading.Contract/ClientContracts/MtClientResponse.cs index 56b249de8..986e5745d 100644 --- a/src/MarginTrading.Contract/ClientContracts/MtClientResponse.cs +++ b/src/MarginTrading.Contract/ClientContracts/MtClientResponse.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class MtClientResponse { diff --git a/src/MarginTrading.Contract/ClientContracts/NewOrderClientContract.cs b/src/MarginTrading.Contract/ClientContracts/NewOrderClientContract.cs index ee887e73d..33d5d0d7d 100644 --- a/src/MarginTrading.Contract/ClientContracts/NewOrderClientContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/NewOrderClientContract.cs @@ -1,4 +1,7 @@ -using MarginTrading.Contract.BackendContracts; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Contract.BackendContracts; namespace MarginTrading.Contract.ClientContracts { diff --git a/src/MarginTrading.Contract/ClientContracts/NotifyEntityType.cs b/src/MarginTrading.Contract/ClientContracts/NotifyEntityType.cs index d8f227326..85c87c9ee 100644 --- a/src/MarginTrading.Contract/ClientContracts/NotifyEntityType.cs +++ b/src/MarginTrading.Contract/ClientContracts/NotifyEntityType.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public enum NotifyEntityType { diff --git a/src/MarginTrading.Contract/ClientContracts/NotifyResponse.cs b/src/MarginTrading.Contract/ClientContracts/NotifyResponse.cs index 59b176035..522cbc38b 100644 --- a/src/MarginTrading.Contract/ClientContracts/NotifyResponse.cs +++ b/src/MarginTrading.Contract/ClientContracts/NotifyResponse.cs @@ -1,4 +1,7 @@ -using System.ComponentModel; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; namespace MarginTrading.Contract.ClientContracts { diff --git a/src/MarginTrading.Contract/ClientContracts/OpenOrderRpcClientRequest.cs b/src/MarginTrading.Contract/ClientContracts/OpenOrderRpcClientRequest.cs index 009024996..86411eee6 100644 --- a/src/MarginTrading.Contract/ClientContracts/OpenOrderRpcClientRequest.cs +++ b/src/MarginTrading.Contract/ClientContracts/OpenOrderRpcClientRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class OpenOrderRpcClientRequest { diff --git a/src/MarginTrading.Contract/ClientContracts/OrderBookClientContract.cs b/src/MarginTrading.Contract/ClientContracts/OrderBookClientContract.cs index 4e9978fea..f48adf152 100644 --- a/src/MarginTrading.Contract/ClientContracts/OrderBookClientContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/OrderBookClientContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class OrderBookClientContract { diff --git a/src/MarginTrading.Contract/ClientContracts/OrderBookLevelClientContract.cs b/src/MarginTrading.Contract/ClientContracts/OrderBookLevelClientContract.cs index 97cc9b439..3e361d218 100644 --- a/src/MarginTrading.Contract/ClientContracts/OrderBookLevelClientContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/OrderBookLevelClientContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class OrderBookLevelClientContract { diff --git a/src/MarginTrading.Contract/ClientContracts/OrderClientContract.cs b/src/MarginTrading.Contract/ClientContracts/OrderClientContract.cs index 1ea514207..096468af7 100644 --- a/src/MarginTrading.Contract/ClientContracts/OrderClientContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/OrderClientContract.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.ComponentModel; namespace MarginTrading.Contract.ClientContracts diff --git a/src/MarginTrading.Contract/ClientContracts/OrderHistoryClientContract.cs b/src/MarginTrading.Contract/ClientContracts/OrderHistoryClientContract.cs index 57a81cd20..158ac1b5c 100644 --- a/src/MarginTrading.Contract/ClientContracts/OrderHistoryClientContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/OrderHistoryClientContract.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using MarginTrading.Contract.BackendContracts; namespace MarginTrading.Contract.ClientContracts diff --git a/src/MarginTrading.Contract/ClientContracts/SetActiveAccountClientRequest.cs b/src/MarginTrading.Contract/ClientContracts/SetActiveAccountClientRequest.cs index 3eaa79928..d4f40408a 100644 --- a/src/MarginTrading.Contract/ClientContracts/SetActiveAccountClientRequest.cs +++ b/src/MarginTrading.Contract/ClientContracts/SetActiveAccountClientRequest.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class SetActiveAccountClientRequest { diff --git a/src/MarginTrading.Contract/ClientContracts/TradeContract.cs b/src/MarginTrading.Contract/ClientContracts/TradeContract.cs index 36d1d53ce..7fc01dbb8 100644 --- a/src/MarginTrading.Contract/ClientContracts/TradeContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/TradeContract.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using System.ComponentModel; using Newtonsoft.Json; using Newtonsoft.Json.Converters; diff --git a/src/MarginTrading.Contract/ClientContracts/UserUpdateEntityBackendContract.cs b/src/MarginTrading.Contract/ClientContracts/UserUpdateEntityBackendContract.cs index 9c63d5229..205ae7e12 100644 --- a/src/MarginTrading.Contract/ClientContracts/UserUpdateEntityBackendContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/UserUpdateEntityBackendContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.ClientContracts +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.ClientContracts { public class UserUpdateEntityBackendContract { diff --git a/src/MarginTrading.Contract/ClientContracts/UserUpdateEntityClientContract.cs b/src/MarginTrading.Contract/ClientContracts/UserUpdateEntityClientContract.cs index 94e755736..2a2dad2d4 100644 --- a/src/MarginTrading.Contract/ClientContracts/UserUpdateEntityClientContract.cs +++ b/src/MarginTrading.Contract/ClientContracts/UserUpdateEntityClientContract.cs @@ -1,4 +1,7 @@ -using System.ComponentModel; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; namespace MarginTrading.Contract.ClientContracts { diff --git a/src/MarginTrading.Contract/Mappers/BackendToClientContractMapper.cs b/src/MarginTrading.Contract/Mappers/BackendToClientContractMapper.cs deleted file mode 100644 index c119787d7..000000000 --- a/src/MarginTrading.Contract/Mappers/BackendToClientContractMapper.cs +++ /dev/null @@ -1,344 +0,0 @@ -using System; -using System.Linq; -using MarginTrading.Contract.BackendContracts; -using MarginTrading.Contract.BackendContracts.TradingConditions; -using MarginTrading.Contract.ClientContracts; - -namespace MarginTrading.Contract.Mappers -{ - public static class BackendToClientContractMapper - { - public static MarginTradingAccountClientContract ToClientContract( - this MarginTradingAccountBackendContract src) - { - return new MarginTradingAccountClientContract - { - Id = src.Id, - TradingConditionId = src.TradingConditionId, - BaseAssetId = src.BaseAssetId, - Balance = src.Balance, - WithdrawTransferLimit = src.WithdrawTransferLimit, - MarginCall = src.MarginCall, - StopOut = src.StopOut, - TotalCapital = src.TotalCapital, - FreeMargin = src.FreeMargin, - MarginAvailable = src.MarginAvailable, - UsedMargin = src.UsedMargin, - MarginInit = src.MarginInit, - PnL = src.PnL, - OpenPositionsCount = src.OpenPositionsCount, - MarginUsageLevel = src.MarginUsageLevel, - IsLive = src.IsLive, - LegalEntity = src.LegalEntity, - }; - } - - public static AssetPairClientContract ToClientContract( - this AssetPairBackendContract src) - { - return new AssetPairClientContract - { - Id = src.Id, - Name = src.Name, - BaseAssetId = src.BaseAssetId, - QuoteAssetId = src.QuoteAssetId, - Accuracy = src.Accuracy - }; - } - - public static AccountAssetPairClientContract ToClientContract( - this AccountAssetPairModel src) - { - return new AccountAssetPairClientContract - { - TradingConditionId = src.TradingConditionId, - BaseAssetId = src.BaseAssetId, - Instrument = src.Instrument, - LeverageInit = src.LeverageInit, - LeverageMaintenance = src.LeverageMaintenance, - SwapLong = src.SwapLong, - SwapShort = src.SwapShort, - CommissionLong = src.CommissionLong, - CommissionShort = src.CommissionShort, - CommissionLot = src.CommissionLot, - DeltaBid = src.DeltaBid, - DeltaAsk = src.DeltaAsk, - DealLimit = src.DealLimit, - PositionLimit = src.PositionLimit - }; - } - - public static GraphBidAskPairClientContract ToClientContract( - this GraphBidAskPairBackendContract src) - { - return new GraphBidAskPairClientContract - { - Bid = src.Bid, - Ask = src.Ask, - Date = src.Date - }; - } - - public static InitDataClientResponse ToClientContract(this InitDataBackendResponse src) - { - return new InitDataClientResponse - { - Accounts = src.Accounts.Select(item => item.ToClientContract()).ToArray(), - TradingConditions = src.AccountAssetPairs.ToDictionary(pair => pair.Key, pair => pair.Value.Select(item => item.ToClientContract()).ToArray()) - }; - } - - public static InitChartDataClientResponse ToClientContract(this InitChartDataBackendResponse src) - { - return new InitChartDataClientResponse - { - ChartData = src.ChartData.ToDictionary(pair => pair.Key, pair => pair.Value.Select(item => item.ToClientContract()).ToArray()) - }; - } - - public static InitAccountInstrumentsClientResponse ToClientContract(this InitAccountInstrumentsBackendResponse src) - { - return new InitAccountInstrumentsClientResponse - { - TradingConditions = src.AccountAssets.ToDictionary(pair => pair.Key, pair => pair.Value.Select(item => item.ToClientContract()).ToArray()) - }; - } - - public static AggregatedOrderBookItemClientContract ToClientContract( - this AggregatedOrderBookItemBackendContract src) - { - return new AggregatedOrderBookItemClientContract - { - Price = src.Price, - Volume = src.Volume - }; - } - - public static AggregatedOrderbookClientContract ToClientContract(this AggregatedOrderbookBackendResponse src) - { - return new AggregatedOrderbookClientContract - { - Buy = src.Buy.Select(item => item.ToClientContract()).ToArray(), - Sell = src.Sell.Select(item => item.ToClientContract()).ToArray(), - }; - } - - public static MtClientResponse ToClientContract(this MtBackendResponse src) - { - return new MtClientResponse - { - Result = src.Result, - Message = src.Message - }; - } - - public static AccountHistoryClientContract ToClientContract(this AccountHistoryBackendContract src) - { - return new AccountHistoryClientContract - { - Id = src.Id, - Date = src.Date, - AccountId = src.AccountId, - ClientId = src.ClientId, - Amount = src.Amount, - Balance = src.Balance, - WithdrawTransferLimit = src.WithdrawTransferLimit, - Comment = src.Comment, - Type = src.Type, - LegalEnity = src.LegalEntity, - }; - } - - public static OrderHistoryClientContract ToClientContract(this OrderHistoryBackendContract src) - { - return new OrderHistoryClientContract - { - Id = src.Id, - AccountId = src.AccountId, - Instrument = src.Instrument, - AssetAccuracy = src.AssetAccuracy, - Type = src.Type, - Status = src.Status, - CloseReason = src.CloseReason, - OpenDate = src.OpenDate, - CloseDate = src.CloseDate, - OpenPrice = src.OpenPrice, - ClosePrice = src.ClosePrice, - Volume = src.Volume, - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss, - TotalPnL = src.TotalPnl, - PnL = src.Pnl, - InterestRateSwap = src.InterestRateSwap, - OpenCommission = src.OpenCommission, - CloseCommission = src.CloseCommission - }; - } - - public static AccountHistoryClientResponse ToClientContract(this AccountHistoryBackendResponse src) - { - return new AccountHistoryClientResponse - { - Account = src.Account.Select(item => item.ToClientContract()).OrderByDescending(item => item.Date).ToArray(), - OpenPositions = src.OpenPositions.Select(item => item.ToClientContract()).ToArray(), - PositionsHistory = src.PositionsHistory.Select(item => item.ToClientContract()).ToArray() - }; - } - - public static NewOrderClientContract ToClientContract(this NewOrderBackendContract src) - { - return new NewOrderClientContract - { - AccountId = src.AccountId, - Instrument = src.Instrument, - ExpectedOpenPrice = src.ExpectedOpenPrice, - Volume = src.Volume, - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss, - FillType = src.FillType - }; - } - - public static OrderClientContract ToClientContract(this OrderBackendContract src) - { - return new OrderClientContract - { - Id = src.Id, - AccountId = src.AccountId, - Instrument = src.Instrument, - Type = (int)src.Type, - Status = (int)src.Status, - CloseReason = (int)src.CloseReason, - RejectReason = (int)src.RejectReason, - RejectReasonText = src.RejectReasonText, - ExpectedOpenPrice = src.ExpectedOpenPrice, - OpenPrice = src.OpenPrice, - ClosePrice = src.ClosePrice, - OpenDate = src.OpenDate, - CloseDate = src.CloseDate, - Volume = src.Volume, - MatchedVolume = src.MatchedVolume, - MatchedCloseVolume = src.MatchedCloseVolume, - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss, - //TODO: fix Fpl value in OrderBackendContract in task LWDEV-3661 - Fpl = src.Fpl, - TotalPnL = src.Fpl, - OpenCommission = src.OpenCommission, - CloseCommission = src.CloseCommission, - SwapCommission = src.SwapCommission - }; - } - - public static OrderClientContract ToClientContract(this OrderContract src) - { - return new OrderClientContract - { - Id = src.Id, - AccountId = src.AccountId, - Instrument = src.Instrument, - Type = (int)src.Type, - Status = (int)src.Status, - CloseReason = (int)src.CloseReason, - RejectReason = (int)src.RejectReason, - RejectReasonText = src.RejectReasonText, - ExpectedOpenPrice = src.ExpectedOpenPrice, - OpenPrice = src.OpenPrice, - ClosePrice = src.ClosePrice, - OpenDate = src.OpenDate, - CloseDate = src.CloseDate, - Volume = src.Volume, - MatchedVolume = src.MatchedVolume, - MatchedCloseVolume = src.MatchedCloseVolume, - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss, - Fpl = src.Fpl, - TotalPnL = src.PnL, - OpenCommission = src.OpenCommission, - CloseCommission = src.CloseCommission, - SwapCommission = src.SwapCommission - }; - } - - public static MtClientResponse ToClientContract(this OpenOrderBackendResponse src) - { - return new MtClientResponse - { - Message = src.Order.RejectReasonText, - Result = src.Order.ToClientContract() - }; - } - - public static OrderBookClientContract ToClientContract(this OrderBookBackendContract src) - { - return new OrderBookClientContract - { - Buy = src.Buy.Values.SelectMany(o => o.Select(l => l.ToClientContract())).ToArray(), - Sell = src.Sell.Values.SelectMany(o => o.Select(l => l.ToClientContract())).ToArray() - }; - } - - public static OrderBookLevelClientContract ToClientContract(this LimitOrderBackendContract src) - { - return new OrderBookLevelClientContract - { - Price = src.Price, - Volume = Math.Abs(src.Volume) - src.MatchedOrders?.Sum(o => Math.Abs(o.Volume)) ?? 0 - }; - } - - public static AccountStopoutClientContract ToClientContract(this AccountStopoutBackendContract src) - { - return new AccountStopoutClientContract - { - AccountId = src.AccountId, - PositionsCount = src.PositionsCount, - TotalPnl = src.TotalPnl - }; - } - - public static UserUpdateEntityClientContract ToClientContract(this UserUpdateEntityBackendContract src) - { - return new UserUpdateEntityClientContract - { - UpdateAccountAssetPairs = src.UpdateAccountAssetPairs, - UpdateAccounts = src.UpdateAccounts - }; - } - - public static ClientOrdersClientResponse ToClientContract(this ClientOrdersBackendResponse src) - { - return new ClientOrdersClientResponse - { - Orders = src.Orders.Select(item => item.ToClientContract()).ToArray(), - Positions = src.Positions.Select(item => item.ToClientContract()).ToArray() - }; - } - - public static AccountHistoryItemClient ToClientContract(this AccountHistoryItemBackend src) - { - return new AccountHistoryItemClient - { - Date = src.Date, - Account = src.Account?.ToClientContract(), - Position = src.Position?.ToClientContract() - }; - } - - public static AccountHistoryItemClient[] ToClientContract(this AccountNewHistoryBackendResponse src) - { - return src.HistoryItems.Select(item => item.ToClientContract()).ToArray(); - } - - public static BidAskClientContract ToClientContract(this InstrumentBidAskPairContract src) - { - return new BidAskClientContract - { - Id = src.Id, - Date = src.Date, - Bid = src.Bid, - Ask = src.Ask - }; - } - } -} diff --git a/src/MarginTrading.Contract/Mappers/ClientToBackendContractMapper.cs b/src/MarginTrading.Contract/Mappers/ClientToBackendContractMapper.cs deleted file mode 100644 index 175ad9140..000000000 --- a/src/MarginTrading.Contract/Mappers/ClientToBackendContractMapper.cs +++ /dev/null @@ -1,75 +0,0 @@ -using MarginTrading.Contract.BackendContracts; -using MarginTrading.Contract.ClientContracts; - -// ReSharper disable PossibleInvalidOperationException - -namespace MarginTrading.Contract.Mappers -{ - public static class ClientToBackendContractMapper - { - public static SetActiveAccountBackendRequest ToBackendContract(this SetActiveAccountClientRequest src, string clientId) - { - return new SetActiveAccountBackendRequest - { - AccountId = src.AccountId, - ClientId = clientId - }; - } - - public static AccountClientIdBackendRequest ToBackendContract(this AccountTokenClientRequest src, string clientId) - { - return new AccountClientIdBackendRequest - { - AccountId = src.AccountId, - ClientId = clientId - }; - } - - public static AccountHistoryBackendRequest ToBackendContract(this AccountHistoryFiltersClientRequest src, string clientId) - { - return new AccountHistoryBackendRequest - { - AccountId = src.AccountId, - ClientId = clientId, - From = src.From, - To = src.To - }; - } - - public static NewOrderBackendContract ToBackendContract(this NewOrderClientContract src) - { - return new NewOrderBackendContract - { - AccountId = src.AccountId, - Instrument = src.Instrument, - ExpectedOpenPrice = src.ExpectedOpenPrice, - Volume = src.Volume.Value, - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss, - FillType = src.FillType - }; - } - - public static OpenOrderBackendRequest ToBackendContract(this NewOrderClientContract src, string clientId) - { - return new OpenOrderBackendRequest - { - ClientId = clientId, - Order = src.ToBackendContract() - }; - } - - public static ChangeOrderLimitsBackendRequest ToBackendContract(this ChangeOrderLimitsClientRequest src, string clientId) - { - return new ChangeOrderLimitsBackendRequest - { - ClientId = clientId, - AccountId = src.AccountId, - OrderId = src.OrderId, - ExpectedOpenPrice = src.ExpectedOpenPrice, - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss - }; - } - } -} diff --git a/src/MarginTrading.Contract/MarginTrading.Contract.csproj b/src/MarginTrading.Contract/MarginTrading.Contract.csproj index ae88e711e..343f9b7a3 100644 --- a/src/MarginTrading.Contract/MarginTrading.Contract.csproj +++ b/src/MarginTrading.Contract/MarginTrading.Contract.csproj @@ -1,11 +1,12 @@  netstandard2.0 - 1.0.1 + 1.16.29 Lykke.MarginTrading.Contracts + 7.3 - - + + \ No newline at end of file diff --git a/src/MarginTrading.Contract/RabbitMqMessageModels/AccountChangedMessage.cs b/src/MarginTrading.Contract/RabbitMqMessageModels/AccountChangedMessage.cs index 522a69ab2..d6ed8e670 100644 --- a/src/MarginTrading.Contract/RabbitMqMessageModels/AccountChangedMessage.cs +++ b/src/MarginTrading.Contract/RabbitMqMessageModels/AccountChangedMessage.cs @@ -1,4 +1,7 @@ -using MarginTrading.Contract.BackendContracts; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using MarginTrading.Contract.BackendContracts; using Newtonsoft.Json; using Newtonsoft.Json.Converters; diff --git a/src/MarginTrading.Contract/RabbitMqMessageModels/AccountEventTypeEnum.cs b/src/MarginTrading.Contract/RabbitMqMessageModels/AccountEventTypeEnum.cs index 71196d038..a469f5170 100644 --- a/src/MarginTrading.Contract/RabbitMqMessageModels/AccountEventTypeEnum.cs +++ b/src/MarginTrading.Contract/RabbitMqMessageModels/AccountEventTypeEnum.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.RabbitMqMessageModels +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.RabbitMqMessageModels { /// /// What happend to the account diff --git a/src/MarginTrading.Contract/RabbitMqMessageModels/AccountStatsContract.cs b/src/MarginTrading.Contract/RabbitMqMessageModels/AccountStatsContract.cs index 175ded8b2..631901a06 100644 --- a/src/MarginTrading.Contract/RabbitMqMessageModels/AccountStatsContract.cs +++ b/src/MarginTrading.Contract/RabbitMqMessageModels/AccountStatsContract.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.RabbitMqMessageModels +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.RabbitMqMessageModels { public class AccountStatsContract { @@ -18,7 +21,6 @@ public class AccountStatsContract public decimal PnL { get; set; } public decimal OpenPositionsCount { get; set; } public decimal MarginUsageLevel { get; set; } - public bool IsLive { get; set; } public string LegalEntity { get; set; } } } \ No newline at end of file diff --git a/src/MarginTrading.Contract/RabbitMqMessageModels/AccountStatsUpdateMessage.cs b/src/MarginTrading.Contract/RabbitMqMessageModels/AccountStatsUpdateMessage.cs index 447d75f30..b9dfea04e 100644 --- a/src/MarginTrading.Contract/RabbitMqMessageModels/AccountStatsUpdateMessage.cs +++ b/src/MarginTrading.Contract/RabbitMqMessageModels/AccountStatsUpdateMessage.cs @@ -1,4 +1,7 @@ -namespace MarginTrading.Contract.RabbitMqMessageModels +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Contract.RabbitMqMessageModels { public class AccountStatsUpdateMessage { diff --git a/src/MarginTrading.Contract/RabbitMqMessageModels/InstrumentBidAskPairClientContract.cs b/src/MarginTrading.Contract/RabbitMqMessageModels/InstrumentBidAskPairClientContract.cs index dbf692b03..bce95dc26 100644 --- a/src/MarginTrading.Contract/RabbitMqMessageModels/InstrumentBidAskPairClientContract.cs +++ b/src/MarginTrading.Contract/RabbitMqMessageModels/InstrumentBidAskPairClientContract.cs @@ -1,4 +1,8 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using Newtonsoft.Json; namespace MarginTrading.Contract.RabbitMqMessageModels { @@ -8,5 +12,8 @@ public class BidAskPairRabbitMqContract public DateTime Date { get; set; } public decimal Bid { get; set; } public decimal Ask { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public bool? IsEod { get; set; } } } \ No newline at end of file diff --git a/src/MarginTrading.Contract/RabbitMqMessageModels/TradeContract.cs b/src/MarginTrading.Contract/RabbitMqMessageModels/TradeContract.cs index 6bd1136fe..6578e3bfb 100644 --- a/src/MarginTrading.Contract/RabbitMqMessageModels/TradeContract.cs +++ b/src/MarginTrading.Contract/RabbitMqMessageModels/TradeContract.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -7,7 +10,6 @@ namespace MarginTrading.Contract.RabbitMqMessageModels public class TradeContract { public string Id { get; set; } - public string ClientId { get; set; } public string AccountId { get; set; } public string OrderId { get; set; } public string AssetPairId { get; set; } diff --git a/src/MarginTrading.DataReader/Application.cs b/src/MarginTrading.DataReader/Application.cs deleted file mode 100644 index 2bc065176..000000000 --- a/src/MarginTrading.DataReader/Application.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Threading.Tasks; -using Autofac; -using MarginTrading.Common.RabbitMq; -using MarginTrading.Common.Services.Settings; -using MarginTrading.DataReader.Settings; - -namespace MarginTrading.DataReader -{ - public class Application : IStartable - { - private readonly IRabbitMqService _rabbitMqService; - private readonly DataReaderSettings _dataReaderSettings; - private readonly IMarginTradingSettingsCacheService _marginTradingSettingsCacheService; - - public Application(IRabbitMqService rabbitMqService, DataReaderSettings dataReaderSettings, - IMarginTradingSettingsCacheService marginTradingSettingsCacheService) - { - _rabbitMqService = rabbitMqService; - _dataReaderSettings = dataReaderSettings; - _marginTradingSettingsCacheService = marginTradingSettingsCacheService; - ; - } - - public void Start() - { - _rabbitMqService.Subscribe( - _dataReaderSettings.Consumers.MarginTradingEnabledChanged, false, - m => - { - _marginTradingSettingsCacheService.OnMarginTradingEnabledChanged(m); - return Task.CompletedTask; - }, - _rabbitMqService.GetJsonDeserializer()); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Attributes/SkipMarginTradingEnabledCheckAttribute.cs b/src/MarginTrading.DataReader/Attributes/SkipMarginTradingEnabledCheckAttribute.cs deleted file mode 100644 index c947c96cd..000000000 --- a/src/MarginTrading.DataReader/Attributes/SkipMarginTradingEnabledCheckAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using MarginTrading.DataReader.Filters; - -namespace MarginTrading.DataReader.Attributes -{ - /// - /// Indicates that filter should not check if current type of margin trading (live or demo) is enabled for clientId. - /// Used for marking actions which accept a clientId but can be called even if particular type of trading is disabled for client. - /// - [AttributeUsage(AttributeTargets.Method)] - public class SkipMarginTradingEnabledCheckAttribute: Attribute - { - } -} diff --git a/src/MarginTrading.DataReader/Connected Services/Application Insights/ConnectedService.json b/src/MarginTrading.DataReader/Connected Services/Application Insights/ConnectedService.json deleted file mode 100644 index f4580e22d..000000000 --- a/src/MarginTrading.DataReader/Connected Services/Application Insights/ConnectedService.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "ProviderId": "Microsoft.ApplicationInsights.ConnectedService.ConnectedServiceProvider", - "Version": "8.6.404.2", - "GettingStartedDocument": { - "Uri": "https://go.microsoft.com/fwlink/?LinkID=798432" - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Controllers/AccountAssetPairsController.cs b/src/MarginTrading.DataReader/Controllers/AccountAssetPairsController.cs deleted file mode 100644 index eab2a3237..000000000 --- a/src/MarginTrading.DataReader/Controllers/AccountAssetPairsController.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.AzureRepositories.Contract; -using MarginTrading.Backend.Contracts; -using MarginTrading.Backend.Contracts.AccountAssetPair; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Messages; -using MarginTrading.Backend.Core.TradingConditions; -using MarginTrading.Common.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.DataReader.Controllers -{ - [Authorize] - [Route("api/accountAssetPairs")] - public class AccountAssetPairsController : Controller, IAccountAssetPairsReadingApi - { - private readonly IConvertService _convertService; - private readonly IAccountAssetPairsRepository _accountAssetPairsRepository; - - public AccountAssetPairsController(IAccountAssetPairsRepository accountAssetPairsRepository, IConvertService convertService) - { - _accountAssetPairsRepository = accountAssetPairsRepository; - _convertService = convertService; - } - - [HttpGet] - [Route("")] - public async Task> List() - { - return (await _accountAssetPairsRepository.GetAllAsync()).Select(Convert).ToList(); - } - - [HttpGet] - [Route("byAsset/{tradingConditionId}/{baseAssetId}")] - public async Task> Get(string tradingConditionId, string baseAssetId) - { - return (await _accountAssetPairsRepository.GetAllAsync(tradingConditionId, baseAssetId)).Select(Convert).ToList(); - } - - [HttpGet] - [Route("byAssetPair/{tradingConditionId}/{baseAssetId}/{assetPairId}")] - public async Task Get(string tradingConditionId, string baseAssetId, string assetPairId) - { - return Convert(await _accountAssetPairsRepository.GetAsync(tradingConditionId, baseAssetId, assetPairId) - ?? throw new Exception(string.Format(MtMessages.AccountAssetForTradingConditionNotFound, - tradingConditionId, baseAssetId, assetPairId))); - } - - private AccountAssetPairContract Convert(IAccountAssetPair settings) - { - return _convertService.Convert(settings); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Controllers/AccountGroupsController.cs b/src/MarginTrading.DataReader/Controllers/AccountGroupsController.cs deleted file mode 100644 index 92e574094..000000000 --- a/src/MarginTrading.DataReader/Controllers/AccountGroupsController.cs +++ /dev/null @@ -1,60 +0,0 @@ -using MarginTrading.AzureRepositories.Contract; -using MarginTrading.Backend.Contracts; -using MarginTrading.Backend.Contracts.TradingConditions; -using MarginTrading.Backend.Core.TradingConditions; -using MarginTrading.Common.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace MarginTrading.DataReader.Controllers -{ - [Authorize] - [Route("api/accountGroups")] - public class AccountGroupsController : Controller, IAccountGroupsReadingApi - { - private readonly IAccountGroupRepository _accountGroupRepository; - private readonly IConvertService _convertService; - - public AccountGroupsController(IAccountGroupRepository accountGroupRepository, IConvertService convertService) - { - _accountGroupRepository = accountGroupRepository; - _convertService = convertService; - } - - - /// - /// Returns all account groups - /// - /// - [HttpGet] - [Route("")] - public async Task> List() - { - return (await _accountGroupRepository.GetAllAsync()) - .Select(Convert) - .ToList(); - } - - /// - /// Returns an account groups by and - /// - [HttpGet] - [Route("byBaseAsset/{tradingConditionId}/{baseAssetId}")] - [ProducesResponseType(typeof(AccountGroup), 200)] - [ProducesResponseType(typeof(AccountGroup), 204)] - public async Task GetByBaseAsset(string tradingConditionId, string baseAssetId) - { - var accountGroup = await _accountGroupRepository.GetAsync(tradingConditionId, baseAssetId); - - return accountGroup == null ? null : Convert(accountGroup); - } - - private AccountGroupContract Convert(IAccountGroup src) - { - return _convertService.Convert(src); - } - } -} diff --git a/src/MarginTrading.DataReader/Controllers/AccountHistoryController.cs b/src/MarginTrading.DataReader/Controllers/AccountHistoryController.cs deleted file mode 100644 index fb8e2b5d7..000000000 --- a/src/MarginTrading.DataReader/Controllers/AccountHistoryController.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.Backend.Contracts; -using MarginTrading.Backend.Contracts.AccountHistory; -using MarginTrading.Backend.Core; -using MarginTrading.Common.Extensions; -using MarginTrading.DataReader.Helpers; -using MarginTrading.DataReader.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using OrderExtensions = MarginTrading.DataReader.Helpers.OrderExtensions; - -namespace MarginTrading.DataReader.Controllers -{ - [Authorize] - [Route("api/accountHistory")] - public class AccountHistoryController : Controller, IAccountHistoryApi - { - private readonly IMarginTradingAccountHistoryRepository _accountsHistoryRepository; - private readonly IMarginTradingOrdersHistoryRepository _ordersHistoryRepository; - private readonly IOrdersSnapshotReaderService _ordersSnapshotReaderService; - private readonly IMarginTradingAccountsRepository _accountsRepository; - - public AccountHistoryController( - IMarginTradingAccountsRepository accountsRepository, - IMarginTradingAccountHistoryRepository accountsHistoryRepository, - IMarginTradingOrdersHistoryRepository ordersHistoryRepository, - IOrdersSnapshotReaderService ordersSnapshotReaderService) - { - _accountsRepository = accountsRepository; - _accountsHistoryRepository = accountsHistoryRepository; - _ordersHistoryRepository = ordersHistoryRepository; - _ordersSnapshotReaderService = ordersSnapshotReaderService; - } - - [Route("byTypes")] - [HttpGet] - public async Task ByTypes([FromQuery] AccountHistoryRequest request) - { - request.From = request.From?.ToUniversalTime(); - request.To = request.To?.ToUniversalTime(); - var clientAccountIds = string.IsNullOrEmpty(request.AccountId) - ? (await _accountsRepository.GetAllAsync(request.ClientId)).Select(item => item.Id).ToArray() - : new[] {request.AccountId}; - - var accounts = (await _accountsHistoryRepository.GetAsync(clientAccountIds, request.From, request.To)) - .Where(item => item.Type != AccountHistoryType.OrderClosed); - - var orders = - (await _ordersHistoryRepository.GetHistoryAsync(request.ClientId, clientAccountIds, request.From, - request.To)) - .Where(item => item.OpenDate != null && // remove cancel pending order rows created before OrderUpdateType was introduced - item.OrderUpdateType == OrderUpdateType.Close); - - var openPositions = await _ordersSnapshotReaderService.GetActiveByAccountIdsAsync(clientAccountIds); - - return new AccountHistoryResponse - { - Account = accounts.Select(AccountHistoryExtensions.ToBackendContract) - .OrderByDescending(item => item.Date).ToArray(), - OpenPositions = openPositions.Select(OrderExtensions.ToBackendHistoryContract) - .OrderByDescending(item => item.OpenDate).ToArray(), - PositionsHistory = orders.Select(OrderHistoryExtensions.ToBackendHistoryContract) - .OrderByDescending(item => item.OpenDate).ToArray(), - }; - } - - [Route("byAccounts")] - [HttpGet] - public async Task> ByAccounts( - [FromQuery] string accountId = null, [FromQuery] DateTime? from = null, [FromQuery] DateTime? to = null) - { - from = from?.ToUniversalTime(); - to = to?.ToUniversalTime(); - var accountIds = accountId != null - ? new[] {accountId} - : (await _accountsRepository.GetAllAsync()).Select(item => item.Id).ToArray(); - - return (await _accountsHistoryRepository.GetAsync(accountIds, from, to)).GroupBy(i => i.AccountId) - .ToDictionary(g => g.Key, g => g.Select(AccountHistoryExtensions.ToBackendContract).ToArray()); - } - - [Route("timeline")] - [HttpGet] - public async Task Timeline([FromQuery] AccountHistoryRequest request) - { - request.From = request.From?.ToUniversalTime(); - request.To = request.To?.ToUniversalTime(); - var clientAccountIds = string.IsNullOrEmpty(request.AccountId) - ? (await _accountsRepository.GetAllAsync(request.ClientId)).Select(item => item.Id).ToArray() - : new[] {request.AccountId}; - - var accounts = (await _accountsHistoryRepository.GetAsync(clientAccountIds, request.From, request.To)) - .Where(item => item.Type != AccountHistoryType.OrderClosed); - - var openOrders = await _ordersSnapshotReaderService.GetActiveByAccountIdsAsync(clientAccountIds); - - var history = (await _ordersHistoryRepository.GetHistoryAsync(request.ClientId, clientAccountIds, - request.From, request.To)) - .Where(item => item.OpenDate != null && // remove cancel pending order rows created before OrderUpdateType was introduced - item.OrderUpdateType == OrderUpdateType.Close).ToList(); - - var items = accounts.Select(item => new AccountHistoryItem - { - Account = item.ToBackendContract(), - Date = item.Date - }) - .Concat(openOrders.Select(item => new AccountHistoryItem - { - Position = item.ToBackendHistoryContract(), - Date = item.OpenDate.Value - })) - .Concat(history.Select(item => new AccountHistoryItem - { - Position = item.ToBackendHistoryOpenedContract(), - Date = item.OpenDate.Value - })) - .Concat(history.Select(item => new AccountHistoryItem - { - Position = item.ToBackendHistoryContract(), - Date = item.CloseDate.Value - })) - .OrderByDescending(item => item.Date); - - return new AccountNewHistoryResponse - { - HistoryItems = items.ToArray() - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Controllers/AccountsController.cs b/src/MarginTrading.DataReader/Controllers/AccountsController.cs deleted file mode 100644 index ca5b79d7e..000000000 --- a/src/MarginTrading.DataReader/Controllers/AccountsController.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.AzureRepositories; -using MarginTrading.Backend.Contracts; -using MarginTrading.Backend.Contracts.Account; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Exceptions; -using MarginTrading.DataReader.Settings; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.DataReader.Controllers -{ - [Authorize] - [Route("api/accounts")] - public class AccountsController : Controller, IAccountsApi - { - private readonly DataReaderSettings _dataReaderSettings; - private readonly IMarginTradingAccountsRepository _accountsRepository; - private readonly IMarginTradingAccountStatsRepository _accountStatsRepository; - - public AccountsController(DataReaderSettings dataReaderSettings, IMarginTradingAccountsRepository accountsRepository, - IMarginTradingAccountStatsRepository accountStatsRepository) - { - _dataReaderSettings = dataReaderSettings; - _accountsRepository = accountsRepository; - _accountStatsRepository = accountStatsRepository; - } - - /// - /// Returns all accounts - /// - [HttpGet] - [Route("")] - public async Task> GetAllAccounts() - { - return (await _accountsRepository.GetAllAsync()) - .Select(item => ToBackendContract(item, _dataReaderSettings.IsLive)); - } - - /// - /// Returns all account stats - /// - [HttpGet] - [Route("stats")] - public async Task> GetAllAccountStats() - { - return (await _accountStatsRepository.GetAllAsync()).Select(ToBackendContract); - } - - /// - /// Returns all accounts by client - /// - /// - /// - [HttpGet] - [Route("byClient/{clientId}")] - public async Task> GetAccountsByClientId(string clientId) - { - return (await _accountsRepository.GetAllAsync(clientId)) - .Select(x => ToBackendContract(MarginTradingAccount.Create(x), _dataReaderSettings.IsLive)); - } - - /// - /// Returns account by it's Id - /// - /// - /// - /// - [HttpGet] - [Route("byId/{id}")] - public async Task GetAccountById(string id) - { - var account = await _accountsRepository.GetAsync(id); - - if (account == null) - throw new AccountNotFoundException(id, "Account was not found."); - - return ToBackendContract(MarginTradingAccount.Create(account), _dataReaderSettings.IsLive); - } - - private static DataReaderAccountBackendContract ToBackendContract(IMarginTradingAccount src, bool isLive) - { - return new DataReaderAccountBackendContract - { - Id = src.Id, - ClientId = src.ClientId, - TradingConditionId = src.TradingConditionId, - BaseAssetId = src.BaseAssetId, - Balance = src.Balance, - WithdrawTransferLimit = src.WithdrawTransferLimit, - IsLive = isLive, - LegalEntity = src.LegalEntity, - }; - } - - private static DataReaderAccountStatsBackendContract ToBackendContract(IMarginTradingAccountStats item) - { - return new DataReaderAccountStatsBackendContract - { - AccountId = item.AccountId, - BaseAssetId = item.BaseAssetId, - MarginCall = item.MarginCall, - StopOut = item.StopOut, - TotalCapital = item.TotalCapital, - FreeMargin = item.FreeMargin, - MarginAvailable = item.MarginAvailable, - UsedMargin = item.UsedMargin, - MarginInit = item.MarginInit, - PnL = item.PnL, - OpenPositionsCount = item.OpenPositionsCount, - MarginUsageLevel = item.MarginUsageLevel, - LegalEntity = item.LegalEntity, - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Controllers/AssetPairsController.cs b/src/MarginTrading.DataReader/Controllers/AssetPairsController.cs deleted file mode 100644 index 93fd23fdd..000000000 --- a/src/MarginTrading.DataReader/Controllers/AssetPairsController.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.AzureRepositories.Contract; -using MarginTrading.Backend.Contracts; -using MarginTrading.Backend.Contracts.AssetPairSettings; -using MarginTrading.Backend.Core; -using MarginTrading.Common.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.DataReader.Controllers -{ - [Route("api/[controller]"), Authorize] - public class AssetPairsController : Controller, IAssetPairsReadingApi - { - private readonly IConvertService _convertService; - private readonly IAssetPairsRepository _assetPairsRepository; - - public AssetPairsController(IConvertService convertService, - IAssetPairsRepository assetPairsRepository) - { - _convertService = convertService; - _assetPairsRepository = assetPairsRepository; - } - - /// - /// Get pairs with optional filtering by LegalEntity and MatchingEngineMode - /// - [HttpGet, Route("")] - public async Task> List([FromQuery]string legalEntity, - [FromQuery]MatchingEngineModeContract? matchingEngineMode) - { - return (await _assetPairsRepository.GetAsync()).Select(Convert) - .Where(s => (matchingEngineMode == null || s.MatchingEngineMode == matchingEngineMode) && - (legalEntity == null || s.LegalEntity == legalEntity)).ToList(); - } - - /// - /// Get pair by id - /// - [HttpGet, Route("{assetPairId}")] - public async Task Get(string assetPairId) - { - return Convert(await _assetPairsRepository.GetAsync(assetPairId)); - } - - private AssetPairContract Convert(IAssetPair settings) - { - return _convertService.Convert(settings); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Controllers/DictionariesController.cs b/src/MarginTrading.DataReader/Controllers/DictionariesController.cs deleted file mode 100644 index 1f3d8e459..000000000 --- a/src/MarginTrading.DataReader/Controllers/DictionariesController.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.AzureRepositories.Contract; -using MarginTrading.Backend.Contracts; -using MarginTrading.Backend.Contracts.AssetPairSettings; -using MarginTrading.Backend.Core; -using MarginTrading.Common.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.DataReader.Controllers -{ - [Authorize] - [Route("api/dictionaries")] - public class DictionariesController : Controller, IDictionariesReadingApi - { - private readonly IAssetPairsRepository _assetPairsRepository; - private readonly IConvertService _convertService; - - public DictionariesController(IAssetPairsRepository assetPairsRepository, IConvertService convertService) - { - _assetPairsRepository = assetPairsRepository; - _convertService = convertService; - } - - [HttpGet] - [Route("assetPairs")] - public async Task> AssetPairs() - { - return (await _assetPairsRepository.GetAsync()).Select(Convert).ToList(); - } - - private AssetPairContract Convert(IAssetPair assetPair) - { - return _convertService.Convert(assetPair); - } - - [HttpGet] - [Route("matchingEngines")] - public Task> MatchingEngines() - { - //TODO: replace by Ids when ME infos will be stored in DB - return Task.FromResult((new[] - { - MatchingEngineConstants.LykkeVuMm, - MatchingEngineConstants.LykkeCyStp, - MatchingEngineConstants.Reject - }).ToList()); - } - - [HttpGet] - [Route("orderTypes")] - [ProducesResponseType(typeof(List), 200)] - public Task> OrderTypes() - { - return Task.FromResult(Enum.GetNames(typeof(OrderDirection)).ToList()); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Controllers/IsAliveController.cs b/src/MarginTrading.DataReader/Controllers/IsAliveController.cs deleted file mode 100644 index 05b6c4c25..000000000 --- a/src/MarginTrading.DataReader/Controllers/IsAliveController.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.DataReader.Controllers -{ - [Route("api/[controller]")] - public class IsAliveController : Controller - { - private readonly Settings.DataReaderSettings _settings; - - public IsAliveController(Settings.DataReaderSettings settings) - { - _settings = settings; - } - - [HttpGet] - public IActionResult GetIsAlive() - { - return Ok(new - { - Version = Microsoft.Extensions.PlatformAbstractions.PlatformServices.Default.Application.ApplicationVersion, - Env = _settings.IsLive ? "Live" : "Demo" - }); - } - } -} diff --git a/src/MarginTrading.DataReader/Controllers/RoutesController.cs b/src/MarginTrading.DataReader/Controllers/RoutesController.cs deleted file mode 100644 index b0df222d9..000000000 --- a/src/MarginTrading.DataReader/Controllers/RoutesController.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.Backend.Contracts; -using MarginTrading.Backend.Contracts.Routes; -using MarginTrading.Backend.Contracts.TradeMonitoring; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.MatchingEngines; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using MarginTrading.Common.Extensions; - -namespace MarginTrading.DataReader.Controllers -{ - [Authorize] - [Route("api/routes")] - public class RoutesController : Controller, IRoutesReadingApi - { - private readonly IMatchingEngineRoutesRepository _routesRepository; - - public RoutesController(IMatchingEngineRoutesRepository routesRepository) - { - _routesRepository = routesRepository; - } - - /// - /// Gets all routes - /// - [HttpGet] - [Route("")] - public async Task> List() - { - return (await _routesRepository.GetAllRoutesAsync()).Select(TransformRoute).ToList(); - } - - /// - /// Gets a route by - /// - [HttpGet] - [Route("{id}")] - public async Task GetById(string id) - { - return TransformRoute(await _routesRepository.GetRouteByIdAsync(id)); - } - - private static MatchingEngineRouteContract TransformRoute(IMatchingEngineRoute route) - { - string GetEmptyIfAny(string value) - { - const string AnyValue = "*"; - return value == AnyValue ? null : value; - } - OrderDirectionContract? GetDirection(OrderDirection? direction) - { - return direction?.ToType(); - } - - return new MatchingEngineRouteContract - { - Id = route.Id, - Rank = route.Rank, - MatchingEngineId = route.MatchingEngineId, - Asset = GetEmptyIfAny(route.Asset), - ClientId = GetEmptyIfAny(route.ClientId), - Instrument = GetEmptyIfAny(route.Instrument), - TradingConditionId = GetEmptyIfAny(route.TradingConditionId), - Type = GetDirection(route.Type) - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Controllers/SettingsController.cs b/src/MarginTrading.DataReader/Controllers/SettingsController.cs deleted file mode 100644 index 867d9154e..000000000 --- a/src/MarginTrading.DataReader/Controllers/SettingsController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Threading.Tasks; -using MarginTrading.Backend.Contracts; -using MarginTrading.Common.Services.Settings; -using MarginTrading.DataReader.Attributes; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.DataReader.Controllers -{ - [Authorize] - [Route("api/settings")] - public class SettingsController : Controller, ISettingsReadingApi - { - private readonly Settings.DataReaderSettings _dataReaderSettings; - private readonly IMarginTradingSettingsCacheService _marginTradingSettingsCacheService; - - public SettingsController(Settings.DataReaderSettings dataReaderSettings, - IMarginTradingSettingsCacheService marginTradingSettingsCacheService) - { - _dataReaderSettings = dataReaderSettings; - _marginTradingSettingsCacheService = marginTradingSettingsCacheService; - } - - [HttpGet] - [Route("enabled/{clientId}")] - [SkipMarginTradingEnabledCheck] - public Task IsMarginTradingEnabled(string clientId) - { - return _marginTradingSettingsCacheService.IsMarginTradingEnabled(clientId, _dataReaderSettings.IsLive); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Controllers/TradeMonitoringController.cs b/src/MarginTrading.DataReader/Controllers/TradeMonitoringController.cs deleted file mode 100644 index c5356c2d5..000000000 --- a/src/MarginTrading.DataReader/Controllers/TradeMonitoringController.cs +++ /dev/null @@ -1,245 +0,0 @@ -using MarginTrading.Backend.Contracts; -using MarginTrading.Backend.Contracts.TradeMonitoring; -using MarginTrading.Backend.Core; -using MarginTrading.Common.Services; -using MarginTrading.DataReader.Models; -using MarginTrading.DataReader.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.Backend.Core.Orderbooks; -using OrderExtensions = MarginTrading.DataReader.Helpers.OrderExtensions; - -namespace MarginTrading.DataReader.Controllers -{ - [Authorize] - [Route("api/trade/")] - public class TradeMonitoringController : Controller, ITradeMonitoringReadingApi - { - private readonly IOrdersSnapshotReaderService _ordersSnapshotReaderService; - private readonly IOrderBookSnapshotReaderService _orderBookSnapshotReaderService; - private readonly IConvertService _convertService; - - public TradeMonitoringController(IOrdersSnapshotReaderService ordersSnapshotReaderService, - IOrderBookSnapshotReaderService orderBookSnapshotReaderService, - IConvertService convertService) - { - _ordersSnapshotReaderService = ordersSnapshotReaderService; - _orderBookSnapshotReaderService = orderBookSnapshotReaderService; - _convertService = convertService; - } - - /// - /// Returns summary asset info from a snapshot (blob) - /// - /// - ///

VolumeLong is a sum of long positions volume

- ///

VolumeShort is a sum of short positions volume

- ///

PnL is a sum of all positions PnL

- ///

Header "api-key" is required

- ///
- /// Returns summary info by assets - [HttpGet] - [Route("assets/summary")] - public async Task> AssetSummaryList() - { - var result = new List(); - var orders = await _ordersSnapshotReaderService.GetAllAsync(); - - foreach (var order in orders) - { - var assetInfo = result.FirstOrDefault(item => item.AssetPairId == order.Instrument); - - if (assetInfo == null) - { - result.Add(new SummaryAssetContract - { - AssetPairId = order.Instrument, - PnL = order.FplData.Fpl, - VolumeLong = order.GetOrderType() == OrderDirection.Buy ? order.GetMatchedVolume() : 0, - VolumeShort = order.GetOrderType() == OrderDirection.Sell ? order.GetMatchedVolume() : 0 - }); - } - else - { - assetInfo.PnL += order.FplData.Fpl; - - if (order.GetOrderType() == OrderDirection.Buy) - { - assetInfo.VolumeLong += order.GetMatchedVolume(); - } - else - { - assetInfo.VolumeShort += order.GetMatchedVolume(); - } - } - } - - return result; - } - - /// - /// Returns list of opened positions from a snapshot (blob) - /// - /// - ///

Returns list of opened positions with matched volume greater or equal provided "volume" parameter

- ///

Header "api-key" is required

- ///
- /// Returns opened positions - [HttpGet] - [Route("openPositions/byVolume/{volume}")] - public async Task> OpenPositionsByVolume([FromRoute]decimal volume) - { - return (await _ordersSnapshotReaderService.GetActiveAsync()) - .Where(order => order.GetMatchedVolume() >= volume) - .Select(OrderExtensions.ToBaseContract) - .ToList(); - } - - /// - /// Returns list of opened positions from a snapshot (blob) - /// - /// - ///

Returns list of all opened positions

- ///

Header "api-key" is required

- ///
- /// Returns opened positions - [HttpGet] - [Route("openPositions")] - public Task> OpenPositions() - { - return OpenPositionsByVolume(0); - } - - /// - /// Returns list of opened positions from a snapshot (blob) filtered by a date interval - /// - /// - ///

Returns list of date filtered opened positions

- ///

Header "api-key" is required

- ///
- /// Returns opened positions - [HttpGet] - [Route("openPositions/byDate")] - public async Task> OpenPositionsByDate([FromQuery] DateTime from, [FromQuery] DateTime to) - { - return (await _ordersSnapshotReaderService.GetActiveAsync()) - .Where(order => order.OpenDate >= from.Date && order.OpenDate< to.Date) - .Select(OrderExtensions.ToBaseContract) - .ToList(); - } - - /// - /// Returns list of opened positions from a snapshot (blob) filtered by client - /// - /// - ///

Returns list of client filtered opened positions

- ///

Header "api-key" is required

- ///
- /// Returns opened positions - [HttpGet] - [Route("openPositions/byClient/{clientId}")] - public async Task> OpenPositionsByClient([FromRoute]string clientId) - { - return (await _ordersSnapshotReaderService.GetActiveAsync()) - .Where(order => order.ClientId == clientId) - .Select(OrderExtensions.ToBaseContract) - .ToList(); - } - - /// - /// Returns list of pending orders from a snapshot (blob) - /// - /// - ///

Returns list of pending orders with volume greater or equal provided "volume" parameter

- ///

Header "api-key" is required

- ///
- /// Returns pending orders - [HttpGet] - [Route("pendingOrders/byVolume/{volume}")] - public async Task> PendingOrdersByVolume([FromRoute]decimal volume) - { - return (await _ordersSnapshotReaderService.GetPendingAsync()) - .Where(order => Math.Abs(order.Volume) >= volume) - .Select(OrderExtensions.ToBaseContract) - .ToList(); - } - - /// - /// Returns list of pending orders from a snapshot (blob) - /// - /// - ///

Returns list of all pending orders

- ///

Header "api-key" is required

- ///
- /// Returns pending orders - [HttpGet] - [Route("pendingOrders")] - public Task> PendingOrders() - { - return PendingOrdersByVolume(0); - } - - /// - /// Returns list of pending orders from a snapshot (blob) filtered by a date interval - /// - /// - ///

Returns list of date filtered pending orders

- ///

Header "api-key" is required

- ///
- /// Returns pending orders - [HttpGet] - [Route("pendingOrders/byDate")] - public async Task> PendingOrdersByDate([FromQuery] DateTime from, [FromQuery] DateTime to) - { - return (await _ordersSnapshotReaderService.GetPendingAsync()) - .Where(order => order.CreateDate >= from && order.CreateDate < to) - .Select(OrderExtensions.ToBaseContract) - .ToList(); - } - - /// - /// Returns list of pending orders from a snapshot (blob) filtered by client - /// - /// - ///

Returns list of client filtered pending orders

- ///

Header "api-key" is required

- ///
- /// Returns pending orders - [HttpGet] - [Route("pendingOrders/byClient/{clientId}")] - public async Task> PendingOrdersByClient([FromRoute]string clientId) - { - return (await _ordersSnapshotReaderService.GetPendingAsync()) - .Where(order => order.ClientId == clientId) - .Select(OrderExtensions.ToBaseContract) - .ToList(); - } - - - /// - /// Returns list of orderbooks from a snapshot (blob) - /// - /// - /// Header "api-key" is required - /// - /// Returns orderbooks - [HttpGet] - [Route("orderbooks/byInstrument/{instrument}")] - public async Task> OrderBooksByInstrument(string instrument) - { - var orderbook = await _orderBookSnapshotReaderService.GetOrderBook(instrument); - - return new List {Convert(orderbook)}; - } - - private OrderBookContract Convert(OrderBook domainContract) - { - return _convertService.Convert(domainContract); - } - - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Controllers/TradingConditionsController.cs b/src/MarginTrading.DataReader/Controllers/TradingConditionsController.cs deleted file mode 100644 index 500f90d09..000000000 --- a/src/MarginTrading.DataReader/Controllers/TradingConditionsController.cs +++ /dev/null @@ -1,53 +0,0 @@ -using MarginTrading.AzureRepositories.Contract; -using MarginTrading.Backend.Contracts; -using MarginTrading.Backend.Contracts.TradingConditions; -using MarginTrading.Backend.Core.TradingConditions; -using MarginTrading.Common.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace MarginTrading.DataReader.Controllers -{ - [Authorize] - [Route("api/tradingConditions")] - public class TradingConditionsController : Controller, ITradingConditionsReadingApi - { - private readonly ITradingConditionRepository _conditionsRepository; - private readonly IConvertService _convertService; - - public TradingConditionsController(ITradingConditionRepository conditionsRepository, IConvertService convertService) - { - _conditionsRepository = conditionsRepository; - _convertService = convertService; - } - - /// - /// Returns all trading conditions - /// - [HttpGet] - [Route("")] - public async Task> List() - { - return (await _conditionsRepository.GetAllAsync()).Select(Convert).ToList(); - } - - /// - /// Returns trading condition by id - /// - [HttpGet] - [Route("{id}")] - public async Task Get(string id) - { - return Convert(await _conditionsRepository.GetAsync(id)); - } - - - private TradingConditionContract Convert(ITradingCondition tradingCondition) - { - return _convertService.Convert(tradingCondition); - } - } -} diff --git a/src/MarginTrading.DataReader/Dockerfile b/src/MarginTrading.DataReader/Dockerfile deleted file mode 100644 index 837fe636d..000000000 --- a/src/MarginTrading.DataReader/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM microsoft/aspnetcore:2.0 -WORKDIR /app -COPY . . -ENTRYPOINT ["dotnet", "MarginTrading.DataReader.dll"] diff --git a/src/MarginTrading.DataReader/Filters/MarginTradingEnabledFilter.cs b/src/MarginTrading.DataReader/Filters/MarginTradingEnabledFilter.cs deleted file mode 100644 index 50795fd4f..000000000 --- a/src/MarginTrading.DataReader/Filters/MarginTradingEnabledFilter.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using Common.Log; -using JetBrains.Annotations; -using MarginTrading.Common.Services.Settings; -using MarginTrading.Common.Settings; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.Filters; -using Rocks.Caching; -using MarginTrading.DataReader.Attributes; -using MarginTrading.DataReader.Settings; - -namespace MarginTrading.DataReader.Filters -{ - /// - /// Restricts access to actions for clients which are not allowed to use current type of margin trading (live/demo). - /// Skips validation if current action method is marked with . - /// If ClientId is not found in the action parameters - does nothing. - /// - public class MarginTradingEnabledFilter: ActionFilterAttribute - { - private readonly DataReaderSettings _dataReaderSettings; - private readonly IMarginTradingSettingsCacheService _marginTradingSettingsCacheService; - private readonly ICacheProvider _cacheProvider; - private readonly ILog _log; - - public MarginTradingEnabledFilter( - DataReaderSettings dataReaderSettings, - IMarginTradingSettingsCacheService marginTradingSettingsCacheService, - ICacheProvider cacheProvider, - ILog log) - { - _dataReaderSettings = dataReaderSettings; - _marginTradingSettingsCacheService = marginTradingSettingsCacheService; - _cacheProvider = cacheProvider; - _log = log; - } - - /// - public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) - { - await ValidateMarginTradingEnabledAsync(context); - await base.OnActionExecutionAsync(context, next); - } - - /// - public override void OnActionExecuting(ActionExecutingContext context) - { - // this won't be called frequently as we have most actions async - ValidateMarginTradingEnabledAsync(context).Wait(); - } - - /// - /// Performs a validation if current type of margin trading is enabled globally and for the particular client - /// (which is extracted from the action parameters). - /// Skips validation if current action method is marked with . - /// - /// - /// Using this type of margin trading is restricted for client or - /// a controller action has more then one ClientId in its parameters. - /// - private async Task ValidateMarginTradingEnabledAsync(ActionExecutingContext context) - { - var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; - if (controllerActionDescriptor == null) - { - return; - } - - var cacheKey = CacheKeyBuilder.Create(nameof(MarginTradingEnabledFilter), nameof(GetSingleClientIdGetter), controllerActionDescriptor.DisplayName); - var clientIdGetter = _cacheProvider.Get(cacheKey, () => new CachableResult(GetSingleClientIdGetter(controllerActionDescriptor), CachingParameters.InfiniteCache)); - if (clientIdGetter != null) - { - var clientId = clientIdGetter(context.ActionArguments); - if (string.IsNullOrWhiteSpace(clientId)) - { - await _log.WriteWarningAsync(nameof(MarginTradingEnabledFilter), nameof(ValidateMarginTradingEnabledAsync), context.ActionDescriptor.DisplayName, "ClientId is null but is expected. No validation will be performed"); - } - else if (!await _marginTradingSettingsCacheService.IsMarginTradingEnabled(clientId, _dataReaderSettings.IsLive)) - { - throw new InvalidOperationException("Using this type of margin trading is restricted for client " + clientId); - } - } - } - - /// - /// Finds single clientId getter func for current action. - /// If none found returns null. - /// If the action is marked to skip the MarginTradingEnabled check also returns null. - /// - [CanBeNull] - private static ClientIdGetter GetSingleClientIdGetter(ControllerActionDescriptor controllerActionDescriptor) - { - if (controllerActionDescriptor.MethodInfo.GetCustomAttribute() != null) - { - return null; - } - - var clientIdGetters = GetClientIdGetters(controllerActionDescriptor.Parameters).ToList(); - switch (clientIdGetters.Count) - { - case 0: - return null; - case 1: - return clientIdGetters[0]; - default: - throw new InvalidOperationException("A controller action cannot have more then one ClientId in its parameters"); - } - } - - /// - /// Searches the controller's actions parameters for the presence of ClientId - /// and returns a func to get the ClientId value from ActionArguments for each of found ClientIds parameters - /// - private static IEnumerable GetClientIdGetters(IList parameterDescriptors) - { - foreach (var parameterDescriptor in parameterDescriptors) - { - if (string.Compare(parameterDescriptor.Name, "ClientId", StringComparison.OrdinalIgnoreCase) == 0 - && parameterDescriptor.ParameterType == typeof(string)) - { - yield return d => (string) d[parameterDescriptor.Name]; - } - else - { - var clientIdPropertyInfo = parameterDescriptor.ParameterType.GetProperty("ClientId", typeof(string)); - if (clientIdPropertyInfo != null) - { - yield return d => (string) ((dynamic) d[parameterDescriptor.Name])?.ClientId; - } - } - } - } - - private delegate string ClientIdGetter(IDictionary actionArguments); - } -} diff --git a/src/MarginTrading.DataReader/Helpers/AccountHistoryExtensions.cs b/src/MarginTrading.DataReader/Helpers/AccountHistoryExtensions.cs deleted file mode 100644 index 95bcf5844..000000000 --- a/src/MarginTrading.DataReader/Helpers/AccountHistoryExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using MarginTrading.Backend.Contracts.AccountHistory; -using MarginTrading.Backend.Core; -using MarginTrading.Common.Extensions; - -namespace MarginTrading.DataReader.Helpers -{ - public static class AccountHistoryExtensions - { - public static AccountHistoryContract ToBackendContract(this IMarginTradingAccountHistory src) - { - return new AccountHistoryContract - { - Id = src.Id, - Date = src.Date, - AccountId = src.AccountId, - ClientId = src.ClientId, - Amount = src.Amount, - Balance = src.Balance, - WithdrawTransferLimit = src.WithdrawTransferLimit, - Comment = src.Comment, - Type = src.Type.ToType(), - OrderId = src.OrderId, - LegalEntity = src.LegalEntity, - AuditLog = src.AuditLog - }; - } - } -} diff --git a/src/MarginTrading.DataReader/Helpers/OrderExtensions.cs b/src/MarginTrading.DataReader/Helpers/OrderExtensions.cs deleted file mode 100644 index 28433c5b0..000000000 --- a/src/MarginTrading.DataReader/Helpers/OrderExtensions.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System.Linq; -using MarginTrading.Backend.Contracts.AccountHistory; -using MarginTrading.Backend.Contracts.AssetPairSettings; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.MatchedOrders; -using MarginTrading.Common.Extensions; -using MarginTrading.Backend.Contracts.TradeMonitoring; - -namespace MarginTrading.DataReader.Helpers -{ - internal static class OrderExtensions - { - public static OrderContract ToBaseContract(this Order src) - { - MatchedOrderBackendContract MatchedOrderToBackendContract(MatchedOrder o) - => new MatchedOrderBackendContract - { - OrderId = o.OrderId, - LimitOrderLeftToMatch = o.LimitOrderLeftToMatch, - Volume = o.Volume, - Price = o.Price, - MatchedDate = o.MatchedDate - }; - - return new OrderContract - { - Id = src.Id, - Code = src.Code, - AccountId = src.AccountId, - AccountAssetId = src.AccountAssetId, - ClientId = src.ClientId, - Instrument = src.Instrument, - Status = src.Status.ToType(), - CreateDate = src.CreateDate, - OpenDate = src.OpenDate, - CloseDate = src.CloseDate, - ExpectedOpenPrice = src.ExpectedOpenPrice, - OpenPrice = src.OpenPrice, - ClosePrice = src.ClosePrice, - Type = src.GetOrderType().ToType(), - Volume = src.Volume, - MatchedVolume = src.GetMatchedVolume(), - MatchedCloseVolume = src.GetMatchedCloseVolume(), - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss, - Fpl = src.FplData.Fpl, - PnL = src.FplData.TotalFplSnapshot, - CloseReason = src.CloseReason.ToType(), - RejectReason = src.RejectReason.ToType(), - RejectReasonText = src.RejectReasonText, - CommissionLot = src.CommissionLot, - OpenCommission = src.GetOpenCommission(), - CloseCommission = src.GetCloseCommission(), - SwapCommission = src.SwapCommission, - MatchedOrders = src.MatchedOrders.Select(MatchedOrderToBackendContract).ToList(), - MatchedCloseOrders = src.MatchedCloseOrders.Select(MatchedOrderToBackendContract).ToList(), - OpenExternalOrderId = src.OpenExternalOrderId, - OpenExternalProviderId = src.OpenExternalProviderId, - CloseExternalOrderId = src.CloseExternalOrderId, - CloseExternalProviderId = src.CloseExternalProviderId, - MatchingEngineMode = src.MatchingEngineMode.ToType(), - LegalEntity = src.LegalEntity, - }; - } - - public static OrderHistoryContract ToBackendHistoryContract(this Order src) - { - return new OrderHistoryContract - { - Id = src.Id, - Code = src.Code, - AccountId = src.AccountId, - Instrument = src.Instrument, - AssetAccuracy = src.AssetAccuracy, - Type = src.GetOrderType().ToType(), - Status = src.Status.ToType(), - CloseReason = src.CloseReason.ToType(), - OpenDate = src.OpenDate, - CloseDate = src.CloseDate, - OpenPrice = src.OpenPrice, - ClosePrice = src.ClosePrice, - Volume = src.Volume, - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss, - TotalPnl = src.FplData.TotalFplSnapshot, - Pnl = src.FplData.Fpl, - InterestRateSwap = src.FplData.SwapsSnapshot, - CommissionLot = src.CommissionLot, - OpenCommission = src.GetOpenCommission(), - CloseCommission = src.GetCloseCommission(), - EquivalentAsset = src.EquivalentAsset, - OpenPriceEquivalent = src.OpenPriceEquivalent, - ClosePriceEquivalent = src.ClosePriceEquivalent, - OpenExternalOrderId = src.OpenExternalOrderId, - OpenExternalProviderId = src.OpenExternalProviderId, - CloseExternalOrderId = src.CloseExternalOrderId, - CloseExternalProviderId = src.CloseExternalProviderId, - MatchingEngineMode = src.MatchingEngineMode - .ToType() - }; - } - - } -} diff --git a/src/MarginTrading.DataReader/Helpers/OrderHistoryExtensions.cs b/src/MarginTrading.DataReader/Helpers/OrderHistoryExtensions.cs deleted file mode 100644 index 68188186e..000000000 --- a/src/MarginTrading.DataReader/Helpers/OrderHistoryExtensions.cs +++ /dev/null @@ -1,81 +0,0 @@ -using MarginTrading.Backend.Contracts.AccountHistory; -using MarginTrading.Backend.Contracts.AssetPairSettings; -using MarginTrading.Backend.Contracts.TradeMonitoring; -using MarginTrading.Backend.Core; -using MarginTrading.Common.Extensions; - -namespace MarginTrading.DataReader.Helpers -{ - public static class OrderHistoryExtensions - { - public static OrderHistoryContract ToBackendHistoryContract(this IOrderHistory src) - { - return new OrderHistoryContract - { - Id = src.Id, - AccountId = src.AccountId, - Instrument = src.Instrument, - AssetAccuracy = src.AssetAccuracy, - Type = src.Type.ToType(), - Status = src.Status.ToType(), - CloseReason = src.CloseReason.ToType(), - OpenDate = src.OpenDate, - CloseDate = src.CloseDate, - OpenPrice = src.OpenPrice, - ClosePrice = src.ClosePrice, - Volume = src.Volume, - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss, - Pnl = src.Fpl, - TotalPnl = src.PnL, - InterestRateSwap = src.InterestRateSwap, - CommissionLot = src.CommissionLot, - OpenCommission = src.OpenCommission, - CloseCommission = src.CloseCommission, - EquivalentAsset = src.EquivalentAsset, - OpenPriceEquivalent = src.OpenPriceEquivalent, - ClosePriceEquivalent = src.ClosePriceEquivalent, - OpenExternalOrderId = src.OpenExternalOrderId, - OpenExternalProviderId = src.OpenExternalProviderId, - CloseExternalOrderId = src.CloseExternalOrderId, - CloseExternalProviderId = src.CloseExternalProviderId, - MatchingEngineMode = src.MatchingEngineMode.ToType() - }; - } - - public static OrderHistoryContract ToBackendHistoryOpenedContract(this IOrderHistory src) - { - return new OrderHistoryContract - { - Id = src.Id, - AccountId = src.AccountId, - Instrument = src.Instrument, - AssetAccuracy = src.AssetAccuracy, - Type = src.Type.ToType(), - Status = OrderStatusContract.Active, - CloseReason = src.CloseReason.ToType(), - OpenDate = src.OpenDate, - CloseDate = null, - OpenPrice = src.OpenPrice, - ClosePrice = src.ClosePrice, - Volume = src.Volume, - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss, - TotalPnl = src.PnL, - Pnl = src.Fpl, - InterestRateSwap = src.InterestRateSwap, - CommissionLot = src.CommissionLot, - OpenCommission = src.OpenCommission, - CloseCommission = src.CloseCommission, - EquivalentAsset = src.EquivalentAsset, - OpenPriceEquivalent = src.OpenPriceEquivalent, - ClosePriceEquivalent = src.ClosePriceEquivalent, - OpenExternalOrderId = src.OpenExternalOrderId, - OpenExternalProviderId = src.OpenExternalProviderId, - CloseExternalOrderId = src.CloseExternalOrderId, - CloseExternalProviderId = src.CloseExternalProviderId, - MatchingEngineMode = src.MatchingEngineMode.ToType() - }; - } - } -} diff --git a/src/MarginTrading.DataReader/Infrastructure/ApiKeyHeaderOperationFilter.cs b/src/MarginTrading.DataReader/Infrastructure/ApiKeyHeaderOperationFilter.cs deleted file mode 100644 index 2f45e0f06..000000000 --- a/src/MarginTrading.DataReader/Infrastructure/ApiKeyHeaderOperationFilter.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Mvc.Authorization; -using Swashbuckle.Swagger.Model; -using Swashbuckle.SwaggerGen.Generator; - -namespace MarginTrading.DataReader.Infrastructure -{ - public class ApiKeyHeaderOperationFilter : IOperationFilter - { - public void Apply(Operation operation, OperationFilterContext context) - { - var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors; - var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter); - var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter); - if (isAuthorized && !allowAnonymous) - { - if (operation.Parameters == null) - operation.Parameters = new List(); - operation.Parameters.Add(new NonBodyParameter - { - Name = "api-key", - In = "header", - Description = "access token", - Required = true, - Type = "string" - }); - } - } - } -} diff --git a/src/MarginTrading.DataReader/Infrastructure/CustomOperationIdOperationFilter.cs b/src/MarginTrading.DataReader/Infrastructure/CustomOperationIdOperationFilter.cs deleted file mode 100644 index 5fbbd9f04..000000000 --- a/src/MarginTrading.DataReader/Infrastructure/CustomOperationIdOperationFilter.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.AspNetCore.Mvc.Controllers; -using Swashbuckle.Swagger.Model; -using Swashbuckle.SwaggerGen.Generator; - -namespace MarginTrading.DataReader.Infrastructure -{ - public class CustomOperationIdOperationFilter : IOperationFilter - { - public void Apply(Operation operation, OperationFilterContext context) - { - var actionDescriptor = (ControllerActionDescriptor)context.ApiDescription.ActionDescriptor; - operation.OperationId = actionDescriptor.ActionName; - } - } -} diff --git a/src/MarginTrading.DataReader/Infrastructure/FixResponseValueTypesNullabilitySchemaFilter.cs b/src/MarginTrading.DataReader/Infrastructure/FixResponseValueTypesNullabilitySchemaFilter.cs deleted file mode 100644 index 89585a911..000000000 --- a/src/MarginTrading.DataReader/Infrastructure/FixResponseValueTypesNullabilitySchemaFilter.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Linq; -using Swashbuckle.Swagger.Model; -using Swashbuckle.SwaggerGen.Generator; - -namespace MarginTrading.DataReader.Infrastructure -{ - public class FixResponseValueTypesNullabilitySchemaFilter : ISchemaFilter - { - public void Apply(Schema schema, SchemaFilterContext context) - { - if (schema.Type != "object" || schema.Properties == null) - { - return; - } - - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - var nonNulableValueTypedPropNames = context.SystemType.GetProperties() - .Where(p => p.PropertyType.IsValueType && Nullable.GetUnderlyingType(p.PropertyType) == null) - .Select(p => p.Name); - - schema.Required = schema.Properties.Keys.Intersect(nonNulableValueTypedPropNames, StringComparer.OrdinalIgnoreCase).ToList(); - - if (schema.Required.Count == 0) - { - schema.Required = null; - } - } - } -} diff --git a/src/MarginTrading.DataReader/MarginTrading.DataReader.csproj b/src/MarginTrading.DataReader/MarginTrading.DataReader.csproj deleted file mode 100644 index 67d3af90f..000000000 --- a/src/MarginTrading.DataReader/MarginTrading.DataReader.csproj +++ /dev/null @@ -1,77 +0,0 @@ - - - netcoreapp2.0 - $(NoWarn);1591 - true - MarginTrading.DataReader - Exe - MarginTrading.DataReader - false - false - false - 1.0.1 - MarginTrading.DataReader - - - - AccountGroupsController.cs - - - PreserveNewest - - - - - {5EC22A63-BA3F-41A2-A70F-216B7E809390} - MarginTrading.Backend.Contracts - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Middleware/KeyAuthHandler.cs b/src/MarginTrading.DataReader/Middleware/KeyAuthHandler.cs deleted file mode 100644 index 05622ef52..000000000 --- a/src/MarginTrading.DataReader/Middleware/KeyAuthHandler.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Linq; -using System.Security.Claims; -using System.Text.Encodings.Web; -using System.Threading.Tasks; -using MarginTrading.DataReader.Middleware.Validator; -using Microsoft.AspNetCore.Authentication; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace MarginTrading.DataReader.Middleware -{ - public class KeyAuthHandler : AuthenticationHandler - { - private readonly IApiKeyValidator _apiKeyValidator; - - public KeyAuthHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IApiKeyValidator apiKeyValidator) - : base(options, logger, encoder, clock) - { - _apiKeyValidator = apiKeyValidator; - } - - protected override async Task HandleAuthenticateAsync() - { - if (!Context.Request.Headers.TryGetValue(KeyAuthOptions.DefaultHeaderName, out var headerValue)) - { - return AuthenticateResult.Fail("No api key header."); - } - - var apiKey = headerValue.First(); - if (!_apiKeyValidator.Validate(apiKey)) - { - return AuthenticateResult.Fail("Invalid API key."); - } - - var identity = new ClaimsIdentity("apikey"); - var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), null, "apikey"); - return await Task.FromResult(AuthenticateResult.Success(ticket)); - } - } -} diff --git a/src/MarginTrading.DataReader/Middleware/KeyAuthOptions.cs b/src/MarginTrading.DataReader/Middleware/KeyAuthOptions.cs deleted file mode 100644 index 8cf6304c8..000000000 --- a/src/MarginTrading.DataReader/Middleware/KeyAuthOptions.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Microsoft.AspNetCore.Authentication; - -namespace MarginTrading.DataReader.Middleware -{ - public class KeyAuthOptions : AuthenticationSchemeOptions - { - public const string DefaultHeaderName = "api-key"; - public const string AuthenticationScheme = "Automatic"; - } -} diff --git a/src/MarginTrading.DataReader/Middleware/Validator/IApiKeyValidator.cs b/src/MarginTrading.DataReader/Middleware/Validator/IApiKeyValidator.cs deleted file mode 100644 index 75f4c1659..000000000 --- a/src/MarginTrading.DataReader/Middleware/Validator/IApiKeyValidator.cs +++ /dev/null @@ -1,25 +0,0 @@ - -using MarginTrading.DataReader.Settings; - -namespace MarginTrading.DataReader.Middleware.Validator -{ - public interface IApiKeyValidator - { - bool Validate(string apiKey); - } - - public class ApiKeyValidator : IApiKeyValidator - { - private readonly DataReaderSettings _settings; - - public ApiKeyValidator(DataReaderSettings settings) - { - _settings = settings; - } - - public bool Validate(string apiKey) - { - return apiKey == _settings.ApiKey; - } - } -} diff --git a/src/MarginTrading.DataReader/Models/BackofficeModels.cs b/src/MarginTrading.DataReader/Models/BackofficeModels.cs deleted file mode 100644 index 16e64c854..000000000 --- a/src/MarginTrading.DataReader/Models/BackofficeModels.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MarginTrading.DataReader.Models -{ - public class SummaryAssetInfo - { - public string AssetPairId { get; set; } - public decimal VolumeLong { get; set; } - public decimal VolumeShort { get; set; } - public decimal PnL { get; set; } - } -} diff --git a/src/MarginTrading.DataReader/Models/OrderBookModel.cs b/src/MarginTrading.DataReader/Models/OrderBookModel.cs deleted file mode 100644 index 90ffc701e..000000000 --- a/src/MarginTrading.DataReader/Models/OrderBookModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; -using MarginTrading.Backend.Core; - -namespace MarginTrading.DataReader.Models -{ - public class OrderBookModel - { - public string Instrument { get; set; } - public List Buy { get; set; } = new List(); - public List Sell { get; set; } = new List(); - } -} diff --git a/src/MarginTrading.DataReader/Modules/DataReaderExternalServicesModule.cs b/src/MarginTrading.DataReader/Modules/DataReaderExternalServicesModule.cs deleted file mode 100644 index a8a4fe376..000000000 --- a/src/MarginTrading.DataReader/Modules/DataReaderExternalServicesModule.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Autofac; -using Lykke.Service.ClientAccount.Client; -using Lykke.SettingsReader; -using MarginTrading.DataReader.Settings; - -namespace MarginTrading.DataReader.Modules -{ - public class DataReaderExternalServicesModule : Module - { - private readonly IReloadingManager _settings; - - public DataReaderExternalServicesModule(IReloadingManager settings) - { - _settings = settings; - } - - protected override void Load(ContainerBuilder builder) - { - builder.RegisterLykkeServiceClient(_settings.CurrentValue.ClientAccountServiceClient.ServiceUrl); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Modules/DataReaderRepositoriesModule.cs b/src/MarginTrading.DataReader/Modules/DataReaderRepositoriesModule.cs deleted file mode 100644 index c0448bef1..000000000 --- a/src/MarginTrading.DataReader/Modules/DataReaderRepositoriesModule.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Autofac; -using AzureStorage.Tables; -using Common.Log; -using Lykke.SettingsReader; -using MarginTrading.AzureRepositories; -using MarginTrading.AzureRepositories.Contract; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.MatchingEngines; -using MarginTrading.Common.Services; -using MarginTrading.DataReader.Settings; - -namespace MarginTrading.DataReader.Modules -{ - public class DataReaderRepositoriesModule : Module - { - private readonly IReloadingManager _settings; - private readonly ILog _log; - - public DataReaderRepositoriesModule(IReloadingManager settings, ILog log) - { - _settings = settings; - _log = log; - } - - protected override void Load(ContainerBuilder builder) - { - builder.RegisterInstance(_log) - .As() - .SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateAccountsRepository(_settings.Nested(s => s.Db.MarginTradingConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateAccountStatsRepository(_settings.Nested(s => s.Db.HistoryConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateOrdersHistoryRepository(_settings.Nested(s => s.Db.HistoryConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateAccountHistoryRepository(_settings.Nested(s => s.Db.HistoryConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateMatchingEngineRoutesRepository(_settings.Nested(s => s.Db.MarginTradingConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateTradingConditionsRepository(_settings.Nested(s => s.Db.MarginTradingConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateAccountGroupRepository(_settings.Nested(s => s.Db.MarginTradingConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateAccountAssetsRepository(_settings.Nested(s => s.Db.MarginTradingConnString), _log) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateBlobRepository(_settings.Nested(s => s.Db.StateConnString)) - ).SingleInstance(); - - builder.Register(ctx => - AzureRepoFactories.MarginTrading.CreateAssetPairSettingsRepository( - _settings.Nested(s => s.Db.MarginTradingConnString), _log, ctx.Resolve()) - ).SingleInstance(); - } - } -} diff --git a/src/MarginTrading.DataReader/Modules/DataReaderServicesModule.cs b/src/MarginTrading.DataReader/Modules/DataReaderServicesModule.cs deleted file mode 100644 index 1508785f8..000000000 --- a/src/MarginTrading.DataReader/Modules/DataReaderServicesModule.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using Autofac; -using Common.Log; -using Lykke.SettingsReader; -using MarginTrading.Backend.Core.Settings; -using MarginTrading.Common.RabbitMq; -using MarginTrading.Common.Services.Settings; -using MarginTrading.Common.Settings; -using MarginTrading.DataReader.Middleware.Validator; -using MarginTrading.DataReader.Services; -using MarginTrading.DataReader.Services.Implementation; -using MarginTrading.DataReader.Settings; -using Rocks.Caching; - -namespace MarginTrading.DataReader.Modules -{ - public class DataReaderServicesModule : Module - { - protected override void Load(ContainerBuilder builder) - { - builder.RegisterType().As() - .SingleInstance(); - builder.RegisterType().As() - .SingleInstance(); - builder.RegisterType().As() - .SingleInstance(); - builder.RegisterType().As() - .SingleInstance(); - builder.RegisterType().As() - .SingleInstance(); - builder.RegisterType().As() - .SingleInstance(); - - builder.Register(c => - { - var settings = c.Resolve>(); - return new RabbitMqService(c.Resolve(), c.Resolve(), - settings.Nested(s => s.Db.StateConnString), settings.CurrentValue.Env); - }) - .As() - .SingleInstance(); - - builder.RegisterInstance(new ConsoleLWriter(Console.WriteLine)).As() - .SingleInstance(); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Modules/DataReaderSettingsModule.cs b/src/MarginTrading.DataReader/Modules/DataReaderSettingsModule.cs deleted file mode 100644 index 6531d69d9..000000000 --- a/src/MarginTrading.DataReader/Modules/DataReaderSettingsModule.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Autofac; -using Lykke.SettingsReader; -using MarginTrading.DataReader.Settings; - -namespace MarginTrading.DataReader.Modules -{ - public class DataReaderSettingsModule : Module - { - private readonly IReloadingManager _settings; - - public DataReaderSettingsModule(IReloadingManager settings) - { - _settings = settings; - } - - protected override void Load(ContainerBuilder builder) - { - builder.RegisterInstance(_settings).SingleInstance(); - builder.RegisterInstance(_settings.CurrentValue).SingleInstance(); - } - } -} diff --git a/src/MarginTrading.DataReader/Program.cs b/src/MarginTrading.DataReader/Program.cs deleted file mode 100644 index 15c150b04..000000000 --- a/src/MarginTrading.DataReader/Program.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using MarginTrading.Common.Services; -using Microsoft.AspNetCore.Hosting; - -#pragma warning disable 1591 - -namespace MarginTrading.DataReader -{ - public class Program - { - public static void Main(string[] args) - { - var restartAttempsLeft = 5; - - while (restartAttempsLeft > 0) - { - try - { - var host = new WebHostBuilder() - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls("http://*:5008") - .UseStartup() - .UseApplicationInsights() - .Build(); - - host.Run(); - - restartAttempsLeft = 0; - } - catch (Exception e) - { - Console.WriteLine($"Error: {e.Message}{Environment.NewLine}{e.StackTrace}{Environment.NewLine}Restarting..."); - LogLocator.CommonLog?.WriteFatalErrorAsync( - "MT DataReader", "Restart host", $"Attempts left: {restartAttempsLeft}", e); - restartAttempsLeft--; - Console.WriteLine($"Error: {e.Message}{Environment.NewLine}Restarting..."); - Thread.Sleep(10000); - } - } - } - } -} diff --git a/src/MarginTrading.DataReader/Properties/AssemblyInfo.cs b/src/MarginTrading.DataReader/Properties/AssemblyInfo.cs deleted file mode 100644 index 1a18f4870..000000000 --- a/src/MarginTrading.DataReader/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Reflection; -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: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Lykke")] -[assembly: AssemblyProduct("MarginTrading.DataReader")] -[assembly: AssemblyTrademark("")] - -// 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("e6a712c9-78f1-4573-b3a4-318fa4a6ba0d")] diff --git a/src/MarginTrading.DataReader/Services/IOrderBookSnapshotReaderService.cs b/src/MarginTrading.DataReader/Services/IOrderBookSnapshotReaderService.cs deleted file mode 100644 index b25b027a9..000000000 --- a/src/MarginTrading.DataReader/Services/IOrderBookSnapshotReaderService.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Threading.Tasks; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Orderbooks; - -namespace MarginTrading.DataReader.Services -{ - public interface IOrderBookSnapshotReaderService - { - Task GetOrderBook(string instrument); - } -} diff --git a/src/MarginTrading.DataReader/Services/IOrdersSnapshotReaderService.cs b/src/MarginTrading.DataReader/Services/IOrdersSnapshotReaderService.cs deleted file mode 100644 index c7538ee37..000000000 --- a/src/MarginTrading.DataReader/Services/IOrdersSnapshotReaderService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using MarginTrading.Backend.Core; - -namespace MarginTrading.DataReader.Services -{ - public interface IOrdersSnapshotReaderService - { - Task> GetAllAsync(); - Task> GetActiveAsync(); - Task> GetPendingAsync(); - Task> GetActiveByAccountIdsAsync(string[] accountIds); - } -} diff --git a/src/MarginTrading.DataReader/Services/Implementation/OrderBookSnapshotReaderService.cs b/src/MarginTrading.DataReader/Services/Implementation/OrderBookSnapshotReaderService.cs deleted file mode 100644 index 280a17306..000000000 --- a/src/MarginTrading.DataReader/Services/Implementation/OrderBookSnapshotReaderService.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using MarginTrading.Backend.Core; -using MarginTrading.Backend.Core.Helpers; -using MarginTrading.Backend.Core.Orderbooks; -using Rocks.Caching; - -namespace MarginTrading.DataReader.Services.Implementation -{ - internal class OrderBookSnapshotReaderService : IOrderBookSnapshotReaderService - { - private readonly IMarginTradingBlobRepository _blobRepository; - private readonly ICacheProvider _cacheProvider; - private static string BlobName = "orderbook"; - - public OrderBookSnapshotReaderService(IMarginTradingBlobRepository blobRepository, ICacheProvider cacheProvider) - { - _blobRepository = blobRepository; - _cacheProvider = cacheProvider; - } - - public async Task GetOrderBook(string instrument) - { - var orderbookState = await GetOrderBookStateAsync(); - return orderbookState.GetValueOrDefault(instrument, k => new OrderBook(instrument)); - } - - private Task> GetOrderBookStateAsync() - { - return _cacheProvider.GetAsync(nameof(OrderBookSnapshotReaderService), - async () => - { - var orderbookState = await _blobRepository.ReadAsync>( - LykkeConstants.StateBlobContainer, BlobName) ?? - new Dictionary(); - return new CachableResult>(orderbookState, - CachingParameters.FromSeconds(10)); - }); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Services/Implementation/OrdersSnapshotReaderService.cs b/src/MarginTrading.DataReader/Services/Implementation/OrdersSnapshotReaderService.cs deleted file mode 100644 index fdb9e35f9..000000000 --- a/src/MarginTrading.DataReader/Services/Implementation/OrdersSnapshotReaderService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Rocks.Caching; -using System.Linq; -using MarginTrading.Backend.Core; - -namespace MarginTrading.DataReader.Services.Implementation -{ - internal class OrdersSnapshotReaderService : IOrdersSnapshotReaderService - { - private readonly IMarginTradingBlobRepository _blobRepository; - private readonly ICacheProvider _cacheProvider; - private const string BlobName = "orders"; - - public OrdersSnapshotReaderService(IMarginTradingBlobRepository blobRepository, ICacheProvider cacheProvider) - { - _blobRepository = blobRepository; - _cacheProvider = cacheProvider; - } - - public Task> GetAllAsync() - { - return _cacheProvider.GetAsync(nameof(OrdersSnapshotReaderService), - async () => new CachableResult>( - (IReadOnlyList) await _blobRepository.ReadAsync>( - LykkeConstants.StateBlobContainer, BlobName) ?? Array.Empty(), - CachingParameters.FromSeconds(10))); - } - - public async Task> GetActiveAsync() - { - return (await GetAllAsync()).Where(o => o.Status == OrderStatus.Active).ToList(); - } - - public async Task> GetActiveByAccountIdsAsync(string[] accountIds) - { - return (await GetAllAsync()).Where(o => o.Status == OrderStatus.Active && accountIds.Contains(o.AccountId)).ToList(); - } - - public async Task> GetPendingAsync() - { - return (await GetAllAsync()).Where(o => o.Status == OrderStatus.WaitingForExecution).ToList(); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Settings/AppSettings.cs b/src/MarginTrading.DataReader/Settings/AppSettings.cs deleted file mode 100644 index eccf5eba7..000000000 --- a/src/MarginTrading.DataReader/Settings/AppSettings.cs +++ /dev/null @@ -1,11 +0,0 @@ -using MarginTrading.Common.Settings; - -namespace MarginTrading.DataReader.Settings -{ - public class AppSettings - { - public DataReaderLiveDemoSettings MtDataReader { get; set; } - public SlackNotificationSettings SlackNotifications { get; set; } - public ClientAccountServiceSettings ClientAccountServiceClient { get; set; } - } -} diff --git a/src/MarginTrading.DataReader/Settings/DataReaderLiveDemoSettings.cs b/src/MarginTrading.DataReader/Settings/DataReaderLiveDemoSettings.cs deleted file mode 100644 index 5dcfff713..000000000 --- a/src/MarginTrading.DataReader/Settings/DataReaderLiveDemoSettings.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MarginTrading.DataReader.Settings -{ - public class DataReaderLiveDemoSettings - { - public DataReaderSettings Live { get; set; } - public DataReaderSettings Demo { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Settings/DataReaderSettings.cs b/src/MarginTrading.DataReader/Settings/DataReaderSettings.cs deleted file mode 100644 index c28ef470c..000000000 --- a/src/MarginTrading.DataReader/Settings/DataReaderSettings.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Lykke.SettingsReader.Attributes; - -namespace MarginTrading.DataReader.Settings -{ - public class DataReaderSettings - { - public string ApiKey { get; set; } - - [Optional] - public string Env { get; set; } - - [Optional] - public bool IsLive { get; set; } - - public Db Db { get; set; } - - [Optional] - public string ApplicationInsightsKey { get; set; } - - public RabbitMqConsumers Consumers { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Settings/Db.cs b/src/MarginTrading.DataReader/Settings/Db.cs deleted file mode 100644 index 2b1f643c4..000000000 --- a/src/MarginTrading.DataReader/Settings/Db.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MarginTrading.DataReader.Settings -{ - public class Db - { - public string LogsConnString { get; set; } - public string MarginTradingConnString { get; set; } - public string HistoryConnString { get; set; } - public string StateConnString { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Settings/RabbitMqConsumers.cs b/src/MarginTrading.DataReader/Settings/RabbitMqConsumers.cs deleted file mode 100644 index f8924b84b..000000000 --- a/src/MarginTrading.DataReader/Settings/RabbitMqConsumers.cs +++ /dev/null @@ -1,9 +0,0 @@ -using MarginTrading.Common.RabbitMq; - -namespace MarginTrading.DataReader.Settings -{ - public class RabbitMqConsumers - { - public RabbitMqSettings MarginTradingEnabledChanged { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Settings/SlackNotificationSettings.cs b/src/MarginTrading.DataReader/Settings/SlackNotificationSettings.cs deleted file mode 100644 index 890860b43..000000000 --- a/src/MarginTrading.DataReader/Settings/SlackNotificationSettings.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Lykke.AzureQueueIntegration; - -namespace MarginTrading.DataReader.Settings -{ - public class SlackNotificationSettings - { - public AzureQueueSettings AzureQueue { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/Startup.cs b/src/MarginTrading.DataReader/Startup.cs deleted file mode 100644 index fe3156ba3..000000000 --- a/src/MarginTrading.DataReader/Startup.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System; -using System.Linq; -using Autofac; -using Autofac.Extensions.DependencyInjection; -using Common.Log; -using JetBrains.Annotations; -using Lykke.Common.ApiLibrary.Middleware; -using Lykke.Common.ApiLibrary.Swagger; -using Lykke.Logs; -using Lykke.SettingsReader; -using Lykke.SlackNotification.AzureQueue; -using MarginTrading.Common.Extensions; -using MarginTrading.Common.Modules; -using MarginTrading.Common.Services; -using MarginTrading.DataReader.Filters; -using MarginTrading.DataReader.Infrastructure; -using MarginTrading.DataReader.Middleware; -using MarginTrading.DataReader.Modules; -using MarginTrading.DataReader.Settings; -using Microsoft.ApplicationInsights.Extensibility; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Swashbuckle.Swagger.Model; -using LogLevel = Microsoft.Extensions.Logging.LogLevel; - -#pragma warning disable 1591 - -namespace MarginTrading.DataReader -{ - public class Startup - { - public IConfigurationRoot Configuration { get; } - public IHostingEnvironment Environment { get; } - public IContainer ApplicationContainer { get; set; } - - public Startup(IHostingEnvironment env) - { - Configuration = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddDevJson(env) - .AddEnvironmentVariables() - .Build(); - - Environment = env; - } - - [UsedImplicitly] - public IServiceProvider ConfigureServices(IServiceCollection services) - { - var loggerFactory = new LoggerFactory() - .AddConsole(LogLevel.Error) - .AddDebug(LogLevel.Error); - - services.AddSingleton(loggerFactory); - services.AddLogging(); - services.AddSingleton(Configuration); - services.AddMvc(options => options.Filters.Add(typeof(MarginTradingEnabledFilter))); - services.AddAuthentication(KeyAuthOptions.AuthenticationScheme) - .AddScheme(KeyAuthOptions.AuthenticationScheme, "", options => { }); - - var isLive = Configuration.IsLive(); - - services.AddSwaggerGen(options => - { - options.DefaultLykkeConfiguration("v1", $"MarginTrading_DataReader_Api_{(isLive ? "Live" : "Demo")}"); - options.OperationFilter(); - options.OperationFilter(); - options.SchemaFilter(); - }); - - var builder = new ContainerBuilder(); - - var readerSettings = Configuration.LoadSettings() - .Nested(s => - { - var inner = isLive ? s.MtDataReader.Live : s.MtDataReader.Demo; - inner.IsLive = isLive; - inner.Env = isLive ? "Live" : "Demo"; - return s; - }); - - var settings = readerSettings.Nested(s => isLive ? s.MtDataReader.Live : s.MtDataReader.Demo); - - Console.WriteLine($"IsLive: {settings.CurrentValue.IsLive}"); - - SetupLoggers(services, readerSettings, settings); - - RegisterModules(builder, readerSettings, settings); - - builder.Populate(services); - ApplicationContainer = builder.Build(); - return new AutofacServiceProvider(ApplicationContainer); - } - - [UsedImplicitly] - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, - IApplicationLifetime appLifetime) - { - app.UseLykkeMiddleware("MarginTradingDataReader", -#if DEBUG - ex => ex.ToString()); -#else - ex => new { ErrorMessage = "Technical problem" }); -#endif - app.UseAuthentication(); - app.UseMvc(); - app.UseSwagger(); - app.UseSwagger(DocumentFilter, "swagger/{apiVersion}/swagger-no-api-key.json"); - app.UseSwaggerUi(); - - appLifetime.ApplicationStopped.Register(() => ApplicationContainer.Dispose()); - - var settings = app.ApplicationServices.GetService(); - - appLifetime.ApplicationStarted.Register(() => - { - if (!string.IsNullOrEmpty(settings.ApplicationInsightsKey)) - { - TelemetryConfiguration.Active.InstrumentationKey = - settings.ApplicationInsightsKey; - } - LogLocator.CommonLog?.WriteMonitorAsync("", "", "Started"); - }); - - appLifetime.ApplicationStopping.Register(() => - { - LogLocator.CommonLog?.WriteMonitorAsync("", "", "Terminating"); - }); - } - - /// - /// If generating swagger without api-key - strip it. - /// - /// - /// This is a nasty workaround for autorest generator not to create apiKey parameters for every method. - /// - private void DocumentFilter(HttpRequest httpRequest, SwaggerDocument swaggerDocument) - { - foreach (var path in swaggerDocument.Paths.Values) - { - path.Get.Parameters?.Remove(path.Get.Parameters.First(p => p.Name == KeyAuthOptions.DefaultHeaderName)); - } - } - - private void RegisterModules(ContainerBuilder builder, IReloadingManager readerSettings, - IReloadingManager settings) - { - builder.RegisterModule(new DataReaderSettingsModule(settings)); - builder.RegisterModule(new DataReaderRepositoriesModule(settings, LogLocator.CommonLog)); - builder.RegisterModule(new DataReaderServicesModule()); - builder.RegisterModule(new MarginTradingCommonModule()); - builder.RegisterModule(new DataReaderExternalServicesModule(readerSettings)); - } - - private static void SetupLoggers(IServiceCollection services, IReloadingManager mtSettings, - IReloadingManager settings) - { - var consoleLogger = new LogToConsole(); - - var commonSlackService = - services.UseSlackNotificationsSenderViaAzureQueue(mtSettings.CurrentValue.SlackNotifications.AzureQueue, - new LogToConsole()); - - var slackService = - new MtSlackNotificationsSender(commonSlackService, "MT DataReader", settings.CurrentValue.Env); - - var log = services.UseLogToAzureStorage(settings.Nested(s => s.Db.LogsConnString), slackService, - "MarginTradingDataReaderLog", consoleLogger); - - LogLocator.CommonLog = log; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.DataReader/docker-compose.yml b/src/MarginTrading.DataReader/docker-compose.yml deleted file mode 100644 index 883301616..000000000 --- a/src/MarginTrading.DataReader/docker-compose.yml +++ /dev/null @@ -1,17 +0,0 @@ -version: '2' -services: - margintrading: - image: lykkedev/margintradingdatareader - container_name: margintradingdatareader - environment: - - SettingsUrl=${SettingsUrl} - - IsLive=${IsLive} - ports: - - "5008:5008" - networks: - mynet: - aliases: - - margintradingdatareader -networks: - mynet: - driver: bridge diff --git a/src/MarginTrading.DataReader/web.config b/src/MarginTrading.DataReader/web.config deleted file mode 100644 index 8700b60c0..000000000 --- a/src/MarginTrading.DataReader/web.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Application.cs b/src/MarginTrading.Frontend/Application.cs deleted file mode 100644 index b3c19bde6..000000000 --- a/src/MarginTrading.Frontend/Application.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Threading.Tasks; -using Autofac; -using Common.Log; -using MarginTrading.Frontend.Services; -using MarginTrading.Frontend.Wamp; -using WampSharp.V2.Realm; - -namespace MarginTrading.Frontend -{ - public class Application - { - private readonly IComponentContext _componentContext; - private readonly IConsole _consoleWriter; - private readonly ILog _logger; - private readonly WampSessionsService _wampSessionsService; - private const string ServiceName = "MarginTrading.Frontend"; - - public Application( - IComponentContext componentContext, - IConsole consoleWriter, - ILog logger, - WampSessionsService wampSessionsService) - { - _componentContext = componentContext; - _consoleWriter = consoleWriter; - _logger = logger; - _wampSessionsService = wampSessionsService; - } - - public async Task StartAsync() - { - _consoleWriter.WriteLine($"Staring {ServiceName}"); - await _logger.WriteInfoAsync(ServiceName, null, null, "Starting broker"); - try - { - var rpcMethods = _componentContext.Resolve(); - var realm = _componentContext.Resolve(); - realm.SessionCreated += (sender, args) => _wampSessionsService.OpenedSessionsCount++; - realm.SessionClosed += (sender, args) => _wampSessionsService.OpenedSessionsCount--; - await realm.Services.RegisterCallee(rpcMethods); - } - catch (Exception ex) - { - _consoleWriter.WriteLine($"{ServiceName} error: {ex.Message}"); - await _logger.WriteErrorAsync(ServiceName, "Application.RunAsync", null, ex); - } - } - - public void Stop() - { - _consoleWriter.WriteLine($"Closing {ServiceName}"); - } - } -} diff --git a/src/MarginTrading.Frontend/Connected Services/Application Insights/ConnectedService.json b/src/MarginTrading.Frontend/Connected Services/Application Insights/ConnectedService.json deleted file mode 100644 index f4580e22d..000000000 --- a/src/MarginTrading.Frontend/Connected Services/Application Insights/ConnectedService.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "ProviderId": "Microsoft.ApplicationInsights.ConnectedService.ConnectedServiceProvider", - "Version": "8.6.404.2", - "GettingStartedDocument": { - "Uri": "https://go.microsoft.com/fwlink/?LinkID=798432" - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Controllers/AccountsHistoryController.cs b/src/MarginTrading.Frontend/Controllers/AccountsHistoryController.cs deleted file mode 100644 index 311e66e22..000000000 --- a/src/MarginTrading.Frontend/Controllers/AccountsHistoryController.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Threading.Tasks; -using MarginTrading.Contract.ClientContracts; -using MarginTrading.Frontend.Extensions; -using MarginTrading.Frontend.Models; -using MarginTrading.Frontend.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.Frontend.Controllers -{ - [Route("api/accountshistory")] - [Authorize] - public class AccountsHistoryController : Controller - { - private readonly RpcFacade _rpcFacade; - - public AccountsHistoryController(RpcFacade rpcFacade) - { - _rpcFacade = rpcFacade; - } - - [Route("")] - [HttpGet] - public async Task> GetAccountHistory([FromQuery]AccountHistoryFiltersClientRequest request) - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError(); - } - - var history = await _rpcFacade.GetAccountHistory(clientId, request); - - return ResponseModel.CreateOk(history); - } - - [Route("timeline")] - [HttpGet] - public async Task> GetAccountHistoryTimeline([FromQuery]AccountHistoryFiltersClientRequest request) - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError(); - } - - var history = await _rpcFacade.GetAccountHistoryTimeline(clientId, request); - - return ResponseModel.CreateOk(history); - } - } -} diff --git a/src/MarginTrading.Frontend/Controllers/HomeController.cs b/src/MarginTrading.Frontend/Controllers/HomeController.cs deleted file mode 100644 index 1417b1ece..000000000 --- a/src/MarginTrading.Frontend/Controllers/HomeController.cs +++ /dev/null @@ -1,25 +0,0 @@ -using MarginTrading.Common.Documentation; -using MarginTrading.Frontend.Models; -using MarginTrading.Frontend.Wamp; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.Frontend.Controllers -{ - [ApiExplorerSettings(IgnoreApi = true)] - public class HomeController : Controller - { - [Route("")] - [HttpGet] - public IActionResult Index() - { - var doc = new TypeDocGenerator(); - var model = new MethodInfoModel - { - Rpc = doc.GetDocumentation(typeof(IRpcMtFrontend)), - Topic = doc.GetDocumentation(typeof(IWampTopic)) - }; - - return View(model); - } - } -} diff --git a/src/MarginTrading.Frontend/Controllers/InitController.cs b/src/MarginTrading.Frontend/Controllers/InitController.cs deleted file mode 100644 index b82ace42c..000000000 --- a/src/MarginTrading.Frontend/Controllers/InitController.cs +++ /dev/null @@ -1,174 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.Common.Services; -using MarginTrading.Contract.ClientContracts; -using MarginTrading.Frontend.Extensions; -using MarginTrading.Frontend.Models; -using MarginTrading.Frontend.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.Frontend.Controllers -{ - [Route("api/init")] - [Authorize] - public class InitController : Controller - { - private readonly RpcFacade _rpcFacade; - private readonly IDateService _dateService; - - public InitController(RpcFacade rpcFacade, IDateService dateService) - { - _rpcFacade = rpcFacade; - _dateService = dateService; - } - - [Route("data")] - [HttpGet] - public async Task> InitData() - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError(); - } - - var initData = await _rpcFacade.InitData(clientId); - - return ResponseModel.CreateOk(initData); - } - - [Route("accounts")] - [HttpGet] - public async Task> InitAccounts() - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError(); - } - - var initAccounts = await _rpcFacade.InitAccounts(clientId); - - return ResponseModel.CreateOk(initAccounts); - } - - [Route("accountinstruments")] - [HttpGet] - public async Task> AccountInstruments() - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError(); - } - - var initAccountInstruments = await _rpcFacade.AccountInstruments(clientId); - - return ResponseModel.CreateOk(initAccountInstruments); - } - - [Route("chart")] - [HttpGet] - public async Task> InitChart() - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError(); - } - - var initGraph = await _rpcFacade.InitGraph(clientId); - - return ResponseModel.CreateOk(initGraph); - } - - [Route("chart/filtered")] - [HttpPost] - public async Task> InitChartFiltered([FromBody] InitChartDataClientRequest request) - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError(); - } - - var initGraph = await _rpcFacade.InitGraph(clientId, request?.AssetIds); - - return ResponseModel.CreateOk(initGraph); - } - - [Route("prices")] - [HttpGet] - public async Task>> InitPrices() - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError>(); - } - - - var initPrices = await _rpcFacade.InitPrices(clientId); - - return ResponseModel>.CreateOk(initPrices); - } - - [Route("prices/filtered")] - [HttpPost] - public async Task>> InitPricesWithFilter([FromBody] InitPricesFilteredRequest request) - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError>(); - } - - var initPrices = await _rpcFacade.InitPrices(clientId, request?.AssetIds); - - return ResponseModel>.CreateOk(initPrices); - } - - [Route("~/api/v2/init/prices")] - [HttpGet] - public async Task> InitPricesV2() - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError(); - } - - var initPrices = await _rpcFacade.InitPrices(clientId); - var result = new InitPricesResponse { Prices = initPrices.Values.ToArray(), ServerTime = _dateService.Now() }; - - return ResponseModel.CreateOk(result); - } - - [Route("~/api/v2/init/prices/filtered")] - [HttpPost] - public async Task> InitPricesWithFilterV2([FromBody] InitPricesFilteredRequest request) - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError(); - } - - var initPrices = await _rpcFacade.InitPrices(clientId, request?.AssetIds); - var result = new InitPricesResponse { Prices = initPrices.Values.ToArray(), ServerTime = _dateService.Now() }; - - return ResponseModel.CreateOk(result); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Controllers/IsAliveController.cs b/src/MarginTrading.Frontend/Controllers/IsAliveController.cs deleted file mode 100644 index ac03658ae..000000000 --- a/src/MarginTrading.Frontend/Controllers/IsAliveController.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Threading.Tasks; -using MarginTrading.Common.Services; -using MarginTrading.Contract.ClientContracts; -using MarginTrading.Frontend.Services; -using MarginTrading.Frontend.Settings; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.PlatformAbstractions; - -namespace MarginTrading.Frontend.Controllers -{ - [Route("api/isAlive")] - public class IsAliveController : Controller - { - private readonly MtFrontendSettings _setings; - private readonly IHttpRequestService _httpRequestService; - private readonly WampSessionsService _wampSessionsService; - private readonly IDateService _dateService; - - public IsAliveController(MtFrontendSettings setings, - IHttpRequestService httpRequestService, - WampSessionsService wampSessionsService, - IDateService dateService) - { - _setings = setings; - _httpRequestService = httpRequestService; - _wampSessionsService = wampSessionsService; - _dateService = dateService; - } - - [HttpGet] - public async Task Get() - { - var result = new IsAliveExtendedResponse - { - Version = PlatformServices.Default.Application.ApplicationVersion, - Env = _setings.MarginTradingFront.Env, - ServerTime = _dateService.Now() - }; - - result.LiveVersion = await GetBackendVersion(true); - result.DemoVersion = await GetBackendVersion(false); - result.WampOpened = _wampSessionsService.OpenedSessionsCount; - - return result; - } - - private async Task GetBackendVersion(bool isLive) - { - try - { - var responce = await _httpRequestService.GetAsync("isAlive", isLive, 3); - return responce.Version; - } - catch (MaintenanceException ex) - { - return $"Maintenance since {ex.EnabledAt}"; - } - catch - { - return "Error"; - } - - } - } -} diff --git a/src/MarginTrading.Frontend/Controllers/OrderBookController.cs b/src/MarginTrading.Frontend/Controllers/OrderBookController.cs deleted file mode 100644 index 33ccfae61..000000000 --- a/src/MarginTrading.Frontend/Controllers/OrderBookController.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Threading.Tasks; -using MarginTrading.Contract.ClientContracts; -using MarginTrading.Frontend.Models; -using MarginTrading.Frontend.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.Frontend.Controllers -{ - [Route("api/orderbook")] - [Authorize] - public class OrderBookController : Controller - { - private readonly RpcFacade _rpcFacade; - - public OrderBookController(RpcFacade rpcFacade) - { - _rpcFacade = rpcFacade; - } - - [HttpGet] - [Route("{instrument}")] - public async Task> GetOrderBook(string instrument) - { - var result = await _rpcFacade.GetOrderBook(instrument); - - return ResponseModel.CreateOk(result); - } - } -} diff --git a/src/MarginTrading.Frontend/Controllers/OrdersController.cs b/src/MarginTrading.Frontend/Controllers/OrdersController.cs deleted file mode 100644 index 63209c7a6..000000000 --- a/src/MarginTrading.Frontend/Controllers/OrdersController.cs +++ /dev/null @@ -1,160 +0,0 @@ -using System.Threading.Tasks; -using MarginTrading.Common.Middleware; -using MarginTrading.Contract.ClientContracts; -using MarginTrading.Frontend.Extensions; -using MarginTrading.Frontend.Models; -using MarginTrading.Frontend.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.Frontend.Controllers -{ - [Route("api/orders")] - [Authorize] - [MiddlewareFilter(typeof(RequestLoggingPipeline))] - public class OrdersController : Controller - { - private readonly RpcFacade _rpcFacade; - - public OrdersController(RpcFacade rpcFacade) - { - _rpcFacade = rpcFacade; - } - - [Route("place")] - [HttpPost] - public async Task> PlaceOrder([FromBody] NewOrderClientContract request) - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError(); - } - - var result = await _rpcFacade.PlaceOrder(clientId, request); - - if (result.IsError()) - { - return ResponseModel.CreateFail(ResponseModel.ErrorCodeType.InconsistentData, - result.Message); - } - - return ResponseModel.CreateOk(result.Result); - } - - [Route("close")] - [HttpPost] - public async Task> CloseOrder([FromBody] CloseOrderClientRequest request) - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError(); - } - - var result = await _rpcFacade.CloseOrder(clientId, request); - - if (result.IsError() || !result.Result) - { - return ResponseModel.CreateFail(ResponseModel.ErrorCodeType.InconsistentData, - result.Message); - } - - return ResponseModel.CreateOk(result.Result); - } - - [Route("cancel")] - [HttpPost] - public async Task> CancelOrder([FromBody] CloseOrderClientRequest request) - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError(); - } - - var result = await _rpcFacade.CancelOrder(clientId, request); - - if (result.IsError() || !result.Result) - { - return ResponseModel.CreateFail(ResponseModel.ErrorCodeType.InconsistentData, - result.Message); - } - - return ResponseModel.CreateOk(result.Result); - } - - [Route("positions")] - [HttpGet] - public async Task> GetOpenPositions() - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError(); - } - - var result = await _rpcFacade.GetOpenPositions(clientId); - - return ResponseModel.CreateOk(result); - } - - [Route("positions/{accountId}")] - [HttpGet] - public async Task> GetAccountOpenPositions(string accountId) - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError(); - } - - var result = await _rpcFacade.GetAccountOpenPositions(clientId, accountId); - - return ResponseModel.CreateOk(result); - } - - [Route("")] - [HttpGet] - public async Task> GetClientOrders() - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError(); - } - - var result = await _rpcFacade.GetClientOrders(clientId); - - return ResponseModel.CreateOk(result); - } - - [Route("limits")] - [HttpPut] - public async Task> ChangeOrderLimits([FromBody] ChangeOrderLimitsClientRequest request) - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError(); - } - - var result = await _rpcFacade.ChangeOrderLimits(clientId, request); - - if (result.IsError() || !result.Result) - { - return ResponseModel.CreateFail(ResponseModel.ErrorCodeType.InconsistentData, - result.Message); - } - - return ResponseModel.CreateOk(result.Result); - } - } -} diff --git a/src/MarginTrading.Frontend/Controllers/UserController.cs b/src/MarginTrading.Frontend/Controllers/UserController.cs deleted file mode 100644 index 67d130928..000000000 --- a/src/MarginTrading.Frontend/Controllers/UserController.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Threading.Tasks; -using MarginTrading.Common.Middleware; -using MarginTrading.Common.Services.Client; -using MarginTrading.Common.Settings; -using MarginTrading.Frontend.Extensions; -using MarginTrading.Frontend.Models; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.Frontend.Controllers -{ - [Route("api/user")] - [Authorize] - [MiddlewareFilter(typeof(RequestLoggingPipeline))] - public class UserController: Controller - { - private readonly IClientAccountService _clientNotificationService; - - public UserController(IClientAccountService clientNotificationService) - { - _clientNotificationService = clientNotificationService; - } - - [Route("notificationId")] - [HttpGet] - public async Task> GetNotificationId() - { - var clientId = this.GetClientId(); - - if (clientId == null) - { - return this.UserNotFoundError(); - } - - var notificationId = await _clientNotificationService.GetNotificationId(clientId); - - return ResponseModel.CreateOk(notificationId); - } - } -} diff --git a/src/MarginTrading.Frontend/Controllers/WatchListsController.cs b/src/MarginTrading.Frontend/Controllers/WatchListsController.cs deleted file mode 100644 index df150cad7..000000000 --- a/src/MarginTrading.Frontend/Controllers/WatchListsController.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System.Collections.Generic; -using System.Security.Claims; -using System.Threading.Tasks; -using AspNet.Security.OpenIdConnect.Extensions; -using MarginTrading.Common.Middleware; -using MarginTrading.Frontend.Models; -using MarginTrading.Frontend.Repositories; -using MarginTrading.Frontend.Repositories.Contract; -using MarginTrading.Frontend.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.Frontend.Controllers -{ - [Route("api/watchlists")] - [Authorize] - [MiddlewareFilter(typeof(RequestLoggingPipeline))] - public class WatchListsController : Controller - { - private readonly IWatchListService _watchListService; - - public WatchListsController(IWatchListService watchListService) - { - _watchListService = watchListService; - } - - [HttpGet] - [Route("")] - public async Task>> GetWatchLists() - { - var clientId = User.GetClaim(ClaimTypes.NameIdentifier); - - if (clientId == null) - { - return ResponseModel>.CreateFail(ResponseModel.ErrorCodeType.NoAccess, "Wrong token"); - } - - var result = new ResponseModel> - { - Result = await _watchListService.GetAllAsync(clientId) - }; - - return result; - } - - [HttpPost] - [Route("")] - public async Task> AddWatchList([FromBody]WatchList model) - { - var clientId = User.GetClaim(ClaimTypes.NameIdentifier); - - if (clientId == null) - { - return ResponseModel.CreateFail(ResponseModel.ErrorCodeType.NoAccess, "Wrong token"); - } - - var result = new ResponseModel(); - - if (model.AssetIds == null || model.AssetIds.Count == 0) - { - return ResponseModel.CreateInvalidFieldError("AssetIds", "AssetIds should not be empty"); - } - - var addResult = await _watchListService.AddAsync(model.Id, clientId, model.Name, model.AssetIds); - - switch (addResult.Status) - { - case WatchListStatus.AssetNotFound: - return ResponseModel.CreateFail(ResponseModel.ErrorCodeType.AssetNotFound, $"Asset '{addResult.Message}' is not found or not allowed"); - case WatchListStatus.ReadOnly: - return ResponseModel.CreateFail(ResponseModel.ErrorCodeType.InconsistentData, "This watch list is readonly"); - } - - result.Result = addResult.Result; - - return result; - } - - [HttpDelete] - [Route("{id}")] - public async Task DeleteWatchList(string id) - { - var clientId = User.GetClaim(ClaimTypes.NameIdentifier); - - if (clientId == null) - { - return ResponseModel>.CreateFail(ResponseModel.ErrorCodeType.NoAccess, "Wrong token"); - } - - var result = await _watchListService.DeleteAsync(clientId, id); - - switch (result.Status) - { - case WatchListStatus.NotFound: - return ResponseModel.CreateFail(ResponseModel.ErrorCodeType.NoData, "Watch list not found"); - case WatchListStatus.ReadOnly: - return ResponseModel.CreateFail(ResponseModel.ErrorCodeType.InconsistentData, "Readonly watch list can't be deleted"); - } - - return ResponseModel.CreateOk(); - } - } -} diff --git a/src/MarginTrading.Frontend/Dockerfile b/src/MarginTrading.Frontend/Dockerfile deleted file mode 100644 index 8a15508ac..000000000 --- a/src/MarginTrading.Frontend/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM microsoft/aspnetcore:2.0 -WORKDIR /app -COPY . . -ENTRYPOINT ["dotnet", "MarginTrading.Frontend.dll"] diff --git a/src/MarginTrading.Frontend/Extensions/ControllerExtensions.cs b/src/MarginTrading.Frontend/Extensions/ControllerExtensions.cs deleted file mode 100644 index 789f750c6..000000000 --- a/src/MarginTrading.Frontend/Extensions/ControllerExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; -using AspNet.Security.OpenIdConnect.Extensions; -using MarginTrading.Frontend.Models; -using Microsoft.AspNetCore.Mvc; - -namespace MarginTrading.Frontend.Extensions -{ - public static class ControllerExtensions - { - public static string GetClientId(this Controller controller) - { - return controller.User.GetClaim(ClaimTypes.NameIdentifier); - } - - public static ResponseModel UserNotFoundError(this Controller controller) - { - return ResponseModel.CreateFail(ResponseModel.ErrorCodeType.NoAccess, "User not found"); - } - } -} diff --git a/src/MarginTrading.Frontend/Infrastructure/AddAuthorizationHeaderParameter.cs b/src/MarginTrading.Frontend/Infrastructure/AddAuthorizationHeaderParameter.cs deleted file mode 100644 index 8c8a98ca9..000000000 --- a/src/MarginTrading.Frontend/Infrastructure/AddAuthorizationHeaderParameter.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc.Authorization; -using Swashbuckle.Swagger.Model; -using Swashbuckle.SwaggerGen.Generator; - -namespace MarginTrading.Frontend.Infrastructure -{ - public class AddAuthorizationHeaderParameter : IOperationFilter - { - void IOperationFilter.Apply(Operation operation, OperationFilterContext context) - { - var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors; - var isAuthorized = filterPipeline.Select(f => f.Filter).Any(f => f is AuthorizeFilter); - var authorizationRequired = context.ApiDescription.GetControllerAttributes().Any(a => a is AuthorizeAttribute); - if (!authorizationRequired) authorizationRequired = context.ApiDescription.GetActionAttributes().Any(a => a is AuthorizeAttribute); - - if (isAuthorized && authorizationRequired) - { - if (operation.Parameters == null) - operation.Parameters = new List(); - - operation.Parameters.Add(new NonBodyParameter - { - Name = "Authorization", - In = "header", - Description = "Bearer Token", - Required = true, - Type = "string" - }); - } - } - } -} diff --git a/src/MarginTrading.Frontend/MarginTrading.Frontend.csproj b/src/MarginTrading.Frontend/MarginTrading.Frontend.csproj deleted file mode 100644 index bce626931..000000000 --- a/src/MarginTrading.Frontend/MarginTrading.Frontend.csproj +++ /dev/null @@ -1,71 +0,0 @@ - - - netcoreapp2.0 - true - MarginTrading.Frontend - Exe - MarginTrading.Frontend - false - false - false - 1.0.1 - - - - - - - PreserveNewest - - - - - {5EC22A63-BA3F-41A2-A70F-216B7E809390} - MarginTrading.Backend.Contracts - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Middleware/GlobalErrorHandlerMiddleware.cs b/src/MarginTrading.Frontend/Middleware/GlobalErrorHandlerMiddleware.cs deleted file mode 100644 index 6f12f3501..000000000 --- a/src/MarginTrading.Frontend/Middleware/GlobalErrorHandlerMiddleware.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using Common; -using Common.Log; -using MarginTrading.Common.Extensions; -using MarginTrading.Frontend.Models; -using MarginTrading.Frontend.Services; -using Microsoft.AspNetCore.Http; - -namespace MarginTrading.Frontend.Middleware -{ - public class GlobalErrorHandlerMiddleware - { - private readonly ILog _log; - private readonly RequestDelegate _next; - - public GlobalErrorHandlerMiddleware(RequestDelegate next, ILog log) - { - _log = log; - _next = next; - } - - public async Task Invoke(HttpContext context) - { - try - { - await _next.Invoke(context); - } - catch (MaintenanceException) - { - await SendMaintenanceError(context); - } - catch (Exception ex) - { - await LogError(context, ex); - - await SendError(context); - } - } - - private async Task LogError(HttpContext context, Exception ex) - { - using (var ms = new MemoryStream()) - { - context.Request.Body.CopyTo(ms); - ms.Seek(0, SeekOrigin.Begin); - await _log.LogPartFromStream(ms, "GlobalHandler", context.Request.GetUri().AbsoluteUri, ex); - } - } - - private async Task SendError(HttpContext ctx) - { - ctx.Response.ContentType = "application/json"; - ctx.Response.StatusCode = 500; - var response = ResponseModel.CreateFail(ResponseModel.ErrorCodeType.RuntimeProblem, "Technical problems"); - await ctx.Response.WriteAsync(response.ToJson()); - } - - private async Task SendMaintenanceError(HttpContext ctx) - { - ctx.Response.ContentType = "application/json"; - ctx.Response.StatusCode = 503; - var response = ResponseModel.CreateFail(ResponseModel.ErrorCodeType.MaintananceMode, - "Sorry, application is on maintenance. Please try again later."); - await ctx.Response.WriteAsync(response.ToJson()); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Middleware/MiddlewareExtensions.cs b/src/MarginTrading.Frontend/Middleware/MiddlewareExtensions.cs deleted file mode 100644 index caa96bc11..000000000 --- a/src/MarginTrading.Frontend/Middleware/MiddlewareExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.AspNetCore.Builder; - -namespace MarginTrading.Frontend.Middleware -{ - public static class MiddlewareExtensions - { - public static IApplicationBuilder UseOptions(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - - public static IApplicationBuilder UseGlobalErrorHandler(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Middleware/OptionsRequestsMiddleware.cs b/src/MarginTrading.Frontend/Middleware/OptionsRequestsMiddleware.cs deleted file mode 100644 index 67e2d475a..000000000 --- a/src/MarginTrading.Frontend/Middleware/OptionsRequestsMiddleware.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Threading.Tasks; -using MarginTrading.Frontend.Settings; -using Microsoft.AspNetCore.Http; - -namespace MarginTrading.Frontend.Middleware -{ - public class OptionsRequestsMiddleware - { - private readonly RequestDelegate _next; - private readonly CorsSettings _settings; - - public OptionsRequestsMiddleware(RequestDelegate next, CorsSettings settings) - { - _next = next; - _settings = settings; - } - - public async Task Invoke(HttpContext context) - { - if (_settings.HandleOptionsRequest && context.Request.Method == "OPTIONS") - { - context.Response.Headers.Add("Access-Control-Allow-Origin", _settings.AllowOrigins); - context.Response.Headers.Add("Access-Control-Allow-Headers", _settings.AllowHeaders); - context.Response.Headers.Add("Access-Control-Allow-Methods", _settings.AllowMethods); - - if (_settings.AllowCredentials) - context.Response.Headers.Add("Access-Control-Allow-Credentials", "true"); - - context.Response.StatusCode = 200; - await context.Response.WriteAsync("OK"); - } - else - { - await this._next.Invoke(context); - } - } - } -} diff --git a/src/MarginTrading.Frontend/Models/ResponseModel.cs b/src/MarginTrading.Frontend/Models/ResponseModel.cs deleted file mode 100644 index 471dfc00b..000000000 --- a/src/MarginTrading.Frontend/Models/ResponseModel.cs +++ /dev/null @@ -1,99 +0,0 @@ -namespace MarginTrading.Frontend.Models -{ - public class ResponseModel - { - public enum ErrorCodeType - { - MaintananceMode = 11, - - InconsistentData = 100, - InvalidInputField = 101, - - NoData = 200, - AssetNotFound = 201, - - NoAccess = 300, - - RuntimeProblem = 1000 - } - - public class ErrorModel - { - public ErrorCodeType Code { get; set; } - public string Field { get; set; } - public string Message { get; set; } - } - - public ErrorModel Error { get; set; } - - public static ResponseModel CreateInvalidFieldError(string field, string message) - { - return new ResponseModel - { - Error = new ErrorModel - { - Code = ErrorCodeType.InvalidInputField, - Field = field, - Message = message - } - }; - } - - public static ResponseModel CreateFail(ErrorCodeType errorCodeType, string message) - { - return new ResponseModel - { - Error = new ErrorModel - { - Code = errorCodeType, - Message = message - } - }; - } - - private static readonly ResponseModel OkInstance = new ResponseModel(); - - public static ResponseModel CreateOk() - { - return OkInstance; - } - } - - public class ResponseModel : ResponseModel - { - public T Result { get; set; } - - public static ResponseModel CreateOk(T result) - { - return new ResponseModel - { - Result = result - }; - } - - public new static ResponseModel CreateInvalidFieldError(string field, string message) - { - return new ResponseModel - { - Error = new ErrorModel - { - Code = ErrorCodeType.InvalidInputField, - Field = field, - Message = message - } - }; - } - - public new static ResponseModel CreateFail(ErrorCodeType errorCodeType, string message) - { - return new ResponseModel - { - Error = new ErrorModel - { - Code = errorCodeType, - Message = message - } - }; - } - } -} diff --git a/src/MarginTrading.Frontend/Models/TerminalInfo.cs b/src/MarginTrading.Frontend/Models/TerminalInfo.cs deleted file mode 100644 index 574e59ad5..000000000 --- a/src/MarginTrading.Frontend/Models/TerminalInfo.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace MarginTrading.Frontend.Models -{ - public class TerminalInfo - { - public string Name { get; } - public bool DemoEnabled { get; } - public bool LiveEnabled { get; } - - public TerminalInfo(string name, bool demoEnabled, bool liveEnabled) - { - Name = name; - DemoEnabled = demoEnabled; - LiveEnabled = liveEnabled; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Models/TokenModel.cs b/src/MarginTrading.Frontend/Models/TokenModel.cs deleted file mode 100644 index d43ebfa6d..000000000 --- a/src/MarginTrading.Frontend/Models/TokenModel.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MarginTrading.Frontend.Models -{ - public class TokenModel - { - public string Token { get; set; } - } -} diff --git a/src/MarginTrading.Frontend/Models/WampDescriptionModels.cs b/src/MarginTrading.Frontend/Models/WampDescriptionModels.cs deleted file mode 100644 index 901c34444..000000000 --- a/src/MarginTrading.Frontend/Models/WampDescriptionModels.cs +++ /dev/null @@ -1,10 +0,0 @@ -using MarginTrading.Common.Documentation; - -namespace MarginTrading.Frontend.Models -{ - public class MethodInfoModel - { - public MethodDocInfo[] Rpc { get; set; } - public MethodDocInfo[] Topic { get; set; } - } -} diff --git a/src/MarginTrading.Frontend/Models/WatchList.cs b/src/MarginTrading.Frontend/Models/WatchList.cs deleted file mode 100644 index 86b6e76af..000000000 --- a/src/MarginTrading.Frontend/Models/WatchList.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; - -namespace MarginTrading.Frontend.Models -{ - public class WatchList - { - public string Id { get; set; } - public string Name { get; set; } - public List AssetIds { get; set; } - } -} diff --git a/src/MarginTrading.Frontend/Modules/FrontendExternalServicesModule.cs b/src/MarginTrading.Frontend/Modules/FrontendExternalServicesModule.cs deleted file mode 100644 index b7965d2a1..000000000 --- a/src/MarginTrading.Frontend/Modules/FrontendExternalServicesModule.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Autofac; -using Autofac.Extensions.DependencyInjection; -using Lykke.HttpClientGenerator; -using Lykke.HttpClientGenerator.Retries; -using Lykke.Service.ClientAccount.Client; -using Lykke.SettingsReader; -using MarginTrading.Backend.Contracts.DataReaderClient; -using MarginTrading.Frontend.Settings; -using Microsoft.Extensions.DependencyInjection; - -namespace MarginTrading.Frontend.Modules -{ - public class FrontendExternalServicesModule : Module - { - private readonly IReloadingManager _settings; - - public FrontendExternalServicesModule(IReloadingManager settings) - { - _settings = settings; - } - - protected override void Load(ContainerBuilder builder) - { - var services = new ServiceCollection(); - services.RegisterMtDataReaderClientsPair( - HttpClientGenerator.BuildForUrl(_settings.CurrentValue.MtDataReaderDemoServiceClient.ServiceUrl) - .WithApiKey(_settings.CurrentValue.MtDataReaderDemoServiceClient.ApiKey) - .Create(), - HttpClientGenerator.BuildForUrl(_settings.CurrentValue.MtDataReaderLiveServiceClient.ServiceUrl) - .WithApiKey(_settings.CurrentValue.MtDataReaderLiveServiceClient.ApiKey) - .Create()); - - builder.RegisterLykkeServiceClient(_settings.CurrentValue.ClientAccountServiceClient.ServiceUrl); - - builder.Populate(services); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Modules/FrontendModule.cs b/src/MarginTrading.Frontend/Modules/FrontendModule.cs deleted file mode 100644 index 7398b78d4..000000000 --- a/src/MarginTrading.Frontend/Modules/FrontendModule.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System; -using Autofac; -using AzureStorage.Tables; -using Common.Log; -using Lykke.Common; -using Lykke.Service.Session; -using Lykke.SettingsReader; -using MarginTrading.Common.RabbitMq; -using MarginTrading.Common.Services; -using MarginTrading.Common.Services.Client; -using MarginTrading.Common.Services.Settings; -using MarginTrading.Frontend.Repositories; -using MarginTrading.Frontend.Repositories.Contract; -using MarginTrading.Frontend.Repositories.Entities; -using MarginTrading.Frontend.Services; -using MarginTrading.Frontend.Settings; -using MarginTrading.Frontend.Wamp; -using Microsoft.AspNetCore.Http; -using Microsoft.IdentityModel.Tokens; -using Rocks.Caching; -using WampSharp.V2; -using WampSharp.V2.Realm; -using MarginTradingOperationsLogRepository = MarginTrading.Frontend.Repositories.MarginTradingOperationsLogRepository; -using OperationLogEntity = MarginTrading.Frontend.Repositories.Entities.OperationLogEntity; - -namespace MarginTrading.Frontend.Modules -{ - public class FrontendModule: Module - { - private readonly IReloadingManager _settings; - - public FrontendModule(IReloadingManager settings) - { - this._settings = settings; - } - - protected override void Load(ContainerBuilder builder) - { - var host = new WampAuthenticationHost(new WampSessionAuthenticatorFactory()); - var realm = host.RealmContainer.GetRealmByName(WampConstants.FrontEndRealmName); - - builder.RegisterInstance(host) - .As() - .SingleInstance(); - - builder.RegisterInstance(realm) - .As() - .SingleInstance(); - - builder.RegisterInstance(LogLocator.CommonLog) - .As() - .SingleInstance(); - - builder.Register(ctx => - new MarginTradingOperationsLogRepository(AzureTableStorage.Create( - _settings.Nested(s => s.MarginTradingFront.Db.LogsConnString), "MarginTradingFrontendOperationsLog", - LogLocator.CommonLog)) - ) - .SingleInstance(); - - builder.Register(ctx => - new MarginTradingWatchListsRepository(AzureTableStorage.Create( - _settings.Nested(s => s.MarginTradingFront.Db.MarginTradingConnString), - "MarginTradingWatchLists", LogLocator.CommonLog))); - - builder.Register(ctx => - new MaintenanceInfoRepository(_settings.Nested(s => s.MarginTradingFront.Db.MarginTradingConnString))); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); - - var consoleWriter = new ConsoleLWriter(Console.WriteLine); - - builder.RegisterInstance(consoleWriter) - .As() - .SingleInstance(); - - builder.RegisterType() - .AsSelf() - .SingleInstance(); - - builder.RegisterInstance(_settings.CurrentValue) - .SingleInstance(); - - builder.RegisterInstance(_settings.CurrentValue.MarginTradingFront) - .SingleInstance(); - - builder.RegisterInstance(_settings.CurrentValue.MarginTradingFront.RequestLoggerSettings) - .SingleInstance(); - - builder.RegisterInstance(_settings.CurrentValue.MarginTradingFront.CorsSettings) - .SingleInstance(); - - builder.RegisterInstance(_settings.CurrentValue.MarginTradingFront.TerminalsSettings) - .SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder.RegisterType() - .SingleInstance(); - - builder.Register(ctx => - new ClientSessionsClient(_settings.CurrentValue.MarginTradingFront.SessionServiceApiUrl, LogLocator.CommonLog) - ).SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder.RegisterType() - .AsSelf() - .SingleInstance(); - - - builder.RegisterType() - .AsSelf() - .SingleInstance(); - - builder.RegisterType() - .As() - .AsSelf() - .SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder.Register(c => new RabbitMqService(c.Resolve(), c.Resolve(), - null, _settings.CurrentValue.MarginTradingFront.Env)) - .As() - .SingleInstance(); - } - } -} diff --git a/src/MarginTrading.Frontend/Program.cs b/src/MarginTrading.Frontend/Program.cs deleted file mode 100644 index 238091c30..000000000 --- a/src/MarginTrading.Frontend/Program.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using MarginTrading.Common.Services; -using Microsoft.AspNetCore.Hosting; - -namespace MarginTrading.Frontend -{ - public class Program - { - public static void Main(string[] args) - { - var restartAttempsLeft = 5; - - while (restartAttempsLeft > 0) - { - try - { - var host = new WebHostBuilder() - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls("http://*:5005") - .UseStartup() - .UseApplicationInsights() - .Build(); - - host.Run(); - - restartAttempsLeft = 0; - } - catch (Exception e) - { - Console.WriteLine($"Error: {e.Message}{Environment.NewLine}{e.StackTrace}{Environment.NewLine}Restarting..."); - LogLocator.CommonLog?.WriteFatalErrorAsync( - "MT Frontend", "Restart host", $"Attempts left: {restartAttempsLeft}", e); - restartAttempsLeft--; - Thread.Sleep(10000); - } - } - } - } -} diff --git a/src/MarginTrading.Frontend/Properties/AssemblyInfo.cs b/src/MarginTrading.Frontend/Properties/AssemblyInfo.cs deleted file mode 100644 index f7008501a..000000000 --- a/src/MarginTrading.Frontend/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Reflection; -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: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Lykke")] -[assembly: AssemblyProduct("MarginTrading.Frontend")] -[assembly: AssemblyTrademark("")] - -// 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("23988b46-8a81-4324-b1f8-0fd2f5efdd24")] diff --git a/src/MarginTrading.Frontend/RabbitMqHandler.cs b/src/MarginTrading.Frontend/RabbitMqHandler.cs deleted file mode 100644 index 97ab8ebda..000000000 --- a/src/MarginTrading.Frontend/RabbitMqHandler.cs +++ /dev/null @@ -1,247 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Reactive.Subjects; -using System.Threading.Tasks; -using Common; -using Common.Log; -using MarginTrading.Common.Extensions; -using MarginTrading.Common.RabbitMq; -using MarginTrading.Common.Services; -using MarginTrading.Common.Services.Client; -using MarginTrading.Common.Services.Settings; -using MarginTrading.Common.Settings; -using MarginTrading.Contract.BackendContracts; -using MarginTrading.Contract.ClientContracts; -using MarginTrading.Contract.Mappers; -using MarginTrading.Contract.RabbitMqMessageModels; -using MarginTrading.Frontend.Settings; -using MarginTrading.Frontend.Wamp; -using WampSharp.V2.Realm; - -namespace MarginTrading.Frontend -{ - public class RabbitMqHandler - { - private readonly IWampHostedRealm _realm; - private readonly IClientAccountService _clientNotificationService; - private readonly IMarginTradingOperationsLogService _operationsLog; - private readonly MtFrontendSettings _settings; - private readonly IConsole _consoleWriter; - private readonly ILog _log; - private readonly IMarginTradingSettingsCacheService _marginTradingSettingsCacheService; - private readonly ISubject _allPairsSubject; - private readonly ISubject _tradesSubject; - - private readonly ConcurrentDictionary> _priceSubjects = - new ConcurrentDictionary>(); - - public RabbitMqHandler( - IWampHostedRealm realm, - IClientAccountService clientNotificationService, - IMarginTradingOperationsLogService operationsLogService, - MtFrontendSettings settings, - IConsole consoleWriter, - ILog log, - IMarginTradingSettingsCacheService marginTradingSettingsCacheService) - { - _realm = realm; - _clientNotificationService = clientNotificationService; - _operationsLog = operationsLogService; - _settings = settings; - _consoleWriter = consoleWriter; - _log = log; - _marginTradingSettingsCacheService = marginTradingSettingsCacheService; - _allPairsSubject = realm.Services.GetSubject(WampConstants.PricesTopicPrefix); - _tradesSubject = realm.Services.GetSubject(WampConstants.TradesTopic); - } - - public Task ProcessPrices(BidAskPairRabbitMqContract bidAskPair) - { - try - { - _allPairsSubject.OnNext(bidAskPair); - GetInstrumentPriceSubject(bidAskPair.Instrument).OnNext(bidAskPair); - } - catch (Exception e) - { - _log.WriteWarning("RabbitMqHandler", "ProcessPrices", bidAskPair.ToJson(), e); - } - - return Task.CompletedTask; - } - - public async Task ProcessTrades(TradeContract trade) - { - var contract = new TradeClientContract - { - Id = trade.Id, - AssetPairId = trade.AssetPairId, - Date = trade.Date, - OrderId = trade.OrderId, - Price = trade.Price, - Type = trade.Type.ToType(), - Volume = trade.Volume - }; - - _tradesSubject.OnNext(contract); - await Task.FromResult(0); - } - - public async Task ProcessAccountChanged(AccountChangedMessage accountChangedMessage) - { - if (accountChangedMessage.EventType != AccountEventTypeEnum.Updated) - return; - - var account = accountChangedMessage.Account; - var queueName = QueueHelper.BuildQueueName(_settings.MarginTradingFront.RabbitMqQueues.AccountChanged.ExchangeName, _settings.MarginTradingFront.Env); - _consoleWriter.WriteLine($"Get account change from {queueName} queue for clientId = {account.ClientId}"); - var notificationId = await _clientNotificationService.GetNotificationId(account.ClientId); - var userTopic = _realm.Services.GetSubject>($"user.{notificationId}"); - - var notifyResponse = new NotifyResponse - { - Entity = account.ToClientContract(), - Type = NotifyEntityType.Account - }; - - userTopic.OnNext(notifyResponse); - - _operationsLog.AddLog($"topic user.{notificationId} (account changed)", account.ClientId, account.Id, null, notifyResponse.ToJson()); - _consoleWriter.WriteLine($"topic user.{notificationId} (account changed) for clientId = {account.ClientId}"); - - var userUpdateTopic = _realm.Services.GetSubject($"user.updates.{notificationId}"); - var userUpdateTopicResponse = new NotifyResponse { Account = notifyResponse.Entity, Order = null }; - - userUpdateTopic.OnNext(userUpdateTopicResponse); - - _operationsLog.AddLog($"topic user.updates.{notificationId} (account changed)", account.ClientId, - account.Id, null, userUpdateTopicResponse.ToJson()); - _consoleWriter.WriteLine($"topic user.updates.{notificationId} (account changed) for clientId = {account.ClientId}"); - } - - public async Task ProcessOrderChanged(OrderContract order) - { - var queueName = QueueHelper.BuildQueueName(_settings.MarginTradingFront.RabbitMqQueues.OrderChanged.ExchangeName, _settings.MarginTradingFront.Env); - _consoleWriter.WriteLine($"Get order change from {queueName} queue for clientId = {order.ClientId}"); - - var notificationId = await _clientNotificationService.GetNotificationId(order.ClientId); - var userTopic = _realm.Services.GetSubject>($"user.{notificationId}"); - - var notifyResponse = new NotifyResponse - { - Entity = order.ToClientContract(), - Type = NotifyEntityType.Order - }; - - userTopic.OnNext(notifyResponse); - - _operationsLog.AddLog($"topic user.{notificationId} (position changed)", order.ClientId, order.AccountId, null, notifyResponse.ToJson()); - _consoleWriter.WriteLine($"topic user.{notificationId} (order changed) for clientId = {order.ClientId}"); - - var userUpdateTopic = _realm.Services.GetSubject($"user.updates.{notificationId}"); - var userUpdateTopicResponse = new NotifyResponse { Account = null, Order = notifyResponse.Entity }; - - userUpdateTopic.OnNext(userUpdateTopicResponse); - - _operationsLog.AddLog($"topic user.updates.{notificationId} (position changed)", order.ClientId, notifyResponse.Entity.AccountId, null, userUpdateTopicResponse.ToJson()); - _consoleWriter.WriteLine($"topic user.updates.{notificationId} (order changed) for clientId = {order.ClientId}"); - } - - public async Task ProcessAccountStopout(AccountStopoutBackendContract stopout) - { - var queueName = QueueHelper.BuildQueueName(_settings.MarginTradingFront.RabbitMqQueues.AccountStopout.ExchangeName, _settings.MarginTradingFront.Env); - _consoleWriter.WriteLine($"Get account stopout from {queueName} queue for clientId = {stopout.ClientId}"); - - var notificationId = await _clientNotificationService.GetNotificationId(stopout.ClientId); - var userTopic = _realm.Services.GetSubject>($"user.{notificationId}"); - - var response = new NotifyResponse - { - Entity = stopout.ToClientContract(), - Type = NotifyEntityType.AccountStopout - }; - - userTopic.OnNext(response); - - _operationsLog.AddLog($"topic user.{notificationId} (account stopout)", stopout.ClientId, response.Entity.AccountId, null, response.ToJson()); - _consoleWriter.WriteLine($"topic user.{notificationId} (account stopout) for clientId = {stopout.ClientId}"); - - var userUpdateTopic = _realm.Services.GetSubject($"user.updates.{notificationId}"); - var userUpdateTopicResponse = new NotifyResponse { Account = null, Order = null, AccountStopout = response.Entity }; - - userUpdateTopic.OnNext(userUpdateTopicResponse); - - _operationsLog.AddLog($"topic user.updates.{notificationId} (account stopout)", stopout.ClientId, response.Entity.AccountId, null, userUpdateTopicResponse.ToJson()); - _consoleWriter.WriteLine($"topic user.updates.{notificationId} (account stopout) for clientId = {stopout.ClientId}"); - } - - public async Task ProcessUserUpdates(UserUpdateEntityBackendContract userUpdate) - { - var queueName = QueueHelper.BuildQueueName(_settings.MarginTradingFront.RabbitMqQueues.UserUpdates.ExchangeName, _settings.MarginTradingFront.Env); - _consoleWriter.WriteLine($"Get user update from {queueName} queue for {userUpdate.ClientIds.Length} clients"); - - foreach (var clientId in userUpdate.ClientIds) - { - try - { - var notificationId = await _clientNotificationService.GetNotificationId(clientId); - var userTopic = - _realm.Services.GetSubject>( - $"user.{notificationId}"); - - var response = new NotifyResponse - { - Entity = userUpdate.ToClientContract(), - Type = NotifyEntityType.UserUpdate - }; - - userTopic.OnNext(response); - - var eventType = string.Empty; - - if (userUpdate.UpdateAccountAssetPairs) - { - eventType = "account assets"; - } - - if (userUpdate.UpdateAccounts) - { - eventType = "accounts"; - } - - _operationsLog.AddLog($"topic user.{notificationId} ({eventType} changed)", clientId, null, null, - response.ToJson()); - _consoleWriter.WriteLine( - $"topic user.{notificationId} ({eventType} changed) for clientId = {clientId}"); - - var userUpdateTopic = _realm.Services.GetSubject($"user.updates.{notificationId}"); - var userUpdateTopicResponse = new NotifyResponse {UserUpdate = response.Entity}; - - userUpdateTopic.OnNext(userUpdateTopicResponse); - - _operationsLog.AddLog($"topic user.updates.{notificationId} ({eventType} changed)", clientId, null, - null, userUpdateTopicResponse.ToJson()); - _consoleWriter.WriteLine( - $"topic user.updates.{notificationId} (account assets changed) for clientId = {clientId}"); - } - catch (Exception ex) - { - await _log.WriteErrorAsync(nameof(RabbitMqHandler), nameof(ProcessUserUpdates), clientId, ex); - } - } - } - - private ISubject GetInstrumentPriceSubject(string instrument) - { - return _priceSubjects.GetOrAdd(instrument, - i => _realm.Services.GetSubject( - $"{WampConstants.PricesTopicPrefix}.{instrument}")); - } - - public Task ProcessMarginTradingEnabledChanged(MarginTradingEnabledChangedMessage message) - { - _marginTradingSettingsCacheService.OnMarginTradingEnabledChanged(message); - return Task.CompletedTask; - } - } -} diff --git a/src/MarginTrading.Frontend/Repositories/Contract/IMaintenanceInfo.cs b/src/MarginTrading.Frontend/Repositories/Contract/IMaintenanceInfo.cs deleted file mode 100644 index 431c48b81..000000000 --- a/src/MarginTrading.Frontend/Repositories/Contract/IMaintenanceInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace MarginTrading.Frontend.Repositories.Contract -{ - public interface IMaintenanceInfo - { - bool IsEnabled { get; } - DateTime ChangedDate { get; } - string ChangedReason { get; } - string ChangedBy { get; } - } - - public class MaintenanceInfo : IMaintenanceInfo - { - public bool IsEnabled { get; set; } - public DateTime ChangedDate { get; set; } - public string ChangedReason { get; set; } - public string ChangedBy { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Repositories/Contract/IMaintenanceInfoRepository.cs b/src/MarginTrading.Frontend/Repositories/Contract/IMaintenanceInfoRepository.cs deleted file mode 100644 index 12d0d4f82..000000000 --- a/src/MarginTrading.Frontend/Repositories/Contract/IMaintenanceInfoRepository.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; - -namespace MarginTrading.Frontend.Repositories.Contract -{ - public interface IMaintenanceInfoRepository - { - Task GetMaintenanceInfo(bool isLive); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Repositories/Contract/IMarginTradingWatchList.cs b/src/MarginTrading.Frontend/Repositories/Contract/IMarginTradingWatchList.cs deleted file mode 100644 index d8682d8b3..000000000 --- a/src/MarginTrading.Frontend/Repositories/Contract/IMarginTradingWatchList.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections.Generic; - -namespace MarginTrading.Frontend.Repositories.Contract -{ - public interface IMarginTradingWatchList - { - string Id { get; } - string ClientId { get; } - string Name { get; } - bool ReadOnly { get; set; } - int Order { get; } - List AssetIds { get; } - } - - public class MarginTradingWatchList : IMarginTradingWatchList - { - public string Id { get; set; } - public string ClientId { get; set; } - public string Name { get; set; } - public bool ReadOnly { get; set; } - public int Order { get; set; } - public List AssetIds { get; set; } - - public static MarginTradingWatchList Create(IMarginTradingWatchList src) - { - return new MarginTradingWatchList - { - Id = src.Id, - ClientId = src.ClientId, - AssetIds = src.AssetIds, - Order = src.Order, - Name = src.Name, - ReadOnly = src.ReadOnly - }; - } - } -} diff --git a/src/MarginTrading.Frontend/Repositories/Contract/IMarginTradingWatchListRepository.cs b/src/MarginTrading.Frontend/Repositories/Contract/IMarginTradingWatchListRepository.cs deleted file mode 100644 index 58f1e675f..000000000 --- a/src/MarginTrading.Frontend/Repositories/Contract/IMarginTradingWatchListRepository.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace MarginTrading.Frontend.Repositories.Contract -{ - public interface IMarginTradingWatchListRepository - { - Task AddAsync(IMarginTradingWatchList watchList); - Task ChangeAllAsync(IEnumerable watchLists); - Task> GetAllAsync(string accountId); - Task GetAsync(string accountId, string id); - Task DeleteAsync(string accountId, string id); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Repositories/Entities/MaintenanceInfoEntity.cs b/src/MarginTrading.Frontend/Repositories/Entities/MaintenanceInfoEntity.cs deleted file mode 100644 index aa5023fd0..000000000 --- a/src/MarginTrading.Frontend/Repositories/Entities/MaintenanceInfoEntity.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using MarginTrading.Frontend.Repositories.Contract; -using Microsoft.WindowsAzure.Storage.Table; - -namespace MarginTrading.Frontend.Repositories.Entities -{ - public class MaintenanceInfoEntity : TableEntity, IMaintenanceInfo - { - public static string GetPartitionKey() - { - return "MaintenanceInfo"; - } - - public static string GetDemoRowKey() - { - return "Demo"; - } - - public static string GetLiveRowKey() - { - return "Live"; - } - - public bool IsEnabled { get; set; } - public DateTime ChangedDate { get; set; } - public string ChangedReason { get; set; } - public string ChangedBy { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Repositories/Entities/MarginTradingWatchListEntity.cs b/src/MarginTrading.Frontend/Repositories/Entities/MarginTradingWatchListEntity.cs deleted file mode 100644 index 482ae6b5e..000000000 --- a/src/MarginTrading.Frontend/Repositories/Entities/MarginTradingWatchListEntity.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MarginTrading.Frontend.Repositories.Contract; -using Microsoft.WindowsAzure.Storage.Table; - -namespace MarginTrading.Frontend.Repositories.Entities -{ - public class MarginTradingWatchListEntity : TableEntity, IMarginTradingWatchList - { - public string Id { get; set; } - public string ClientId { get; set; } - public string Name { get; set; } - public bool ReadOnly { get; set; } - public int Order { get; set; } - public string AssetIds { get; set; } - - List IMarginTradingWatchList.AssetIds - => AssetIds.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).ToList(); - - public static string GeneratePartitionKey(string accountId) - { - return accountId; - } - - public static string GenerateRowKey(string id) - { - return id; - } - - public static MarginTradingWatchListEntity Create(IMarginTradingWatchList src) - { - return new MarginTradingWatchListEntity - { - PartitionKey = GeneratePartitionKey(src.ClientId), - RowKey = GenerateRowKey(src.Id), - Id = src.Id, - ClientId = src.ClientId, - Name = src.Name, - ReadOnly = src.ReadOnly, - Order = src.Order, - AssetIds = string.Join(",", src.AssetIds) - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Repositories/Entities/OperationLogEntity.cs b/src/MarginTrading.Frontend/Repositories/Entities/OperationLogEntity.cs deleted file mode 100644 index e21bf6cd5..000000000 --- a/src/MarginTrading.Frontend/Repositories/Entities/OperationLogEntity.cs +++ /dev/null @@ -1,32 +0,0 @@ -using MarginTrading.Common.Services; -using Microsoft.WindowsAzure.Storage.Table; - -namespace MarginTrading.Frontend.Repositories.Entities -{ - public class OperationLogEntity : TableEntity, IOperationLog - { - public string Name { get; set; } - public string ClientId { get; set; } - public string AccountId { get; set; } - public string Input { get; set; } - public string Data { get; set; } - - public static string GeneratePartitionKey(string clientId, string name) - { - return clientId ?? name; - } - - public static OperationLogEntity Create(IOperationLog src) - { - return new OperationLogEntity - { - PartitionKey = GeneratePartitionKey(src.ClientId, src.Name), - Name = src.Name, - Input = src.Input, - Data = src.Data, - AccountId = src.AccountId, - ClientId = src.ClientId - }; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Repositories/MaintenanceInfoRepository.cs b/src/MarginTrading.Frontend/Repositories/MaintenanceInfoRepository.cs deleted file mode 100644 index e03b7b237..000000000 --- a/src/MarginTrading.Frontend/Repositories/MaintenanceInfoRepository.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Threading.Tasks; -using AzureStorage; -using AzureStorage.Tables; -using Lykke.SettingsReader; -using MarginTrading.Common.Services; -using MarginTrading.Frontend.Repositories.Contract; -using MarginTrading.Frontend.Repositories.Entities; - -namespace MarginTrading.Frontend.Repositories -{ - public class MaintenanceInfoRepository : IMaintenanceInfoRepository - { - private readonly INoSQLTableStorage _tableStorage; - - public MaintenanceInfoRepository(IReloadingManager connectionStringManager) - { - _tableStorage = AzureTableStorage.Create(connectionStringManager, - "MaintenanceInfo", LogLocator.CommonLog); - } - - public async Task GetMaintenanceInfo(bool isLive) - { - var rk = isLive ? MaintenanceInfoEntity.GetLiveRowKey() : MaintenanceInfoEntity.GetDemoRowKey(); - - return (IMaintenanceInfo) await _tableStorage.GetDataAsync(MaintenanceInfoEntity.GetPartitionKey(), rk) ?? - new MaintenanceInfo(); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Repositories/MarginTradingOperationsLogRepository.cs b/src/MarginTrading.Frontend/Repositories/MarginTradingOperationsLogRepository.cs deleted file mode 100644 index 1b31e3b5f..000000000 --- a/src/MarginTrading.Frontend/Repositories/MarginTradingOperationsLogRepository.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Threading.Tasks; -using AzureStorage; -using MarginTrading.Common.Services; -using MarginTrading.Frontend.Repositories.Entities; - -namespace MarginTrading.Frontend.Repositories -{ - public class MarginTradingOperationsLogRepository : IMarginTradingOperationsLogRepository - { - private readonly INoSQLTableStorage _tableStorage; - - public MarginTradingOperationsLogRepository(INoSQLTableStorage tableStorage) - { - _tableStorage = tableStorage; - } - - public async Task AddLogAsync(IOperationLog logEntity) - { - var entity = OperationLogEntity.Create(logEntity); - await _tableStorage.InsertAndGenerateRowKeyAsTimeAsync(entity, DateTime.UtcNow); - } - } -} diff --git a/src/MarginTrading.Frontend/Repositories/MarginTradingWatchListsRepository.cs b/src/MarginTrading.Frontend/Repositories/MarginTradingWatchListsRepository.cs deleted file mode 100644 index de14bf41c..000000000 --- a/src/MarginTrading.Frontend/Repositories/MarginTradingWatchListsRepository.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using AzureStorage; -using MarginTrading.Frontend.Repositories.Contract; -using MarginTrading.Frontend.Repositories.Entities; - -namespace MarginTrading.Frontend.Repositories -{ - public class MarginTradingWatchListsRepository : IMarginTradingWatchListRepository - { - private readonly INoSQLTableStorage _tableStorage; - - public MarginTradingWatchListsRepository(INoSQLTableStorage tableStorage) - { - _tableStorage = tableStorage; - } - - public async Task AddAsync(IMarginTradingWatchList watchList) - { - await _tableStorage.InsertOrReplaceAsync(MarginTradingWatchListEntity.Create(watchList)); - var entity = - await _tableStorage.GetDataAsync(MarginTradingWatchListEntity.GeneratePartitionKey(watchList.ClientId), - MarginTradingWatchListEntity.GenerateRowKey(watchList.Id)); - - return MarginTradingWatchList.Create(entity); - } - - public async Task> GetAllAsync(string accountId) - { - var entities = await _tableStorage.GetDataAsync(MarginTradingWatchListEntity.GeneratePartitionKey(accountId)); - - return entities.Select(MarginTradingWatchList.Create).OrderBy(item => item.Order); - } - - public async Task DeleteAsync(string accountId, string id) - { - await _tableStorage.DeleteAsync(MarginTradingWatchListEntity.GeneratePartitionKey(accountId), - MarginTradingWatchListEntity.GenerateRowKey(id)); - } - - public async Task GetAsync(string accountId, string id) - { - var entity = await _tableStorage.GetDataAsync(MarginTradingWatchListEntity.GeneratePartitionKey(accountId), - MarginTradingWatchListEntity.GenerateRowKey(id)); - - return entity == null - ? null - : MarginTradingWatchListEntity.Create(entity); - } - - public async Task ChangeAllAsync(IEnumerable watchLists) - { - await _tableStorage.InsertOrReplaceAsync(watchLists.Select(MarginTradingWatchListEntity.Create)); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/RpcMtFrontend.cs b/src/MarginTrading.Frontend/RpcMtFrontend.cs deleted file mode 100644 index 7fb66f78e..000000000 --- a/src/MarginTrading.Frontend/RpcMtFrontend.cs +++ /dev/null @@ -1,214 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.Common.Services; -using MarginTrading.Contract.ClientContracts; -using MarginTrading.Frontend.Services; -using MarginTrading.Frontend.Settings; -using MarginTrading.Frontend.Wamp; -using Microsoft.Extensions.PlatformAbstractions; -using Newtonsoft.Json; - -namespace MarginTrading.Frontend -{ - public class RpcMtFrontend : IRpcMtFrontend - { - private readonly MtFrontendSettings _settings; - private readonly IClientTokenService _clientTokenService; - private readonly RpcFacade _rpcFacade; - private readonly IDateService _dateService; - - public RpcMtFrontend( - MtFrontendSettings settings, - IClientTokenService clientTokenService, - RpcFacade rpcFacade, - IDateService dateService) - { - _settings = settings; - _clientTokenService = clientTokenService; - _rpcFacade = rpcFacade; - _dateService = dateService; - } - - #region Service - - public IsAliveResponse IsAlive() - { - return new IsAliveResponse - { - Version = PlatformServices.Default.Application.ApplicationVersion, - Env = _settings.MarginTradingFront.Env, - ServerTime = _dateService.Now() - }; - } - - #endregion - - - #region Init data - - public async Task InitData(string token) - { - var clientId = await GetClientId(token); - - return await _rpcFacade.InitData(clientId); - } - - public async Task InitAccounts(string token) - { - var clientId = await GetClientId(token); - - return await _rpcFacade.InitAccounts(clientId); - } - - public async Task AccountInstruments(string token) - { - var clientId = await GetClientId(token); - - return await _rpcFacade.AccountInstruments(clientId); - } - - public async Task InitGraph(string token = null, string[] assetIds = null) - { - var clientId = string.IsNullOrEmpty(token) ? null : await GetClientId(token); - - return await _rpcFacade.InitGraph(clientId, assetIds); - } - - #endregion - - - #region Account - - public async Task GetAccountHistory(string requestJson) - { - var accountHistoryClientRequest = DeserializeRequest(requestJson); - var clientId = await GetClientId(accountHistoryClientRequest.Token); - - return await _rpcFacade.GetAccountHistory(clientId, accountHistoryClientRequest); - } - - public async Task GetHistory(string requestJson) - { - var accountHistoryClientRequest = DeserializeRequest(requestJson); - var clientId = await GetClientId(accountHistoryClientRequest.Token); - - return await _rpcFacade.GetAccountHistoryTimeline(clientId, accountHistoryClientRequest); - } - - #endregion - - - #region Order - - public async Task> PlaceOrder(string requestJson) - { - var clientRequest = DeserializeRequest(requestJson); - var clientId = await GetClientId(clientRequest.Token); - - return await _rpcFacade.PlaceOrder(clientId, clientRequest.Order); - } - - public async Task> CloseOrder(string requestJson) - { - var clientRequest = DeserializeRequest(requestJson); - var clientId = await GetClientId(clientRequest.Token); - - return await _rpcFacade.CloseOrder(clientId, clientRequest); - } - - public async Task> CancelOrder(string requestJson) - { - var clientRequest = DeserializeRequest(requestJson); - var clientId = await GetClientId(clientRequest.Token); - - return await _rpcFacade.CancelOrder(clientId, clientRequest); - } - - public async Task GetOpenPositions(string token) - { - var clientId = await GetClientId(token); - - return await _rpcFacade.GetOpenPositions(clientId); - } - - public async Task GetAccountOpenPositions(string requestJson) - { - var clientRequest = DeserializeRequest(requestJson); - var clientId = await GetClientId(clientRequest.Token); - - return await _rpcFacade.GetAccountOpenPositions(clientId, clientRequest.AccountId); - } - - public async Task GetClientOrders(string token) - { - var clientId = await GetClientId(token); - - return await _rpcFacade.GetClientOrders(clientId); - } - - public async Task> ChangeOrderLimits(string requestJson) - { - var clientRequest = DeserializeRequest(requestJson); - var clientId = await GetClientId(clientRequest.Token); - - return await _rpcFacade.ChangeOrderLimits(clientId, clientRequest); - } - - #endregion - - - #region Orderbook - - public Task GetOrderBook(string instrument) - { - return _rpcFacade.GetOrderBook(instrument); - } - - #endregion - - - #region Private methods - - private TRequestContract DeserializeRequest(string requestJson) - { - if (string.IsNullOrWhiteSpace(requestJson)) - throw new ArgumentNullException(nameof(requestJson)); - - var result = JsonConvert.DeserializeObject(requestJson); - - var validationContext = new ValidationContext(result); - var validationResults = new List(); - if (!Validator.TryValidateObject(result, validationContext, validationResults, true)) - { - var errorMessage = - validationResults.Where(x => !string.IsNullOrWhiteSpace(x.ErrorMessage)) - .Select(x => x.ErrorMessage) - .Aggregate((x, y) => x + "; " + y); - errorMessage = string.IsNullOrWhiteSpace(errorMessage) - ? $"Request {requestJson} contains validation errors" : - $"Request {requestJson} contains validation errors: {errorMessage}"; - throw new ValidationException(errorMessage); - } - - return result; - } - - private async Task GetClientId(string token) - { - if (string.IsNullOrEmpty(token)) - throw new Exception("Token is null or empty"); - - var clientId = await _clientTokenService.GetClientId(token); - - if (string.IsNullOrWhiteSpace(clientId)) - throw new KeyNotFoundException($"Can't find session by provided token '{token}'"); - - return clientId; - } - - #endregion - } -} diff --git a/src/MarginTrading.Frontend/Services/ClientTokenService.cs b/src/MarginTrading.Frontend/Services/ClientTokenService.cs deleted file mode 100644 index bb88225cc..000000000 --- a/src/MarginTrading.Frontend/Services/ClientTokenService.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Threading.Tasks; -using Lykke.Service.Session; - -namespace MarginTrading.Frontend.Services -{ - public class ClientTokenService : IClientTokenService - { - private readonly IClientsSessionsRepository _sessionService; - - public ClientTokenService(IClientsSessionsRepository sessionService) - { - _sessionService = sessionService; - } - - public async Task GetClientId(string token) - { - try - { - var sessionModel = await _sessionService.GetAsync(token); - return sessionModel?.ClientId; - } - catch - { - return null; - } - } - } -} diff --git a/src/MarginTrading.Frontend/Services/ClientTokenValidator.cs b/src/MarginTrading.Frontend/Services/ClientTokenValidator.cs deleted file mode 100644 index 22896a7ce..000000000 --- a/src/MarginTrading.Frontend/Services/ClientTokenValidator.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections.Generic; -using System.Security.Claims; -using Microsoft.IdentityModel.Tokens; - -namespace MarginTrading.Frontend.Services -{ - public class ClientTokenValidator : ISecurityTokenValidator - { - private readonly IClientTokenService _clientTokenService; - - public ClientTokenValidator(IClientTokenService clientTokenService) - { - _clientTokenService = clientTokenService; - } - - public bool CanReadToken(string securityToken) => true; - - public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken) - { - validatedToken = null; - - var ls = new List(); - var clientId = _clientTokenService.GetClientId(securityToken).Result; - ls.Add(new Claim(ClaimTypes.NameIdentifier, clientId, ClaimValueTypes.String)); - var id = new ClaimsIdentity(ls, "magic"); - var principal = new ClaimsPrincipal(id); - return principal; - } - - public bool CanValidateToken => true; - public int MaximumTokenSizeInBytes { get; set; } - } -} diff --git a/src/MarginTrading.Frontend/Services/HttpRequestService.cs b/src/MarginTrading.Frontend/Services/HttpRequestService.cs deleted file mode 100644 index 8b0900e91..000000000 --- a/src/MarginTrading.Frontend/Services/HttpRequestService.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System; -using System.Threading.Tasks; -using Common; -using Flurl.Http; -using MarginTrading.Common.Extensions; -using MarginTrading.Frontend.Settings; -using System.Linq; -using System.Net; -using MarginTrading.Common.Services.Settings; -using MarginTrading.Contract.BackendContracts; -using MarginTrading.Frontend.Repositories.Contract; -using Rocks.Caching; - -namespace MarginTrading.Frontend.Services -{ - public class HttpRequestService : IHttpRequestService - { - private readonly MtFrontendSettings _settings; - private readonly ICacheProvider _cacheProvider; - private readonly IMaintenanceInfoRepository _maintenanceInfoRepository; - private readonly ITerminalInfoService _terminalInfoService; - - public HttpRequestService(MtFrontendSettings settings, - ICacheProvider cacheProvider, - IMaintenanceInfoRepository maintenanceInfoRepository, - ITerminalInfoService terminalInfoService) - { - _settings = settings; - _cacheProvider = cacheProvider; - _maintenanceInfoRepository = maintenanceInfoRepository; - _terminalInfoService = terminalInfoService; - } - - public async Task<(TResponse Demo, TResponse Live)> RequestIfAvailableAsync(object request, string action, Func defaultResult, EnabledMarginTradingTypes enabledMarginTradingTypes, string controller = "mt") - where TResponse : class - { - async Task Request(bool isLive, bool isTradingEnabled) - { - var maintenanceInfo = await GetMaintenance(isLive); - var terminalInfo = _terminalInfoService.Get(); - var enabledForTerminal = isLive ? terminalInfo.LiveEnabled : terminalInfo.DemoEnabled; - - if (!isTradingEnabled || maintenanceInfo.IsEnabled || !enabledForTerminal) - { - return defaultResult(); - } - - return await RequestWithRetriesAsync(request, action, isLive, controller) - .ContinueWith(t => t.IsFaulted ? defaultResult() : t.Result); - } - - return (Demo: await Request(false, enabledMarginTradingTypes.Demo), - Live: await Request(true, enabledMarginTradingTypes.Live)); - } - - public async Task RequestWithRetriesAsync(object request, string action, bool isLive = true, string controller = "mt") - { - await CheckMaintenance(isLive); - - try - { - var flurlClient = $"{(isLive ? _settings.MarginTradingLive.ApiRootUrl : _settings.MarginTradingDemo.ApiRootUrl)}/api/{controller}/{action}" - .WithHeader("api-key", isLive ? _settings.MarginTradingLive.ApiKey : _settings.MarginTradingDemo.ApiKey); - - return await ActionExtensions.RetryOnExceptionAsync( - () => flurlClient.PostJsonAsync(request).ReceiveJson(), - ex => ex is FlurlHttpException && !new int?[] {400, 500}.Contains((int?) ((FlurlHttpException) ex).Call.HttpStatus), - 6, - TimeSpan.FromSeconds(5)); - } - catch (Exception ex) - { - throw new Exception(GetErrorMessage(isLive, action, request.ToJson(), ex)); - } - } - - public async Task GetAsync(string path, bool isLive = true, int timeout = 30) - { - await CheckMaintenance(isLive); - - try - { - return await $"{(isLive ? _settings.MarginTradingLive.ApiRootUrl : _settings.MarginTradingDemo.ApiRootUrl)}/api/{path}" - .WithHeader("api-key", isLive ? _settings.MarginTradingLive.ApiKey : _settings.MarginTradingDemo.ApiKey) - .WithTimeout(timeout) - .GetJsonAsync(); - } - catch (Exception ex) - { - throw new Exception(GetErrorMessage(isLive, path, "GET", ex)); - } - } - - - #region Helpers - - private string GetErrorMessage(bool isLive, string path, string context, Exception ex) - { - path = $"{(isLive ? "Live: " : "Demo: ")}{path}"; - - var error = ex.Message; - - if (ex is FlurlHttpException flurException) - { - var responseBody = flurException.Call.ErrorResponseBody; - - if (!string.IsNullOrEmpty(responseBody)) - { - var response = responseBody.DeserializeJson>(); - if (!string.IsNullOrEmpty(response?.Message)) - { - error += " " + response.Message; - } - } - - if (flurException.Call.HttpStatus == HttpStatusCode.ServiceUnavailable) - { - ClearMaintenanceCache(isLive); - } - } - - return $"Backend {path} request failed. Error: {error}. Payload: {context}."; - } - - private async Task GetMaintenance(bool isLive) - { - var cacheKey = GetMaintenanceModeCacheKey(isLive); - - return await _cacheProvider.GetAsync(cacheKey, - async () => new CachableResult( - await _maintenanceInfoRepository.GetMaintenanceInfo(isLive), - CachingParameters.FromSeconds(15))); - } - - private async Task CheckMaintenance(bool isLive) - { - var maintenanceInfo = await GetMaintenance(isLive); - - if (maintenanceInfo?.IsEnabled == true) - throw new MaintenanceException(maintenanceInfo.ChangedDate); - } - - private string GetMaintenanceModeCacheKey(bool isLive) - { - return CacheKeyBuilder.Create(nameof(HttpRequestService), nameof(CheckMaintenance), isLive); - } - - private void ClearMaintenanceCache(bool isLive) - { - _cacheProvider.Remove(GetMaintenanceModeCacheKey(isLive)); - } - - #endregion - - } -} diff --git a/src/MarginTrading.Frontend/Services/IClientTokenService.cs b/src/MarginTrading.Frontend/Services/IClientTokenService.cs deleted file mode 100644 index 2eb5adb4d..000000000 --- a/src/MarginTrading.Frontend/Services/IClientTokenService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; - -namespace MarginTrading.Frontend.Services -{ - public interface IClientTokenService - { - Task GetClientId(string token); - } -} diff --git a/src/MarginTrading.Frontend/Services/IHttpRequestService.cs b/src/MarginTrading.Frontend/Services/IHttpRequestService.cs deleted file mode 100644 index 8f2a117e2..000000000 --- a/src/MarginTrading.Frontend/Services/IHttpRequestService.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Threading.Tasks; -using MarginTrading.Common.Services.Settings; - -namespace MarginTrading.Frontend.Services -{ - public interface IHttpRequestService - { - Task RequestWithRetriesAsync(object request, string action, bool isLive = true, string controller = "mt"); - - Task GetAsync(string path, bool isLive = true, int timeout = 30); - - /// - /// Makes a post requests for available backends for client (live/demo) and gets results. - /// If a backend is not available for client or request fails - is returned instead. - /// - Task<(TResponse Demo, TResponse Live)> RequestIfAvailableAsync(object request, string action, Func defaultResult, EnabledMarginTradingTypes enabledMarginTradingTypes, string controller = "mt") - where TResponse : class; - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Services/ITerminalInfoService.cs b/src/MarginTrading.Frontend/Services/ITerminalInfoService.cs deleted file mode 100644 index d7b5743c5..000000000 --- a/src/MarginTrading.Frontend/Services/ITerminalInfoService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using MarginTrading.Frontend.Models; - -namespace MarginTrading.Frontend.Services -{ - public interface ITerminalInfoService - { - TerminalInfo Get(); - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Services/IWatchListService.cs b/src/MarginTrading.Frontend/Services/IWatchListService.cs deleted file mode 100644 index 86d81da1f..000000000 --- a/src/MarginTrading.Frontend/Services/IWatchListService.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using MarginTrading.Frontend.Repositories; -using MarginTrading.Frontend.Repositories.Contract; - -namespace MarginTrading.Frontend.Services -{ - public interface IWatchListService - { - Task> GetAllAsync(string clientId); - Task GetAsync(string clientId, string id); - Task> AddAsync(string id, string clientId, string name, List assetIds); - - Task> DeleteAsync(string clientId, string id); - } - - public class WatchListResult - { - public T Result { get; set; } - public WatchListStatus Status { get; set; } - public string Message { get; set; } - } - - public enum WatchListStatus - { - Ok, - NotFound, - AssetNotFound, - ReadOnly, - AlreadyDefault - } -} diff --git a/src/MarginTrading.Frontend/Services/MaintenanceException.cs b/src/MarginTrading.Frontend/Services/MaintenanceException.cs deleted file mode 100644 index c8907152c..000000000 --- a/src/MarginTrading.Frontend/Services/MaintenanceException.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace MarginTrading.Frontend.Services -{ - public class MaintenanceException : Exception - { - public DateTime EnabledAt { get; } - - public MaintenanceException(DateTime enabledAt) - { - EnabledAt = enabledAt; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Services/RpcFacade.cs b/src/MarginTrading.Frontend/Services/RpcFacade.cs deleted file mode 100644 index 039f0d05e..000000000 --- a/src/MarginTrading.Frontend/Services/RpcFacade.cs +++ /dev/null @@ -1,355 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Common; -using MarginTrading.Backend.Contracts.AccountHistory; -using MarginTrading.Backend.Contracts.DataReaderClient; -using MarginTrading.Backend.Contracts.Trading; -using MarginTrading.Common.Services.Settings; -using MarginTrading.Contract.BackendContracts; -using MarginTrading.Contract.ClientContracts; -using MarginTrading.Contract.Mappers; -using MarginTrading.Frontend.Settings; -using AccountHistoryTypeContract = MarginTrading.Contract.BackendContracts.AccountHistoryTypeContract; - -namespace MarginTrading.Frontend.Services -{ - public class RpcFacade - { - private readonly MtFrontendSettings _settings; - private readonly IHttpRequestService _httpRequestService; - private readonly IMarginTradingSettingsCacheService _marginTradingSettingsCacheService; - private readonly IMtDataReaderClientsPair _dataReaderClients; - - public RpcFacade( - MtFrontendSettings settings, - IHttpRequestService httpRequestService, - IMarginTradingSettingsCacheService marginTradingSettingsCacheService, - IMtDataReaderClientsPair dataReaderClients) - { - _settings = settings; - _httpRequestService = httpRequestService; - _marginTradingSettingsCacheService = marginTradingSettingsCacheService; - _dataReaderClients = dataReaderClients; - } - - #region Init data - - public async Task InitData(string clientId) - { - var marginTradingEnabled = await _marginTradingSettingsCacheService.IsMarginTradingEnabled(clientId); - if (!marginTradingEnabled.Demo && !marginTradingEnabled.Live) - { - throw new Exception("Margin trading is not available"); - } - - var initData = new InitDataLiveDemoClientResponse(); - var clientIdRequest = new ClientIdBackendRequest {ClientId = clientId}; - - var initDataResponses = await _httpRequestService.RequestIfAvailableAsync(clientIdRequest, "init.data", () => null, marginTradingEnabled); - initData.Live = initDataResponses.Live?.ToClientContract(); - initData.Demo = initDataResponses.Demo?.ToClientContract(); - - var initAssetsResponses = await _httpRequestService.RequestIfAvailableAsync(clientIdRequest, "init.assets", Array.Empty, marginTradingEnabled); - initData.Assets = initAssetsResponses.Live.Concat(initAssetsResponses.Demo).GroupBy(a => a.Id) - .Select(g => g.First().ToClientContract()).ToArray(); - - var initPricesResponse = - await _httpRequestService.RequestWithRetriesAsync>( - clientIdRequest, "init.prices"); - initData.Prices = initPricesResponse.ToDictionary(p => p.Key, p => p.Value.ToClientContract()); - - return initData; - } - - public async Task InitAccounts(string clientId) - { - var marginTradingEnabled = await _marginTradingSettingsCacheService.IsMarginTradingEnabled(clientId); - var responses = await _httpRequestService.RequestIfAvailableAsync(new ClientIdBackendRequest { ClientId = clientId }, - "init.accounts", - Array.Empty, - marginTradingEnabled); - return new InitAccountsLiveDemoClientResponse - { - Live = responses.Live.Select(item => item.ToClientContract()).ToArray(), - Demo = responses.Demo.Select(item => item.ToClientContract()).ToArray(), - }; - } - - public async Task AccountInstruments(string clientId) - { - var marginTradingEnabled = await _marginTradingSettingsCacheService.IsMarginTradingEnabled(clientId); - var responses = await _httpRequestService.RequestIfAvailableAsync(new ClientIdBackendRequest { ClientId = clientId }, - "init.accountinstruments", - InitAccountInstrumentsBackendResponse.CreateEmpty, - marginTradingEnabled); - - return new InitAccountInstrumentsLiveDemoClientResponse - { - Live = responses.Live.ToClientContract(), - Demo = responses.Demo.ToClientContract() - }; - } - - public async Task InitGraph(string clientId = null, string[] assetIds = null) - { - var request = new InitChartDataBackendRequest {ClientId = clientId, AssetIds = assetIds}; - - var initChartDataLiveResponse = await _httpRequestService.RequestWithRetriesAsync(request, "init.graph"); - - return initChartDataLiveResponse.ToClientContract(); - } - - public async Task> InitPrices(string clientId = null, string[] assetIds = null) - { - var request = new InitPricesBackendRequest {ClientId = clientId, AssetIds = assetIds}; - - var initPricesResponse = await _httpRequestService - .RequestWithRetriesAsync>(request, "init.prices"); - - return initPricesResponse.ToDictionary(p => p.Key, p => p.Value.ToClientContract()); - } - - #endregion - - - #region Account - - public async Task GetAccountHistory(string clientId, AccountHistoryFiltersClientRequest request) - { - var isLive = !string.IsNullOrEmpty(request.AccountId) - ? IsLiveAccount(request.AccountId) - : request.IsLive; - - var marginTradingEnabled = await _marginTradingSettingsCacheService.IsMarginTradingEnabled(clientId, isLive); - if (!marginTradingEnabled) - { - return new AccountHistoryClientResponse - { - Account = Array.Empty(), - OpenPositions = Array.Empty(), - PositionsHistory = Array.Empty(), - }; - } - - var accountHistoryBackendResponse = await _dataReaderClients.Get(isLive).AccountHistory.ByTypes( - new AccountHistoryRequest - { - AccountId = request.AccountId, - ClientId = clientId, - From = request.From, - To = request.To - }); - return ToClientContract(accountHistoryBackendResponse); - } - - public async Task GetAccountHistoryTimeline(string clientId, AccountHistoryFiltersClientRequest request) - { - var isLive = !string.IsNullOrEmpty(request.AccountId) - ? IsLiveAccount(request.AccountId) - : request.IsLive; - - var marginTradingEnabled = await _marginTradingSettingsCacheService.IsMarginTradingEnabled(clientId, isLive); - if (!marginTradingEnabled) - { - return Array.Empty(); - } - var accountHistoryBackendResponse = await _dataReaderClients.Get(isLive).AccountHistory.Timeline(new AccountHistoryRequest - { - AccountId = request.AccountId, - ClientId = clientId, - From = request.From, - To = request.To - }); - return ToClientContract(accountHistoryBackendResponse); - } - - #endregion - - - #region Order - - public async Task> PlaceOrder(string clientId, NewOrderClientContract request) - { - var backendRequest = request.ToBackendContract(clientId); - var backendResponse = await _httpRequestService.RequestWithRetriesAsync(backendRequest, "order.place", - IsLiveAccount(backendRequest.Order.AccountId)); - return backendResponse.ToClientContract(); - } - - public async Task> CloseOrder(string clientId, CloseOrderClientRequest request) - { - var backendRequest = new CloseOrderBackendRequest - { - ClientId = clientId, - OrderId = request.OrderId, - AccountId = request.AccountId - }; - - var backendResponse = await _httpRequestService.RequestWithRetriesAsync>(backendRequest, "order.close", - IsLiveAccount(backendRequest.AccountId)); - return backendResponse.ToClientContract(); - } - - public async Task> CancelOrder(string clientId, CloseOrderClientRequest request) - { - var backendRequest = new CloseOrderBackendRequest - { - ClientId = clientId, - OrderId = request.OrderId, - AccountId = request.AccountId - }; - - var backendResponse = await _httpRequestService.RequestWithRetriesAsync>(backendRequest, "order.cancel", - IsLiveAccount(backendRequest.AccountId)); - return backendResponse.ToClientContract(); - } - - public async Task GetOpenPositions(string clientId) - { - var marginTradingEnabled = await _marginTradingSettingsCacheService.IsMarginTradingEnabled(clientId); - var responses = await _httpRequestService.RequestIfAvailableAsync(new ClientIdBackendRequest { ClientId = clientId }, - "order.list", - Array.Empty, - marginTradingEnabled); - - return new ClientOrdersLiveDemoClientResponse - { - Live = responses.Live.Select(item => item.ToClientContract()).ToArray(), - Demo = responses.Demo.Select(item => item.ToClientContract()).ToArray() - }; - } - - public async Task GetAccountOpenPositions(string clientId, string accountId) - { - var backendRequest = new AccountClientIdBackendRequest {AccountId = accountId, ClientId = clientId}; - var backendResponse = await _httpRequestService.RequestWithRetriesAsync(backendRequest, - "order.account.list", IsLiveAccount(backendRequest.AccountId)); - - return backendResponse.Select(item => item.ToClientContract()).ToArray(); - } - - public async Task GetClientOrders(string clientId) - { - var marginTradingEnabled = await _marginTradingSettingsCacheService.IsMarginTradingEnabled(clientId); - var responses = await _httpRequestService.RequestIfAvailableAsync(new ClientIdBackendRequest { ClientId = clientId }, - "order.positions", - () => new ClientOrdersBackendResponse - { - Orders = Array.Empty(), - Positions = Array.Empty() - }, - marginTradingEnabled); - - return new ClientPositionsLiveDemoClientResponse - { - Live = responses.Live.ToClientContract(), - Demo = responses.Demo.ToClientContract() - }; - } - - public async Task> ChangeOrderLimits(string clientId, - ChangeOrderLimitsClientRequest request) - { - var backendRequest = request.ToBackendContract(clientId); - var backendResponse = await _httpRequestService.RequestWithRetriesAsync>(backendRequest, - "order.changeLimits", - IsLiveAccount(backendRequest.AccountId)); - return backendResponse.ToClientContract(); - } - - #endregion - - - #region Orderbook - - public async Task GetOrderBook(string instrument) - { - var backendResponse = - await _httpRequestService.RequestWithRetriesAsync( - new OrderbooksBackendRequest {Instrument = instrument}, "orderbooks"); - return backendResponse.Orderbook.ToClientContract(); - } - - #endregion - - - #region Private methods - - private bool IsLiveAccount(string accountId) - { - return !accountId.StartsWith(_settings.MarginTradingFront.DemoAccountIdPrefix); - } - - private static AccountHistoryClientResponse ToClientContract(AccountHistoryResponse src) - { - return new AccountHistoryClientResponse - { - Account = src.Account.Select(ToClientContract).OrderByDescending(item => item.Date).ToArray(), - OpenPositions = src.OpenPositions.Select(ToClientContract).ToArray(), - PositionsHistory = src.PositionsHistory.Select(ToClientContract).ToArray() - }; - } - - private static AccountHistoryClientContract ToClientContract(AccountHistoryContract src) - { - return new AccountHistoryClientContract - { - Id = src.Id, - Date = src.Date, - AccountId = src.AccountId, - ClientId = src.ClientId, - Amount = src.Amount, - Balance = src.Balance, - WithdrawTransferLimit = src.WithdrawTransferLimit, - Comment = src.Comment, - Type = ConvertEnum(src.Type), - LegalEnity = src.LegalEntity, - }; - } - - private static OrderHistoryClientContract ToClientContract(OrderHistoryContract src) - { - return new OrderHistoryClientContract - { - Id = src.Id, - AccountId = src.AccountId, - Instrument = src.Instrument, - AssetAccuracy = src.AssetAccuracy, - Type = ConvertEnum(src.Type), - Status = ConvertEnum(src.Status), - CloseReason = ConvertEnum(src.CloseReason), - OpenDate = src.OpenDate, - CloseDate = src.CloseDate, - OpenPrice = src.OpenPrice, - ClosePrice = src.ClosePrice, - Volume = src.Volume, - TakeProfit = src.TakeProfit, - StopLoss = src.StopLoss, - TotalPnL = src.TotalPnl, - PnL = src.Pnl, - InterestRateSwap = src.InterestRateSwap, - OpenCommission = src.OpenCommission, - CloseCommission = src.CloseCommission - }; - } - - public static AccountHistoryItemClient[] ToClientContract(AccountNewHistoryResponse src) - { - return src.HistoryItems.Select(i => new AccountHistoryItemClient - { - Date = i.Date, - Account = i.Account == null ? null : ToClientContract(i.Account), - Position = i.Position == null ? null : ToClientContract(i.Position) - }).ToArray(); - } - - private static TResult ConvertEnum(Enum e) - { - return e.ToString().ParseEnum(); - } - - #endregion - } -} diff --git a/src/MarginTrading.Frontend/Services/TerminalInfoService.cs b/src/MarginTrading.Frontend/Services/TerminalInfoService.cs deleted file mode 100644 index 5f854f240..000000000 --- a/src/MarginTrading.Frontend/Services/TerminalInfoService.cs +++ /dev/null @@ -1,48 +0,0 @@ -using MarginTrading.Frontend.Models; -using MarginTrading.Frontend.Settings; -using Microsoft.AspNetCore.Http; - -namespace MarginTrading.Frontend.Services -{ - /// - /// Gets terminal info from settings for terminal ID passed in corresponding request header - /// - public class TerminalInfoService : ITerminalInfoService - { - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly TerminalsSettings _settings; - - public TerminalInfoService(IHttpContextAccessor httpContextAccessor, - TerminalsSettings settings) - { - _httpContextAccessor = httpContextAccessor; - _settings = settings; - } - - public TerminalInfo Get() - { - var context = _httpContextAccessor.HttpContext; - - string terminalId = string.Empty; - - if (context.Request.Headers.ContainsKey(_settings.TerminalIdHeaderName)) - { - terminalId = context.Request.Headers[_settings.TerminalIdHeaderName].ToString(); - } - - //try get settings for terminal ID passed in header (or for empty string if header is empty) - if (!_settings.Settings.TryGetValue(terminalId, out var terminalSettings)) - { - //if terminal ID was not empty but no settings exists for it, we try to get default settings - if (terminalId != string.Empty) - { - _settings.Settings.TryGetValue(string.Empty, out terminalSettings); - } - } - - return new TerminalInfo(terminalId, - terminalSettings?.DemoEnabled ?? false, - terminalSettings?.LiveEnabled ?? false); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Services/WampSessionsService.cs b/src/MarginTrading.Frontend/Services/WampSessionsService.cs deleted file mode 100644 index 65de2d621..000000000 --- a/src/MarginTrading.Frontend/Services/WampSessionsService.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MarginTrading.Frontend.Services -{ - public class WampSessionsService - { - public int OpenedSessionsCount { get; set; } - } -} diff --git a/src/MarginTrading.Frontend/Services/WatchListService.cs b/src/MarginTrading.Frontend/Services/WatchListService.cs deleted file mode 100644 index e696887de..000000000 --- a/src/MarginTrading.Frontend/Services/WatchListService.cs +++ /dev/null @@ -1,164 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MarginTrading.Common.Services.Settings; -using MarginTrading.Common.Settings; -using MarginTrading.Contract.BackendContracts; -using MarginTrading.Frontend.Repositories; -using MarginTrading.Frontend.Repositories.Contract; - -namespace MarginTrading.Frontend.Services -{ - public class WatchListService : IWatchListService - { - private readonly IHttpRequestService _httpRequestService; - private readonly IMarginTradingWatchListRepository _watchListRepository; - private readonly IMarginTradingSettingsCacheService _marginTradingSettingsCacheService; - private const string AllAssetsWatchListId = "all_assets_watchlist"; - - public WatchListService( - IHttpRequestService httpRequestService, - IMarginTradingWatchListRepository watchListRepository, - IMarginTradingSettingsCacheService marginTradingSettingsCacheService) - { - _httpRequestService = httpRequestService; - _watchListRepository = watchListRepository; - _marginTradingSettingsCacheService = marginTradingSettingsCacheService; - } - - public async Task> GetAllAsync(string clientId) - { - return await GetWatchLists(clientId); - } - - public async Task GetAsync(string clientId, string id) - { - return id == AllAssetsWatchListId - ? await GetAllAssetsWatchList(clientId) - : await _watchListRepository.GetAsync(clientId, id); - } - - public async Task> AddAsync(string id, string clientId, string name, List assetIds) - { - var result = new WatchListResult(); - var isNew = string.IsNullOrEmpty(id); - var watchLists = (await GetWatchLists(clientId)).ToList(); - var allAssets = await GetAvailableAssetIds(clientId); - - foreach (var assetId in assetIds) - { - if (!allAssets.Contains(assetId)) - { - result.Status = WatchListStatus.AssetNotFound; - result.Message = assetId; - return result; - } - } - - var existing = watchLists.FirstOrDefault(item => item.Id == id); - - if (existing != null && existing.ReadOnly) - { - result.Status = WatchListStatus.ReadOnly; - result.Message = "This watch list is readonly"; - return result; - } - - var watchList = new MarginTradingWatchList - { - Id = isNew ? Guid.NewGuid().ToString("N") : id, - ClientId = clientId, - Name = name, - AssetIds = assetIds - }; - - if (isNew) - { - watchList.Order = watchLists.Count; - } - - if (existing != null) - { - watchList.Order = existing.Order; - } - - result.Result = await _watchListRepository.AddAsync(watchList); - return result; - } - - public async Task> DeleteAsync(string clientId, string id) - { - var result = new WatchListResult(); - - var watchList = await GetAsync(clientId, id); - - if (watchList == null) - { - result.Status = WatchListStatus.NotFound; - return result; - } - - if (watchList.ReadOnly) - { - result.Status = WatchListStatus.ReadOnly; - return result; - } - - await _watchListRepository.DeleteAsync(clientId, id); - - result.Result = true; - return result; - } - - private async Task> GetAvailableAssetIds(string clientId) - { - - var marginTradingEnabled = await _marginTradingSettingsCacheService.IsMarginTradingEnabled(clientId); - var responses = await _httpRequestService.RequestIfAvailableAsync(new ClientIdBackendRequest { ClientId = clientId }, - "init.availableassets", - () => new List(), - marginTradingEnabled); - return responses.Live.Concat(responses.Demo).Distinct().ToList(); - } - - private async Task> GetWatchLists(string clientId) - { - var availableAssets = await GetAvailableAssetIds(clientId); - - var result = new List(); - - var watchLists = (await _watchListRepository.GetAllAsync(clientId)).ToList(); - - if (watchLists.Any()) - { - foreach (var watchlist in watchLists) - { - watchlist.AssetIds.RemoveAll(item => !availableAssets.Contains(item)); - } - - result.AddRange(watchLists.Select(MarginTradingWatchList.Create)); - } - - var watchList = await GetAllAssetsWatchList(clientId); - - result.Insert(0, watchList); - - return result; - } - - private async Task GetAllAssetsWatchList(string clientId) - { - var allAssets = await GetAvailableAssetIds(clientId); - - return new MarginTradingWatchList - { - Id = AllAssetsWatchListId, - ClientId = clientId, - Name = "All assets", - AssetIds = allAssets, - ReadOnly = true - }; - } - } -} diff --git a/src/MarginTrading.Frontend/Settings/ApplicationSettings.cs b/src/MarginTrading.Frontend/Settings/ApplicationSettings.cs deleted file mode 100644 index cd4080372..000000000 --- a/src/MarginTrading.Frontend/Settings/ApplicationSettings.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.Collections.Generic; -using Lykke.SettingsReader.Attributes; -using MarginTrading.Common.RabbitMq; -using MarginTrading.Common.Settings; - -namespace MarginTrading.Frontend.Settings -{ - public class ApplicationSettings - { - public MtFrontendSettings MtFrontend {get; set;} - public SlackNotificationSettings SlackNotifications { get; set; } - public ClientAccountServiceSettings ClientAccountServiceClient { get; set; } - public MtDataReaderClientSettings MtDataReaderLiveServiceClient { get; set; } - public MtDataReaderClientSettings MtDataReaderDemoServiceClient { get; set; } - } - - public class MtFrontendSettings - { - public MtSettings MarginTradingLive { get; set; } - public MtSettings MarginTradingDemo { get; set; } - public MtFrontSettings MarginTradingFront { get; set; } - } - - public class MtSettings - { - public string ApiRootUrl { get; set; } - public string ApiKey { get; set; } - - public string MtRabbitMqConnString { get; set; } - } - - public class DbSettings - { - public string LogsConnString { get; set; } - public string MarginTradingConnString { get; set; } - } - - public class MtQueues - { - public RabbitMqQueueInfo AccountChanged { get; set; } - public RabbitMqQueueInfo OrderChanged { get; set; } - public RabbitMqQueueInfo AccountStopout { get; set; } - public RabbitMqQueueInfo UserUpdates { get; set; } - public RabbitMqQueueInfo OrderbookPrices { get; set; } - public RabbitMqQueueInfo Trades { get; set; } - public RabbitMqQueueInfo MarginTradingEnabledChanged { get; set; } - } - - public class MtFrontSettings - { - public string SessionServiceApiUrl { get; set; } - public string DemoAccountIdPrefix { get; set; } - public CorsSettings CorsSettings { get; set; } - public DbSettings Db { get; set; } - public MtQueues RabbitMqQueues { get; set; } - public RequestLoggerSettings RequestLoggerSettings { get; set; } - public TerminalsSettings TerminalsSettings { get; set; } - - [Optional] - public string ApplicationInsightsKey { get; set; } - - #region From env variables - - [Optional] - public string Env { get; set; } - - #endregion - } - - public class TerminalsSettings - { - public string TerminalIdHeaderName { get; set; } - public Dictionary Settings { get; set; } - } - - public class TerminalSettings - { - public bool DemoEnabled { get; set; } - public bool LiveEnabled { get; set; } - } - - public class CorsSettings - { - public bool Enabled { get; set; } - public bool HandleOptionsRequest { get; set; } - public string AllowOrigins { get; set; } - public string AllowHeaders { get; set; } - public string AllowMethods { get; set; } - public bool AllowCredentials { get; set; } - } -} diff --git a/src/MarginTrading.Frontend/Settings/MtDataReaderClientSettings.cs b/src/MarginTrading.Frontend/Settings/MtDataReaderClientSettings.cs deleted file mode 100644 index 9dd2b0553..000000000 --- a/src/MarginTrading.Frontend/Settings/MtDataReaderClientSettings.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MarginTrading.Frontend.Settings -{ - public class MtDataReaderClientSettings - { - public string ServiceUrl { get; set; } - public string ApiKey { get; set; } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Startup.cs b/src/MarginTrading.Frontend/Startup.cs deleted file mode 100644 index 04b4e603b..000000000 --- a/src/MarginTrading.Frontend/Startup.cs +++ /dev/null @@ -1,314 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using Autofac; -using Autofac.Extensions.DependencyInjection; -using Common.Log; -using Lykke.AzureQueueIntegration; -using Lykke.Common.ApiLibrary.Swagger; -using Lykke.Logs; -using Lykke.SettingsReader; -using Lykke.SlackNotification.AzureQueue; -using MarginTrading.Common.Extensions; -using MarginTrading.Common.Json; -using MarginTrading.Common.Modules; -using MarginTrading.Common.RabbitMq; -using MarginTrading.Common.Services; -using MarginTrading.Common.Services.Settings; -using MarginTrading.Contract.BackendContracts; -using MarginTrading.Contract.ClientContracts; -using MarginTrading.Contract.RabbitMqMessageModels; -using MarginTrading.Frontend.Infrastructure; -using MarginTrading.Frontend.Middleware; -using MarginTrading.Frontend.Modules; -using MarginTrading.Frontend.Settings; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.IdentityModel.Tokens; -using Newtonsoft.Json; -using WampSharp.AspNetCore.WebSockets.Server; -using WampSharp.Binding; -using WampSharp.V2; -using WampSharp.V2.MetaApi; -using WampSharp.V2.Realm; -using LogLevel = Microsoft.Extensions.Logging.LogLevel; - -#pragma warning disable 1591 - -namespace MarginTrading.Frontend -{ - public class Startup - { - public IConfigurationRoot Configuration { get; } - public IHostingEnvironment Environment { get; } - public IContainer ApplicationContainer { get; set; } - - public Startup(IHostingEnvironment env) - { - Configuration = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddDevJson(env) - .AddEnvironmentVariables() - .Build(); - - Environment = env; - } - - public IServiceProvider ConfigureServices(IServiceCollection services) - { - var loggerFactory = new LoggerFactory() - .AddConsole(LogLevel.Error) - .AddDebug(LogLevel.Error); - - services.AddSingleton(loggerFactory); - services.AddLogging(); - services.AddSingleton(Configuration); - services.AddMvc() - .AddJsonOptions(options => - { - options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver(); - options.SerializerSettings.Converters = SerializerSettings.GetDefaultConverters(); - }); - - services.AddSwaggerGen(options => - { - options.DefaultLykkeConfiguration("v1", "MarginTrading_Api"); - options.OperationFilter(); - }); - - services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - .AddJwtBearer(options => - { - options.SecurityTokenValidators.Clear(); - options.SecurityTokenValidators.Add(ApplicationContainer.Resolve()); - }); - - var builder = new ContainerBuilder(); - - - var appSettings = Configuration.LoadSettings() - .Nested(s => - { - if (!string.IsNullOrEmpty(Configuration["Env"])) - { - s.MtFrontend.MarginTradingFront.Env = Configuration["Env"]; - } - - return s; - }); - - var settings = appSettings.Nested(s => s.MtFrontend); - - - Console.WriteLine($"Env: {settings.CurrentValue.MarginTradingFront.Env}"); - - SetupLoggers(services, appSettings); - - RegisterModules(builder, appSettings); - - builder.Populate(services); - - ApplicationContainer = builder.Build(); - - SetSubscribers(settings.CurrentValue); - - return new AutofacServiceProvider(ApplicationContainer); - } - - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime appLifetime) - { - app.UseGlobalErrorHandler(); - app.UseOptions(); - - var settings = ApplicationContainer.Resolve(); - - if (settings.CorsSettings.Enabled) - { - app.UseCors(builder => - { - builder.WithOrigins(settings.CorsSettings.AllowOrigins) - .WithHeaders(settings.CorsSettings.AllowHeaders) - .WithMethods(settings.CorsSettings.AllowMethods); - - if (settings.CorsSettings.AllowCredentials) - builder.AllowCredentials(); - }); - } - - - var host = ApplicationContainer.Resolve(); - var realm = ApplicationContainer.Resolve(); - var realmMetaService = realm.HostMetaApiService(); - - app.UseAuthentication(); - - app.UseMvc(routes => - { - routes.MapRoute(name: "Default", template: "{controller=Home}/{action=Index}/{id?}"); - }); - - app.UseSwagger(); - app.UseSwaggerUi(); - app.UseStaticFiles(); - - app.Map("/ws", builder => - { - builder.UseWebSockets(new WebSocketOptions {KeepAliveInterval = TimeSpan.FromMinutes(1)}); - - var jsonSettings = - new JsonSerializerSettings() {Converters = SerializerSettings.GetDefaultConverters()}; - var jsonSerializer = JsonSerializer.Create(jsonSettings); - - host.RegisterTransport(new AspNetCoreWebSocketTransport(builder), - new JTokenJsonBinding(jsonSerializer), - new JTokenMsgpackBinding(jsonSerializer)); - }); - - appLifetime.ApplicationStopped.Register(() => ApplicationContainer.Dispose()); - - var application = app.ApplicationServices.GetService(); - - appLifetime.ApplicationStarted.Register(() => - { - if (!string.IsNullOrEmpty(settings.ApplicationInsightsKey)) - { - Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.Active.InstrumentationKey = - settings.ApplicationInsightsKey; - } - - application.StartAsync().Wait(); - - LogLocator.CommonLog?.WriteMonitorAsync("", "", settings.Env + " Started"); - }); - - appLifetime.ApplicationStopping.Register(() => - { - LogLocator.CommonLog?.WriteMonitorAsync("", "", settings.Env + " Terminating"); - realmMetaService.Dispose(); - application.Stop(); - } - ); - - host.Open(); - } - - private static void RegisterModules(ContainerBuilder builder, IReloadingManager appSettings) - { - var settings = appSettings.Nested(s => s.MtFrontend); - - builder.RegisterModule(new FrontendModule(settings)); - builder.RegisterModule(new MarginTradingCommonModule()); - builder.RegisterModule(new FrontendExternalServicesModule(appSettings)); - } - - private void SetSubscribers(MtFrontendSettings settings) - { - var rabbitMqService = ApplicationContainer.Resolve(); - var rabbitMqHandler = ApplicationContainer.Resolve(); - - // Best prices (only live) - - Subscribe(rabbitMqService, settings.MarginTradingLive.MtRabbitMqConnString, - settings.MarginTradingFront.RabbitMqQueues.OrderbookPrices.ExchangeName, rabbitMqHandler.ProcessPrices); - - // Account changes - - Subscribe(rabbitMqService, settings.MarginTradingLive.MtRabbitMqConnString, - settings.MarginTradingFront.RabbitMqQueues.AccountChanged.ExchangeName, - rabbitMqHandler.ProcessAccountChanged); - - Subscribe(rabbitMqService, settings.MarginTradingDemo.MtRabbitMqConnString, - settings.MarginTradingFront.RabbitMqQueues.AccountChanged.ExchangeName, - rabbitMqHandler.ProcessAccountChanged); - - // Order changes - - Subscribe(rabbitMqService, settings.MarginTradingLive.MtRabbitMqConnString, - settings.MarginTradingFront.RabbitMqQueues.OrderChanged.ExchangeName, - rabbitMqHandler.ProcessOrderChanged); - - Subscribe(rabbitMqService, settings.MarginTradingDemo.MtRabbitMqConnString, - settings.MarginTradingFront.RabbitMqQueues.OrderChanged.ExchangeName, - rabbitMqHandler.ProcessOrderChanged); - - // Stopout - - Subscribe(rabbitMqService, settings.MarginTradingLive.MtRabbitMqConnString, - settings.MarginTradingFront.RabbitMqQueues.AccountStopout.ExchangeName, - rabbitMqHandler.ProcessAccountStopout); - - Subscribe(rabbitMqService, settings.MarginTradingDemo.MtRabbitMqConnString, - settings.MarginTradingFront.RabbitMqQueues.AccountStopout.ExchangeName, - rabbitMqHandler.ProcessAccountStopout); - - // User updates - - Subscribe(rabbitMqService, settings.MarginTradingLive.MtRabbitMqConnString, - settings.MarginTradingFront.RabbitMqQueues.UserUpdates.ExchangeName, - rabbitMqHandler.ProcessUserUpdates); - - Subscribe(rabbitMqService, settings.MarginTradingDemo.MtRabbitMqConnString, - settings.MarginTradingFront.RabbitMqQueues.UserUpdates.ExchangeName, - rabbitMqHandler.ProcessUserUpdates); - - // Trades - - Subscribe(rabbitMqService, settings.MarginTradingLive.MtRabbitMqConnString, - settings.MarginTradingFront.RabbitMqQueues.Trades.ExchangeName, rabbitMqHandler.ProcessTrades); - - Subscribe(rabbitMqService, settings.MarginTradingDemo.MtRabbitMqConnString, - settings.MarginTradingFront.RabbitMqQueues.Trades.ExchangeName, rabbitMqHandler.ProcessTrades); - - Subscribe(rabbitMqService, - settings.MarginTradingLive.MtRabbitMqConnString, - settings.MarginTradingFront.RabbitMqQueues.MarginTradingEnabledChanged.ExchangeName, - rabbitMqHandler.ProcessMarginTradingEnabledChanged); - - Subscribe(rabbitMqService, - settings.MarginTradingDemo.MtRabbitMqConnString, - settings.MarginTradingFront.RabbitMqQueues.MarginTradingEnabledChanged.ExchangeName, - rabbitMqHandler.ProcessMarginTradingEnabledChanged); - } - - private static void SetupLoggers(IServiceCollection services, IReloadingManager settings) - { - var consoleLogger = new LogToConsole(); - - var azureQueue = new AzureQueueSettings - { - ConnectionString = settings.CurrentValue.SlackNotifications.AzureQueue.ConnectionString, - QueueName = settings.CurrentValue.SlackNotifications.AzureQueue.QueueName - }; - - var comonSlackService = - services.UseSlackNotificationsSenderViaAzureQueue(azureQueue, consoleLogger); - - var slackService = - new MtSlackNotificationsSender(comonSlackService, "MT Frontend", settings.CurrentValue.MtFrontend.MarginTradingFront.Env); - - // Order of logs registration is important - UseLogToAzureStorage() registers ILog in container. - // Last registration wins. - LogLocator.RequestsLog = services.UseLogToAzureStorage(settings.Nested(s => s.MtFrontend.MarginTradingFront.Db.LogsConnString), - slackService, "MarginTradingFrontendRequestsLog", consoleLogger); - - LogLocator.CommonLog = services.UseLogToAzureStorage(settings.Nested(s => s.MtFrontend.MarginTradingFront.Db.LogsConnString), - slackService, "MarginTradingFrontendLog", consoleLogger); - } - - private static void Subscribe(IRabbitMqService rabbitMqService, string connectionString, - string exchangeName, Func handler) - { - var settings = new RabbitMqSettings - { - ConnectionString = connectionString, - ExchangeName = exchangeName, - }; - - rabbitMqService.Subscribe(settings, false, handler, rabbitMqService.GetJsonDeserializer()); - } - } -} diff --git a/src/MarginTrading.Frontend/Views/Home/Index.cshtml b/src/MarginTrading.Frontend/Views/Home/Index.cshtml deleted file mode 100644 index b77627aba..000000000 --- a/src/MarginTrading.Frontend/Views/Home/Index.cshtml +++ /dev/null @@ -1,36 +0,0 @@ -@model MarginTrading.Frontend.Models.MethodInfoModel - - - Wamp documentation - - - - - - -
- -
-
- @Html.Partial("MethodsListPartial", Model.Rpc) -
-
- @Html.Partial("MethodsListPartial", Model.Topic) -
-
-
- - - - - - \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Views/Shared/MethodsListPartial.cshtml b/src/MarginTrading.Frontend/Views/Shared/MethodsListPartial.cshtml deleted file mode 100644 index 89e4522c9..000000000 --- a/src/MarginTrading.Frontend/Views/Shared/MethodsListPartial.cshtml +++ /dev/null @@ -1,63 +0,0 @@ -@model MarginTrading.Common.Documentation.MethodDocInfo[] -@{ - var guid = Guid.NewGuid().ToString(); -} - -
- @foreach (var rpc in Model) - { -
- -
-
- - - - - - - - - - - -
InputOutputDescription
- @if (rpc.InputTypes?.Length > 0) - { - -
- @Html.Partial("TypesListParial", rpc.InputTypes) -
- } - else - { - @rpc.Input - } -
- @if (rpc.OutputTypes?.Length > 0) - { - -
- @Html.Partial("TypesListParial", rpc.OutputTypes) -
- } - else - { - @rpc.Output - } -
@rpc.Description
-
-
-
- } -
\ No newline at end of file diff --git a/src/MarginTrading.Frontend/Views/Shared/TypesListParial.cshtml b/src/MarginTrading.Frontend/Views/Shared/TypesListParial.cshtml deleted file mode 100644 index dcb52015f..000000000 --- a/src/MarginTrading.Frontend/Views/Shared/TypesListParial.cshtml +++ /dev/null @@ -1,11 +0,0 @@ -@using MarginTrading.Common.Extensions -@model Type[] - -
-    
-        @foreach (var type in Model)
-        {
-            @type.GetTypeDefinition()
-        }
-    
-
\ No newline at end of file diff --git a/src/MarginTrading.Frontend/Wamp/AnonymousWampAuthorizer.cs b/src/MarginTrading.Frontend/Wamp/AnonymousWampAuthorizer.cs deleted file mode 100644 index 4dc66860c..000000000 --- a/src/MarginTrading.Frontend/Wamp/AnonymousWampAuthorizer.cs +++ /dev/null @@ -1,28 +0,0 @@ -using WampSharp.V2.Authentication; -using WampSharp.V2.Core.Contracts; - -namespace MarginTrading.Frontend.Wamp -{ - public class AnonymousWampAuthorizer : IWampAuthorizer - { - public bool CanRegister(RegisterOptions options, string procedure) - { - return false; - } - - public bool CanCall(CallOptions options, string procedure) - { - return true; - } - - public bool CanPublish(PublishOptions options, string topicUri) - { - return false; - } - - public bool CanSubscribe(SubscribeOptions options, string topicUri) - { - return string.IsNullOrEmpty(options?.Match) || options.Match == WampMatchPattern.Exact; - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Wamp/AnonymousWampSessionAuthenticator.cs b/src/MarginTrading.Frontend/Wamp/AnonymousWampSessionAuthenticator.cs deleted file mode 100644 index dd8be877d..000000000 --- a/src/MarginTrading.Frontend/Wamp/AnonymousWampSessionAuthenticator.cs +++ /dev/null @@ -1,27 +0,0 @@ -using WampSharp.V2.Authentication; -using WampSharp.V2.Core.Contracts; - -namespace MarginTrading.Frontend.Wamp -{ - public class AnonymousWampSessionAuthenticator : WampSessionAuthenticator - { - private readonly IWampAuthorizer _authorizer; - - public AnonymousWampSessionAuthenticator() - { - _authorizer = new AnonymousWampAuthorizer(); - } - - public override void Authenticate(string signature, AuthenticateExtraData extra) - { - } - - public override IWampAuthorizer Authorizer => _authorizer; - - public override bool IsAuthenticated => true; - - public override string AuthenticationId => "Anonymous"; - - public override string AuthenticationMethod => "None"; - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/Wamp/IRpcMtFrontend.cs b/src/MarginTrading.Frontend/Wamp/IRpcMtFrontend.cs deleted file mode 100644 index a26d628a0..000000000 --- a/src/MarginTrading.Frontend/Wamp/IRpcMtFrontend.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Threading.Tasks; -using MarginTrading.Common.Documentation; -using MarginTrading.Contract.ClientContracts; -using WampSharp.V2.Rpc; - -namespace MarginTrading.Frontend.Wamp -{ - public interface IRpcMtFrontend - { - [WampProcedure("is.alive")] - [DocMe(Name = "is.alive", Description = "Checks service isAlive")] - IsAliveResponse IsAlive(); - - [WampProcedure("init.data")] - [DocMe(Name = "init.data", Description = "Gets init data: client accounts and trading conditions")] - Task InitData(string token); - - [WampProcedure("init.accounts")] - [DocMe(Name = "init.accounts", Description = "Gets client accounts")] - Task InitAccounts(string token); - - [WampProcedure("init.accountinstruments")] - [DocMe(Name = "init.accountinstruments", Description = "Gets trading conditions")] - Task AccountInstruments(string token = null); - - [WampProcedure("init.graph")] - [DocMe(Name = "init.graph", Description = "Gets data for micrographics")] - Task InitGraph(string token = null, string[] assetIds = null); - - [WampProcedure("account.history")] - [DocMe(Name = "account.history", Description = "Gets account history", InputType = typeof(AccountHistoryRpcClientRequest))] - Task GetAccountHistory(string requestJson); - - [WampProcedure("account.history.new")] - [DocMe(Name = "account.history.new", Description = "Gets account history (different format)", InputType = typeof(AccountHistoryRpcClientRequest))] - Task GetHistory(string requestJson); - - [WampProcedure("order.place")] - [DocMe(Name = "order.place", Description = "Places order", InputType = typeof(OpenOrderRpcClientRequest))] - Task> PlaceOrder(string requestJson); - - [WampProcedure("order.close")] - [DocMe(Name = "order.close", Description = "Close order", InputType = typeof(CloseOrderRpcClientRequest))] - Task> CloseOrder(string requestJson); - - [WampProcedure("order.cancel")] - [DocMe(Name = "order.cancel", Description = "Cancel order", InputType = typeof(CloseOrderRpcClientRequest))] - Task> CancelOrder(string requestJson); - - [WampProcedure("order.list")] - [DocMe(Name = "order.list", Description = "Gets client open positions")] - Task GetOpenPositions(string token); - - [WampProcedure("order.account.list")] - [DocMe(Name = "order.account.list", Description = "Gets client account open positions", InputType = typeof(AccountTokenClientRequest))] - Task GetAccountOpenPositions(string requestJson); - - [WampProcedure("order.positions")] - Task GetClientOrders(string token); - - [WampProcedure("order.changeLimits")] - [DocMe(Name = "order.changeLimits", Description = "Sets order limits", InputType = typeof(ChangeOrderLimitsRpcClientRequest))] - Task> ChangeOrderLimits(string requestJson); - - [WampProcedure("orderbooks")] - Task GetOrderBook(string instrument); - } -} diff --git a/src/MarginTrading.Frontend/Wamp/IWampTopic.cs b/src/MarginTrading.Frontend/Wamp/IWampTopic.cs deleted file mode 100644 index 87a3e94ce..000000000 --- a/src/MarginTrading.Frontend/Wamp/IWampTopic.cs +++ /dev/null @@ -1,24 +0,0 @@ -using MarginTrading.Common.Documentation; -using MarginTrading.Contract.ClientContracts; -using MarginTrading.Contract.RabbitMqMessageModels; - -namespace MarginTrading.Frontend.Wamp -{ - public interface IWampTopic - { - [DocMe(Name = "prices.update", Description = " [ Obsolete ] sends prices for all instruments")] - BidAskPairRabbitMqContract AllPricesUpdate(); - - [DocMe(Name = "prices.update.{instrumentId}", Description = "sends prices for specific instrument")] - BidAskPairRabbitMqContract InstumentPricesUpdate(); - - [DocMe(Name = "user.{notificationId}", Description = " [ Obsolete ] sends user updates on position, account changes and dictionaries")] - NotifyResponse UserUpdates(); - - [DocMe(Name = "user.updates.{notificationId}", Description = "sends user updates on position, account changes and dictionaries")] - NotifyResponse UserUpdates(); - - [DocMe(Name = "trades", Description = "sends trades info")] - TradeClientContract Trades(); - } -} diff --git a/src/MarginTrading.Frontend/Wamp/WampConstants.cs b/src/MarginTrading.Frontend/Wamp/WampConstants.cs deleted file mode 100644 index 9cf94abf2..000000000 --- a/src/MarginTrading.Frontend/Wamp/WampConstants.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace MarginTrading.Frontend.Wamp -{ - public static class WampConstants - { - public const string FrontEndRealmName = "mtcrossbar"; - - public const string PricesTopicPrefix = "prices.update"; - public const string UserUpdatesTopicPrefix = "user"; - public const string TradesTopic = "trades"; - } -} diff --git a/src/MarginTrading.Frontend/Wamp/WampSessionAuthenticatorFactory.cs b/src/MarginTrading.Frontend/Wamp/WampSessionAuthenticatorFactory.cs deleted file mode 100644 index 1a9e21541..000000000 --- a/src/MarginTrading.Frontend/Wamp/WampSessionAuthenticatorFactory.cs +++ /dev/null @@ -1,14 +0,0 @@ -using WampSharp.V2.Authentication; - -namespace MarginTrading.Frontend.Wamp -{ - public class WampSessionAuthenticatorFactory : IWampSessionAuthenticatorFactory - { - public IWampSessionAuthenticator GetSessionAuthenticator - (WampPendingClientDetails details, - IWampSessionAuthenticator transportAuthenticator) - { - return new AnonymousWampSessionAuthenticator(); - } - } -} \ No newline at end of file diff --git a/src/MarginTrading.Frontend/docker-compose.yml b/src/MarginTrading.Frontend/docker-compose.yml deleted file mode 100644 index 7549b9d71..000000000 --- a/src/MarginTrading.Frontend/docker-compose.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: '2' -services: - margintradingfront: - image: lykkex/margintradingfront - container_name: margintradingfront - environment: - - SettingsUrl=${SettingsUrl} - - IsLive=${IsLive} - - Env=${Env} - - KestrelThreadCount=${KestrelThreadCount} - ports: - - "5005:5005" - networks: - mynet: - aliases: - - margintradingfront - -networks: - mynet: - driver: bridge diff --git a/src/MarginTrading.Frontend/web.config b/src/MarginTrading.Frontend/web.config deleted file mode 100644 index a8d667275..000000000 --- a/src/MarginTrading.Frontend/web.config +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - diff --git a/src/MarginTrading.Frontend/wwwroot/css/bootstrap.css b/src/MarginTrading.Frontend/wwwroot/css/bootstrap.css deleted file mode 100644 index 6167622ce..000000000 --- a/src/MarginTrading.Frontend/wwwroot/css/bootstrap.css +++ /dev/null @@ -1,6757 +0,0 @@ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ -/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ -html { - font-family: sans-serif; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; -} -body { - margin: 0; -} -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -menu, -nav, -section, -summary { - display: block; -} -audio, -canvas, -progress, -video { - display: inline-block; - vertical-align: baseline; -} -audio:not([controls]) { - display: none; - height: 0; -} -[hidden], -template { - display: none; -} -a { - background-color: transparent; -} -a:active, -a:hover { - outline: 0; -} -abbr[title] { - border-bottom: 1px dotted; -} -b, -strong { - font-weight: bold; -} -dfn { - font-style: italic; -} -h1 { - margin: .67em 0; - font-size: 2em; -} -mark { - color: #000; - background: #ff0; -} -small { - font-size: 80%; -} -sub, -sup { - position: relative; - font-size: 75%; - line-height: 0; - vertical-align: baseline; -} -sup { - top: -.5em; -} -sub { - bottom: -.25em; -} -img { - border: 0; -} -svg:not(:root) { - overflow: hidden; -} -figure { - margin: 1em 40px; -} -hr { - height: 0; - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; -} -pre { - overflow: auto; -} -code, -kbd, -pre, -samp { - font-family: monospace, monospace; - font-size: 1em; -} -button, -input, -optgroup, -select, -textarea { - margin: 0; - font: inherit; - color: inherit; -} -button { - overflow: visible; -} -button, -select { - text-transform: none; -} -button, -html input[type="button"], -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; - cursor: pointer; -} -button[disabled], -html input[disabled] { - cursor: default; -} -button::-moz-focus-inner, -input::-moz-focus-inner { - padding: 0; - border: 0; -} -input { - line-height: normal; -} -input[type="checkbox"], -input[type="radio"] { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - padding: 0; -} -input[type="number"]::-webkit-inner-spin-button, -input[type="number"]::-webkit-outer-spin-button { - height: auto; -} -input[type="search"] { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; - -webkit-appearance: textfield; -} -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} -fieldset { - padding: .35em .625em .75em; - margin: 0 2px; - border: 1px solid #c0c0c0; -} -legend { - padding: 0; - border: 0; -} -textarea { - overflow: auto; -} -optgroup { - font-weight: bold; -} -table { - border-spacing: 0; - border-collapse: collapse; -} -td, -th { - padding: 0; -} -/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ -@media print { - *, - *:before, - *:after { - color: #000 !important; - text-shadow: none !important; - background: transparent !important; - -webkit-box-shadow: none !important; - box-shadow: none !important; - } - a, - a:visited { - text-decoration: underline; - } - a[href]:after { - content: " (" attr(href) ")"; - } - abbr[title]:after { - content: " (" attr(title) ")"; - } - a[href^="#"]:after, - a[href^="javascript:"]:after { - content: ""; - } - pre, - blockquote { - border: 1px solid #999; - - page-break-inside: avoid; - } - thead { - display: table-header-group; - } - tr, - img { - page-break-inside: avoid; - } - img { - max-width: 100% !important; - } - p, - h2, - h3 { - orphans: 3; - widows: 3; - } - h2, - h3 { - page-break-after: avoid; - } - .navbar { - display: none; - } - .btn > .caret, - .dropup > .btn > .caret { - border-top-color: #000 !important; - } - .label { - border: 1px solid #000; - } - .table { - border-collapse: collapse !important; - } - .table td, - .table th { - background-color: #fff !important; - } - .table-bordered th, - .table-bordered td { - border: 1px solid #ddd !important; - } -} -@font-face { - font-family: 'Glyphicons Halflings'; - - src: url('../fonts/glyphicons-halflings-regular.eot'); - src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); -} -.glyphicon { - position: relative; - top: 1px; - display: inline-block; - font-family: 'Glyphicons Halflings'; - font-style: normal; - font-weight: normal; - line-height: 1; - - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} -.glyphicon-asterisk:before { - content: "\002a"; -} -.glyphicon-plus:before { - content: "\002b"; -} -.glyphicon-euro:before, -.glyphicon-eur:before { - content: "\20ac"; -} -.glyphicon-minus:before { - content: "\2212"; -} -.glyphicon-cloud:before { - content: "\2601"; -} -.glyphicon-envelope:before { - content: "\2709"; -} -.glyphicon-pencil:before { - content: "\270f"; -} -.glyphicon-glass:before { - content: "\e001"; -} -.glyphicon-music:before { - content: "\e002"; -} -.glyphicon-search:before { - content: "\e003"; -} -.glyphicon-heart:before { - content: "\e005"; -} -.glyphicon-star:before { - content: "\e006"; -} -.glyphicon-star-empty:before { - content: "\e007"; -} -.glyphicon-user:before { - content: "\e008"; -} -.glyphicon-film:before { - content: "\e009"; -} -.glyphicon-th-large:before { - content: "\e010"; -} -.glyphicon-th:before { - content: "\e011"; -} -.glyphicon-th-list:before { - content: "\e012"; -} -.glyphicon-ok:before { - content: "\e013"; -} -.glyphicon-remove:before { - content: "\e014"; -} -.glyphicon-zoom-in:before { - content: "\e015"; -} -.glyphicon-zoom-out:before { - content: "\e016"; -} -.glyphicon-off:before { - content: "\e017"; -} -.glyphicon-signal:before { - content: "\e018"; -} -.glyphicon-cog:before { - content: "\e019"; -} -.glyphicon-trash:before { - content: "\e020"; -} -.glyphicon-home:before { - content: "\e021"; -} -.glyphicon-file:before { - content: "\e022"; -} -.glyphicon-time:before { - content: "\e023"; -} -.glyphicon-road:before { - content: "\e024"; -} -.glyphicon-download-alt:before { - content: "\e025"; -} -.glyphicon-download:before { - content: "\e026"; -} -.glyphicon-upload:before { - content: "\e027"; -} -.glyphicon-inbox:before { - content: "\e028"; -} -.glyphicon-play-circle:before { - content: "\e029"; -} -.glyphicon-repeat:before { - content: "\e030"; -} -.glyphicon-refresh:before { - content: "\e031"; -} -.glyphicon-list-alt:before { - content: "\e032"; -} -.glyphicon-lock:before { - content: "\e033"; -} -.glyphicon-flag:before { - content: "\e034"; -} -.glyphicon-headphones:before { - content: "\e035"; -} -.glyphicon-volume-off:before { - content: "\e036"; -} -.glyphicon-volume-down:before { - content: "\e037"; -} -.glyphicon-volume-up:before { - content: "\e038"; -} -.glyphicon-qrcode:before { - content: "\e039"; -} -.glyphicon-barcode:before { - content: "\e040"; -} -.glyphicon-tag:before { - content: "\e041"; -} -.glyphicon-tags:before { - content: "\e042"; -} -.glyphicon-book:before { - content: "\e043"; -} -.glyphicon-bookmark:before { - content: "\e044"; -} -.glyphicon-print:before { - content: "\e045"; -} -.glyphicon-camera:before { - content: "\e046"; -} -.glyphicon-font:before { - content: "\e047"; -} -.glyphicon-bold:before { - content: "\e048"; -} -.glyphicon-italic:before { - content: "\e049"; -} -.glyphicon-text-height:before { - content: "\e050"; -} -.glyphicon-text-width:before { - content: "\e051"; -} -.glyphicon-align-left:before { - content: "\e052"; -} -.glyphicon-align-center:before { - content: "\e053"; -} -.glyphicon-align-right:before { - content: "\e054"; -} -.glyphicon-align-justify:before { - content: "\e055"; -} -.glyphicon-list:before { - content: "\e056"; -} -.glyphicon-indent-left:before { - content: "\e057"; -} -.glyphicon-indent-right:before { - content: "\e058"; -} -.glyphicon-facetime-video:before { - content: "\e059"; -} -.glyphicon-picture:before { - content: "\e060"; -} -.glyphicon-map-marker:before { - content: "\e062"; -} -.glyphicon-adjust:before { - content: "\e063"; -} -.glyphicon-tint:before { - content: "\e064"; -} -.glyphicon-edit:before { - content: "\e065"; -} -.glyphicon-share:before { - content: "\e066"; -} -.glyphicon-check:before { - content: "\e067"; -} -.glyphicon-move:before { - content: "\e068"; -} -.glyphicon-step-backward:before { - content: "\e069"; -} -.glyphicon-fast-backward:before { - content: "\e070"; -} -.glyphicon-backward:before { - content: "\e071"; -} -.glyphicon-play:before { - content: "\e072"; -} -.glyphicon-pause:before { - content: "\e073"; -} -.glyphicon-stop:before { - content: "\e074"; -} -.glyphicon-forward:before { - content: "\e075"; -} -.glyphicon-fast-forward:before { - content: "\e076"; -} -.glyphicon-step-forward:before { - content: "\e077"; -} -.glyphicon-eject:before { - content: "\e078"; -} -.glyphicon-chevron-left:before { - content: "\e079"; -} -.glyphicon-chevron-right:before { - content: "\e080"; -} -.glyphicon-plus-sign:before { - content: "\e081"; -} -.glyphicon-minus-sign:before { - content: "\e082"; -} -.glyphicon-remove-sign:before { - content: "\e083"; -} -.glyphicon-ok-sign:before { - content: "\e084"; -} -.glyphicon-question-sign:before { - content: "\e085"; -} -.glyphicon-info-sign:before { - content: "\e086"; -} -.glyphicon-screenshot:before { - content: "\e087"; -} -.glyphicon-remove-circle:before { - content: "\e088"; -} -.glyphicon-ok-circle:before { - content: "\e089"; -} -.glyphicon-ban-circle:before { - content: "\e090"; -} -.glyphicon-arrow-left:before { - content: "\e091"; -} -.glyphicon-arrow-right:before { - content: "\e092"; -} -.glyphicon-arrow-up:before { - content: "\e093"; -} -.glyphicon-arrow-down:before { - content: "\e094"; -} -.glyphicon-share-alt:before { - content: "\e095"; -} -.glyphicon-resize-full:before { - content: "\e096"; -} -.glyphicon-resize-small:before { - content: "\e097"; -} -.glyphicon-exclamation-sign:before { - content: "\e101"; -} -.glyphicon-gift:before { - content: "\e102"; -} -.glyphicon-leaf:before { - content: "\e103"; -} -.glyphicon-fire:before { - content: "\e104"; -} -.glyphicon-eye-open:before { - content: "\e105"; -} -.glyphicon-eye-close:before { - content: "\e106"; -} -.glyphicon-warning-sign:before { - content: "\e107"; -} -.glyphicon-plane:before { - content: "\e108"; -} -.glyphicon-calendar:before { - content: "\e109"; -} -.glyphicon-random:before { - content: "\e110"; -} -.glyphicon-comment:before { - content: "\e111"; -} -.glyphicon-magnet:before { - content: "\e112"; -} -.glyphicon-chevron-up:before { - content: "\e113"; -} -.glyphicon-chevron-down:before { - content: "\e114"; -} -.glyphicon-retweet:before { - content: "\e115"; -} -.glyphicon-shopping-cart:before { - content: "\e116"; -} -.glyphicon-folder-close:before { - content: "\e117"; -} -.glyphicon-folder-open:before { - content: "\e118"; -} -.glyphicon-resize-vertical:before { - content: "\e119"; -} -.glyphicon-resize-horizontal:before { - content: "\e120"; -} -.glyphicon-hdd:before { - content: "\e121"; -} -.glyphicon-bullhorn:before { - content: "\e122"; -} -.glyphicon-bell:before { - content: "\e123"; -} -.glyphicon-certificate:before { - content: "\e124"; -} -.glyphicon-thumbs-up:before { - content: "\e125"; -} -.glyphicon-thumbs-down:before { - content: "\e126"; -} -.glyphicon-hand-right:before { - content: "\e127"; -} -.glyphicon-hand-left:before { - content: "\e128"; -} -.glyphicon-hand-up:before { - content: "\e129"; -} -.glyphicon-hand-down:before { - content: "\e130"; -} -.glyphicon-circle-arrow-right:before { - content: "\e131"; -} -.glyphicon-circle-arrow-left:before { - content: "\e132"; -} -.glyphicon-circle-arrow-up:before { - content: "\e133"; -} -.glyphicon-circle-arrow-down:before { - content: "\e134"; -} -.glyphicon-globe:before { - content: "\e135"; -} -.glyphicon-wrench:before { - content: "\e136"; -} -.glyphicon-tasks:before { - content: "\e137"; -} -.glyphicon-filter:before { - content: "\e138"; -} -.glyphicon-briefcase:before { - content: "\e139"; -} -.glyphicon-fullscreen:before { - content: "\e140"; -} -.glyphicon-dashboard:before { - content: "\e141"; -} -.glyphicon-paperclip:before { - content: "\e142"; -} -.glyphicon-heart-empty:before { - content: "\e143"; -} -.glyphicon-link:before { - content: "\e144"; -} -.glyphicon-phone:before { - content: "\e145"; -} -.glyphicon-pushpin:before { - content: "\e146"; -} -.glyphicon-usd:before { - content: "\e148"; -} -.glyphicon-gbp:before { - content: "\e149"; -} -.glyphicon-sort:before { - content: "\e150"; -} -.glyphicon-sort-by-alphabet:before { - content: "\e151"; -} -.glyphicon-sort-by-alphabet-alt:before { - content: "\e152"; -} -.glyphicon-sort-by-order:before { - content: "\e153"; -} -.glyphicon-sort-by-order-alt:before { - content: "\e154"; -} -.glyphicon-sort-by-attributes:before { - content: "\e155"; -} -.glyphicon-sort-by-attributes-alt:before { - content: "\e156"; -} -.glyphicon-unchecked:before { - content: "\e157"; -} -.glyphicon-expand:before { - content: "\e158"; -} -.glyphicon-collapse-down:before { - content: "\e159"; -} -.glyphicon-collapse-up:before { - content: "\e160"; -} -.glyphicon-log-in:before { - content: "\e161"; -} -.glyphicon-flash:before { - content: "\e162"; -} -.glyphicon-log-out:before { - content: "\e163"; -} -.glyphicon-new-window:before { - content: "\e164"; -} -.glyphicon-record:before { - content: "\e165"; -} -.glyphicon-save:before { - content: "\e166"; -} -.glyphicon-open:before { - content: "\e167"; -} -.glyphicon-saved:before { - content: "\e168"; -} -.glyphicon-import:before { - content: "\e169"; -} -.glyphicon-export:before { - content: "\e170"; -} -.glyphicon-send:before { - content: "\e171"; -} -.glyphicon-floppy-disk:before { - content: "\e172"; -} -.glyphicon-floppy-saved:before { - content: "\e173"; -} -.glyphicon-floppy-remove:before { - content: "\e174"; -} -.glyphicon-floppy-save:before { - content: "\e175"; -} -.glyphicon-floppy-open:before { - content: "\e176"; -} -.glyphicon-credit-card:before { - content: "\e177"; -} -.glyphicon-transfer:before { - content: "\e178"; -} -.glyphicon-cutlery:before { - content: "\e179"; -} -.glyphicon-header:before { - content: "\e180"; -} -.glyphicon-compressed:before { - content: "\e181"; -} -.glyphicon-earphone:before { - content: "\e182"; -} -.glyphicon-phone-alt:before { - content: "\e183"; -} -.glyphicon-tower:before { - content: "\e184"; -} -.glyphicon-stats:before { - content: "\e185"; -} -.glyphicon-sd-video:before { - content: "\e186"; -} -.glyphicon-hd-video:before { - content: "\e187"; -} -.glyphicon-subtitles:before { - content: "\e188"; -} -.glyphicon-sound-stereo:before { - content: "\e189"; -} -.glyphicon-sound-dolby:before { - content: "\e190"; -} -.glyphicon-sound-5-1:before { - content: "\e191"; -} -.glyphicon-sound-6-1:before { - content: "\e192"; -} -.glyphicon-sound-7-1:before { - content: "\e193"; -} -.glyphicon-copyright-mark:before { - content: "\e194"; -} -.glyphicon-registration-mark:before { - content: "\e195"; -} -.glyphicon-cloud-download:before { - content: "\e197"; -} -.glyphicon-cloud-upload:before { - content: "\e198"; -} -.glyphicon-tree-conifer:before { - content: "\e199"; -} -.glyphicon-tree-deciduous:before { - content: "\e200"; -} -.glyphicon-cd:before { - content: "\e201"; -} -.glyphicon-save-file:before { - content: "\e202"; -} -.glyphicon-open-file:before { - content: "\e203"; -} -.glyphicon-level-up:before { - content: "\e204"; -} -.glyphicon-copy:before { - content: "\e205"; -} -.glyphicon-paste:before { - content: "\e206"; -} -.glyphicon-alert:before { - content: "\e209"; -} -.glyphicon-equalizer:before { - content: "\e210"; -} -.glyphicon-king:before { - content: "\e211"; -} -.glyphicon-queen:before { - content: "\e212"; -} -.glyphicon-pawn:before { - content: "\e213"; -} -.glyphicon-bishop:before { - content: "\e214"; -} -.glyphicon-knight:before { - content: "\e215"; -} -.glyphicon-baby-formula:before { - content: "\e216"; -} -.glyphicon-tent:before { - content: "\26fa"; -} -.glyphicon-blackboard:before { - content: "\e218"; -} -.glyphicon-bed:before { - content: "\e219"; -} -.glyphicon-apple:before { - content: "\f8ff"; -} -.glyphicon-erase:before { - content: "\e221"; -} -.glyphicon-hourglass:before { - content: "\231b"; -} -.glyphicon-lamp:before { - content: "\e223"; -} -.glyphicon-duplicate:before { - content: "\e224"; -} -.glyphicon-piggy-bank:before { - content: "\e225"; -} -.glyphicon-scissors:before { - content: "\e226"; -} -.glyphicon-bitcoin:before { - content: "\e227"; -} -.glyphicon-btc:before { - content: "\e227"; -} -.glyphicon-xbt:before { - content: "\e227"; -} -.glyphicon-yen:before { - content: "\00a5"; -} -.glyphicon-jpy:before { - content: "\00a5"; -} -.glyphicon-ruble:before { - content: "\20bd"; -} -.glyphicon-rub:before { - content: "\20bd"; -} -.glyphicon-scale:before { - content: "\e230"; -} -.glyphicon-ice-lolly:before { - content: "\e231"; -} -.glyphicon-ice-lolly-tasted:before { - content: "\e232"; -} -.glyphicon-education:before { - content: "\e233"; -} -.glyphicon-option-horizontal:before { - content: "\e234"; -} -.glyphicon-option-vertical:before { - content: "\e235"; -} -.glyphicon-menu-hamburger:before { - content: "\e236"; -} -.glyphicon-modal-window:before { - content: "\e237"; -} -.glyphicon-oil:before { - content: "\e238"; -} -.glyphicon-grain:before { - content: "\e239"; -} -.glyphicon-sunglasses:before { - content: "\e240"; -} -.glyphicon-text-size:before { - content: "\e241"; -} -.glyphicon-text-color:before { - content: "\e242"; -} -.glyphicon-text-background:before { - content: "\e243"; -} -.glyphicon-object-align-top:before { - content: "\e244"; -} -.glyphicon-object-align-bottom:before { - content: "\e245"; -} -.glyphicon-object-align-horizontal:before { - content: "\e246"; -} -.glyphicon-object-align-left:before { - content: "\e247"; -} -.glyphicon-object-align-vertical:before { - content: "\e248"; -} -.glyphicon-object-align-right:before { - content: "\e249"; -} -.glyphicon-triangle-right:before { - content: "\e250"; -} -.glyphicon-triangle-left:before { - content: "\e251"; -} -.glyphicon-triangle-bottom:before { - content: "\e252"; -} -.glyphicon-triangle-top:before { - content: "\e253"; -} -.glyphicon-console:before { - content: "\e254"; -} -.glyphicon-superscript:before { - content: "\e255"; -} -.glyphicon-subscript:before { - content: "\e256"; -} -.glyphicon-menu-left:before { - content: "\e257"; -} -.glyphicon-menu-right:before { - content: "\e258"; -} -.glyphicon-menu-down:before { - content: "\e259"; -} -.glyphicon-menu-up:before { - content: "\e260"; -} -* { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -*:before, -*:after { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -html { - font-size: 10px; - - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -} -body { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 1.42857143; - color: #333; - background-color: #fff; -} -input, -button, -select, -textarea { - font-family: inherit; - font-size: inherit; - line-height: inherit; -} -a { - color: #337ab7; - text-decoration: none; -} -a:hover, -a:focus { - color: #23527c; - text-decoration: underline; -} -a:focus { - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} -figure { - margin: 0; -} -img { - vertical-align: middle; -} -.img-responsive, -.thumbnail > img, -.thumbnail a > img, -.carousel-inner > .item > img, -.carousel-inner > .item > a > img { - display: block; - max-width: 100%; - height: auto; -} -.img-rounded { - border-radius: 6px; -} -.img-thumbnail { - display: inline-block; - max-width: 100%; - height: auto; - padding: 4px; - line-height: 1.42857143; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 4px; - -webkit-transition: all .2s ease-in-out; - -o-transition: all .2s ease-in-out; - transition: all .2s ease-in-out; -} -.img-circle { - border-radius: 50%; -} -hr { - margin-top: 20px; - margin-bottom: 20px; - border: 0; - border-top: 1px solid #eee; -} -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - border: 0; -} -.sr-only-focusable:active, -.sr-only-focusable:focus { - position: static; - width: auto; - height: auto; - margin: 0; - overflow: visible; - clip: auto; -} -[role="button"] { - cursor: pointer; -} -h1, -h2, -h3, -h4, -h5, -h6, -.h1, -.h2, -.h3, -.h4, -.h5, -.h6 { - font-family: inherit; - font-weight: 500; - line-height: 1.1; - color: inherit; -} -h1 small, -h2 small, -h3 small, -h4 small, -h5 small, -h6 small, -.h1 small, -.h2 small, -.h3 small, -.h4 small, -.h5 small, -.h6 small, -h1 .small, -h2 .small, -h3 .small, -h4 .small, -h5 .small, -h6 .small, -.h1 .small, -.h2 .small, -.h3 .small, -.h4 .small, -.h5 .small, -.h6 .small { - font-weight: normal; - line-height: 1; - color: #777; -} -h1, -.h1, -h2, -.h2, -h3, -.h3 { - margin-top: 20px; - margin-bottom: 10px; -} -h1 small, -.h1 small, -h2 small, -.h2 small, -h3 small, -.h3 small, -h1 .small, -.h1 .small, -h2 .small, -.h2 .small, -h3 .small, -.h3 .small { - font-size: 65%; -} -h4, -.h4, -h5, -.h5, -h6, -.h6 { - margin-top: 10px; - margin-bottom: 10px; -} -h4 small, -.h4 small, -h5 small, -.h5 small, -h6 small, -.h6 small, -h4 .small, -.h4 .small, -h5 .small, -.h5 .small, -h6 .small, -.h6 .small { - font-size: 75%; -} -h1, -.h1 { - font-size: 36px; -} -h2, -.h2 { - font-size: 30px; -} -h3, -.h3 { - font-size: 24px; -} -h4, -.h4 { - font-size: 18px; -} -h5, -.h5 { - font-size: 14px; -} -h6, -.h6 { - font-size: 12px; -} -p { - margin: 0 0 10px; -} -.lead { - margin-bottom: 20px; - font-size: 16px; - font-weight: 300; - line-height: 1.4; -} -@media (min-width: 768px) { - .lead { - font-size: 21px; - } -} -small, -.small { - font-size: 85%; -} -mark, -.mark { - padding: .2em; - background-color: #fcf8e3; -} -.text-left { - text-align: left; -} -.text-right { - text-align: right; -} -.text-center { - text-align: center; -} -.text-justify { - text-align: justify; -} -.text-nowrap { - white-space: nowrap; -} -.text-lowercase { - text-transform: lowercase; -} -.text-uppercase { - text-transform: uppercase; -} -.text-capitalize { - text-transform: capitalize; -} -.text-muted { - color: #777; -} -.text-primary { - color: #337ab7; -} -a.text-primary:hover, -a.text-primary:focus { - color: #286090; -} -.text-success { - color: #3c763d; -} -a.text-success:hover, -a.text-success:focus { - color: #2b542c; -} -.text-info { - color: #31708f; -} -a.text-info:hover, -a.text-info:focus { - color: #245269; -} -.text-warning { - color: #8a6d3b; -} -a.text-warning:hover, -a.text-warning:focus { - color: #66512c; -} -.text-danger { - color: #a94442; -} -a.text-danger:hover, -a.text-danger:focus { - color: #843534; -} -.bg-primary { - color: #fff; - background-color: #337ab7; -} -a.bg-primary:hover, -a.bg-primary:focus { - background-color: #286090; -} -.bg-success { - background-color: #dff0d8; -} -a.bg-success:hover, -a.bg-success:focus { - background-color: #c1e2b3; -} -.bg-info { - background-color: #d9edf7; -} -a.bg-info:hover, -a.bg-info:focus { - background-color: #afd9ee; -} -.bg-warning { - background-color: #fcf8e3; -} -a.bg-warning:hover, -a.bg-warning:focus { - background-color: #f7ecb5; -} -.bg-danger { - background-color: #f2dede; -} -a.bg-danger:hover, -a.bg-danger:focus { - background-color: #e4b9b9; -} -.page-header { - padding-bottom: 9px; - margin: 40px 0 20px; - border-bottom: 1px solid #eee; -} -ul, -ol { - margin-top: 0; - margin-bottom: 10px; -} -ul ul, -ol ul, -ul ol, -ol ol { - margin-bottom: 0; -} -.list-unstyled { - padding-left: 0; - list-style: none; -} -.list-inline { - padding-left: 0; - margin-left: -5px; - list-style: none; -} -.list-inline > li { - display: inline-block; - padding-right: 5px; - padding-left: 5px; -} -dl { - margin-top: 0; - margin-bottom: 20px; -} -dt, -dd { - line-height: 1.42857143; -} -dt { - font-weight: bold; -} -dd { - margin-left: 0; -} -@media (min-width: 768px) { - .dl-horizontal dt { - float: left; - width: 160px; - overflow: hidden; - clear: left; - text-align: right; - text-overflow: ellipsis; - white-space: nowrap; - } - .dl-horizontal dd { - margin-left: 180px; - } -} -abbr[title], -abbr[data-original-title] { - cursor: help; - border-bottom: 1px dotted #777; -} -.initialism { - font-size: 90%; - text-transform: uppercase; -} -blockquote { - padding: 10px 20px; - margin: 0 0 20px; - font-size: 17.5px; - border-left: 5px solid #eee; -} -blockquote p:last-child, -blockquote ul:last-child, -blockquote ol:last-child { - margin-bottom: 0; -} -blockquote footer, -blockquote small, -blockquote .small { - display: block; - font-size: 80%; - line-height: 1.42857143; - color: #777; -} -blockquote footer:before, -blockquote small:before, -blockquote .small:before { - content: '\2014 \00A0'; -} -.blockquote-reverse, -blockquote.pull-right { - padding-right: 15px; - padding-left: 0; - text-align: right; - border-right: 5px solid #eee; - border-left: 0; -} -.blockquote-reverse footer:before, -blockquote.pull-right footer:before, -.blockquote-reverse small:before, -blockquote.pull-right small:before, -.blockquote-reverse .small:before, -blockquote.pull-right .small:before { - content: ''; -} -.blockquote-reverse footer:after, -blockquote.pull-right footer:after, -.blockquote-reverse small:after, -blockquote.pull-right small:after, -.blockquote-reverse .small:after, -blockquote.pull-right .small:after { - content: '\00A0 \2014'; -} -address { - margin-bottom: 20px; - font-style: normal; - line-height: 1.42857143; -} -code, -kbd, -pre, -samp { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; -} -code { - padding: 2px 4px; - font-size: 90%; - color: #c7254e; - background-color: #f9f2f4; - border-radius: 4px; -} -kbd { - padding: 2px 4px; - font-size: 90%; - color: #fff; - background-color: #333; - border-radius: 3px; - -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); -} -kbd kbd { - padding: 0; - font-size: 100%; - font-weight: bold; - -webkit-box-shadow: none; - box-shadow: none; -} -pre { - display: block; - padding: 9.5px; - margin: 0 0 10px; - font-size: 13px; - line-height: 1.42857143; - color: #333; - word-break: break-all; - word-wrap: break-word; - background-color: #f5f5f5; - border: 1px solid #ccc; - border-radius: 4px; -} -pre code { - padding: 0; - font-size: inherit; - color: inherit; - white-space: pre-wrap; - background-color: transparent; - border-radius: 0; -} -.pre-scrollable { - max-height: 340px; - overflow-y: scroll; -} -.container { - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; -} -@media (min-width: 768px) { - .container { - width: 750px; - } -} -@media (min-width: 992px) { - .container { - width: 970px; - } -} -@media (min-width: 1200px) { - .container { - width: 1170px; - } -} -.container-fluid { - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; -} -.row { - margin-right: -15px; - margin-left: -15px; -} -.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { - position: relative; - min-height: 1px; - padding-right: 15px; - padding-left: 15px; -} -.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { - float: left; -} -.col-xs-12 { - width: 100%; -} -.col-xs-11 { - width: 91.66666667%; -} -.col-xs-10 { - width: 83.33333333%; -} -.col-xs-9 { - width: 75%; -} -.col-xs-8 { - width: 66.66666667%; -} -.col-xs-7 { - width: 58.33333333%; -} -.col-xs-6 { - width: 50%; -} -.col-xs-5 { - width: 41.66666667%; -} -.col-xs-4 { - width: 33.33333333%; -} -.col-xs-3 { - width: 25%; -} -.col-xs-2 { - width: 16.66666667%; -} -.col-xs-1 { - width: 8.33333333%; -} -.col-xs-pull-12 { - right: 100%; -} -.col-xs-pull-11 { - right: 91.66666667%; -} -.col-xs-pull-10 { - right: 83.33333333%; -} -.col-xs-pull-9 { - right: 75%; -} -.col-xs-pull-8 { - right: 66.66666667%; -} -.col-xs-pull-7 { - right: 58.33333333%; -} -.col-xs-pull-6 { - right: 50%; -} -.col-xs-pull-5 { - right: 41.66666667%; -} -.col-xs-pull-4 { - right: 33.33333333%; -} -.col-xs-pull-3 { - right: 25%; -} -.col-xs-pull-2 { - right: 16.66666667%; -} -.col-xs-pull-1 { - right: 8.33333333%; -} -.col-xs-pull-0 { - right: auto; -} -.col-xs-push-12 { - left: 100%; -} -.col-xs-push-11 { - left: 91.66666667%; -} -.col-xs-push-10 { - left: 83.33333333%; -} -.col-xs-push-9 { - left: 75%; -} -.col-xs-push-8 { - left: 66.66666667%; -} -.col-xs-push-7 { - left: 58.33333333%; -} -.col-xs-push-6 { - left: 50%; -} -.col-xs-push-5 { - left: 41.66666667%; -} -.col-xs-push-4 { - left: 33.33333333%; -} -.col-xs-push-3 { - left: 25%; -} -.col-xs-push-2 { - left: 16.66666667%; -} -.col-xs-push-1 { - left: 8.33333333%; -} -.col-xs-push-0 { - left: auto; -} -.col-xs-offset-12 { - margin-left: 100%; -} -.col-xs-offset-11 { - margin-left: 91.66666667%; -} -.col-xs-offset-10 { - margin-left: 83.33333333%; -} -.col-xs-offset-9 { - margin-left: 75%; -} -.col-xs-offset-8 { - margin-left: 66.66666667%; -} -.col-xs-offset-7 { - margin-left: 58.33333333%; -} -.col-xs-offset-6 { - margin-left: 50%; -} -.col-xs-offset-5 { - margin-left: 41.66666667%; -} -.col-xs-offset-4 { - margin-left: 33.33333333%; -} -.col-xs-offset-3 { - margin-left: 25%; -} -.col-xs-offset-2 { - margin-left: 16.66666667%; -} -.col-xs-offset-1 { - margin-left: 8.33333333%; -} -.col-xs-offset-0 { - margin-left: 0; -} -@media (min-width: 768px) { - .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { - float: left; - } - .col-sm-12 { - width: 100%; - } - .col-sm-11 { - width: 91.66666667%; - } - .col-sm-10 { - width: 83.33333333%; - } - .col-sm-9 { - width: 75%; - } - .col-sm-8 { - width: 66.66666667%; - } - .col-sm-7 { - width: 58.33333333%; - } - .col-sm-6 { - width: 50%; - } - .col-sm-5 { - width: 41.66666667%; - } - .col-sm-4 { - width: 33.33333333%; - } - .col-sm-3 { - width: 25%; - } - .col-sm-2 { - width: 16.66666667%; - } - .col-sm-1 { - width: 8.33333333%; - } - .col-sm-pull-12 { - right: 100%; - } - .col-sm-pull-11 { - right: 91.66666667%; - } - .col-sm-pull-10 { - right: 83.33333333%; - } - .col-sm-pull-9 { - right: 75%; - } - .col-sm-pull-8 { - right: 66.66666667%; - } - .col-sm-pull-7 { - right: 58.33333333%; - } - .col-sm-pull-6 { - right: 50%; - } - .col-sm-pull-5 { - right: 41.66666667%; - } - .col-sm-pull-4 { - right: 33.33333333%; - } - .col-sm-pull-3 { - right: 25%; - } - .col-sm-pull-2 { - right: 16.66666667%; - } - .col-sm-pull-1 { - right: 8.33333333%; - } - .col-sm-pull-0 { - right: auto; - } - .col-sm-push-12 { - left: 100%; - } - .col-sm-push-11 { - left: 91.66666667%; - } - .col-sm-push-10 { - left: 83.33333333%; - } - .col-sm-push-9 { - left: 75%; - } - .col-sm-push-8 { - left: 66.66666667%; - } - .col-sm-push-7 { - left: 58.33333333%; - } - .col-sm-push-6 { - left: 50%; - } - .col-sm-push-5 { - left: 41.66666667%; - } - .col-sm-push-4 { - left: 33.33333333%; - } - .col-sm-push-3 { - left: 25%; - } - .col-sm-push-2 { - left: 16.66666667%; - } - .col-sm-push-1 { - left: 8.33333333%; - } - .col-sm-push-0 { - left: auto; - } - .col-sm-offset-12 { - margin-left: 100%; - } - .col-sm-offset-11 { - margin-left: 91.66666667%; - } - .col-sm-offset-10 { - margin-left: 83.33333333%; - } - .col-sm-offset-9 { - margin-left: 75%; - } - .col-sm-offset-8 { - margin-left: 66.66666667%; - } - .col-sm-offset-7 { - margin-left: 58.33333333%; - } - .col-sm-offset-6 { - margin-left: 50%; - } - .col-sm-offset-5 { - margin-left: 41.66666667%; - } - .col-sm-offset-4 { - margin-left: 33.33333333%; - } - .col-sm-offset-3 { - margin-left: 25%; - } - .col-sm-offset-2 { - margin-left: 16.66666667%; - } - .col-sm-offset-1 { - margin-left: 8.33333333%; - } - .col-sm-offset-0 { - margin-left: 0; - } -} -@media (min-width: 992px) { - .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { - float: left; - } - .col-md-12 { - width: 100%; - } - .col-md-11 { - width: 91.66666667%; - } - .col-md-10 { - width: 83.33333333%; - } - .col-md-9 { - width: 75%; - } - .col-md-8 { - width: 66.66666667%; - } - .col-md-7 { - width: 58.33333333%; - } - .col-md-6 { - width: 50%; - } - .col-md-5 { - width: 41.66666667%; - } - .col-md-4 { - width: 33.33333333%; - } - .col-md-3 { - width: 25%; - } - .col-md-2 { - width: 16.66666667%; - } - .col-md-1 { - width: 8.33333333%; - } - .col-md-pull-12 { - right: 100%; - } - .col-md-pull-11 { - right: 91.66666667%; - } - .col-md-pull-10 { - right: 83.33333333%; - } - .col-md-pull-9 { - right: 75%; - } - .col-md-pull-8 { - right: 66.66666667%; - } - .col-md-pull-7 { - right: 58.33333333%; - } - .col-md-pull-6 { - right: 50%; - } - .col-md-pull-5 { - right: 41.66666667%; - } - .col-md-pull-4 { - right: 33.33333333%; - } - .col-md-pull-3 { - right: 25%; - } - .col-md-pull-2 { - right: 16.66666667%; - } - .col-md-pull-1 { - right: 8.33333333%; - } - .col-md-pull-0 { - right: auto; - } - .col-md-push-12 { - left: 100%; - } - .col-md-push-11 { - left: 91.66666667%; - } - .col-md-push-10 { - left: 83.33333333%; - } - .col-md-push-9 { - left: 75%; - } - .col-md-push-8 { - left: 66.66666667%; - } - .col-md-push-7 { - left: 58.33333333%; - } - .col-md-push-6 { - left: 50%; - } - .col-md-push-5 { - left: 41.66666667%; - } - .col-md-push-4 { - left: 33.33333333%; - } - .col-md-push-3 { - left: 25%; - } - .col-md-push-2 { - left: 16.66666667%; - } - .col-md-push-1 { - left: 8.33333333%; - } - .col-md-push-0 { - left: auto; - } - .col-md-offset-12 { - margin-left: 100%; - } - .col-md-offset-11 { - margin-left: 91.66666667%; - } - .col-md-offset-10 { - margin-left: 83.33333333%; - } - .col-md-offset-9 { - margin-left: 75%; - } - .col-md-offset-8 { - margin-left: 66.66666667%; - } - .col-md-offset-7 { - margin-left: 58.33333333%; - } - .col-md-offset-6 { - margin-left: 50%; - } - .col-md-offset-5 { - margin-left: 41.66666667%; - } - .col-md-offset-4 { - margin-left: 33.33333333%; - } - .col-md-offset-3 { - margin-left: 25%; - } - .col-md-offset-2 { - margin-left: 16.66666667%; - } - .col-md-offset-1 { - margin-left: 8.33333333%; - } - .col-md-offset-0 { - margin-left: 0; - } -} -@media (min-width: 1200px) { - .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { - float: left; - } - .col-lg-12 { - width: 100%; - } - .col-lg-11 { - width: 91.66666667%; - } - .col-lg-10 { - width: 83.33333333%; - } - .col-lg-9 { - width: 75%; - } - .col-lg-8 { - width: 66.66666667%; - } - .col-lg-7 { - width: 58.33333333%; - } - .col-lg-6 { - width: 50%; - } - .col-lg-5 { - width: 41.66666667%; - } - .col-lg-4 { - width: 33.33333333%; - } - .col-lg-3 { - width: 25%; - } - .col-lg-2 { - width: 16.66666667%; - } - .col-lg-1 { - width: 8.33333333%; - } - .col-lg-pull-12 { - right: 100%; - } - .col-lg-pull-11 { - right: 91.66666667%; - } - .col-lg-pull-10 { - right: 83.33333333%; - } - .col-lg-pull-9 { - right: 75%; - } - .col-lg-pull-8 { - right: 66.66666667%; - } - .col-lg-pull-7 { - right: 58.33333333%; - } - .col-lg-pull-6 { - right: 50%; - } - .col-lg-pull-5 { - right: 41.66666667%; - } - .col-lg-pull-4 { - right: 33.33333333%; - } - .col-lg-pull-3 { - right: 25%; - } - .col-lg-pull-2 { - right: 16.66666667%; - } - .col-lg-pull-1 { - right: 8.33333333%; - } - .col-lg-pull-0 { - right: auto; - } - .col-lg-push-12 { - left: 100%; - } - .col-lg-push-11 { - left: 91.66666667%; - } - .col-lg-push-10 { - left: 83.33333333%; - } - .col-lg-push-9 { - left: 75%; - } - .col-lg-push-8 { - left: 66.66666667%; - } - .col-lg-push-7 { - left: 58.33333333%; - } - .col-lg-push-6 { - left: 50%; - } - .col-lg-push-5 { - left: 41.66666667%; - } - .col-lg-push-4 { - left: 33.33333333%; - } - .col-lg-push-3 { - left: 25%; - } - .col-lg-push-2 { - left: 16.66666667%; - } - .col-lg-push-1 { - left: 8.33333333%; - } - .col-lg-push-0 { - left: auto; - } - .col-lg-offset-12 { - margin-left: 100%; - } - .col-lg-offset-11 { - margin-left: 91.66666667%; - } - .col-lg-offset-10 { - margin-left: 83.33333333%; - } - .col-lg-offset-9 { - margin-left: 75%; - } - .col-lg-offset-8 { - margin-left: 66.66666667%; - } - .col-lg-offset-7 { - margin-left: 58.33333333%; - } - .col-lg-offset-6 { - margin-left: 50%; - } - .col-lg-offset-5 { - margin-left: 41.66666667%; - } - .col-lg-offset-4 { - margin-left: 33.33333333%; - } - .col-lg-offset-3 { - margin-left: 25%; - } - .col-lg-offset-2 { - margin-left: 16.66666667%; - } - .col-lg-offset-1 { - margin-left: 8.33333333%; - } - .col-lg-offset-0 { - margin-left: 0; - } -} -table { - background-color: transparent; -} -caption { - padding-top: 8px; - padding-bottom: 8px; - color: #777; - text-align: left; -} -th { - text-align: left; -} -.table { - width: 100%; - max-width: 100%; - margin-bottom: 20px; -} -.table > thead > tr > th, -.table > tbody > tr > th, -.table > tfoot > tr > th, -.table > thead > tr > td, -.table > tbody > tr > td, -.table > tfoot > tr > td { - padding: 8px; - line-height: 1.42857143; - vertical-align: top; - border-top: 1px solid #ddd; -} -.table > thead > tr > th { - vertical-align: bottom; - border-bottom: 2px solid #ddd; -} -.table > caption + thead > tr:first-child > th, -.table > colgroup + thead > tr:first-child > th, -.table > thead:first-child > tr:first-child > th, -.table > caption + thead > tr:first-child > td, -.table > colgroup + thead > tr:first-child > td, -.table > thead:first-child > tr:first-child > td { - border-top: 0; -} -.table > tbody + tbody { - border-top: 2px solid #ddd; -} -.table .table { - background-color: #fff; -} -.table-condensed > thead > tr > th, -.table-condensed > tbody > tr > th, -.table-condensed > tfoot > tr > th, -.table-condensed > thead > tr > td, -.table-condensed > tbody > tr > td, -.table-condensed > tfoot > tr > td { - padding: 5px; -} -.table-bordered { - border: 1px solid #ddd; -} -.table-bordered > thead > tr > th, -.table-bordered > tbody > tr > th, -.table-bordered > tfoot > tr > th, -.table-bordered > thead > tr > td, -.table-bordered > tbody > tr > td, -.table-bordered > tfoot > tr > td { - border: 1px solid #ddd; -} -.table-bordered > thead > tr > th, -.table-bordered > thead > tr > td { - border-bottom-width: 2px; -} -.table-striped > tbody > tr:nth-of-type(odd) { - background-color: #f9f9f9; -} -.table-hover > tbody > tr:hover { - background-color: #f5f5f5; -} -table col[class*="col-"] { - position: static; - display: table-column; - float: none; -} -table td[class*="col-"], -table th[class*="col-"] { - position: static; - display: table-cell; - float: none; -} -.table > thead > tr > td.active, -.table > tbody > tr > td.active, -.table > tfoot > tr > td.active, -.table > thead > tr > th.active, -.table > tbody > tr > th.active, -.table > tfoot > tr > th.active, -.table > thead > tr.active > td, -.table > tbody > tr.active > td, -.table > tfoot > tr.active > td, -.table > thead > tr.active > th, -.table > tbody > tr.active > th, -.table > tfoot > tr.active > th { - background-color: #f5f5f5; -} -.table-hover > tbody > tr > td.active:hover, -.table-hover > tbody > tr > th.active:hover, -.table-hover > tbody > tr.active:hover > td, -.table-hover > tbody > tr:hover > .active, -.table-hover > tbody > tr.active:hover > th { - background-color: #e8e8e8; -} -.table > thead > tr > td.success, -.table > tbody > tr > td.success, -.table > tfoot > tr > td.success, -.table > thead > tr > th.success, -.table > tbody > tr > th.success, -.table > tfoot > tr > th.success, -.table > thead > tr.success > td, -.table > tbody > tr.success > td, -.table > tfoot > tr.success > td, -.table > thead > tr.success > th, -.table > tbody > tr.success > th, -.table > tfoot > tr.success > th { - background-color: #dff0d8; -} -.table-hover > tbody > tr > td.success:hover, -.table-hover > tbody > tr > th.success:hover, -.table-hover > tbody > tr.success:hover > td, -.table-hover > tbody > tr:hover > .success, -.table-hover > tbody > tr.success:hover > th { - background-color: #d0e9c6; -} -.table > thead > tr > td.info, -.table > tbody > tr > td.info, -.table > tfoot > tr > td.info, -.table > thead > tr > th.info, -.table > tbody > tr > th.info, -.table > tfoot > tr > th.info, -.table > thead > tr.info > td, -.table > tbody > tr.info > td, -.table > tfoot > tr.info > td, -.table > thead > tr.info > th, -.table > tbody > tr.info > th, -.table > tfoot > tr.info > th { - background-color: #d9edf7; -} -.table-hover > tbody > tr > td.info:hover, -.table-hover > tbody > tr > th.info:hover, -.table-hover > tbody > tr.info:hover > td, -.table-hover > tbody > tr:hover > .info, -.table-hover > tbody > tr.info:hover > th { - background-color: #c4e3f3; -} -.table > thead > tr > td.warning, -.table > tbody > tr > td.warning, -.table > tfoot > tr > td.warning, -.table > thead > tr > th.warning, -.table > tbody > tr > th.warning, -.table > tfoot > tr > th.warning, -.table > thead > tr.warning > td, -.table > tbody > tr.warning > td, -.table > tfoot > tr.warning > td, -.table > thead > tr.warning > th, -.table > tbody > tr.warning > th, -.table > tfoot > tr.warning > th { - background-color: #fcf8e3; -} -.table-hover > tbody > tr > td.warning:hover, -.table-hover > tbody > tr > th.warning:hover, -.table-hover > tbody > tr.warning:hover > td, -.table-hover > tbody > tr:hover > .warning, -.table-hover > tbody > tr.warning:hover > th { - background-color: #faf2cc; -} -.table > thead > tr > td.danger, -.table > tbody > tr > td.danger, -.table > tfoot > tr > td.danger, -.table > thead > tr > th.danger, -.table > tbody > tr > th.danger, -.table > tfoot > tr > th.danger, -.table > thead > tr.danger > td, -.table > tbody > tr.danger > td, -.table > tfoot > tr.danger > td, -.table > thead > tr.danger > th, -.table > tbody > tr.danger > th, -.table > tfoot > tr.danger > th { - background-color: #f2dede; -} -.table-hover > tbody > tr > td.danger:hover, -.table-hover > tbody > tr > th.danger:hover, -.table-hover > tbody > tr.danger:hover > td, -.table-hover > tbody > tr:hover > .danger, -.table-hover > tbody > tr.danger:hover > th { - background-color: #ebcccc; -} -.table-responsive { - min-height: .01%; - overflow-x: auto; -} -@media screen and (max-width: 767px) { - .table-responsive { - width: 100%; - margin-bottom: 15px; - overflow-y: hidden; - -ms-overflow-style: -ms-autohiding-scrollbar; - border: 1px solid #ddd; - } - .table-responsive > .table { - margin-bottom: 0; - } - .table-responsive > .table > thead > tr > th, - .table-responsive > .table > tbody > tr > th, - .table-responsive > .table > tfoot > tr > th, - .table-responsive > .table > thead > tr > td, - .table-responsive > .table > tbody > tr > td, - .table-responsive > .table > tfoot > tr > td { - white-space: nowrap; - } - .table-responsive > .table-bordered { - border: 0; - } - .table-responsive > .table-bordered > thead > tr > th:first-child, - .table-responsive > .table-bordered > tbody > tr > th:first-child, - .table-responsive > .table-bordered > tfoot > tr > th:first-child, - .table-responsive > .table-bordered > thead > tr > td:first-child, - .table-responsive > .table-bordered > tbody > tr > td:first-child, - .table-responsive > .table-bordered > tfoot > tr > td:first-child { - border-left: 0; - } - .table-responsive > .table-bordered > thead > tr > th:last-child, - .table-responsive > .table-bordered > tbody > tr > th:last-child, - .table-responsive > .table-bordered > tfoot > tr > th:last-child, - .table-responsive > .table-bordered > thead > tr > td:last-child, - .table-responsive > .table-bordered > tbody > tr > td:last-child, - .table-responsive > .table-bordered > tfoot > tr > td:last-child { - border-right: 0; - } - .table-responsive > .table-bordered > tbody > tr:last-child > th, - .table-responsive > .table-bordered > tfoot > tr:last-child > th, - .table-responsive > .table-bordered > tbody > tr:last-child > td, - .table-responsive > .table-bordered > tfoot > tr:last-child > td { - border-bottom: 0; - } -} -fieldset { - min-width: 0; - padding: 0; - margin: 0; - border: 0; -} -legend { - display: block; - width: 100%; - padding: 0; - margin-bottom: 20px; - font-size: 21px; - line-height: inherit; - color: #333; - border: 0; - border-bottom: 1px solid #e5e5e5; -} -label { - display: inline-block; - max-width: 100%; - margin-bottom: 5px; - font-weight: bold; -} -input[type="search"] { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -input[type="radio"], -input[type="checkbox"] { - margin: 4px 0 0; - margin-top: 1px \9; - line-height: normal; -} -input[type="file"] { - display: block; -} -input[type="range"] { - display: block; - width: 100%; -} -select[multiple], -select[size] { - height: auto; -} -input[type="file"]:focus, -input[type="radio"]:focus, -input[type="checkbox"]:focus { - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} -output { - display: block; - padding-top: 7px; - font-size: 14px; - line-height: 1.42857143; - color: #555; -} -.form-control { - display: block; - width: 100%; - height: 34px; - padding: 6px 12px; - font-size: 14px; - line-height: 1.42857143; - color: #555; - background-color: #fff; - background-image: none; - border: 1px solid #ccc; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; - -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; - transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; -} -.form-control:focus { - border-color: #66afe9; - outline: 0; - -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); - box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); -} -.form-control::-moz-placeholder { - color: #999; - opacity: 1; -} -.form-control:-ms-input-placeholder { - color: #999; -} -.form-control::-webkit-input-placeholder { - color: #999; -} -.form-control::-ms-expand { - background-color: transparent; - border: 0; -} -.form-control[disabled], -.form-control[readonly], -fieldset[disabled] .form-control { - background-color: #eee; - opacity: 1; -} -.form-control[disabled], -fieldset[disabled] .form-control { - cursor: not-allowed; -} -textarea.form-control { - height: auto; -} -input[type="search"] { - -webkit-appearance: none; -} -@media screen and (-webkit-min-device-pixel-ratio: 0) { - input[type="date"].form-control, - input[type="time"].form-control, - input[type="datetime-local"].form-control, - input[type="month"].form-control { - line-height: 34px; - } - input[type="date"].input-sm, - input[type="time"].input-sm, - input[type="datetime-local"].input-sm, - input[type="month"].input-sm, - .input-group-sm input[type="date"], - .input-group-sm input[type="time"], - .input-group-sm input[type="datetime-local"], - .input-group-sm input[type="month"] { - line-height: 30px; - } - input[type="date"].input-lg, - input[type="time"].input-lg, - input[type="datetime-local"].input-lg, - input[type="month"].input-lg, - .input-group-lg input[type="date"], - .input-group-lg input[type="time"], - .input-group-lg input[type="datetime-local"], - .input-group-lg input[type="month"] { - line-height: 46px; - } -} -.form-group { - margin-bottom: 15px; -} -.radio, -.checkbox { - position: relative; - display: block; - margin-top: 10px; - margin-bottom: 10px; -} -.radio label, -.checkbox label { - min-height: 20px; - padding-left: 20px; - margin-bottom: 0; - font-weight: normal; - cursor: pointer; -} -.radio input[type="radio"], -.radio-inline input[type="radio"], -.checkbox input[type="checkbox"], -.checkbox-inline input[type="checkbox"] { - position: absolute; - margin-top: 4px \9; - margin-left: -20px; -} -.radio + .radio, -.checkbox + .checkbox { - margin-top: -5px; -} -.radio-inline, -.checkbox-inline { - position: relative; - display: inline-block; - padding-left: 20px; - margin-bottom: 0; - font-weight: normal; - vertical-align: middle; - cursor: pointer; -} -.radio-inline + .radio-inline, -.checkbox-inline + .checkbox-inline { - margin-top: 0; - margin-left: 10px; -} -input[type="radio"][disabled], -input[type="checkbox"][disabled], -input[type="radio"].disabled, -input[type="checkbox"].disabled, -fieldset[disabled] input[type="radio"], -fieldset[disabled] input[type="checkbox"] { - cursor: not-allowed; -} -.radio-inline.disabled, -.checkbox-inline.disabled, -fieldset[disabled] .radio-inline, -fieldset[disabled] .checkbox-inline { - cursor: not-allowed; -} -.radio.disabled label, -.checkbox.disabled label, -fieldset[disabled] .radio label, -fieldset[disabled] .checkbox label { - cursor: not-allowed; -} -.form-control-static { - min-height: 34px; - padding-top: 7px; - padding-bottom: 7px; - margin-bottom: 0; -} -.form-control-static.input-lg, -.form-control-static.input-sm { - padding-right: 0; - padding-left: 0; -} -.input-sm { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -select.input-sm { - height: 30px; - line-height: 30px; -} -textarea.input-sm, -select[multiple].input-sm { - height: auto; -} -.form-group-sm .form-control { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -.form-group-sm select.form-control { - height: 30px; - line-height: 30px; -} -.form-group-sm textarea.form-control, -.form-group-sm select[multiple].form-control { - height: auto; -} -.form-group-sm .form-control-static { - height: 30px; - min-height: 32px; - padding: 6px 10px; - font-size: 12px; - line-height: 1.5; -} -.input-lg { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px; -} -select.input-lg { - height: 46px; - line-height: 46px; -} -textarea.input-lg, -select[multiple].input-lg { - height: auto; -} -.form-group-lg .form-control { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px; -} -.form-group-lg select.form-control { - height: 46px; - line-height: 46px; -} -.form-group-lg textarea.form-control, -.form-group-lg select[multiple].form-control { - height: auto; -} -.form-group-lg .form-control-static { - height: 46px; - min-height: 38px; - padding: 11px 16px; - font-size: 18px; - line-height: 1.3333333; -} -.has-feedback { - position: relative; -} -.has-feedback .form-control { - padding-right: 42.5px; -} -.form-control-feedback { - position: absolute; - top: 0; - right: 0; - z-index: 2; - display: block; - width: 34px; - height: 34px; - line-height: 34px; - text-align: center; - pointer-events: none; -} -.input-lg + .form-control-feedback, -.input-group-lg + .form-control-feedback, -.form-group-lg .form-control + .form-control-feedback { - width: 46px; - height: 46px; - line-height: 46px; -} -.input-sm + .form-control-feedback, -.input-group-sm + .form-control-feedback, -.form-group-sm .form-control + .form-control-feedback { - width: 30px; - height: 30px; - line-height: 30px; -} -.has-success .help-block, -.has-success .control-label, -.has-success .radio, -.has-success .checkbox, -.has-success .radio-inline, -.has-success .checkbox-inline, -.has-success.radio label, -.has-success.checkbox label, -.has-success.radio-inline label, -.has-success.checkbox-inline label { - color: #3c763d; -} -.has-success .form-control { - border-color: #3c763d; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -} -.has-success .form-control:focus { - border-color: #2b542c; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; -} -.has-success .input-group-addon { - color: #3c763d; - background-color: #dff0d8; - border-color: #3c763d; -} -.has-success .form-control-feedback { - color: #3c763d; -} -.has-warning .help-block, -.has-warning .control-label, -.has-warning .radio, -.has-warning .checkbox, -.has-warning .radio-inline, -.has-warning .checkbox-inline, -.has-warning.radio label, -.has-warning.checkbox label, -.has-warning.radio-inline label, -.has-warning.checkbox-inline label { - color: #8a6d3b; -} -.has-warning .form-control { - border-color: #8a6d3b; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -} -.has-warning .form-control:focus { - border-color: #66512c; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; -} -.has-warning .input-group-addon { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #8a6d3b; -} -.has-warning .form-control-feedback { - color: #8a6d3b; -} -.has-error .help-block, -.has-error .control-label, -.has-error .radio, -.has-error .checkbox, -.has-error .radio-inline, -.has-error .checkbox-inline, -.has-error.radio label, -.has-error.checkbox label, -.has-error.radio-inline label, -.has-error.checkbox-inline label { - color: #a94442; -} -.has-error .form-control { - border-color: #a94442; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -} -.has-error .form-control:focus { - border-color: #843534; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; -} -.has-error .input-group-addon { - color: #a94442; - background-color: #f2dede; - border-color: #a94442; -} -.has-error .form-control-feedback { - color: #a94442; -} -.has-feedback label ~ .form-control-feedback { - top: 25px; -} -.has-feedback label.sr-only ~ .form-control-feedback { - top: 0; -} -.help-block { - display: block; - margin-top: 5px; - margin-bottom: 10px; - color: #737373; -} -@media (min-width: 768px) { - .form-inline .form-group { - display: inline-block; - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .form-control { - display: inline-block; - width: auto; - vertical-align: middle; - } - .form-inline .form-control-static { - display: inline-block; - } - .form-inline .input-group { - display: inline-table; - vertical-align: middle; - } - .form-inline .input-group .input-group-addon, - .form-inline .input-group .input-group-btn, - .form-inline .input-group .form-control { - width: auto; - } - .form-inline .input-group > .form-control { - width: 100%; - } - .form-inline .control-label { - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .radio, - .form-inline .checkbox { - display: inline-block; - margin-top: 0; - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .radio label, - .form-inline .checkbox label { - padding-left: 0; - } - .form-inline .radio input[type="radio"], - .form-inline .checkbox input[type="checkbox"] { - position: relative; - margin-left: 0; - } - .form-inline .has-feedback .form-control-feedback { - top: 0; - } -} -.form-horizontal .radio, -.form-horizontal .checkbox, -.form-horizontal .radio-inline, -.form-horizontal .checkbox-inline { - padding-top: 7px; - margin-top: 0; - margin-bottom: 0; -} -.form-horizontal .radio, -.form-horizontal .checkbox { - min-height: 27px; -} -.form-horizontal .form-group { - margin-right: -15px; - margin-left: -15px; -} -@media (min-width: 768px) { - .form-horizontal .control-label { - padding-top: 7px; - margin-bottom: 0; - text-align: right; - } -} -.form-horizontal .has-feedback .form-control-feedback { - right: 15px; -} -@media (min-width: 768px) { - .form-horizontal .form-group-lg .control-label { - padding-top: 11px; - font-size: 18px; - } -} -@media (min-width: 768px) { - .form-horizontal .form-group-sm .control-label { - padding-top: 6px; - font-size: 12px; - } -} -.btn { - display: inline-block; - padding: 6px 12px; - margin-bottom: 0; - font-size: 14px; - font-weight: normal; - line-height: 1.42857143; - text-align: center; - white-space: nowrap; - vertical-align: middle; - -ms-touch-action: manipulation; - touch-action: manipulation; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-image: none; - border: 1px solid transparent; - border-radius: 4px; -} -.btn:focus, -.btn:active:focus, -.btn.active:focus, -.btn.focus, -.btn:active.focus, -.btn.active.focus { - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} -.btn:hover, -.btn:focus, -.btn.focus { - color: #333; - text-decoration: none; -} -.btn:active, -.btn.active { - background-image: none; - outline: 0; - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); -} -.btn.disabled, -.btn[disabled], -fieldset[disabled] .btn { - cursor: not-allowed; - filter: alpha(opacity=65); - -webkit-box-shadow: none; - box-shadow: none; - opacity: .65; -} -a.btn.disabled, -fieldset[disabled] a.btn { - pointer-events: none; -} -.btn-default { - color: #333; - background-color: #fff; - border-color: #ccc; -} -.btn-default:focus, -.btn-default.focus { - color: #333; - background-color: #e6e6e6; - border-color: #8c8c8c; -} -.btn-default:hover { - color: #333; - background-color: #e6e6e6; - border-color: #adadad; -} -.btn-default:active, -.btn-default.active, -.open > .dropdown-toggle.btn-default { - color: #333; - background-color: #e6e6e6; - border-color: #adadad; -} -.btn-default:active:hover, -.btn-default.active:hover, -.open > .dropdown-toggle.btn-default:hover, -.btn-default:active:focus, -.btn-default.active:focus, -.open > .dropdown-toggle.btn-default:focus, -.btn-default:active.focus, -.btn-default.active.focus, -.open > .dropdown-toggle.btn-default.focus { - color: #333; - background-color: #d4d4d4; - border-color: #8c8c8c; -} -.btn-default:active, -.btn-default.active, -.open > .dropdown-toggle.btn-default { - background-image: none; -} -.btn-default.disabled:hover, -.btn-default[disabled]:hover, -fieldset[disabled] .btn-default:hover, -.btn-default.disabled:focus, -.btn-default[disabled]:focus, -fieldset[disabled] .btn-default:focus, -.btn-default.disabled.focus, -.btn-default[disabled].focus, -fieldset[disabled] .btn-default.focus { - background-color: #fff; - border-color: #ccc; -} -.btn-default .badge { - color: #fff; - background-color: #333; -} -.btn-primary { - color: #fff; - background-color: #337ab7; - border-color: #2e6da4; -} -.btn-primary:focus, -.btn-primary.focus { - color: #fff; - background-color: #286090; - border-color: #122b40; -} -.btn-primary:hover { - color: #fff; - background-color: #286090; - border-color: #204d74; -} -.btn-primary:active, -.btn-primary.active, -.open > .dropdown-toggle.btn-primary { - color: #fff; - background-color: #286090; - border-color: #204d74; -} -.btn-primary:active:hover, -.btn-primary.active:hover, -.open > .dropdown-toggle.btn-primary:hover, -.btn-primary:active:focus, -.btn-primary.active:focus, -.open > .dropdown-toggle.btn-primary:focus, -.btn-primary:active.focus, -.btn-primary.active.focus, -.open > .dropdown-toggle.btn-primary.focus { - color: #fff; - background-color: #204d74; - border-color: #122b40; -} -.btn-primary:active, -.btn-primary.active, -.open > .dropdown-toggle.btn-primary { - background-image: none; -} -.btn-primary.disabled:hover, -.btn-primary[disabled]:hover, -fieldset[disabled] .btn-primary:hover, -.btn-primary.disabled:focus, -.btn-primary[disabled]:focus, -fieldset[disabled] .btn-primary:focus, -.btn-primary.disabled.focus, -.btn-primary[disabled].focus, -fieldset[disabled] .btn-primary.focus { - background-color: #337ab7; - border-color: #2e6da4; -} -.btn-primary .badge { - color: #337ab7; - background-color: #fff; -} -.btn-success { - color: #fff; - background-color: #5cb85c; - border-color: #4cae4c; -} -.btn-success:focus, -.btn-success.focus { - color: #fff; - background-color: #449d44; - border-color: #255625; -} -.btn-success:hover { - color: #fff; - background-color: #449d44; - border-color: #398439; -} -.btn-success:active, -.btn-success.active, -.open > .dropdown-toggle.btn-success { - color: #fff; - background-color: #449d44; - border-color: #398439; -} -.btn-success:active:hover, -.btn-success.active:hover, -.open > .dropdown-toggle.btn-success:hover, -.btn-success:active:focus, -.btn-success.active:focus, -.open > .dropdown-toggle.btn-success:focus, -.btn-success:active.focus, -.btn-success.active.focus, -.open > .dropdown-toggle.btn-success.focus { - color: #fff; - background-color: #398439; - border-color: #255625; -} -.btn-success:active, -.btn-success.active, -.open > .dropdown-toggle.btn-success { - background-image: none; -} -.btn-success.disabled:hover, -.btn-success[disabled]:hover, -fieldset[disabled] .btn-success:hover, -.btn-success.disabled:focus, -.btn-success[disabled]:focus, -fieldset[disabled] .btn-success:focus, -.btn-success.disabled.focus, -.btn-success[disabled].focus, -fieldset[disabled] .btn-success.focus { - background-color: #5cb85c; - border-color: #4cae4c; -} -.btn-success .badge { - color: #5cb85c; - background-color: #fff; -} -.btn-info { - color: #fff; - background-color: #5bc0de; - border-color: #46b8da; -} -.btn-info:focus, -.btn-info.focus { - color: #fff; - background-color: #31b0d5; - border-color: #1b6d85; -} -.btn-info:hover { - color: #fff; - background-color: #31b0d5; - border-color: #269abc; -} -.btn-info:active, -.btn-info.active, -.open > .dropdown-toggle.btn-info { - color: #fff; - background-color: #31b0d5; - border-color: #269abc; -} -.btn-info:active:hover, -.btn-info.active:hover, -.open > .dropdown-toggle.btn-info:hover, -.btn-info:active:focus, -.btn-info.active:focus, -.open > .dropdown-toggle.btn-info:focus, -.btn-info:active.focus, -.btn-info.active.focus, -.open > .dropdown-toggle.btn-info.focus { - color: #fff; - background-color: #269abc; - border-color: #1b6d85; -} -.btn-info:active, -.btn-info.active, -.open > .dropdown-toggle.btn-info { - background-image: none; -} -.btn-info.disabled:hover, -.btn-info[disabled]:hover, -fieldset[disabled] .btn-info:hover, -.btn-info.disabled:focus, -.btn-info[disabled]:focus, -fieldset[disabled] .btn-info:focus, -.btn-info.disabled.focus, -.btn-info[disabled].focus, -fieldset[disabled] .btn-info.focus { - background-color: #5bc0de; - border-color: #46b8da; -} -.btn-info .badge { - color: #5bc0de; - background-color: #fff; -} -.btn-warning { - color: #fff; - background-color: #f0ad4e; - border-color: #eea236; -} -.btn-warning:focus, -.btn-warning.focus { - color: #fff; - background-color: #ec971f; - border-color: #985f0d; -} -.btn-warning:hover { - color: #fff; - background-color: #ec971f; - border-color: #d58512; -} -.btn-warning:active, -.btn-warning.active, -.open > .dropdown-toggle.btn-warning { - color: #fff; - background-color: #ec971f; - border-color: #d58512; -} -.btn-warning:active:hover, -.btn-warning.active:hover, -.open > .dropdown-toggle.btn-warning:hover, -.btn-warning:active:focus, -.btn-warning.active:focus, -.open > .dropdown-toggle.btn-warning:focus, -.btn-warning:active.focus, -.btn-warning.active.focus, -.open > .dropdown-toggle.btn-warning.focus { - color: #fff; - background-color: #d58512; - border-color: #985f0d; -} -.btn-warning:active, -.btn-warning.active, -.open > .dropdown-toggle.btn-warning { - background-image: none; -} -.btn-warning.disabled:hover, -.btn-warning[disabled]:hover, -fieldset[disabled] .btn-warning:hover, -.btn-warning.disabled:focus, -.btn-warning[disabled]:focus, -fieldset[disabled] .btn-warning:focus, -.btn-warning.disabled.focus, -.btn-warning[disabled].focus, -fieldset[disabled] .btn-warning.focus { - background-color: #f0ad4e; - border-color: #eea236; -} -.btn-warning .badge { - color: #f0ad4e; - background-color: #fff; -} -.btn-danger { - color: #fff; - background-color: #d9534f; - border-color: #d43f3a; -} -.btn-danger:focus, -.btn-danger.focus { - color: #fff; - background-color: #c9302c; - border-color: #761c19; -} -.btn-danger:hover { - color: #fff; - background-color: #c9302c; - border-color: #ac2925; -} -.btn-danger:active, -.btn-danger.active, -.open > .dropdown-toggle.btn-danger { - color: #fff; - background-color: #c9302c; - border-color: #ac2925; -} -.btn-danger:active:hover, -.btn-danger.active:hover, -.open > .dropdown-toggle.btn-danger:hover, -.btn-danger:active:focus, -.btn-danger.active:focus, -.open > .dropdown-toggle.btn-danger:focus, -.btn-danger:active.focus, -.btn-danger.active.focus, -.open > .dropdown-toggle.btn-danger.focus { - color: #fff; - background-color: #ac2925; - border-color: #761c19; -} -.btn-danger:active, -.btn-danger.active, -.open > .dropdown-toggle.btn-danger { - background-image: none; -} -.btn-danger.disabled:hover, -.btn-danger[disabled]:hover, -fieldset[disabled] .btn-danger:hover, -.btn-danger.disabled:focus, -.btn-danger[disabled]:focus, -fieldset[disabled] .btn-danger:focus, -.btn-danger.disabled.focus, -.btn-danger[disabled].focus, -fieldset[disabled] .btn-danger.focus { - background-color: #d9534f; - border-color: #d43f3a; -} -.btn-danger .badge { - color: #d9534f; - background-color: #fff; -} -.btn-link { - font-weight: normal; - color: #337ab7; - border-radius: 0; -} -.btn-link, -.btn-link:active, -.btn-link.active, -.btn-link[disabled], -fieldset[disabled] .btn-link { - background-color: transparent; - -webkit-box-shadow: none; - box-shadow: none; -} -.btn-link, -.btn-link:hover, -.btn-link:focus, -.btn-link:active { - border-color: transparent; -} -.btn-link:hover, -.btn-link:focus { - color: #23527c; - text-decoration: underline; - background-color: transparent; -} -.btn-link[disabled]:hover, -fieldset[disabled] .btn-link:hover, -.btn-link[disabled]:focus, -fieldset[disabled] .btn-link:focus { - color: #777; - text-decoration: none; -} -.btn-lg, -.btn-group-lg > .btn { - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px; -} -.btn-sm, -.btn-group-sm > .btn { - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -.btn-xs, -.btn-group-xs > .btn { - padding: 1px 5px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -.btn-block { - display: block; - width: 100%; -} -.btn-block + .btn-block { - margin-top: 5px; -} -input[type="submit"].btn-block, -input[type="reset"].btn-block, -input[type="button"].btn-block { - width: 100%; -} -.fade { - opacity: 0; - -webkit-transition: opacity .15s linear; - -o-transition: opacity .15s linear; - transition: opacity .15s linear; -} -.fade.in { - opacity: 1; -} -.collapse { - display: none; -} -.collapse.in { - display: block; -} -tr.collapse.in { - display: table-row; -} -tbody.collapse.in { - display: table-row-group; -} -.collapsing { - position: relative; - height: 0; - overflow: hidden; - -webkit-transition-timing-function: ease; - -o-transition-timing-function: ease; - transition-timing-function: ease; - -webkit-transition-duration: .35s; - -o-transition-duration: .35s; - transition-duration: .35s; - -webkit-transition-property: height, visibility; - -o-transition-property: height, visibility; - transition-property: height, visibility; -} -.caret { - display: inline-block; - width: 0; - height: 0; - margin-left: 2px; - vertical-align: middle; - border-top: 4px dashed; - border-top: 4px solid \9; - border-right: 4px solid transparent; - border-left: 4px solid transparent; -} -.dropup, -.dropdown { - position: relative; -} -.dropdown-toggle:focus { - outline: 0; -} -.dropdown-menu { - position: absolute; - top: 100%; - left: 0; - z-index: 1000; - display: none; - float: left; - min-width: 160px; - padding: 5px 0; - margin: 2px 0 0; - font-size: 14px; - text-align: left; - list-style: none; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, .15); - border-radius: 4px; - -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); - box-shadow: 0 6px 12px rgba(0, 0, 0, .175); -} -.dropdown-menu.pull-right { - right: 0; - left: auto; -} -.dropdown-menu .divider { - height: 1px; - margin: 9px 0; - overflow: hidden; - background-color: #e5e5e5; -} -.dropdown-menu > li > a { - display: block; - padding: 3px 20px; - clear: both; - font-weight: normal; - line-height: 1.42857143; - color: #333; - white-space: nowrap; -} -.dropdown-menu > li > a:hover, -.dropdown-menu > li > a:focus { - color: #262626; - text-decoration: none; - background-color: #f5f5f5; -} -.dropdown-menu > .active > a, -.dropdown-menu > .active > a:hover, -.dropdown-menu > .active > a:focus { - color: #fff; - text-decoration: none; - background-color: #337ab7; - outline: 0; -} -.dropdown-menu > .disabled > a, -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { - color: #777; -} -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { - text-decoration: none; - cursor: not-allowed; - background-color: transparent; - background-image: none; - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); -} -.open > .dropdown-menu { - display: block; -} -.open > a { - outline: 0; -} -.dropdown-menu-right { - right: 0; - left: auto; -} -.dropdown-menu-left { - right: auto; - left: 0; -} -.dropdown-header { - display: block; - padding: 3px 20px; - font-size: 12px; - line-height: 1.42857143; - color: #777; - white-space: nowrap; -} -.dropdown-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 990; -} -.pull-right > .dropdown-menu { - right: 0; - left: auto; -} -.dropup .caret, -.navbar-fixed-bottom .dropdown .caret { - content: ""; - border-top: 0; - border-bottom: 4px dashed; - border-bottom: 4px solid \9; -} -.dropup .dropdown-menu, -.navbar-fixed-bottom .dropdown .dropdown-menu { - top: auto; - bottom: 100%; - margin-bottom: 2px; -} -@media (min-width: 768px) { - .navbar-right .dropdown-menu { - right: 0; - left: auto; - } - .navbar-right .dropdown-menu-left { - right: auto; - left: 0; - } -} -.btn-group, -.btn-group-vertical { - position: relative; - display: inline-block; - vertical-align: middle; -} -.btn-group > .btn, -.btn-group-vertical > .btn { - position: relative; - float: left; -} -.btn-group > .btn:hover, -.btn-group-vertical > .btn:hover, -.btn-group > .btn:focus, -.btn-group-vertical > .btn:focus, -.btn-group > .btn:active, -.btn-group-vertical > .btn:active, -.btn-group > .btn.active, -.btn-group-vertical > .btn.active { - z-index: 2; -} -.btn-group .btn + .btn, -.btn-group .btn + .btn-group, -.btn-group .btn-group + .btn, -.btn-group .btn-group + .btn-group { - margin-left: -1px; -} -.btn-toolbar { - margin-left: -5px; -} -.btn-toolbar .btn, -.btn-toolbar .btn-group, -.btn-toolbar .input-group { - float: left; -} -.btn-toolbar > .btn, -.btn-toolbar > .btn-group, -.btn-toolbar > .input-group { - margin-left: 5px; -} -.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { - border-radius: 0; -} -.btn-group > .btn:first-child { - margin-left: 0; -} -.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.btn-group > .btn:last-child:not(:first-child), -.btn-group > .dropdown-toggle:not(:first-child) { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} -.btn-group > .btn-group { - float: left; -} -.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { - border-radius: 0; -} -.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, -.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} -.btn-group .dropdown-toggle:active, -.btn-group.open .dropdown-toggle { - outline: 0; -} -.btn-group > .btn + .dropdown-toggle { - padding-right: 8px; - padding-left: 8px; -} -.btn-group > .btn-lg + .dropdown-toggle { - padding-right: 12px; - padding-left: 12px; -} -.btn-group.open .dropdown-toggle { - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); -} -.btn-group.open .dropdown-toggle.btn-link { - -webkit-box-shadow: none; - box-shadow: none; -} -.btn .caret { - margin-left: 0; -} -.btn-lg .caret { - border-width: 5px 5px 0; - border-bottom-width: 0; -} -.dropup .btn-lg .caret { - border-width: 0 5px 5px; -} -.btn-group-vertical > .btn, -.btn-group-vertical > .btn-group, -.btn-group-vertical > .btn-group > .btn { - display: block; - float: none; - width: 100%; - max-width: 100%; -} -.btn-group-vertical > .btn-group > .btn { - float: none; -} -.btn-group-vertical > .btn + .btn, -.btn-group-vertical > .btn + .btn-group, -.btn-group-vertical > .btn-group + .btn, -.btn-group-vertical > .btn-group + .btn-group { - margin-top: -1px; - margin-left: 0; -} -.btn-group-vertical > .btn:not(:first-child):not(:last-child) { - border-radius: 0; -} -.btn-group-vertical > .btn:first-child:not(:last-child) { - border-top-left-radius: 4px; - border-top-right-radius: 4px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -.btn-group-vertical > .btn:last-child:not(:first-child) { - border-top-left-radius: 0; - border-top-right-radius: 0; - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; -} -.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { - border-radius: 0; -} -.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, -.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { - border-top-left-radius: 0; - border-top-right-radius: 0; -} -.btn-group-justified { - display: table; - width: 100%; - table-layout: fixed; - border-collapse: separate; -} -.btn-group-justified > .btn, -.btn-group-justified > .btn-group { - display: table-cell; - float: none; - width: 1%; -} -.btn-group-justified > .btn-group .btn { - width: 100%; -} -.btn-group-justified > .btn-group .dropdown-menu { - left: auto; -} -[data-toggle="buttons"] > .btn input[type="radio"], -[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], -[data-toggle="buttons"] > .btn input[type="checkbox"], -[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { - position: absolute; - clip: rect(0, 0, 0, 0); - pointer-events: none; -} -.input-group { - position: relative; - display: table; - border-collapse: separate; -} -.input-group[class*="col-"] { - float: none; - padding-right: 0; - padding-left: 0; -} -.input-group .form-control { - position: relative; - z-index: 2; - float: left; - width: 100%; - margin-bottom: 0; -} -.input-group .form-control:focus { - z-index: 3; -} -.input-group-lg > .form-control, -.input-group-lg > .input-group-addon, -.input-group-lg > .input-group-btn > .btn { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px; -} -select.input-group-lg > .form-control, -select.input-group-lg > .input-group-addon, -select.input-group-lg > .input-group-btn > .btn { - height: 46px; - line-height: 46px; -} -textarea.input-group-lg > .form-control, -textarea.input-group-lg > .input-group-addon, -textarea.input-group-lg > .input-group-btn > .btn, -select[multiple].input-group-lg > .form-control, -select[multiple].input-group-lg > .input-group-addon, -select[multiple].input-group-lg > .input-group-btn > .btn { - height: auto; -} -.input-group-sm > .form-control, -.input-group-sm > .input-group-addon, -.input-group-sm > .input-group-btn > .btn { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -select.input-group-sm > .form-control, -select.input-group-sm > .input-group-addon, -select.input-group-sm > .input-group-btn > .btn { - height: 30px; - line-height: 30px; -} -textarea.input-group-sm > .form-control, -textarea.input-group-sm > .input-group-addon, -textarea.input-group-sm > .input-group-btn > .btn, -select[multiple].input-group-sm > .form-control, -select[multiple].input-group-sm > .input-group-addon, -select[multiple].input-group-sm > .input-group-btn > .btn { - height: auto; -} -.input-group-addon, -.input-group-btn, -.input-group .form-control { - display: table-cell; -} -.input-group-addon:not(:first-child):not(:last-child), -.input-group-btn:not(:first-child):not(:last-child), -.input-group .form-control:not(:first-child):not(:last-child) { - border-radius: 0; -} -.input-group-addon, -.input-group-btn { - width: 1%; - white-space: nowrap; - vertical-align: middle; -} -.input-group-addon { - padding: 6px 12px; - font-size: 14px; - font-weight: normal; - line-height: 1; - color: #555; - text-align: center; - background-color: #eee; - border: 1px solid #ccc; - border-radius: 4px; -} -.input-group-addon.input-sm { - padding: 5px 10px; - font-size: 12px; - border-radius: 3px; -} -.input-group-addon.input-lg { - padding: 10px 16px; - font-size: 18px; - border-radius: 6px; -} -.input-group-addon input[type="radio"], -.input-group-addon input[type="checkbox"] { - margin-top: 0; -} -.input-group .form-control:first-child, -.input-group-addon:first-child, -.input-group-btn:first-child > .btn, -.input-group-btn:first-child > .btn-group > .btn, -.input-group-btn:first-child > .dropdown-toggle, -.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), -.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.input-group-addon:first-child { - border-right: 0; -} -.input-group .form-control:last-child, -.input-group-addon:last-child, -.input-group-btn:last-child > .btn, -.input-group-btn:last-child > .btn-group > .btn, -.input-group-btn:last-child > .dropdown-toggle, -.input-group-btn:first-child > .btn:not(:first-child), -.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} -.input-group-addon:last-child { - border-left: 0; -} -.input-group-btn { - position: relative; - font-size: 0; - white-space: nowrap; -} -.input-group-btn > .btn { - position: relative; -} -.input-group-btn > .btn + .btn { - margin-left: -1px; -} -.input-group-btn > .btn:hover, -.input-group-btn > .btn:focus, -.input-group-btn > .btn:active { - z-index: 2; -} -.input-group-btn:first-child > .btn, -.input-group-btn:first-child > .btn-group { - margin-right: -1px; -} -.input-group-btn:last-child > .btn, -.input-group-btn:last-child > .btn-group { - z-index: 2; - margin-left: -1px; -} -.nav { - padding-left: 0; - margin-bottom: 0; - list-style: none; -} -.nav > li { - position: relative; - display: block; -} -.nav > li > a { - position: relative; - display: block; - padding: 10px 15px; -} -.nav > li > a:hover, -.nav > li > a:focus { - text-decoration: none; - background-color: #eee; -} -.nav > li.disabled > a { - color: #777; -} -.nav > li.disabled > a:hover, -.nav > li.disabled > a:focus { - color: #777; - text-decoration: none; - cursor: not-allowed; - background-color: transparent; -} -.nav .open > a, -.nav .open > a:hover, -.nav .open > a:focus { - background-color: #eee; - border-color: #337ab7; -} -.nav .nav-divider { - height: 1px; - margin: 9px 0; - overflow: hidden; - background-color: #e5e5e5; -} -.nav > li > a > img { - max-width: none; -} -.nav-tabs { - border-bottom: 1px solid #ddd; -} -.nav-tabs > li { - float: left; - margin-bottom: -1px; -} -.nav-tabs > li > a { - margin-right: 2px; - line-height: 1.42857143; - border: 1px solid transparent; - border-radius: 4px 4px 0 0; -} -.nav-tabs > li > a:hover { - border-color: #eee #eee #ddd; -} -.nav-tabs > li.active > a, -.nav-tabs > li.active > a:hover, -.nav-tabs > li.active > a:focus { - color: #555; - cursor: default; - background-color: #fff; - border: 1px solid #ddd; - border-bottom-color: transparent; -} -.nav-tabs.nav-justified { - width: 100%; - border-bottom: 0; -} -.nav-tabs.nav-justified > li { - float: none; -} -.nav-tabs.nav-justified > li > a { - margin-bottom: 5px; - text-align: center; -} -.nav-tabs.nav-justified > .dropdown .dropdown-menu { - top: auto; - left: auto; -} -@media (min-width: 768px) { - .nav-tabs.nav-justified > li { - display: table-cell; - width: 1%; - } - .nav-tabs.nav-justified > li > a { - margin-bottom: 0; - } -} -.nav-tabs.nav-justified > li > a { - margin-right: 0; - border-radius: 4px; -} -.nav-tabs.nav-justified > .active > a, -.nav-tabs.nav-justified > .active > a:hover, -.nav-tabs.nav-justified > .active > a:focus { - border: 1px solid #ddd; -} -@media (min-width: 768px) { - .nav-tabs.nav-justified > li > a { - border-bottom: 1px solid #ddd; - border-radius: 4px 4px 0 0; - } - .nav-tabs.nav-justified > .active > a, - .nav-tabs.nav-justified > .active > a:hover, - .nav-tabs.nav-justified > .active > a:focus { - border-bottom-color: #fff; - } -} -.nav-pills > li { - float: left; -} -.nav-pills > li > a { - border-radius: 4px; -} -.nav-pills > li + li { - margin-left: 2px; -} -.nav-pills > li.active > a, -.nav-pills > li.active > a:hover, -.nav-pills > li.active > a:focus { - color: #fff; - background-color: #337ab7; -} -.nav-stacked > li { - float: none; -} -.nav-stacked > li + li { - margin-top: 2px; - margin-left: 0; -} -.nav-justified { - width: 100%; -} -.nav-justified > li { - float: none; -} -.nav-justified > li > a { - margin-bottom: 5px; - text-align: center; -} -.nav-justified > .dropdown .dropdown-menu { - top: auto; - left: auto; -} -@media (min-width: 768px) { - .nav-justified > li { - display: table-cell; - width: 1%; - } - .nav-justified > li > a { - margin-bottom: 0; - } -} -.nav-tabs-justified { - border-bottom: 0; -} -.nav-tabs-justified > li > a { - margin-right: 0; - border-radius: 4px; -} -.nav-tabs-justified > .active > a, -.nav-tabs-justified > .active > a:hover, -.nav-tabs-justified > .active > a:focus { - border: 1px solid #ddd; -} -@media (min-width: 768px) { - .nav-tabs-justified > li > a { - border-bottom: 1px solid #ddd; - border-radius: 4px 4px 0 0; - } - .nav-tabs-justified > .active > a, - .nav-tabs-justified > .active > a:hover, - .nav-tabs-justified > .active > a:focus { - border-bottom-color: #fff; - } -} -.tab-content > .tab-pane { - display: none; -} -.tab-content > .active { - display: block; -} -.nav-tabs .dropdown-menu { - margin-top: -1px; - border-top-left-radius: 0; - border-top-right-radius: 0; -} -.navbar { - position: relative; - min-height: 50px; - margin-bottom: 20px; - border: 1px solid transparent; -} -@media (min-width: 768px) { - .navbar { - border-radius: 4px; - } -} -@media (min-width: 768px) { - .navbar-header { - float: left; - } -} -.navbar-collapse { - padding-right: 15px; - padding-left: 15px; - overflow-x: visible; - -webkit-overflow-scrolling: touch; - border-top: 1px solid transparent; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); -} -.navbar-collapse.in { - overflow-y: auto; -} -@media (min-width: 768px) { - .navbar-collapse { - width: auto; - border-top: 0; - -webkit-box-shadow: none; - box-shadow: none; - } - .navbar-collapse.collapse { - display: block !important; - height: auto !important; - padding-bottom: 0; - overflow: visible !important; - } - .navbar-collapse.in { - overflow-y: visible; - } - .navbar-fixed-top .navbar-collapse, - .navbar-static-top .navbar-collapse, - .navbar-fixed-bottom .navbar-collapse { - padding-right: 0; - padding-left: 0; - } -} -.navbar-fixed-top .navbar-collapse, -.navbar-fixed-bottom .navbar-collapse { - max-height: 340px; -} -@media (max-device-width: 480px) and (orientation: landscape) { - .navbar-fixed-top .navbar-collapse, - .navbar-fixed-bottom .navbar-collapse { - max-height: 200px; - } -} -.container > .navbar-header, -.container-fluid > .navbar-header, -.container > .navbar-collapse, -.container-fluid > .navbar-collapse { - margin-right: -15px; - margin-left: -15px; -} -@media (min-width: 768px) { - .container > .navbar-header, - .container-fluid > .navbar-header, - .container > .navbar-collapse, - .container-fluid > .navbar-collapse { - margin-right: 0; - margin-left: 0; - } -} -.navbar-static-top { - z-index: 1000; - border-width: 0 0 1px; -} -@media (min-width: 768px) { - .navbar-static-top { - border-radius: 0; - } -} -.navbar-fixed-top, -.navbar-fixed-bottom { - position: fixed; - right: 0; - left: 0; - z-index: 1030; -} -@media (min-width: 768px) { - .navbar-fixed-top, - .navbar-fixed-bottom { - border-radius: 0; - } -} -.navbar-fixed-top { - top: 0; - border-width: 0 0 1px; -} -.navbar-fixed-bottom { - bottom: 0; - margin-bottom: 0; - border-width: 1px 0 0; -} -.navbar-brand { - float: left; - height: 50px; - padding: 15px 15px; - font-size: 18px; - line-height: 20px; -} -.navbar-brand:hover, -.navbar-brand:focus { - text-decoration: none; -} -.navbar-brand > img { - display: block; -} -@media (min-width: 768px) { - .navbar > .container .navbar-brand, - .navbar > .container-fluid .navbar-brand { - margin-left: -15px; - } -} -.navbar-toggle { - position: relative; - float: right; - padding: 9px 10px; - margin-top: 8px; - margin-right: 15px; - margin-bottom: 8px; - background-color: transparent; - background-image: none; - border: 1px solid transparent; - border-radius: 4px; -} -.navbar-toggle:focus { - outline: 0; -} -.navbar-toggle .icon-bar { - display: block; - width: 22px; - height: 2px; - border-radius: 1px; -} -.navbar-toggle .icon-bar + .icon-bar { - margin-top: 4px; -} -@media (min-width: 768px) { - .navbar-toggle { - display: none; - } -} -.navbar-nav { - margin: 7.5px -15px; -} -.navbar-nav > li > a { - padding-top: 10px; - padding-bottom: 10px; - line-height: 20px; -} -@media (max-width: 767px) { - .navbar-nav .open .dropdown-menu { - position: static; - float: none; - width: auto; - margin-top: 0; - background-color: transparent; - border: 0; - -webkit-box-shadow: none; - box-shadow: none; - } - .navbar-nav .open .dropdown-menu > li > a, - .navbar-nav .open .dropdown-menu .dropdown-header { - padding: 5px 15px 5px 25px; - } - .navbar-nav .open .dropdown-menu > li > a { - line-height: 20px; - } - .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-nav .open .dropdown-menu > li > a:focus { - background-image: none; - } -} -@media (min-width: 768px) { - .navbar-nav { - float: left; - margin: 0; - } - .navbar-nav > li { - float: left; - } - .navbar-nav > li > a { - padding-top: 15px; - padding-bottom: 15px; - } -} -.navbar-form { - padding: 10px 15px; - margin-top: 8px; - margin-right: -15px; - margin-bottom: 8px; - margin-left: -15px; - border-top: 1px solid transparent; - border-bottom: 1px solid transparent; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); -} -@media (min-width: 768px) { - .navbar-form .form-group { - display: inline-block; - margin-bottom: 0; - vertical-align: middle; - } - .navbar-form .form-control { - display: inline-block; - width: auto; - vertical-align: middle; - } - .navbar-form .form-control-static { - display: inline-block; - } - .navbar-form .input-group { - display: inline-table; - vertical-align: middle; - } - .navbar-form .input-group .input-group-addon, - .navbar-form .input-group .input-group-btn, - .navbar-form .input-group .form-control { - width: auto; - } - .navbar-form .input-group > .form-control { - width: 100%; - } - .navbar-form .control-label { - margin-bottom: 0; - vertical-align: middle; - } - .navbar-form .radio, - .navbar-form .checkbox { - display: inline-block; - margin-top: 0; - margin-bottom: 0; - vertical-align: middle; - } - .navbar-form .radio label, - .navbar-form .checkbox label { - padding-left: 0; - } - .navbar-form .radio input[type="radio"], - .navbar-form .checkbox input[type="checkbox"] { - position: relative; - margin-left: 0; - } - .navbar-form .has-feedback .form-control-feedback { - top: 0; - } -} -@media (max-width: 767px) { - .navbar-form .form-group { - margin-bottom: 5px; - } - .navbar-form .form-group:last-child { - margin-bottom: 0; - } -} -@media (min-width: 768px) { - .navbar-form { - width: auto; - padding-top: 0; - padding-bottom: 0; - margin-right: 0; - margin-left: 0; - border: 0; - -webkit-box-shadow: none; - box-shadow: none; - } -} -.navbar-nav > li > .dropdown-menu { - margin-top: 0; - border-top-left-radius: 0; - border-top-right-radius: 0; -} -.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { - margin-bottom: 0; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -.navbar-btn { - margin-top: 8px; - margin-bottom: 8px; -} -.navbar-btn.btn-sm { - margin-top: 10px; - margin-bottom: 10px; -} -.navbar-btn.btn-xs { - margin-top: 14px; - margin-bottom: 14px; -} -.navbar-text { - margin-top: 15px; - margin-bottom: 15px; -} -@media (min-width: 768px) { - .navbar-text { - float: left; - margin-right: 15px; - margin-left: 15px; - } -} -@media (min-width: 768px) { - .navbar-left { - float: left !important; - } - .navbar-right { - float: right !important; - margin-right: -15px; - } - .navbar-right ~ .navbar-right { - margin-right: 0; - } -} -.navbar-default { - background-color: #f8f8f8; - border-color: #e7e7e7; -} -.navbar-default .navbar-brand { - color: #777; -} -.navbar-default .navbar-brand:hover, -.navbar-default .navbar-brand:focus { - color: #5e5e5e; - background-color: transparent; -} -.navbar-default .navbar-text { - color: #777; -} -.navbar-default .navbar-nav > li > a { - color: #777; -} -.navbar-default .navbar-nav > li > a:hover, -.navbar-default .navbar-nav > li > a:focus { - color: #333; - background-color: transparent; -} -.navbar-default .navbar-nav > .active > a, -.navbar-default .navbar-nav > .active > a:hover, -.navbar-default .navbar-nav > .active > a:focus { - color: #555; - background-color: #e7e7e7; -} -.navbar-default .navbar-nav > .disabled > a, -.navbar-default .navbar-nav > .disabled > a:hover, -.navbar-default .navbar-nav > .disabled > a:focus { - color: #ccc; - background-color: transparent; -} -.navbar-default .navbar-toggle { - border-color: #ddd; -} -.navbar-default .navbar-toggle:hover, -.navbar-default .navbar-toggle:focus { - background-color: #ddd; -} -.navbar-default .navbar-toggle .icon-bar { - background-color: #888; -} -.navbar-default .navbar-collapse, -.navbar-default .navbar-form { - border-color: #e7e7e7; -} -.navbar-default .navbar-nav > .open > a, -.navbar-default .navbar-nav > .open > a:hover, -.navbar-default .navbar-nav > .open > a:focus { - color: #555; - background-color: #e7e7e7; -} -@media (max-width: 767px) { - .navbar-default .navbar-nav .open .dropdown-menu > li > a { - color: #777; - } - .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { - color: #333; - background-color: transparent; - } - .navbar-default .navbar-nav .open .dropdown-menu > .active > a, - .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { - color: #555; - background-color: #e7e7e7; - } - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { - color: #ccc; - background-color: transparent; - } -} -.navbar-default .navbar-link { - color: #777; -} -.navbar-default .navbar-link:hover { - color: #333; -} -.navbar-default .btn-link { - color: #777; -} -.navbar-default .btn-link:hover, -.navbar-default .btn-link:focus { - color: #333; -} -.navbar-default .btn-link[disabled]:hover, -fieldset[disabled] .navbar-default .btn-link:hover, -.navbar-default .btn-link[disabled]:focus, -fieldset[disabled] .navbar-default .btn-link:focus { - color: #ccc; -} -.navbar-inverse { - background-color: #222; - border-color: #080808; -} -.navbar-inverse .navbar-brand { - color: #9d9d9d; -} -.navbar-inverse .navbar-brand:hover, -.navbar-inverse .navbar-brand:focus { - color: #fff; - background-color: transparent; -} -.navbar-inverse .navbar-text { - color: #9d9d9d; -} -.navbar-inverse .navbar-nav > li > a { - color: #9d9d9d; -} -.navbar-inverse .navbar-nav > li > a:hover, -.navbar-inverse .navbar-nav > li > a:focus { - color: #fff; - background-color: transparent; -} -.navbar-inverse .navbar-nav > .active > a, -.navbar-inverse .navbar-nav > .active > a:hover, -.navbar-inverse .navbar-nav > .active > a:focus { - color: #fff; - background-color: #080808; -} -.navbar-inverse .navbar-nav > .disabled > a, -.navbar-inverse .navbar-nav > .disabled > a:hover, -.navbar-inverse .navbar-nav > .disabled > a:focus { - color: #444; - background-color: transparent; -} -.navbar-inverse .navbar-toggle { - border-color: #333; -} -.navbar-inverse .navbar-toggle:hover, -.navbar-inverse .navbar-toggle:focus { - background-color: #333; -} -.navbar-inverse .navbar-toggle .icon-bar { - background-color: #fff; -} -.navbar-inverse .navbar-collapse, -.navbar-inverse .navbar-form { - border-color: #101010; -} -.navbar-inverse .navbar-nav > .open > a, -.navbar-inverse .navbar-nav > .open > a:hover, -.navbar-inverse .navbar-nav > .open > a:focus { - color: #fff; - background-color: #080808; -} -@media (max-width: 767px) { - .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { - border-color: #080808; - } - .navbar-inverse .navbar-nav .open .dropdown-menu .divider { - background-color: #080808; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { - color: #9d9d9d; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { - color: #fff; - background-color: transparent; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { - color: #fff; - background-color: #080808; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { - color: #444; - background-color: transparent; - } -} -.navbar-inverse .navbar-link { - color: #9d9d9d; -} -.navbar-inverse .navbar-link:hover { - color: #fff; -} -.navbar-inverse .btn-link { - color: #9d9d9d; -} -.navbar-inverse .btn-link:hover, -.navbar-inverse .btn-link:focus { - color: #fff; -} -.navbar-inverse .btn-link[disabled]:hover, -fieldset[disabled] .navbar-inverse .btn-link:hover, -.navbar-inverse .btn-link[disabled]:focus, -fieldset[disabled] .navbar-inverse .btn-link:focus { - color: #444; -} -.breadcrumb { - padding: 8px 15px; - margin-bottom: 20px; - list-style: none; - background-color: #f5f5f5; - border-radius: 4px; -} -.breadcrumb > li { - display: inline-block; -} -.breadcrumb > li + li:before { - padding: 0 5px; - color: #ccc; - content: "/\00a0"; -} -.breadcrumb > .active { - color: #777; -} -.pagination { - display: inline-block; - padding-left: 0; - margin: 20px 0; - border-radius: 4px; -} -.pagination > li { - display: inline; -} -.pagination > li > a, -.pagination > li > span { - position: relative; - float: left; - padding: 6px 12px; - margin-left: -1px; - line-height: 1.42857143; - color: #337ab7; - text-decoration: none; - background-color: #fff; - border: 1px solid #ddd; -} -.pagination > li:first-child > a, -.pagination > li:first-child > span { - margin-left: 0; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; -} -.pagination > li:last-child > a, -.pagination > li:last-child > span { - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; -} -.pagination > li > a:hover, -.pagination > li > span:hover, -.pagination > li > a:focus, -.pagination > li > span:focus { - z-index: 2; - color: #23527c; - background-color: #eee; - border-color: #ddd; -} -.pagination > .active > a, -.pagination > .active > span, -.pagination > .active > a:hover, -.pagination > .active > span:hover, -.pagination > .active > a:focus, -.pagination > .active > span:focus { - z-index: 3; - color: #fff; - cursor: default; - background-color: #337ab7; - border-color: #337ab7; -} -.pagination > .disabled > span, -.pagination > .disabled > span:hover, -.pagination > .disabled > span:focus, -.pagination > .disabled > a, -.pagination > .disabled > a:hover, -.pagination > .disabled > a:focus { - color: #777; - cursor: not-allowed; - background-color: #fff; - border-color: #ddd; -} -.pagination-lg > li > a, -.pagination-lg > li > span { - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; -} -.pagination-lg > li:first-child > a, -.pagination-lg > li:first-child > span { - border-top-left-radius: 6px; - border-bottom-left-radius: 6px; -} -.pagination-lg > li:last-child > a, -.pagination-lg > li:last-child > span { - border-top-right-radius: 6px; - border-bottom-right-radius: 6px; -} -.pagination-sm > li > a, -.pagination-sm > li > span { - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; -} -.pagination-sm > li:first-child > a, -.pagination-sm > li:first-child > span { - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; -} -.pagination-sm > li:last-child > a, -.pagination-sm > li:last-child > span { - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; -} -.pager { - padding-left: 0; - margin: 20px 0; - text-align: center; - list-style: none; -} -.pager li { - display: inline; -} -.pager li > a, -.pager li > span { - display: inline-block; - padding: 5px 14px; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 15px; -} -.pager li > a:hover, -.pager li > a:focus { - text-decoration: none; - background-color: #eee; -} -.pager .next > a, -.pager .next > span { - float: right; -} -.pager .previous > a, -.pager .previous > span { - float: left; -} -.pager .disabled > a, -.pager .disabled > a:hover, -.pager .disabled > a:focus, -.pager .disabled > span { - color: #777; - cursor: not-allowed; - background-color: #fff; -} -.label { - display: inline; - padding: .2em .6em .3em; - font-size: 75%; - font-weight: bold; - line-height: 1; - color: #fff; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - border-radius: .25em; -} -a.label:hover, -a.label:focus { - color: #fff; - text-decoration: none; - cursor: pointer; -} -.label:empty { - display: none; -} -.btn .label { - position: relative; - top: -1px; -} -.label-default { - background-color: #777; -} -.label-default[href]:hover, -.label-default[href]:focus { - background-color: #5e5e5e; -} -.label-primary { - background-color: #337ab7; -} -.label-primary[href]:hover, -.label-primary[href]:focus { - background-color: #286090; -} -.label-success { - background-color: #5cb85c; -} -.label-success[href]:hover, -.label-success[href]:focus { - background-color: #449d44; -} -.label-info { - background-color: #5bc0de; -} -.label-info[href]:hover, -.label-info[href]:focus { - background-color: #31b0d5; -} -.label-warning { - background-color: #f0ad4e; -} -.label-warning[href]:hover, -.label-warning[href]:focus { - background-color: #ec971f; -} -.label-danger { - background-color: #d9534f; -} -.label-danger[href]:hover, -.label-danger[href]:focus { - background-color: #c9302c; -} -.badge { - display: inline-block; - min-width: 10px; - padding: 3px 7px; - font-size: 12px; - font-weight: bold; - line-height: 1; - color: #fff; - text-align: center; - white-space: nowrap; - vertical-align: middle; - background-color: #777; - border-radius: 10px; -} -.badge:empty { - display: none; -} -.btn .badge { - position: relative; - top: -1px; -} -.btn-xs .badge, -.btn-group-xs > .btn .badge { - top: 0; - padding: 1px 5px; -} -a.badge:hover, -a.badge:focus { - color: #fff; - text-decoration: none; - cursor: pointer; -} -.list-group-item.active > .badge, -.nav-pills > .active > a > .badge { - color: #337ab7; - background-color: #fff; -} -.list-group-item > .badge { - float: right; -} -.list-group-item > .badge + .badge { - margin-right: 5px; -} -.nav-pills > li > a > .badge { - margin-left: 3px; -} -.jumbotron { - padding-top: 30px; - padding-bottom: 30px; - margin-bottom: 30px; - color: inherit; - background-color: #eee; -} -.jumbotron h1, -.jumbotron .h1 { - color: inherit; -} -.jumbotron p { - margin-bottom: 15px; - font-size: 21px; - font-weight: 200; -} -.jumbotron > hr { - border-top-color: #d5d5d5; -} -.container .jumbotron, -.container-fluid .jumbotron { - padding-right: 15px; - padding-left: 15px; - border-radius: 6px; -} -.jumbotron .container { - max-width: 100%; -} -@media screen and (min-width: 768px) { - .jumbotron { - padding-top: 48px; - padding-bottom: 48px; - } - .container .jumbotron, - .container-fluid .jumbotron { - padding-right: 60px; - padding-left: 60px; - } - .jumbotron h1, - .jumbotron .h1 { - font-size: 63px; - } -} -.thumbnail { - display: block; - padding: 4px; - margin-bottom: 20px; - line-height: 1.42857143; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 4px; - -webkit-transition: border .2s ease-in-out; - -o-transition: border .2s ease-in-out; - transition: border .2s ease-in-out; -} -.thumbnail > img, -.thumbnail a > img { - margin-right: auto; - margin-left: auto; -} -a.thumbnail:hover, -a.thumbnail:focus, -a.thumbnail.active { - border-color: #337ab7; -} -.thumbnail .caption { - padding: 9px; - color: #333; -} -.alert { - padding: 15px; - margin-bottom: 20px; - border: 1px solid transparent; - border-radius: 4px; -} -.alert h4 { - margin-top: 0; - color: inherit; -} -.alert .alert-link { - font-weight: bold; -} -.alert > p, -.alert > ul { - margin-bottom: 0; -} -.alert > p + p { - margin-top: 5px; -} -.alert-dismissable, -.alert-dismissible { - padding-right: 35px; -} -.alert-dismissable .close, -.alert-dismissible .close { - position: relative; - top: -2px; - right: -21px; - color: inherit; -} -.alert-success { - color: #3c763d; - background-color: #dff0d8; - border-color: #d6e9c6; -} -.alert-success hr { - border-top-color: #c9e2b3; -} -.alert-success .alert-link { - color: #2b542c; -} -.alert-info { - color: #31708f; - background-color: #d9edf7; - border-color: #bce8f1; -} -.alert-info hr { - border-top-color: #a6e1ec; -} -.alert-info .alert-link { - color: #245269; -} -.alert-warning { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #faebcc; -} -.alert-warning hr { - border-top-color: #f7e1b5; -} -.alert-warning .alert-link { - color: #66512c; -} -.alert-danger { - color: #a94442; - background-color: #f2dede; - border-color: #ebccd1; -} -.alert-danger hr { - border-top-color: #e4b9c0; -} -.alert-danger .alert-link { - color: #843534; -} -@-webkit-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} -@-o-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} -@keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} -.progress { - height: 20px; - margin-bottom: 20px; - overflow: hidden; - background-color: #f5f5f5; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); -} -.progress-bar { - float: left; - width: 0; - height: 100%; - font-size: 12px; - line-height: 20px; - color: #fff; - text-align: center; - background-color: #337ab7; - -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); - -webkit-transition: width .6s ease; - -o-transition: width .6s ease; - transition: width .6s ease; -} -.progress-striped .progress-bar, -.progress-bar-striped { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - -webkit-background-size: 40px 40px; - background-size: 40px 40px; -} -.progress.active .progress-bar, -.progress-bar.active { - -webkit-animation: progress-bar-stripes 2s linear infinite; - -o-animation: progress-bar-stripes 2s linear infinite; - animation: progress-bar-stripes 2s linear infinite; -} -.progress-bar-success { - background-color: #5cb85c; -} -.progress-striped .progress-bar-success { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} -.progress-bar-info { - background-color: #5bc0de; -} -.progress-striped .progress-bar-info { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} -.progress-bar-warning { - background-color: #f0ad4e; -} -.progress-striped .progress-bar-warning { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} -.progress-bar-danger { - background-color: #d9534f; -} -.progress-striped .progress-bar-danger { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} -.media { - margin-top: 15px; -} -.media:first-child { - margin-top: 0; -} -.media, -.media-body { - overflow: hidden; - zoom: 1; -} -.media-body { - width: 10000px; -} -.media-object { - display: block; -} -.media-object.img-thumbnail { - max-width: none; -} -.media-right, -.media > .pull-right { - padding-left: 10px; -} -.media-left, -.media > .pull-left { - padding-right: 10px; -} -.media-left, -.media-right, -.media-body { - display: table-cell; - vertical-align: top; -} -.media-middle { - vertical-align: middle; -} -.media-bottom { - vertical-align: bottom; -} -.media-heading { - margin-top: 0; - margin-bottom: 5px; -} -.media-list { - padding-left: 0; - list-style: none; -} -.list-group { - padding-left: 0; - margin-bottom: 20px; -} -.list-group-item { - position: relative; - display: block; - padding: 10px 15px; - margin-bottom: -1px; - background-color: #fff; - border: 1px solid #ddd; -} -.list-group-item:first-child { - border-top-left-radius: 4px; - border-top-right-radius: 4px; -} -.list-group-item:last-child { - margin-bottom: 0; - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; -} -a.list-group-item, -button.list-group-item { - color: #555; -} -a.list-group-item .list-group-item-heading, -button.list-group-item .list-group-item-heading { - color: #333; -} -a.list-group-item:hover, -button.list-group-item:hover, -a.list-group-item:focus, -button.list-group-item:focus { - color: #555; - text-decoration: none; - background-color: #f5f5f5; -} -button.list-group-item { - width: 100%; - text-align: left; -} -.list-group-item.disabled, -.list-group-item.disabled:hover, -.list-group-item.disabled:focus { - color: #777; - cursor: not-allowed; - background-color: #eee; -} -.list-group-item.disabled .list-group-item-heading, -.list-group-item.disabled:hover .list-group-item-heading, -.list-group-item.disabled:focus .list-group-item-heading { - color: inherit; -} -.list-group-item.disabled .list-group-item-text, -.list-group-item.disabled:hover .list-group-item-text, -.list-group-item.disabled:focus .list-group-item-text { - color: #777; -} -.list-group-item.active, -.list-group-item.active:hover, -.list-group-item.active:focus { - z-index: 2; - color: #fff; - background-color: #337ab7; - border-color: #337ab7; -} -.list-group-item.active .list-group-item-heading, -.list-group-item.active:hover .list-group-item-heading, -.list-group-item.active:focus .list-group-item-heading, -.list-group-item.active .list-group-item-heading > small, -.list-group-item.active:hover .list-group-item-heading > small, -.list-group-item.active:focus .list-group-item-heading > small, -.list-group-item.active .list-group-item-heading > .small, -.list-group-item.active:hover .list-group-item-heading > .small, -.list-group-item.active:focus .list-group-item-heading > .small { - color: inherit; -} -.list-group-item.active .list-group-item-text, -.list-group-item.active:hover .list-group-item-text, -.list-group-item.active:focus .list-group-item-text { - color: #c7ddef; -} -.list-group-item-success { - color: #3c763d; - background-color: #dff0d8; -} -a.list-group-item-success, -button.list-group-item-success { - color: #3c763d; -} -a.list-group-item-success .list-group-item-heading, -button.list-group-item-success .list-group-item-heading { - color: inherit; -} -a.list-group-item-success:hover, -button.list-group-item-success:hover, -a.list-group-item-success:focus, -button.list-group-item-success:focus { - color: #3c763d; - background-color: #d0e9c6; -} -a.list-group-item-success.active, -button.list-group-item-success.active, -a.list-group-item-success.active:hover, -button.list-group-item-success.active:hover, -a.list-group-item-success.active:focus, -button.list-group-item-success.active:focus { - color: #fff; - background-color: #3c763d; - border-color: #3c763d; -} -.list-group-item-info { - color: #31708f; - background-color: #d9edf7; -} -a.list-group-item-info, -button.list-group-item-info { - color: #31708f; -} -a.list-group-item-info .list-group-item-heading, -button.list-group-item-info .list-group-item-heading { - color: inherit; -} -a.list-group-item-info:hover, -button.list-group-item-info:hover, -a.list-group-item-info:focus, -button.list-group-item-info:focus { - color: #31708f; - background-color: #c4e3f3; -} -a.list-group-item-info.active, -button.list-group-item-info.active, -a.list-group-item-info.active:hover, -button.list-group-item-info.active:hover, -a.list-group-item-info.active:focus, -button.list-group-item-info.active:focus { - color: #fff; - background-color: #31708f; - border-color: #31708f; -} -.list-group-item-warning { - color: #8a6d3b; - background-color: #fcf8e3; -} -a.list-group-item-warning, -button.list-group-item-warning { - color: #8a6d3b; -} -a.list-group-item-warning .list-group-item-heading, -button.list-group-item-warning .list-group-item-heading { - color: inherit; -} -a.list-group-item-warning:hover, -button.list-group-item-warning:hover, -a.list-group-item-warning:focus, -button.list-group-item-warning:focus { - color: #8a6d3b; - background-color: #faf2cc; -} -a.list-group-item-warning.active, -button.list-group-item-warning.active, -a.list-group-item-warning.active:hover, -button.list-group-item-warning.active:hover, -a.list-group-item-warning.active:focus, -button.list-group-item-warning.active:focus { - color: #fff; - background-color: #8a6d3b; - border-color: #8a6d3b; -} -.list-group-item-danger { - color: #a94442; - background-color: #f2dede; -} -a.list-group-item-danger, -button.list-group-item-danger { - color: #a94442; -} -a.list-group-item-danger .list-group-item-heading, -button.list-group-item-danger .list-group-item-heading { - color: inherit; -} -a.list-group-item-danger:hover, -button.list-group-item-danger:hover, -a.list-group-item-danger:focus, -button.list-group-item-danger:focus { - color: #a94442; - background-color: #ebcccc; -} -a.list-group-item-danger.active, -button.list-group-item-danger.active, -a.list-group-item-danger.active:hover, -button.list-group-item-danger.active:hover, -a.list-group-item-danger.active:focus, -button.list-group-item-danger.active:focus { - color: #fff; - background-color: #a94442; - border-color: #a94442; -} -.list-group-item-heading { - margin-top: 0; - margin-bottom: 5px; -} -.list-group-item-text { - margin-bottom: 0; - line-height: 1.3; -} -.panel { - margin-bottom: 20px; - background-color: #fff; - border: 1px solid transparent; - border-radius: 4px; - -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); - box-shadow: 0 1px 1px rgba(0, 0, 0, .05); -} -.panel-body { - padding: 15px; -} -.panel-heading { - padding: 10px 15px; - border-bottom: 1px solid transparent; - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} -.panel-heading > .dropdown .dropdown-toggle { - color: inherit; -} -.panel-title { - margin-top: 0; - margin-bottom: 0; - font-size: 16px; - color: inherit; -} -.panel-title > a, -.panel-title > small, -.panel-title > .small, -.panel-title > small > a, -.panel-title > .small > a { - color: inherit; -} -.panel-footer { - padding: 10px 15px; - background-color: #f5f5f5; - border-top: 1px solid #ddd; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} -.panel > .list-group, -.panel > .panel-collapse > .list-group { - margin-bottom: 0; -} -.panel > .list-group .list-group-item, -.panel > .panel-collapse > .list-group .list-group-item { - border-width: 1px 0; - border-radius: 0; -} -.panel > .list-group:first-child .list-group-item:first-child, -.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { - border-top: 0; - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} -.panel > .list-group:last-child .list-group-item:last-child, -.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { - border-bottom: 0; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} -.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { - border-top-left-radius: 0; - border-top-right-radius: 0; -} -.panel-heading + .list-group .list-group-item:first-child { - border-top-width: 0; -} -.list-group + .panel-footer { - border-top-width: 0; -} -.panel > .table, -.panel > .table-responsive > .table, -.panel > .panel-collapse > .table { - margin-bottom: 0; -} -.panel > .table caption, -.panel > .table-responsive > .table caption, -.panel > .panel-collapse > .table caption { - padding-right: 15px; - padding-left: 15px; -} -.panel > .table:first-child, -.panel > .table-responsive:first-child > .table:first-child { - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} -.panel > .table:first-child > thead:first-child > tr:first-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, -.panel > .table:first-child > tbody:first-child > tr:first-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} -.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, -.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, -.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, -.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { - border-top-left-radius: 3px; -} -.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, -.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, -.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, -.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { - border-top-right-radius: 3px; -} -.panel > .table:last-child, -.panel > .table-responsive:last-child > .table:last-child { - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} -.panel > .table:last-child > tbody:last-child > tr:last-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} -.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, -.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { - border-bottom-left-radius: 3px; -} -.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, -.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { - border-bottom-right-radius: 3px; -} -.panel > .panel-body + .table, -.panel > .panel-body + .table-responsive, -.panel > .table + .panel-body, -.panel > .table-responsive + .panel-body { - border-top: 1px solid #ddd; -} -.panel > .table > tbody:first-child > tr:first-child th, -.panel > .table > tbody:first-child > tr:first-child td { - border-top: 0; -} -.panel > .table-bordered, -.panel > .table-responsive > .table-bordered { - border: 0; -} -.panel > .table-bordered > thead > tr > th:first-child, -.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, -.panel > .table-bordered > tbody > tr > th:first-child, -.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, -.panel > .table-bordered > tfoot > tr > th:first-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, -.panel > .table-bordered > thead > tr > td:first-child, -.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, -.panel > .table-bordered > tbody > tr > td:first-child, -.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, -.panel > .table-bordered > tfoot > tr > td:first-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { - border-left: 0; -} -.panel > .table-bordered > thead > tr > th:last-child, -.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, -.panel > .table-bordered > tbody > tr > th:last-child, -.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, -.panel > .table-bordered > tfoot > tr > th:last-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, -.panel > .table-bordered > thead > tr > td:last-child, -.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, -.panel > .table-bordered > tbody > tr > td:last-child, -.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, -.panel > .table-bordered > tfoot > tr > td:last-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { - border-right: 0; -} -.panel > .table-bordered > thead > tr:first-child > td, -.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, -.panel > .table-bordered > tbody > tr:first-child > td, -.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, -.panel > .table-bordered > thead > tr:first-child > th, -.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, -.panel > .table-bordered > tbody > tr:first-child > th, -.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { - border-bottom: 0; -} -.panel > .table-bordered > tbody > tr:last-child > td, -.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, -.panel > .table-bordered > tfoot > tr:last-child > td, -.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, -.panel > .table-bordered > tbody > tr:last-child > th, -.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, -.panel > .table-bordered > tfoot > tr:last-child > th, -.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { - border-bottom: 0; -} -.panel > .table-responsive { - margin-bottom: 0; - border: 0; -} -.panel-group { - margin-bottom: 20px; -} -.panel-group .panel { - margin-bottom: 0; - border-radius: 4px; -} -.panel-group .panel + .panel { - margin-top: 5px; -} -.panel-group .panel-heading { - border-bottom: 0; -} -.panel-group .panel-heading + .panel-collapse > .panel-body, -.panel-group .panel-heading + .panel-collapse > .list-group { - border-top: 1px solid #ddd; -} -.panel-group .panel-footer { - border-top: 0; -} -.panel-group .panel-footer + .panel-collapse .panel-body { - border-bottom: 1px solid #ddd; -} -.panel-default { - border-color: #ddd; -} -.panel-default > .panel-heading { - color: #333; - background-color: #f5f5f5; - border-color: #ddd; -} -.panel-default > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #ddd; -} -.panel-default > .panel-heading .badge { - color: #f5f5f5; - background-color: #333; -} -.panel-default > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #ddd; -} -.panel-primary { - border-color: #337ab7; -} -.panel-primary > .panel-heading { - color: #fff; - background-color: #337ab7; - border-color: #337ab7; -} -.panel-primary > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #337ab7; -} -.panel-primary > .panel-heading .badge { - color: #337ab7; - background-color: #fff; -} -.panel-primary > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #337ab7; -} -.panel-success { - border-color: #d6e9c6; -} -.panel-success > .panel-heading { - color: #3c763d; - background-color: #dff0d8; - border-color: #d6e9c6; -} -.panel-success > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #d6e9c6; -} -.panel-success > .panel-heading .badge { - color: #dff0d8; - background-color: #3c763d; -} -.panel-success > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #d6e9c6; -} -.panel-info { - border-color: #bce8f1; -} -.panel-info > .panel-heading { - color: #31708f; - background-color: #d9edf7; - border-color: #bce8f1; -} -.panel-info > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #bce8f1; -} -.panel-info > .panel-heading .badge { - color: #d9edf7; - background-color: #31708f; -} -.panel-info > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #bce8f1; -} -.panel-warning { - border-color: #faebcc; -} -.panel-warning > .panel-heading { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #faebcc; -} -.panel-warning > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #faebcc; -} -.panel-warning > .panel-heading .badge { - color: #fcf8e3; - background-color: #8a6d3b; -} -.panel-warning > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #faebcc; -} -.panel-danger { - border-color: #ebccd1; -} -.panel-danger > .panel-heading { - color: #a94442; - background-color: #f2dede; - border-color: #ebccd1; -} -.panel-danger > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #ebccd1; -} -.panel-danger > .panel-heading .badge { - color: #f2dede; - background-color: #a94442; -} -.panel-danger > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #ebccd1; -} -.embed-responsive { - position: relative; - display: block; - height: 0; - padding: 0; - overflow: hidden; -} -.embed-responsive .embed-responsive-item, -.embed-responsive iframe, -.embed-responsive embed, -.embed-responsive object, -.embed-responsive video { - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - border: 0; -} -.embed-responsive-16by9 { - padding-bottom: 56.25%; -} -.embed-responsive-4by3 { - padding-bottom: 75%; -} -.well { - min-height: 20px; - padding: 19px; - margin-bottom: 20px; - background-color: #f5f5f5; - border: 1px solid #e3e3e3; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); -} -.well blockquote { - border-color: #ddd; - border-color: rgba(0, 0, 0, .15); -} -.well-lg { - padding: 24px; - border-radius: 6px; -} -.well-sm { - padding: 9px; - border-radius: 3px; -} -.close { - float: right; - font-size: 21px; - font-weight: bold; - line-height: 1; - color: #000; - text-shadow: 0 1px 0 #fff; - filter: alpha(opacity=20); - opacity: .2; -} -.close:hover, -.close:focus { - color: #000; - text-decoration: none; - cursor: pointer; - filter: alpha(opacity=50); - opacity: .5; -} -button.close { - -webkit-appearance: none; - padding: 0; - cursor: pointer; - background: transparent; - border: 0; -} -.modal-open { - overflow: hidden; -} -.modal { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1050; - display: none; - overflow: hidden; - -webkit-overflow-scrolling: touch; - outline: 0; -} -.modal.fade .modal-dialog { - -webkit-transition: -webkit-transform .3s ease-out; - -o-transition: -o-transform .3s ease-out; - transition: transform .3s ease-out; - -webkit-transform: translate(0, -25%); - -ms-transform: translate(0, -25%); - -o-transform: translate(0, -25%); - transform: translate(0, -25%); -} -.modal.in .modal-dialog { - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -o-transform: translate(0, 0); - transform: translate(0, 0); -} -.modal-open .modal { - overflow-x: hidden; - overflow-y: auto; -} -.modal-dialog { - position: relative; - width: auto; - margin: 10px; -} -.modal-content { - position: relative; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #999; - border: 1px solid rgba(0, 0, 0, .2); - border-radius: 6px; - outline: 0; - -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); - box-shadow: 0 3px 9px rgba(0, 0, 0, .5); -} -.modal-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1040; - background-color: #000; -} -.modal-backdrop.fade { - filter: alpha(opacity=0); - opacity: 0; -} -.modal-backdrop.in { - filter: alpha(opacity=50); - opacity: .5; -} -.modal-header { - padding: 15px; - border-bottom: 1px solid #e5e5e5; -} -.modal-header .close { - margin-top: -2px; -} -.modal-title { - margin: 0; - line-height: 1.42857143; -} -.modal-body { - position: relative; - padding: 15px; -} -.modal-footer { - padding: 15px; - text-align: right; - border-top: 1px solid #e5e5e5; -} -.modal-footer .btn + .btn { - margin-bottom: 0; - margin-left: 5px; -} -.modal-footer .btn-group .btn + .btn { - margin-left: -1px; -} -.modal-footer .btn-block + .btn-block { - margin-left: 0; -} -.modal-scrollbar-measure { - position: absolute; - top: -9999px; - width: 50px; - height: 50px; - overflow: scroll; -} -@media (min-width: 768px) { - .modal-dialog { - width: 600px; - margin: 30px auto; - } - .modal-content { - -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); - box-shadow: 0 5px 15px rgba(0, 0, 0, .5); - } - .modal-sm { - width: 300px; - } -} -@media (min-width: 992px) { - .modal-lg { - width: 900px; - } -} -.tooltip { - position: absolute; - z-index: 1070; - display: block; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 12px; - font-style: normal; - font-weight: normal; - line-height: 1.42857143; - text-align: left; - text-align: start; - text-decoration: none; - text-shadow: none; - text-transform: none; - letter-spacing: normal; - word-break: normal; - word-spacing: normal; - word-wrap: normal; - white-space: normal; - filter: alpha(opacity=0); - opacity: 0; - - line-break: auto; -} -.tooltip.in { - filter: alpha(opacity=90); - opacity: .9; -} -.tooltip.top { - padding: 5px 0; - margin-top: -3px; -} -.tooltip.right { - padding: 0 5px; - margin-left: 3px; -} -.tooltip.bottom { - padding: 5px 0; - margin-top: 3px; -} -.tooltip.left { - padding: 0 5px; - margin-left: -3px; -} -.tooltip-inner { - max-width: 200px; - padding: 3px 8px; - color: #fff; - text-align: center; - background-color: #000; - border-radius: 4px; -} -.tooltip-arrow { - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} -.tooltip.top .tooltip-arrow { - bottom: 0; - left: 50%; - margin-left: -5px; - border-width: 5px 5px 0; - border-top-color: #000; -} -.tooltip.top-left .tooltip-arrow { - right: 5px; - bottom: 0; - margin-bottom: -5px; - border-width: 5px 5px 0; - border-top-color: #000; -} -.tooltip.top-right .tooltip-arrow { - bottom: 0; - left: 5px; - margin-bottom: -5px; - border-width: 5px 5px 0; - border-top-color: #000; -} -.tooltip.right .tooltip-arrow { - top: 50%; - left: 0; - margin-top: -5px; - border-width: 5px 5px 5px 0; - border-right-color: #000; -} -.tooltip.left .tooltip-arrow { - top: 50%; - right: 0; - margin-top: -5px; - border-width: 5px 0 5px 5px; - border-left-color: #000; -} -.tooltip.bottom .tooltip-arrow { - top: 0; - left: 50%; - margin-left: -5px; - border-width: 0 5px 5px; - border-bottom-color: #000; -} -.tooltip.bottom-left .tooltip-arrow { - top: 0; - right: 5px; - margin-top: -5px; - border-width: 0 5px 5px; - border-bottom-color: #000; -} -.tooltip.bottom-right .tooltip-arrow { - top: 0; - left: 5px; - margin-top: -5px; - border-width: 0 5px 5px; - border-bottom-color: #000; -} -.popover { - position: absolute; - top: 0; - left: 0; - z-index: 1060; - display: none; - max-width: 276px; - padding: 1px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - font-style: normal; - font-weight: normal; - line-height: 1.42857143; - text-align: left; - text-align: start; - text-decoration: none; - text-shadow: none; - text-transform: none; - letter-spacing: normal; - word-break: normal; - word-spacing: normal; - word-wrap: normal; - white-space: normal; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, .2); - border-radius: 6px; - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); - box-shadow: 0 5px 10px rgba(0, 0, 0, .2); - - line-break: auto; -} -.popover.top { - margin-top: -10px; -} -.popover.right { - margin-left: 10px; -} -.popover.bottom { - margin-top: 10px; -} -.popover.left { - margin-left: -10px; -} -.popover-title { - padding: 8px 14px; - margin: 0; - font-size: 14px; - background-color: #f7f7f7; - border-bottom: 1px solid #ebebeb; - border-radius: 5px 5px 0 0; -} -.popover-content { - padding: 9px 14px; -} -.popover > .arrow, -.popover > .arrow:after { - position: absolute; - display: block; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} -.popover > .arrow { - border-width: 11px; -} -.popover > .arrow:after { - content: ""; - border-width: 10px; -} -.popover.top > .arrow { - bottom: -11px; - left: 50%; - margin-left: -11px; - border-top-color: #999; - border-top-color: rgba(0, 0, 0, .25); - border-bottom-width: 0; -} -.popover.top > .arrow:after { - bottom: 1px; - margin-left: -10px; - content: " "; - border-top-color: #fff; - border-bottom-width: 0; -} -.popover.right > .arrow { - top: 50%; - left: -11px; - margin-top: -11px; - border-right-color: #999; - border-right-color: rgba(0, 0, 0, .25); - border-left-width: 0; -} -.popover.right > .arrow:after { - bottom: -10px; - left: 1px; - content: " "; - border-right-color: #fff; - border-left-width: 0; -} -.popover.bottom > .arrow { - top: -11px; - left: 50%; - margin-left: -11px; - border-top-width: 0; - border-bottom-color: #999; - border-bottom-color: rgba(0, 0, 0, .25); -} -.popover.bottom > .arrow:after { - top: 1px; - margin-left: -10px; - content: " "; - border-top-width: 0; - border-bottom-color: #fff; -} -.popover.left > .arrow { - top: 50%; - right: -11px; - margin-top: -11px; - border-right-width: 0; - border-left-color: #999; - border-left-color: rgba(0, 0, 0, .25); -} -.popover.left > .arrow:after { - right: 1px; - bottom: -10px; - content: " "; - border-right-width: 0; - border-left-color: #fff; -} -.carousel { - position: relative; -} -.carousel-inner { - position: relative; - width: 100%; - overflow: hidden; -} -.carousel-inner > .item { - position: relative; - display: none; - -webkit-transition: .6s ease-in-out left; - -o-transition: .6s ease-in-out left; - transition: .6s ease-in-out left; -} -.carousel-inner > .item > img, -.carousel-inner > .item > a > img { - line-height: 1; -} -@media all and (transform-3d), (-webkit-transform-3d) { - .carousel-inner > .item { - -webkit-transition: -webkit-transform .6s ease-in-out; - -o-transition: -o-transform .6s ease-in-out; - transition: transform .6s ease-in-out; - - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - -webkit-perspective: 1000px; - perspective: 1000px; - } - .carousel-inner > .item.next, - .carousel-inner > .item.active.right { - left: 0; - -webkit-transform: translate3d(100%, 0, 0); - transform: translate3d(100%, 0, 0); - } - .carousel-inner > .item.prev, - .carousel-inner > .item.active.left { - left: 0; - -webkit-transform: translate3d(-100%, 0, 0); - transform: translate3d(-100%, 0, 0); - } - .carousel-inner > .item.next.left, - .carousel-inner > .item.prev.right, - .carousel-inner > .item.active { - left: 0; - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } -} -.carousel-inner > .active, -.carousel-inner > .next, -.carousel-inner > .prev { - display: block; -} -.carousel-inner > .active { - left: 0; -} -.carousel-inner > .next, -.carousel-inner > .prev { - position: absolute; - top: 0; - width: 100%; -} -.carousel-inner > .next { - left: 100%; -} -.carousel-inner > .prev { - left: -100%; -} -.carousel-inner > .next.left, -.carousel-inner > .prev.right { - left: 0; -} -.carousel-inner > .active.left { - left: -100%; -} -.carousel-inner > .active.right { - left: 100%; -} -.carousel-control { - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 15%; - font-size: 20px; - color: #fff; - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, .6); - background-color: rgba(0, 0, 0, 0); - filter: alpha(opacity=50); - opacity: .5; -} -.carousel-control.left { - background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); - background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); - background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); - background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); - background-repeat: repeat-x; -} -.carousel-control.right { - right: 0; - left: auto; - background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); - background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); - background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); - background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); - background-repeat: repeat-x; -} -.carousel-control:hover, -.carousel-control:focus { - color: #fff; - text-decoration: none; - filter: alpha(opacity=90); - outline: 0; - opacity: .9; -} -.carousel-control .icon-prev, -.carousel-control .icon-next, -.carousel-control .glyphicon-chevron-left, -.carousel-control .glyphicon-chevron-right { - position: absolute; - top: 50%; - z-index: 5; - display: inline-block; - margin-top: -10px; -} -.carousel-control .icon-prev, -.carousel-control .glyphicon-chevron-left { - left: 50%; - margin-left: -10px; -} -.carousel-control .icon-next, -.carousel-control .glyphicon-chevron-right { - right: 50%; - margin-right: -10px; -} -.carousel-control .icon-prev, -.carousel-control .icon-next { - width: 20px; - height: 20px; - font-family: serif; - line-height: 1; -} -.carousel-control .icon-prev:before { - content: '\2039'; -} -.carousel-control .icon-next:before { - content: '\203a'; -} -.carousel-indicators { - position: absolute; - bottom: 10px; - left: 50%; - z-index: 15; - width: 60%; - padding-left: 0; - margin-left: -30%; - text-align: center; - list-style: none; -} -.carousel-indicators li { - display: inline-block; - width: 10px; - height: 10px; - margin: 1px; - text-indent: -999px; - cursor: pointer; - background-color: #000 \9; - background-color: rgba(0, 0, 0, 0); - border: 1px solid #fff; - border-radius: 10px; -} -.carousel-indicators .active { - width: 12px; - height: 12px; - margin: 0; - background-color: #fff; -} -.carousel-caption { - position: absolute; - right: 15%; - bottom: 20px; - left: 15%; - z-index: 10; - padding-top: 20px; - padding-bottom: 20px; - color: #fff; - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, .6); -} -.carousel-caption .btn { - text-shadow: none; -} -@media screen and (min-width: 768px) { - .carousel-control .glyphicon-chevron-left, - .carousel-control .glyphicon-chevron-right, - .carousel-control .icon-prev, - .carousel-control .icon-next { - width: 30px; - height: 30px; - margin-top: -10px; - font-size: 30px; - } - .carousel-control .glyphicon-chevron-left, - .carousel-control .icon-prev { - margin-left: -10px; - } - .carousel-control .glyphicon-chevron-right, - .carousel-control .icon-next { - margin-right: -10px; - } - .carousel-caption { - right: 20%; - left: 20%; - padding-bottom: 30px; - } - .carousel-indicators { - bottom: 20px; - } -} -.clearfix:before, -.clearfix:after, -.dl-horizontal dd:before, -.dl-horizontal dd:after, -.container:before, -.container:after, -.container-fluid:before, -.container-fluid:after, -.row:before, -.row:after, -.form-horizontal .form-group:before, -.form-horizontal .form-group:after, -.btn-toolbar:before, -.btn-toolbar:after, -.btn-group-vertical > .btn-group:before, -.btn-group-vertical > .btn-group:after, -.nav:before, -.nav:after, -.navbar:before, -.navbar:after, -.navbar-header:before, -.navbar-header:after, -.navbar-collapse:before, -.navbar-collapse:after, -.pager:before, -.pager:after, -.panel-body:before, -.panel-body:after, -.modal-header:before, -.modal-header:after, -.modal-footer:before, -.modal-footer:after { - display: table; - content: " "; -} -.clearfix:after, -.dl-horizontal dd:after, -.container:after, -.container-fluid:after, -.row:after, -.form-horizontal .form-group:after, -.btn-toolbar:after, -.btn-group-vertical > .btn-group:after, -.nav:after, -.navbar:after, -.navbar-header:after, -.navbar-collapse:after, -.pager:after, -.panel-body:after, -.modal-header:after, -.modal-footer:after { - clear: both; -} -.center-block { - display: block; - margin-right: auto; - margin-left: auto; -} -.pull-right { - float: right !important; -} -.pull-left { - float: left !important; -} -.hide { - display: none !important; -} -.show { - display: block !important; -} -.invisible { - visibility: hidden; -} -.text-hide { - font: 0/0 a; - color: transparent; - text-shadow: none; - background-color: transparent; - border: 0; -} -.hidden { - display: none !important; -} -.affix { - position: fixed; -} -@-ms-viewport { - width: device-width; -} -.visible-xs, -.visible-sm, -.visible-md, -.visible-lg { - display: none !important; -} -.visible-xs-block, -.visible-xs-inline, -.visible-xs-inline-block, -.visible-sm-block, -.visible-sm-inline, -.visible-sm-inline-block, -.visible-md-block, -.visible-md-inline, -.visible-md-inline-block, -.visible-lg-block, -.visible-lg-inline, -.visible-lg-inline-block { - display: none !important; -} -@media (max-width: 767px) { - .visible-xs { - display: block !important; - } - table.visible-xs { - display: table !important; - } - tr.visible-xs { - display: table-row !important; - } - th.visible-xs, - td.visible-xs { - display: table-cell !important; - } -} -@media (max-width: 767px) { - .visible-xs-block { - display: block !important; - } -} -@media (max-width: 767px) { - .visible-xs-inline { - display: inline !important; - } -} -@media (max-width: 767px) { - .visible-xs-inline-block { - display: inline-block !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm { - display: block !important; - } - table.visible-sm { - display: table !important; - } - tr.visible-sm { - display: table-row !important; - } - th.visible-sm, - td.visible-sm { - display: table-cell !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-block { - display: block !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-inline { - display: inline !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-inline-block { - display: inline-block !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md { - display: block !important; - } - table.visible-md { - display: table !important; - } - tr.visible-md { - display: table-row !important; - } - th.visible-md, - td.visible-md { - display: table-cell !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-block { - display: block !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-inline { - display: inline !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-inline-block { - display: inline-block !important; - } -} -@media (min-width: 1200px) { - .visible-lg { - display: block !important; - } - table.visible-lg { - display: table !important; - } - tr.visible-lg { - display: table-row !important; - } - th.visible-lg, - td.visible-lg { - display: table-cell !important; - } -} -@media (min-width: 1200px) { - .visible-lg-block { - display: block !important; - } -} -@media (min-width: 1200px) { - .visible-lg-inline { - display: inline !important; - } -} -@media (min-width: 1200px) { - .visible-lg-inline-block { - display: inline-block !important; - } -} -@media (max-width: 767px) { - .hidden-xs { - display: none !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .hidden-sm { - display: none !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .hidden-md { - display: none !important; - } -} -@media (min-width: 1200px) { - .hidden-lg { - display: none !important; - } -} -.visible-print { - display: none !important; -} -@media print { - .visible-print { - display: block !important; - } - table.visible-print { - display: table !important; - } - tr.visible-print { - display: table-row !important; - } - th.visible-print, - td.visible-print { - display: table-cell !important; - } -} -.visible-print-block { - display: none !important; -} -@media print { - .visible-print-block { - display: block !important; - } -} -.visible-print-inline { - display: none !important; -} -@media print { - .visible-print-inline { - display: inline !important; - } -} -.visible-print-inline-block { - display: none !important; -} -@media print { - .visible-print-inline-block { - display: inline-block !important; - } -} -@media print { - .hidden-print { - display: none !important; - } -} -/*# sourceMappingURL=bootstrap.css.map */ diff --git a/src/MarginTrading.Frontend/wwwroot/css/highlight-vs.css b/src/MarginTrading.Frontend/wwwroot/css/highlight-vs.css deleted file mode 100644 index c5d07d311..000000000 --- a/src/MarginTrading.Frontend/wwwroot/css/highlight-vs.css +++ /dev/null @@ -1,68 +0,0 @@ -/* - -Visual Studio-like style based on original C# coloring by Jason Diamond - -*/ -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: white; - color: black; -} - -.hljs-comment, -.hljs-quote, -.hljs-variable { - color: #008000; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-built_in, -.hljs-name, -.hljs-tag { - color: #00f; -} - -.hljs-string, -.hljs-title, -.hljs-section, -.hljs-attribute, -.hljs-literal, -.hljs-template-tag, -.hljs-template-variable, -.hljs-type, -.hljs-addition { - color: #a31515; -} - -.hljs-deletion, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-meta { - color: #2b91af; -} - -.hljs-doctag { - color: #808080; -} - -.hljs-attr { - color: #f00; -} - -.hljs-symbol, -.hljs-bullet, -.hljs-link { - color: #00b0e8; -} - - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/src/MarginTrading.Frontend/wwwroot/fonts/glyphicons-halflings-regular.eot b/src/MarginTrading.Frontend/wwwroot/fonts/glyphicons-halflings-regular.eot deleted file mode 100644 index b93a4953f..000000000 Binary files a/src/MarginTrading.Frontend/wwwroot/fonts/glyphicons-halflings-regular.eot and /dev/null differ diff --git a/src/MarginTrading.Frontend/wwwroot/fonts/glyphicons-halflings-regular.svg b/src/MarginTrading.Frontend/wwwroot/fonts/glyphicons-halflings-regular.svg deleted file mode 100644 index 94fb5490a..000000000 --- a/src/MarginTrading.Frontend/wwwroot/fonts/glyphicons-halflings-regular.svg +++ /dev/null @@ -1,288 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/MarginTrading.Frontend/wwwroot/fonts/glyphicons-halflings-regular.ttf b/src/MarginTrading.Frontend/wwwroot/fonts/glyphicons-halflings-regular.ttf deleted file mode 100644 index 1413fc609..000000000 Binary files a/src/MarginTrading.Frontend/wwwroot/fonts/glyphicons-halflings-regular.ttf and /dev/null differ diff --git a/src/MarginTrading.Frontend/wwwroot/fonts/glyphicons-halflings-regular.woff b/src/MarginTrading.Frontend/wwwroot/fonts/glyphicons-halflings-regular.woff deleted file mode 100644 index 9e612858f..000000000 Binary files a/src/MarginTrading.Frontend/wwwroot/fonts/glyphicons-halflings-regular.woff and /dev/null differ diff --git a/src/MarginTrading.Frontend/wwwroot/fonts/glyphicons-halflings-regular.woff2 b/src/MarginTrading.Frontend/wwwroot/fonts/glyphicons-halflings-regular.woff2 deleted file mode 100644 index 64539b54c..000000000 Binary files a/src/MarginTrading.Frontend/wwwroot/fonts/glyphicons-halflings-regular.woff2 and /dev/null differ diff --git a/src/MarginTrading.Frontend/wwwroot/images/favicon.png b/src/MarginTrading.Frontend/wwwroot/images/favicon.png deleted file mode 100644 index 01c19e17e..000000000 Binary files a/src/MarginTrading.Frontend/wwwroot/images/favicon.png and /dev/null differ diff --git a/src/MarginTrading.Frontend/wwwroot/js/bootstrap.min.js b/src/MarginTrading.Frontend/wwwroot/js/bootstrap.min.js deleted file mode 100644 index 9bcd2fcca..000000000 --- a/src/MarginTrading.Frontend/wwwroot/js/bootstrap.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under the MIT license - */ -if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/src/MarginTrading.Frontend/wwwroot/js/highlight.pack.js b/src/MarginTrading.Frontend/wwwroot/js/highlight.pack.js deleted file mode 100644 index ef27d2f00..000000000 --- a/src/MarginTrading.Frontend/wwwroot/js/highlight.pack.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! highlight.js v9.11.0 | BSD3 License | git.io/hljslicense */ -!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){s+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"
":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("http",function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],i:"\\S",c:[{b:"^"+t,e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{b:"^[A-Z]+ (.*?) "+t+"$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0},{b:t},{cN:"keyword",b:"[A-Z]+"}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{e:"$",r:0}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}});hljs.registerLanguage("cs",function(e){var i={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do decimal else enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while nameof add alias ascending async await by descending dynamic equals from get global group into join let on orderby partial remove select set value var where yield",literal:"null false true"},r={cN:"string",b:'@"',e:'"',c:[{b:'""'}]},t=e.inherit(r,{i:/\n/}),a={cN:"subst",b:"{",e:"}",k:i},n=e.inherit(a,{i:/\n/}),c={cN:"string",b:/\$"/,e:'"',i:/\n/,c:[{b:"{{"},{b:"}}"},e.BE,n]},s={cN:"string",b:/\$@"/,e:'"',c:[{b:"{{"},{b:"}}"},{b:'""'},a]},o=e.inherit(s,{i:/\n/,c:[{b:"{{"},{b:"}}"},{b:'""'},n]});a.c=[s,c,r,e.ASM,e.QSM,e.CNM,e.CBCM],n.c=[o,c,t,e.ASM,e.QSM,e.CNM,e.inherit(e.CBCM,{i:/\n/})];var l={v:[s,c,r,e.ASM,e.QSM]},b=e.IR+"(<"+e.IR+"(\\s*,\\s*"+e.IR+")*>)?(\\[\\])?";return{aliases:["csharp"],k:i,i:/::/,c:[e.C("///","$",{rB:!0,c:[{cN:"doctag",v:[{b:"///",r:0},{b:""},{b:""}]}]}),e.CLCM,e.CBCM,{cN:"meta",b:"#",e:"$",k:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},l,e.CNM,{bK:"class interface",e:/[{;=]/,i:/[^\s:]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:"namespace",e:/[{;=]/,i:/[^\s:]/,c:[e.inherit(e.TM,{b:"[a-zA-Z](\\.?\\w)*"}),e.CLCM,e.CBCM]},{bK:"new return throw await",r:0},{cN:"function",b:"("+b+"\\s+)+"+e.IR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:i,c:[{b:e.IR+"\\s*\\(",rB:!0,c:[e.TM],r:0},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:i,r:0,c:[l,e.CNM,e.CBCM]},e.CLCM,e.CBCM]}]}});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b://,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:n,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})],i:"\\S"},c={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return n.splice(n.length,0,t,c),{c:n,k:i,i:"\\S"}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",t={b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{b:"@",e:"[{;]",i:/:/,c:[{cN:"keyword",b:/\w+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:c,r:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,t]}]}});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0}]},{cN:"tag",b:"|$)",e:">",k:{name:"style"},c:[t],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"|$)",e:">",k:{name:"script"},c:[t],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}}); \ No newline at end of file diff --git a/src/MarginTrading.Frontend/wwwroot/js/jquery.min.js b/src/MarginTrading.Frontend/wwwroot/js/jquery.min.js deleted file mode 100644 index 0f60b7bd0..000000000 --- a/src/MarginTrading.Frontend/wwwroot/js/jquery.min.js +++ /dev/null @@ -1,5 +0,0 @@ -/*! jQuery v1.11.3 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.3",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b="length"in a&&a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1; - -return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="
a",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function aa(){return!0}function ba(){return!1}function ca(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),ha=/^\s+/,ia=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ja=/<([\w:]+)/,ka=/\s*$/g,ra={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:k.htmlSerialize?[0,"",""]:[1,"X
","
"]},sa=da(y),ta=sa.appendChild(y.createElement("div"));ra.optgroup=ra.option,ra.tbody=ra.tfoot=ra.colgroup=ra.caption=ra.thead,ra.th=ra.td;function ua(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ua(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function va(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wa(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xa(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function ya(a){var b=pa.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function za(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Aa(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Ba(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xa(b).text=a.text,ya(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!ga.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ta.innerHTML=a.outerHTML,ta.removeChild(f=ta.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ua(f),h=ua(a),g=0;null!=(e=h[g]);++g)d[g]&&Ba(e,d[g]);if(b)if(c)for(h=h||ua(a),d=d||ua(f),g=0;null!=(e=h[g]);g++)Aa(e,d[g]);else Aa(a,f);return d=ua(f,"script"),d.length>0&&za(d,!i&&ua(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=da(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(la.test(f)){h=h||o.appendChild(b.createElement("div")),i=(ja.exec(f)||["",""])[1].toLowerCase(),l=ra[i]||ra._default,h.innerHTML=l[1]+f.replace(ia,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&ha.test(f)&&p.push(b.createTextNode(ha.exec(f)[0])),!k.tbody){f="table"!==i||ka.test(f)?""!==l[1]||ka.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ua(p,"input"),va),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ua(o.appendChild(f),"script"),g&&za(h),c)){e=0;while(f=h[e++])oa.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wa(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ua(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&za(ua(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ua(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fa,""):void 0;if(!("string"!=typeof a||ma.test(a)||!k.htmlSerialize&&ga.test(a)||!k.leadingWhitespace&&ha.test(a)||ra[(ja.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ia,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ua(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ua(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&na.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ua(i,"script"),xa),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ua(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,ya),j=0;f>j;j++)d=g[j],oa.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qa,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Ca,Da={};function Ea(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fa(a){var b=y,c=Da[a];return c||(c=Ea(a,b),"none"!==c&&c||(Ca=(Ca||m("