329 lines
14 KiB
C#
329 lines
14 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 System;
|
|
using System.Linq;
|
|
using NUnit.Framework;
|
|
using QuantConnect.Data;
|
|
using QuantConnect.Data.Market;
|
|
using QuantConnect.Optimizer.Parameters;
|
|
using QuantConnect.Orders;
|
|
using QuantConnect.Orders.Fills;
|
|
using QuantConnect.Securities;
|
|
using QuantConnect.Tests.Common.Data;
|
|
using QuantConnect.Tests.Common.Securities;
|
|
|
|
namespace QuantConnect.Tests.Common.Orders.Fills
|
|
{
|
|
[TestFixture]
|
|
public partial class EquityFillModelTests
|
|
{
|
|
[Test]
|
|
public void PerformsStopMarketFillBuy()
|
|
{
|
|
var model = new EquityFillModel();
|
|
var order = new StopMarketOrder(Symbols.SPY, 100, 101.5m, Noon);
|
|
|
|
var parameters = GetFillModelParameters(order);
|
|
|
|
var security = parameters.Security;
|
|
security.SetLocalTimeKeeper(TimeKeeper.GetLocalTimeKeeper(TimeZones.NewYork));
|
|
security.SetMarketPrice(new TradeBar(Noon, Symbols.SPY, 101m, 101m, 101m, 101m, 100));
|
|
|
|
var fill = model.Fill(parameters).Single();
|
|
|
|
Assert.AreEqual(0, fill.FillQuantity);
|
|
Assert.AreEqual(0, fill.FillPrice);
|
|
Assert.AreEqual(OrderStatus.None, fill.Status);
|
|
|
|
security.SetMarketPrice(new TradeBar(Noon, Symbols.SPY, 101m, 102.5m, 101m, 102m, 100));
|
|
|
|
fill = model.StopMarketFill(security, order);
|
|
|
|
Assert.AreEqual(order.Quantity, fill.FillQuantity);
|
|
Assert.AreEqual(order.StopPrice, fill.FillPrice);
|
|
Assert.AreEqual(OrderStatus.Filled, fill.Status);
|
|
}
|
|
|
|
[Test]
|
|
public void PerformsStopMarketFillSell()
|
|
{
|
|
var model = new EquityFillModel();
|
|
var order = new StopMarketOrder(Symbols.SPY, -100, 101.5m, Noon);
|
|
|
|
var parameters = GetFillModelParameters(order);
|
|
|
|
var security = parameters.Security;
|
|
security.SetLocalTimeKeeper(TimeKeeper.GetLocalTimeKeeper(TimeZones.NewYork));
|
|
security.SetMarketPrice(new TradeBar(Noon, Symbols.SPY, 102m, 102m, 102m, 102m, 100));
|
|
|
|
var fill = model.Fill(parameters).Single();
|
|
|
|
Assert.AreEqual(0, fill.FillQuantity);
|
|
Assert.AreEqual(0, fill.FillPrice);
|
|
Assert.AreEqual(OrderStatus.None, fill.Status);
|
|
|
|
security.SetMarketPrice(new TradeBar(Noon, Symbols.SPY, 102m, 102.5m, 101m, 101.5m, 100));
|
|
|
|
fill = model.StopMarketFill(security, order);
|
|
|
|
Assert.AreEqual(order.Quantity, fill.FillQuantity);
|
|
Assert.AreEqual(order.StopPrice, fill.FillPrice);
|
|
Assert.AreEqual(OrderStatus.Filled, fill.Status);
|
|
}
|
|
|
|
[TestCase(100, 290.50)]
|
|
[TestCase(-100, 291.50)]
|
|
public void PerformsStopMarketFillWithTickTradeData(decimal orderQuantity, decimal stopPrice)
|
|
{
|
|
var time = new DateTime(2018, 9, 24, 9, 30, 0);
|
|
var timeKeeper = new TimeKeeper(time.ConvertToUtc(TimeZones.NewYork), TimeZones.NewYork);
|
|
|
|
var fillModel = new EquityFillModel();
|
|
var order = new StopMarketOrder(Symbols.SPY, orderQuantity, stopPrice, time.ConvertToUtc(TimeZones.NewYork));
|
|
|
|
var configTick = CreateTickConfig(Symbols.SPY);
|
|
var equity = CreateEquity(configTick);
|
|
|
|
equity.SetLocalTimeKeeper(timeKeeper.GetLocalTimeKeeper(TimeZones.NewYork));
|
|
|
|
// The order will not fill with this price
|
|
var tradeTick = new Tick { TickType = TickType.Trade, Time = time, Value = 291m };
|
|
equity.SetMarketPrice(tradeTick);
|
|
|
|
time += TimeSpan.FromMinutes(1);
|
|
timeKeeper.SetUtcDateTime(time.ConvertToUtc(TimeZones.NewYork));
|
|
|
|
var fill = fillModel.Fill(new FillModelParameters(
|
|
equity,
|
|
order,
|
|
new MockSubscriptionDataConfigProvider(configTick),
|
|
Time.OneHour,
|
|
null)).Single();
|
|
|
|
// Do not fill on stale data
|
|
Assert.AreEqual(0, fill.FillQuantity);
|
|
Assert.AreEqual(0, fill.FillPrice);
|
|
Assert.AreEqual(OrderStatus.None, fill.Status);
|
|
|
|
time += TimeSpan.FromMinutes(2);
|
|
timeKeeper.SetUtcDateTime(time.ConvertToUtc(TimeZones.NewYork));
|
|
|
|
// Create a series of price where the last value will not fill
|
|
// and the fill model need to use the minimum/maximum instead
|
|
var trades = new[] { 0.1m, -0.1m, 0m, 0.1m, 0m }
|
|
.Select(delta => new Tick
|
|
{
|
|
TickType = TickType.Trade,
|
|
Time = time,
|
|
Value = stopPrice - delta * Math.Sign(orderQuantity)
|
|
})
|
|
.ToList();
|
|
|
|
equity.Update(trades, typeof(Tick));
|
|
|
|
fill = fillModel.StopMarketFill(equity, order);
|
|
|
|
Assert.AreEqual(OrderStatus.Filled, fill.Status);
|
|
Assert.AreEqual(order.Quantity, fill.FillQuantity);
|
|
// Fill at the stop price because the equity price matches it
|
|
Assert.AreEqual(stopPrice, fill.FillPrice);
|
|
}
|
|
|
|
[TestCase(100, 290.50)]
|
|
[TestCase(-100, 291.50)]
|
|
public void StopMarketOrderDoesNotFillUsingQuoteBar(decimal orderQuantity, decimal stopPrice)
|
|
{
|
|
var time = new DateTime(2018, 9, 24, 9, 30, 0);
|
|
var timeKeeper = new TimeKeeper(time.ConvertToUtc(TimeZones.NewYork), TimeZones.NewYork);
|
|
|
|
var fillModel = new EquityFillModel();
|
|
var order = new StopMarketOrder(Symbols.SPY, orderQuantity, stopPrice, time.ConvertToUtc(TimeZones.NewYork));
|
|
|
|
var parameters = GetFillModelParameters(order);
|
|
|
|
var equity = parameters.Security;
|
|
equity.SetLocalTimeKeeper(timeKeeper.GetLocalTimeKeeper(TimeZones.NewYork));
|
|
|
|
// The order will not fill with these prices
|
|
var tradeBar = new TradeBar(time.AddMinutes(-10), Symbols.SPY, 291m, 291m, 291m, 291m, 12345);
|
|
equity.SetMarketPrice(tradeBar);
|
|
|
|
time += TimeSpan.FromMinutes(1);
|
|
timeKeeper.SetUtcDateTime(time.ConvertToUtc(TimeZones.NewYork));
|
|
|
|
var fill = fillModel.Fill(parameters).Single();
|
|
|
|
// Do not fill on stale data
|
|
Assert.AreEqual(0, fill.FillQuantity);
|
|
Assert.AreEqual(0, fill.FillPrice);
|
|
Assert.AreEqual(OrderStatus.None, fill.Status);
|
|
|
|
time += TimeSpan.FromMinutes(2);
|
|
timeKeeper.SetUtcDateTime(time.ConvertToUtc(TimeZones.NewYork));
|
|
|
|
var quoteBar = new QuoteBar(time, Symbols.SPY,
|
|
new Bar(290m, 292m, 289m, 291m), 12345,
|
|
new Bar(290m, 292m, 289m, 291m), 12345);
|
|
equity.SetMarketPrice(quoteBar);
|
|
|
|
fill = fillModel.StopMarketFill(equity, order);
|
|
|
|
// Stop market orders don't fill with QuoteBar:
|
|
Assert.AreEqual(0, fill.FillQuantity);
|
|
Assert.AreEqual(0, fill.FillPrice);
|
|
Assert.AreEqual(OrderStatus.None, fill.Status);
|
|
}
|
|
|
|
[TestCase(100, 290.50)]
|
|
[TestCase(-100, 291.50)]
|
|
public void StopMarketOrderDoesNotFillUsingTickTypeQuote(decimal orderQuantity, decimal stopPrice)
|
|
{
|
|
var time = new DateTime(2018, 9, 24, 9, 30, 0);
|
|
var timeKeeper = new TimeKeeper(time.ConvertToUtc(TimeZones.NewYork), TimeZones.NewYork);
|
|
|
|
var fillModel = new EquityFillModel();
|
|
var order = new StopMarketOrder(Symbols.SPY, orderQuantity, stopPrice, time.ConvertToUtc(TimeZones.NewYork));
|
|
|
|
var configTick = CreateTickConfig(Symbols.SPY);
|
|
var equity = CreateEquity(configTick);
|
|
|
|
equity.SetLocalTimeKeeper(timeKeeper.GetLocalTimeKeeper(TimeZones.NewYork));
|
|
|
|
// The order will not fill with this price
|
|
var tradeTick = new Tick { TickType = TickType.Trade, Time = time, Value = 291m };
|
|
equity.SetMarketPrice(tradeTick);
|
|
|
|
time += TimeSpan.FromMinutes(1);
|
|
timeKeeper.SetUtcDateTime(time.ConvertToUtc(TimeZones.NewYork));
|
|
|
|
var fill = fillModel.Fill(new FillModelParameters(
|
|
equity,
|
|
order,
|
|
new MockSubscriptionDataConfigProvider(configTick),
|
|
Time.OneHour,
|
|
null)).Single();
|
|
|
|
// Do not fill on stale data
|
|
Assert.AreEqual(0, fill.FillQuantity);
|
|
Assert.AreEqual(0, fill.FillPrice);
|
|
Assert.AreEqual(OrderStatus.None, fill.Status);
|
|
|
|
time += TimeSpan.FromMinutes(2);
|
|
timeKeeper.SetUtcDateTime(time.ConvertToUtc(TimeZones.NewYork));
|
|
|
|
var price = stopPrice - 0.1m * Math.Sign(orderQuantity);
|
|
var quoteTick = new Tick { TickType = TickType.Quote, Time = time, BidPrice = price, AskPrice = price };
|
|
equity.SetMarketPrice(quoteTick);
|
|
|
|
fill = fillModel.StopMarketFill(equity, order);
|
|
|
|
// Stop market orders don't fill with TickType.Quote:
|
|
Assert.AreEqual(0, fill.FillQuantity);
|
|
Assert.AreEqual(0, fill.FillPrice);
|
|
Assert.AreEqual(OrderStatus.None, fill.Status);
|
|
}
|
|
|
|
[TestCase(100, 290.50)]
|
|
[TestCase(-100, 291.50)]
|
|
public void StopMarketOrderFillsAtOpenWithUnfavourableGap(decimal orderQuantity, decimal stopPrice)
|
|
{
|
|
// See https://github.com/QuantConnect/Lean/issues/4545
|
|
var time = new DateTime(2018, 9, 24, 9, 30, 0);
|
|
var timeKeeper = new TimeKeeper(time.ConvertToUtc(TimeZones.NewYork), TimeZones.NewYork);
|
|
|
|
var fillModel = new EquityFillModel();
|
|
var order = new StopMarketOrder(Symbols.SPY, orderQuantity, stopPrice, time.ConvertToUtc(TimeZones.NewYork));
|
|
|
|
var parameters = GetFillModelParameters(order);
|
|
|
|
var equity = parameters.Security;
|
|
equity.SetLocalTimeKeeper(timeKeeper.GetLocalTimeKeeper(TimeZones.NewYork));
|
|
|
|
// The order will not fill with these prices
|
|
equity.SetMarketPrice(new TradeBar(time.AddMinutes(-10), Symbols.SPY, 291m, 291m, 291m, 291m, 12345));
|
|
|
|
var fill = fillModel.Fill(parameters).Single();
|
|
|
|
// Do not fill on stale data
|
|
Assert.AreEqual(0, fill.FillQuantity);
|
|
Assert.AreEqual(0, fill.FillPrice);
|
|
Assert.AreEqual(OrderStatus.None, fill.Status);
|
|
|
|
time += TimeSpan.FromMinutes(2);
|
|
timeKeeper.SetUtcDateTime(time.ConvertToUtc(TimeZones.NewYork));
|
|
|
|
// The Gap TradeBar has all prices below/above the stop price
|
|
var gapTradeBar = Math.Sign(orderQuantity) switch
|
|
{
|
|
1 => new TradeBar(time, Symbols.SPY, stopPrice + 1, stopPrice + 2, stopPrice + 1, stopPrice + 1, 12345),
|
|
-1 => new TradeBar(time, Symbols.SPY, stopPrice - 1, stopPrice - 1, stopPrice - 2, stopPrice - 1, 12345),
|
|
};
|
|
|
|
equity.SetMarketPrice(gapTradeBar);
|
|
|
|
fill = fillModel.StopMarketFill(equity, order);
|
|
|
|
// Fills at the open
|
|
Assert.AreEqual(gapTradeBar.Open, fill.FillPrice);
|
|
Assert.AreEqual(order.Quantity, fill.FillQuantity);
|
|
Assert.AreEqual(OrderStatus.Filled, fill.Status);
|
|
}
|
|
|
|
[TestCase(100, 291.50)]
|
|
[TestCase(-100, 290.50)]
|
|
public void StopMarketOrderDoesNotFillUsingDataBeforeSubmitTime(decimal orderQuantity, decimal stopPrice)
|
|
{
|
|
var time = new DateTime(2018, 9, 24, 9, 30, 0);
|
|
var timeKeeper = new TimeKeeper(time.ConvertToUtc(TimeZones.NewYork), TimeZones.NewYork);
|
|
|
|
var fillModel = new EquityFillModel();
|
|
var order = new StopMarketOrder(Symbols.SPY, orderQuantity, stopPrice, time.ConvertToUtc(TimeZones.NewYork));
|
|
|
|
var parameters = GetFillModelParameters(order);
|
|
|
|
var security = parameters.Security;
|
|
security.SetLocalTimeKeeper(timeKeeper.GetLocalTimeKeeper(TimeZones.NewYork));
|
|
|
|
var tradeBar = new TradeBar(time.AddMinutes(-10), Symbols.SPY, 290m, 292m, 289m, 291m, 12345);
|
|
security.SetMarketPrice(tradeBar);
|
|
|
|
time += TimeSpan.FromMinutes(1);
|
|
timeKeeper.SetUtcDateTime(time.ConvertToUtc(TimeZones.NewYork));
|
|
|
|
var fillForwardBar = (TradeBar)tradeBar.Clone(true);
|
|
security.SetMarketPrice(fillForwardBar);
|
|
|
|
var fill = fillModel.Fill(parameters).Single();
|
|
|
|
Assert.AreEqual(0, fill.FillQuantity);
|
|
Assert.AreEqual(0, fill.FillPrice);
|
|
Assert.AreEqual(OrderStatus.None, fill.Status);
|
|
|
|
time += TimeSpan.FromMinutes(1);
|
|
timeKeeper.SetUtcDateTime(time.ConvertToUtc(TimeZones.NewYork));
|
|
|
|
tradeBar = new TradeBar(time, Symbols.SPY, stopPrice - 0.01m * Math.Sign(orderQuantity), 292m, 289m, stopPrice, 12345);
|
|
security.SetMarketPrice(tradeBar);
|
|
|
|
fill = fillModel.StopMarketFill(security, order);
|
|
|
|
Assert.AreEqual(orderQuantity, fill.FillQuantity);
|
|
Assert.AreEqual(stopPrice, fill.FillPrice);
|
|
Assert.AreEqual(OrderStatus.Filled, fill.Status);
|
|
}
|
|
}
|
|
}
|