/* * 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 NUnit.Framework; using QuantConnect.Algorithm; using QuantConnect.Algorithm.Framework.Alphas; using QuantConnect.Data; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Lean.Engine.DataFeeds; using QuantConnect.Lean.Engine.HistoricalData; using QuantConnect.Orders; using QuantConnect.Orders.Slippage; using QuantConnect.Securities; using QuantConnect.Tests.Common.Data.Fundamental; using QuantConnect.Tests.Engine.DataFeeds; using System; using System.Collections.Generic; namespace QuantConnect.Tests.Common.Orders.Slippage { [TestFixture] public class MarketImpactSlippageModelTests { private QCAlgorithm _algorithm; private MarketImpactSlippageModel _slippageModel; private List _securities; [SetUp] public void Initialize() { _algorithm = new QCAlgorithm(); _algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(_algorithm)); var historyProvider = new SubscriptionDataReaderHistoryProvider(); historyProvider.Initialize(new HistoryProviderInitializeParameters(null, null, TestGlobals.DataProvider, TestGlobals.DataCacheProvider, TestGlobals.MapFileProvider, TestGlobals.FactorFileProvider, null, true, new DataPermissionManager(), _algorithm.ObjectStore, _algorithm.Settings)); _algorithm.SetHistoryProvider(historyProvider); FundamentalService.Initialize(TestGlobals.DataProvider, new TestFundamentalDataProvider(), false); var optionContract = Symbol.CreateOption(Symbols.AAPL, Market.USA, OptionStyle.American, OptionRight.Call, 100, new DateTime(2016, 1, 15)); _algorithm.SetDateTime(new DateTime(2015, 6, 10, 15, 0, 0)); _securities = new List { _algorithm.AddEquity("SPY", Resolution.Daily), // liquid stock _algorithm.AddEquity("AIG", Resolution.Daily), // illquid stock _algorithm.AddCrypto("BTCUSD", Resolution.Daily, Market.Coinbase), // crypto _algorithm.AddOptionContract(optionContract, Resolution.Minute) // equity options }; foreach (var security in _securities) { security.SetMarketPrice(new TradeBar(_algorithm.Time, security.Symbol, 100m, 100m, 100m, 100m, 1)); } _algorithm.Settings.AutomaticIndicatorWarmUp = true; _slippageModel = new MarketImpactSlippageModel(_algorithm); } // Test on buy & sell orders [TestCase(InsightDirection.Up)] [TestCase(InsightDirection.Down)] public void SizeSlippageComparisonTests(InsightDirection direction) { // Test on all liquid/illquid stocks/other asset classes foreach (var asset in _securities) { // A significantly large difference that noise cannot affect the result var smallBuyOrder = new MarketOrder(asset.Symbol, 10 * (int)direction, new DateTime(2015, 6, 10, 14, 00, 0)); var largeBuyOrder = new MarketOrder(asset.Symbol, 10000000000 * (int)direction, new DateTime(2015, 6, 10, 14, 00, 0)); var smallBuySlippage = _slippageModel.GetSlippageApproximation(asset, smallBuyOrder); var largeBuySlippage = _slippageModel.GetSlippageApproximation(asset, largeBuyOrder); // We expect small size order has less slippage than large size order on the same asset Assert.Less(smallBuySlippage, largeBuySlippage); } } // Order quantity large enough to create significant market impact // Test for buy & sell orders [TestCase(100000)] [TestCase(-100000)] public void VolatileSlippageComparisonTests(decimal orderQuantity) { var highVolAsset = _securities[0]; var lowVolAsset = _securities[1]; var highVolOrder = new MarketOrder(highVolAsset.Symbol, orderQuantity, new DateTime(2015, 6, 10, 14, 00, 0)); var lowVolOrder = new MarketOrder(lowVolAsset.Symbol, orderQuantity, new DateTime(2015, 6, 10, 14, 00, 0)); var highVolSlippage = _slippageModel.GetSlippageApproximation(highVolAsset, highVolOrder); var lowVolSlippage = _slippageModel.GetSlippageApproximation(lowVolAsset, lowVolOrder); // We expect same size order on volatile asset has greater slippage than less volatile asset Assert.Greater(highVolSlippage, lowVolSlippage); } // Test on buy & sell orders [TestCase(10000)] [TestCase(-10000)] [TestCase(10000000)] [TestCase(-10000000)] public void TimeSlippageComparisonTests(decimal orderQuantity) { // set up another slippage model with much longer execution time var slowSlippageModel = new MarketImpactSlippageModel(_algorithm, latency: 10); // Test on all liquid/illquid stocks/other asset classes foreach (var asset in _securities) { var order = new MarketOrder(asset.Symbol, orderQuantity, new DateTime(2015, 6, 10, 14, 00, 0)); var fastFilledSlippage = _slippageModel.GetSlippageApproximation(asset, order); var slowFilledSlippage = slowSlippageModel.GetSlippageApproximation(asset, order); // We expect same size order on same asset has less slippage if filled slower since the market can digest slowly Assert.Less(slowFilledSlippage, fastFilledSlippage); } } // To test whether the slippage matches our expectation [TestCase(100, 0, 0.0)] [TestCase(100, 1, 0.0808)] [TestCase(1, 2, 15.5061)] [TestCase(1, 3, 38.7598)] [TestCase(-100, 0, 0.0)] [TestCase(-100, 1, 0.0808)] [TestCase(-1, 2, 15.5061)] [TestCase(-1, 3, 38.7598)] [TestCase(10000, 0, 0.5075)] [TestCase(10000, 1, 3.8421)] [TestCase(100, 2, 100.0)] [TestCase(100, 3, 100.0)] [TestCase(-10000, 0, 0.5075)] [TestCase(-10000, 1, 3.8421)] [TestCase(-100, 2, 100.0)] [TestCase(-100, 3, 100.0)] public void SlippageExpectationTests(decimal orderQuantity, int index, double expected) { var asset = _securities[index]; var order = new MarketOrder(asset.Symbol, orderQuantity, new DateTime(2015, 6, 10, 14, 00, 0)); var slippage = _slippageModel.GetSlippageApproximation(asset, order); Assert.AreEqual(expected, (double)slippage, 0.005d); } // Test on buy & sell orders [TestCase(1)] [TestCase(-1)] [TestCase(1000)] [TestCase(-1000)] [TestCase(1000000)] [TestCase(-1000000)] public void NonNegativeSlippageTests(decimal orderQuantity) { // Test on all liquid/illquid stocks/other asset classes foreach (var asset in _securities) { var order = new MarketOrder(asset.Symbol, orderQuantity, new DateTime(2015, 6, 10, 14, 00, 0)); var slippage = _slippageModel.GetSlippageApproximation(asset, order); Assert.GreaterOrEqual(slippage, 0m); } } // Large order size to hit the threshold // Test on buy & sell orders [TestCase(10000)] [TestCase(-10000)] [TestCase(1000000000)] [TestCase(-1000000000)] public void MaxSlippageValueTests(decimal orderQuantity) { // Test on all liquid/illquid stocks/other asset classes foreach (var asset in _securities) { var order = new MarketOrder(asset.Symbol, orderQuantity, new DateTime(2015, 6, 10, 14, 00, 0)); var slippage = _slippageModel.GetSlippageApproximation(asset, order); // Slippage is at max the asset's price, no limit on negative slippage Assert.LessOrEqual(slippage, asset.Price); } } [Test] public void CfdExceptionTests() { var cfd = _algorithm.AddCfd("XAUUSD", Resolution.Daily, Market.Oanda); var cfdOrder = new MarketOrder(cfd.Symbol, 10, new DateTime(2013, 10, 10, 14, 00, 0)); Assert.Throws(() => _slippageModel.GetSlippageApproximation(cfd, cfdOrder)); } [Test] public void ForexExceptionTests() { var forex = _algorithm.AddForex("EURUSD", Resolution.Daily, Market.Oanda); var forexOrder = new MarketOrder(forex.Symbol, 10, new DateTime(2013, 10, 10, 14, 00, 0)); Assert.Throws(() => _slippageModel.GetSlippageApproximation(forex, forexOrder)); } } }