Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Engine/TransactionHandlers/BrokerageTransactionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1914,11 +1916,12 @@ private void InvalidateOrders(List<Order> 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;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<Symbol, long> AllShortableSymbols(DateTime localTime)
Expand Down