Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Common/Messages/Messages.Orders.Fills.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ public static string MarketNeverCloses(Securities.Security security, OrderType o
return Invariant($"Market never closes for this symbol {security.Symbol}, can no submit a {nameof(orderType)} order.");
}

/// <summary>
/// Returns a string message saying that the order was filled using the open price due to a favorable gap
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string FilledWithOpenDueToFavorableGap(Securities.Security security, Prices prices)
{
return Invariant($@"Due to a favorable gap at {prices.EndTime.ToStringInvariant()} {security.Exchange.TimeZone}, order filled using the open price ({prices.Open})");
}
/// <summary>
/// Returns a string message containing the given subscribedTypes
/// </summary>
Expand Down
56 changes: 43 additions & 13 deletions Common/Orders/Fills/EquityFillModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,53 +304,83 @@ public override OrderEvent StopLimitFill(Security asset, StopLimitOrder order)
if (pricesEndTime <= order.Time) return fill;

//Check if the Stop Order was filled: opposite to a limit order
var triggeredOnEarlierBar = order.StopTriggered;
switch (order.Direction)
{
case OrderDirection.Buy:
//-> 1.2 Buy Stop: If Price Above Setpoint, Buy:
if (prices.High > order.StopPrice || order.StopTriggered)
if (prices.High > order.StopPrice || triggeredOnEarlierBar)
{
if (!order.StopTriggered)
if (!triggeredOnEarlierBar)
{
order.StopTriggered = true;
Parameters.OnOrderUpdated(order);
}

// Fill the limit order, using closing price of bar:
// Note > Can't use minimum price, because no way to be sure minimum wasn't before the stop triggered.
if (prices.Current < order.LimitPrice)
if (triggeredOnEarlierBar)
{
// The entire bar is post-trigger, so the limit leg behaves as a resting limit order.
var tradeBar = GetBestEffortTradeBar(asset, order.Time);
if (tradeBar != null && tradeBar.Low < order.LimitPrice)
{
fill.Status = OrderStatus.Filled;
fill.FillPrice = order.LimitPrice;
fill.FillQuantity = order.Quantity;

if (tradeBar.Open < order.LimitPrice)
{
fill.FillPrice = tradeBar.Open;
fill.Message = Messages.EquityFillModel.FilledWithOpenDueToFavorableGap(asset, tradeBar);
}
}
}
else if (prices.Current < order.LimitPrice)
{
// On the trigger bar, preserve the conservative close-based test.
fill.Status = OrderStatus.Filled;
fill.FillPrice = Math.Min(prices.High, order.LimitPrice);
// assume the order completely filled
fill.FillQuantity = order.Quantity;
}
}
break;

case OrderDirection.Sell:
//-> 1.1 Sell Stop: If Price below setpoint, Sell:
if (prices.Low < order.StopPrice || order.StopTriggered)
if (prices.Low < order.StopPrice || triggeredOnEarlierBar)
{
if (!order.StopTriggered)
if (!triggeredOnEarlierBar)
{
order.StopTriggered = true;
Parameters.OnOrderUpdated(order);
}

// Fill the limit order, using minimum price of the bar
// Note > Can't use minimum price, because no way to be sure minimum wasn't before the stop triggered.
if (prices.Current > order.LimitPrice)
if (triggeredOnEarlierBar)
{
// The entire bar is post-trigger, so the limit leg behaves as a resting limit order.
var tradeBar = GetBestEffortTradeBar(asset, order.Time);
if (tradeBar != null && tradeBar.High > order.LimitPrice)
{
fill.Status = OrderStatus.Filled;
fill.FillPrice = order.LimitPrice;
fill.FillQuantity = order.Quantity;

if (tradeBar.Open > order.LimitPrice)
{
fill.FillPrice = tradeBar.Open;
fill.Message = Messages.EquityFillModel.FilledWithOpenDueToFavorableGap(asset, tradeBar);
}
}
}
else if (prices.Current > order.LimitPrice)
{
// On the trigger bar, preserve the conservative close-based test.
fill.Status = OrderStatus.Filled;
fill.FillPrice = Math.Max(prices.Low, order.LimitPrice);
// assume the order completely filled
fill.FillQuantity = order.Quantity;
}
}
break;
}

return fill;
}

Expand Down
54 changes: 41 additions & 13 deletions Common/Orders/Fills/FillModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -506,53 +506,81 @@ public virtual OrderEvent StopLimitFill(Security asset, StopLimitOrder order)
if (pricesEndTime <= order.Time) return fill;

//Check if the Stop Order was filled: opposite to a limit order
var triggeredOnEarlierBar = order.StopTriggered;
switch (order.Direction)
{
case OrderDirection.Buy:
//-> 1.2 Buy Stop: If Price Above Setpoint, Buy:
if (prices.High > order.StopPrice || order.StopTriggered)
if (prices.High > order.StopPrice || triggeredOnEarlierBar)
{
if (!order.StopTriggered)
if (!triggeredOnEarlierBar)
{
order.StopTriggered = true;
Parameters.OnOrderUpdated(order);
}

// Fill the limit order, using closing price of bar:
// Note > Can't use minimum price, because no way to be sure minimum wasn't before the stop triggered.
if (prices.Current < order.LimitPrice)
if (triggeredOnEarlierBar)
{
// The entire bar is post-trigger, so the limit leg behaves as a resting limit order.
if (prices.Low < order.LimitPrice)
{
fill.Status = OrderStatus.Filled;
fill.FillPrice = order.LimitPrice;
fill.FillQuantity = order.Quantity;

if (prices.Open < order.LimitPrice)
{
fill.FillPrice = prices.Open;
fill.Message = Messages.FillModel.FilledWithOpenDueToFavorableGap(asset, prices);
}
}
}
else if (prices.Current < order.LimitPrice)
{
// On the trigger bar, preserve the conservative close-based test.
fill.Status = OrderStatus.Filled;
fill.FillPrice = Math.Min(prices.High, order.LimitPrice);
// assume the order completely filled
fill.FillQuantity = order.Quantity;
}
}
break;

case OrderDirection.Sell:
//-> 1.1 Sell Stop: If Price below setpoint, Sell:
if (prices.Low < order.StopPrice || order.StopTriggered)
if (prices.Low < order.StopPrice || triggeredOnEarlierBar)
{
if (!order.StopTriggered)
if (!triggeredOnEarlierBar)
{
order.StopTriggered = true;
Parameters.OnOrderUpdated(order);
}

// Fill the limit order, using minimum price of the bar
// Note > Can't use minimum price, because no way to be sure minimum wasn't before the stop triggered.
if (prices.Current > order.LimitPrice)
if (triggeredOnEarlierBar)
{
// The entire bar is post-trigger, so the limit leg behaves as a resting limit order.
if (prices.High > order.LimitPrice)
{
fill.Status = OrderStatus.Filled;
fill.FillPrice = order.LimitPrice;
fill.FillQuantity = order.Quantity;

if (prices.Open > order.LimitPrice)
{
fill.FillPrice = prices.Open;
fill.Message = Messages.FillModel.FilledWithOpenDueToFavorableGap(asset, prices);
}
}
}
else if (prices.Current > order.LimitPrice)
{
// On the trigger bar, preserve the conservative close-based test.
fill.Status = OrderStatus.Filled;
fill.FillPrice = Math.Max(prices.Low, order.LimitPrice);
// assume the order completely filled
fill.FillQuantity = order.Quantity;
}
}
break;
}

return fill;
}

Expand Down
Loading