diff --git a/Engine/TransactionHandlers/BrokerageTransactionHandler.cs b/Engine/TransactionHandlers/BrokerageTransactionHandler.cs index 56ba6b48b31e..e4aec9b5a58a 100644 --- a/Engine/TransactionHandlers/BrokerageTransactionHandler.cs +++ b/Engine/TransactionHandlers/BrokerageTransactionHandler.cs @@ -54,7 +54,9 @@ public class BrokerageTransactionHandler : ITransactionHandler private int _totalOrderCount; // this bool is used to check if the warning message for the rounding of order quantity has been displayed for the first time - private bool _firstRoundOffMessage = false; + private bool _firstRoundOffMessage; + // this bool is used to check if the warning message for price rounding has been displayed for the first time + private bool _hasLoggedPriceRoundingWarning; // this value is used for determining how confident we are in our cash balance update private long _lastFillTimeTicks; @@ -1914,11 +1916,12 @@ private void InvalidateOrders(List orders, string message) private void SendWarningOnPriceChange(string priceType, decimal priceRound, decimal priceOriginal) { - if (!priceOriginal.Equals(priceRound)) + if (!priceOriginal.Equals(priceRound) && !_hasLoggedPriceRoundingWarning) { _algorithm.Error( $"Warning: To meet brokerage precision requirements, order {priceType.ToStringInvariant()} was rounded to {priceRound.ToStringInvariant()} from {priceOriginal.ToStringInvariant()}" ); + _hasLoggedPriceRoundingWarning = true; } } diff --git a/Tests/Engine/BrokerageTransactionHandlerTests/BrokerageTransactionHandlerTests.cs b/Tests/Engine/BrokerageTransactionHandlerTests/BrokerageTransactionHandlerTests.cs index 842407d4c98a..795ffacac376 100644 --- a/Tests/Engine/BrokerageTransactionHandlerTests/BrokerageTransactionHandlerTests.cs +++ b/Tests/Engine/BrokerageTransactionHandlerTests/BrokerageTransactionHandlerTests.cs @@ -13,11 +13,6 @@ * limitations under the License. */ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading; using Moq; using NodaTime; using NUnit.Framework; @@ -26,19 +21,24 @@ using QuantConnect.Brokerages; using QuantConnect.Brokerages.Backtesting; using QuantConnect.Data; +using QuantConnect.Data.Market; +using QuantConnect.Interfaces; using QuantConnect.Lean.Engine.Results; using QuantConnect.Lean.Engine.TransactionHandlers; using QuantConnect.Orders; -using QuantConnect.Securities; -using QuantConnect.Data.Market; -using QuantConnect.Interfaces; using QuantConnect.Orders.Fees; using QuantConnect.Packets; +using QuantConnect.Securities; using QuantConnect.Tests.Engine.DataFeeds; using QuantConnect.Tests.Engine.Setup; using QuantConnect.Util; -using HistoryRequest = QuantConnect.Data.HistoryRequest; +using System; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using HistoryRequest = QuantConnect.Data.HistoryRequest; namespace QuantConnect.Tests.Engine.BrokerageTransactionHandlerTests { @@ -68,7 +68,7 @@ public void Initialize() [TearDown] public void TearDown() { - _transactionHandler?.Exit(); + _transactionHandler?.Exit(); } private static SubmitOrderRequest MakeOrderRequest(Security security, OrderType orderType, DateTime date) @@ -714,6 +714,41 @@ public void RoundOff_Long_Fractional_Orders() Assert.AreEqual(123.12345678m, actual); } + [Test] + public void PriceRoundingWarningLogsOnlyOnceWithMultipleOrders() + { + var algo = new QCAlgorithm(); + algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo)); + algo.SetBrokerageModel(BrokerageName.Default); + + var security = algo.AddSecurity(SecurityType.Equity, "SPY", Resolution.Minute, Market.USA, false, 1m, false); + security.PriceVariationModel = new TestPriceVariationModel(0.01m); + + var transactionHandler = new TestBrokerageTransactionHandler(); + using var brokerage = new BacktestingBrokerage(algo); + transactionHandler.Initialize(algo, brokerage, new BacktestingResultHandler()); + var hasLoggedField = typeof(BrokerageTransactionHandler).GetField("_hasLoggedPriceRoundingWarning", BindingFlags.NonPublic | BindingFlags.Instance); + var hasLogged = (bool)hasLoggedField.GetValue(transactionHandler); + + Assert.IsFalse(hasLogged); + + var date = new DateTime(2013, 10, 7, 9, 35, 0); + var orders = new[] + { + new LimitOrder(security.Symbol, 1000, 123.252m, date), + new LimitOrder(security.Symbol, 1000, 234.259m, date.AddDays(1)), + new LimitOrder(security.Symbol, 1000, 345.225m, date.AddDays(2)), + new LimitOrder(security.Symbol, 1000, 456.235m, date.AddDays(3)) + }; + + for (int i = 0; i < orders.Length; i++) + { + transactionHandler.RoundOrderPrices(orders[i], security); + hasLogged = (bool)hasLoggedField.GetValue(transactionHandler); + Assert.IsTrue(hasLogged); + } + } + [Test] public void RoundOff_Short_Fractional_Orders() { @@ -2397,7 +2432,7 @@ public void ProcessesOrdersConcurrently() using var finishedEvent = new ManualResetEventSlim(false); var transactionHandler = new TestableConcurrentBrokerageTransactionHandler(expectedOrdersCount, finishedEvent); transactionHandler.Initialize(algorithm, brokerage, new BacktestingResultHandler()); - + try { algorithm.Transactions.SetOrderProcessor(transactionHandler); @@ -2705,6 +2740,21 @@ protected override void InitializeTransactionThread() } } + private class TestPriceVariationModel : IPriceVariationModel + { + private readonly decimal _increment; + + public TestPriceVariationModel(decimal increment) + { + _increment = increment; + } + + public decimal GetMinimumPriceVariation(GetMinimumPriceVariationParameters parameters) + { + return _increment; + } + } + private class TestNonShortableProvider : IShortableProvider { public Dictionary AllShortableSymbols(DateTime localTime)