Files
2026-07-13 13:02:50 +08:00

171 lines
7.9 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.Collections.Generic;
using QuantConnect.Util;
using QuantConnect.Data;
using QuantConnect.Securities;
using QuantConnect.Orders.Fees;
namespace QuantConnect.Orders.Fills
{
/// <summary>
/// Represents the fill model used to simulate order fills for futures
/// </summary>
public class FutureFillModel : ImmediateFillModel
{
/// <summary>
/// Default market fill model for the base security class. Fills at the last traded price.
/// </summary>
/// <param name="asset">Security asset we're filling</param>
/// <param name="order">Order packet to model</param>
/// <returns>Order fill information detailing the average price and quantity filled.</returns>
public override OrderEvent MarketFill(Security asset, MarketOrder order)
{
//Default order event to return.
var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone);
var fill = new OrderEvent(order, utcTime, OrderFee.Zero);
if (order.Status == OrderStatus.Canceled) return fill;
// make sure the exchange is open on regular/extended market hours before filling
if (!IsExchangeOpen(asset, true)) return fill;
var prices = GetPricesCheckingPythonWrapper(asset, order.Direction);
var pricesEndTimeUtc = prices.EndTime.ConvertToUtc(asset.Exchange.TimeZone);
// If the order would be filled on stale (fill-forward / already past) data, wait for fresh data instead of
// filling at a stale price when the latest data is more than one resolution bar behind the order submission
// time (e.g. the first bar of the session after the open, or an intraday data gap). Otherwise fill on the
// stale price with a warning.
// Fetched lazily and reused across the stale-data check and the fill-price resolution so the subscription
// configs are resolved at most once per fill.
List<SubscriptionDataConfig> subscriptionConfigs = null;
if (pricesEndTimeUtc.Add(Parameters.StalePriceTimeSpan) < order.Time)
{
subscriptionConfigs = GetSubscriptionDataConfigs(asset);
if (ShouldWaitForFreshDataOnStale(asset, pricesEndTimeUtc, order.Time, subscriptionConfigs))
{
return fill;
}
fill.Message = Messages.FillModel.FilledAtStalePrice(asset, prices);
}
//Order [fill]price for a market order model is the current security price (or the bar open if the order
//was resting before this bar opened, see GetMarketFillPrice)
fill.FillPrice = GetMarketFillPrice(asset, order, prices, subscriptionConfigs);
fill.Status = OrderStatus.Filled;
//Calculate the model slippage: e.g. 0.01c
var slip = asset.SlippageModel.GetSlippageApproximation(asset, order);
//Apply slippage
switch (order.Direction)
{
case OrderDirection.Buy:
fill.FillPrice += slip;
break;
case OrderDirection.Sell:
fill.FillPrice -= slip;
break;
}
// assume the order completely filled
fill.FillQuantity = order.Quantity;
return fill;
}
/// <summary>
/// Stop fill model implementation for Future.
/// </summary>
/// <param name="asset">Security asset we're filling</param>
/// <param name="order">Order packet to model</param>
/// <returns>Order fill information detailing the average price and quantity filled.</returns>
/// <remarks>
/// A Stop order is an instruction to submit a buy or sell market order
/// if and when the user-specified stop trigger price is attained or penetrated.
///
/// A Sell Stop order is always placed below the current market price.
/// We assume a fluid/continuous, high volume market. Therefore, it is filled at the stop trigger price
/// if the current low price of trades is less than or equal to this price.
///
/// A Buy Stop order is always placed above the current market price.
/// We assume a fluid, high volume market. Therefore, it is filled at the stop trigger price
/// if the current high price of trades is greater or equal than this price.
///
/// The continuous market assumption is not valid if the market opens with an unfavorable gap.
/// In this case, a new bar opens below/above the stop trigger price, and the order is filled with the opening price.
/// <seealso cref="MarketFill(Security, MarketOrder)"/></remarks>
public override OrderEvent StopMarketFill(Security asset, StopMarketOrder order)
{
//Default order event to return.
var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone);
var fill = new OrderEvent(order, utcTime, OrderFee.Zero);
//If its cancelled don't need anymore checks:
if (order.Status == OrderStatus.Canceled) return fill;
// Fill only if open or extended
if (!IsExchangeOpen(asset, GetSubscriptionDataConfigs(asset).IsExtendedMarketHours()))
{
return fill;
}
//Get the range of prices in the last bar:
var prices = GetPricesCheckingPythonWrapper(asset, order.Direction);
var pricesEndTime = prices.EndTime.ConvertToUtc(asset.Exchange.TimeZone);
// do not fill on stale data
if (pricesEndTime <= order.Time) return fill;
//Calculate the model slippage: e.g. 0.01c
var slip = asset.SlippageModel.GetSlippageApproximation(asset, order);
//Check if the Stop Order was filled: opposite to a limit order
switch (order.Direction)
{
case OrderDirection.Sell:
//-> 1.1 Sell Stop: If Price below setpoint, Sell:
if (prices.Low < order.StopPrice)
{
fill.Status = OrderStatus.Filled;
// Assuming worse case scenario fill - fill at lowest of the stop & asset price.
fill.FillPrice = Math.Min(order.StopPrice, prices.Current - slip);
// assume the order completely filled
fill.FillQuantity = order.Quantity;
}
break;
case OrderDirection.Buy:
//-> 1.2 Buy Stop: If Price Above Setpoint, Buy:
if (prices.High > order.StopPrice)
{
fill.Status = OrderStatus.Filled;
// Assuming worse case scenario fill - fill at highest of the stop & asset price.
fill.FillPrice = Math.Max(order.StopPrice, prices.Current + slip);
// assume the order completely filled
fill.FillQuantity = order.Quantity;
}
break;
}
return fill;
}
}
}