3219 lines
156 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|
|
}
|