diff --git a/src/MarginTrading.Backend.Core/Extensions/InstrumentBidAskPairExtensions.cs b/src/MarginTrading.Backend.Core/Extensions/InstrumentBidAskPairExtensions.cs
new file mode 100644
index 000000000..606bc20b5
--- /dev/null
+++ b/src/MarginTrading.Backend.Core/Extensions/InstrumentBidAskPairExtensions.cs
@@ -0,0 +1,23 @@
+namespace MarginTrading.Backend.Core.Extensions
+{
+ public static class InstrumentBidAskPairExtensions
+ {
+ ///
+ /// Calculates a hash for the bid/ask pair, which is used only to determine
+ /// if the quote has already been warned about being stale. Probably, the
+ /// implementation entirely correct only for the stale usage scenario.
+ ///
+ ///
+ ///
+ public static int GetStaleHash(this InstrumentBidAskPair bidAskPair)
+ {
+ unchecked
+ {
+ int hash = 23;
+ hash = hash * 31 + (bidAskPair?.Instrument.GetHashCode() ?? 0);
+ hash = hash * 31 + (bidAskPair?.Date.GetHashCode() ?? 0);
+ return hash;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/MarginTrading.Backend.Core/Settings/MarginTradingSettings.cs b/src/MarginTrading.Backend.Core/Settings/MarginTradingSettings.cs
index 88063dfa1..1c15bda5a 100644
--- a/src/MarginTrading.Backend.Core/Settings/MarginTradingSettings.cs
+++ b/src/MarginTrading.Backend.Core/Settings/MarginTradingSettings.cs
@@ -122,5 +122,8 @@ public class MarginTradingSettings
[Optional]
public bool LogBlockedMarginCalculation { get; set; }
+
+ [Optional]
+ public MonitoringSettings Monitoring { get; set; }
}
}
\ No newline at end of file
diff --git a/src/MarginTrading.Backend.Core/Settings/MonitoringSettings.cs b/src/MarginTrading.Backend.Core/Settings/MonitoringSettings.cs
new file mode 100644
index 000000000..0299537f9
--- /dev/null
+++ b/src/MarginTrading.Backend.Core/Settings/MonitoringSettings.cs
@@ -0,0 +1,13 @@
+// Copyright (c) 2019 Lykke Corp.
+// See the LICENSE file in the project root for more information.
+
+using Lykke.SettingsReader.Attributes;
+
+namespace MarginTrading.Backend.Core.Settings
+{
+ public class MonitoringSettings
+ {
+ [Optional]
+ public QuotesMonitoringSettings Quotes { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/MarginTrading.Backend.Core/Settings/QuotesMonitoringSettings.cs b/src/MarginTrading.Backend.Core/Settings/QuotesMonitoringSettings.cs
new file mode 100644
index 000000000..e7ba1f40e
--- /dev/null
+++ b/src/MarginTrading.Backend.Core/Settings/QuotesMonitoringSettings.cs
@@ -0,0 +1,13 @@
+// Copyright (c) 2019 Lykke Corp.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace MarginTrading.Backend.Core.Settings
+{
+ public class QuotesMonitoringSettings
+ {
+ public bool IsEnabled { get; set; }
+ public TimeSpan ConsiderQuoteStalePeriod { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj b/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj
index b42dfe156..347133eba 100644
--- a/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj
+++ b/src/MarginTrading.Backend.Services/MarginTrading.Backend.Services.csproj
@@ -50,8 +50,7 @@
-
- <_Parameter1>MarginTradingTests
-
+
+
\ No newline at end of file
diff --git a/src/MarginTrading.Backend.Services/Modules/ServicesModule.cs b/src/MarginTrading.Backend.Services/Modules/ServicesModule.cs
index 2c0b3d7a1..79495224e 100644
--- a/src/MarginTrading.Backend.Services/Modules/ServicesModule.cs
+++ b/src/MarginTrading.Backend.Services/Modules/ServicesModule.cs
@@ -33,6 +33,13 @@ namespace MarginTrading.Backend.Services.Modules
{
public class ServicesModule : Module
{
+ private readonly MarginTradingSettings _settings;
+
+ public ServicesModule(MarginTradingSettings settings)
+ {
+ _settings = settings;
+ }
+
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType()
@@ -40,7 +47,12 @@ protected override void Load(ContainerBuilder builder)
.As()
.As>()
.SingleInstance();
-
+
+ if (_settings.Monitoring?.Quotes?.IsEnabled ?? false)
+ {
+ builder.RegisterDecorator();
+ }
+
builder.RegisterType()
.AsSelf()
.As()
diff --git a/src/MarginTrading.Backend.Services/Quotes/QuoteCacheInspector.cs b/src/MarginTrading.Backend.Services/Quotes/QuoteCacheInspector.cs
new file mode 100644
index 000000000..1ac610b24
--- /dev/null
+++ b/src/MarginTrading.Backend.Services/Quotes/QuoteCacheInspector.cs
@@ -0,0 +1,118 @@
+// Copyright (c) 2019 Lykke Corp.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using MarginTrading.Backend.Core;
+using MarginTrading.Backend.Core.Extensions;
+using MarginTrading.Backend.Core.Quotes;
+using MarginTrading.Backend.Core.Settings;
+using MarginTrading.Common.Services;
+using Microsoft.Extensions.Logging;
+
+namespace MarginTrading.Backend.Services.Quotes
+{
+ ///
+ /// Inspects the quotes returned from the cache.
+ /// Logs a warning if the quote is not up to date (Is older than 5 seconds).
+ ///
+ internal sealed class QuoteCacheInspector : IQuoteCacheService
+ {
+ private readonly IQuoteCacheService _decoratee;
+ private readonly IDateService _dateService;
+ private readonly ILogger _logger;
+ private readonly TimeSpan _quoteStalePeriod;
+ private readonly ConcurrentDictionary _warnedQuotes = new ConcurrentDictionary();
+
+ public QuoteCacheInspector(IQuoteCacheService decoratee,
+ IDateService dateService,
+ ILogger logger,
+ MarginTradingSettings settings)
+ {
+ _decoratee = decoratee;
+ _dateService = dateService;
+ _logger = logger;
+ _quoteStalePeriod = settings.Monitoring?.Quotes?.ConsiderQuoteStalePeriod ?? TimeSpan.FromSeconds(5);
+
+ _logger.LogInformation("Quote Cache Inspector is activated with stale period {stalePeriod}",
+ _quoteStalePeriod);
+ }
+
+ public InstrumentBidAskPair GetQuote(string instrument)
+ {
+ var quote = _decoratee.GetQuote(instrument);
+
+ try
+ {
+ if (CanWarn(quote))
+ WarnOnStaleQuote(quote);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Failed to inspect quote for {instrument}", instrument);
+ }
+
+ return quote;
+ }
+
+ public Dictionary GetAllQuotes()
+ {
+ return _decoratee.GetAllQuotes();
+ }
+
+ public bool TryGetQuoteById(string instrument, out InstrumentBidAskPair result)
+ {
+ var success = _decoratee.TryGetQuoteById(instrument, out var quote);
+
+ try
+ {
+ if (CanWarn(quote))
+ WarnOnStaleQuote(quote);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Failed to inspect quote for {instrument}", instrument);
+ }
+
+ result = quote;
+ return success;
+ }
+
+ public RemoveQuoteError RemoveQuote(string assetPairId)
+ {
+ return _decoratee.RemoveQuote(assetPairId);
+ }
+
+ public void WarnOnStaleQuote(InstrumentBidAskPair quote)
+ {
+ if (quote == null)
+ return;
+
+ _logger.LogWarning("Quote for {instrument} is stale. Quote date: {quoteDate}, now: {now}",
+ quote.Instrument, quote.Date, _dateService.Now());
+
+ _warnedQuotes.TryAdd(quote.GetStaleHash(), 0);
+ }
+
+ public bool CanWarn(InstrumentBidAskPair quote)
+ {
+ if (quote == null)
+ return false;
+
+ var current = _dateService.Now();
+ if (IsQuoteStale(quote.Date, current, _quoteStalePeriod))
+ {
+ var hash = quote.GetStaleHash();
+ return !_warnedQuotes.ContainsKey(hash);
+ }
+
+ return false;
+ }
+
+ public static bool IsQuoteStale(DateTime quoteDateTime, DateTime now, TimeSpan stalePeriod)
+ {
+ return now.Subtract(quoteDateTime) > stalePeriod;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/MarginTrading.Backend.Services/Services/OrderValidator.cs b/src/MarginTrading.Backend.Services/Services/OrderValidator.cs
index a0e1db735..5620cec96 100644
--- a/src/MarginTrading.Backend.Services/Services/OrderValidator.cs
+++ b/src/MarginTrading.Backend.Services/Services/OrderValidator.cs
@@ -436,13 +436,6 @@ private void ValidateBaseOrderPrice(Order order, decimal? orderPrice)
throw new OrderRejectionException(OrderRejectReason.NoLiquidity, "Quote not found");
}
- //TODO: implement in MTC-155
-// if (_assetDayOffService.ArePendingOrdersDisabled(order.AssetPairId))
-// {
-// throw new ValidateOrderException(OrderRejectReason.NoLiquidity,
-// "Trades for instrument are not available");
-// }
-
if (order.OrderType == OrderType.Limit)
{
if (order.Direction == OrderDirection.Buy && quote.Ask <= orderPrice)
diff --git a/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs b/src/MarginTrading.Backend.Services/Workflow/SpecialLiquidation/SpecialLiquidationSaga.cs
index 65c7a28b8..0c420bb5a 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;
-using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Common.Log;
@@ -455,16 +454,6 @@ await InternalRetryPriceRequest(e.CreationTime, sender, executionInfo,
}
}
- 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;
- }
-
private void RequestPrice(ICommandSender sender, IOperationExecutionInfo
executionInfo)
{
diff --git a/src/MarginTrading.Backend/Startup.cs b/src/MarginTrading.Backend/Startup.cs
index f304c7947..68713775b 100644
--- a/src/MarginTrading.Backend/Startup.cs
+++ b/src/MarginTrading.Backend/Startup.cs
@@ -223,7 +223,7 @@ private static void RegisterModules(
builder.RegisterModule(new EventModule());
builder.RegisterModule(new CacheModule());
builder.RegisterModule(new ManagersModule());
- builder.RegisterModule(new ServicesModule());
+ builder.RegisterModule(new ServicesModule(settings.CurrentValue));
builder.RegisterModule(new BackendServicesModule(
mtSettings.CurrentValue,
settings.CurrentValue,
diff --git a/tests/MarginTradingTests/BaseTests.cs b/tests/MarginTradingTests/BaseTests.cs
index 3c2830fa3..1f117be20 100644
--- a/tests/MarginTradingTests/BaseTests.cs
+++ b/tests/MarginTradingTests/BaseTests.cs
@@ -32,6 +32,7 @@
using MarginTrading.Backend.Services.AssetPairs;
using MarginTrading.Backend.Services.Quotes;
using MarginTradingTests.Modules;
+using Microsoft.Extensions.Logging;
using Moq;
namespace MarginTradingTests
@@ -83,6 +84,9 @@ private void RegisterDependenciesCore(bool mockEvents = false)
OvernightMargin = overnightMarginSettings
};
+ // register mocks of loggers
+ builder.RegisterInstance(Mock.Of>());
+
builder.RegisterInstance(marginSettings).SingleInstance();
builder.RegisterInstance(PositionHistoryEvents).As>().SingleInstance();
builder.RegisterInstance(overnightMarginSettings).SingleInstance();
@@ -123,7 +127,7 @@ private void RegisterDependenciesCore(bool mockEvents = false)
}
builder.RegisterModule(new CacheModule());
- builder.RegisterModule(new ServicesModule());
+ builder.RegisterModule(new ServicesModule(marginSettings));
builder.RegisterModule(new ManagersModule());
builder.RegisterType>()
diff --git a/tests/MarginTradingTests/QuoteInspectorTests.cs b/tests/MarginTradingTests/QuoteInspectorTests.cs
new file mode 100644
index 000000000..c2e430a1b
--- /dev/null
+++ b/tests/MarginTradingTests/QuoteInspectorTests.cs
@@ -0,0 +1,129 @@
+// Copyright (c) 2019 Lykke Corp.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using FluentAssertions;
+using MarginTrading.Backend.Core;
+using MarginTrading.Backend.Core.Extensions;
+using MarginTrading.Backend.Core.Settings;
+using MarginTrading.Backend.Services.Quotes;
+using MarginTrading.Common.Services;
+using Microsoft.Extensions.Logging;
+using Moq;
+using NUnit.Framework;
+
+namespace MarginTradingTests
+{
+ [TestFixture]
+ public class QuoteInspectorTests
+ {
+ private IDateService _dateService;
+ private static readonly TimeSpan StalePeriod = TimeSpan.FromSeconds(5);
+
+
+ [SetUp]
+ public void SetUp()
+ {
+ _dateService = new DateService();
+ }
+
+ [Test]
+ [TestCaseSource(nameof(IsQuoteStaleTestCases))]
+ public void IsQuoteStale_WhenQuoteIsStale_ShouldReturnTrue(string quoteTimestamp,
+ string now,
+ string stalePeriod)
+ {
+ var quoteTimestampParsed = DateTime.Parse(quoteTimestamp);
+ var nowParsed = DateTime.Parse(now);
+ var stalePeriodParsed = TimeSpan.Parse(stalePeriod);
+
+ QuoteCacheInspector.IsQuoteStale(quoteTimestampParsed, nowParsed, stalePeriodParsed).Should().BeTrue();
+ }
+
+ [Test]
+ public void CanWarn_WhenQuoteIsNull_ShouldReturnFalse()
+ {
+ var sut = CreateSut();
+
+ sut.CanWarn(null).Should().BeFalse();
+ }
+
+ [TestCase(1)]
+ [TestCase(100)]
+ [TestCase(100000)]
+ public void CanWarn_WhenQuoteIsStale_ShouldReturnTrue(int addSeconds)
+ {
+ var sut = CreateSut();
+
+ var staleQuoteDateTime = _dateService.Now() - StalePeriod.Add(TimeSpan.FromSeconds(addSeconds));
+
+ var canWarn = sut.CanWarn(new InstrumentBidAskPair
+ { Instrument = "whatever", Date = staleQuoteDateTime });
+
+ canWarn.Should().BeTrue();
+ }
+
+ [Test]
+ public void CanWarn_WhenSameQuote_ShouldReturnFalse()
+ {
+ var sut = CreateSut();
+
+ var staleQuote = new InstrumentBidAskPair { Instrument = "whatever", Date = DateTime.MinValue };
+
+ var canWarnFirstTime = sut.CanWarn(staleQuote);
+
+ canWarnFirstTime.Should().BeTrue();
+ sut.WarnOnStaleQuote(staleQuote);
+
+ var canWarnSecondTime = sut.CanWarn(staleQuote);
+
+ canWarnSecondTime.Should().BeFalse();
+ }
+
+ [Test]
+ public void GetQuoteHash_Same_For_Instrument_And_Date()
+ {
+ var quoteDate = DateTime.MinValue;
+
+ var quote1 = new InstrumentBidAskPair { Instrument = "whatever", Date = quoteDate };
+ var quote2 = new InstrumentBidAskPair { Instrument = "whatever", Date = quoteDate, Ask = 1 };
+
+ Assert.AreEqual(quote1.GetStaleHash(), quote2.GetStaleHash());
+ }
+
+ [Test]
+ public void GetQuoteHash_Different_When_Date_Different()
+ {
+ var quoteDate = DateTime.MinValue;
+
+ var quote1 = new InstrumentBidAskPair { Instrument = "whatever", Date = quoteDate };
+ var quote2 = new InstrumentBidAskPair { Instrument = "whatever", Date = quoteDate.AddTicks(1) };
+
+ Assert.AreNotEqual(quote1.GetStaleHash(), quote2.GetStaleHash());
+ }
+
+ public static object[] IsQuoteStaleTestCases =
+ {
+ new object[] { "2000-01-01", "2001-01-01", "00:00:05" },
+ new object[] { "2000-01-01", "2000-01-01 00:00:06", "00:00:05" },
+ new object[] { "2000-01-01 12:12:12", "2000-01-01 12:13:00", "00:00:05" },
+ new object[] { "2000-01-01 12:00:00", "2000-01-11 12:00:00", "5.0:00:00" },
+ new object[] { "2000-01-01", "2000-01-01 00:00:05.001", "00:00:05" },
+ };
+
+ private QuoteCacheInspector CreateSut()
+ {
+ return new QuoteCacheInspector(Mock.Of(),
+ _dateService,
+ Mock.Of>(),
+ new MarginTradingSettings
+ {
+ Monitoring = new MonitoringSettings
+ {
+ Quotes = new QuotesMonitoringSettings { ConsiderQuoteStalePeriod = StalePeriod }
+ }
+ }
+ );
+ }
+ }
+}
\ No newline at end of file