Files
2026-07-13 13:02:50 +08:00

3219 lines
156 KiB
C#

/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Moq;
using NodaTime;
using NUnit.Framework;
using QuantConnect.Algorithm;
using QuantConnect.Algorithm.CSharp;
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.Orders.Fees;
using QuantConnect.Packets;
using QuantConnect.Securities;
using QuantConnect.Tests.Engine.DataFeeds;
using QuantConnect.Tests.Engine.Setup;
using QuantConnect.Util;
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
{
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
public class BrokerageTransactionHandlerTests
{
private const string Ticker = "EURUSD";
private MethodInfo _handleOptionNotification;
private TestAlgorithm _algorithm;
private Symbol _symbol;
private TestBrokerageTransactionHandler _transactionHandler;
[SetUp]
public void Initialize()
{
_algorithm = new TestAlgorithm { HistoryProvider = new EmptyHistoryProvider() };
_algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(_algorithm));
_algorithm.SetBrokerageModel(BrokerageName.FxcmBrokerage);
_algorithm.SetCash(100000);
_symbol = _algorithm.AddSecurity(SecurityType.Forex, Ticker).Symbol;
_algorithm.SetFinishedWarmingUp();
_handleOptionNotification = typeof(BrokerageTransactionHandler).GetMethod("HandleOptionNotification", BindingFlags.NonPublic | BindingFlags.Instance);
Assert.IsNotNull(_handleOptionNotification);
}
[TearDown]
public void TearDown()
{
_transactionHandler?.Exit();
}
private static SubmitOrderRequest MakeOrderRequest(Security security, OrderType orderType, DateTime date)
{
var groupOrderManager = new GroupOrderManager(1, 1, 100, orderType == OrderType.ComboLimit ? 100 : 0);
return orderType switch
{
OrderType.Market => new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 1, 0, 0, date, ""),
OrderType.Limit => new SubmitOrderRequest(OrderType.Limit, security.Type, security.Symbol, 1, 0, 290, date, ""),
OrderType.StopMarket => new SubmitOrderRequest(OrderType.StopMarket, security.Type, security.Symbol, 1, 305, 0, date, ""),
OrderType.StopLimit => new SubmitOrderRequest(OrderType.StopLimit, security.Type, security.Symbol, 1, 305, 295, date, ""),
OrderType.MarketOnOpen => new SubmitOrderRequest(OrderType.MarketOnOpen, security.Type, security.Symbol, 1, 0, 0, date, ""),
OrderType.MarketOnClose => new SubmitOrderRequest(OrderType.MarketOnClose, security.Type, security.Symbol, 1, 0, 0, date, ""),
OrderType.LimitIfTouched => new SubmitOrderRequest(OrderType.LimitIfTouched, security.Type, security.Symbol, 1, 0, 300, 305, date, ""),
OrderType.OptionExercise => new SubmitOrderRequest(OrderType.OptionExercise, security.Type, security.Symbol, 1, 0, 0, date, ""),
OrderType.ComboMarket => new SubmitOrderRequest(OrderType.ComboMarket, security.Type, security.Symbol, 1, 0, 0, date, "", groupOrderManager: groupOrderManager),
OrderType.ComboLimit => new SubmitOrderRequest(OrderType.ComboLimit, security.Type, security.Symbol, 1, 295, 0, date, "", groupOrderManager: groupOrderManager),
OrderType.ComboLegLimit => new SubmitOrderRequest(OrderType.ComboLegLimit, security.Type, security.Symbol, 1, 295, 0, date, "", groupOrderManager: groupOrderManager),
OrderType.TrailingStop => new SubmitOrderRequest(OrderType.TrailingStop, security.Type, security.Symbol, 1, 305, 0, 305, date, ""),
_ => throw new ArgumentOutOfRangeException(nameof(orderType), orderType, null)
};
}
[TestCase("1", 3)]
public void RetrieveComboOrdersWithTheSameBrokerageIdEvenIfOneOfThemIsFilled(string brokerageOrderId, int expectedComboOrdersCount)
{
var symbol = Symbols.AAPL;
_algorithm.AddSecurity(symbol);
_algorithm.Securities[symbol].SetMarketPrice(new Tick(DateTime.UtcNow.AddDays(-1), symbol, 220m, 220m, 220m));
var transactionHandler = new BrokerageTransactionHandler();
using var brokerage = new TestingBrokerage();
transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
try
{
var groupOrderManager = new GroupOrderManager(1, 3, 10);
var comboOrders = new List<ComboMarketOrder>
{
new ComboMarketOrder(symbol, 10m, DateTime.UtcNow, groupOrderManager) { Status = OrderStatus.Submitted },
new ComboMarketOrder(symbol, -10m, DateTime.UtcNow, groupOrderManager) { Status = OrderStatus.PartiallyFilled },
new ComboMarketOrder(symbol, 10m, DateTime.UtcNow, groupOrderManager) { Status = OrderStatus.Filled }
};
foreach (var comboOrder in comboOrders)
{
comboOrder.BrokerId.Add(brokerageOrderId);
transactionHandler.AddOpenOrder(comboOrder, _algorithm);
}
var openOrders = transactionHandler.GetOrdersByBrokerageId(brokerageOrderId);
Assert.IsNotEmpty(openOrders);
Assert.That(openOrders.Count, Is.EqualTo(expectedComboOrdersCount));
Assert.True(openOrders.Any(o => o.Status == OrderStatus.Submitted));
Assert.True(openOrders.Any(o => o.Status == OrderStatus.PartiallyFilled));
Assert.True(openOrders.Any(o => o.Status == OrderStatus.Filled));
}
finally
{
transactionHandler.Exit();
}
}
[Test]
public void OrderTagIsSetToTheDefaultOne([Values] OrderType orderType)
{
var reference = new DateTime(2024, 01, 25, 10, 0, 0);
// Initialize the algorithm
var algorithm = new TestAlgorithm { HistoryProvider = new EmptyHistoryProvider() };
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
algorithm.SetCash(100000);
var security = (Security)algorithm.AddEquity("SPY");
algorithm.SetFinishedWarmingUp();
//Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(algorithm);
_transactionHandler.Initialize(algorithm, brokerage, new BacktestingResultHandler());
// Set up security
security.SetMarketPrice(new Tick(reference, security.Symbol, 300, 300));
if (orderType == OrderType.OptionExercise)
{
algorithm.AddOption(security.Symbol);
security = algorithm.AddOptionContract(Symbol.CreateOption(security.Symbol, Market.USA, OptionStyle.American, OptionRight.Call,
300, reference.AddDays(4).Date));
security.SetMarketPrice(new Tick(reference, security.Symbol, 10, 10));
}
// Creates the order
var orderRequest = MakeOrderRequest(security, orderType, reference);
// Mock the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(algorithm.Transactions, orderRequest));
algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Act
var orderTicket = _transactionHandler.Process(orderRequest);
Assert.IsTrue(orderTicket.Status == OrderStatus.New);
_transactionHandler.HandleOrderRequest(orderRequest);
// Assert
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.AreEqual(OrderStatus.Submitted, orderTicket.Status);
// Assert the order tag is set to the default one
var order = _transactionHandler.GetOpenOrders().Single();
Assert.AreEqual(orderType, order.Type);
Assert.AreEqual(order.GetDefaultTag(), order.Tag);
}
[Test]
public void OrderQuantityIsFlooredToNearestMultipleOfLotSizeWhenLongOrderIsRounded()
{
//Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
// Creates the order
var security = _algorithm.Securities[_symbol];
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 1600, 0, 0, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Act
var orderTicket = _transactionHandler.Process(orderRequest);
Assert.IsTrue(orderTicket.Status == OrderStatus.New);
_transactionHandler.HandleOrderRequest(orderRequest);
// Assert
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.IsTrue(orderTicket.Status == OrderStatus.Submitted);
// 1600 after round off becomes 1000
Assert.AreEqual(1000, orderTicket.Quantity);
}
[Test]
public void BrokerageOrderIdChanged()
{
//Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var testBrokerage = new TestBroker(_algorithm, true);
_transactionHandler.Initialize(_algorithm, testBrokerage, new BacktestingResultHandler());
// Creates the order
var security = _algorithm.Securities[_symbol];
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 1600, 0, 0, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Act
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
var originalBrokerageOrderId = _transactionHandler.GetOrderById(orderTicket.OrderId).BrokerId;
var orderIdChanged = new BrokerageOrderIdChangedEvent { OrderId = orderTicket.OrderId, BrokerId = new List<string> { "asd" } };
testBrokerage.OnOrderIdChangedEventPublic(orderIdChanged);
var newBrokerageOrderId = _transactionHandler.GetOrderById(orderTicket.OrderId).BrokerId;
Assert.AreNotEqual(originalBrokerageOrderId, newBrokerageOrderId);
Assert.AreEqual(1, newBrokerageOrderId.Count);
Assert.AreEqual("asd", newBrokerageOrderId[0]);
}
[Test]
public void OrderQuantityIsCeiledToNearestMultipleOfLotSizeWhenShortOrderIsRounded()
{
//Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
// Creates the order
var security = _algorithm.Securities[_symbol];
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, -1600, 0, 0, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Act
var orderTicket = _transactionHandler.Process(orderRequest);
Assert.IsTrue(orderTicket.Status == OrderStatus.New);
_transactionHandler.HandleOrderRequest(orderRequest);
// Assert
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.IsTrue(orderTicket.Status == OrderStatus.Submitted);
// -1600 after round off becomes -1000
Assert.AreEqual(-1000, orderTicket.Quantity);
}
[Test]
public void OrderIsNotPlacedWhenOrderIsLowerThanLotSize()
{
//Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
// Creates the order
var security = _algorithm.Securities[_symbol];
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 600, 0, 0, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Act
var orderTicket = _transactionHandler.Process(orderRequest);
Assert.IsTrue(orderTicket.Status == OrderStatus.New);
_transactionHandler.HandleOrderRequest(orderRequest);
// 600 after round off becomes 0 -> order is not placed
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsError);
Assert.IsTrue(orderTicket.Status == OrderStatus.Invalid);
}
[Test]
public void GetOpenOrderTicketsDoesWorksCorrectly()
{
//Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
// Creates the order
var security = _algorithm.Securities[_symbol];
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 1000, 0, 0, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Act
var orderTicket = _transactionHandler.Process(orderRequest);
var newTicket = _transactionHandler.GetOpenOrderTickets(ticket => ticket.Symbol == security.Symbol).Single();
Assert.IsTrue(orderTicket.Status == OrderStatus.New);
Assert.AreEqual(newTicket, orderTicket);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsFalse(orderRequest.Response.IsError);
Assert.IsTrue(orderTicket.Status == OrderStatus.Submitted);
var processedTicket = _transactionHandler.GetOpenOrderTickets(ticket => ticket.Symbol == security.Symbol).ToList();
Assert.IsNotEmpty(processedTicket);
}
[Test]
public void GetOpenOrderTicketsDoesNotReturnInvalidatedOrder()
{
//Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
// Creates the order
var security = _algorithm.Securities[_symbol];
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 600, 0, 0, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Act
var orderTicket = _transactionHandler.Process(orderRequest);
var newTicket = _transactionHandler.GetOpenOrderTickets(ticket => ticket.Symbol == security.Symbol).Single();
Assert.IsTrue(orderTicket.Status == OrderStatus.New);
Assert.AreEqual(newTicket, orderTicket);
_transactionHandler.HandleOrderRequest(orderRequest);
// 600 after round off becomes 0 -> order is not placed
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsError);
Assert.IsTrue(orderTicket.Status == OrderStatus.Invalid);
var processedTicket = _transactionHandler.GetOpenOrderTickets(ticket => ticket.Symbol == security.Symbol).ToList();
Assert.IsEmpty(processedTicket);
}
[TestCase("NDX", "1.14", "1.15")]
[TestCase("NDX", "1.16", "1.15")]
[TestCase("NDX", "4.14", "4.10")]
[TestCase("NDX", "4.16", "4.20")]
[TestCase("VIX", "1.14", "1.14")]
[TestCase("VIX", "1.16", "1.16")]
[TestCase("VIX", "4.14", "4.15")]
[TestCase("VIX", "4.18", "4.20")]
[TestCase("VIXW", "1.14", "1.14")]
[TestCase("VIXW", "1.16", "1.16")]
[TestCase("VIXW", "4.14", "4.14")]
[TestCase("VIXW", "4.16", "4.16")]
public void DynamicIndexOptionPriceRoundeding(string indexOption, string orderPriceStr, string expectedPriceStr)
{
var orderPrice = decimal.Parse(orderPriceStr, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture);
var expectedPrice = decimal.Parse(expectedPriceStr, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture);
//Initializes the transaction handler
_algorithm.SetBrokerageModel(new DefaultBrokerageModel());
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
// Creates the order
var security = _algorithm.AddIndexOption(indexOption);
var price = 1.12129m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Limit, security.Type, security.Symbol, 10, orderPrice, orderPrice, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Act
var orderTicket = _transactionHandler.Process(orderRequest);
Assert.IsTrue(orderTicket.Status == OrderStatus.New);
_transactionHandler.HandleOrderRequest(orderRequest);
// Assert
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.IsTrue(orderTicket.Status == OrderStatus.Submitted);
Assert.AreEqual(10, orderTicket.Quantity);
// 1.16 after round becomes 1.10
Assert.AreEqual(expectedPrice, orderTicket.Get(OrderField.LimitPrice));
}
[Test]
public void LimitOrderPriceIsRounded()
{
//Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
// Creates the order
var security = _algorithm.Securities[_symbol];
var price = 1.12129m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Limit, security.Type, security.Symbol, 1600, 1.12121212m, 1.12121212m, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Act
var orderTicket = _transactionHandler.Process(orderRequest);
Assert.IsTrue(orderTicket.Status == OrderStatus.New);
_transactionHandler.HandleOrderRequest(orderRequest);
// Assert
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.IsTrue(orderTicket.Status == OrderStatus.Submitted);
// 1600 after round off becomes 1000
Assert.AreEqual(1000, orderTicket.Quantity);
// 1.12121212 after round becomes 1.12121
Assert.AreEqual(1.12121m, orderTicket.Get(OrderField.LimitPrice));
}
[Test]
public void StopMarketOrderPriceIsRounded()
{
//Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
// Creates the order
var security = _algorithm.Securities[_symbol];
var price = 1.12129m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.StopMarket, security.Type, security.Symbol, 1600, 1.12131212m, 1.12131212m, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Act
var orderTicket = _transactionHandler.Process(orderRequest);
Assert.IsTrue(orderTicket.Status == OrderStatus.New);
_transactionHandler.HandleOrderRequest(orderRequest);
// Assert
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.IsTrue(orderTicket.Status == OrderStatus.Submitted);
// 1600 after round off becomes 1000
Assert.AreEqual(1000, orderTicket.Quantity);
// 1.12131212 after round becomes 1.12131
Assert.AreEqual(1.12131m, orderTicket.Get(OrderField.StopPrice));
}
[Test]
public void TrailingStopOrderPriceIsRounded([Values] bool trailingAsPercentage)
{
//Initialize the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
// Create the order
_algorithm.SetBrokerageModel(new DefaultBrokerageModel());
var security = _algorithm.AddEquity("SPY");
security.PriceVariationModel = new EquityPriceVariationModel();
var price = 330.12129m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.TrailingStop, security.Type, security.Symbol, 100, stopPrice: 300.12121212m, 0, 0,
trailingAmount: 20.12121212m, trailingAsPercentage, DateTime.Now, "");
// Mock the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Act
var orderTicket = _transactionHandler.Process(orderRequest);
Assert.IsTrue(orderTicket.Status == OrderStatus.New);
_transactionHandler.HandleOrderRequest(orderRequest);
// Assert
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.IsTrue(orderTicket.Status == OrderStatus.Submitted);
// 300.12121212 after round becomes 300.12
Assert.AreEqual(300.12m, orderTicket.Get(OrderField.StopPrice));
// If trailing amount is not a price, it's not rounded
Assert.AreEqual(trailingAsPercentage ? 20.12121212m : 20.12, orderTicket.Get(OrderField.TrailingAmount));
}
// 331.12121212m after round becomes 331.12m, the smallest price variation is 0.01 - index. For index options it is 0.1
[TestCase(OrderType.ComboLimit, 300.12121212, 0, 0, 300.12, 300.12)]
[TestCase(OrderType.ComboLegLimit, 0, 1.12121212, 300.13131313, 1.12, 300.1)]
[TestCase(OrderType.ComboLegLimit, 0, 1.12121212, 300.15151515, 1.12, 300.2)]
public void ComboLimitOrderPriceIsRounded(OrderType orderType, decimal groupOrderLimitPrice, decimal leg1LimitPrice, decimal leg2LimitPrice,
decimal expectedLeg1LimitPrice, decimal expectedLeg2LimitPrice)
{
var algorithm = new TestAlgorithm { HistoryProvider = new EmptyHistoryProvider() };
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
algorithm.SetBrokerageModel(BrokerageName.Default);
algorithm.SetCash(1000000);
var symbol1 = algorithm.AddIndex("SPX").Symbol;
var symbol2 = Symbol.CreateOption(symbol1, Market.USA, OptionStyle.European, OptionRight.Put, 300m, new DateTime(2024, 05, 16));
algorithm.AddIndexOptionContract(symbol2);
algorithm.SetFinishedWarmingUp();
//Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(algorithm);
_transactionHandler.Initialize(algorithm, brokerage, new BacktestingResultHandler());
var expectedGroupOrderLimitPrice = 0m;
if (orderType == OrderType.ComboLimit)
{
// legs have the same global limit price
leg1LimitPrice = groupOrderLimitPrice;
leg2LimitPrice = groupOrderLimitPrice;
expectedGroupOrderLimitPrice = expectedLeg2LimitPrice = expectedLeg1LimitPrice;
}
else if (orderType == OrderType.ComboLegLimit)
{
// Each leg has its own limit price
groupOrderLimitPrice = 0m;
expectedGroupOrderLimitPrice = 0m;
}
// Creates the orders
var dateTime = new DateTime(2024, 05, 14, 12, 0, 0);
var groupOrderManager = new GroupOrderManager(1, 2, 10, groupOrderLimitPrice);
var security1 = algorithm.Securities[symbol1];
var price1 = 1.12129m;
security1.SetMarketPrice(new Tick(dateTime, security1.Symbol, price1, price1, price1));
var orderRequest1 = new SubmitOrderRequest(orderType, security1.Type, security1.Symbol, 20, leg1LimitPrice, leg1LimitPrice,
dateTime, "", groupOrderManager: groupOrderManager);
var security2 = algorithm.Securities[symbol2];
var price2 = 330.12129m;
security2.SetMarketPrice(new Tick(dateTime, security2.Symbol, price2, price2, price2));
var orderRequest2 = new SubmitOrderRequest(orderType, security2.Type, security2.Symbol, 10, leg2LimitPrice, leg2LimitPrice,
dateTime, "", groupOrderManager: groupOrderManager);
orderRequest1.SetOrderId(1);
orderRequest2.SetOrderId(2);
groupOrderManager.OrderIds.Add(1);
groupOrderManager.OrderIds.Add(2);
// Mock the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(1)).Returns(new OrderTicket(algorithm.Transactions, orderRequest1));
orderProcessorMock.Setup(m => m.GetOrderTicket(2)).Returns(new OrderTicket(algorithm.Transactions, orderRequest2));
algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Act
var orderTicket1 = _transactionHandler.Process(orderRequest1);
Assert.AreEqual(OrderStatus.New, orderTicket1.Status);
_transactionHandler.HandleOrderRequest(orderRequest1);
var orderTicket2 = _transactionHandler.Process(orderRequest2);
Assert.AreEqual(OrderStatus.New, orderTicket2.Status);
_transactionHandler.HandleOrderRequest(orderRequest2);
// Assert
Assert.IsTrue(orderRequest1.Response.IsProcessed);
Assert.IsTrue(orderRequest1.Response.IsSuccess);
Assert.AreEqual(OrderStatus.Submitted, orderTicket1.Status);
Assert.AreEqual(expectedLeg1LimitPrice, orderTicket1.Get(OrderField.LimitPrice));
Assert.IsTrue(orderRequest2.Response.IsProcessed);
Assert.IsTrue(orderRequest2.Response.IsSuccess);
Assert.AreEqual(OrderStatus.Submitted, orderTicket2.Status);
Assert.AreEqual(expectedLeg2LimitPrice, orderTicket2.Get(OrderField.LimitPrice));
Assert.AreEqual(expectedGroupOrderLimitPrice, groupOrderManager.LimitPrice);
}
[Test]
public void OrderCancellationTransitionsThroughCancelPendingStatus()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
// Creates a limit order
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Limit, security.Type, security.Symbol, 1000, 0, 1.11m, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Submit and process a limit order
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.IsTrue(orderTicket.Status == OrderStatus.Submitted);
// Cancel the order
var cancelRequest = new CancelOrderRequest(DateTime.Now, orderTicket.OrderId, "");
_transactionHandler.Process(cancelRequest);
Assert.IsTrue(cancelRequest.Response.IsProcessed);
Assert.IsTrue(cancelRequest.Response.IsSuccess);
Assert.IsTrue(cancelRequest.Status == OrderRequestStatus.Processing);
Assert.IsTrue(orderTicket.Status == OrderStatus.CancelPending);
Assert.AreEqual(_transactionHandler.CancelPendingOrdersSize, 1);
_transactionHandler.HandleOrderRequest(cancelRequest);
Assert.IsTrue(cancelRequest.Response.IsProcessed);
Assert.IsTrue(cancelRequest.Response.IsSuccess);
Assert.IsTrue(cancelRequest.Status == OrderRequestStatus.Processed);
Assert.IsTrue(orderTicket.Status == OrderStatus.Canceled);
Assert.AreEqual(_transactionHandler.CancelPendingOrdersSize, 0);
// Check CancelPending was sent
Assert.AreEqual(_algorithm.OrderEvents.Count, 3);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.Submitted), 1);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.CancelPending), 1);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.Canceled), 1);
}
[TestCase(0.9, 1.123456789, 1.12)]
[TestCase(0.9, 0.987654321, 0.9877)]
[TestCase(0.9, 0.999999999, 1)]
[TestCase(0.9, 1, 1)]
[TestCase(0.9, 1.000000001, 1)]
[TestCase(1.1, 1.123456789, 1.12)]
[TestCase(1.1, 0.987654321, 0.9877)]
[TestCase(1.1, 0.999999999, 1)]
[TestCase(1.1, 1, 1)]
[TestCase(1.1, 1.000000001, 1)]
public void RoundsEquityLimitOrderPricesCorrectly(decimal securityPrice, decimal orderPrice, decimal expected)
{
var algo = new QCAlgorithm();
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
algo.SetLiveMode(true);
var security = algo.AddEquity("YGTY");
security.SetMarketPrice(new Tick { Value = securityPrice });
_transactionHandler = new TestBrokerageTransactionHandler();
var brokerage = new Mock<IBrokerage>();
_transactionHandler.Initialize(algo, brokerage.Object, null);
var order = new LimitOrder(security.Symbol, 1000, orderPrice, DateTime.UtcNow);
_transactionHandler.RoundOrderPrices(order, security);
Assert.AreEqual(expected, order.LimitPrice);
}
[Test]
public void RoundOff_Long_Fractional_Orders()
{
var algo = new QCAlgorithm();
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
algo.SetBrokerageModel(BrokerageName.Default);
algo.SetCash(100000);
// Sets the Security
var security = algo.AddSecurity(SecurityType.Crypto, "BTCUSD", Resolution.Hour, Market.Coinbase, false, 1m, true);
//Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(algo);
_transactionHandler.Initialize(algo, brokerage, new BacktestingResultHandler());
// Creates the order
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 123.123456789m, 0, 0, DateTime.Now, "");
var order = Order.CreateOrder(orderRequest);
var actual = _transactionHandler.RoundOffOrder(order, security);
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()
{
var algo = new QCAlgorithm();
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
algo.SetBrokerageModel(BrokerageName.Default);
algo.SetCash(100000);
// Sets the Security
var security = algo.AddSecurity(SecurityType.Crypto, "BTCUSD", Resolution.Hour, Market.Coinbase, false, 1m, true);
//Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(algo);
_transactionHandler.Initialize(algo, brokerage, new BacktestingResultHandler());
// Creates the order
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, -123.123456789m, 0, 0, DateTime.Now, "");
var order = Order.CreateOrder(orderRequest);
var actual = _transactionHandler.RoundOffOrder(order, security);
Assert.AreEqual(-123.12345678m, actual);
}
[Test]
public void RoundOff_LessThanLotSize_Fractional_Orders()
{
var algo = new QCAlgorithm();
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
algo.SetBrokerageModel(BrokerageName.Default);
algo.SetCash(100000);
// Sets the Security
var security = algo.AddSecurity(SecurityType.Crypto, "BTCUSD", Resolution.Hour, Market.Coinbase, false, 1m, true);
//Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(algo);
_transactionHandler.Initialize(algo, brokerage, new BacktestingResultHandler());
// Creates the order
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 0.000000009m, 0, 0, DateTime.Now, "");
var order = Order.CreateOrder(orderRequest);
var actual = _transactionHandler.RoundOffOrder(order, security);
Assert.AreEqual(0, actual);
}
[Test]
public void InvalidUpdateOrderRequestShouldNotInvalidateCanceledOrder()
{
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new NoSubmitTestBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Limit, security.Type, security.Symbol, 1000, 0, 1.11m, DateTime.Now, "");
_algorithm.Transactions.SetOrderProcessor(_transactionHandler);
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
brokerage.PublishOrderEvent(new OrderEvent(_algorithm.Transactions.GetOrders().Single(), _algorithm.UtcTime, OrderFee.Zero)
{ Status = OrderStatus.Submitted });
Assert.AreEqual(OrderStatus.Submitted, orderTicket.Status);
var updateRequest = new UpdateOrderRequest(DateTime.Now, orderTicket.OrderId, new UpdateOrderFields() { Quantity = 10000 });
var updateTicket = _transactionHandler.Process(updateRequest);
_transactionHandler.HandleOrderRequest(updateRequest);
Assert.AreEqual(OrderRequestStatus.Processed, updateRequest.Status);
Assert.IsTrue(updateRequest.Response.IsSuccess);
// Canceled!
brokerage.PublishOrderEvent(new OrderEvent(_algorithm.Transactions.GetOrders().Single(), _algorithm.UtcTime, OrderFee.Zero)
{ Status = OrderStatus.Canceled });
Assert.AreEqual(OrderStatus.Canceled, orderTicket.Status);
// update failed!
brokerage.PublishOrderEvent(new OrderEvent(_algorithm.Transactions.GetOrders().Single(), _algorithm.UtcTime, OrderFee.Zero)
{ Status = OrderStatus.Invalid });
// nothing should change
Assert.AreEqual(OrderStatus.Canceled, orderTicket.Status);
}
[Test]
public void InvalidUpdateOrderRequestShouldNotInvalidateFilledOrder()
{
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new NoSubmitTestBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Limit, security.Type, security.Symbol, 1000, 0, 1.11m, DateTime.Now, "");
_algorithm.Transactions.SetOrderProcessor(_transactionHandler);
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
brokerage.PublishOrderEvent(new OrderEvent(_algorithm.Transactions.GetOrders().Single(), _algorithm.UtcTime, OrderFee.Zero)
{ Status = OrderStatus.Submitted });
Assert.AreEqual(OrderStatus.Submitted, orderTicket.Status);
var updateRequest = new UpdateOrderRequest(DateTime.Now, orderTicket.OrderId, new UpdateOrderFields() { Quantity = 10000 });
var updateTicket = _transactionHandler.Process(updateRequest);
_transactionHandler.HandleOrderRequest(updateRequest);
Assert.AreEqual(OrderRequestStatus.Processed, updateRequest.Status);
Assert.IsTrue(updateRequest.Response.IsSuccess);
// filled!
brokerage.PublishOrderEvent(new OrderEvent(_algorithm.Transactions.GetOrders().Single(), _algorithm.UtcTime, OrderFee.Zero)
{ Status = OrderStatus.Filled, FillQuantity = 1000, FillPrice = price });
Assert.AreEqual(1000, security.Holdings.Quantity);
Assert.AreEqual(price, security.Holdings.AveragePrice);
Assert.AreEqual(OrderStatus.Filled, orderTicket.Status);
// update failed!
brokerage.PublishOrderEvent(new OrderEvent(_algorithm.Transactions.GetOrders().Single(), _algorithm.UtcTime, OrderFee.Zero)
{ Status = OrderStatus.Invalid });
// nothing should change
Assert.AreEqual(1000, security.Holdings.Quantity);
Assert.AreEqual(price, security.Holdings.AveragePrice);
Assert.AreEqual(OrderStatus.Filled, orderTicket.Status);
}
[Test]
public void InvalidUpdateOrderRequestShouldNotInvalidateOrder()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
_algorithm.SetBrokerageModel(new TestBrokerageModel());
// Creates a limit order
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Limit, security.Type, security.Symbol, 1000, 0, 1.11m, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Submit and process a limit order
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.AreEqual(orderTicket.Status, OrderStatus.Submitted);
var updateRequest = new UpdateOrderRequest(DateTime.Now, orderTicket.OrderId, new UpdateOrderFields());
_transactionHandler.Process(updateRequest);
Assert.AreEqual(updateRequest.Status, OrderRequestStatus.Processing);
Assert.IsTrue(updateRequest.Response.IsSuccess);
Assert.AreEqual(orderTicket.Status, OrderStatus.Submitted);
_transactionHandler.HandleOrderRequest(updateRequest);
Assert.IsFalse(updateRequest.Response.ErrorMessage.IsNullOrEmpty());
Assert.IsTrue(updateRequest.Response.IsError);
Assert.AreEqual(orderTicket.Status, OrderStatus.Submitted);
Assert.AreEqual(_algorithm.OrderEvents.Count, 2);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Message.Contains("unable to update order")), 1);
Assert.IsTrue(_algorithm.OrderEvents.TrueForAll(orderEvent => orderEvent.Status == OrderStatus.Submitted));
}
[Test]
public void UpdateOrderRequestShouldWork()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
// Creates a limit order
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Limit, security.Type, security.Symbol, 1000, 0, 1.11m, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Submit and process a limit order
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.AreEqual(orderTicket.Status, OrderStatus.Submitted);
var updateRequest = new UpdateOrderRequest(DateTime.Now, orderTicket.OrderId, new UpdateOrderFields());
_transactionHandler.Process(updateRequest);
Assert.AreEqual(updateRequest.Status, OrderRequestStatus.Processing);
Assert.IsTrue(updateRequest.Response.IsSuccess);
Assert.AreEqual(OrderStatus.Submitted, orderTicket.Status);
_transactionHandler.HandleOrderRequest(updateRequest);
Assert.IsTrue(updateRequest.Response.IsSuccess);
Assert.AreEqual(OrderStatus.UpdateSubmitted, orderTicket.Status);
Assert.AreEqual(_algorithm.OrderEvents.Count, 2);
Assert.IsTrue(_algorithm.OrderEvents[0].Status == OrderStatus.Submitted);
Assert.IsTrue(_algorithm.OrderEvents[1].Status == OrderStatus.UpdateSubmitted);
}
[Test]
public void UpdatePartiallyFilledOrderRequestShouldWork()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using (var broker = new BacktestingBrokerage(_algorithm))
{
_transactionHandler.Initialize(_algorithm, broker, new BacktestingResultHandler());
// Creates a limit order
var security = _algorithm.Securities[_symbol];
var originalFillModel = security.FillModel;
security.SetFillModel(new PartialFillModel(_algorithm, 0.5m));
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 1000, 0,
1.11m, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>()))
.Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Submit and process a limit order
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.AreEqual(orderTicket.Status, OrderStatus.Submitted);
broker.Scan();
Assert.AreEqual(orderTicket.Status, OrderStatus.PartiallyFilled);
var updateRequest = new UpdateOrderRequest(DateTime.Now, orderTicket.OrderId, new UpdateOrderFields());
_transactionHandler.Process(updateRequest);
Assert.AreEqual(updateRequest.Status, OrderRequestStatus.Processing);
Assert.IsTrue(updateRequest.Response.IsSuccess);
Assert.AreEqual(OrderStatus.PartiallyFilled, orderTicket.Status);
_transactionHandler.HandleOrderRequest(updateRequest);
Assert.IsTrue(updateRequest.Response.IsSuccess);
Assert.AreEqual(OrderStatus.UpdateSubmitted, orderTicket.Status);
Assert.AreEqual(_algorithm.OrderEvents.Count, 3);
Assert.IsTrue(_algorithm.OrderEvents[0].Status == OrderStatus.Submitted);
Assert.IsTrue(_algorithm.OrderEvents[1].Status == OrderStatus.PartiallyFilled);
Assert.IsTrue(_algorithm.OrderEvents[2].Status == OrderStatus.UpdateSubmitted);
security.SetFillModel(originalFillModel);
}
}
[Test]
public void UpdateOrderRequestShouldFailForInvalidOrder()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var broker = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, broker, new BacktestingResultHandler());
// Creates a limit order
var security = _algorithm.Securities[_symbol];
_algorithm.Transactions.SetOrderProcessor(_transactionHandler);
var orderTicket = _algorithm.MarketOrder(security.Symbol, 1);
Assert.AreEqual(orderTicket.Status, OrderStatus.Invalid);
orderTicket.UpdateQuantity(10);
Assert.AreEqual(orderTicket.Status, OrderStatus.Invalid);
}
[Test]
public void CancelOrderRequestShouldFailForInvalidOrder()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var broker = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, broker, new BacktestingResultHandler());
// Creates a limit order
var security = _algorithm.Securities[_symbol];
_algorithm.Transactions.SetOrderProcessor(_transactionHandler);
var orderTicket = _algorithm.MarketOrder(security.Symbol, 1);
Assert.AreEqual(orderTicket.Status, OrderStatus.Invalid);
orderTicket.Cancel();
Assert.AreEqual(orderTicket.Status, OrderStatus.Invalid);
}
[Test]
public void UpdateOrderRequestShouldFailForFilledOrder()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var broker = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, broker, new BacktestingResultHandler());
// Creates a limit order
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 1000, 0, 1.11m, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Submit and process a limit order
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.AreEqual(orderTicket.Status, OrderStatus.Submitted);
broker.Scan();
Assert.AreEqual(orderTicket.Status, OrderStatus.Filled);
var updateRequest = new UpdateOrderRequest(DateTime.Now, orderTicket.OrderId, new UpdateOrderFields() { Quantity = 100 });
_transactionHandler.Process(updateRequest);
Assert.AreEqual(updateRequest.Status, OrderRequestStatus.Error);
Assert.IsTrue(updateRequest.Response.IsError);
Assert.AreEqual(updateRequest.Response.ErrorCode, OrderResponseErrorCode.InvalidOrderStatus);
Assert.AreEqual(orderTicket.Status, OrderStatus.Filled);
Assert.AreEqual(_algorithm.OrderEvents.Count, 2);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.Submitted), 1);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.Filled), 1);
}
[Test]
public void TagUpdateOrderRequestShouldSucceedForFilledOrder()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var broker = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, broker, new BacktestingResultHandler());
// Creates a limit order
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 1000, 0, 1.11m, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Submit and process a limit order
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.AreEqual(orderTicket.Status, OrderStatus.Submitted);
broker.Scan();
Assert.AreEqual(orderTicket.Status, OrderStatus.Filled);
var updateRequest = new UpdateOrderRequest(DateTime.Now, orderTicket.OrderId, new UpdateOrderFields() { Tag = "New tag" });
_transactionHandler.Process(updateRequest);
Assert.AreEqual(updateRequest.Status, OrderRequestStatus.Processing);
Assert.IsTrue(updateRequest.Response.IsSuccess);
Assert.AreEqual(_algorithm.OrderEvents.Count, 2);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.Submitted), 1);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.Filled), 1);
}
[Test]
public void UpdateOrderRequestShouldFailForNewOrderStatus()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new NoSubmitTestBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
// Creates a limit order
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Limit, security.Type, security.Symbol, 1000, 0, 1.11m, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Submit and process a limit order
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.AreEqual(orderTicket.Status, OrderStatus.New);
var updateRequest = new UpdateOrderRequest(DateTime.Now, orderTicket.OrderId, new UpdateOrderFields());
_transactionHandler.Process(updateRequest);
Assert.AreEqual(updateRequest.Status, OrderRequestStatus.Error);
Assert.IsTrue(updateRequest.Response.IsError);
Assert.AreEqual(updateRequest.Response.ErrorCode, OrderResponseErrorCode.InvalidNewOrderStatus);
Assert.AreEqual(orderTicket.Status, OrderStatus.New);
Assert.AreEqual(_algorithm.OrderEvents.Count, 0);
}
[Test]
public void CancelOrderRequestShouldFailForNewOrderStatus()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new NoSubmitTestBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
// Creates a limit order
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Limit, security.Type, security.Symbol, 1000, 0, 1.11m, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Submit and process a limit order
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.AreEqual(orderTicket.Status, OrderStatus.New);
var cancelRequest = new CancelOrderRequest(DateTime.Now, orderTicket.OrderId, "");
_transactionHandler.Process(cancelRequest);
Assert.AreEqual(cancelRequest.Status, OrderRequestStatus.Error);
Assert.IsTrue(cancelRequest.Response.IsError);
Assert.AreEqual(cancelRequest.Response.ErrorCode, OrderResponseErrorCode.InvalidNewOrderStatus);
Assert.AreEqual(orderTicket.Status, OrderStatus.New);
Assert.AreEqual(_algorithm.OrderEvents.Count, 0);
}
[Test]
public void CancelOrderTicket()
{
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new NoSubmitTestBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Limit, security.Type, security.Symbol, 1000, 0, 1.11m, DateTime.Now, "");
_algorithm.Transactions.SetOrderProcessor(_transactionHandler);
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.AreEqual(orderTicket.Status, OrderStatus.New);
// will fail because order status is new
var response = orderTicket.Cancel();
Assert.IsTrue(response.IsProcessed);
Assert.IsTrue(response.IsError);
Assert.AreEqual(response.ErrorCode, OrderResponseErrorCode.InvalidNewOrderStatus);
Assert.AreEqual(orderTicket.Status, OrderStatus.New);
Assert.AreEqual(_algorithm.OrderEvents.Count, 0);
var submitted = new OrderEvent(_algorithm.Transactions.GetOpenOrders().Single(), _algorithm.UtcTime, OrderFee.Zero)
{ Status = OrderStatus.Submitted };
brokerage.PublishOrderEvent(submitted);
Assert.AreEqual(orderTicket.Status, OrderStatus.Submitted);
var response2 = orderTicket.Cancel();
Assert.IsTrue(response2.IsProcessed);
Assert.IsFalse(response2.IsError);
Assert.AreEqual(orderTicket.Status, OrderStatus.CancelPending);
}
[Test]
public void CancelOrderRequestShouldFailForFilledOrder()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var broker = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, broker, new BacktestingResultHandler());
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 1000, 0, 1.11m, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.AreEqual(orderTicket.Status, OrderStatus.Submitted);
broker.Scan();
Assert.AreEqual(orderTicket.Status, OrderStatus.Filled);
var cancelRequest = new CancelOrderRequest(DateTime.Now, orderTicket.OrderId, "");
_transactionHandler.Process(cancelRequest);
Assert.AreEqual(cancelRequest.Status, OrderRequestStatus.Error);
Assert.IsTrue(cancelRequest.Response.IsError);
Assert.AreEqual(cancelRequest.Response.ErrorCode, OrderResponseErrorCode.InvalidOrderStatus);
Assert.AreEqual(orderTicket.Status, OrderStatus.Filled);
Assert.AreEqual(_algorithm.OrderEvents.Count, 2);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.Submitted), 1);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.Filled), 1);
}
[Test]
public void SyncFailedCancelOrderRequestShouldUpdateOrderStatusCorrectly()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var broker = new TestBroker(_algorithm, false);
_transactionHandler.Initialize(_algorithm, broker, new BacktestingResultHandler());
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 1000, 0, 1.11m, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.AreEqual(orderTicket.Status, OrderStatus.Submitted);
var cancelRequest = new CancelOrderRequest(DateTime.Now, orderTicket.OrderId, "");
Assert.AreEqual(_transactionHandler.CancelPendingOrdersSize, 0);
_transactionHandler.Process(cancelRequest);
Assert.AreEqual(_transactionHandler.CancelPendingOrdersSize, 1);
Assert.AreEqual(cancelRequest.Status, OrderRequestStatus.Processing);
Assert.IsTrue(cancelRequest.Response.IsSuccess);
Assert.AreEqual(orderTicket.Status, OrderStatus.CancelPending);
_transactionHandler.HandleOrderRequest(cancelRequest);
Assert.AreEqual(orderTicket.Status, OrderStatus.Submitted);
Assert.AreEqual(cancelRequest.Status, OrderRequestStatus.Error);
Assert.IsTrue(cancelRequest.Response.IsProcessed);
Assert.IsTrue(cancelRequest.Response.IsError);
Assert.IsTrue(cancelRequest.Response.ErrorMessage.Contains("Brokerage failed to cancel order"));
Assert.AreEqual(_transactionHandler.CancelPendingOrdersSize, 0);
Assert.AreEqual(_algorithm.OrderEvents.Count, 2);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.CancelPending), 1);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.Submitted), 1);
}
[Test]
public void AsyncFailedCancelOrderRequestShouldUpdateOrderStatusCorrectly()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var broker = new TestBroker(_algorithm, true);
_transactionHandler.Initialize(_algorithm, broker, new BacktestingResultHandler());
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 1000, 0, 1.11m, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.AreEqual(orderTicket.Status, OrderStatus.Submitted);
var cancelRequest = new CancelOrderRequest(DateTime.Now, orderTicket.OrderId, "");
Assert.AreEqual(_transactionHandler.CancelPendingOrdersSize, 0);
_transactionHandler.Process(cancelRequest);
Assert.AreEqual(_transactionHandler.CancelPendingOrdersSize, 1);
Assert.AreEqual(cancelRequest.Status, OrderRequestStatus.Processing);
Assert.IsTrue(cancelRequest.Response.IsSuccess);
Assert.AreEqual(orderTicket.Status, OrderStatus.CancelPending);
_transactionHandler.HandleOrderRequest(cancelRequest);
Assert.AreEqual(orderTicket.Status, OrderStatus.CancelPending);
Assert.AreEqual(cancelRequest.Status, OrderRequestStatus.Processed);
Assert.IsTrue(cancelRequest.Response.IsProcessed);
Assert.IsFalse(cancelRequest.Response.IsError);
Assert.AreEqual(_transactionHandler.CancelPendingOrdersSize, 1);
broker.Scan();
Assert.AreEqual(orderTicket.Status, OrderStatus.Filled);
Assert.AreEqual(_transactionHandler.CancelPendingOrdersSize, 0);
Assert.AreEqual(_algorithm.OrderEvents.Count, 3);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.CancelPending), 1);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.Submitted), 1);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.Filled), 1);
}
[Test]
public void AsyncFailedCancelOrderRequestShouldUpdateOrderStatusCorrectlyWithIntermediateUpdate()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var broker = new TestBroker(_algorithm, true);
_transactionHandler.Initialize(_algorithm, broker, new BacktestingResultHandler());
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 1000, 0, 1.11m, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.AreEqual(orderTicket.Status, OrderStatus.Submitted);
var cancelRequest = new CancelOrderRequest(DateTime.Now, orderTicket.OrderId, "");
Assert.AreEqual(_transactionHandler.CancelPendingOrdersSize, 0);
_transactionHandler.Process(cancelRequest);
Assert.AreEqual(_transactionHandler.CancelPendingOrdersSize, 1);
Assert.AreEqual(cancelRequest.Status, OrderRequestStatus.Processing);
Assert.IsTrue(cancelRequest.Response.IsSuccess);
Assert.AreEqual(orderTicket.Status, OrderStatus.CancelPending);
broker.Scan();
Assert.AreEqual(orderTicket.Status, OrderStatus.Filled);
_transactionHandler.HandleOrderRequest(cancelRequest);
Assert.AreEqual(_transactionHandler.CancelPendingOrdersSize, 0);
Assert.AreEqual(orderTicket.Status, OrderStatus.Filled);
Assert.AreEqual(cancelRequest.Status, OrderRequestStatus.Error);
Assert.IsTrue(cancelRequest.Response.IsProcessed);
Assert.IsTrue(cancelRequest.Response.IsError);
Assert.AreEqual(cancelRequest.Response.ErrorCode, OrderResponseErrorCode.InvalidOrderStatus);
Assert.AreEqual(_algorithm.OrderEvents.Count, 3);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.CancelPending), 1);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.Submitted), 1);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.Filled), 1);
}
[Test]
public void SyncFailedCancelOrderRequestShouldUpdateOrderStatusCorrectlyWithIntermediateUpdate()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var broker = new TestBroker(_algorithm, false);
_transactionHandler.Initialize(_algorithm, broker, new BacktestingResultHandler());
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 1000, 0, 1.11m, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.AreEqual(orderTicket.Status, OrderStatus.Submitted);
var cancelRequest = new CancelOrderRequest(DateTime.Now, orderTicket.OrderId, "");
Assert.AreEqual(_transactionHandler.CancelPendingOrdersSize, 0);
_transactionHandler.Process(cancelRequest);
Assert.AreEqual(_transactionHandler.CancelPendingOrdersSize, 1);
Assert.AreEqual(cancelRequest.Status, OrderRequestStatus.Processing);
Assert.IsTrue(cancelRequest.Response.IsSuccess);
Assert.AreEqual(orderTicket.Status, OrderStatus.CancelPending);
broker.Scan();
Assert.AreEqual(orderTicket.Status, OrderStatus.Filled);
_transactionHandler.HandleOrderRequest(cancelRequest);
Assert.AreEqual(_transactionHandler.CancelPendingOrdersSize, 0);
Assert.AreEqual(orderTicket.Status, OrderStatus.Filled);
Assert.AreEqual(cancelRequest.Status, OrderRequestStatus.Error);
Assert.IsTrue(cancelRequest.Response.IsProcessed);
Assert.IsTrue(cancelRequest.Response.IsError);
Assert.AreEqual(cancelRequest.Response.ErrorCode, OrderResponseErrorCode.InvalidOrderStatus);
Assert.AreEqual(_algorithm.OrderEvents.Count, 3);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.CancelPending), 1);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.Submitted), 1);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.Filled), 1);
}
[Test]
public void UpdateOrderRequestShouldFailForInvalidOrderId()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
var updateRequest = new UpdateOrderRequest(DateTime.Now, -10, new UpdateOrderFields());
_transactionHandler.Process(updateRequest);
Assert.AreEqual(updateRequest.Status, OrderRequestStatus.Error);
Assert.IsTrue(updateRequest.Response.IsError);
Assert.IsTrue(_algorithm.OrderEvents.IsNullOrEmpty());
}
[Test]
public void GetOpenOrdersWorksForSubmittedFilledStatus()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var broker = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, broker, new BacktestingResultHandler());
// Creates a limit order
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 1000, 0, 1.11m, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
Assert.AreEqual(_transactionHandler.GetOpenOrders().Count, 0);
// Submit and process a limit order
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.AreEqual(orderTicket.Status, OrderStatus.Submitted);
var openOrders = _transactionHandler.GetOpenOrders();
Assert.AreEqual(openOrders.Count, 1);
Assert.AreEqual(openOrders[0].Id, orderTicket.OrderId);
broker.Scan();
Assert.AreEqual(orderTicket.Status, OrderStatus.Filled);
Assert.AreEqual(_transactionHandler.GetOpenOrders().Count, 0);
}
[Test]
public void GetOpenOrdersWorksForCancelPendingCanceledStatus()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
// Creates a limit order
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Limit, security.Type, security.Symbol, 1000, 0, 1.11m, DateTime.Now, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
Assert.AreEqual(_transactionHandler.GetOpenOrders().Count, 0);
// Submit and process a limit order
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.IsTrue(orderTicket.Status == OrderStatus.Submitted);
var openOrders = _transactionHandler.GetOpenOrders();
Assert.AreEqual(openOrders.Count, 1);
Assert.AreEqual(openOrders[0].Id, orderTicket.OrderId);
// Cancel the order
var cancelRequest = new CancelOrderRequest(DateTime.Now, orderTicket.OrderId, "");
_transactionHandler.Process(cancelRequest);
Assert.IsTrue(orderTicket.Status == OrderStatus.CancelPending);
openOrders = _transactionHandler.GetOpenOrders();
Assert.AreEqual(openOrders.Count, 1);
Assert.AreEqual(openOrders[0].Id, orderTicket.OrderId);
_transactionHandler.HandleOrderRequest(cancelRequest);
Assert.IsTrue(orderTicket.Status == OrderStatus.Canceled);
Assert.AreEqual(_transactionHandler.GetOpenOrders().Count, 0);
}
[Test]
public void ProcessSynchronousEventsShouldPerformCashSyncOnce()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new TestBrokerage();
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
_algorithm.SetLiveMode(true);
var lastSyncDateBefore = _transactionHandler.GetLastSyncDate();
// Advance current time UTC so cash sync is performed
_transactionHandler.TestCurrentTimeUtc = _transactionHandler.TestCurrentTimeUtc.AddDays(2);
_transactionHandler.ProcessSynchronousEvents();
var lastSyncDateAfter = _transactionHandler.GetLastSyncDate();
Assert.AreNotEqual(lastSyncDateAfter, lastSyncDateBefore);
_transactionHandler.ProcessSynchronousEvents();
var lastSyncDateAfterAgain = _transactionHandler.GetLastSyncDate();
Assert.AreEqual(lastSyncDateAfter, lastSyncDateAfterAgain);
Assert.AreEqual(1, brokerage.GetCashBalanceCallCount);
}
[Test]
public void OrderFillShouldTriggerRePerformingCashSync()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new TestBrokerage();
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
_algorithm.SetLiveMode(true);
var lastSyncDateBefore = _transactionHandler.GetLastSyncDate();
// Advance current time UTC so cash sync is performed
_transactionHandler.TestCurrentTimeUtc = _transactionHandler.TestCurrentTimeUtc.AddDays(2);
// update last fill time
_transactionHandler.TestTimeSinceLastFill = TimeSpan.FromSeconds(15);
_transactionHandler.ProcessSynchronousEvents();
var lastSyncDateAfter = _transactionHandler.GetLastSyncDate();
// cash sync happened
Assert.AreNotEqual(lastSyncDateAfter, lastSyncDateBefore);
var count = 0;
while (!brokerage.ShouldPerformCashSync(_transactionHandler.TestCurrentTimeUtc))
{
count++;
if (count > 40)
{
Assert.Fail("Timeout waiting for ShouldPerformCashSync");
}
// delayed task should take ~10 seconds to set the perform cash sync flag up, due to TimeSinceLastFill
Thread.Sleep(1000);
}
_transactionHandler.ProcessSynchronousEvents();
Assert.AreEqual(2, brokerage.GetCashBalanceCallCount);
}
[Test]
public void ProcessSynchronousEventsShouldPerformCashSyncOnlyAtExpectedTime()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new TestBrokerage();
// This is 2 am New York
_transactionHandler.TestCurrentTimeUtc = new DateTime(1, 1, 1, 7, 0, 0);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
_algorithm.SetLiveMode(true);
var lastSyncDateBefore = _transactionHandler.GetLastSyncDate();
// Advance current time UTC
_transactionHandler.TestCurrentTimeUtc = _transactionHandler.TestCurrentTimeUtc.AddDays(2);
_transactionHandler.ProcessSynchronousEvents();
var lastSyncDateAfter = _transactionHandler.GetLastSyncDate();
Assert.AreEqual(lastSyncDateAfter, lastSyncDateBefore);
Assert.AreEqual(0, brokerage.GetCashBalanceCallCount);
}
[Test]
public void EmptyCashBalanceIsValid()
{
var mock = new Mock<TestBrokerage>
{
CallBase = true
};
var cashBalance = mock.Setup(m => m.GetCashBalance()).Returns(new List<CashAmount>());
mock.Setup(m => m.IsConnected).Returns(true);
mock.Setup(m => m.ShouldPerformCashSync(It.IsAny<DateTime>())).Returns(true);
var brokerage = mock.Object;
Assert.IsTrue(brokerage.IsConnected);
var algorithm = new QCAlgorithm();
var marketHoursDatabase = MarketHoursDatabase.FromDataFolder();
var symbolPropertiesDataBase = SymbolPropertiesDatabase.FromDataFolder();
var securityService = new SecurityService(algorithm.Portfolio.CashBook, marketHoursDatabase, symbolPropertiesDataBase, algorithm, RegisteredSecurityDataTypesProvider.Null, new SecurityCacheProvider(algorithm.Portfolio), algorithm: algorithm);
algorithm.Securities.SetSecurityService(securityService);
algorithm.SetLiveMode(true);
algorithm.SetFinishedWarmingUp();
_transactionHandler = new TestBrokerageTransactionHandler();
var resultHandler = new TestResultHandler();
_transactionHandler.Initialize(algorithm, brokerage, resultHandler);
// Advance current time UTC so cash sync is performed
_transactionHandler.TestCurrentTimeUtc = _transactionHandler.TestCurrentTimeUtc.AddDays(2);
_transactionHandler.ProcessSynchronousEvents();
resultHandler.Exit();
mock.VerifyAll();
}
[Test]
public void DoesNotLoopEndlesslyIfGetCashBalanceAlwaysThrows()
{
// simulate connect failure
var ib = new Mock<IBrokerage>();
ib.Setup(m => m.GetCashBalance()).Callback(() => { throw new Exception("Connection error in CashBalance"); });
ib.Setup(m => m.IsConnected).Returns(false);
ib.Setup(m => m.ShouldPerformCashSync(It.IsAny<DateTime>())).Returns(true);
ib.Setup(m => m.PerformCashSync(It.IsAny<IAlgorithm>(), It.IsAny<DateTime>(), It.IsAny<Func<TimeSpan>>()))
.Returns(
() =>
{
try
{
ib.Object.GetCashBalance();
}
catch (Exception)
{
return false;
}
return true;
});
var brokerage = ib.Object;
Assert.IsFalse(brokerage.IsConnected);
var algorithm = new QCAlgorithm();
var marketHoursDatabase = MarketHoursDatabase.FromDataFolder();
var symbolPropertiesDataBase = SymbolPropertiesDatabase.FromDataFolder();
var securityService = new SecurityService(algorithm.Portfolio.CashBook, marketHoursDatabase, symbolPropertiesDataBase, algorithm, RegisteredSecurityDataTypesProvider.Null, new SecurityCacheProvider(algorithm.Portfolio), algorithm: algorithm);
algorithm.Securities.SetSecurityService(securityService);
algorithm.SetLiveMode(true);
algorithm.SetFinishedWarmingUp();
_transactionHandler = new TestBrokerageTransactionHandler();
var resultHandler = new TestResultHandler();
_transactionHandler.Initialize(algorithm, brokerage, resultHandler);
// Advance current time UTC so cash sync is performed
_transactionHandler.TestCurrentTimeUtc = _transactionHandler.TestCurrentTimeUtc.AddDays(2);
try
{
while (true)
{
_transactionHandler.ProcessSynchronousEvents();
Assert.IsFalse(brokerage.IsConnected);
Thread.Sleep(1000);
}
}
catch (Exception exception)
{
// expect exception from ProcessSynchronousEvents when max attempts reached
Assert.That(exception.Message.Contains("maximum number of attempts"));
}
resultHandler.Exit();
}
[Test]
public void AddOrderWaitsForOrderToBeProcessed()
{
var algorithm = new QCAlgorithm();
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
var security = algorithm.AddSecurity(SecurityType.Equity, "SPY");
security.SetMarketPrice(new Tick { Value = 150 });
algorithm.SetFinishedWarmingUp();
var transactionHandler = new BrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(_algorithm);
transactionHandler.Initialize(algorithm, brokerage, new BacktestingResultHandler());
// lets wait until the transactionHandler starts running
Thread.Sleep(250);
try
{
algorithm.Transactions.SetOrderProcessor(transactionHandler);
var ticket = algorithm.LimitOrder(security.Symbol, 1, 100);
var openOrders = algorithm.Transactions.GetOpenOrders();
transactionHandler.Exit();
Assert.AreEqual(1, openOrders.Count);
Assert.IsTrue(ticket.HasOrder);
}
finally
{
transactionHandler.Exit();
}
}
[Test, Parallelizable(ParallelScope.None)]
public void IncrementalOrderId()
{
var parameter = new RegressionTests.AlgorithmStatisticsTestParameters(nameof(TestIncrementalOrderIdAlgorithm),
new Dictionary<string, string>(),
Language.CSharp,
AlgorithmStatus.Completed);
AlgorithmRunner.RunLocalBacktest(parameter.Algorithm,
parameter.Statistics,
parameter.Language,
parameter.ExpectedFinalStatus,
setupHandler: "TestIncrementalOrderIdSetupHandler");
Assert.AreEqual(12, TestIncrementalOrderIdAlgorithm.OrderEventIds.Count);
}
[Test]
public void InvalidOrderEventDueToNonShortableAsset()
{
// Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var broker = new TestBroker(_algorithm, false);
_algorithm.SetBrokerageModel(new TestShortableBrokerageModel());
_transactionHandler.Initialize(_algorithm, broker, new BacktestingResultHandler());
var security = _algorithm.Securities[_symbol];
var price = 1.12m;
security.SetMarketPrice(new Tick(DateTime.Now, security.Symbol, price, price, price));
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, -1000, 0, 1.11m, DateTime.UtcNow, "");
// Mock the the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.AreEqual(orderTicket.Status, OrderStatus.Invalid);
Assert.AreEqual(_algorithm.OrderEvents.Count, 1);
Assert.AreEqual(_algorithm.OrderEvents.Count(orderEvent => orderEvent.Status == OrderStatus.Invalid), 1);
}
// Short Call --> OTM (expired worthless)
[TestCase(-1, OptionRight.Call, 455, 100, 450, 1, 0, 100, "OTM")]
// Short Put --> OTM (expired worthless)
[TestCase(-1, OptionRight.Put, 455, 100, 460, 1, 0, 100, "OTM")]
// Long Call --> OTM (expired worthless)
[TestCase(1, OptionRight.Call, 455, 100, 450, 1, 0, 100, "OTM")]
// Long Put --> OTM (expired worthless)
[TestCase(1, OptionRight.Put, 455, 100, 460, 1, 0, 100, "OTM")]
// Short Call --> ITM (assigned)
[TestCase(-1, OptionRight.Call, 450, 100, 455, 2, 0, 0, null)]
// Short Put --> ITM (assigned)
[TestCase(-1, OptionRight.Put, 455, 100, 450, 2, 0, 200, null)]
// Long Call --> ITM (auto-exercised)
[TestCase(1, OptionRight.Call, 450, 100, 455, 2, 0, 200, "Automatic Exercise")]
// Long Put --> ITM (auto-exercised)
[TestCase(1, OptionRight.Put, 455, 100, 450, 2, 0, 0, "Automatic Exercise")]
public void OptionExpirationEmitsOrderEvents(
int initialOptionPosition,
OptionRight optionRight,
decimal strikePrice,
int initialUnderlyingPosition,
decimal underlyingPrice,
int expectedOrderEvents,
int expectedOptionPosition,
int expectedUnderlyingPosition,
string expectedMessage
)
{
var algorithm = new TestAlgorithm();
var equity = algorithm.AddEquity("SPY");
var optionSymbol = Symbol.CreateOption(equity.Symbol, equity.Symbol.ID.Market, OptionStyle.American, optionRight, strikePrice,
new DateTime(2021, 9, 8));
var option = algorithm.AddOptionContract(optionSymbol);
algorithm.Portfolio[equity.Symbol].SetHoldings(underlyingPrice, initialUnderlyingPosition);
algorithm.Portfolio[option.Symbol].SetHoldings(0.01m, initialOptionPosition);
equity.SetMarketPrice(new Tick { Value = underlyingPrice });
using var brokerage = new NoSubmitTestBrokerage(algorithm);
_transactionHandler = new TestBrokerageTransactionHandler();
_transactionHandler.Initialize(algorithm, brokerage, new BacktestingResultHandler());
algorithm.Transactions.SetOrderProcessor(_transactionHandler);
// 9 PM ET
_transactionHandler.TestCurrentTimeUtc = new DateTime(2021, 9, 9, 1, 0, 0);
var parameters = new object[] { new OptionNotificationEventArgs(optionSymbol, 0) };
_handleOptionNotification.Invoke(_transactionHandler, parameters);
var tickets = algorithm.Transactions.GetOrderTickets().ToList();
Assert.AreEqual(1, tickets.Count);
var ticket = tickets.First();
Assert.IsTrue(ticket.HasOrder);
Assert.AreEqual(expectedOrderEvents, ticket.OrderEvents.Count);
if (expectedMessage != null)
{
Assert.AreEqual(1, ticket.OrderEvents.Count(x => x.Message.Contains(expectedMessage, StringComparison.InvariantCulture)));
}
Assert.AreEqual(expectedUnderlyingPosition, algorithm.Portfolio[equity.Symbol].Quantity);
Assert.AreEqual(expectedOptionPosition, algorithm.Portfolio[optionSymbol].Quantity);
// let's push the same event again
_handleOptionNotification.Invoke(_transactionHandler, parameters);
_transactionHandler.Exit();
// we should not see any new orders or events come through
tickets = algorithm.Transactions.GetOrderTickets().ToList();
Assert.AreEqual(1, tickets.Count);
}
// Long Call --> ITM (exercised early - full)
[TestCase(1, OptionRight.Call, 450, 100, 455, 2, 0, 200, "Automatic Exercise")]
// Long Put --> ITM (exercised early - full)
[TestCase(1, OptionRight.Put, 455, 100, 450, 2, 0, 0, "Automatic Exercise")]
// Long Call --> ITM (exercised early - partial)
[TestCase(3, OptionRight.Call, 450, 100, 455, 2, 1, 300, "Automatic Exercise")]
// Long Put --> ITM (exercised early - partial)
[TestCase(3, OptionRight.Put, 455, 300, 450, 2, 1, 100, "Automatic Exercise")]
public void EarlyExerciseEmitsOrderEvents(
int initialOptionPosition,
OptionRight optionRight,
decimal strikePrice,
int initialUnderlyingPosition,
decimal underlyingPrice,
int expectedOrderEvents,
int expectedOptionPosition,
int expectedUnderlyingPosition,
string expectedMessage
)
{
var algorithm = new TestAlgorithm();
var equity = algorithm.AddEquity("SPY");
var optionSymbol = Symbol.CreateOption(equity.Symbol, equity.Symbol.ID.Market, OptionStyle.American, optionRight, strikePrice,
new DateTime(2021, 9, 8));
var option = algorithm.AddOptionContract(optionSymbol);
algorithm.Portfolio[equity.Symbol].SetHoldings(underlyingPrice, initialUnderlyingPosition);
algorithm.Portfolio[option.Symbol].SetHoldings(0.01m, initialOptionPosition);
equity.SetMarketPrice(new Tick { Value = underlyingPrice });
using var brokerage = new NoSubmitTestBrokerage(algorithm);
_transactionHandler = new TestBrokerageTransactionHandler();
_transactionHandler.Initialize(algorithm, brokerage, new BacktestingResultHandler());
algorithm.Transactions.SetOrderProcessor(_transactionHandler);
// 10 AM ET
_transactionHandler.TestCurrentTimeUtc = new DateTime(2021, 9, 8, 14, 0, 0);
// Creates an exercise order
var exerciseQuantity = initialOptionPosition - expectedOptionPosition;
var orderRequest = new SubmitOrderRequest(OrderType.OptionExercise, option.Type, option.Symbol, -exerciseQuantity, 0, 0, _transactionHandler.TestCurrentTimeUtc, "");
// Submit and process the exercise order
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.AreEqual(orderTicket.Status, OrderStatus.New);
var parameters = new object[] { new OptionNotificationEventArgs(optionSymbol, expectedOptionPosition) };
_handleOptionNotification.Invoke(_transactionHandler, parameters);
var tickets = algorithm.Transactions.GetOrderTickets().ToList();
Assert.AreEqual(1, tickets.Count);
var ticket = tickets.First();
Assert.IsTrue(ticket.HasOrder);
Assert.AreEqual(expectedOrderEvents, ticket.OrderEvents.Count);
Assert.AreEqual(1, ticket.OrderEvents.Count(x => x.Message.Contains(expectedMessage, StringComparison.InvariantCulture)));
Assert.AreEqual(expectedUnderlyingPosition, algorithm.Portfolio[equity.Symbol].Quantity);
Assert.AreEqual(expectedOptionPosition, algorithm.Portfolio[optionSymbol].Quantity);
// let's push the same event again
_handleOptionNotification.Invoke(_transactionHandler, parameters);
_transactionHandler.Exit();
// we should not see any new orders or events come through
tickets = algorithm.Transactions.GetOrderTickets().ToList();
Assert.AreEqual(1, tickets.Count);
}
// Long Call --> ITM (exercised early - full)
[TestCase(1, OptionRight.Call, 450, 100, 455, 1, 0, "Automatic Exercise")]
// Long Put --> ITM (exercised early - full)
[TestCase(1, OptionRight.Put, 455, 100, 450, 1, 0, "Automatic Exercise")]
// Long Call --> ITM (exercised early - partial)
[TestCase(3, OptionRight.Call, 450, 100, 455, 1, 1, "Automatic Exercise")]
// Long Put --> ITM (exercised early - partial)
[TestCase(3, OptionRight.Put, 455, 300, 450, 1, 1, "Automatic Exercise")]
public void EarlyExerciseDoesNotEmitsOrderEvents(
int initialOptionPosition,
OptionRight optionRight,
decimal strikePrice,
int initialUnderlyingPosition,
decimal underlyingPrice,
int expectedOrderEvents,
int expectedOptionPosition,
string expectedMessage
)
{
var algorithm = new TestAlgorithm();
var equity = algorithm.AddEquity("SPY");
var optionSymbol = Symbol.CreateOption(equity.Symbol, equity.Symbol.ID.Market, OptionStyle.American, optionRight, strikePrice,
new DateTime(2021, 9, 8));
var option = algorithm.AddOptionContract(optionSymbol);
algorithm.Portfolio[equity.Symbol].SetHoldings(underlyingPrice, initialUnderlyingPosition);
algorithm.Portfolio[option.Symbol].SetHoldings(0.01m, initialOptionPosition);
equity.SetMarketPrice(new Tick { Value = underlyingPrice });
using var brokerage = new NoSubmitTestBrokerage(algorithm);
_transactionHandler = new TestBrokerageTransactionHandler();
_transactionHandler.Initialize(algorithm, brokerage, new BacktestingResultHandler());
algorithm.Transactions.SetOrderProcessor(_transactionHandler);
// 10 AM ET
_transactionHandler.TestCurrentTimeUtc = new DateTime(2021, 9, 8, 14, 0, 0);
// Creates an exercise order
var exerciseQuantity = initialOptionPosition - expectedOptionPosition;
var orderRequest = new SubmitOrderRequest(OrderType.OptionExercise, option.Type, option.Symbol, -exerciseQuantity, 0, 0, _transactionHandler.TestCurrentTimeUtc, "");
// Submit and process the exercise order
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.AreEqual(orderTicket.Status, OrderStatus.New);
// Fill the exercise order
brokerage.PublishOrderEvent(new OrderEvent(orderTicket.OrderId, option.Symbol, _transactionHandler.TestCurrentTimeUtc,
OrderStatus.Filled, OrderDirection.Sell, 0, orderRequest.Quantity, OrderFee.Zero));
Assert.IsTrue(orderTicket.Status.IsClosed());
var tickets = algorithm.Transactions.GetOrderTickets().ToList();
Assert.AreEqual(1, tickets.Count);
_handleOptionNotification.Invoke(_transactionHandler, new object[] { new OptionNotificationEventArgs(optionSymbol, expectedOptionPosition) });
// assert nothing happens!
tickets = algorithm.Transactions.GetOrderTickets().ToList();
Assert.AreEqual(1, tickets.Count);
}
// Short Call --> ITM (assigned early - full)
[TestCase(-1, OptionRight.Call, 450, 100, 455, 2, 0, 0)]
// Short Put --> ITM (assigned early - full)
[TestCase(-1, OptionRight.Put, 455, 100, 450, 2, 0, 200)]
// Short Call --> ITM (assigned early - partial)
[TestCase(-3, OptionRight.Call, 450, 300, 455, 2, -1, 100)]
// Short Put --> ITM (assigned early - partial)
[TestCase(-3, OptionRight.Put, 455, 100, 450, 2, -1, 300)]
public void EarlyAssignmentEmitsOrderEvents(
int initialOptionPosition,
OptionRight optionRight,
decimal strikePrice,
int initialUnderlyingPosition,
decimal underlyingPrice,
int expectedOrderEvents,
int expectedOptionPosition,
int expectedUnderlyingPosition
)
{
var algorithm = new TestAlgorithm();
var equity = algorithm.AddEquity("SPY");
var optionSymbol = Symbol.CreateOption(equity.Symbol, equity.Symbol.ID.Market, OptionStyle.American, optionRight, strikePrice,
new DateTime(2021, 9, 8));
var option = algorithm.AddOptionContract(optionSymbol);
algorithm.Portfolio[equity.Symbol].SetHoldings(underlyingPrice, initialUnderlyingPosition);
algorithm.Portfolio[option.Symbol].SetHoldings(0.01m, initialOptionPosition);
equity.SetMarketPrice(new Tick { Value = underlyingPrice });
using var brokerage = new NoSubmitTestBrokerage(algorithm);
_transactionHandler = new TestBrokerageTransactionHandler();
_transactionHandler.Initialize(algorithm, brokerage, new BacktestingResultHandler());
algorithm.Transactions.SetOrderProcessor(_transactionHandler);
// 10 AM ET
_transactionHandler.TestCurrentTimeUtc = new DateTime(2021, 9, 8, 14, 0, 0);
var parameters = new object[] { new OptionNotificationEventArgs(optionSymbol, expectedOptionPosition) };
_handleOptionNotification.Invoke(_transactionHandler, parameters);
var tickets = algorithm.Transactions.GetOrderTickets().ToList();
Assert.AreEqual(1, tickets.Count);
var ticket = tickets.First();
Assert.IsTrue(ticket.HasOrder);
Assert.AreEqual(expectedOrderEvents, ticket.OrderEvents.Count);
Assert.AreEqual(expectedUnderlyingPosition, algorithm.Portfolio[equity.Symbol].Quantity);
Assert.AreEqual(expectedOptionPosition, algorithm.Portfolio[optionSymbol].Quantity);
// let's push the same event again
_handleOptionNotification.Invoke(_transactionHandler, parameters);
_transactionHandler.Exit();
// we should not see any new orders or events come through
tickets = algorithm.Transactions.GetOrderTickets().ToList();
Assert.AreEqual(1, tickets.Count);
}
// Short Call --> ITM (assigned early - full)
[TestCase(-2, OptionRight.Call, 450, 100, 455, 2, 0, 0)]
// Short Put --> ITM (assigned early - full)
[TestCase(-2, OptionRight.Put, 455, 100, 450, 2, 0, 200)]
// Short Call --> ITM (assigned early - partial)
[TestCase(-3, OptionRight.Call, 450, 300, 455, 2, -1, 200)]
// Short Put --> ITM (assigned early - partial)
[TestCase(-3, OptionRight.Put, 455, 100, 450, 2, -1, 200)]
public void EarlyAssignmentEmitsOrderEventsEvenIfOldBuyOrderPresent(
int initialOptionPosition,
OptionRight optionRight,
decimal strikePrice,
int initialUnderlyingPosition,
decimal underlyingPrice,
int expectedOrderEvents,
int expectedOptionPosition,
int expectedUnderlyingPosition
)
{
var algorithm = new TestAlgorithm();
var equity = algorithm.AddEquity("SPY");
var optionSymbol = Symbol.CreateOption(equity.Symbol, equity.Symbol.ID.Market, OptionStyle.American, optionRight, strikePrice,
new DateTime(2021, 9, 8));
var option = algorithm.AddOptionContract(optionSymbol);
algorithm.Portfolio[equity.Symbol].SetHoldings(underlyingPrice, initialUnderlyingPosition);
algorithm.Portfolio[option.Symbol].SetHoldings(0.01m, initialOptionPosition);
equity.SetMarketPrice(new Tick { Value = underlyingPrice });
using var brokerage = new NoSubmitTestBrokerage(algorithm);
_transactionHandler = new TestBrokerageTransactionHandler();
_transactionHandler.Initialize(algorithm, brokerage, new BacktestingResultHandler());
algorithm.Transactions.SetOrderProcessor(_transactionHandler);
// 10 AM ET
_transactionHandler.TestCurrentTimeUtc = new DateTime(2021, 9, 8, 14, 0, 0);
// Creates a market order
var orderTime = _transactionHandler.TestCurrentTimeUtc.AddMinutes(-10);
var orderRequest = new SubmitOrderRequest(OrderType.Market, option.Type, option.Symbol, 1, 0, 0, orderTime, "");
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.AreEqual(1, algorithm.Transactions.GetOrderTickets().Count());
// Fill the order, 1 second later, but ~10 minutes away form current time
brokerage.PublishOrderEvent(new OrderEvent(orderTicket.OrderId, option.Symbol, orderTime.AddSeconds(1),
OrderStatus.Filled, OrderDirection.Buy, 10, orderRequest.Quantity, OrderFee.Zero));
Assert.IsTrue(orderTicket.Status.IsClosed());
Assert.AreEqual(1, algorithm.Transactions.GetOrderTickets().Count());
var parameters = new object[] { new OptionNotificationEventArgs(optionSymbol, expectedOptionPosition) };
_handleOptionNotification.Invoke(_transactionHandler, parameters);
var tickets = algorithm.Transactions.GetOrderTickets().ToList();
Assert.AreEqual(2, tickets.Count);
var ticket = tickets[0];
Assert.IsTrue(ticket.HasOrder);
Assert.AreEqual(expectedOrderEvents, ticket.OrderEvents.Count);
Assert.AreEqual(expectedUnderlyingPosition, algorithm.Portfolio[equity.Symbol].Quantity);
Assert.AreEqual(expectedOptionPosition, algorithm.Portfolio[optionSymbol].Quantity);
}
// Short Call --> ITM (assigned early - full)
[TestCase(-2, OptionRight.Call, 450, 100, 455, 0, OrderDirection.Buy)]
// Short Put --> ITM (assigned early - full)
[TestCase(-2, OptionRight.Put, 455, 100, 450, 0, OrderDirection.Buy)]
// Short Call --> ITM (assigned early - partial)
[TestCase(-3, OptionRight.Call, 450, 300, 455, -1, OrderDirection.Buy)]
// Short Put --> ITM (assigned early - partial)
[TestCase(-3, OptionRight.Put, 455, 100, 450, -1, OrderDirection.Buy)]
// Short Call --> ITM (assigned early - full)
[TestCase(-2, OptionRight.Call, 450, 100, 455, 0, OrderDirection.Sell)]
// Short Put --> ITM (assigned early - full)
[TestCase(-2, OptionRight.Put, 455, 100, 450, 0, OrderDirection.Sell)]
// Short Call --> ITM (assigned early - partial)
[TestCase(-3, OptionRight.Call, 450, 300, 455, -1, OrderDirection.Sell)]
// Short Put --> ITM (assigned early - partial)
[TestCase(-3, OptionRight.Put, 455, 100, 450, -1, OrderDirection.Sell)]
public void EarlyAssignmentDoesNotEmitsOrderEventsInLive(
int initialOptionPosition,
OptionRight optionRight,
decimal strikePrice,
int initialUnderlyingPosition,
decimal underlyingPrice,
int expectedOptionPosition,
OrderDirection orderDirection
)
{
var algorithm = new TestAlgorithm();
var equity = algorithm.AddEquity("SPY");
var optionSymbol = Symbol.CreateOption(equity.Symbol, equity.Symbol.ID.Market, OptionStyle.American, optionRight, strikePrice,
new DateTime(2021, 9, 8));
var option = algorithm.AddOptionContract(optionSymbol);
algorithm.Portfolio[equity.Symbol].SetHoldings(underlyingPrice, initialUnderlyingPosition);
algorithm.Portfolio[option.Symbol].SetHoldings(0.01m, initialOptionPosition);
equity.SetMarketPrice(new Tick { Value = underlyingPrice });
using var brokerage = new NoSubmitTestBrokerage(algorithm);
_transactionHandler = new TestBrokerageTransactionHandler();
_transactionHandler.Initialize(algorithm, brokerage, new BacktestingResultHandler());
algorithm.Transactions.SetOrderProcessor(_transactionHandler);
// 10 AM ET
_transactionHandler.TestCurrentTimeUtc = new DateTime(2021, 9, 8, 14, 0, 0);
// Creates a market order
var orderRequest = new SubmitOrderRequest(OrderType.Market, option.Type, option.Symbol, orderDirection == OrderDirection.Buy ? 1 : -1, 0, 0, _transactionHandler.TestCurrentTimeUtc, "");
var orderTicket = _transactionHandler.Process(orderRequest);
_transactionHandler.HandleOrderRequest(orderRequest);
Assert.AreEqual(1, algorithm.Transactions.GetOrderTickets().Count());
_handleOptionNotification.Invoke(_transactionHandler, new object[] { new OptionNotificationEventArgs(optionSymbol, expectedOptionPosition) });
// we expect no difference because there is an open market order!
Assert.AreEqual(1, algorithm.Transactions.GetOrderTickets().Count());
// Fill the order
brokerage.PublishOrderEvent(new OrderEvent(orderTicket.OrderId, option.Symbol, _transactionHandler.TestCurrentTimeUtc,
OrderStatus.Filled, orderDirection, 10, orderRequest.Quantity, OrderFee.Zero));
Assert.IsTrue(orderTicket.Status.IsClosed());
_handleOptionNotification.Invoke(_transactionHandler, new object[] { new OptionNotificationEventArgs(optionSymbol, expectedOptionPosition) });
// we expect no difference because there is a closed market order!
Assert.AreEqual(1, algorithm.Transactions.GetOrderTickets().Count());
// Timeout the order effect
_transactionHandler.TestCurrentTimeUtc = _transactionHandler.TestCurrentTimeUtc.AddMinutes(1);
_handleOptionNotification.Invoke(_transactionHandler, new object[] { new OptionNotificationEventArgs(optionSymbol, expectedOptionPosition) });
// we expect difference because market order is old!
Assert.AreEqual(2, algorithm.Transactions.GetOrderTickets().Count());
}
private static TestCaseData[] PriceAdjustmentModeTestCases => Enum.GetValues(typeof(DataNormalizationMode))
.Cast<DataNormalizationMode>()
.SelectMany(x => new[] { new TestCaseData(x, false), new TestCaseData(x, true) })
.ToArray();
[TestCaseSource(nameof(PriceAdjustmentModeTestCases))]
public void OrderPriceAdjustmentModeIsSetAfterPlacingOrder(DataNormalizationMode dataNormalizationMode, bool liveMode)
{
_algorithm.SetLiveMode(liveMode);
//Initializes the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new BacktestingBrokerage(_algorithm);
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
// Add the security
var security = _algorithm.AddSecurity(SecurityType.Forex, "CADUSD", dataNormalizationMode: dataNormalizationMode);
var securityNormalizationMode = _algorithm.SubscriptionManager.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(security.Symbol)[0]
.DataNormalizationMode;
Assert.AreEqual(dataNormalizationMode, securityNormalizationMode);
// Creates the order
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 1600, 0, 0, DateTime.Now, "");
// Mock the order processor
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOrderTicket(It.IsAny<int>())).Returns(new OrderTicket(_algorithm.Transactions, orderRequest));
_algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
// Act
var orderTicket = _transactionHandler.Process(orderRequest);
Assert.AreEqual(OrderStatus.New, orderTicket.Status);
_transactionHandler.HandleOrderRequest(orderRequest);
// Assert
Assert.IsTrue(orderRequest.Response.IsProcessed);
Assert.IsTrue(orderRequest.Response.IsSuccess);
Assert.IsTrue(orderTicket.Status == OrderStatus.Submitted);
var expectedNormalizationMode = liveMode ? DataNormalizationMode.Raw : dataNormalizationMode;
Assert.AreEqual(expectedNormalizationMode, _transactionHandler.GetOrderById(orderTicket.OrderId).PriceAdjustmentMode);
}
[TestCaseSource(nameof(PriceAdjustmentModeTestCases))]
public void OrderPriceAdjustmentModeIsSetWhenAddingOpenOrder(DataNormalizationMode dataNormalizationMode, bool liveMode)
{
_algorithm.SetLiveMode(liveMode);
// The engine might fetch brokerage open orders before even initializing the transaction handler,
// so let's not initialize it here to simulate that scenario
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new TestingBrokerage();
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
// Add the security
var security = _algorithm.AddSecurity(SecurityType.Forex, "CADUSD", dataNormalizationMode: dataNormalizationMode);
var securityNormalizationMode = _algorithm.SubscriptionManager.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(security.Symbol)[0]
.DataNormalizationMode;
Assert.AreEqual(dataNormalizationMode, securityNormalizationMode);
// Creates the order
var orderRequest = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 1600, 0, 0, DateTime.Now, "");
var order = Order.CreateOrder(orderRequest);
// Act
_transactionHandler.AddOpenOrder(order, _algorithm);
// Assert
Assert.Greater(order.Id, 0);
var expectedNormalizationMode = liveMode ? DataNormalizationMode.Raw : dataNormalizationMode;
Assert.AreEqual(expectedNormalizationMode, _transactionHandler.GetOrderById(order.Id).PriceAdjustmentMode);
}
private static TestCaseData[] BrokerageSideOrdersTestCases => new[]
{
new TestCaseData(OrderType.Limit, false),
new TestCaseData(OrderType.StopMarket, false),
new TestCaseData(OrderType.StopLimit, false),
new TestCaseData(OrderType.MarketOnOpen, false),
new TestCaseData(OrderType.MarketOnClose, false),
new TestCaseData(OrderType.LimitIfTouched, false),
new TestCaseData(OrderType.ComboMarket, false),
new TestCaseData(OrderType.ComboLimit, false),
new TestCaseData(OrderType.ComboLegLimit, false),
new TestCaseData(OrderType.TrailingStop, false),
// Only market orders are supported for this test
new TestCaseData(OrderType.Market, true),
};
private static Order GetOrder(OrderType type, Symbol symbol)
{
switch (type)
{
case OrderType.Market:
return new MarketOrder(symbol, 100, new DateTime(2024, 01, 19, 12, 0, 0));
case OrderType.Limit:
return new LimitOrder(symbol, 100, 100m, new DateTime(2024, 01, 19, 12, 0, 0));
case OrderType.StopMarket:
return new StopMarketOrder(symbol, 100, 100m, new DateTime(2024, 01, 19, 12, 0, 0));
case OrderType.StopLimit:
return new StopLimitOrder(symbol, 100, 100m, 100m, new DateTime(2024, 01, 19, 12, 0, 0));
case OrderType.MarketOnOpen:
return new MarketOnOpenOrder(symbol, 100, new DateTime(2024, 01, 19, 12, 0, 0));
case OrderType.MarketOnClose:
return new MarketOnCloseOrder(symbol, 100, new DateTime(2024, 01, 19, 12, 0, 0));
case OrderType.LimitIfTouched:
return new LimitIfTouchedOrder(symbol, 100, 100m, 100m, new DateTime(2024, 01, 19, 12, 0, 0));
case OrderType.ComboMarket:
return new ComboMarketOrder(symbol, 100, new DateTime(2024, 01, 19, 12, 0, 0), new GroupOrderManager(1, 1, 10));
case OrderType.ComboLimit:
return new ComboLimitOrder(symbol, 100, 100m, new DateTime(2024, 01, 19, 12, 0, 0), new GroupOrderManager(1, 1, 10, 100));
case OrderType.ComboLegLimit:
return new ComboLegLimitOrder(symbol, 100, 100m, new DateTime(2024, 01, 19, 12, 0, 0), new GroupOrderManager(1, 1, 10));
case OrderType.TrailingStop:
return new TrailingStopOrder(symbol, 100, 100m, 100m, false, new DateTime(2024, 01, 19, 12, 0, 0));
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
[TestCaseSource(nameof(BrokerageSideOrdersTestCases))]
public void NewBrokerageOrdersAreFiltered(OrderType orderType, bool accepted)
{
//Initialize the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new TestingBrokerage();
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
_algorithm.SetBrokerageModel(new DefaultBrokerageModel());
var brokerageMessageHandler = new TestBrokerageMessageHandler();
_algorithm.SetBrokerageMessageHandler(brokerageMessageHandler);
var symbol = _algorithm.AddEquity("SPY").Symbol;
var order = GetOrder(orderType, symbol);
Assert.AreEqual(orderType, order.Type);
brokerage.OnNewBrokerageOrder(new NewBrokerageOrderNotificationEventArgs(order));
Assert.AreEqual(accepted, brokerageMessageHandler.LastHandleOrderResult);
Assert.AreEqual(accepted ? 1 : 0, _transactionHandler.OrdersCount);
}
[Test]
public void UnrequestedSecuritiesAreAddedForNewBrokerageSideOrders()
{
//Initialize the transaction handler
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new TestingBrokerage();
_transactionHandler.Initialize(_algorithm, brokerage, new BacktestingResultHandler());
_algorithm.SetBrokerageModel(new DefaultBrokerageModel());
var brokerageMessageHandler = new TestBrokerageMessageHandler();
_algorithm.SetBrokerageMessageHandler(brokerageMessageHandler);
var symbol = Symbols.SPY;
Assert.IsFalse(_algorithm.Securities.ContainsKey(symbol));
var order = GetOrder(OrderType.Market, symbol);
brokerage.OnNewBrokerageOrder(new NewBrokerageOrderNotificationEventArgs(order));
Assert.IsTrue(brokerageMessageHandler.LastHandleOrderResult);
Assert.AreEqual(1, _transactionHandler.OrdersCount);
Assert.IsTrue(_algorithm.Securities.TryGetValue(symbol, out var security));
Assert.AreEqual(symbol, security.Symbol);
}
[Test]
public void ProcessesOrdersConcurrently()
{
var algorithm = new TestAlgorithm();
using var brokerage = new TestingConcurrentBrokerage();
const int expectedOrdersCount = 10;
using var finishedEvent = new ManualResetEventSlim(false);
var transactionHandler = new TestableConcurrentBrokerageTransactionHandler(expectedOrdersCount, finishedEvent);
transactionHandler.Initialize(algorithm, brokerage, new BacktestingResultHandler());
try
{
algorithm.Transactions.SetOrderProcessor(transactionHandler);
var security = (Security)algorithm.AddEquity("SPY");
algorithm.SetFinishedWarmingUp();
// Set up security
var reference = new DateTime(2025, 07, 03, 10, 0, 0);
security.SetMarketPrice(new Tick(reference, security.Symbol, 300, 300));
// Creates the order
var orderRequests = Enumerable.Range(0, expectedOrdersCount).Select(_ => MakeOrderRequest(security, OrderType.Market, reference)).ToList();
// Act
for (var i = 0; i < orderRequests.Count; i++)
{
var orderRequest = orderRequests[i];
orderRequest.SetOrderId(i + 1);
transactionHandler.Process(orderRequest);
}
// Wait for all orders to be processed
Assert.IsTrue(finishedEvent.Wait(10000));
Assert.Greater(transactionHandler.ProcessingThreadNames.Count, 1);
CollectionAssert.AreEquivalent(orderRequests.Select(x => x.ToString()), transactionHandler.ProcessedRequests.Select(x => x.ToString()));
}
finally
{
transactionHandler.Exit();
}
}
[Test]
public void ProcessesComboRequestsWhenConcurrencyIsEnabled()
{
var algorithm = new TestAlgorithm();
using var brokerage = new TestingConcurrentBrokerage();
const int expectedOrdersCount = 2;
using var finishedEvent = new ManualResetEventSlim(false);
var transactionHandler = new TestableConcurrentBrokerageTransactionHandler(expectedOrdersCount, finishedEvent);
transactionHandler.Initialize(algorithm, brokerage, new BacktestingResultHandler());
try
{
algorithm.Transactions.SetOrderProcessor(transactionHandler);
algorithm.SetCash(100000);
algorithm.SetFinishedWarmingUp();
var security1 = (Security)algorithm.AddEquity("SPY");
var security2 = (Security)algorithm.AddEquity("AAPL");
var reference = new DateTime(2025, 07, 03, 10, 0, 0);
security1.SetMarketPrice(new Tick(reference, security1.Symbol, 500, 500));
security2.SetMarketPrice(new Tick(reference, security2.Symbol, 200, 200));
var groupOrderManager = new GroupOrderManager(1, 2, -1, 1m);
var orderRequest1 = new SubmitOrderRequest(OrderType.ComboLimit, security1.Type, security1.Symbol, -1, 1m, 0, reference, "",
groupOrderManager: groupOrderManager);
var orderRequest2 = new SubmitOrderRequest(OrderType.ComboLimit, security2.Type, security2.Symbol, 1, 1m, 0, reference, "",
groupOrderManager: groupOrderManager);
orderRequest1.SetOrderId(1);
orderRequest2.SetOrderId(2);
transactionHandler.Process(orderRequest1);
transactionHandler.Process(orderRequest2);
Assert.IsTrue(finishedEvent.Wait(10000));
// both legs of the combo must be processed
Assert.IsTrue(transactionHandler.RequestProcessingThreads.ContainsKey(orderRequest1.OrderId));
Assert.IsTrue(transactionHandler.RequestProcessingThreads.ContainsKey(orderRequest2.OrderId));
}
finally
{
transactionHandler.Exit();
}
}
[Test]
public void TransactionThreadPoolStartsAtMinimumThreads()
{
var algorithm = new TestAlgorithm();
using var brokerage = new TestingConcurrentBrokerage();
using var finishedEvent = new ManualResetEventSlim(false);
var transactionHandler = new TestableConcurrentBrokerageTransactionHandler(1, finishedEvent);
transactionHandler.Initialize(algorithm, brokerage, new BacktestingResultHandler());
try
{
// the pool starts with the minimum number of threads and grows only on demand
Assert.AreEqual(2, transactionHandler.ActiveThreadCount);
}
finally
{
transactionHandler.Exit();
}
}
[TestCase(10)]
[TestCase(3)]
public void TransactionThreadPoolGrowsUnderBacklogUpToMaximum(int maximumThreads)
{
var algorithm = new TestAlgorithm();
using var brokerage = new TestingConcurrentBrokerage();
using var finishedEvent = new ManualResetEventSlim(false);
using var gate = new ManualResetEventSlim(false);
var transactionHandler = new TestableConcurrentBrokerageTransactionHandler(int.MaxValue, finishedEvent)
{
Gate = gate,
MaxThreadsOverride = maximumThreads
};
transactionHandler.Initialize(algorithm, brokerage, new BacktestingResultHandler());
try
{
algorithm.Transactions.SetOrderProcessor(transactionHandler);
var security = (Security)algorithm.AddEquity("SPY");
algorithm.SetFinishedWarmingUp();
var reference = new DateTime(2025, 07, 03, 10, 0, 0);
security.SetMarketPrice(new Tick(reference, security.Symbol, 300, 300));
// starts at the minimum
Assert.AreEqual(2, transactionHandler.ActiveThreadCount);
// keep feeding orders while threads stay blocked on the gate, forcing the pool to grow to the max
var orderId = 0;
var reachedMax = SpinWait.SpinUntil(() =>
{
if (orderId < 1000)
{
var request = MakeAsyncMarketRequest(security, reference);
request.SetOrderId(++orderId);
transactionHandler.Process(request);
}
return transactionHandler.ActiveThreadCount >= maximumThreads;
}, 10000);
Assert.IsTrue(reachedMax, $"Pool did not grow to the maximum, current size: {transactionHandler.ActiveThreadCount}");
// never grows beyond the configured maximum
Assert.AreEqual(maximumThreads, transactionHandler.ActiveThreadCount);
}
finally
{
gate.Set();
transactionHandler.Exit();
}
}
[Test]
public void ProcessesAnOrdersRequestsInOrderAsThePoolGrows()
{
// the requests of a single order must be processed in arrival order even when the pool grows between them
using var gate = new ManualResetEventSlim(false);
var processed = new ConcurrentQueue<(int OrderId, OrderRequestType Type)>();
Exception processingError = null;
var pool = new OrderRequestProcessingPool(concurrencyEnabled: true, minimumThreads: 1, maximumThreads: 10,
request =>
{
// block first so the requests pile up, then record the order they run in
gate.Wait();
processed.Enqueue((request.OrderId, request.OrderRequestType));
},
exception => processingError = exception);
try
{
var symbol = Symbols.SPY;
var reference = new DateTime(2025, 07, 03, 10, 0, 0);
// the order we track, its submit claims a worker and blocks on the gate
var submit = new SubmitOrderRequest(OrderType.Market, symbol.SecurityType, symbol, 1, 0, 0, reference, "");
submit.SetOrderId(1);
var order = Order.CreateOrder(submit);
pool.Dispatch(submit, order);
// saturate the pool with unrelated orders so it grows while the submit is still in flight
var fillerId = 1000;
var grew = SpinWait.SpinUntil(() =>
{
var filler = new SubmitOrderRequest(OrderType.Market, symbol.SecurityType, symbol, 1, 0, 0, 0, 0, false, reference, "",
asynchronous: true);
filler.SetOrderId(++fillerId);
pool.Dispatch(filler, Order.CreateOrder(filler));
return pool.ThreadCount >= 3;
}, 10000);
Assert.IsTrue(grew, $"the pool did not grow, current size: {pool.ThreadCount}");
// the update and cancel arrive after the pool grew, they must still run after the submit and in order
pool.Dispatch(new UpdateOrderRequest(reference, order.Id, new UpdateOrderFields()), order);
pool.Dispatch(new CancelOrderRequest(reference, order.Id, ""), order);
gate.Set();
Assert.IsTrue(SpinWait.SpinUntil(() => processed.Count(x => x.OrderId == 1) >= 3, 10000),
"the order's requests were not all processed");
var sequence = processed.Where(x => x.OrderId == 1).Select(x => x.Type).ToList();
Assert.AreEqual(new[] { OrderRequestType.Submit, OrderRequestType.Update, OrderRequestType.Cancel }, sequence);
Assert.IsNull(processingError, $"the pool reported an error: {processingError}");
}
finally
{
gate.Set();
pool.DisposeSafely();
}
}
[Test]
public void DoesNotGrowWhenOnlyOneOrderIsBusy()
{
using var gate = new ManualResetEventSlim(false);
var pool = new OrderRequestProcessingPool(concurrencyEnabled: true, minimumThreads: 2, maximumThreads: 10,
request => gate.Wait(),
exception => { });
try
{
var symbol = Symbols.SPY;
var reference = new DateTime(2025, 07, 03, 10, 0, 0);
var submit = new SubmitOrderRequest(OrderType.Market, symbol.SecurityType, symbol, 1, 0, 0, reference, "");
submit.SetOrderId(1);
var order = Order.CreateOrder(submit);
pool.Dispatch(submit, order);
// every follow up request is for the same order, so they are parked behind the one busy worker while
// the other stays idle, so the pool must not grow no matter how many pile up
for (var i = 0; i < 20; i++)
{
pool.Dispatch(new UpdateOrderRequest(reference, order.Id, new UpdateOrderFields()), order);
}
Assert.AreEqual(2, pool.ThreadCount);
}
finally
{
gate.Set();
pool.DisposeSafely();
}
}
[Test]
public void ProcessesManyOrdersWithUpdatesAndCancelsQuickly()
{
// lots of submit/update/cancel at once, the handler does nothing so this only measures the pool
const int orderCount = 1000;
const int requestsPerOrder = 3;
var processedCount = 0;
Exception processingError = null;
var pool = new OrderRequestProcessingPool(concurrencyEnabled: true, minimumThreads: 2, maximumThreads: 10,
_ => Interlocked.Increment(ref processedCount),
exception => processingError = exception);
try
{
var symbol = Symbols.SPY;
var reference = new DateTime(2025, 07, 03, 10, 0, 0);
for (var i = 1; i <= orderCount; i++)
{
var submit = new SubmitOrderRequest(OrderType.Market, symbol.SecurityType, symbol, 1, 0, 0, reference, "");
submit.SetOrderId(i);
var order = Order.CreateOrder(submit);
pool.Dispatch(submit, order);
pool.Dispatch(new UpdateOrderRequest(reference, order.Id, new UpdateOrderFields()), order);
pool.Dispatch(new CancelOrderRequest(reference, order.Id, ""), order);
}
// if the pool ever hangs or falls behind this wait times out instead of finishing in a few ms
var expectedRequests = orderCount * requestsPerOrder;
Assert.IsTrue(SpinWait.SpinUntil(() => processedCount >= expectedRequests, 10000));
Assert.IsNull(processingError);
}
finally
{
pool.DisposeSafely();
}
}
private static SubmitOrderRequest MakeAsyncMarketRequest(Security security, DateTime date)
{
return new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, 1, 0, 0, 0, 0, false, date, "",
asynchronous: true);
}
[TestCase("OnAccountChanged")]
[TestCase("OnOptionNotification")]
[TestCase("OnNewBrokerageOrderNotification")]
[TestCase("OnOrderIdChanged")]
[TestCase("OnOrderUpdated")]
public void BrokerageTransactionHandlerDoesNotProcessEventsWhenAlgorithmIsStopped(string eventName)
{
var referenceDateTime = new DateTime(2024, 01, 25, 10, 0, 0);
// Initialize the algorithm
var algorithm = new TestAlgorithm();
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
algorithm.SetBrokerageMessageHandler(new TestBrokerageMessageHandler());
// Initialize the transaction handler and brokerage
_transactionHandler = new TestBrokerageTransactionHandler();
using var brokerage = new EventEmittingBrokerage(algorithm);
_transactionHandler.Initialize(algorithm, brokerage, new BacktestingResultHandler());
// Set up option security and add a submitted order
algorithm.AddOption("SPY");
var option = algorithm.AddOptionContract(Symbol.CreateOption(
"SPY", Market.USA, OptionStyle.American, OptionRight.Call, 300, referenceDateTime.AddDays(4).Date
));
var orderRequest = new OptionExerciseOrder(option.Symbol, 1, referenceDateTime)
{
Id = 1,
BrokerId = new List<string> { "1" }
};
_transactionHandler.AddOpenOrder(orderRequest, algorithm);
// Order status should be Submitted
Assert.AreEqual(OrderStatus.Submitted, _transactionHandler.GetOrdersByBrokerageId(1)[0].Status);
// Stop the algorithm
algorithm.Status = AlgorithmStatus.Stopped;
// The brokerage should ignore the following events because the algorithm is not running
switch (eventName)
{
case "OnAccountChanged":
brokerage.CreateAccountChangedEvent(new AccountEvent("USD", 100));
// Cash balance should remain unchanged
Assert.AreEqual(algorithm.Portfolio.Cash, 100000);
break;
case "OnOptionNotification":
brokerage.CreateOptionNotificationEvent(new OptionNotificationEventArgs(option.Symbol, 5));
// Order status should still be Submitted
Assert.AreEqual(OrderStatus.Submitted, _transactionHandler.GetOrdersByBrokerageId(1)[0].Status);
break;
case "OnNewBrokerageOrderNotification":
var order = new MarketOrder(option.Symbol, 100, new DateTime(2024, 01, 19, 12, 0, 0))
{
Id = 2,
BrokerId = new List<string> { "1" }
};
brokerage.CreateNewBrokerageOrderNotificationEvent(new NewBrokerageOrderNotificationEventArgs(order));
// No new orders should have been added
Assert.AreEqual(1, _transactionHandler.GetOrdersByBrokerageId(1).Count);
break;
case "OnOrderIdChanged":
brokerage.CreateOrderIdChangedEvent(new BrokerageOrderIdChangedEvent
{
OrderId = 1,
BrokerId = new List<string> { "2" }
});
// No order should exist under the new broker ID
Assert.AreEqual(0, _transactionHandler.GetOrdersByBrokerageId(2).Count);
// Original broker ID should remain unchanged
Assert.AreEqual("1", _transactionHandler.GetOrdersByBrokerageId(1)[0].BrokerId[0]);
break;
case "OnOrderUpdated":
var stopLimitOrder = new StopLimitOrder(option.Symbol, 100, 100, 100, referenceDateTime)
{
Id = 2,
BrokerId = new List<string> { "1" },
StopTriggered = false
};
_transactionHandler.AddOpenOrder(stopLimitOrder, algorithm);
brokerage.CreateOrderUpdatedEvent(new OrderUpdateEvent { OrderId = 2, StopTriggered = true });
var updatedStopLimitOrder = (StopLimitOrder)_transactionHandler
.GetOrdersByBrokerageId(1)
.First(e => e.Id == 2);
// StopTriggered flag should remain false
Assert.IsFalse(updatedStopLimitOrder.StopTriggered);
break;
}
}
internal class TestIncrementalOrderIdAlgorithm : OrderTicketDemoAlgorithm
{
public static readonly Dictionary<int, int> OrderEventIds = new Dictionary<int, int>();
public override void OnOrderEvent(OrderEvent orderEvent)
{
if (!OrderEventIds.ContainsKey(orderEvent.OrderId))
{
OrderEventIds[orderEvent.OrderId] = orderEvent.Id;
if (orderEvent.Id != 1)
{
throw new Exception("Expected first order event to have id 1");
}
}
else
{
var previous = OrderEventIds[orderEvent.OrderId];
if (orderEvent.Id != (previous + 1))
{
throw new Exception("Expected incremental order event ids");
}
OrderEventIds[orderEvent.OrderId] = orderEvent.Id;
}
}
}
internal class TestIncrementalOrderIdSetupHandler : AlgorithmRunner.RegressionSetupHandlerWrapper
{
public override IAlgorithm CreateAlgorithmInstance(AlgorithmNodePacket algorithmNodePacket, string assemblyPath)
{
return Algorithm = new TestIncrementalOrderIdAlgorithm();
}
}
internal class EmptyHistoryProvider : HistoryProviderBase
{
public override int DataPointCount => 0;
public override void Initialize(HistoryProviderInitializeParameters parameters)
{
}
public override IEnumerable<Slice> GetHistory(IEnumerable<HistoryRequest> requests, DateTimeZone sliceTimeZone)
{
return Enumerable.Empty<Slice>();
}
}
internal class TestBrokerageModel : DefaultBrokerageModel
{
public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
{
message = new BrokerageMessageEvent(0, 0, "");
return false;
}
}
internal class TestAlgorithm : QCAlgorithm
{
public List<OrderEvent> OrderEvents = new List<OrderEvent>();
public TestAlgorithm()
{
SubscriptionManager.SetDataManager(new DataManagerStub(this));
SetFinishedWarmingUp();
}
public override void OnOrderEvent(OrderEvent orderEvent)
{
OrderEvents.Add(orderEvent);
}
}
// Implemented through an underlying BactestingBrokerage instead of directly inheriting from it for easy implementation
// and for tests that require simulating using a live brokerage, not derived from BacktestingBrokerage.
internal class NoSubmitTestBrokerage : Brokerage
{
private BacktestingBrokerage _underlyingBrokerage;
public override bool IsConnected => _underlyingBrokerage.IsConnected;
public NoSubmitTestBrokerage(IAlgorithm algorithm) : base("NoSubmitTestBrokerage")
{
_underlyingBrokerage = new BacktestingBrokerage(algorithm);
}
public override bool PlaceOrder(Order order)
{
return true;
}
public override bool UpdateOrder(Order order)
{
return true;
}
public void PublishOrderEvent(OrderEvent orderEvent)
{
OnOrderEvent(orderEvent);
}
public override bool CancelOrder(Order order)
{
return _underlyingBrokerage.CancelOrder(order);
}
public override void Connect()
{
_underlyingBrokerage.Connect();
}
public override void Disconnect()
{
_underlyingBrokerage.Disconnect();
}
public override List<Order> GetOpenOrders()
{
return _underlyingBrokerage.GetOpenOrders();
}
public override List<Holding> GetAccountHoldings()
{
return _underlyingBrokerage.GetAccountHoldings();
}
public override List<CashAmount> GetCashBalance()
{
return _underlyingBrokerage.GetCashBalance();
}
}
internal class TestBroker : BacktestingBrokerage
{
private readonly bool _cancelOrderResult;
public TestBroker(IAlgorithm algorithm, bool cancelOrderResult) : base(algorithm)
{
_cancelOrderResult = cancelOrderResult;
}
public override bool CancelOrder(Order order)
{
return _cancelOrderResult;
}
public void OnOrderIdChangedEventPublic(BrokerageOrderIdChangedEvent e)
{
base.OnOrderIdChangedEvent(e);
}
}
public class TestBrokerageTransactionHandler : BrokerageTransactionHandler
{
private IBrokerageCashSynchronizer _brokerage;
public int CancelPendingOrdersSize => _cancelPendingOrders.GetCancelPendingOrdersSize;
public TimeSpan TestTimeSinceLastFill { get; set; } = TimeSpan.FromDays(1);
public DateTime TestCurrentTimeUtc { get; set; } = new DateTime(13, 1, 13, 13, 13, 13);
// modifying current time so cash sync is triggered
protected override DateTime CurrentTimeUtc => TestCurrentTimeUtc;
protected override TimeSpan TimeSinceLastFill => TestTimeSinceLastFill;
// no worker thread: these tests drive HandleOrderRequest manually
protected override bool SynchronousProcessing => true;
public override void Initialize(IAlgorithm algorithm, IBrokerage brokerage, IResultHandler resultHandler)
{
_brokerage = brokerage;
base.Initialize(algorithm, brokerage, resultHandler);
}
public DateTime GetLastSyncDate()
{
return _brokerage.LastSyncDateTimeUtc.ConvertFromUtc(TimeZones.NewYork);
}
public new void RoundOrderPrices(Order order, Security security)
{
base.RoundOrderPrices(order, security);
}
}
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)
{
return new Dictionary<Symbol, long>();
}
public decimal FeeRate(Symbol symbol, DateTime localTime)
{
return 0;
}
public decimal RebateRate(Symbol symbol, DateTime localTime)
{
return 0;
}
public long? ShortableQuantity(Symbol symbol, DateTime localTime)
{
return 0;
}
}
private class TestShortableBrokerageModel : DefaultBrokerageModel
{
public override IShortableProvider GetShortableProvider(Security security)
{
return new TestNonShortableProvider();
}
}
private class TestBrokerageMessageHandler : IBrokerageMessageHandler
{
public bool LastHandleOrderResult { get; private set; }
public void HandleMessage(BrokerageMessageEvent messageEvent)
{
}
public bool HandleOrder(NewBrokerageOrderNotificationEventArgs eventArgs)
{
// For testing purposes, only market orders are handled
return LastHandleOrderResult = eventArgs.Order.Type == OrderType.Market;
}
}
private class TestingBrokerage : TestBrokerage
{
public void OnNewBrokerageOrder(NewBrokerageOrderNotificationEventArgs e)
{
OnNewBrokerageOrderNotification(e);
}
}
private class TestingConcurrentBrokerage : TestingBrokerage
{
public override bool ConcurrencyEnabled => true;
}
private class TestableConcurrentBrokerageTransactionHandler : BrokerageTransactionHandler
{
private readonly int _expectedOrdersCount;
private readonly ManualResetEventSlim _finishedEvent;
private int _currentOrdersCount;
public HashSet<string> ProcessingThreadNames = new();
public ConcurrentBag<OrderRequest> ProcessedRequests = new();
public ConcurrentDictionary<int, string> RequestProcessingThreads = new();
// blocks threads so requests pile up and force the pool to grow
public ManualResetEventSlim Gate;
public int ActiveThreadCount => ProcessingThreadsCount;
// overrides the pool maximum without touching the global Config
public int? MaxThreadsOverride { get; set; }
protected override int MaximumTransactionThreads => MaxThreadsOverride ?? base.MaximumTransactionThreads;
public TestableConcurrentBrokerageTransactionHandler(int expectedOrdersCount, ManualResetEventSlim finishedEvent)
{
_expectedOrdersCount = expectedOrdersCount;
_finishedEvent = finishedEvent;
}
public override void HandleOrderRequest(OrderRequest request)
{
Gate?.Wait();
base.HandleOrderRequest(request);
// Capture the thread name for debugging purposes
var threadName = Thread.CurrentThread.Name ?? Environment.CurrentManagedThreadId.ToString();
lock (ProcessingThreadNames)
{
ProcessingThreadNames.Add(threadName);
}
RequestProcessingThreads[request.OrderId] = threadName;
ProcessedRequests.Add(request);
if (Interlocked.Increment(ref _currentOrdersCount) >= _expectedOrdersCount)
{
// Signal that we have processed the expected number of orders
_finishedEvent.Set();
}
}
}
internal class EventEmittingBrokerage : Brokerage
{
private BacktestingBrokerage _underlyingBrokerage;
public override bool IsConnected => _underlyingBrokerage.IsConnected;
public override bool AccountInstantlyUpdated => true;
public EventEmittingBrokerage(IAlgorithm algorithm) : base("NoSubmitTestBrokerage")
{
_underlyingBrokerage = new BacktestingBrokerage(algorithm);
}
public override bool PlaceOrder(Order order)
{
return true;
}
public override bool UpdateOrder(Order order)
{
return true;
}
// Events
public void CreateOrderEvent(OrderEvent orderEvent)
{
OnOrderEvent(orderEvent);
}
public void CreateAccountChangedEvent(AccountEvent accountEvent)
{
OnAccountChanged(accountEvent);
}
public void CreateOptionPositionAssignedEvent(OrderEvent orderEvent)
{
OnOptionPositionAssigned(orderEvent);
}
public void CreateOptionNotificationEvent(OptionNotificationEventArgs optionNotificationEventArgs)
{
OnOptionNotification(optionNotificationEventArgs);
}
public void CreateNewBrokerageOrderNotificationEvent(NewBrokerageOrderNotificationEventArgs newBrokerageOrderNotificationEventArgs)
{
OnNewBrokerageOrderNotification(newBrokerageOrderNotificationEventArgs);
}
public void CreateDelistingNotificationEvent(DelistingNotificationEventArgs delistingNotificationEventArgs)
{
OnDelistingNotification(delistingNotificationEventArgs);
}
public void CreateOrderIdChangedEvent(BrokerageOrderIdChangedEvent brokerageOrderIdChangedEvent)
{
OnOrderIdChangedEvent(brokerageOrderIdChangedEvent);
}
public void CreateOrderUpdatedEvent(OrderUpdateEvent orderEvent)
{
OnOrderUpdated(orderEvent);
}
public override bool CancelOrder(Order order)
{
return _underlyingBrokerage.CancelOrder(order);
}
public override void Connect()
{
_underlyingBrokerage.Connect();
}
public override void Disconnect()
{
_underlyingBrokerage.Disconnect();
}
public override List<Order> GetOpenOrders()
{
return _underlyingBrokerage.GetOpenOrders();
}
public override List<Holding> GetAccountHoldings()
{
return _underlyingBrokerage.GetAccountHoldings();
}
public override List<CashAmount> GetCashBalance()
{
return _underlyingBrokerage.GetCashBalance();
}
}
}
}