/* * 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 Deedle; using NUnit.Framework; using QuantConnect.Orders; using QuantConnect.Report; using QuantConnect.Brokerages; using System; using System.Collections.Generic; using System.Linq; namespace QuantConnect.Tests.Report { [TestFixture] public class PortfolioLooperTests { [Test] public void EmptyEquitySeriesDoesNotCrash() { var equityPoints = new SortedList { { new DateTime(2019, 1, 3, 5, 0, 5), 100000 } }; var series = new Series(equityPoints); var order = new MarketOrder(Symbols.SPY, 1m, new DateTime(2019, 1, 3, 5, 0, 0)); // Force an order ID >= 1 on the order, otherwise the test will fail // because the order will be filtered out. order.GetType().GetProperty("Id").SetValue(order, 1); var orders = new List { order }; Assert.DoesNotThrow(() => PortfolioLooper.FromOrders(series, orders).ToList()); } [TestCase(OrderType.Market, 0, 0)] [TestCase(OrderType.Limit, 0, 80000)] [TestCase(OrderType.StopLimit, 80000, 80000)] [TestCase(OrderType.StopMarket, 80000, 0)] [TestCase(OrderType.MarketOnOpen, 0, 0, true)] [TestCase(OrderType.MarketOnClose, 0, 0, true)] public void OrderProcessedInLooper(OrderType orderType, double stopPrice, double limitPrice, bool hasNullLastFillTime = false) { var equityPoints = new SortedList { { new DateTime(2019, 1, 3, 5, 0, 5), 100000 }, { new DateTime(2019, 1, 4, 5, 0, 5), 90000 }, }; var series = new Series(equityPoints); var entryOrder = Order.CreateOrder(new SubmitOrderRequest( orderType, SecurityType.Equity, Symbols.SPY, 1, (decimal)stopPrice, (decimal)limitPrice, new DateTime(2019, 1, 3, 5, 0, 5), string.Empty )); var exitOrder = Order.CreateOrder(new SubmitOrderRequest( orderType, SecurityType.Equity, Symbols.SPY, -1, (decimal)stopPrice, (decimal)limitPrice, new DateTime(2019, 1, 4, 5, 0, 5), string.Empty )); if (!hasNullLastFillTime) { entryOrder.LastFillTime = new DateTime(2019, 1, 3, 5, 0, 5); exitOrder.LastFillTime = new DateTime(2019, 1, 4, 5, 0, 5); } entryOrder.GetType().GetProperty("Id").SetValue(entryOrder, 1); entryOrder.GetType().GetProperty("Price").SetValue(entryOrder, 100000m); Order marketOnFillOrder = null; if (hasNullLastFillTime) { marketOnFillOrder = entryOrder.Clone(); marketOnFillOrder.GetType().GetProperty("Status").SetValue(marketOnFillOrder, OrderStatus.Filled); marketOnFillOrder.GetType().GetProperty("Time").SetValue(marketOnFillOrder, new DateTime(2019, 1, 3, 6, 0 ,5)); } exitOrder.GetType().GetProperty("Id").SetValue(exitOrder, 2); exitOrder.GetType().GetProperty("Price").SetValue(exitOrder, 80000m); exitOrder.GetType().GetProperty("Status").SetValue(exitOrder, OrderStatus.Filled); var orders = new[] { entryOrder, marketOnFillOrder, exitOrder }.Where(x => x != null); var looper = PortfolioLooper.FromOrders(series, orders); var pointInTimePortfolio = looper.ToList(); Assert.AreEqual(3, pointInTimePortfolio.Count); Assert.AreEqual(100000, pointInTimePortfolio[0].TotalPortfolioValue); Assert.AreEqual(80000, pointInTimePortfolio[1].TotalPortfolioValue); Assert.AreEqual(80000, pointInTimePortfolio[2].TotalPortfolioValue); } [Test] public void OptionOrderDoesNotThrow() { var equityPoints = new SortedList { { new DateTime(2019, 1, 3, 5, 0, 5), 100000 }, { new DateTime(2019, 1, 4, 5, 0, 5), 90000 }, }; var series = new Series(equityPoints); var equity = Symbol.Create("SPY", SecurityType.Equity, Market.USA); var optionSid = SecurityIdentifier.GenerateOption( equity.ID.Date, equity.ID, equity.ID.Market, 200m, OptionRight.Call, OptionStyle.American); var option = new Symbol(optionSid, optionSid.Symbol); var entryOrder = Order.CreateOrder(new SubmitOrderRequest( OrderType.Market, SecurityType.Option, option, 1, 0m, 0m, new DateTime(2019, 1, 3, 5, 0, 5), string.Empty )); var exitOrder = Order.CreateOrder(new SubmitOrderRequest( OrderType.Market, SecurityType.Option, option, -1, 0m, 0m, new DateTime(2019, 1, 4, 5, 0, 5), string.Empty )); entryOrder.LastFillTime = new DateTime(2019, 1, 3, 5, 0, 5); exitOrder.LastFillTime = new DateTime(2019, 1, 4, 5, 0, 5); entryOrder.GetType().GetProperty("Id").SetValue(entryOrder, 1); entryOrder.GetType().GetProperty("Price").SetValue(entryOrder, 100000m); Order marketOnFillOrder = null; exitOrder.GetType().GetProperty("Id").SetValue(exitOrder, 2); exitOrder.GetType().GetProperty("Price").SetValue(exitOrder, 80000m); exitOrder.GetType().GetProperty("Status").SetValue(exitOrder, OrderStatus.Filled); var orders = new[] { entryOrder, marketOnFillOrder, exitOrder }.Where(x => x != null); var looper = PortfolioLooper.FromOrders(series, orders); Assert.DoesNotThrow(() => { foreach (var pointInTimePortfolio in looper) { Assert.AreEqual(option, pointInTimePortfolio.Order.Symbol); Assert.AreEqual(option.Underlying, pointInTimePortfolio.Order.Symbol.Underlying); } }); } [TestCase("BNTUSDT", "USDT")] [TestCase("AUDBUSD", "BUSD")] public void OrderProcessedInLooper_WithNonDefaultAlgorithmSettings(string symbol, string currency) { var equityPoints = new SortedList { { new DateTime(2020, 2, 12, 20, 0, 0), 100000 }, { new DateTime(2020, 2, 13, 20, 0, 0), 900000 }, }; var series = new Series(equityPoints); var orderPrice = 0.35m; var orderQuantity = 30000m; var order = Order.CreateOrder(new SubmitOrderRequest( OrderType.Market, SecurityType.Crypto, Symbol.Create(symbol, SecurityType.Crypto, Market.Binance), orderQuantity, 0m, 0m, new DateTime(2020, 2, 12, 20, 0, 0), string.Empty )); order.LastFillTime = new DateTime(2020, 2, 12, 20, 0, 0); order.GetType().GetProperty("Id").SetValue(order, 1); order.GetType().GetProperty("Status").SetValue(order, OrderStatus.Filled); order.GetType().GetProperty("Price").SetValue(order, orderPrice); var orders = new[] { order }; var looper = PortfolioLooper.FromOrders(series, orders, new AlgorithmConfiguration("AlgorightmName", new HashSet(), currency, BrokerageName.Binance, AccountType.Cash, new Dictionary(), DateTime.MinValue, DateTime.MinValue, null, 0)); var pointInTimePortfolio = looper.ToList(); Assert.AreEqual(2, pointInTimePortfolio.Count); Assert.AreEqual(100000m, pointInTimePortfolio[0].TotalPortfolioValue); var holdings = pointInTimePortfolio[0].Holdings; Assert.AreEqual(1, holdings.Count); Assert.AreEqual(orderQuantity, holdings[0].Quantity); Assert.AreEqual(orderQuantity * orderPrice, holdings[0].HoldingsValue); } } }