From 1a254d7f0f9f82a05c147b13fee7a14b23ce4293 Mon Sep 17 00:00:00 2001 From: Aliaksandr Vashetsin Date: Fri, 9 Feb 2024 14:09:13 +0300 Subject: [PATCH 01/25] LT-4926: Potential corrupted snapshot --- .../Repositories/IOrdersHistoryRepository.cs | 2 +- .../Infrastructure/SnapshotValidationService.cs | 5 ++++- .../Repositories/OrdersHistoryRepository.cs | 6 +++--- .../Infrastructure/SnapshotValidationServiceTests.cs | 2 +- tests/MarginTradingTests/Modules/MockRepositoriesModule.cs | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/MarginTrading.Backend.Core/Repositories/IOrdersHistoryRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IOrdersHistoryRepository.cs index 500154f82..326ddaa61 100644 --- a/src/MarginTrading.Backend.Core/Repositories/IOrdersHistoryRepository.cs +++ b/src/MarginTrading.Backend.Core/Repositories/IOrdersHistoryRepository.cs @@ -10,6 +10,6 @@ namespace MarginTrading.Backend.Core.Repositories { public interface IOrdersHistoryRepository { - Task> GetLastSnapshot(DateTime @from); + Task> GetLastSnapshot(DateTime @from, DateTime? @to = null); } } diff --git a/src/MarginTrading.Backend.Services/Infrastructure/SnapshotValidationService.cs b/src/MarginTrading.Backend.Services/Infrastructure/SnapshotValidationService.cs index cfb4253d5..d5f480b83 100644 --- a/src/MarginTrading.Backend.Services/Infrastructure/SnapshotValidationService.cs +++ b/src/MarginTrading.Backend.Services/Infrastructure/SnapshotValidationService.cs @@ -62,7 +62,10 @@ await _log.WriteInfoAsync(nameof(SnapshotValidationService), nameof(ValidateCurr var lastOrders = GetOrders(tradingEngineSnapshot); var lastPositions = GetPositions(tradingEngineSnapshot); - var ordersHistory = await _ordersHistoryRepository.GetLastSnapshot(tradingEngineSnapshot.Timestamp); + var latestLastModified = currentOrders.Any() + ? currentOrders.Max(x => x.LastModified) + : (DateTime?)null; + var ordersHistory = await _ordersHistoryRepository.GetLastSnapshot(tradingEngineSnapshot.Timestamp, latestLastModified); var positionsHistory = await _positionsHistoryRepository.GetLastSnapshot(tradingEngineSnapshot.Timestamp); var restoredOrders = RestoreOrdersCurrentStateFromHistory(lastOrders, ordersHistory); diff --git a/src/MarginTrading.SqlRepositories/Repositories/OrdersHistoryRepository.cs b/src/MarginTrading.SqlRepositories/Repositories/OrdersHistoryRepository.cs index 4977b17cd..b23e82f38 100644 --- a/src/MarginTrading.SqlRepositories/Repositories/OrdersHistoryRepository.cs +++ b/src/MarginTrading.SqlRepositories/Repositories/OrdersHistoryRepository.cs @@ -34,7 +34,7 @@ CASE [Status] ELSE 99 END as StatusOrder FROM [{0}] oh (NOLOCK) - WHERE oh.ModifiedTimestamp > @Timestamp + WHERE oh.ModifiedTimestamp > @From AND (@To IS NULL OR oh.ModifiedTimestamp <= @To) ), filteredOrderHistWithRowNumber AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY Id ORDER BY @@ -54,11 +54,11 @@ public OrdersHistoryRepository(string connectionString, string tableName, int ge _getLastSnapshotTimeoutS = getLastSnapshotTimeoutS; } - public async Task> GetLastSnapshot(DateTime @from) + public async Task> GetLastSnapshot(DateTime @from, DateTime? @to = null) { using (var conn = new SqlConnection(_connectionString)) { - var data = await conn.QueryAsync(_select, new { Timestamp = @from }, commandTimeout: _getLastSnapshotTimeoutS); + var data = await conn.QueryAsync(_select, new { From = @from, To = @to }, commandTimeout: _getLastSnapshotTimeoutS); return data.Cast().ToList(); } diff --git a/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs b/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs index 33dccad2e..f389aad46 100644 --- a/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs +++ b/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs @@ -62,7 +62,7 @@ public void SetUp() _orderCacheMock.Setup(o => o.GetPositions()) .Returns(() => _currentPositions.ToImmutableArray()); - _ordersHistoryRepositoryMock.Setup(o => o.GetLastSnapshot(It.IsAny())) + _ordersHistoryRepositoryMock.Setup(o => o.GetLastSnapshot(It.IsAny(), It.IsAny())) .ReturnsAsync((DateTime date) => _ordersHistory); _positionsHistoryRepositoryMock.Setup(o => o.GetLastSnapshot(It.IsAny())) diff --git a/tests/MarginTradingTests/Modules/MockRepositoriesModule.cs b/tests/MarginTradingTests/Modules/MockRepositoriesModule.cs index 9cfd403a4..22fd9eede 100644 --- a/tests/MarginTradingTests/Modules/MockRepositoriesModule.cs +++ b/tests/MarginTradingTests/Modules/MockRepositoriesModule.cs @@ -29,7 +29,7 @@ protected override void Load(ContainerBuilder builder) blobRepository.Setup(s => s.ReadWithTimestampAsync>(It.IsAny(), It.IsAny())) .ReturnsAsync((new List(), DateTime.UtcNow)); var orderHistoryRepository = new Mock(); - orderHistoryRepository.Setup(s => s.GetLastSnapshot(It.IsAny())) + orderHistoryRepository.Setup(s => s.GetLastSnapshot(It.IsAny(), It.IsAny())) .ReturnsAsync(new List()); var positionHistoryRepository = new Mock(); var accountHistoryRepository = new Mock(); From 88d3f894c001b8a2a651d1f7099a181ce73fae44 Mon Sep 17 00:00:00 2001 From: Aliaksandr Vashetsin Date: Fri, 9 Feb 2024 14:20:52 +0300 Subject: [PATCH 02/25] LT-4926: fix unit test --- .../Infrastructure/SnapshotValidationServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs b/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs index f389aad46..3b43f72ea 100644 --- a/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs +++ b/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs @@ -63,7 +63,7 @@ public void SetUp() .Returns(() => _currentPositions.ToImmutableArray()); _ordersHistoryRepositoryMock.Setup(o => o.GetLastSnapshot(It.IsAny(), It.IsAny())) - .ReturnsAsync((DateTime date) => _ordersHistory); + .ReturnsAsync((DateTime from, DateTime? to) => _ordersHistory); _positionsHistoryRepositoryMock.Setup(o => o.GetLastSnapshot(It.IsAny())) .ReturnsAsync((DateTime date) => _positionsHistory); From db4350d66a511d24a8b4260d8c584047c0da1917 Mon Sep 17 00:00:00 2001 From: atarutin Date: Mon, 26 Feb 2024 16:50:36 +0300 Subject: [PATCH 03/25] fix(LT-5298): increase the criticality of the message when snapshot was not created The only reason to avoid the error is when the platform closure event is related to the trading day in the past (assume stale event) --- .../TradingEngineSnapshotExtensions.cs | 3 + .../Workflow/PlatformClosureProjection.cs | 62 ++++++++++++------- 2 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/MarginTrading.Backend.Services/Extensions/TradingEngineSnapshotExtensions.cs b/src/MarginTrading.Backend.Services/Extensions/TradingEngineSnapshotExtensions.cs index 87bd3b222..879399dd6 100644 --- a/src/MarginTrading.Backend.Services/Extensions/TradingEngineSnapshotExtensions.cs +++ b/src/MarginTrading.Backend.Services/Extensions/TradingEngineSnapshotExtensions.cs @@ -88,6 +88,9 @@ public static bool Initialized(this IDraftSnapshotKeeper keeper) public static bool IsPlatformClosureEvent(this MarketStateChangedEvent evt) => evt.Id == LykkeConstants.PlatformMarketIdentifier && !evt.IsEnabled; + public static bool IsNotPlatformClosureEvent(this MarketStateChangedEvent evt) => + !evt.IsPlatformClosureEvent(); + private static List GetOrders(this TradingEngineSnapshot snapshot) { return string.IsNullOrWhiteSpace(snapshot.OrdersJson) diff --git a/src/MarginTrading.Backend.Services/Workflow/PlatformClosureProjection.cs b/src/MarginTrading.Backend.Services/Workflow/PlatformClosureProjection.cs index 677b7a073..9888df390 100644 --- a/src/MarginTrading.Backend.Services/Workflow/PlatformClosureProjection.cs +++ b/src/MarginTrading.Backend.Services/Workflow/PlatformClosureProjection.cs @@ -36,44 +36,62 @@ public PlatformClosureProjection(ISnapshotService snapshotService, [UsedImplicitly] public async Task Handle(MarketStateChangedEvent e) { - if (!e.IsPlatformClosureEvent()) + if (e.IsNotPlatformClosureEvent()) return; - var tradingDay = DateOnly.FromDateTime(e.EventTimestamp); - - string result; + var successMessage = string.Empty; try { - result = await _snapshotService.MakeTradingDataSnapshot(e.EventTimestamp.Date, - _identityGenerator.GenerateGuid(), - SnapshotStatus.Draft); + successMessage = await CreateDraftSnapshot(e.EventTimestamp.Date); } catch (Exception ex) { - await _log.WriteWarningAsync(nameof(PlatformClosureProjection), - nameof(Handle), - e.ToJson(), - $"Failed to make trading data draft snapshot for [{tradingDay}]", ex); - - if (tradingDay < _dateService.NowDateOnly()) + if (!await IsExceptionExpected(ex, e)) { - await _log.WriteWarningAsync(nameof(PlatformClosureProjection), - nameof(Handle), - e.ToJson(), - "The event is for the past date, so the snapshot draft will not be created.", ex); - return; + throw; } - - throw; } - if (!string.IsNullOrWhiteSpace(result)) + if (!string.IsNullOrWhiteSpace(successMessage)) { await _log.WriteInfoAsync(nameof(PlatformClosureProjection), nameof(Handle), e.ToJson(), - result); + successMessage); + } + } + + private Task CreateDraftSnapshot(DateTime tradingDay) + { + return _snapshotService.MakeTradingDataSnapshot(tradingDay, + _identityGenerator.GenerateGuid(), + SnapshotStatus.Draft); + } + + private async Task IsExceptionExpected(Exception ex, MarketStateChangedEvent evt) + { + if (IsEventForPastDate(evt)) + { + await _log.WriteWarningAsync(nameof(PlatformClosureProjection), + nameof(IsExceptionExpected), + evt.ToJson(), + "The event is for the past date, so the snapshot draft will not be created.", ex); + return true; } + + var tradingDayFromEvent = DateOnly.FromDateTime(evt.EventTimestamp); + await _log.WriteErrorAsync(nameof(PlatformClosureProjection), + nameof(IsExceptionExpected), + new {eventJson = evt.ToJson(), tradingDay = tradingDayFromEvent}.ToJson(), + ex); + + return false; + } + + private bool IsEventForPastDate(MarketStateChangedEvent evt) + { + var tradingDayFromEvent = DateOnly.FromDateTime(evt.EventTimestamp); + return tradingDayFromEvent < _dateService.NowDateOnly(); } } } \ No newline at end of file From 2abed9568ad643b2bd5f476c80747c1c6c511e93 Mon Sep 17 00:00:00 2001 From: atarutin Date: Mon, 26 Feb 2024 18:58:25 +0300 Subject: [PATCH 04/25] refactor(LT-5298): minor refactoring --- .../Workflow/PlatformClosureProjection.cs | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/MarginTrading.Backend.Services/Workflow/PlatformClosureProjection.cs b/src/MarginTrading.Backend.Services/Workflow/PlatformClosureProjection.cs index 9888df390..02811f3c7 100644 --- a/src/MarginTrading.Backend.Services/Workflow/PlatformClosureProjection.cs +++ b/src/MarginTrading.Backend.Services/Workflow/PlatformClosureProjection.cs @@ -39,33 +39,38 @@ public async Task Handle(MarketStateChangedEvent e) if (e.IsNotPlatformClosureEvent()) return; - var successMessage = string.Empty; try { - successMessage = await CreateDraftSnapshot(e.EventTimestamp.Date); + var successMessage = await CreateDraftSnapshot(e.EventTimestamp.Date); + await LogIfSucceeded(successMessage, e); } catch (Exception ex) { - if (!await IsExceptionExpected(ex, e)) + var exceptionExpected = await IsExceptionExpected(ex, e); + if (!exceptionExpected) { throw; } } - - if (!string.IsNullOrWhiteSpace(successMessage)) - { - await _log.WriteInfoAsync(nameof(PlatformClosureProjection), - nameof(Handle), - e.ToJson(), - successMessage); - } + } + + private async Task LogIfSucceeded(string successMessage, MarketStateChangedEvent evt) + { + var failed = string.IsNullOrWhiteSpace(successMessage); + if (failed) return; + + await _log.WriteInfoAsync(nameof(PlatformClosureProjection), + nameof(LogIfSucceeded), + evt.ToJson(), + successMessage); } - private Task CreateDraftSnapshot(DateTime tradingDay) + private async Task CreateDraftSnapshot(DateTime tradingDay) { - return _snapshotService.MakeTradingDataSnapshot(tradingDay, + var result = await _snapshotService.MakeTradingDataSnapshot(tradingDay, _identityGenerator.GenerateGuid(), SnapshotStatus.Draft); + return result; } private async Task IsExceptionExpected(Exception ex, MarketStateChangedEvent evt) From 672a4818f783fa2f2c513b8ac7915f9df69e73ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 00:45:05 +0000 Subject: [PATCH 05/25] chore(deps): bump Lykke.Snow.Domain from 2.0.3 to 2.1.4 Bumps Lykke.Snow.Domain from 2.0.3 to 2.1.4. --- updated-dependencies: - dependency-name: Lykke.Snow.Domain dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .../MarginTrading.Backend.Services.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj b/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj index 7aab54792..e1833c30b 100644 --- a/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj +++ b/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj @@ -32,7 +32,7 @@ - + From e7b905f0e64901f8965f3a5e7c06658e14bf8c35 Mon Sep 17 00:00:00 2001 From: Aliaksandr Vashetsin Date: Thu, 14 Mar 2024 12:22:01 +0100 Subject: [PATCH 06/25] Revert "LT-4926: fix unit test" This reverts commit 88d3f894c001b8a2a651d1f7099a181ce73fae44. --- .../Infrastructure/SnapshotValidationServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs b/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs index 3b43f72ea..f389aad46 100644 --- a/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs +++ b/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs @@ -63,7 +63,7 @@ public void SetUp() .Returns(() => _currentPositions.ToImmutableArray()); _ordersHistoryRepositoryMock.Setup(o => o.GetLastSnapshot(It.IsAny(), It.IsAny())) - .ReturnsAsync((DateTime from, DateTime? to) => _ordersHistory); + .ReturnsAsync((DateTime date) => _ordersHistory); _positionsHistoryRepositoryMock.Setup(o => o.GetLastSnapshot(It.IsAny())) .ReturnsAsync((DateTime date) => _positionsHistory); From ae9bac99db1b7863a2b9dc2adad59a9a80eef48e Mon Sep 17 00:00:00 2001 From: Aliaksandr Vashetsin Date: Thu, 14 Mar 2024 12:22:25 +0100 Subject: [PATCH 07/25] Revert "LT-4926: Potential corrupted snapshot" This reverts commit 1a254d7f0f9f82a05c147b13fee7a14b23ce4293. --- .../Repositories/IOrdersHistoryRepository.cs | 2 +- .../Infrastructure/SnapshotValidationService.cs | 5 +---- .../Repositories/OrdersHistoryRepository.cs | 6 +++--- .../Infrastructure/SnapshotValidationServiceTests.cs | 2 +- tests/MarginTradingTests/Modules/MockRepositoriesModule.cs | 2 +- 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/MarginTrading.Backend.Core/Repositories/IOrdersHistoryRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IOrdersHistoryRepository.cs index 326ddaa61..500154f82 100644 --- a/src/MarginTrading.Backend.Core/Repositories/IOrdersHistoryRepository.cs +++ b/src/MarginTrading.Backend.Core/Repositories/IOrdersHistoryRepository.cs @@ -10,6 +10,6 @@ namespace MarginTrading.Backend.Core.Repositories { public interface IOrdersHistoryRepository { - Task> GetLastSnapshot(DateTime @from, DateTime? @to = null); + Task> GetLastSnapshot(DateTime @from); } } diff --git a/src/MarginTrading.Backend.Services/Infrastructure/SnapshotValidationService.cs b/src/MarginTrading.Backend.Services/Infrastructure/SnapshotValidationService.cs index d5f480b83..cfb4253d5 100644 --- a/src/MarginTrading.Backend.Services/Infrastructure/SnapshotValidationService.cs +++ b/src/MarginTrading.Backend.Services/Infrastructure/SnapshotValidationService.cs @@ -62,10 +62,7 @@ await _log.WriteInfoAsync(nameof(SnapshotValidationService), nameof(ValidateCurr var lastOrders = GetOrders(tradingEngineSnapshot); var lastPositions = GetPositions(tradingEngineSnapshot); - var latestLastModified = currentOrders.Any() - ? currentOrders.Max(x => x.LastModified) - : (DateTime?)null; - var ordersHistory = await _ordersHistoryRepository.GetLastSnapshot(tradingEngineSnapshot.Timestamp, latestLastModified); + var ordersHistory = await _ordersHistoryRepository.GetLastSnapshot(tradingEngineSnapshot.Timestamp); var positionsHistory = await _positionsHistoryRepository.GetLastSnapshot(tradingEngineSnapshot.Timestamp); var restoredOrders = RestoreOrdersCurrentStateFromHistory(lastOrders, ordersHistory); diff --git a/src/MarginTrading.SqlRepositories/Repositories/OrdersHistoryRepository.cs b/src/MarginTrading.SqlRepositories/Repositories/OrdersHistoryRepository.cs index b23e82f38..4977b17cd 100644 --- a/src/MarginTrading.SqlRepositories/Repositories/OrdersHistoryRepository.cs +++ b/src/MarginTrading.SqlRepositories/Repositories/OrdersHistoryRepository.cs @@ -34,7 +34,7 @@ CASE [Status] ELSE 99 END as StatusOrder FROM [{0}] oh (NOLOCK) - WHERE oh.ModifiedTimestamp > @From AND (@To IS NULL OR oh.ModifiedTimestamp <= @To) + WHERE oh.ModifiedTimestamp > @Timestamp ), filteredOrderHistWithRowNumber AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY Id ORDER BY @@ -54,11 +54,11 @@ public OrdersHistoryRepository(string connectionString, string tableName, int ge _getLastSnapshotTimeoutS = getLastSnapshotTimeoutS; } - public async Task> GetLastSnapshot(DateTime @from, DateTime? @to = null) + public async Task> GetLastSnapshot(DateTime @from) { using (var conn = new SqlConnection(_connectionString)) { - var data = await conn.QueryAsync(_select, new { From = @from, To = @to }, commandTimeout: _getLastSnapshotTimeoutS); + var data = await conn.QueryAsync(_select, new { Timestamp = @from }, commandTimeout: _getLastSnapshotTimeoutS); return data.Cast().ToList(); } diff --git a/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs b/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs index f389aad46..33dccad2e 100644 --- a/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs +++ b/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs @@ -62,7 +62,7 @@ public void SetUp() _orderCacheMock.Setup(o => o.GetPositions()) .Returns(() => _currentPositions.ToImmutableArray()); - _ordersHistoryRepositoryMock.Setup(o => o.GetLastSnapshot(It.IsAny(), It.IsAny())) + _ordersHistoryRepositoryMock.Setup(o => o.GetLastSnapshot(It.IsAny())) .ReturnsAsync((DateTime date) => _ordersHistory); _positionsHistoryRepositoryMock.Setup(o => o.GetLastSnapshot(It.IsAny())) diff --git a/tests/MarginTradingTests/Modules/MockRepositoriesModule.cs b/tests/MarginTradingTests/Modules/MockRepositoriesModule.cs index 22fd9eede..9cfd403a4 100644 --- a/tests/MarginTradingTests/Modules/MockRepositoriesModule.cs +++ b/tests/MarginTradingTests/Modules/MockRepositoriesModule.cs @@ -29,7 +29,7 @@ protected override void Load(ContainerBuilder builder) blobRepository.Setup(s => s.ReadWithTimestampAsync>(It.IsAny(), It.IsAny())) .ReturnsAsync((new List(), DateTime.UtcNow)); var orderHistoryRepository = new Mock(); - orderHistoryRepository.Setup(s => s.GetLastSnapshot(It.IsAny(), It.IsAny())) + orderHistoryRepository.Setup(s => s.GetLastSnapshot(It.IsAny())) .ReturnsAsync(new List()); var positionHistoryRepository = new Mock(); var accountHistoryRepository = new Mock(); From e9867c210afa2f7b785279d396c959bca178315b Mon Sep 17 00:00:00 2001 From: Aliaksandr Vashetsin Date: Thu, 14 Mar 2024 12:53:03 +0100 Subject: [PATCH 08/25] Revert "Revert "LT-4926: Potential corrupted snapshot"" This reverts commit ae9bac99db1b7863a2b9dc2adad59a9a80eef48e. --- .../Repositories/IOrdersHistoryRepository.cs | 2 +- .../Infrastructure/SnapshotValidationService.cs | 5 ++++- .../Repositories/OrdersHistoryRepository.cs | 6 +++--- .../Infrastructure/SnapshotValidationServiceTests.cs | 2 +- tests/MarginTradingTests/Modules/MockRepositoriesModule.cs | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/MarginTrading.Backend.Core/Repositories/IOrdersHistoryRepository.cs b/src/MarginTrading.Backend.Core/Repositories/IOrdersHistoryRepository.cs index 500154f82..326ddaa61 100644 --- a/src/MarginTrading.Backend.Core/Repositories/IOrdersHistoryRepository.cs +++ b/src/MarginTrading.Backend.Core/Repositories/IOrdersHistoryRepository.cs @@ -10,6 +10,6 @@ namespace MarginTrading.Backend.Core.Repositories { public interface IOrdersHistoryRepository { - Task> GetLastSnapshot(DateTime @from); + Task> GetLastSnapshot(DateTime @from, DateTime? @to = null); } } diff --git a/src/MarginTrading.Backend.Services/Infrastructure/SnapshotValidationService.cs b/src/MarginTrading.Backend.Services/Infrastructure/SnapshotValidationService.cs index cfb4253d5..d5f480b83 100644 --- a/src/MarginTrading.Backend.Services/Infrastructure/SnapshotValidationService.cs +++ b/src/MarginTrading.Backend.Services/Infrastructure/SnapshotValidationService.cs @@ -62,7 +62,10 @@ await _log.WriteInfoAsync(nameof(SnapshotValidationService), nameof(ValidateCurr var lastOrders = GetOrders(tradingEngineSnapshot); var lastPositions = GetPositions(tradingEngineSnapshot); - var ordersHistory = await _ordersHistoryRepository.GetLastSnapshot(tradingEngineSnapshot.Timestamp); + var latestLastModified = currentOrders.Any() + ? currentOrders.Max(x => x.LastModified) + : (DateTime?)null; + var ordersHistory = await _ordersHistoryRepository.GetLastSnapshot(tradingEngineSnapshot.Timestamp, latestLastModified); var positionsHistory = await _positionsHistoryRepository.GetLastSnapshot(tradingEngineSnapshot.Timestamp); var restoredOrders = RestoreOrdersCurrentStateFromHistory(lastOrders, ordersHistory); diff --git a/src/MarginTrading.SqlRepositories/Repositories/OrdersHistoryRepository.cs b/src/MarginTrading.SqlRepositories/Repositories/OrdersHistoryRepository.cs index 4977b17cd..b23e82f38 100644 --- a/src/MarginTrading.SqlRepositories/Repositories/OrdersHistoryRepository.cs +++ b/src/MarginTrading.SqlRepositories/Repositories/OrdersHistoryRepository.cs @@ -34,7 +34,7 @@ CASE [Status] ELSE 99 END as StatusOrder FROM [{0}] oh (NOLOCK) - WHERE oh.ModifiedTimestamp > @Timestamp + WHERE oh.ModifiedTimestamp > @From AND (@To IS NULL OR oh.ModifiedTimestamp <= @To) ), filteredOrderHistWithRowNumber AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY Id ORDER BY @@ -54,11 +54,11 @@ public OrdersHistoryRepository(string connectionString, string tableName, int ge _getLastSnapshotTimeoutS = getLastSnapshotTimeoutS; } - public async Task> GetLastSnapshot(DateTime @from) + public async Task> GetLastSnapshot(DateTime @from, DateTime? @to = null) { using (var conn = new SqlConnection(_connectionString)) { - var data = await conn.QueryAsync(_select, new { Timestamp = @from }, commandTimeout: _getLastSnapshotTimeoutS); + var data = await conn.QueryAsync(_select, new { From = @from, To = @to }, commandTimeout: _getLastSnapshotTimeoutS); return data.Cast().ToList(); } diff --git a/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs b/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs index 33dccad2e..f389aad46 100644 --- a/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs +++ b/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs @@ -62,7 +62,7 @@ public void SetUp() _orderCacheMock.Setup(o => o.GetPositions()) .Returns(() => _currentPositions.ToImmutableArray()); - _ordersHistoryRepositoryMock.Setup(o => o.GetLastSnapshot(It.IsAny())) + _ordersHistoryRepositoryMock.Setup(o => o.GetLastSnapshot(It.IsAny(), It.IsAny())) .ReturnsAsync((DateTime date) => _ordersHistory); _positionsHistoryRepositoryMock.Setup(o => o.GetLastSnapshot(It.IsAny())) diff --git a/tests/MarginTradingTests/Modules/MockRepositoriesModule.cs b/tests/MarginTradingTests/Modules/MockRepositoriesModule.cs index 9cfd403a4..22fd9eede 100644 --- a/tests/MarginTradingTests/Modules/MockRepositoriesModule.cs +++ b/tests/MarginTradingTests/Modules/MockRepositoriesModule.cs @@ -29,7 +29,7 @@ protected override void Load(ContainerBuilder builder) blobRepository.Setup(s => s.ReadWithTimestampAsync>(It.IsAny(), It.IsAny())) .ReturnsAsync((new List(), DateTime.UtcNow)); var orderHistoryRepository = new Mock(); - orderHistoryRepository.Setup(s => s.GetLastSnapshot(It.IsAny())) + orderHistoryRepository.Setup(s => s.GetLastSnapshot(It.IsAny(), It.IsAny())) .ReturnsAsync(new List()); var positionHistoryRepository = new Mock(); var accountHistoryRepository = new Mock(); From 32b1f5882e06b127a2c79ebca4a508e128d817ee Mon Sep 17 00:00:00 2001 From: Aliaksandr Vashetsin Date: Thu, 14 Mar 2024 12:53:13 +0100 Subject: [PATCH 09/25] Revert "Revert "LT-4926: fix unit test"" This reverts commit e7b905f0e64901f8965f3a5e7c06658e14bf8c35. --- .../Infrastructure/SnapshotValidationServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs b/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs index f389aad46..3b43f72ea 100644 --- a/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs +++ b/tests/MarginTradingTests/Infrastructure/SnapshotValidationServiceTests.cs @@ -63,7 +63,7 @@ public void SetUp() .Returns(() => _currentPositions.ToImmutableArray()); _ordersHistoryRepositoryMock.Setup(o => o.GetLastSnapshot(It.IsAny(), It.IsAny())) - .ReturnsAsync((DateTime date) => _ordersHistory); + .ReturnsAsync((DateTime from, DateTime? to) => _ordersHistory); _positionsHistoryRepositoryMock.Setup(o => o.GetLastSnapshot(It.IsAny())) .ReturnsAsync((DateTime date) => _positionsHistory); From fbaef236d3e7f5c085e09d5523875c665d5d51bc Mon Sep 17 00:00:00 2001 From: atarutin Date: Mon, 30 Oct 2023 12:22:30 +0300 Subject: [PATCH 10/25] fix(LT-5061): let special liquidation fail We'll let special liquidation fail if there were no price provided upon RFQ. The price request retry will happen on a later steps after checking liquidity. --- .../SpecialLiquidationSaga.cs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs index 2e8cfd2d2..73fe53ece 100644 --- a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs @@ -162,23 +162,9 @@ private async Task Handle(PriceForSpecialLiquidationCalculationFailedEvent e, IC var executionInfo = await _operationExecutionInfoRepository.GetAsync( operationName: OperationName, id: e.OperationId); - - if (executionInfo?.Data == null) - return; - - if (PriceRequestRetryRequired(executionInfo.Data.RequestedFromCorporateActions)) - { - var isDiscontinued = await FailIfInstrumentDiscontinued(executionInfo, sender); - if (isDiscontinued) return; - - var pauseAcknowledged = await _rfqPauseService.AcknowledgeAsync(executionInfo.Id); - if (pauseAcknowledged) return; - await InternalRetryPriceRequest(e.CreationTime, sender, executionInfo, - _marginTradingSettings.SpecialLiquidation.PriceRequestRetryTimeout.Value); - + if (executionInfo?.Data == null) return; - } if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.PriceRequested, SpecialLiquidationOperationState.OnTheWayToFail)) @@ -333,6 +319,9 @@ private async Task Handle(SpecialLiquidationFailedEvent e, ICommandSender sender if (PriceRequestRetryRequired(executionInfo.Data.RequestedFromCorporateActions)) { + var isDiscontinued = await FailIfInstrumentDiscontinued(executionInfo, sender); + if (isDiscontinued) return; + var pauseAcknowledged = await _rfqPauseService.AcknowledgeAsync(executionInfo.Id); if (pauseAcknowledged) return; From ee7fc35040932bdfd15dc0eee0c39a0ac19c2401 Mon Sep 17 00:00:00 2001 From: atarutin Date: Tue, 28 Nov 2023 17:35:57 +0300 Subject: [PATCH 11/25] fix(LT-5061): switch to sagas event handlers concept for SpecialLiquidationFailedEvent --- .../Modules/CqrsModule.cs | 4 + .../Workflow/ISagaEventHandler.cs | 77 +++++++++++++++++++ .../SpecialLiquidationSaga.cs | 4 + 3 files changed, 85 insertions(+) create mode 100644 src/MarginTrading.Backend.Services/Workflow/ISagaEventHandler.cs diff --git a/src/MarginTrading.Backend.Services/Modules/CqrsModule.cs b/src/MarginTrading.Backend.Services/Modules/CqrsModule.cs index 8ec883162..d74d048be 100644 --- a/src/MarginTrading.Backend.Services/Modules/CqrsModule.cs +++ b/src/MarginTrading.Backend.Services/Modules/CqrsModule.cs @@ -72,6 +72,10 @@ protected override void Load(ContainerBuilder builder) .SingleInstance(); builder.RegisterInstance(new CqrsContextNamesSettings()).AsSelf().SingleInstance(); + builder.RegisterType() + .As() + .InstancePerDependency(); + // Sagas & command handlers builder.RegisterAssemblyTypes(GetType().Assembly).Where(t => new[] { "Saga", "CommandsHandler", "Projection" }.Any(ending => t.Name.EndsWith(ending))).AsSelf(); diff --git a/src/MarginTrading.Backend.Services/Workflow/ISagaEventHandler.cs b/src/MarginTrading.Backend.Services/Workflow/ISagaEventHandler.cs new file mode 100644 index 000000000..16414e956 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/ISagaEventHandler.cs @@ -0,0 +1,77 @@ +// 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.Cqrs; +using MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Events; +using Microsoft.Extensions.Logging; + +namespace MarginTrading.Backend.Services.Workflow +{ + /// + /// Base interface for all saga event handlers + /// + public interface ISagaEventHandler + { + Task Handle(object @event, ICommandSender sender); + } + + /// + /// Marker interface for special liquidation saga only event handlers + /// + public interface ISpecialLiquidationSagaEventHandler : ISagaEventHandler + { + } + + /// + /// Base class for special liquidation saga event handler implementations + /// + /// + public abstract class SpecialLiquidationSagaEventHandler : ISpecialLiquidationSagaEventHandler + { + public Task Handle(object @event, ICommandSender sender) + { + if (@event is TEvent typedEvent) + { + return HandleEvent(typedEvent, sender); + } + + return Task.CompletedTask; + } + + protected abstract Task HandleEvent(TEvent @event, ICommandSender sender); + } + + /// + /// Event handler implementation for + /// + public class SpecialLiquidationFailedEventHandler : SpecialLiquidationSagaEventHandler + { + private readonly ILogger _logger; + + public SpecialLiquidationFailedEventHandler(ILogger logger) + { + _logger = logger; + } + + protected override Task HandleEvent(SpecialLiquidationFailedEvent @event, ICommandSender sender) + { + _logger.LogInformation("Special liquidation failed {@event}", @event); + return Task.CompletedTask; + } + } + + public static class EventHandlerExtensions + { + public static async Task HandleEvent(this IEnumerable handlers, + object @event, + ICommandSender sender) + { + foreach (var handler in handlers) + { + await handler.Handle(@event, sender); + } + } + } +} \ 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 index 73fe53ece..2feb76e89 100644 --- a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs @@ -43,6 +43,8 @@ public class SpecialLiquidationSaga public const string OperationName = "SpecialLiquidation"; + private readonly IEnumerable _eventHandlers; + public SpecialLiquidationSaga( IDateService dateService, IChaosKitty chaosKitty, @@ -289,6 +291,8 @@ private async Task Handle(SpecialLiquidationFinishedEvent e, ICommandSender send [UsedImplicitly] private async Task Handle(SpecialLiquidationFailedEvent e, ICommandSender sender) { + await _eventHandlers.HandleEvent(e, sender); + var executionInfo = await _operationExecutionInfoRepository.GetAsync( operationName: OperationName, id: e.OperationId); From c2bc1d13d75525128b08db63b1400576097d2e1f Mon Sep 17 00:00:00 2001 From: atarutin Date: Wed, 29 Nov 2023 14:32:10 +0300 Subject: [PATCH 12/25] fix(LT-5061): add handler for SpecialLiquidationFailedEvent --- .../Helpers/LiquidationHelper.cs | 110 ++++++++- .../Modules/CqrsModule.cs | 2 +- .../Services/RfqPauseService.cs | 32 +-- .../Workflow/ISagaEventHandler.cs | 65 +----- .../ISpecialLiquidationSagaEventHandler.cs | 12 + .../Workflow/SagaEventHandlerExtensions.cs | 47 ++++ .../SpecialLiquidationCommandsHandler.cs | 22 +- .../SpecialLiquidationSaga.cs | 215 +++--------------- .../SpecialLiquidationFailedEventHandler.cs | 133 +++++++++++ tests/MarginTradingTests/RfqPauseTests.cs | 22 +- 10 files changed, 369 insertions(+), 291 deletions(-) create mode 100644 src/MarginTrading.Backend.Services/Workflow/ISpecialLiquidationSagaEventHandler.cs create mode 100644 src/MarginTrading.Backend.Services/Workflow/SagaEventHandlerExtensions.cs create mode 100644 src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs diff --git a/src/MarginTrading.Backend.Services/Helpers/LiquidationHelper.cs b/src/MarginTrading.Backend.Services/Helpers/LiquidationHelper.cs index c90be0a68..81138a729 100644 --- a/src/MarginTrading.Backend.Services/Helpers/LiquidationHelper.cs +++ b/src/MarginTrading.Backend.Services/Helpers/LiquidationHelper.cs @@ -4,15 +4,23 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Common; +using Lykke.Cqrs; +using MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Commands; using MarginTrading.Backend.Core; using MarginTrading.Backend.Core.Exceptions; +using MarginTrading.Backend.Core.Extensions; 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.Infrastructure; +using MarginTrading.Backend.Services.Services; using MarginTrading.Backend.Services.Workflow.Liquidation.Commands; +using MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Commands; using MarginTrading.Common.Services; using MoreLinq; @@ -25,19 +33,34 @@ public class LiquidationHelper private readonly ICqrsSender _cqrsSender; private readonly OrdersCache _ordersCache; private readonly IAssetPairDayOffService _assetPairDayOffService; + private readonly IAssetPairsCache _assetPairsCache; + private readonly CqrsContextNamesSettings _cqrsContextNamesSettings; + private readonly MarginTradingSettings _marginTradingSettings; + private readonly IOperationExecutionInfoRepository _operationExecutionInfoRepository; + private readonly IRfqService _specialLiquidationService; public LiquidationHelper(IMatchingEngineRouter matchingEngineRouter, IDateService dateService, ICqrsSender cqrsSender, OrdersCache ordersCache, - IAssetPairDayOffService assetPairDayOffService) + IAssetPairDayOffService assetPairDayOffService, + IAssetPairsCache assetPairsCache, + CqrsContextNamesSettings cqrsContextNamesSettings, + IOperationExecutionInfoRepository operationExecutionInfoRepository, + IRfqService specialLiquidationService, + MarginTradingSettings marginTradingSettings) { _matchingEngineRouter = matchingEngineRouter; _dateService = dateService; _cqrsSender = cqrsSender; _ordersCache = ordersCache; _assetPairDayOffService = assetPairDayOffService; + _assetPairsCache = assetPairsCache; + _cqrsContextNamesSettings = cqrsContextNamesSettings; + _operationExecutionInfoRepository = operationExecutionInfoRepository; + _specialLiquidationService = specialLiquidationService; + _marginTradingSettings = marginTradingSettings; } public bool CheckIfNetVolumeCanBeLiquidated(string assetPairId, Position[] positions, @@ -138,6 +161,91 @@ public bool CheckIfNetVolumeCanBeLiquidated(string assetPairId, Position[] posit return result; } + + public async Task FailIfInstrumentDiscontinued(IOperationExecutionInfo executionInfo, ICommandSender sender) + { + var isDiscontinued = _assetPairsCache.GetAssetPairById(executionInfo.Data.Instrument).IsDiscontinued; + + if (isDiscontinued) + { + if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.PriceRequested, + SpecialLiquidationOperationState.OnTheWayToFail)) + { + sender.SendCommand(new FailSpecialLiquidationInternalCommand + { + OperationId = executionInfo.Id, + CreationTime = _dateService.Now(), + Reason = "Instrument discontinuation", + + }, _cqrsContextNamesSettings.TradingEngine); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + + return true; + } + + return false; + } + + public async Task InternalRetryPriceRequest(DateTime eventCreationTime, + ICommandSender sender, + IOperationExecutionInfo executionInfo, + TimeSpan retryTimeout) + { + // fix the intention to make another price request to not let the parallel + // ongoing GetPriceForSpecialLiquidationTimeoutInternalCommand execution + // break (fail) the flow + executionInfo.Data.NextRequestNumber(); + await _operationExecutionInfoRepository.Save(executionInfo); + + var shouldRetryAfter = eventCreationTime.Add(retryTimeout); + + var timeLeftBeforeRetry = shouldRetryAfter - _dateService.Now(); + + if (timeLeftBeforeRetry > TimeSpan.Zero) + { + await Task.Delay(timeLeftBeforeRetry); + } + + RequestPrice(sender, executionInfo); + } + + public void RequestPrice(ICommandSender sender, IOperationExecutionInfo + executionInfo) + { + //hack, requested by the bank + var positionsVolume = executionInfo.Data.Volume != 0 ? executionInfo.Data.Volume : 1; + + var command = new GetPriceForSpecialLiquidationCommand + { + OperationId = executionInfo.Id, + CreationTime = _dateService.Now(), + Instrument = executionInfo.Data.Instrument, + Volume = positionsVolume, + RequestNumber = executionInfo.Data.RequestNumber, + RequestedFromCorporateActions = executionInfo.Data.RequestedFromCorporateActions + }; + + if (_marginTradingSettings.ExchangeConnector == ExchangeConnectorType.RealExchangeConnector) + { + //send it to the Gavel + sender.SendCommand(command, _cqrsContextNamesSettings.Gavel); + } + else + { + _specialLiquidationService.SavePriceRequestForSpecialLiquidation(command); + } + + //special command is sent instantly for timeout control.. it is retried until timeout occurs + sender.SendCommand(new GetPriceForSpecialLiquidationTimeoutInternalCommand + { + OperationId = executionInfo.Id, + CreationTime = _dateService.Now(), + TimeoutSeconds = _marginTradingSettings.SpecialLiquidation.PriceRequestTimeoutSec, + RequestNumber = executionInfo.Data.RequestNumber + }, _cqrsContextNamesSettings.TradingEngine); + } public static string GetComment(LiquidationType liquidationType) { diff --git a/src/MarginTrading.Backend.Services/Modules/CqrsModule.cs b/src/MarginTrading.Backend.Services/Modules/CqrsModule.cs index d74d048be..0fbdcb2e7 100644 --- a/src/MarginTrading.Backend.Services/Modules/CqrsModule.cs +++ b/src/MarginTrading.Backend.Services/Modules/CqrsModule.cs @@ -73,7 +73,7 @@ protected override void Load(ContainerBuilder builder) builder.RegisterInstance(new CqrsContextNamesSettings()).AsSelf().SingleInstance(); builder.RegisterType() - .As() + .AsImplementedInterfaces() .InstancePerDependency(); // Sagas & command handlers diff --git a/src/MarginTrading.Backend.Services/Services/RfqPauseService.cs b/src/MarginTrading.Backend.Services/Services/RfqPauseService.cs index f249e6cb3..fd47987ba 100644 --- a/src/MarginTrading.Backend.Services/Services/RfqPauseService.cs +++ b/src/MarginTrading.Backend.Services/Services/RfqPauseService.cs @@ -73,7 +73,7 @@ public async Task AddAsync(string operationId, PauseSource so { var existingPause = (await _pauseRepository.FindAsync( operationId, - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, NotCancelledPredicate)) .SingleOrDefault(); @@ -84,7 +84,7 @@ public async Task AddAsync(string operationId, PauseSource so } var executionInfo = await _executionInfoRepository - .GetAsync(SpecialLiquidationSaga.OperationName, operationId); + .GetAsync(SpecialLiquidationSaga.Name, operationId); if (executionInfo == null) return RfqPauseErrorCode.NotFound; @@ -99,7 +99,7 @@ await _log.WriteWarningAsync(nameof(RfqPauseService), nameof(AddAsync), var pause = Pause.Create( operationId, - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, source, initiator, _dateService.Now()); @@ -123,7 +123,7 @@ public async Task GetCurrentAsync(string operationId) return (await _pauseRepository.FindAsync( operationId, - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, NotCancelledPredicate)) .SingleOrDefault(); } @@ -138,21 +138,21 @@ public async Task AcknowledgeAsync(string operationId) { var activePause = (await _pauseRepository.FindAsync( operationId, - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, ActivePredicate)) .SingleOrDefault(); if (activePause != null) { await _log.WriteInfoAsync(nameof(RfqPauseService), nameof(AcknowledgeAsync), null, - $"The pause for operation id [{operationId}] and name [{SpecialLiquidationSaga.OperationName}] is effective since [{activePause.EffectiveSince}]"); + $"The pause for operation id [{operationId}] and name [{SpecialLiquidationSaga.Name}] is effective since [{activePause.EffectiveSince}]"); return true; } var pendingPause = (await _pauseRepository.FindAsync( operationId, - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, PendingPredicate)) .SingleOrDefault(); @@ -173,7 +173,7 @@ await _log.WriteInfoAsync(nameof(RfqPauseService), nameof(AcknowledgeAsync), nul if (!updated) { await _log.WriteWarningAsync(nameof(RfqPauseService), nameof(AcknowledgeAsync), null, - $"Couldn't activate pending pause for operation id [{operationId}] and name [{SpecialLiquidationSaga.OperationName}]"); + $"Couldn't activate pending pause for operation id [{operationId}] and name [{SpecialLiquidationSaga.Name}]"); return false; } @@ -201,7 +201,7 @@ public async Task StopPendingAsync(string operationId, PauseCancellationSource s { var pendingPause = (await _pauseRepository.FindAsync( operationId, - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, PendingPredicate)) .SingleOrDefault(); @@ -222,7 +222,7 @@ public async Task StopPendingAsync(string operationId, PauseCancellationSource s if (!updated) { await _log.WriteWarningAsync(nameof(RfqPauseService), nameof(StopPendingAsync), null, - $"Couldn't stop pending pause for operation id [{operationId}] and name [{SpecialLiquidationSaga.OperationName}]"); + $"Couldn't stop pending pause for operation id [{operationId}] and name [{SpecialLiquidationSaga.Name}]"); } } @@ -244,7 +244,7 @@ public async Task AcknowledgeCancellationAsync(string operationId) { var pendingCancellationPause = (await _pauseRepository.FindAsync( operationId, - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, PendingCancellationPredicate)) .SingleOrDefault(); @@ -265,7 +265,7 @@ public async Task AcknowledgeCancellationAsync(string operationId) if (!updated) { await _log.WriteWarningAsync(nameof(RfqPauseService), nameof(AcknowledgeCancellationAsync), null, - $"Couldn't cancel pending cancellation pause for operation id [{operationId}] and name [{SpecialLiquidationSaga.OperationName}]"); + $"Couldn't cancel pending cancellation pause for operation id [{operationId}] and name [{SpecialLiquidationSaga.Name}]"); return false; } @@ -295,21 +295,21 @@ public async Task ResumeAsync(string operationId, PauseCance try { var executionInfo = await _executionInfoRepository - .GetAsync(SpecialLiquidationSaga.OperationName, operationId); + .GetAsync(SpecialLiquidationSaga.Name, operationId); if (executionInfo == null) return RfqResumeErrorCode.NotFound; var activePause = (await _pauseRepository.FindAsync( operationId, - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, ActivePredicate)) .SingleOrDefault(); if (activePause == null) { await _log.WriteInfoAsync(nameof(RfqPauseService), nameof(ResumeAsync), null, - $"The active pause for operation id [{operationId}] and name [{SpecialLiquidationSaga.OperationName}] was not found"); + $"The active pause for operation id [{operationId}] and name [{SpecialLiquidationSaga.Name}] was not found"); return RfqResumeErrorCode.NotPaused; } @@ -342,7 +342,7 @@ await _log.WriteWarningAsync(nameof(RfqPauseService), nameof(ResumeAsync), null, else { await _log.WriteWarningAsync(nameof(RfqPauseService), nameof(ResumeAsync), null, - $"Couldn't cancel active pause for operation id [{operationId}] and name [{SpecialLiquidationSaga.OperationName}] due to database issues"); + $"Couldn't cancel active pause for operation id [{operationId}] and name [{SpecialLiquidationSaga.Name}] due to database issues"); return RfqResumeErrorCode.Persistence; } diff --git a/src/MarginTrading.Backend.Services/Workflow/ISagaEventHandler.cs b/src/MarginTrading.Backend.Services/Workflow/ISagaEventHandler.cs index 16414e956..4b12319e0 100644 --- a/src/MarginTrading.Backend.Services/Workflow/ISagaEventHandler.cs +++ b/src/MarginTrading.Backend.Services/Workflow/ISagaEventHandler.cs @@ -1,77 +1,16 @@ // 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.Cqrs; -using MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Events; -using Microsoft.Extensions.Logging; namespace MarginTrading.Backend.Services.Workflow { /// /// Base interface for all saga event handlers /// - public interface ISagaEventHandler + public interface ISagaEventHandler { - Task Handle(object @event, ICommandSender sender); - } - - /// - /// Marker interface for special liquidation saga only event handlers - /// - public interface ISpecialLiquidationSagaEventHandler : ISagaEventHandler - { - } - - /// - /// Base class for special liquidation saga event handler implementations - /// - /// - public abstract class SpecialLiquidationSagaEventHandler : ISpecialLiquidationSagaEventHandler - { - public Task Handle(object @event, ICommandSender sender) - { - if (@event is TEvent typedEvent) - { - return HandleEvent(typedEvent, sender); - } - - return Task.CompletedTask; - } - - protected abstract Task HandleEvent(TEvent @event, ICommandSender sender); - } - - /// - /// Event handler implementation for - /// - public class SpecialLiquidationFailedEventHandler : SpecialLiquidationSagaEventHandler - { - private readonly ILogger _logger; - - public SpecialLiquidationFailedEventHandler(ILogger logger) - { - _logger = logger; - } - - protected override Task HandleEvent(SpecialLiquidationFailedEvent @event, ICommandSender sender) - { - _logger.LogInformation("Special liquidation failed {@event}", @event); - return Task.CompletedTask; - } - } - - public static class EventHandlerExtensions - { - public static async Task HandleEvent(this IEnumerable handlers, - object @event, - ICommandSender sender) - { - foreach (var handler in handlers) - { - await handler.Handle(@event, sender); - } - } + Task Handle(TEvent @event, ICommandSender sender); } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/ISpecialLiquidationSagaEventHandler.cs b/src/MarginTrading.Backend.Services/Workflow/ISpecialLiquidationSagaEventHandler.cs new file mode 100644 index 000000000..190c5fec6 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/ISpecialLiquidationSagaEventHandler.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +namespace MarginTrading.Backend.Services.Workflow +{ + /// + /// Marker interface for special liquidation saga event handlers + /// + public interface ISpecialLiquidationSagaEventHandler + { + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/SagaEventHandlerExtensions.cs b/src/MarginTrading.Backend.Services/Workflow/SagaEventHandlerExtensions.cs new file mode 100644 index 000000000..55e422fd4 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/SagaEventHandlerExtensions.cs @@ -0,0 +1,47 @@ +// 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.Cqrs; + +namespace MarginTrading.Backend.Services.Workflow +{ + public static class SagaEventHandlerExtensions + { + /// + /// Find handler which implements ISagaEventHandler{TEvent} by convention + /// + /// + /// + /// + /// When no handler found + public static ISagaEventHandler First( + this IEnumerable handlers) + { + foreach (var handler in handlers) + { + if (handler is ISagaEventHandler typedHandler) + { + return typedHandler; + } + } + + throw new KeyNotFoundException($"No handler for {typeof(TEvent)} found"); + } + + /// + /// Finds handler which implements ISagaEventHandler{TEvent} by convention and calls it + /// + /// + /// + /// + /// + /// + public static Task Handle( + this IEnumerable handlers, + TEvent @event, + ICommandSender sender) => + handlers.First().Handle(@event, sender); + } +} \ 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 index ec42ceda2..99e6df59c 100644 --- a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationCommandsHandler.cs +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationCommandsHandler.cs @@ -179,10 +179,10 @@ await _log.WriteWarningAsync( } var (executionInfo, _) = await _operationExecutionInfoRepository.GetOrAddAsync( - operationName: SpecialLiquidationSaga.OperationName, + operationName: SpecialLiquidationSaga.Name, operationId: command.OperationId, factory: () => new OperationExecutionInfo( - operationName: SpecialLiquidationSaga.OperationName, + operationName: SpecialLiquidationSaga.Name, id: command.OperationId, lastModified: _dateService.Now(), data: new SpecialLiquidationOperationData @@ -275,10 +275,10 @@ await _log.WriteWarningAsync( } var (executionInfo, _) = await _operationExecutionInfoRepository.GetOrAddAsync( - operationName: SpecialLiquidationSaga.OperationName, + operationName: SpecialLiquidationSaga.Name, operationId: command.OperationId, factory: () => new OperationExecutionInfo( - operationName: SpecialLiquidationSaga.OperationName, + operationName: SpecialLiquidationSaga.Name, id: command.OperationId, lastModified: _dateService.Now(), data: new SpecialLiquidationOperationData @@ -313,7 +313,7 @@ private async Task Handle(GetPriceForSpecialLiquidationTi IEventPublisher publisher) { var executionInfo = await _operationExecutionInfoRepository.GetAsync( - operationName: SpecialLiquidationSaga.OperationName, + operationName: SpecialLiquidationSaga.Name, id: command.OperationId); if (executionInfo?.Data != null) @@ -356,7 +356,7 @@ await _log.WriteInfoAsync(nameof(SpecialLiquidationCommandsHandler), nameof(GetP private async Task Handle(ExecuteSpecialLiquidationOrderCommand command, IEventPublisher publisher) { var executionInfo = await _operationExecutionInfoRepository.GetAsync( - operationName: SpecialLiquidationSaga.OperationName, + operationName: SpecialLiquidationSaga.Name, id: command.OperationId); if (executionInfo?.Data == null) @@ -515,7 +515,7 @@ await _log.WriteWarningAsync(nameof(SpecialLiquidationCommandsHandler), private async Task Handle(ExecuteSpecialLiquidationOrdersInternalCommand command, IEventPublisher publisher) { var executionInfo = await _operationExecutionInfoRepository.GetAsync( - operationName: SpecialLiquidationSaga.OperationName, + operationName: SpecialLiquidationSaga.Name, id: command.OperationId); if (executionInfo?.Data == null) @@ -570,7 +570,7 @@ await _tradingEngine.LiquidatePositionsUsingSpecialWorkflowAsync( private async Task Handle(FailSpecialLiquidationInternalCommand command, IEventPublisher publisher) { var executionInfo = await _operationExecutionInfoRepository.GetAsync( - operationName: SpecialLiquidationSaga.OperationName, + operationName: SpecialLiquidationSaga.Name, id: command.OperationId); if (executionInfo?.Data == null) @@ -598,7 +598,7 @@ private async Task Handle(FailSpecialLiquidationInternalCommand command, IEventP private async Task Handle(CancelSpecialLiquidationCommand command, IEventPublisher publisher) { var executionInfo = await _operationExecutionInfoRepository.GetAsync( - operationName: SpecialLiquidationSaga.OperationName, + operationName: SpecialLiquidationSaga.Name, id: command.OperationId); if (executionInfo?.Data == null) @@ -628,7 +628,7 @@ private async Task Handle(CancelSpecialLiquidationCommand command, IEventPublish private async Task Handle(ClosePositionsRegularFlowCommand command, IEventPublisher publisher) { var executionInfo = await _operationExecutionInfoRepository.GetAsync( - operationName: SpecialLiquidationSaga.OperationName, + operationName: SpecialLiquidationSaga.Name, id: command.OperationId); if (executionInfo?.Data == null) @@ -662,7 +662,7 @@ private bool TryGetExchangeNameFromPositions(IEnumerable positions, ou private async Task Handle(ResumePausedSpecialLiquidationCommand command, IEventPublisher publisher) { var executionInfo = await _operationExecutionInfoRepository.GetAsync( - operationName: SpecialLiquidationSaga.OperationName, + operationName: SpecialLiquidationSaga.Name, id: command.OperationId); if (executionInfo?.Data == null) diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs index 2feb76e89..182c8db8b 100644 --- a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs @@ -1,7 +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 System.Linq; using System.Threading.Tasks; @@ -31,17 +30,15 @@ public class SpecialLiquidationSaga private readonly IDateService _dateService; private readonly IChaosKitty _chaosKitty; private readonly IOperationExecutionInfoRepository _operationExecutionInfoRepository; - private readonly IRfqService _specialLiquidationService; - private readonly LiquidationHelper _liquidationHelper; private readonly OrdersCache _ordersCache; private readonly IRfqPauseService _rfqPauseService; - private readonly IAssetPairsCache _assetPairsCache; + private readonly LiquidationHelper _liquidationHelper; private readonly ILog _log; private readonly MarginTradingSettings _marginTradingSettings; private readonly CqrsContextNamesSettings _cqrsContextNamesSettings; - public const string OperationName = "SpecialLiquidation"; + public const string Name = "SpecialLiquidation"; private readonly IEnumerable _eventHandlers; @@ -52,30 +49,28 @@ public SpecialLiquidationSaga( IRfqService specialLiquidationService, MarginTradingSettings marginTradingSettings, CqrsContextNamesSettings cqrsContextNamesSettings, - LiquidationHelper liquidationHelper, OrdersCache ordersCache, IRfqPauseService rfqPauseService, ILog log, - IAssetPairsCache assetPairsCache) + IAssetPairsCache assetPairsCache, + LiquidationHelper liquidationHelper) { _dateService = dateService; _chaosKitty = chaosKitty; _operationExecutionInfoRepository = operationExecutionInfoRepository; - _specialLiquidationService = specialLiquidationService; _marginTradingSettings = marginTradingSettings; _cqrsContextNamesSettings = cqrsContextNamesSettings; - _liquidationHelper = liquidationHelper; _ordersCache = ordersCache; _rfqPauseService = rfqPauseService; _log = log; - _assetPairsCache = assetPairsCache; + _liquidationHelper = liquidationHelper; } [UsedImplicitly] private async Task Handle(SpecialLiquidationStartedInternalEvent e, ICommandSender sender) { var executionInfo = await _operationExecutionInfoRepository.GetAsync( - operationName: OperationName, + operationName: Name, id: e.OperationId); if (executionInfo?.Data == null) @@ -91,7 +86,7 @@ private async Task Handle(SpecialLiquidationStartedInternalEvent e, ICommandSend executionInfo.Data.RequestNumber = 1; executionInfo.Data.TryStartClosing(id => _ordersCache.Positions.GetPositionById(id), _dateService.Now); - RequestPrice(sender, executionInfo); + _liquidationHelper.RequestPrice(sender, executionInfo); _chaosKitty.Meow(e.OperationId); @@ -103,7 +98,7 @@ private async Task Handle(SpecialLiquidationStartedInternalEvent e, ICommandSend private async Task Handle(PriceForSpecialLiquidationCalculatedEvent e, ICommandSender sender) { var executionInfo = await _operationExecutionInfoRepository.GetAsync( - operationName: OperationName, + operationName: Name, id: e.OperationId); if (executionInfo?.Data == null) @@ -127,7 +122,7 @@ private async Task Handle(PriceForSpecialLiquidationCalculatedEvent e, ICommandS executionInfo.Data.NextRequestNumber(); - RequestPrice(sender, executionInfo); + _liquidationHelper.RequestPrice(sender, executionInfo); //switch state back, because we requested the price again and should handle in correctly when received executionInfo.Data.State = SpecialLiquidationOperationState.PriceRequested; @@ -162,7 +157,7 @@ await _rfqPauseService.StopPendingAsync(e.OperationId, PauseCancellationSource.P private async Task Handle(PriceForSpecialLiquidationCalculationFailedEvent e, ICommandSender sender) { var executionInfo = await _operationExecutionInfoRepository.GetAsync( - operationName: OperationName, + operationName: Name, id: e.OperationId); if (executionInfo?.Data == null) @@ -188,7 +183,7 @@ private async Task Handle(PriceForSpecialLiquidationCalculationFailedEvent e, IC private async Task Handle(SpecialLiquidationOrderExecutedEvent e, ICommandSender sender) { var executionInfo = await _operationExecutionInfoRepository.GetAsync( - operationName: OperationName, + operationName: Name, id: e.OperationId); if (executionInfo?.Data == null) @@ -219,21 +214,21 @@ private async Task Handle(SpecialLiquidationOrderExecutedEvent e, ICommandSender private async Task Handle(SpecialLiquidationOrderExecutionFailedEvent e, ICommandSender sender) { var executionInfo = await _operationExecutionInfoRepository.GetAsync( - operationName: OperationName, + operationName: Name, id: e.OperationId); if (executionInfo?.Data == null) return; - if (PriceRequestRetryRequired(executionInfo.Data.RequestedFromCorporateActions)) + if (PriceRequestRetryRequired(executionInfo.Data.RequestedFromCorporateActions, _marginTradingSettings.SpecialLiquidation)) { - var isDiscontinued = await FailIfInstrumentDiscontinued(executionInfo, sender); + var isDiscontinued = await _liquidationHelper.FailIfInstrumentDiscontinued(executionInfo, sender); if (isDiscontinued) return; if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.ExternalOrderExecuted, SpecialLiquidationOperationState.PriceRequested)) { - await InternalRetryPriceRequest(e.CreationTime, sender, executionInfo, + await _liquidationHelper.InternalRetryPriceRequest(e.CreationTime, sender, executionInfo, _marginTradingSettings.SpecialLiquidation.PriceRequestRetryTimeout.Value); return; @@ -260,7 +255,7 @@ await InternalRetryPriceRequest(e.CreationTime, sender, executionInfo, private async Task Handle(SpecialLiquidationFinishedEvent e, ICommandSender sender) { var executionInfo = await _operationExecutionInfoRepository.GetAsync( - operationName: OperationName, + operationName: Name, id: e.OperationId); if (executionInfo?.Data == null) @@ -289,83 +284,13 @@ private async Task Handle(SpecialLiquidationFinishedEvent e, ICommandSender send } [UsedImplicitly] - private async Task Handle(SpecialLiquidationFailedEvent e, ICommandSender sender) - { - await _eventHandlers.HandleEvent(e, sender); - - var executionInfo = await _operationExecutionInfoRepository.GetAsync( - operationName: OperationName, - id: e.OperationId); - - if (executionInfo?.Data == null) - return; - - if (e.CanRetryPriceRequest) - { - if (!executionInfo.Data.RequestedFromCorporateActions) - { - var positions = _ordersCache.Positions - .GetPositionsByAccountIds(executionInfo.Data.AccountId) - .Where(p => executionInfo.Data.PositionIds.Contains(p.Id)) - .ToArray(); - - if (_liquidationHelper.CheckIfNetVolumeCanBeLiquidated(executionInfo.Data.Instrument, positions, out _)) - { - // there is liquidity so we can cancel the special liquidation flow. - sender.SendCommand(new CancelSpecialLiquidationCommand - { - OperationId = e.OperationId, - Reason = "Liquidity is enough to close positions within regular flow" - }, _cqrsContextNamesSettings.TradingEngine); - return; - } - } - - if (PriceRequestRetryRequired(executionInfo.Data.RequestedFromCorporateActions)) - { - var isDiscontinued = await FailIfInstrumentDiscontinued(executionInfo, sender); - if (isDiscontinued) return; - - var pauseAcknowledged = await _rfqPauseService.AcknowledgeAsync(executionInfo.Id); - if (pauseAcknowledged) return; - - if (executionInfo.Data.SwitchState(executionInfo.Data.State, - SpecialLiquidationOperationState.PriceRequested)) - { - await InternalRetryPriceRequest(e.CreationTime, sender, executionInfo, - _marginTradingSettings.SpecialLiquidation.PriceRequestRetryTimeout.Value); - - 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); + private Task Handle(SpecialLiquidationFailedEvent e, ICommandSender sender) => _eventHandlers.Handle(e, sender); - await _operationExecutionInfoRepository.Save(executionInfo); - } - } - [UsedImplicitly] private async Task Handle(SpecialLiquidationCancelledEvent e, ICommandSender sender) { var executionInfo = await _operationExecutionInfoRepository.GetAsync( - operationName: OperationName, + operationName: Name, id: e.OperationId); if (executionInfo?.Data == null) @@ -397,7 +322,7 @@ private async Task Handle(SpecialLiquidationCancelledEvent e, ICommandSender sen private async Task Handle(ResumePausedSpecialLiquidationFailedEvent e, ICommandSender sender) { var executionInfo = await _operationExecutionInfoRepository.GetAsync( - operationName: OperationName, + operationName: Name, id: e.OperationId); if (executionInfo?.Data == null) @@ -411,21 +336,21 @@ await _log.WriteWarningAsync(nameof(SpecialLiquidationSaga), private async Task Handle(ResumePausedSpecialLiquidationSucceededEvent e, ICommandSender sender) { var executionInfo = await _operationExecutionInfoRepository.GetAsync( - operationName: OperationName, + operationName: Name, id: e.OperationId); if (executionInfo?.Data == null) return; - if (PriceRequestRetryRequired(executionInfo.Data.RequestedFromCorporateActions)) + if (PriceRequestRetryRequired(executionInfo.Data.RequestedFromCorporateActions, _marginTradingSettings.SpecialLiquidation)) { - var isDiscontinued = await FailIfInstrumentDiscontinued(executionInfo, sender); + var isDiscontinued = await _liquidationHelper.FailIfInstrumentDiscontinued(executionInfo, sender); if (isDiscontinued) return; if (executionInfo.Data.SwitchState(executionInfo.Data.State, SpecialLiquidationOperationState.PriceRequested)) { - await InternalRetryPriceRequest(e.CreationTime, sender, executionInfo, + await _liquidationHelper.InternalRetryPriceRequest(e.CreationTime, sender, executionInfo, _marginTradingSettings.SpecialLiquidation.PriceRequestRetryTimeout.Value); return; @@ -439,7 +364,7 @@ await InternalRetryPriceRequest(e.CreationTime, sender, executionInfo, { OperationId = e.OperationId, CreationTime = _dateService.Now(), - Reason = $"Pause cancellation succeeded but then price request was not initiated for operation id [{e.OperationId} and name [{OperationName}]]" + Reason = $"Pause cancellation succeeded but then price request was not initiated for operation id [{e.OperationId} and name [{Name}]]" }, _cqrsContextNamesSettings.TradingEngine); _chaosKitty.Meow(e.OperationId); @@ -458,94 +383,8 @@ private decimal GetActualNetPositionCloseVolume(ICollection positionIds, return -netPositionVolume; } - private void RequestPrice(ICommandSender sender, IOperationExecutionInfo - executionInfo) - { - //hack, requested by the bank - var positionsVolume = executionInfo.Data.Volume != 0 ? executionInfo.Data.Volume : 1; - - var command = new GetPriceForSpecialLiquidationCommand - { - OperationId = executionInfo.Id, - CreationTime = _dateService.Now(), - Instrument = executionInfo.Data.Instrument, - Volume = positionsVolume, - RequestNumber = executionInfo.Data.RequestNumber, - RequestedFromCorporateActions = executionInfo.Data.RequestedFromCorporateActions - }; - - if (_marginTradingSettings.ExchangeConnector == ExchangeConnectorType.RealExchangeConnector) - { - //send it to the Gavel - sender.SendCommand(command, _cqrsContextNamesSettings.Gavel); - } - else - { - _specialLiquidationService.SavePriceRequestForSpecialLiquidation(command); - } - - //special command is sent instantly for timeout control.. it is retried until timeout occurs - sender.SendCommand(new GetPriceForSpecialLiquidationTimeoutInternalCommand - { - OperationId = executionInfo.Id, - CreationTime = _dateService.Now(), - TimeoutSeconds = _marginTradingSettings.SpecialLiquidation.PriceRequestTimeoutSec, - RequestNumber = executionInfo.Data.RequestNumber - }, _cqrsContextNamesSettings.TradingEngine); - } - - private bool PriceRequestRetryRequired(bool requestedFromCorporateActions) => - _marginTradingSettings.SpecialLiquidation.PriceRequestRetryTimeout.HasValue && - (!requestedFromCorporateActions || - _marginTradingSettings.SpecialLiquidation.RetryPriceRequestForCorporateActions); - - private async Task InternalRetryPriceRequest(DateTime eventCreationTime, - ICommandSender sender, - IOperationExecutionInfo executionInfo, - TimeSpan retryTimeout) - { - // fix the intention to make another price request to not let the parallel - // ongoing GetPriceForSpecialLiquidationTimeoutInternalCommand execution - // break (fail) the flow - executionInfo.Data.NextRequestNumber(); - await _operationExecutionInfoRepository.Save(executionInfo); - - var shouldRetryAfter = eventCreationTime.Add(retryTimeout); - - var timeLeftBeforeRetry = shouldRetryAfter - _dateService.Now(); - - if (timeLeftBeforeRetry > TimeSpan.Zero) - { - await Task.Delay(timeLeftBeforeRetry); - } - - RequestPrice(sender, executionInfo); - } - - private async Task FailIfInstrumentDiscontinued(IOperationExecutionInfo executionInfo, ICommandSender sender) - { - var isDiscontinued = _assetPairsCache.GetAssetPairById(executionInfo.Data.Instrument).IsDiscontinued; - - if (isDiscontinued) - { - if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.PriceRequested, - SpecialLiquidationOperationState.OnTheWayToFail)) - { - sender.SendCommand(new FailSpecialLiquidationInternalCommand - { - OperationId = executionInfo.Id, - CreationTime = _dateService.Now(), - Reason = "Instrument discontinuation", - - }, _cqrsContextNamesSettings.TradingEngine); - - await _operationExecutionInfoRepository.Save(executionInfo); - } - - return true; - } - - return false; - } + internal static bool PriceRequestRetryRequired(bool requestedFromCorporateActions, SpecialLiquidationSettings specialLiquidationSettings) => + specialLiquidationSettings.PriceRequestRetryTimeout.HasValue && + (!requestedFromCorporateActions || specialLiquidationSettings.RetryPriceRequestForCorporateActions); } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs new file mode 100644 index 000000000..0ff41332e --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs @@ -0,0 +1,133 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Threading.Tasks; +using Lykke.Common.Chaos; +using Lykke.Cqrs; +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.Settings; +using MarginTrading.Backend.Services.Helpers; +using MarginTrading.Backend.Services.Services; +using MarginTrading.Backend.Services.Workflow.Liquidation.Commands; +using MarginTrading.Backend.Services.Workflow.SpecialLiquidation; +using MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Commands; +using MarginTrading.Common.Services; +using Microsoft.Extensions.Logging; + +namespace MarginTrading.Backend.Services.Workflow +{ + /// + /// Event handler implementation for + /// + public class SpecialLiquidationFailedEventHandler : + ISagaEventHandler, + ISpecialLiquidationSagaEventHandler + { + private readonly IDateService _dateService; + private readonly IChaosKitty _chaosKitty; + private readonly IOperationExecutionInfoRepository _operationExecutionInfoRepository; + private readonly OrdersCache _ordersCache; + private readonly LiquidationHelper _liquidationHelper; + private readonly CqrsContextNamesSettings _cqrsContextNamesSettings; + private readonly MarginTradingSettings _marginTradingSettings; + private readonly IRfqPauseService _rfqPauseService; + private readonly ILogger _logger; + + public SpecialLiquidationFailedEventHandler(ILogger logger, + IOperationExecutionInfoRepository operationExecutionInfoRepository, + OrdersCache ordersCache, + LiquidationHelper liquidationHelper, + CqrsContextNamesSettings cqrsContextNamesSettings, + IRfqPauseService rfqPauseService, + IDateService dateService, + IChaosKitty chaosKitty, + MarginTradingSettings marginTradingSettings) + { + _logger = logger; + _operationExecutionInfoRepository = operationExecutionInfoRepository; + _ordersCache = ordersCache; + _liquidationHelper = liquidationHelper; + _cqrsContextNamesSettings = cqrsContextNamesSettings; + _rfqPauseService = rfqPauseService; + _dateService = dateService; + _chaosKitty = chaosKitty; + _marginTradingSettings = marginTradingSettings; + } + + public async Task Handle(SpecialLiquidationFailedEvent @event, ICommandSender sender) + { + var executionInfo = await _operationExecutionInfoRepository.GetAsync( + // todo: remove this dependency on SpecialLiquidationSaga class + operationName: SpecialLiquidationSaga.Name, + id: @event.OperationId); + + if (executionInfo?.Data == null) + return; + + if (@event.CanRetryPriceRequest) + { + if (!executionInfo.Data.RequestedFromCorporateActions) + { + var positions = _ordersCache.Positions + .GetPositionsByAccountIds(executionInfo.Data.AccountId) + .Where(p => executionInfo.Data.PositionIds.Contains(p.Id)) + .ToArray(); + + if (_liquidationHelper.CheckIfNetVolumeCanBeLiquidated(executionInfo.Data.Instrument, positions, out _)) + { + // there is liquidity so we can cancel the special liquidation flow. + sender.SendCommand(new CancelSpecialLiquidationCommand + { + OperationId = @event.OperationId, + Reason = "Liquidity is enough to close positions within regular flow" + }, _cqrsContextNamesSettings.TradingEngine); + return; + } + } + + if (SpecialLiquidationSaga.PriceRequestRetryRequired(executionInfo.Data.RequestedFromCorporateActions, + _marginTradingSettings.SpecialLiquidation)) + { + var isDiscontinued = await _liquidationHelper.FailIfInstrumentDiscontinued(executionInfo, sender); + if (isDiscontinued) return; + + var pauseAcknowledged = await _rfqPauseService.AcknowledgeAsync(executionInfo.Id); + if (pauseAcknowledged) return; + + if (executionInfo.Data.SwitchState(executionInfo.Data.State, + SpecialLiquidationOperationState.PriceRequested)) + { + await _liquidationHelper.InternalRetryPriceRequest(@event.CreationTime, sender, executionInfo, + _marginTradingSettings.SpecialLiquidation.PriceRequestRetryTimeout.Value); + + 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: {@event.Reason}", + IsCausedBySpecialLiquidation = true, + CausationOperationId = executionInfo.Id + }, _cqrsContextNamesSettings.TradingEngine); + } + + _chaosKitty.Meow(@event.OperationId); + + await _operationExecutionInfoRepository.Save(executionInfo); + } + } + } +} \ No newline at end of file diff --git a/tests/MarginTradingTests/RfqPauseTests.cs b/tests/MarginTradingTests/RfqPauseTests.cs index 4e017e677..b12e6814c 100644 --- a/tests/MarginTradingTests/RfqPauseTests.cs +++ b/tests/MarginTradingTests/RfqPauseTests.cs @@ -106,7 +106,7 @@ public async Task Acknowledge_When_Active_Pause_Exists_Returns_Success() _repositoryPauseMock .Setup(x => x.FindAsync( "active", - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, It.IsAny>())) .ReturnsAsync(new[] { GetPause(PauseState.Active) }); @@ -124,7 +124,7 @@ public async Task Acknowledge_When_Pending_Pause_Exists_Updates_It_And_Returns_S _repositoryPauseMock .Setup(x => x.FindAsync( "pending", - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, RfqPauseService.ActivePredicate)) .ReturnsAsync(Enumerable.Empty()); @@ -133,7 +133,7 @@ public async Task Acknowledge_When_Pending_Pause_Exists_Updates_It_And_Returns_S _repositoryPauseMock .Setup(x => x.FindAsync( "pending", - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, RfqPauseService.PendingPredicate)) .ReturnsAsync(new[] { GetPersistedPause(PauseState.Pending) }); @@ -173,7 +173,7 @@ public async Task Acknowledge_When_No_Pause_Exists_Returns_Failure() _repositoryPauseMock .Setup(x => x.FindAsync( It.IsAny(), - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, It.IsAny>())) .ReturnsAsync(Enumerable.Empty()); @@ -191,7 +191,7 @@ public async Task StopPending_When_Pending_Exists_Updates_State_To_Cancelled() _repositoryPauseMock .Setup(x => x.FindAsync( "pending", - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, RfqPauseService.PendingPredicate)) .ReturnsAsync(new[] { GetPersistedPause(PauseState.Pending) }); @@ -217,7 +217,7 @@ public async Task AcknowledgeCancellation_When_PendingCancellation_Exists_Update _repositoryPauseMock .Setup(x => x.FindAsync( "pending cancellation", - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, RfqPauseService.PendingCancellationPredicate)) .ReturnsAsync(new[] { GetPersistedPause(PauseState.PendingCancellation) }); @@ -257,7 +257,7 @@ public async Task AcknowledgeCancellation_When_NoPause_Exists_Returns_Failure() _repositoryPauseMock .Setup(x => x.FindAsync( It.IsAny(), - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, It.IsAny>())) .ReturnsAsync(Enumerable.Empty()); @@ -301,7 +301,7 @@ public async Task Resume_When_ThereIsNo_Active_Pause_Returns_Error() _repositoryPauseMock .Setup(x => x.FindAsync( It.IsAny(), - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, RfqPauseService.ActivePredicate)) .ReturnsAsync(Enumerable.Empty()); @@ -328,7 +328,7 @@ public async Task Resume_Manually_When_Paused_Not_Manually_Returns_Error() _repositoryPauseMock .Setup(x => x.FindAsync( "active", - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, RfqPauseService.ActivePredicate)) .ReturnsAsync(new[] { GetPersistedPause(PauseState.Active, PauseSource.TradingDisabled) }); @@ -355,7 +355,7 @@ public async Task Resume_When_ActivePauseExists_Updates_It_And_Returns_Success() _repositoryPauseMock .Setup(x => x.FindAsync( "active", - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, RfqPauseService.ActivePredicate)) .ReturnsAsync(new[] { GetPersistedPause(PauseState.Active) }); @@ -514,7 +514,7 @@ private static Pause GetPersistedPause(PauseState? state = null, PauseSource? so private static IOperationExecutionInfo GetExecutionInfoWithState(SpecialLiquidationOperationState? state = null) { var result = new OperationExecutionInfo( - SpecialLiquidationSaga.OperationName, + SpecialLiquidationSaga.Name, "id", DateTime.UtcNow, new SpecialLiquidationOperationData()); From 3979cd618ac90a11dde2ab4aa00c963948f42a19 Mon Sep 17 00:00:00 2001 From: atarutin Date: Wed, 29 Nov 2023 15:33:32 +0300 Subject: [PATCH 13/25] fix(LT-5061): fix unit tests --- tests/MarginTradingTests/BaseTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/MarginTradingTests/BaseTests.cs b/tests/MarginTradingTests/BaseTests.cs index 3a0d0acfe..f2ec1fa98 100644 --- a/tests/MarginTradingTests/BaseTests.cs +++ b/tests/MarginTradingTests/BaseTests.cs @@ -188,6 +188,7 @@ private void RegisterDependenciesCore(bool mockEvents = false) var exchangeConnector = Mock.Of(); builder.RegisterInstance(exchangeConnector).As(); builder.RegisterInstance(Mock.Of()).As(); + builder.RegisterInstance(Mock.Of()).As(); builder.RegisterBuildCallback(c => { From 3663462405d6a3940474f29321e26987da33e2f9 Mon Sep 17 00:00:00 2001 From: atarutin Date: Wed, 29 Nov 2023 16:22:03 +0300 Subject: [PATCH 14/25] fix(LT-5061): inject special liquidation event handlers --- .../Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs index 182c8db8b..d7f7f4eb2 100644 --- a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs @@ -53,7 +53,8 @@ public SpecialLiquidationSaga( IRfqPauseService rfqPauseService, ILog log, IAssetPairsCache assetPairsCache, - LiquidationHelper liquidationHelper) + LiquidationHelper liquidationHelper, + IEnumerable eventHandlers) { _dateService = dateService; _chaosKitty = chaosKitty; @@ -64,6 +65,7 @@ public SpecialLiquidationSaga( _rfqPauseService = rfqPauseService; _log = log; _liquidationHelper = liquidationHelper; + _eventHandlers = eventHandlers; } [UsedImplicitly] From fa63b60511b3e22713b6f5309fde5bcf34969cd2 Mon Sep 17 00:00:00 2001 From: atarutin Date: Thu, 30 Nov 2023 13:43:33 +0300 Subject: [PATCH 15/25] fix(LT-5061): streamline SpecialLiquidationFailedEvent processing logic (wip) --- .../Extensions/SagaExtensions.cs | 14 +- .../SpecialLiquidationFailedEventHandler.cs | 123 ++++++++++-------- 2 files changed, 77 insertions(+), 60 deletions(-) diff --git a/src/MarginTrading.Backend.Core/Extensions/SagaExtensions.cs b/src/MarginTrading.Backend.Core/Extensions/SagaExtensions.cs index cdf4e6fdc..67bb35af0 100644 --- a/src/MarginTrading.Backend.Core/Extensions/SagaExtensions.cs +++ b/src/MarginTrading.Backend.Core/Extensions/SagaExtensions.cs @@ -28,7 +28,7 @@ public static bool SwitchState(this OperationDataBase data, TSta if (Convert.ToInt32(data.State) > Convert.ToInt32(expectedState)) { - LogLocator.CommonLog.WriteWarning(nameof(SagaExtensions), nameof(SwitchState), + LogLocator.CommonLog.WriteWarning(nameof(SagaExtensions), nameof(SwitchToState), $"Operation is already in the next state, so this event is ignored, {new {data, expectedState, nextState}.ToJson()}."); return false; } @@ -38,6 +38,14 @@ public static bool SwitchState(this OperationDataBase data, TSta return true; } + public static bool SwitchToState(this OperationDataBase data, + SpecialLiquidationOperationState nextState) => + data.SwitchState(data.State, nextState); + + public static bool SwitchToState(this IOperationExecutionInfo info, + SpecialLiquidationOperationState nextState) => + info.Data.SwitchToState(nextState); + public static bool SwitchState(this OperationDataBase data, SpecialLiquidationOperationState expectedState, SpecialLiquidationOperationState nextState) { @@ -55,7 +63,7 @@ public static bool SwitchState(this OperationDataBase Convert.ToInt32(expectedState)) { - LogLocator.CommonLog.WriteWarning(nameof(SagaExtensions), nameof(SwitchState), + LogLocator.CommonLog.WriteWarning(nameof(SagaExtensions), nameof(SwitchToState), $"Operation is already in the next state, so this event is ignored, {new {data, expectedState, nextState}.ToJson()}."); return false; } @@ -63,7 +71,7 @@ public static bool SwitchState(this OperationDataBase executionInfo, + DateTime timestamp) + { + sender.SendCommand(new ResumeLiquidationInternalCommand + { + OperationId = executionInfo.Data.CausationOperationId, + CreationTime = timestamp, + Comment = $"Resume after special liquidation {executionInfo.Id} failed.", + IsCausedBySpecialLiquidation = true, + CausationOperationId = executionInfo.Id + }, "TradingEngine"); + } + } + /// /// Event handler implementation for /// @@ -35,10 +52,8 @@ public class SpecialLiquidationFailedEventHandler : private readonly CqrsContextNamesSettings _cqrsContextNamesSettings; private readonly MarginTradingSettings _marginTradingSettings; private readonly IRfqPauseService _rfqPauseService; - private readonly ILogger _logger; - public SpecialLiquidationFailedEventHandler(ILogger logger, - IOperationExecutionInfoRepository operationExecutionInfoRepository, + public SpecialLiquidationFailedEventHandler(IOperationExecutionInfoRepository operationExecutionInfoRepository, OrdersCache ordersCache, LiquidationHelper liquidationHelper, CqrsContextNamesSettings cqrsContextNamesSettings, @@ -47,7 +62,6 @@ public SpecialLiquidationFailedEventHandler(ILogger( + private Task> GetExecutionInfo(string operationId) => + _operationExecutionInfoRepository.GetAsync( // todo: remove this dependency on SpecialLiquidationSaga class operationName: SpecialLiquidationSaga.Name, - id: @event.OperationId); + operationId); + + public async Task Handle(SpecialLiquidationFailedEvent @event, ICommandSender sender) + { + var eInfo = await GetExecutionInfo(@event.OperationId); - if (executionInfo?.Data == null) + if (eInfo?.Data == null) return; - if (@event.CanRetryPriceRequest) + var isDiscontinued = await _liquidationHelper.FailIfInstrumentDiscontinued(eInfo, sender); + if (isDiscontinued) return; + + if (!eInfo.Data.RequestedFromCorporateActions) { - if (!executionInfo.Data.RequestedFromCorporateActions) + var positions = _ordersCache.Positions + .GetPositionsByAccountIds(eInfo.Data.AccountId) + .Where(p => eInfo.Data.PositionIds.Contains(p.Id)) + .ToArray(); + + if (_liquidationHelper.CheckIfNetVolumeCanBeLiquidated(eInfo.Data.Instrument, positions, out _)) { - var positions = _ordersCache.Positions - .GetPositionsByAccountIds(executionInfo.Data.AccountId) - .Where(p => executionInfo.Data.PositionIds.Contains(p.Id)) - .ToArray(); - - if (_liquidationHelper.CheckIfNetVolumeCanBeLiquidated(executionInfo.Data.Instrument, positions, out _)) + // there is liquidity so we can cancel the special liquidation flow. + sender.SendCommand(new CancelSpecialLiquidationCommand { - // there is liquidity so we can cancel the special liquidation flow. - sender.SendCommand(new CancelSpecialLiquidationCommand - { - OperationId = @event.OperationId, - Reason = "Liquidity is enough to close positions within regular flow" - }, _cqrsContextNamesSettings.TradingEngine); - return; - } + OperationId = @event.OperationId, + Reason = "Liquidity is enough to close positions within regular flow" + }, _cqrsContextNamesSettings.TradingEngine); + return; } - - if (SpecialLiquidationSaga.PriceRequestRetryRequired(executionInfo.Data.RequestedFromCorporateActions, - _marginTradingSettings.SpecialLiquidation)) + } + + if (SpecialLiquidationSaga.PriceRequestRetryRequired( + eInfo.Data.RequestedFromCorporateActions, + _marginTradingSettings.SpecialLiquidation) && + @event.CanRetryPriceRequest) + { + var pauseAcknowledged = await _rfqPauseService.AcknowledgeAsync(eInfo.Id); + if (pauseAcknowledged) return; + + if (eInfo.SwitchToState(SpecialLiquidationOperationState.PriceRequested)) { - var isDiscontinued = await _liquidationHelper.FailIfInstrumentDiscontinued(executionInfo, sender); - if (isDiscontinued) return; - - var pauseAcknowledged = await _rfqPauseService.AcknowledgeAsync(executionInfo.Id); - if (pauseAcknowledged) return; - - if (executionInfo.Data.SwitchState(executionInfo.Data.State, - SpecialLiquidationOperationState.PriceRequested)) - { - await _liquidationHelper.InternalRetryPriceRequest(@event.CreationTime, sender, executionInfo, - _marginTradingSettings.SpecialLiquidation.PriceRequestRetryTimeout.Value); - - return; - } + await _liquidationHelper.InternalRetryPriceRequest(@event.CreationTime, sender, eInfo, + _marginTradingSettings.SpecialLiquidation.PriceRequestRetryTimeout.Value); + + await _operationExecutionInfoRepository.Save(eInfo); + + return; } } - - if (executionInfo.Data.SwitchState(executionInfo.Data.State,//from any state - SpecialLiquidationOperationState.Failed)) + + if (eInfo.SwitchToState(SpecialLiquidationOperationState.Failed)) { - if (!string.IsNullOrEmpty(executionInfo.Data.CausationOperationId)) + if (!string.IsNullOrEmpty(eInfo.Data.CausationOperationId)) { - sender.SendCommand(new ResumeLiquidationInternalCommand - { - OperationId = executionInfo.Data.CausationOperationId, - CreationTime = _dateService.Now(), - Comment = $"Resume after special liquidation {executionInfo.Id} failed. Reason: {@event.Reason}", - IsCausedBySpecialLiquidation = true, - CausationOperationId = executionInfo.Id - }, _cqrsContextNamesSettings.TradingEngine); + sender.SendResume(eInfo, _dateService.Now()); } _chaosKitty.Meow(@event.OperationId); - - await _operationExecutionInfoRepository.Save(executionInfo); + + await _operationExecutionInfoRepository.Save(eInfo); } } } From 749022b28fd98972c6d43b489350908f4ef1a572 Mon Sep 17 00:00:00 2001 From: atarutin Date: Mon, 4 Dec 2023 17:34:11 +0300 Subject: [PATCH 16/25] refactor(LT-5061): improve readability of SpecialLiquidationFailedEventHandler --- .../Modules/CqrsModule.cs | 2 +- .../Workflow/ISagaEventHandler.cs | 2 + .../Workflow/SagaEventHandlerExtensions.cs | 12 +- .../SpecialLiquidationSaga.cs | 11 - ...ecialLiquidationCommandSenderExtensions.cs | 60 +++++ .../SpecialLiquidationFailedEventHandler.cs | 241 ++++++++++++------ ...ecialLiquidationFailedEventHandlerTests.cs | 140 ++++++++++ 7 files changed, 372 insertions(+), 96 deletions(-) create mode 100644 src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationCommandSenderExtensions.cs create mode 100644 tests/MarginTradingTests/WorkflowTests/SpecialLiquidationFailedEventHandlerTests.cs diff --git a/src/MarginTrading.Backend.Services/Modules/CqrsModule.cs b/src/MarginTrading.Backend.Services/Modules/CqrsModule.cs index 0fbdcb2e7..67fb960a7 100644 --- a/src/MarginTrading.Backend.Services/Modules/CqrsModule.cs +++ b/src/MarginTrading.Backend.Services/Modules/CqrsModule.cs @@ -74,7 +74,7 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType() .AsImplementedInterfaces() - .InstancePerDependency(); + .SingleInstance(); // Sagas & command handlers builder.RegisterAssemblyTypes(GetType().Assembly).Where(t => diff --git a/src/MarginTrading.Backend.Services/Workflow/ISagaEventHandler.cs b/src/MarginTrading.Backend.Services/Workflow/ISagaEventHandler.cs index 4b12319e0..4575df0da 100644 --- a/src/MarginTrading.Backend.Services/Workflow/ISagaEventHandler.cs +++ b/src/MarginTrading.Backend.Services/Workflow/ISagaEventHandler.cs @@ -12,5 +12,7 @@ namespace MarginTrading.Backend.Services.Workflow public interface ISagaEventHandler { Task Handle(TEvent @event, ICommandSender sender); + + Task CanHandle(TEvent @event) => Task.FromResult(true); } } \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/SagaEventHandlerExtensions.cs b/src/MarginTrading.Backend.Services/Workflow/SagaEventHandlerExtensions.cs index 55e422fd4..e01c9fbc5 100644 --- a/src/MarginTrading.Backend.Services/Workflow/SagaEventHandlerExtensions.cs +++ b/src/MarginTrading.Backend.Services/Workflow/SagaEventHandlerExtensions.cs @@ -38,10 +38,16 @@ public static ISagaEventHandler First( /// /// /// - public static Task Handle( + /// Before calling handler checks if it can handle event + public static async Task Handle( this IEnumerable handlers, TEvent @event, - ICommandSender sender) => - handlers.First().Handle(@event, sender); + ICommandSender sender) + { + var firstHandler = handlers.First(); + + if (await firstHandler.CanHandle(@event)) + await firstHandler.Handle(@event, sender); + } } } \ 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 index d7f7f4eb2..db18afb93 100644 --- a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs @@ -2,7 +2,6 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Common.Log; using JetBrains.Annotations; @@ -375,16 +374,6 @@ await _liquidationHelper.InternalRetryPriceRequest(e.CreationTime, sender, execu } } - private decimal GetActualNetPositionCloseVolume(ICollection positionIds, string accountId) - { - var netPositionVolume = _ordersCache.GetPositions() - .Where(x => positionIds.Contains(x.Id) - && (string.IsNullOrEmpty(accountId) || x.AccountId == accountId)) - .Sum(x => x.Volume); - - return -netPositionVolume; - } - internal static bool PriceRequestRetryRequired(bool requestedFromCorporateActions, SpecialLiquidationSettings specialLiquidationSettings) => specialLiquidationSettings.PriceRequestRetryTimeout.HasValue && (!requestedFromCorporateActions || specialLiquidationSettings.RetryPriceRequestForCorporateActions); diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationCommandSenderExtensions.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationCommandSenderExtensions.cs new file mode 100644 index 000000000..6c798ddd6 --- /dev/null +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationCommandSenderExtensions.cs @@ -0,0 +1,60 @@ +// Copyright (c) 2019 Lykke Corp. +// See the LICENSE file in the project root for more information. + +using System; +using Lykke.Cqrs; +using MarginTrading.Backend.Services.Workflow.Liquidation.Commands; +using MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Commands; + +namespace MarginTrading.Backend.Services.Workflow +{ + public static class SpecialLiquidationCommandSenderExtensions + { + /// + /// Sends a command to resume the initial flow (liquidation) + /// + /// + /// + /// + /// + /// + public static void SendResumeLiquidation(this ICommandSender sender, + string liquidationId, + string specialLiquidationId, + DateTime timestamp) + { + if (string.IsNullOrWhiteSpace(liquidationId)) + throw new ArgumentNullException(nameof(liquidationId)); + + if (string.IsNullOrWhiteSpace(specialLiquidationId)) + throw new ArgumentNullException(nameof(specialLiquidationId)); + + sender.SendCommand(new ResumeLiquidationInternalCommand + { + OperationId = liquidationId, + CreationTime = timestamp, + Comment = $"Resume after special liquidation {specialLiquidationId} failed.", + IsCausedBySpecialLiquidation = true, + CausationOperationId = specialLiquidationId + }, "TradingEngine"); + } + + /// + /// Sends a command to cancel special liquidation + /// + /// + /// + /// + public static void SendCancellation(this ICommandSender sender, string liquidationId) + { + if (string.IsNullOrWhiteSpace(liquidationId)) + throw new ArgumentNullException(nameof(liquidationId)); + + sender.SendCommand(new CancelSpecialLiquidationCommand + { + OperationId = liquidationId, + Reason = "Liquidity is enough to close positions within regular flow" + }, "TradingEngine"); + } + } +} \ No newline at end of file diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs index d195ee867..bc5fc9dd1 100644 --- a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs @@ -2,9 +2,10 @@ // 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 Lykke.Common.Chaos; +using JetBrains.Annotations; using Lykke.Cqrs; using MarginTrading.Backend.Contracts.Workflow.SpecialLiquidation.Events; using MarginTrading.Backend.Core; @@ -13,30 +14,12 @@ using MarginTrading.Backend.Core.Settings; using MarginTrading.Backend.Services.Helpers; using MarginTrading.Backend.Services.Services; -using MarginTrading.Backend.Services.Workflow.Liquidation.Commands; using MarginTrading.Backend.Services.Workflow.SpecialLiquidation; -using MarginTrading.Backend.Services.Workflow.SpecialLiquidation.Commands; using MarginTrading.Common.Services; +using ExecutionInfo = MarginTrading.Backend.Core.IOperationExecutionInfo; namespace MarginTrading.Backend.Services.Workflow { - public static class SpecialLiquidationCommandSenderExtensions - { - public static void SendResume(this ICommandSender sender, - IOperationExecutionInfo executionInfo, - DateTime timestamp) - { - sender.SendCommand(new ResumeLiquidationInternalCommand - { - OperationId = executionInfo.Data.CausationOperationId, - CreationTime = timestamp, - Comment = $"Resume after special liquidation {executionInfo.Id} failed.", - IsCausedBySpecialLiquidation = true, - CausationOperationId = executionInfo.Id - }, "TradingEngine"); - } - } - /// /// Event handler implementation for /// @@ -44,99 +27,195 @@ public class SpecialLiquidationFailedEventHandler : ISagaEventHandler, ISpecialLiquidationSagaEventHandler { + /// + /// The possible verdicts of the event handler what to do next + /// + internal enum NextAction + { + /// + /// Finish the special liquidation flow + /// + Complete, + + /// + /// Cancel the special liquidation flow + /// + Cancel, + + /// + /// Pause the special liquidation flow + /// + Pause, + + /// + /// Continue with price request retry + /// + RetryPriceRequest, + + /// + /// Resume the initial flow caused the special liquidation + /// + ResumeInitialFlow + } + + /// + /// Map of the possible next states of the special liquidation flow. + /// Null means that the state should not be changed. + /// + private static readonly Dictionary NextStateMap = + new Dictionary + { + { NextAction.Complete, SpecialLiquidationOperationState.Failed }, + { NextAction.Cancel, SpecialLiquidationOperationState.Cancelled }, + { NextAction.Pause, null }, + { NextAction.RetryPriceRequest, SpecialLiquidationOperationState.PriceRequested }, + { NextAction.ResumeInitialFlow, SpecialLiquidationOperationState.Failed }, + }; + private readonly IDateService _dateService; - private readonly IChaosKitty _chaosKitty; private readonly IOperationExecutionInfoRepository _operationExecutionInfoRepository; private readonly OrdersCache _ordersCache; private readonly LiquidationHelper _liquidationHelper; - private readonly CqrsContextNamesSettings _cqrsContextNamesSettings; private readonly MarginTradingSettings _marginTradingSettings; private readonly IRfqPauseService _rfqPauseService; + private readonly IAssetPairsCache _assetPairsCache; public SpecialLiquidationFailedEventHandler(IOperationExecutionInfoRepository operationExecutionInfoRepository, OrdersCache ordersCache, LiquidationHelper liquidationHelper, - CqrsContextNamesSettings cqrsContextNamesSettings, IRfqPauseService rfqPauseService, IDateService dateService, - IChaosKitty chaosKitty, - MarginTradingSettings marginTradingSettings) + MarginTradingSettings marginTradingSettings, + IAssetPairsCache assetPairsCache) { _operationExecutionInfoRepository = operationExecutionInfoRepository; _ordersCache = ordersCache; _liquidationHelper = liquidationHelper; - _cqrsContextNamesSettings = cqrsContextNamesSettings; _rfqPauseService = rfqPauseService; _dateService = dateService; - _chaosKitty = chaosKitty; _marginTradingSettings = marginTradingSettings; + _assetPairsCache = assetPairsCache; } - - private Task> GetExecutionInfo(string operationId) => - _operationExecutionInfoRepository.GetAsync( - // todo: remove this dependency on SpecialLiquidationSaga class - operationName: SpecialLiquidationSaga.Name, - operationId); - + public async Task Handle(SpecialLiquidationFailedEvent @event, ICommandSender sender) { - var eInfo = await GetExecutionInfo(@event.OperationId); - - if (eInfo?.Data == null) + var executionInfo = await GetExecutionInfo(@event.OperationId); + + var instrument = _assetPairsCache.GetAssetPairById(executionInfo!.Data.Instrument); + + var nextAction = await GetNextAction( + executionInfo: executionInfo, + instrumentDiscontinued: instrument.IsDiscontinued, + liquidityIsEnough: GetLiquidityIsEnough, + canRetryPriceRequest: @event.CanRetryPriceRequest, + pauseIsAcknowledged: _rfqPauseService.AcknowledgeAsync, + configuration: _marginTradingSettings.SpecialLiquidation); + + var nextState = GetNextState(nextAction); + if (!await TrySaveState(executionInfo, nextState)) return; - var isDiscontinued = await _liquidationHelper.FailIfInstrumentDiscontinued(eInfo, sender); - if (isDiscontinued) return; - - if (!eInfo.Data.RequestedFromCorporateActions) + if (nextAction == NextAction.Cancel) { - var positions = _ordersCache.Positions - .GetPositionsByAccountIds(eInfo.Data.AccountId) - .Where(p => eInfo.Data.PositionIds.Contains(p.Id)) - .ToArray(); - - if (_liquidationHelper.CheckIfNetVolumeCanBeLiquidated(eInfo.Data.Instrument, positions, out _)) - { - // there is liquidity so we can cancel the special liquidation flow. - sender.SendCommand(new CancelSpecialLiquidationCommand - { - OperationId = @event.OperationId, - Reason = "Liquidity is enough to close positions within regular flow" - }, _cqrsContextNamesSettings.TradingEngine); - return; - } + sender.SendCancellation(@event.OperationId); + return; } - if (SpecialLiquidationSaga.PriceRequestRetryRequired( - eInfo.Data.RequestedFromCorporateActions, - _marginTradingSettings.SpecialLiquidation) && - @event.CanRetryPriceRequest) + if (nextAction == NextAction.ResumeInitialFlow) { - var pauseAcknowledged = await _rfqPauseService.AcknowledgeAsync(eInfo.Id); - if (pauseAcknowledged) return; - - if (eInfo.SwitchToState(SpecialLiquidationOperationState.PriceRequested)) - { - await _liquidationHelper.InternalRetryPriceRequest(@event.CreationTime, sender, eInfo, - _marginTradingSettings.SpecialLiquidation.PriceRequestRetryTimeout.Value); - - await _operationExecutionInfoRepository.Save(eInfo); - - return; - } + sender.SendResumeLiquidation(executionInfo.Data.CausationOperationId, + executionInfo.Id, + _dateService.Now()); + return; } - - if (eInfo.SwitchToState(SpecialLiquidationOperationState.Failed)) + + if (nextAction == NextAction.RetryPriceRequest) { - if (!string.IsNullOrEmpty(eInfo.Data.CausationOperationId)) - { - sender.SendResume(eInfo, _dateService.Now()); - } - - _chaosKitty.Meow(@event.OperationId); + await _liquidationHelper.InternalRetryPriceRequest(@event.CreationTime, sender, executionInfo, + _marginTradingSettings.SpecialLiquidation.PriceRequestRetryTimeout.Value); + return; + } + } + + public async Task CanHandle(SpecialLiquidationFailedEvent @event) + { + var executionInfo = await GetExecutionInfo(@event.OperationId); + + return executionInfo?.Data != null; + } + + /// + /// Pure business logic to determine what to be the next step of the special liquidation flow + /// + /// Current special liquidation execution info + /// Current state of discontinuation of the instrument + /// Function to check if there is enough liquidity to close the positions + /// Whether the price request can be retried + /// Function to check if the pause was requested and acknowledged + /// Special liquidation settings + /// + [Pure] + internal static async Task GetNextAction( + ExecutionInfo executionInfo, + bool instrumentDiscontinued, + Func liquidityIsEnough, + bool canRetryPriceRequest, + Func> pauseIsAcknowledged, + SpecialLiquidationSettings configuration) + { + if (instrumentDiscontinued) + return NextAction.Complete; + + var caInitiated = executionInfo.Data.RequestedFromCorporateActions; + if (!caInitiated && liquidityIsEnough(executionInfo)) + return NextAction.Cancel; + + var retryRequired = SpecialLiquidationSaga.PriceRequestRetryRequired(caInitiated, configuration); + if (retryRequired && canRetryPriceRequest) + { + if (await pauseIsAcknowledged(executionInfo.Id)) + return NextAction.Pause; + + return NextAction.RetryPriceRequest; + } - await _operationExecutionInfoRepository.Save(eInfo); + var hasCausingLiquidation = !string.IsNullOrEmpty(executionInfo.Data.CausationOperationId); + if (hasCausingLiquidation) + return NextAction.ResumeInitialFlow; + + return NextAction.Complete; + } + + private static SpecialLiquidationOperationState? GetNextState(NextAction nextAction) => + NextStateMap.TryGetValue(nextAction, out var state) ? state : null; + + private bool GetLiquidityIsEnough(ExecutionInfo executionInfo) + { + var positions = _ordersCache.Positions + .GetPositionsByAccountIds(executionInfo.Data.AccountId) + .Where(p => executionInfo.Data.PositionIds.Contains(p.Id)) + .ToArray(); + + return _liquidationHelper.CheckIfNetVolumeCanBeLiquidated(executionInfo.Data.Instrument, positions, out _); + } + + private async Task TrySaveState(ExecutionInfo executionInfo, SpecialLiquidationOperationState? state) + { + if (!state.HasValue) + return false; + + if (executionInfo.SwitchToState(state.Value)) + { + await _operationExecutionInfoRepository.Save(executionInfo); + return true; } + + return false; } + + private Task> GetExecutionInfo(string operationId) => + _operationExecutionInfoRepository.GetAsync( + operationName: SpecialLiquidationSaga.Name, + operationId); } } \ No newline at end of file diff --git a/tests/MarginTradingTests/WorkflowTests/SpecialLiquidationFailedEventHandlerTests.cs b/tests/MarginTradingTests/WorkflowTests/SpecialLiquidationFailedEventHandlerTests.cs new file mode 100644 index 000000000..71b672856 --- /dev/null +++ b/tests/MarginTradingTests/WorkflowTests/SpecialLiquidationFailedEventHandlerTests.cs @@ -0,0 +1,140 @@ +// 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.Settings; +using MarginTrading.Backend.Services.Workflow; +using Moq; +using NUnit.Framework; +using ExecutionInfo = MarginTrading.Backend.Core.IOperationExecutionInfo; + +namespace MarginTradingTests.WorkflowTests +{ + [TestFixture] + public class SpecialLiquidationFailedEventHandlerTests + { + [Test] + public async Task GetNextAction_ReturnsComplete_WhenInstrumentIsDiscontinued() + { + var result = await SpecialLiquidationFailedEventHandler.GetNextAction(null, + true, + _ => false, + false, + _ => Task.FromResult(false), + new SpecialLiquidationSettings()); + + Assert.AreEqual(SpecialLiquidationFailedEventHandler.NextAction.Complete, result); + } + + [Test] + public async Task GetNextAction_ReturnsCancel_WhenLiquidityIsEnough() + { + var executionInfo = Mock.Of(x => + x.Data == new SpecialLiquidationOperationData { RequestedFromCorporateActions = false }); + + var result = await SpecialLiquidationFailedEventHandler.GetNextAction(executionInfo, + false, + _ => true, + false, + _ => Task.FromResult(false), + new SpecialLiquidationSettings()); + + Assert.AreEqual(SpecialLiquidationFailedEventHandler.NextAction.Cancel, result); + } + + [Test] + public void GetNextAction_ChecksLiquidity_IfOnly_NotInitiatedByCorporateActions() + { + var executionInfo = Mock.Of(x => + x.Data == new SpecialLiquidationOperationData { RequestedFromCorporateActions = false }); + + var ex = Assert.ThrowsAsync(async () => + await SpecialLiquidationFailedEventHandler.GetNextAction(executionInfo, + false, + _ => throw new InvalidOperationException("Liquidity check expected"), + false, + _ => Task.FromResult(false), + new SpecialLiquidationSettings())); + + Assert.NotNull(ex); + Assert.AreEqual("Liquidity check expected", ex.Message); + } + + [Test] + public async Task GetNextAction_ReturnsRetryPriceRequest_WhenRetryIsRequiredAndCanRetryPriceRequest() + { + var executionInfo = Mock.Of(x => + x.Data == new SpecialLiquidationOperationData { RequestedFromCorporateActions = true }); // to skip liquidity check + + var result = await SpecialLiquidationFailedEventHandler.GetNextAction(executionInfo, + false, + _ => false, + true, + _ => Task.FromResult(false), + new SpecialLiquidationSettings + { PriceRequestRetryTimeout = TimeSpan.Zero, RetryPriceRequestForCorporateActions = true }); + + Assert.AreEqual(SpecialLiquidationFailedEventHandler.NextAction.RetryPriceRequest, result); + } + + [Test] + public async Task GetNextAction_ChecksForPause_BeforeRetryingPriceRequest() + { + var executionInfo = Mock.Of(x => + x.Data == new SpecialLiquidationOperationData { RequestedFromCorporateActions = true }); // to skip liquidity check + + var result = await SpecialLiquidationFailedEventHandler.GetNextAction(executionInfo, + false, + _ => false, + true, + _ => Task.FromResult(true), + new SpecialLiquidationSettings + { PriceRequestRetryTimeout = TimeSpan.Zero, RetryPriceRequestForCorporateActions = true }); + + Assert.AreEqual(SpecialLiquidationFailedEventHandler.NextAction.Pause, result); + } + + [Test] + public async Task GetNextAction_ResumesInitialFlow_WhenHasCausingLiquidation() + { + var executionInfo = Mock.Of(x => + x.Data == new SpecialLiquidationOperationData + { + CausationOperationId = "123", + RequestedFromCorporateActions = true + }); + + var result = await SpecialLiquidationFailedEventHandler.GetNextAction(executionInfo, + false, + _ => false, + false, + _ => Task.FromResult(false), + new SpecialLiquidationSettings + { PriceRequestRetryTimeout = TimeSpan.Zero, RetryPriceRequestForCorporateActions = true }); + + Assert.AreEqual(SpecialLiquidationFailedEventHandler.NextAction.ResumeInitialFlow, result); + } + + [Test] + public async Task GetNextAction_ReturnsComplete_WhenAsDefault() + { + var executionInfo = Mock.Of(x => + x.Data == new SpecialLiquidationOperationData + { + CausationOperationId = null, + RequestedFromCorporateActions = false + }); + + var result = await SpecialLiquidationFailedEventHandler.GetNextAction(executionInfo, + false, + _ => false, + false, + _ => Task.FromResult(false), + new SpecialLiquidationSettings { PriceRequestRetryTimeout = null, }); + + Assert.AreEqual(SpecialLiquidationFailedEventHandler.NextAction.Complete, result); + } + } +} \ No newline at end of file From f6c536db590ea3608798ebfa4b176229c081db8d Mon Sep 17 00:00:00 2001 From: atarutin Date: Mon, 4 Dec 2023 23:39:40 +0300 Subject: [PATCH 17/25] refactor(LT-5061): a bit of renaming --- .../SpecialLiquidationFailedEventHandler.cs | 39 +++++++++---------- ...ecialLiquidationFailedEventHandlerTests.cs | 28 ++++++------- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs index bc5fc9dd1..1d2a16f6d 100644 --- a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs @@ -103,7 +103,7 @@ public async Task Handle(SpecialLiquidationFailedEvent @event, ICommandSender se var instrument = _assetPairsCache.GetAssetPairById(executionInfo!.Data.Instrument); - var nextAction = await GetNextAction( + var nextAction = await DetermineNextAction( executionInfo: executionInfo, instrumentDiscontinued: instrument.IsDiscontinued, liquidityIsEnough: GetLiquidityIsEnough, @@ -115,25 +115,24 @@ public async Task Handle(SpecialLiquidationFailedEvent @event, ICommandSender se if (!await TrySaveState(executionInfo, nextState)) return; - if (nextAction == NextAction.Cancel) + switch (nextAction) { - sender.SendCancellation(@event.OperationId); - return; - } - - if (nextAction == NextAction.ResumeInitialFlow) - { - sender.SendResumeLiquidation(executionInfo.Data.CausationOperationId, - executionInfo.Id, - _dateService.Now()); - return; - } - - if (nextAction == NextAction.RetryPriceRequest) - { - await _liquidationHelper.InternalRetryPriceRequest(@event.CreationTime, sender, executionInfo, - _marginTradingSettings.SpecialLiquidation.PriceRequestRetryTimeout.Value); - return; + case NextAction.Cancel: + sender.SendCancellation(@event.OperationId); + break; + case NextAction.ResumeInitialFlow: + sender.SendResumeLiquidation(executionInfo.Data.CausationOperationId, + executionInfo.Id, + _dateService.Now()); + break; + case NextAction.RetryPriceRequest: + await _liquidationHelper.InternalRetryPriceRequest(@event.CreationTime, + sender, + executionInfo, + _marginTradingSettings.SpecialLiquidation.PriceRequestRetryTimeout!.Value); + break; + default: + return; } } @@ -155,7 +154,7 @@ public async Task CanHandle(SpecialLiquidationFailedEvent @event) /// Special liquidation settings /// [Pure] - internal static async Task GetNextAction( + internal static async Task DetermineNextAction( ExecutionInfo executionInfo, bool instrumentDiscontinued, Func liquidityIsEnough, diff --git a/tests/MarginTradingTests/WorkflowTests/SpecialLiquidationFailedEventHandlerTests.cs b/tests/MarginTradingTests/WorkflowTests/SpecialLiquidationFailedEventHandlerTests.cs index 71b672856..14e2d4c83 100644 --- a/tests/MarginTradingTests/WorkflowTests/SpecialLiquidationFailedEventHandlerTests.cs +++ b/tests/MarginTradingTests/WorkflowTests/SpecialLiquidationFailedEventHandlerTests.cs @@ -16,9 +16,9 @@ namespace MarginTradingTests.WorkflowTests public class SpecialLiquidationFailedEventHandlerTests { [Test] - public async Task GetNextAction_ReturnsComplete_WhenInstrumentIsDiscontinued() + public async Task DetermineNextAction_ReturnsComplete_WhenInstrumentIsDiscontinued() { - var result = await SpecialLiquidationFailedEventHandler.GetNextAction(null, + var result = await SpecialLiquidationFailedEventHandler.DetermineNextAction(null, true, _ => false, false, @@ -29,12 +29,12 @@ public async Task GetNextAction_ReturnsComplete_WhenInstrumentIsDiscontinued() } [Test] - public async Task GetNextAction_ReturnsCancel_WhenLiquidityIsEnough() + public async Task DetermineNextAction_ReturnsCancel_WhenLiquidityIsEnough() { var executionInfo = Mock.Of(x => x.Data == new SpecialLiquidationOperationData { RequestedFromCorporateActions = false }); - var result = await SpecialLiquidationFailedEventHandler.GetNextAction(executionInfo, + var result = await SpecialLiquidationFailedEventHandler.DetermineNextAction(executionInfo, false, _ => true, false, @@ -45,13 +45,13 @@ public async Task GetNextAction_ReturnsCancel_WhenLiquidityIsEnough() } [Test] - public void GetNextAction_ChecksLiquidity_IfOnly_NotInitiatedByCorporateActions() + public void DetermineNextAction_ChecksLiquidity_IfOnly_NotInitiatedByCorporateActions() { var executionInfo = Mock.Of(x => x.Data == new SpecialLiquidationOperationData { RequestedFromCorporateActions = false }); var ex = Assert.ThrowsAsync(async () => - await SpecialLiquidationFailedEventHandler.GetNextAction(executionInfo, + await SpecialLiquidationFailedEventHandler.DetermineNextAction(executionInfo, false, _ => throw new InvalidOperationException("Liquidity check expected"), false, @@ -63,12 +63,12 @@ await SpecialLiquidationFailedEventHandler.GetNextAction(executionInfo, } [Test] - public async Task GetNextAction_ReturnsRetryPriceRequest_WhenRetryIsRequiredAndCanRetryPriceRequest() + public async Task DetermineNextAction_ReturnsRetryPriceRequest_WhenRetryIsRequiredAndCanRetryPriceRequest() { var executionInfo = Mock.Of(x => x.Data == new SpecialLiquidationOperationData { RequestedFromCorporateActions = true }); // to skip liquidity check - var result = await SpecialLiquidationFailedEventHandler.GetNextAction(executionInfo, + var result = await SpecialLiquidationFailedEventHandler.DetermineNextAction(executionInfo, false, _ => false, true, @@ -80,12 +80,12 @@ public async Task GetNextAction_ReturnsRetryPriceRequest_WhenRetryIsRequiredAndC } [Test] - public async Task GetNextAction_ChecksForPause_BeforeRetryingPriceRequest() + public async Task DetermineNextAction_ChecksForPause_BeforeRetryingPriceRequest() { var executionInfo = Mock.Of(x => x.Data == new SpecialLiquidationOperationData { RequestedFromCorporateActions = true }); // to skip liquidity check - var result = await SpecialLiquidationFailedEventHandler.GetNextAction(executionInfo, + var result = await SpecialLiquidationFailedEventHandler.DetermineNextAction(executionInfo, false, _ => false, true, @@ -97,7 +97,7 @@ public async Task GetNextAction_ChecksForPause_BeforeRetryingPriceRequest() } [Test] - public async Task GetNextAction_ResumesInitialFlow_WhenHasCausingLiquidation() + public async Task DetermineNextAction_ResumesInitialFlow_WhenHasCausingLiquidation() { var executionInfo = Mock.Of(x => x.Data == new SpecialLiquidationOperationData @@ -106,7 +106,7 @@ public async Task GetNextAction_ResumesInitialFlow_WhenHasCausingLiquidation() RequestedFromCorporateActions = true }); - var result = await SpecialLiquidationFailedEventHandler.GetNextAction(executionInfo, + var result = await SpecialLiquidationFailedEventHandler.DetermineNextAction(executionInfo, false, _ => false, false, @@ -118,7 +118,7 @@ public async Task GetNextAction_ResumesInitialFlow_WhenHasCausingLiquidation() } [Test] - public async Task GetNextAction_ReturnsComplete_WhenAsDefault() + public async Task DetermineNextAction_ReturnsComplete_WhenAsDefault() { var executionInfo = Mock.Of(x => x.Data == new SpecialLiquidationOperationData @@ -127,7 +127,7 @@ public async Task GetNextAction_ReturnsComplete_WhenAsDefault() RequestedFromCorporateActions = false }); - var result = await SpecialLiquidationFailedEventHandler.GetNextAction(executionInfo, + var result = await SpecialLiquidationFailedEventHandler.DetermineNextAction(executionInfo, false, _ => false, false, From 88def088dd45c5dafd19c4189b171133c619d854 Mon Sep 17 00:00:00 2001 From: atarutin Date: Thu, 14 Mar 2024 16:18:11 +0300 Subject: [PATCH 18/25] fix(LT-5061): executionInfo was previously saved and LastModified updated so, to modify and save it again we have to request it again from database to get latest LastModified value --- MarginTrading.sln.DotSettings | 1 + .../Workflow/SpecialLiquidationFailedEventHandler.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/MarginTrading.sln.DotSettings b/MarginTrading.sln.DotSettings index 5e9904edd..b99bda025 100644 --- a/MarginTrading.sln.DotSettings +++ b/MarginTrading.sln.DotSettings @@ -1,4 +1,5 @@  + True True True True diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs index 1d2a16f6d..bc67c3214 100644 --- a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs @@ -126,6 +126,7 @@ public async Task Handle(SpecialLiquidationFailedEvent @event, ICommandSender se _dateService.Now()); break; case NextAction.RetryPriceRequest: + executionInfo = await GetExecutionInfo(@event.OperationId); await _liquidationHelper.InternalRetryPriceRequest(@event.CreationTime, sender, executionInfo, From 5a402afa1e26047237d04ddc8e1a85fab9b34cd1 Mon Sep 17 00:00:00 2001 From: atarutin Date: Thu, 14 Mar 2024 18:39:14 +0300 Subject: [PATCH 19/25] fix(LT-5061): canRetryPriceRequest has to be true when price was rejected by trader --- .../Commands/FailSpecialLiquidationInternalCommand.cs | 3 +++ .../SpecialLiquidation/SpecialLiquidationCommandsHandler.cs | 1 + .../Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Commands/FailSpecialLiquidationInternalCommand.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Commands/FailSpecialLiquidationInternalCommand.cs index ce24515ba..1a425dc88 100644 --- a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Commands/FailSpecialLiquidationInternalCommand.cs +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/Commands/FailSpecialLiquidationInternalCommand.cs @@ -17,5 +17,8 @@ public class FailSpecialLiquidationInternalCommand [Key(2)] public string Reason { get; set; } + + [Key(3)] + public bool? CanRetryPriceRequest { 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 index 99e6df59c..73c66cdf4 100644 --- a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationCommandsHandler.cs +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationCommandsHandler.cs @@ -586,6 +586,7 @@ private async Task Handle(FailSpecialLiquidationInternalCommand command, IEventP OperationId = command.OperationId, CreationTime = _dateService.Now(), Reason = command.Reason, + CanRetryPriceRequest = command.CanRetryPriceRequest ?? false }); _chaosKitty.Meow(command.OperationId); diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs index db18afb93..dc4b744a6 100644 --- a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs @@ -171,7 +171,8 @@ private async Task Handle(PriceForSpecialLiquidationCalculationFailedEvent e, IC { OperationId = e.OperationId, CreationTime = _dateService.Now(), - Reason = e.Reason + Reason = e.Reason, + CanRetryPriceRequest = true }, _cqrsContextNamesSettings.TradingEngine); _chaosKitty.Meow(e.OperationId); From e68ee9838cd476dba52c550b1976ec2ee8f47ebc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 01:12:28 +0000 Subject: [PATCH 20/25] chore(deps): bump Lykke.MarginTrading.BookKeeper.Contracts Bumps Lykke.MarginTrading.BookKeeper.Contracts from 1.5.2 to 1.6.0. --- updated-dependencies: - dependency-name: Lykke.MarginTrading.BookKeeper.Contracts dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .../MarginTrading.Backend.Services.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj b/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj index e1833c30b..c79a97ddc 100644 --- a/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj +++ b/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj @@ -28,7 +28,7 @@ - + From 83f2dde2d116bfd1dda02ea3d7de0f94989f6190 Mon Sep 17 00:00:00 2001 From: atarutin Date: Wed, 20 Mar 2024 18:46:46 +0300 Subject: [PATCH 21/25] chore(LT-5061): honor pull request comments --- .../Workflow/SpecialLiquidationCommandSenderExtensions.cs | 5 +++-- .../Workflow/SpecialLiquidationFailedEventHandler.cs | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationCommandSenderExtensions.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationCommandSenderExtensions.cs index 6c798ddd6..9a6dcc636 100644 --- a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationCommandSenderExtensions.cs +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationCommandSenderExtensions.cs @@ -44,8 +44,9 @@ public static void SendResumeLiquidation(this ICommandSender sender, /// /// /// + /// /// - public static void SendCancellation(this ICommandSender sender, string liquidationId) + public static void SendCancellation(this ICommandSender sender, string liquidationId, string reason) { if (string.IsNullOrWhiteSpace(liquidationId)) throw new ArgumentNullException(nameof(liquidationId)); @@ -53,7 +54,7 @@ public static void SendCancellation(this ICommandSender sender, string liquidati sender.SendCommand(new CancelSpecialLiquidationCommand { OperationId = liquidationId, - Reason = "Liquidity is enough to close positions within regular flow" + Reason = reason }, "TradingEngine"); } } diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs index bc67c3214..2da429636 100644 --- a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs +++ b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidationFailedEventHandler.cs @@ -71,6 +71,8 @@ internal enum NextAction { NextAction.RetryPriceRequest, SpecialLiquidationOperationState.PriceRequested }, { NextAction.ResumeInitialFlow, SpecialLiquidationOperationState.Failed }, }; + + private const string LiquidityIsSufficientReason = "Liquidity is sufficient to close positions within regular flow"; private readonly IDateService _dateService; private readonly IOperationExecutionInfoRepository _operationExecutionInfoRepository; @@ -118,7 +120,7 @@ public async Task Handle(SpecialLiquidationFailedEvent @event, ICommandSender se switch (nextAction) { case NextAction.Cancel: - sender.SendCancellation(@event.OperationId); + sender.SendCancellation(@event.OperationId, LiquidityIsSufficientReason); break; case NextAction.ResumeInitialFlow: sender.SendResumeLiquidation(executionInfo.Data.CausationOperationId, From 62d7a5b5cf29bd14a6596b234187099e73246472 Mon Sep 17 00:00:00 2001 From: Aliaksandr Vashetsin Date: Thu, 21 Mar 2024 10:29:35 +0100 Subject: [PATCH 22/25] LT-4926: suggested changes --- .../Infrastructure/SnapshotValidationService.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/MarginTrading.Backend.Services/Infrastructure/SnapshotValidationService.cs b/src/MarginTrading.Backend.Services/Infrastructure/SnapshotValidationService.cs index d5f480b83..edbaf2710 100644 --- a/src/MarginTrading.Backend.Services/Infrastructure/SnapshotValidationService.cs +++ b/src/MarginTrading.Backend.Services/Infrastructure/SnapshotValidationService.cs @@ -62,10 +62,8 @@ await _log.WriteInfoAsync(nameof(SnapshotValidationService), nameof(ValidateCurr var lastOrders = GetOrders(tradingEngineSnapshot); var lastPositions = GetPositions(tradingEngineSnapshot); - var latestLastModified = currentOrders.Any() - ? currentOrders.Max(x => x.LastModified) - : (DateTime?)null; - var ordersHistory = await _ordersHistoryRepository.GetLastSnapshot(tradingEngineSnapshot.Timestamp, latestLastModified); + var latestOrder = currentOrders.MaxBy(x => x.LastModified); + var ordersHistory = await _ordersHistoryRepository.GetLastSnapshot(tradingEngineSnapshot.Timestamp, latestOrder?.LastModified); var positionsHistory = await _positionsHistoryRepository.GetLastSnapshot(tradingEngineSnapshot.Timestamp); var restoredOrders = RestoreOrdersCurrentStateFromHistory(lastOrders, ordersHistory); From bd16c577a4eedc77c8d8080b11908bea1cbf5901 Mon Sep 17 00:00:00 2001 From: lykketech Date: Fri, 22 Mar 2024 06:11:53 +0000 Subject: [PATCH 23/25] Released version [2.29.2] --- .../MarginTrading.AzureRepositories.csproj | 2 +- .../MarginTrading.Backend.Contracts.csproj | 2 +- .../MarginTrading.Backend.Core.Mappers.csproj | 2 +- .../MarginTrading.Backend.Core.csproj | 2 +- .../MarginTrading.Backend.Services.csproj | 2 +- .../MarginTrading.Backend.TestClient.csproj | 2 +- src/MarginTrading.Backend/MarginTrading.Backend.csproj | 2 +- .../MarginTrading.AccountMarginEventsBroker.csproj | 2 +- src/MarginTrading.Common/MarginTrading.Common.csproj | 2 +- src/MarginTrading.Contract/MarginTrading.Contract.csproj | 2 +- .../MarginTrading.SqlRepositories.csproj | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/MarginTrading.AzureRepositories/MarginTrading.AzureRepositories.csproj b/src/MarginTrading.AzureRepositories/MarginTrading.AzureRepositories.csproj index 101699c62..d84037f3d 100644 --- a/src/MarginTrading.AzureRepositories/MarginTrading.AzureRepositories.csproj +++ b/src/MarginTrading.AzureRepositories/MarginTrading.AzureRepositories.csproj @@ -6,7 +6,7 @@ false false false - 2.31.0 + 2.29.2 8.0 diff --git a/src/MarginTrading.Backend.Contracts/MarginTrading.Backend.Contracts.csproj b/src/MarginTrading.Backend.Contracts/MarginTrading.Backend.Contracts.csproj index ea284313e..042fa01de 100644 --- a/src/MarginTrading.Backend.Contracts/MarginTrading.Backend.Contracts.csproj +++ b/src/MarginTrading.Backend.Contracts/MarginTrading.Backend.Contracts.csproj @@ -1,7 +1,7 @@  net6.0 - 2.31.0 + 2.29.2 Lykke.MarginTrading.BackendSnow.Contracts 8.0 true 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 1e1b6aa65..bbabb249c 100644 --- a/src/MarginTrading.Backend.Core.Mappers/MarginTrading.Backend.Core.Mappers.csproj +++ b/src/MarginTrading.Backend.Core.Mappers/MarginTrading.Backend.Core.Mappers.csproj @@ -1,7 +1,7 @@  net6.0 - 2.31.0 + 2.29.2 8.0 diff --git a/src/MarginTrading.Backend.Core/MarginTrading.Backend.Core.csproj b/src/MarginTrading.Backend.Core/MarginTrading.Backend.Core.csproj index c9df42e50..c7c6842cd 100644 --- a/src/MarginTrading.Backend.Core/MarginTrading.Backend.Core.csproj +++ b/src/MarginTrading.Backend.Core/MarginTrading.Backend.Core.csproj @@ -6,7 +6,7 @@ false false false - 2.31.0 + 2.29.2 8.0 diff --git a/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj b/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj index c79a97ddc..64d67023a 100644 --- a/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj +++ b/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj @@ -6,7 +6,7 @@ false false false - 2.31.0 + 2.29.2 8.0 diff --git a/src/MarginTrading.Backend.TestClient/MarginTrading.Backend.TestClient.csproj b/src/MarginTrading.Backend.TestClient/MarginTrading.Backend.TestClient.csproj index dd4f260c1..fea6dcb89 100644 --- a/src/MarginTrading.Backend.TestClient/MarginTrading.Backend.TestClient.csproj +++ b/src/MarginTrading.Backend.TestClient/MarginTrading.Backend.TestClient.csproj @@ -3,7 +3,7 @@ Exe net6.0 8.0 - 2.31.0 + 2.29.2 1701;1702;1705;CA2007;0612;0618;1591 diff --git a/src/MarginTrading.Backend/MarginTrading.Backend.csproj b/src/MarginTrading.Backend/MarginTrading.Backend.csproj index da704ca10..f7b2882e1 100644 --- a/src/MarginTrading.Backend/MarginTrading.Backend.csproj +++ b/src/MarginTrading.Backend/MarginTrading.Backend.csproj @@ -8,7 +8,7 @@ false false false - 2.31.0 + 2.29.2 8.0 OutOfProcess AspNetCoreModuleV2 diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/MarginTrading.AccountMarginEventsBroker.csproj b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/MarginTrading.AccountMarginEventsBroker.csproj index b5ee2f710..22afd0713 100644 --- a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/MarginTrading.AccountMarginEventsBroker.csproj +++ b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/MarginTrading.AccountMarginEventsBroker.csproj @@ -2,7 +2,7 @@ Exe net6.0 - 2.31.0 + 2.29.2 8.0 diff --git a/src/MarginTrading.Common/MarginTrading.Common.csproj b/src/MarginTrading.Common/MarginTrading.Common.csproj index 5c718f864..93bc46337 100644 --- a/src/MarginTrading.Common/MarginTrading.Common.csproj +++ b/src/MarginTrading.Common/MarginTrading.Common.csproj @@ -6,7 +6,7 @@ false false false - 2.31.0 + 2.29.2 8.0 diff --git a/src/MarginTrading.Contract/MarginTrading.Contract.csproj b/src/MarginTrading.Contract/MarginTrading.Contract.csproj index 0cad1e935..f5ad1571a 100644 --- a/src/MarginTrading.Contract/MarginTrading.Contract.csproj +++ b/src/MarginTrading.Contract/MarginTrading.Contract.csproj @@ -1,7 +1,7 @@  netstandard2.0 - 2.31.0 + 2.29.2 Lykke.MarginTrading.Contracts 8.0 diff --git a/src/MarginTrading.SqlRepositories/MarginTrading.SqlRepositories.csproj b/src/MarginTrading.SqlRepositories/MarginTrading.SqlRepositories.csproj index 0cc43c6c6..6a233a8c4 100644 --- a/src/MarginTrading.SqlRepositories/MarginTrading.SqlRepositories.csproj +++ b/src/MarginTrading.SqlRepositories/MarginTrading.SqlRepositories.csproj @@ -6,7 +6,7 @@ false false false - 2.31.0 + 2.29.2 8.0 From 444c869908f07f963bd9a6fdb528fb3089300768 Mon Sep 17 00:00:00 2001 From: lykketech Date: Mon, 25 Mar 2024 08:09:25 +0000 Subject: [PATCH 24/25] Released version [2.29.3] --- .../MarginTrading.AzureRepositories.csproj | 2 +- .../MarginTrading.Backend.Contracts.csproj | 2 +- .../MarginTrading.Backend.Core.Mappers.csproj | 2 +- .../MarginTrading.Backend.Core.csproj | 2 +- .../MarginTrading.Backend.Services.csproj | 2 +- .../MarginTrading.Backend.TestClient.csproj | 2 +- src/MarginTrading.Backend/MarginTrading.Backend.csproj | 2 +- .../MarginTrading.AccountMarginEventsBroker.csproj | 2 +- src/MarginTrading.Common/MarginTrading.Common.csproj | 2 +- src/MarginTrading.Contract/MarginTrading.Contract.csproj | 2 +- .../MarginTrading.SqlRepositories.csproj | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/MarginTrading.AzureRepositories/MarginTrading.AzureRepositories.csproj b/src/MarginTrading.AzureRepositories/MarginTrading.AzureRepositories.csproj index d84037f3d..2cbac8b0b 100644 --- a/src/MarginTrading.AzureRepositories/MarginTrading.AzureRepositories.csproj +++ b/src/MarginTrading.AzureRepositories/MarginTrading.AzureRepositories.csproj @@ -6,7 +6,7 @@ false false false - 2.29.2 + 2.29.3 8.0 diff --git a/src/MarginTrading.Backend.Contracts/MarginTrading.Backend.Contracts.csproj b/src/MarginTrading.Backend.Contracts/MarginTrading.Backend.Contracts.csproj index 042fa01de..26a9d5b9d 100644 --- a/src/MarginTrading.Backend.Contracts/MarginTrading.Backend.Contracts.csproj +++ b/src/MarginTrading.Backend.Contracts/MarginTrading.Backend.Contracts.csproj @@ -1,7 +1,7 @@  net6.0 - 2.29.2 + 2.29.3 Lykke.MarginTrading.BackendSnow.Contracts 8.0 true 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 bbabb249c..b1d1eff71 100644 --- a/src/MarginTrading.Backend.Core.Mappers/MarginTrading.Backend.Core.Mappers.csproj +++ b/src/MarginTrading.Backend.Core.Mappers/MarginTrading.Backend.Core.Mappers.csproj @@ -1,7 +1,7 @@  net6.0 - 2.29.2 + 2.29.3 8.0 diff --git a/src/MarginTrading.Backend.Core/MarginTrading.Backend.Core.csproj b/src/MarginTrading.Backend.Core/MarginTrading.Backend.Core.csproj index c7c6842cd..eb18c9844 100644 --- a/src/MarginTrading.Backend.Core/MarginTrading.Backend.Core.csproj +++ b/src/MarginTrading.Backend.Core/MarginTrading.Backend.Core.csproj @@ -6,7 +6,7 @@ false false false - 2.29.2 + 2.29.3 8.0 diff --git a/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj b/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj index 64d67023a..16b318f51 100644 --- a/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj +++ b/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj @@ -6,7 +6,7 @@ false false false - 2.29.2 + 2.29.3 8.0 diff --git a/src/MarginTrading.Backend.TestClient/MarginTrading.Backend.TestClient.csproj b/src/MarginTrading.Backend.TestClient/MarginTrading.Backend.TestClient.csproj index fea6dcb89..582140574 100644 --- a/src/MarginTrading.Backend.TestClient/MarginTrading.Backend.TestClient.csproj +++ b/src/MarginTrading.Backend.TestClient/MarginTrading.Backend.TestClient.csproj @@ -3,7 +3,7 @@ Exe net6.0 8.0 - 2.29.2 + 2.29.3 1701;1702;1705;CA2007;0612;0618;1591 diff --git a/src/MarginTrading.Backend/MarginTrading.Backend.csproj b/src/MarginTrading.Backend/MarginTrading.Backend.csproj index f7b2882e1..ff1acacac 100644 --- a/src/MarginTrading.Backend/MarginTrading.Backend.csproj +++ b/src/MarginTrading.Backend/MarginTrading.Backend.csproj @@ -8,7 +8,7 @@ false false false - 2.29.2 + 2.29.3 8.0 OutOfProcess AspNetCoreModuleV2 diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/MarginTrading.AccountMarginEventsBroker.csproj b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/MarginTrading.AccountMarginEventsBroker.csproj index 22afd0713..2f3ad4075 100644 --- a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/MarginTrading.AccountMarginEventsBroker.csproj +++ b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/MarginTrading.AccountMarginEventsBroker.csproj @@ -2,7 +2,7 @@ Exe net6.0 - 2.29.2 + 2.29.3 8.0 diff --git a/src/MarginTrading.Common/MarginTrading.Common.csproj b/src/MarginTrading.Common/MarginTrading.Common.csproj index 93bc46337..721245223 100644 --- a/src/MarginTrading.Common/MarginTrading.Common.csproj +++ b/src/MarginTrading.Common/MarginTrading.Common.csproj @@ -6,7 +6,7 @@ false false false - 2.29.2 + 2.29.3 8.0 diff --git a/src/MarginTrading.Contract/MarginTrading.Contract.csproj b/src/MarginTrading.Contract/MarginTrading.Contract.csproj index f5ad1571a..70e56a084 100644 --- a/src/MarginTrading.Contract/MarginTrading.Contract.csproj +++ b/src/MarginTrading.Contract/MarginTrading.Contract.csproj @@ -1,7 +1,7 @@  netstandard2.0 - 2.29.2 + 2.29.3 Lykke.MarginTrading.Contracts 8.0 diff --git a/src/MarginTrading.SqlRepositories/MarginTrading.SqlRepositories.csproj b/src/MarginTrading.SqlRepositories/MarginTrading.SqlRepositories.csproj index 6a233a8c4..a4e309a2f 100644 --- a/src/MarginTrading.SqlRepositories/MarginTrading.SqlRepositories.csproj +++ b/src/MarginTrading.SqlRepositories/MarginTrading.SqlRepositories.csproj @@ -6,7 +6,7 @@ false false false - 2.29.2 + 2.29.3 8.0 From b92a31be196612a3f9d9fb2564aec3ea154bd4ba Mon Sep 17 00:00:00 2001 From: lykketech Date: Mon, 25 Mar 2024 08:12:05 +0000 Subject: [PATCH 25/25] Released version [2.29.4] --- .../MarginTrading.AzureRepositories.csproj | 2 +- .../MarginTrading.Backend.Contracts.csproj | 2 +- .../MarginTrading.Backend.Core.Mappers.csproj | 2 +- .../MarginTrading.Backend.Core.csproj | 2 +- .../MarginTrading.Backend.Services.csproj | 2 +- .../MarginTrading.Backend.TestClient.csproj | 2 +- src/MarginTrading.Backend/MarginTrading.Backend.csproj | 2 +- .../MarginTrading.AccountMarginEventsBroker.csproj | 2 +- src/MarginTrading.Common/MarginTrading.Common.csproj | 2 +- src/MarginTrading.Contract/MarginTrading.Contract.csproj | 2 +- .../MarginTrading.SqlRepositories.csproj | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/MarginTrading.AzureRepositories/MarginTrading.AzureRepositories.csproj b/src/MarginTrading.AzureRepositories/MarginTrading.AzureRepositories.csproj index 2cbac8b0b..c4a2cade1 100644 --- a/src/MarginTrading.AzureRepositories/MarginTrading.AzureRepositories.csproj +++ b/src/MarginTrading.AzureRepositories/MarginTrading.AzureRepositories.csproj @@ -6,7 +6,7 @@ false false false - 2.29.3 + 2.29.4 8.0 diff --git a/src/MarginTrading.Backend.Contracts/MarginTrading.Backend.Contracts.csproj b/src/MarginTrading.Backend.Contracts/MarginTrading.Backend.Contracts.csproj index 26a9d5b9d..955c53df6 100644 --- a/src/MarginTrading.Backend.Contracts/MarginTrading.Backend.Contracts.csproj +++ b/src/MarginTrading.Backend.Contracts/MarginTrading.Backend.Contracts.csproj @@ -1,7 +1,7 @@  net6.0 - 2.29.3 + 2.29.4 Lykke.MarginTrading.BackendSnow.Contracts 8.0 true 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 b1d1eff71..f78632bd9 100644 --- a/src/MarginTrading.Backend.Core.Mappers/MarginTrading.Backend.Core.Mappers.csproj +++ b/src/MarginTrading.Backend.Core.Mappers/MarginTrading.Backend.Core.Mappers.csproj @@ -1,7 +1,7 @@  net6.0 - 2.29.3 + 2.29.4 8.0 diff --git a/src/MarginTrading.Backend.Core/MarginTrading.Backend.Core.csproj b/src/MarginTrading.Backend.Core/MarginTrading.Backend.Core.csproj index eb18c9844..d23efb6a4 100644 --- a/src/MarginTrading.Backend.Core/MarginTrading.Backend.Core.csproj +++ b/src/MarginTrading.Backend.Core/MarginTrading.Backend.Core.csproj @@ -6,7 +6,7 @@ false false false - 2.29.3 + 2.29.4 8.0 diff --git a/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj b/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj index 16b318f51..3a226390c 100644 --- a/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj +++ b/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj @@ -6,7 +6,7 @@ false false false - 2.29.3 + 2.29.4 8.0 diff --git a/src/MarginTrading.Backend.TestClient/MarginTrading.Backend.TestClient.csproj b/src/MarginTrading.Backend.TestClient/MarginTrading.Backend.TestClient.csproj index 582140574..3201d3090 100644 --- a/src/MarginTrading.Backend.TestClient/MarginTrading.Backend.TestClient.csproj +++ b/src/MarginTrading.Backend.TestClient/MarginTrading.Backend.TestClient.csproj @@ -3,7 +3,7 @@ Exe net6.0 8.0 - 2.29.3 + 2.29.4 1701;1702;1705;CA2007;0612;0618;1591 diff --git a/src/MarginTrading.Backend/MarginTrading.Backend.csproj b/src/MarginTrading.Backend/MarginTrading.Backend.csproj index ff1acacac..9efd3d30f 100644 --- a/src/MarginTrading.Backend/MarginTrading.Backend.csproj +++ b/src/MarginTrading.Backend/MarginTrading.Backend.csproj @@ -8,7 +8,7 @@ false false false - 2.29.3 + 2.29.4 8.0 OutOfProcess AspNetCoreModuleV2 diff --git a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/MarginTrading.AccountMarginEventsBroker.csproj b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/MarginTrading.AccountMarginEventsBroker.csproj index 2f3ad4075..eb9c91ae2 100644 --- a/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/MarginTrading.AccountMarginEventsBroker.csproj +++ b/src/MarginTrading.Brokers/MarginTrading.AccountMarginEventsBroker/MarginTrading.AccountMarginEventsBroker.csproj @@ -2,7 +2,7 @@ Exe net6.0 - 2.29.3 + 2.29.4 8.0 diff --git a/src/MarginTrading.Common/MarginTrading.Common.csproj b/src/MarginTrading.Common/MarginTrading.Common.csproj index 721245223..9c575d36d 100644 --- a/src/MarginTrading.Common/MarginTrading.Common.csproj +++ b/src/MarginTrading.Common/MarginTrading.Common.csproj @@ -6,7 +6,7 @@ false false false - 2.29.3 + 2.29.4 8.0 diff --git a/src/MarginTrading.Contract/MarginTrading.Contract.csproj b/src/MarginTrading.Contract/MarginTrading.Contract.csproj index 70e56a084..2110a4171 100644 --- a/src/MarginTrading.Contract/MarginTrading.Contract.csproj +++ b/src/MarginTrading.Contract/MarginTrading.Contract.csproj @@ -1,7 +1,7 @@  netstandard2.0 - 2.29.3 + 2.29.4 Lykke.MarginTrading.Contracts 8.0 diff --git a/src/MarginTrading.SqlRepositories/MarginTrading.SqlRepositories.csproj b/src/MarginTrading.SqlRepositories/MarginTrading.SqlRepositories.csproj index a4e309a2f..a4f18ffaa 100644 --- a/src/MarginTrading.SqlRepositories/MarginTrading.SqlRepositories.csproj +++ b/src/MarginTrading.SqlRepositories/MarginTrading.SqlRepositories.csproj @@ -6,7 +6,7 @@ false false false - 2.29.3 + 2.29.4 8.0