/* * 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 System; using NUnit.Framework; using System.Collections.Generic; using System.Linq; using QuantConnect.Algorithm; using QuantConnect.Data.Market; using QuantConnect.Orders; using QuantConnect.Orders.Fees; using QuantConnect.Securities; using QuantConnect.Tests.Engine.DataFeeds; namespace QuantConnect.Tests.Algorithm { [TestFixture, Parallelizable(ParallelScope.Fixtures)] public class AlgorithmSetHoldingsTests { // Test class to enable calling protected methods public class TestSecurityMarginModel : SecurityMarginModel { public TestSecurityMarginModel(decimal leverage) : base(leverage) { } public new decimal GetInitialMarginRequiredForOrder( InitialMarginRequiredForOrderParameters parameters) { return base.GetInitialMarginRequiredForOrder(parameters).Value; } public new decimal GetMarginRemaining(SecurityPortfolioManager portfolio, Security security, OrderDirection direction) { return base.GetMarginRemaining(portfolio, security, direction); } } public enum Position { Zero = 0, Long = 1, Short = -1 } public enum FeeType { None, Small, Large, InteractiveBrokers } public enum PriceMovement { Static, RisingSmall, FallingSmall, RisingLarge, FallingLarge } private readonly Dictionary _feeModels = new Dictionary { { FeeType.None, new ConstantFeeModel(0) }, { FeeType.Small, new ConstantFeeModel(1) }, { FeeType.Large, new ConstantFeeModel(100) }, { FeeType.InteractiveBrokers, new InteractiveBrokersFeeModel() } }; private readonly Symbol _symbol = Symbols.SPY; private const decimal Cash = 100000m; private const decimal VeryLowPrice = 155m; private const decimal LowPrice = 159m; private const decimal BasePrice = 160m; private const decimal HighPrice = 161m; private const decimal VeryHighPrice = 165m; public class Permuter { private static void Permute(T[] row, int index, IReadOnlyList> data, ICollection result) { foreach (var dataRow in data[index]) { row[index] = dataRow; if (index >= data.Count - 1) { var rowCopy = new T[row.Length]; row.CopyTo(rowCopy, 0); result.Add(rowCopy); } else { Permute(row, index + 1, data, result); } } } public static void Permute(List> data, List result) { if (data.Count == 0) return; Permute(new T[data.Count], 0, data, result); } } private static IEnumerable TestParameters { get { var initialPositions = Enum.GetValues(typeof(Position)).Cast().ToList(); var finalPositions = Enum.GetValues(typeof(Position)).Cast().ToList(); var feeTypes = Enum.GetValues(typeof(FeeType)).Cast().ToList(); var priceMovements = Enum.GetValues(typeof(PriceMovement)).Cast().ToList(); var leverages = new List { 1, 100 }; var data = new List> { initialPositions.Cast().ToList(), finalPositions.Cast().ToList(), feeTypes.Cast().ToList(), priceMovements.Cast().ToList(), leverages.Cast().ToList() }; var permutations = new List(); Permuter.Permute(data, permutations); var ret = permutations .Where(row => (Position)row[0] != (Position)row[1]); // initialPosition != finalPosition return ret; } } [Test, TestCaseSource(nameof(TestParameters))] public void Run(object[] parameters) { Position initialPosition = (Position)parameters[0]; Position finalPosition = (Position)parameters[1]; FeeType feeType = (FeeType)parameters[2]; PriceMovement priceMovement = (PriceMovement)parameters[3]; int leverage = (int)parameters[4]; //Console.WriteLine("----------"); //Console.WriteLine("PARAMETERS"); //Console.WriteLine("Initial position: " + initialPosition); //Console.WriteLine("Final position: " + finalPosition); //Console.WriteLine("Fee type: " + feeType); //Console.WriteLine("Price movement: " + priceMovement); //Console.WriteLine("Leverage: " + leverage); //Console.WriteLine("----------"); //Console.WriteLine(); var algorithm = new QCAlgorithm(); algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm)); var security = algorithm.AddSecurity(_symbol.ID.SecurityType, _symbol.ID.Symbol); security.FeeModel = _feeModels[feeType]; security.SetLeverage(leverage); var buyingPowerModel = new TestSecurityMarginModel(leverage); security.BuyingPowerModel = buyingPowerModel; algorithm.SetCash(Cash); Update(security, BasePrice); decimal targetPercentage; OrderDirection orderDirection; MarketOrder order; OrderFee orderFee; OrderEvent fill; decimal orderQuantity; decimal freeMargin; decimal requiredMargin; if (initialPosition != Position.Zero) { targetPercentage = (decimal)initialPosition; orderDirection = initialPosition == Position.Long ? OrderDirection.Buy : OrderDirection.Sell; orderQuantity = algorithm.CalculateOrderQuantity(_symbol, targetPercentage); order = new MarketOrder(_symbol, orderQuantity, DateTime.UtcNow); freeMargin = buyingPowerModel.GetMarginRemaining(algorithm.Portfolio, security, orderDirection); requiredMargin = buyingPowerModel.GetInitialMarginRequiredForOrder( new InitialMarginRequiredForOrderParameters( new IdentityCurrencyConverter(algorithm.Portfolio.CashBook.AccountCurrency), security, order)); //Console.WriteLine("Current price: " + security.Price); //Console.WriteLine("Target percentage: " + targetPercentage); //Console.WriteLine("Order direction: " + orderDirection); //Console.WriteLine("Order quantity: " + orderQuantity); //Console.WriteLine("Free margin: " + freeMargin); //Console.WriteLine("Required margin: " + requiredMargin); //Console.WriteLine(); Assert.That(Math.Abs(requiredMargin) <= freeMargin); orderFee = security.FeeModel.GetOrderFee( new OrderFeeParameters(security, order)); fill = new OrderEvent(order, DateTime.UtcNow, orderFee) { FillPrice = security.Price, FillQuantity = orderQuantity }; algorithm.Portfolio.ProcessFills(new List { fill }); //Console.WriteLine("Portfolio.Cash: " + algorithm.Portfolio.Cash); //Console.WriteLine("Portfolio.TotalPortfolioValue: " + algorithm.Portfolio.TotalPortfolioValue); //Console.WriteLine(); if (priceMovement == PriceMovement.RisingSmall) { Update(security, HighPrice); } else if (priceMovement == PriceMovement.FallingSmall) { Update(security, LowPrice); } else if (priceMovement == PriceMovement.RisingLarge) { Update(security, VeryHighPrice); } else if (priceMovement == PriceMovement.FallingLarge) { Update(security, VeryLowPrice); } } targetPercentage = (decimal)finalPosition; orderDirection = finalPosition == Position.Long || (finalPosition == Position.Zero && initialPosition == Position.Short) ? OrderDirection.Buy : OrderDirection.Sell; orderQuantity = algorithm.CalculateOrderQuantity(_symbol, targetPercentage); order = new MarketOrder(_symbol, orderQuantity, DateTime.UtcNow); freeMargin = buyingPowerModel.GetMarginRemaining(algorithm.Portfolio, security, orderDirection); requiredMargin = buyingPowerModel.GetInitialMarginRequiredForOrder( new InitialMarginRequiredForOrderParameters( new IdentityCurrencyConverter(algorithm.Portfolio.CashBook.AccountCurrency), security, order)); //Console.WriteLine("Current price: " + security.Price); //Console.WriteLine("Target percentage: " + targetPercentage); //Console.WriteLine("Order direction: " + orderDirection); //Console.WriteLine("Order quantity: " + orderQuantity); //Console.WriteLine("Free margin: " + freeMargin); //Console.WriteLine("Required margin: " + requiredMargin); //Console.WriteLine(); Assert.That(Math.Abs(requiredMargin) <= freeMargin); orderFee = security.FeeModel.GetOrderFee( new OrderFeeParameters(security, order)); fill = new OrderEvent(order, DateTime.UtcNow, orderFee) { FillPrice = security.Price, FillQuantity = orderQuantity }; algorithm.Portfolio.ProcessFills(new List { fill }); //Console.WriteLine("Portfolio.Cash: " + algorithm.Portfolio.Cash); //Console.WriteLine("Portfolio.TotalPortfolioValue: " + algorithm.Portfolio.TotalPortfolioValue); //Console.WriteLine(); } private static void Update(Security security, decimal price) { security.SetMarketPrice(new TradeBar { Time = DateTime.Now, Symbol = security.Symbol, Open = price, High = price, Low = price, Close = price }); } } }