Lean  $LEAN_TAG$
FillModel.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Collections.Generic;
18 using System.Linq;
20 using QuantConnect.Python;
23 using QuantConnect.Util;
24 
26 {
27  /// <summary>
28  /// Provides a base class for all fill models
29  /// </summary>
30  public class FillModel : IFillModel
31  {
32  /// <summary>
33  /// The parameters instance to be used by the different XxxxFill() implementations
34  /// </summary>
35  protected FillModelParameters Parameters { get; set; }
36 
37  /// <summary>
38  /// This is required due to a limitation in PythonNet to resolved overriden methods.
39  /// When Python calls a C# method that calls a method that's overriden in python it won't
40  /// run the python implementation unless the call is performed through python too.
41  /// </summary>
42  protected FillModelPythonWrapper PythonWrapper { get; set; }
43 
44  /// <summary>
45  /// Used to set the <see cref="FillModelPythonWrapper"/> instance if any
46  /// </summary>
47  public void SetPythonWrapper(FillModelPythonWrapper pythonWrapper)
48  {
49  PythonWrapper = pythonWrapper;
50  }
51 
52  /// <summary>
53  /// Return an order event with the fill details
54  /// </summary>
55  /// <param name="parameters">A <see cref="FillModelParameters"/> object containing the security and order</param>
56  /// <returns>Order fill information detailing the average price and quantity filled.</returns>
57  public virtual Fill Fill(FillModelParameters parameters)
58  {
59  // Important: setting the parameters is required because it is
60  // consumed by the different XxxxFill() implementations
61  Parameters = parameters;
62 
63  var orderEvents = new List<OrderEvent>(1);
64  switch (parameters.Order.Type)
65  {
66  case OrderType.Market:
67  orderEvents.Add(PythonWrapper != null
68  ? PythonWrapper.MarketFill(parameters.Security, parameters.Order as MarketOrder)
69  : MarketFill(parameters.Security, parameters.Order as MarketOrder));
70  break;
71  case OrderType.Limit:
72  orderEvents.Add(PythonWrapper != null
73  ? PythonWrapper.LimitFill(parameters.Security, parameters.Order as LimitOrder)
74  : LimitFill(parameters.Security, parameters.Order as LimitOrder));
75  break;
76  case OrderType.LimitIfTouched:
77  orderEvents.Add(PythonWrapper != null
78  ? PythonWrapper.LimitIfTouchedFill(parameters.Security, parameters.Order as LimitIfTouchedOrder)
79  : LimitIfTouchedFill(parameters.Security, parameters.Order as LimitIfTouchedOrder));
80  break;
81  case OrderType.StopMarket:
82  orderEvents.Add(PythonWrapper != null
83  ? PythonWrapper.StopMarketFill(parameters.Security, parameters.Order as StopMarketOrder)
84  : StopMarketFill(parameters.Security, parameters.Order as StopMarketOrder));
85  break;
86  case OrderType.TrailingStop:
87  orderEvents.Add(PythonWrapper != null
88  ? PythonWrapper.TrailingStopFill(parameters.Security, parameters.Order as TrailingStopOrder)
89  : TrailingStopFill(parameters.Security, parameters.Order as TrailingStopOrder));
90  break;
91  case OrderType.StopLimit:
92  orderEvents.Add(PythonWrapper != null
93  ? PythonWrapper.StopLimitFill(parameters.Security, parameters.Order as StopLimitOrder)
94  : StopLimitFill(parameters.Security, parameters.Order as StopLimitOrder));
95  break;
96  case OrderType.MarketOnOpen:
97  orderEvents.Add(PythonWrapper != null
98  ? PythonWrapper.MarketOnOpenFill(parameters.Security, parameters.Order as MarketOnOpenOrder)
99  : MarketOnOpenFill(parameters.Security, parameters.Order as MarketOnOpenOrder));
100  break;
101  case OrderType.MarketOnClose:
102  orderEvents.Add(PythonWrapper != null
103  ? PythonWrapper.MarketOnCloseFill(parameters.Security, parameters.Order as MarketOnCloseOrder)
104  : MarketOnCloseFill(parameters.Security, parameters.Order as MarketOnCloseOrder));
105  break;
106  case OrderType.ComboMarket:
107  orderEvents = PythonWrapper != null
108  ? PythonWrapper.ComboMarketFill(parameters.Order, parameters)
109  : ComboMarketFill(parameters.Order, parameters);
110  break;
111  case OrderType.ComboLimit:
112  orderEvents = PythonWrapper != null
113  ? PythonWrapper.ComboLimitFill(parameters.Order, parameters)
114  : ComboLimitFill(parameters.Order, parameters);
115  break;
116  case OrderType.ComboLegLimit:
117  orderEvents = PythonWrapper != null
118  ? PythonWrapper.ComboLegLimitFill(parameters.Order, parameters)
119  : ComboLegLimitFill(parameters.Order, parameters);
120  break;
121  default:
122  throw new ArgumentOutOfRangeException();
123  }
124  return new Fill(orderEvents);
125  }
126 
127 
128  /// <summary>
129  /// Default combo market fill model for the base security class. Fills at the last traded price for each leg.
130  /// </summary>
131  /// <param name="order">Order to fill</param>
132  /// <param name="parameters">Fill parameters for the order</param>
133  /// <returns>Order fill information detailing the average price and quantity filled for each leg. If any of the fills fails, none of the orders will be filled and the returned list will be empty</returns>
134  public virtual List<OrderEvent> ComboMarketFill(Order order, FillModelParameters parameters)
135  {
136  var fills = new List<OrderEvent>(parameters.SecuritiesForOrders.Count);
137  foreach (var kvp in parameters.SecuritiesForOrders.OrderBy(x => x.Key.Id))
138  {
139  var targetOrder = kvp.Key;
140  var security = kvp.Value;
141  var fill = InternalMarketFill(security, targetOrder, targetOrder.Quantity);
142  if (fill.Status != OrderStatus.Filled)
143  {
144  return new List<OrderEvent>();
145  }
146 
147  fills.Add(fill);
148  }
149 
150  return fills;
151  }
152 
153  /// <summary>
154  /// Default combo limit fill model for the base security class. Fills at the sum of prices for the assets of every leg.
155  /// </summary>
156  /// <param name="order">Order to fill</param>
157  /// <param name="parameters">Fill parameters for the order</param>
158  /// <returns>Order fill information detailing the average price and quantity filled for each leg. If any of the fills fails, none of the orders will be filled and the returned list will be empty</returns>
159  public virtual List<OrderEvent> ComboLimitFill(Order order, FillModelParameters parameters)
160  {
161  // aggregate the prices from all the securities
162  var fillParameters = new List<ComboLimitOrderLegParameters>(parameters.SecuritiesForOrders.Count);
163  foreach (var kvp in parameters.SecuritiesForOrders.OrderBy(x => x.Key.Id))
164  {
165  var targetOrder = kvp.Key;
166  var security = kvp.Value;
167  var prices = GetPricesCheckingPythonWrapper(security, targetOrder.Direction);
168 
169  if (prices.EndTime.ConvertToUtc(security.Exchange.TimeZone) < targetOrder.Time)
170  {
171  // do not fill on stale data
172  return new List<OrderEvent>();
173  }
174 
175  fillParameters.Add(new ComboLimitOrderLegParameters
176  {
177  Security = security,
178  Order = targetOrder,
179  Prices = prices
180  });
181  }
182 
183  var currentPrice = fillParameters.Aggregate(0m, (accumulatedPrice, p) => accumulatedPrice + p.Price);
184  var limitPrice = order.GroupOrderManager.LimitPrice;
185 
186  var fills = new List<OrderEvent>(fillParameters.Count);
187 
188  switch (order.GroupOrderManager.Direction)
189  {
190  case OrderDirection.Buy:
191  //Buy limit seeks lowest price
192  if (currentPrice < limitPrice)
193  {
194  for (var i = 0; i < fillParameters.Count; i++)
195  {
196  var targetParameters = fillParameters[i];
197  var utcTime = targetParameters.Security.LocalTime.ConvertToUtc(targetParameters.Security.Exchange.TimeZone);
198  var fill = new OrderEvent(targetParameters.Order, utcTime, OrderFee.Zero);
199 
200  //Set order fill:
201  fill.Status = OrderStatus.Filled;
202  fill.FillPrice = targetParameters.Prices.Low;
203  // assume the order completely filled
204  fill.FillQuantity = targetParameters.Order.Quantity;
205 
206  fills.Add(fill);
207  }
208  }
209  break;
210 
211  case OrderDirection.Sell:
212  //Sell limit seeks highest price possible
213  if (currentPrice > limitPrice)
214  {
215  for (var i = 0; i < fillParameters.Count; i++)
216  {
217  var targetParameters = fillParameters[i];
218  var utcTime = targetParameters.Security.LocalTime.ConvertToUtc(targetParameters.Security.Exchange.TimeZone);
219  var fill = new OrderEvent(targetParameters.Order, utcTime, OrderFee.Zero);
220 
221  //Set order fill:
222  fill.Status = OrderStatus.Filled;
223  fill.FillPrice = targetParameters.Prices.High;
224  // assume the order completely filled
225  fill.FillQuantity = targetParameters.Order.Quantity;
226 
227  fills.Add(fill);
228  }
229  }
230  break;
231  }
232 
233  return fills;
234  }
235 
236  /// <summary>
237  /// Default combo limit fill model for the base security class. Fills at the limit price for each leg
238  /// </summary>
239  /// <param name="order">Order to fill</param>
240  /// <param name="parameters">Fill parameters for the order</param>
241  /// <returns>Order fill information detailing the average price and quantity filled for each leg. If any of the fills fails, none of the orders will be filled and the returned list will be empty</returns>
242  public virtual List<OrderEvent> ComboLegLimitFill(Order order, FillModelParameters parameters)
243  {
244  var fills = new List<OrderEvent>(order.GroupOrderManager.OrderIds.Count);
245 
246  foreach (var kvp in parameters.SecuritiesForOrders.OrderBy(x => x.Key.Id))
247  {
248  var targetOrder = kvp.Key;
249  var security = kvp.Value;
250 
251  var fill = InternalLimitFill(security, targetOrder, (targetOrder as ComboLegLimitOrder).LimitPrice,
252  targetOrder.Quantity);
253 
254  if (fill.Status != OrderStatus.Filled)
255  {
256  return new List<OrderEvent>();
257  }
258 
259  fills.Add(fill);
260  }
261 
262  return fills;
263  }
264 
265  /// <summary>
266  /// Default market fill model for the base security class. Fills at the last traded price.
267  /// </summary>
268  /// <param name="asset">Security asset we're filling</param>
269  /// <param name="order">Order packet to model</param>
270  /// <returns>Order fill information detailing the average price and quantity filled.</returns>
271  public virtual OrderEvent MarketFill(Security asset, MarketOrder order)
272  {
273  return InternalMarketFill(asset, order, order.Quantity);
274  }
275 
276 
277  /// <summary>
278  /// Default market fill model for the base security class. Fills at the last traded price.
279  /// </summary>
280  private OrderEvent InternalMarketFill(Security asset, Order order, decimal quantity)
281  {
282  //Default order event to return.
283  var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone);
284  var fill = new OrderEvent(order, utcTime, OrderFee.Zero);
285 
286  if (order.Status == OrderStatus.Canceled) return fill;
287 
288  // make sure the exchange is open/normal market hours before filling
289  if (!IsExchangeOpen(asset, false)) return fill;
290 
291  var orderDirection = order.Direction;
292  var prices = GetPricesCheckingPythonWrapper(asset, orderDirection);
293  var pricesEndTimeUtc = prices.EndTime.ConvertToUtc(asset.Exchange.TimeZone);
294 
295  // if the order is filled on stale (fill-forward) data, set a warning message on the order event
296  if (pricesEndTimeUtc.Add(Parameters.StalePriceTimeSpan) < order.Time)
297  {
298  fill.Message = Messages.FillModel.FilledAtStalePrice(asset, prices);
299  }
300 
301  //Order [fill]price for a market order model is the current security price
302  fill.FillPrice = prices.Current;
303  fill.Status = OrderStatus.Filled;
304 
305  //Calculate the model slippage: e.g. 0.01c
306  var slip = asset.SlippageModel.GetSlippageApproximation(asset, order);
307 
308  //Apply slippage
309  switch (orderDirection)
310  {
311  case OrderDirection.Buy:
312  fill.FillPrice += slip;
313  break;
314  case OrderDirection.Sell:
315  fill.FillPrice -= slip;
316  break;
317  }
318 
319  // assume the order completely filled
320  fill.FillQuantity = quantity;
321 
322  return fill;
323  }
324 
325  /// <summary>
326  /// Default stop fill model implementation in base class security. (Stop Market Order Type)
327  /// </summary>
328  /// <param name="asset">Security asset we're filling</param>
329  /// <param name="order">Order packet to model</param>
330  /// <returns>Order fill information detailing the average price and quantity filled.</returns>
331  /// <seealso cref="MarketFill(Security, MarketOrder)"/>
332  public virtual OrderEvent StopMarketFill(Security asset, StopMarketOrder order)
333  {
334  //Default order event to return.
335  var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone);
336  var fill = new OrderEvent(order, utcTime, OrderFee.Zero);
337 
338  //If its cancelled don't need anymore checks:
339  if (order.Status == OrderStatus.Canceled) return fill;
340 
341  // make sure the exchange is open/normal market hours before filling
342  if (!IsExchangeOpen(asset, false)) return fill;
343 
344  //Get the range of prices in the last bar:
345  var prices = GetPricesCheckingPythonWrapper(asset, order.Direction);
346  var pricesEndTime = prices.EndTime.ConvertToUtc(asset.Exchange.TimeZone);
347 
348  // do not fill on stale data
349  if (pricesEndTime <= order.Time) return fill;
350 
351  //Calculate the model slippage: e.g. 0.01c
352  var slip = asset.SlippageModel.GetSlippageApproximation(asset, order);
353 
354  //Check if the Stop Order was filled: opposite to a limit order
355  switch (order.Direction)
356  {
357  case OrderDirection.Sell:
358  //-> 1.1 Sell Stop: If Price below setpoint, Sell:
359  if (prices.Low < order.StopPrice)
360  {
361  fill.Status = OrderStatus.Filled;
362  // Assuming worse case scenario fill - fill at lowest of the stop & asset price.
363  fill.FillPrice = Math.Min(order.StopPrice, prices.Current - slip);
364  // assume the order completely filled
365  fill.FillQuantity = order.Quantity;
366  }
367  break;
368 
369  case OrderDirection.Buy:
370  //-> 1.2 Buy Stop: If Price Above Setpoint, Buy:
371  if (prices.High > order.StopPrice)
372  {
373  fill.Status = OrderStatus.Filled;
374  // Assuming worse case scenario fill - fill at highest of the stop & asset price.
375  fill.FillPrice = Math.Max(order.StopPrice, prices.Current + slip);
376  // assume the order completely filled
377  fill.FillQuantity = order.Quantity;
378  }
379  break;
380  }
381 
382  return fill;
383  }
384 
385  /// <summary>
386  /// Default trailing stop fill model implementation in base class security. (Trailing Stop Order Type)
387  /// </summary>
388  /// <param name="asset">Security asset we're filling</param>
389  /// <param name="order">Order packet to model</param>
390  /// <returns>Order fill information detailing the average price and quantity filled.</returns>
392  {
393  // Default order event to return.
394  var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone);
395  var fill = new OrderEvent(order, utcTime, OrderFee.Zero);
396 
397  // If its canceled don't need anymore checks:
398  if (order.Status == OrderStatus.Canceled) return fill;
399 
400  // Make sure the exchange is open/normal market hours before filling
401  if (!IsExchangeOpen(asset, false)) return fill;
402 
403  // Get the range of prices in the last bar:
404  var prices = GetPricesCheckingPythonWrapper(asset, order.Direction);
405  var pricesEndTime = prices.EndTime.ConvertToUtc(asset.Exchange.TimeZone);
406 
407  // Do not fill on stale data
408  if (pricesEndTime <= order.Time) return fill;
409 
410  // Calculate the model slippage: e.g. 0.01c
411  var slip = asset.SlippageModel.GetSlippageApproximation(asset, order);
412 
413  switch (order.Direction)
414  {
415  case OrderDirection.Sell:
416  // Fill sell if market price drops below stop price
417  if (prices.Low <= order.StopPrice)
418  {
419  fill.Status = OrderStatus.Filled;
420  // Assuming worse case scenario fill - fill at lowest of the stop & asset price.
421  fill.FillPrice = Math.Min(order.StopPrice, prices.Current - slip);
422  // assume the order completely filled
423  fill.FillQuantity = order.Quantity;
424  }
425  break;
426 
427  case OrderDirection.Buy:
428  // Fill buy if market price rises above stop price
429  if (prices.High >= order.StopPrice)
430  {
431  fill.Status = OrderStatus.Filled;
432  // Assuming worse case scenario fill - fill at highest of the stop & asset price.
433  fill.FillPrice = Math.Max(order.StopPrice, prices.Current + slip);
434  // assume the order completely filled
435  fill.FillQuantity = order.Quantity;
436  }
437  break;
438  }
439 
440  // Update the stop price:
441  // NOTE: Doing this after attempting to fill the order in the following cases:
442  // - Sell: if low < stop price, order is filled. If we were to update the stop price before and it is moved towards the high price
443  // placing the stop price above the low price, it will not trigger a fill.
444  // - Buy: if high > stop price, order is filled. If we were to update the stop price before and it is moved towards the low price
445  // placing the stop price below the high price, it will not trigger a fill.
446  if (fill.Status != OrderStatus.Filled &&
447  TrailingStopOrder.TryUpdateStopPrice(order.Direction == OrderDirection.Sell ? prices.High : prices.Low, order.StopPrice,
448  order.TrailingAmount, order.TrailingAsPercentage, order.Direction, out var updatedStopPrice))
449  {
450  order.StopPrice = updatedStopPrice;
451  Parameters.OnOrderUpdated(order);
452  }
453 
454  return fill;
455  }
456 
457  /// <summary>
458  /// Default stop limit fill model implementation in base class security. (Stop Limit Order Type)
459  /// </summary>
460  /// <param name="asset">Security asset we're filling</param>
461  /// <param name="order">Order packet to model</param>
462  /// <returns>Order fill information detailing the average price and quantity filled.</returns>
463  /// <seealso cref="StopMarketFill(Security, StopMarketOrder)"/>
464  /// <remarks>
465  /// There is no good way to model limit orders with OHLC because we never know whether the market has
466  /// gapped past our fill price. We have to make the assumption of a fluid, high volume market.
467  ///
468  /// Stop limit orders we also can't be sure of the order of the H - L values for the limit fill. The assumption
469  /// was made the limit fill will be done with closing price of the bar after the stop has been triggered..
470  /// </remarks>
471  public virtual OrderEvent StopLimitFill(Security asset, StopLimitOrder order)
472  {
473  //Default order event to return.
474  var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone);
475  var fill = new OrderEvent(order, utcTime, OrderFee.Zero);
476 
477  //If its cancelled don't need anymore checks:
478  if (order.Status == OrderStatus.Canceled) return fill;
479 
480  // make sure the exchange is open before filling -- allow pre/post market fills to occur
481  if (!IsExchangeOpen(asset))
482  {
483  return fill;
484  }
485 
486  //Get the range of prices in the last bar:
487  var prices = GetPricesCheckingPythonWrapper(asset, order.Direction);
488  var pricesEndTime = prices.EndTime.ConvertToUtc(asset.Exchange.TimeZone);
489 
490  // do not fill on stale data
491  if (pricesEndTime <= order.Time) return fill;
492 
493  //Check if the Stop Order was filled: opposite to a limit order
494  switch (order.Direction)
495  {
496  case OrderDirection.Buy:
497  //-> 1.2 Buy Stop: If Price Above Setpoint, Buy:
498  if (prices.High > order.StopPrice || order.StopTriggered)
499  {
500  if (!order.StopTriggered)
501  {
502  order.StopTriggered = true;
503  Parameters.OnOrderUpdated(order);
504  }
505 
506  // Fill the limit order, using closing price of bar:
507  // Note > Can't use minimum price, because no way to be sure minimum wasn't before the stop triggered.
508  if (prices.Current < order.LimitPrice)
509  {
510  fill.Status = OrderStatus.Filled;
511  fill.FillPrice = Math.Min(prices.High, order.LimitPrice);
512  // assume the order completely filled
513  fill.FillQuantity = order.Quantity;
514  }
515  }
516  break;
517 
518  case OrderDirection.Sell:
519  //-> 1.1 Sell Stop: If Price below setpoint, Sell:
520  if (prices.Low < order.StopPrice || order.StopTriggered)
521  {
522  if (!order.StopTriggered)
523  {
524  order.StopTriggered = true;
525  Parameters.OnOrderUpdated(order);
526  }
527 
528  // Fill the limit order, using minimum price of the bar
529  // Note > Can't use minimum price, because no way to be sure minimum wasn't before the stop triggered.
530  if (prices.Current > order.LimitPrice)
531  {
532  fill.Status = OrderStatus.Filled;
533  fill.FillPrice = Math.Max(prices.Low, order.LimitPrice);
534  // assume the order completely filled
535  fill.FillQuantity = order.Quantity;
536  }
537  }
538  break;
539  }
540 
541  return fill;
542  }
543 
544  /// <summary>
545  /// Default limit if touched fill model implementation in base class security. (Limit If Touched Order Type)
546  /// </summary>
547  /// <param name="asset"></param>
548  /// <param name="order"></param>
549  /// <returns>Order fill information detailing the average price and quantity filled.</returns>
550  /// <remarks>
551  /// There is no good way to model limit orders with OHLC because we never know whether the market has
552  /// gapped past our fill price. We have to make the assumption of a fluid, high volume market.
553  ///
554  /// With Limit if Touched orders, whether or not a trigger is surpassed is determined by the high (low)
555  /// of the previous tradebar when making a sell (buy) request. Following the behaviour of
556  /// <see cref="StopLimitFill"/>, current quote information is used when determining fill parameters
557  /// (e.g., price, quantity) as the tradebar containing the incoming data is not yet consolidated.
558  /// This conservative approach, however, can lead to trades not occuring as would be expected when
559  /// compared to future consolidated data.
560  /// </remarks>
562  {
563  //Default order event to return.
564  var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone);
565  var fill = new OrderEvent(order, utcTime, OrderFee.Zero);
566 
567  //If its cancelled don't need anymore checks:
568  if (order.Status == OrderStatus.Canceled) return fill;
569 
570  // Fill only if open or extended
571  if (!IsExchangeOpen(asset))
572  {
573  return fill;
574  }
575 
576  // Get the range of prices in the last bar:
577  var tradeHigh = 0m;
578  var tradeLow = 0m;
579  var pricesEndTime = DateTime.MinValue;
580 
581  var subscribedTypes = GetSubscribedTypes(asset);
582 
583  if (subscribedTypes.Contains(typeof(Tick)))
584  {
585  var trade = GetPricesCheckingPythonWrapper(asset, order.Direction);
586 
587  if (trade != null)
588  {
589  tradeHigh = trade.Current;
590  tradeLow = trade.Current;
591  pricesEndTime = trade.EndTime.ConvertToUtc(asset.Exchange.TimeZone);
592  }
593  }
594 
595  else if (subscribedTypes.Contains(typeof(TradeBar)))
596  {
597  var tradeBar = asset.Cache.GetData<TradeBar>();
598  if (tradeBar != null)
599  {
600  tradeHigh = tradeBar.High;
601  tradeLow = tradeBar.Low;
602  pricesEndTime = tradeBar.EndTime.ConvertToUtc(asset.Exchange.TimeZone);
603  }
604  }
605 
606  // do not fill on stale data
607  if (pricesEndTime <= order.Time) return fill;
608 
609  switch (order.Direction)
610  {
611  case OrderDirection.Sell:
612  if (tradeHigh >= order.TriggerPrice || order.TriggerTouched)
613  {
614  order.TriggerTouched = true;
615 
616  //-> 1.1 Limit surpassed: Sell.
617  if (GetAskPrice(asset, out pricesEndTime) >= order.LimitPrice)
618  {
619  fill.Status = OrderStatus.Filled;
620  fill.FillPrice = order.LimitPrice;
621  // assume the order completely filled
622  fill.FillQuantity = order.Quantity;
623  }
624  }
625  break;
626 
627  case OrderDirection.Buy:
628  if (tradeLow <= order.TriggerPrice || order.TriggerTouched)
629  {
630  order.TriggerTouched = true;
631 
632  //-> 1.2 Limit surpassed: Buy.
633  if (GetBidPrice(asset, out pricesEndTime) <= order.LimitPrice)
634  {
635  fill.Status = OrderStatus.Filled;
636  fill.FillPrice = order.LimitPrice;
637  // assume the order completely filled
638  fill.FillQuantity = order.Quantity;
639  }
640  }
641  break;
642  }
643 
644  return fill;
645  }
646 
647  /// <summary>
648  /// Default limit order fill model in the base security class.
649  /// </summary>
650  /// <param name="asset">Security asset we're filling</param>
651  /// <param name="order">Order packet to model</param>
652  /// <returns>Order fill information detailing the average price and quantity filled.</returns>
653  /// <seealso cref="StopMarketFill(Security, StopMarketOrder)"/>
654  /// <seealso cref="MarketFill(Security, MarketOrder)"/>
655  public virtual OrderEvent LimitFill(Security asset, LimitOrder order)
656  {
657  return InternalLimitFill(asset, order, order.LimitPrice, order.Quantity);
658  }
659 
660  /// <summary>
661  /// Default limit order fill model in the base security class.
662  /// </summary>
663  private OrderEvent InternalLimitFill(Security asset, Order order, decimal limitPrice, decimal quantity)
664  {
665  //Initialise;
666  var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone);
667  var fill = new OrderEvent(order, utcTime, OrderFee.Zero);
668 
669  //If its cancelled don't need anymore checks:
670  if (order.Status == OrderStatus.Canceled) return fill;
671 
672  // make sure the exchange is open before filling -- allow pre/post market fills to occur
673  if (!IsExchangeOpen(asset))
674  {
675  return fill;
676  }
677  //Get the range of prices in the last bar:
678  var orderDirection = order.Direction;
679  var prices = GetPricesCheckingPythonWrapper(asset, orderDirection);
680  var pricesEndTime = prices.EndTime.ConvertToUtc(asset.Exchange.TimeZone);
681 
682  // do not fill on stale data
683  if (pricesEndTime <= order.Time) return fill;
684 
685  //-> Valid Live/Model Order:
686  switch (orderDirection)
687  {
688  case OrderDirection.Buy:
689  //Buy limit seeks lowest price
690  if (prices.Low < limitPrice)
691  {
692  //Set order fill:
693  fill.Status = OrderStatus.Filled;
694  // fill at the worse price this bar or the limit price, this allows far out of the money limits
695  // to be executed properly
696  fill.FillPrice = Math.Min(prices.High, limitPrice);
697  // assume the order completely filled
698  fill.FillQuantity = quantity;
699  }
700  break;
701  case OrderDirection.Sell:
702  //Sell limit seeks highest price possible
703  if (prices.High > limitPrice)
704  {
705  fill.Status = OrderStatus.Filled;
706  // fill at the worse price this bar or the limit price, this allows far out of the money limits
707  // to be executed properly
708  fill.FillPrice = Math.Max(prices.Low, limitPrice);
709  // assume the order completely filled
710  fill.FillQuantity = quantity;
711  }
712  break;
713  }
714 
715  return fill;
716  }
717 
718  /// <summary>
719  /// Market on Open Fill Model. Return an order event with the fill details
720  /// </summary>
721  /// <param name="asset">Asset we're trading with this order</param>
722  /// <param name="order">Order to be filled</param>
723  /// <returns>Order fill information detailing the average price and quantity filled.</returns>
725  {
727  {
728  throw new InvalidOperationException(Messages.FillModel.MarketNeverCloses(asset, OrderType.MarketOnOpen));
729  }
730 
731  var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone);
732  var fill = new OrderEvent(order, utcTime, OrderFee.Zero);
733 
734  if (order.Status == OrderStatus.Canceled) return fill;
735 
736  // MOO should never fill on the same bar or on stale data
737  // Imagine the case where we have a thinly traded equity, ASUR, and another liquid
738  // equity, say SPY, SPY gets data every minute but ASUR, if not on fill forward, maybe
739  // have large gaps, in which case the currentBar.EndTime will be in the past
740  // ASUR | | | [order] | | | | | | |
741  // SPY | | | | | | | | | | | | | | | | | | | |
742  var currentBar = asset.GetLastData();
743  var localOrderTime = order.Time.ConvertFromUtc(asset.Exchange.TimeZone);
744  if (currentBar == null || localOrderTime >= currentBar.EndTime) return fill;
745 
746  // if the MOO was submitted during market the previous day, wait for a day to turn over
747  if (asset.Exchange.DateTimeIsOpen(localOrderTime) && localOrderTime.Date == asset.LocalTime.Date)
748  {
749  return fill;
750  }
751 
752  // wait until market open
753  // make sure the exchange is open/normal market hours before filling
754  if (!IsExchangeOpen(asset, false)) return fill;
755 
756  fill.FillPrice = GetPricesCheckingPythonWrapper(asset, order.Direction).Open;
757  fill.Status = OrderStatus.Filled;
758  //Calculate the model slippage: e.g. 0.01c
759  var slip = asset.SlippageModel.GetSlippageApproximation(asset, order);
760 
761  //Apply slippage
762  switch (order.Direction)
763  {
764  case OrderDirection.Buy:
765  fill.FillPrice += slip;
766  // assume the order completely filled
767  fill.FillQuantity = order.Quantity;
768  break;
769  case OrderDirection.Sell:
770  fill.FillPrice -= slip;
771  // assume the order completely filled
772  fill.FillQuantity = order.Quantity;
773  break;
774  }
775 
776  return fill;
777  }
778 
779  /// <summary>
780  /// Market on Close Fill Model. Return an order event with the fill details
781  /// </summary>
782  /// <param name="asset">Asset we're trading with this order</param>
783  /// <param name="order">Order to be filled</param>
784  /// <returns>Order fill information detailing the average price and quantity filled.</returns>
786  {
788  {
789  throw new InvalidOperationException(Messages.FillModel.MarketNeverCloses(asset, OrderType.MarketOnClose));
790  }
791 
792  var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone);
793  var fill = new OrderEvent(order, utcTime, OrderFee.Zero);
794 
795  if (order.Status == OrderStatus.Canceled) return fill;
796 
797  var localOrderTime = order.Time.ConvertFromUtc(asset.Exchange.TimeZone);
798  var nextMarketClose = asset.Exchange.Hours.GetNextMarketClose(localOrderTime, false);
799 
800  // wait until market closes after the order time
801  if (asset.LocalTime < nextMarketClose)
802  {
803  return fill;
804  }
805  // make sure the exchange is open/normal market hours before filling
806  if (!IsExchangeOpen(asset, false)) return fill;
807 
808  fill.FillPrice = GetPricesCheckingPythonWrapper(asset, order.Direction).Close;
809  fill.Status = OrderStatus.Filled;
810  //Calculate the model slippage: e.g. 0.01c
811  var slip = asset.SlippageModel.GetSlippageApproximation(asset, order);
812 
813  //Apply slippage
814  switch (order.Direction)
815  {
816  case OrderDirection.Buy:
817  fill.FillPrice += slip;
818  // assume the order completely filled
819  fill.FillQuantity = order.Quantity;
820  break;
821  case OrderDirection.Sell:
822  fill.FillPrice -= slip;
823  // assume the order completely filled
824  fill.FillQuantity = order.Quantity;
825  break;
826  }
827 
828  return fill;
829  }
830 
831  /// <summary>
832  /// Get current ask price for subscribed data
833  /// This method will try to get the most recent ask price data, so it will try to get tick quote first, then quote bar.
834  /// If no quote, tick or bar, is available (e.g. hourly data), use trade data with preference to tick data.
835  /// </summary>
836  /// <param name="asset">Security which has subscribed data types</param>
837  /// <param name="endTime">Timestamp of the most recent data type</param>
838  private decimal GetAskPrice(Security asset, out DateTime endTime)
839  {
840  var subscribedTypes = GetSubscribedTypes(asset);
841 
842  List<Tick> ticks = null;
843  var isTickSubscribed = subscribedTypes.Contains(typeof(Tick));
844 
845  if (isTickSubscribed)
846  {
847  ticks = asset.Cache.GetAll<Tick>().ToList();
848 
849  var quote = ticks.LastOrDefault(x => x.TickType == TickType.Quote && x.AskPrice > 0);
850  if (quote != null)
851  {
852  endTime = quote.EndTime;
853  return quote.AskPrice;
854  }
855  }
856 
857  if (subscribedTypes.Contains(typeof(QuoteBar)))
858  {
859  var quoteBar = asset.Cache.GetData<QuoteBar>();
860  if (quoteBar != null)
861  {
862  endTime = quoteBar.EndTime;
863  return quoteBar.Ask?.Close ?? quoteBar.Close;
864  }
865  }
866 
867  if (isTickSubscribed)
868  {
869  var trade = ticks.LastOrDefault(x => x.TickType == TickType.Trade && x.Price > 0);
870  if (trade != null)
871  {
872  endTime = trade.EndTime;
873  return trade.Price;
874  }
875  }
876 
877  if (subscribedTypes.Contains(typeof(TradeBar)))
878  {
879  var tradeBar = asset.Cache.GetData<TradeBar>();
880  if (tradeBar != null)
881  {
882  endTime = tradeBar.EndTime;
883  return tradeBar.Close;
884  }
885  }
886 
887  throw new InvalidOperationException(Messages.FillModel.NoMarketDataToGetAskPriceForFilling(asset));
888  }
889 
890  /// <summary>
891  /// Get current bid price for subscribed data
892  /// This method will try to get the most recent bid price data, so it will try to get tick quote first, then quote bar.
893  /// If no quote, tick or bar, is available (e.g. hourly data), use trade data with preference to tick data.
894  /// </summary>
895  /// <param name="asset">Security which has subscribed data types</param>
896  /// <param name="endTime">Timestamp of the most recent data type</param>
897  private decimal GetBidPrice(Security asset, out DateTime endTime)
898  {
899  var subscribedTypes = GetSubscribedTypes(asset);
900 
901  List<Tick> ticks = null;
902  var isTickSubscribed = subscribedTypes.Contains(typeof(Tick));
903 
904  if (isTickSubscribed)
905  {
906  ticks = asset.Cache.GetAll<Tick>().ToList();
907 
908  var quote = ticks.LastOrDefault(x => x.TickType == TickType.Quote && x.BidPrice > 0);
909  if (quote != null)
910  {
911  endTime = quote.EndTime;
912  return quote.BidPrice;
913  }
914  }
915 
916  if (subscribedTypes.Contains(typeof(QuoteBar)))
917  {
918  var quoteBar = asset.Cache.GetData<QuoteBar>();
919  if (quoteBar != null)
920  {
921  endTime = quoteBar.EndTime;
922  return quoteBar.Bid?.Close ?? quoteBar.Close;
923  }
924  }
925 
926  if (isTickSubscribed)
927  {
928  var trade = ticks.LastOrDefault(x => x.TickType == TickType.Trade && x.Price > 0);
929  if (trade != null)
930  {
931  endTime = trade.EndTime;
932  return trade.Price;
933  }
934  }
935 
936  if (subscribedTypes.Contains(typeof(TradeBar)))
937  {
938  var tradeBar = asset.Cache.GetData<TradeBar>();
939  if (tradeBar != null)
940  {
941  endTime = tradeBar.EndTime;
942  return tradeBar.Close;
943  }
944  }
945 
946  throw new InvalidOperationException(Messages.FillModel.NoMarketDataToGetBidPriceForFilling(asset));
947  }
948 
949  /// <summary>
950  /// Get data types the Security is subscribed to
951  /// </summary>
952  /// <param name="asset">Security which has subscribed data types</param>
953  protected virtual HashSet<Type> GetSubscribedTypes(Security asset)
954  {
955  var subscribedTypes = Parameters
957  // even though data from internal configurations are not sent to the algorithm.OnData they still drive security cache and data
958  // this is specially relevant for the continuous contract underlying mapped contracts which are internal configurations
959  .GetSubscriptionDataConfigs(asset.Symbol, includeInternalConfigs: true)
960  .ToHashSet(x => x.Type);
961 
962  if (subscribedTypes.Count == 0)
963  {
964  throw new InvalidOperationException(Messages.FillModel.NoDataSubscriptionFoundForFilling(asset));
965  }
966 
967  return subscribedTypes;
968  }
969 
970  /// <summary>
971  /// Helper method to determine if the exchange is open before filling. Will allow pre/post market fills to occur based on configuration
972  /// </summary>
973  /// <param name="asset">Security which has subscribed data types</param>
974  private bool IsExchangeOpen(Security asset)
975  {
976  // even though data from internal configurations are not sent to the algorithm.OnData they still drive security cache and data
977  // this is specially relevant for the continuous contract underlying mapped contracts which are internal configurations
978  var configs = Parameters.ConfigProvider.GetSubscriptionDataConfigs(asset.Symbol, includeInternalConfigs: true);
979  if (configs.Count == 0)
980  {
981  throw new InvalidOperationException(Messages.FillModel.NoDataSubscriptionFoundForFilling(asset));
982  }
983 
984  var hasNonInternals = false;
985  var exchangeOpenNonInternals = false;
986  var exchangeOpenInternals = false;
987  for (int i = 0; i < configs.Count; i++)
988  {
989  var config = configs[i];
990 
991  if (config.IsInternalFeed)
992  {
993  exchangeOpenInternals |= config.ExtendedMarketHours;
994  }
995  else
996  {
997  hasNonInternals = true;
998  exchangeOpenNonInternals |= config.ExtendedMarketHours;
999  }
1000  }
1001 
1002  if (hasNonInternals)
1003  {
1004  // give priority to non internals if any
1005  return IsExchangeOpen(asset, exchangeOpenNonInternals);
1006  }
1007  return IsExchangeOpen(asset, exchangeOpenInternals);
1008  }
1009 
1010  /// <summary>
1011  /// This is required due to a limitation in PythonNet to resolved
1012  /// overriden methods. <see cref="GetPrices"/>
1013  /// </summary>
1014  protected virtual Prices GetPricesCheckingPythonWrapper(Security asset, OrderDirection direction)
1015  {
1016  if (PythonWrapper != null)
1017  {
1018  return PythonWrapper.GetPrices(asset, direction);
1019  }
1020  return GetPrices(asset, direction);
1021  }
1022 
1023  /// <summary>
1024  /// Get the minimum and maximum price for this security in the last bar:
1025  /// </summary>
1026  /// <param name="asset">Security asset we're checking</param>
1027  /// <param name="direction">The order direction, decides whether to pick bid or ask</param>
1028  protected virtual Prices GetPrices(Security asset, OrderDirection direction)
1029  {
1030  var low = asset.Low;
1031  var high = asset.High;
1032  var open = asset.Open;
1033  var close = asset.Close;
1034  var current = asset.Price;
1035  var endTime = asset.Cache.GetData()?.EndTime ?? DateTime.MinValue;
1036 
1037  if (direction == OrderDirection.Hold)
1038  {
1039  return new Prices(endTime, current, open, high, low, close);
1040  }
1041 
1042  // Only fill with data types we are subscribed to
1043  var subscriptionTypes = GetSubscribedTypes(asset);
1044  // Tick
1045  var tick = asset.Cache.GetData<Tick>();
1046  if (tick != null && subscriptionTypes.Contains(typeof(Tick)))
1047  {
1048  var price = direction == OrderDirection.Sell ? tick.BidPrice : tick.AskPrice;
1049  if (price != 0m)
1050  {
1051  return new Prices(tick.EndTime, price, 0, 0, 0, 0);
1052  }
1053 
1054  // If the ask/bid spreads are not available for ticks, try the price
1055  price = tick.Price;
1056  if (price != 0m)
1057  {
1058  return new Prices(tick.EndTime, price, 0, 0, 0, 0);
1059  }
1060  }
1061 
1062  // Quote
1063  var quoteBar = asset.Cache.GetData<QuoteBar>();
1064  if (quoteBar != null && subscriptionTypes.Contains(typeof(QuoteBar)))
1065  {
1066  var bar = direction == OrderDirection.Sell ? quoteBar.Bid : quoteBar.Ask;
1067  if (bar != null)
1068  {
1069  return new Prices(quoteBar.EndTime, bar);
1070  }
1071  }
1072 
1073  // Trade
1074  var tradeBar = asset.Cache.GetData<TradeBar>();
1075  if (tradeBar != null && subscriptionTypes.Contains(typeof(TradeBar)))
1076  {
1077  return new Prices(tradeBar);
1078  }
1079 
1080  return new Prices(endTime, current, open, high, low, close);
1081  }
1082 
1083  /// <summary>
1084  /// Determines if the exchange is open using the current time of the asset
1085  /// </summary>
1086  protected virtual bool IsExchangeOpen(Security asset, bool isExtendedMarketHours)
1087  {
1088  if (!asset.Exchange.Hours.IsOpen(asset.LocalTime, isExtendedMarketHours))
1089  {
1090  // if we're not open at the current time exactly, check the bar size, this handle large sized bars (hours/days)
1091  var currentBar = asset.GetLastData();
1092  if (currentBar == null)
1093  {
1094  return false;
1095  }
1096 
1097  var barSpan = currentBar.EndTime - currentBar.Time;
1098  var isOnCurrentBar = barSpan > Time.OneHour
1099  // for fill purposes we consider the market open for daily bars if we are in the same day
1100  ? asset.LocalTime.Date == currentBar.EndTime.Date
1101  // for other resolution bars, market is considered open if we are within the bar time
1102  : asset.LocalTime <= currentBar.EndTime;
1103 
1104  return isOnCurrentBar && asset.Exchange.IsOpenDuringBar(currentBar.Time, currentBar.EndTime, isExtendedMarketHours);
1105  }
1106 
1107  return true;
1108  }
1109 
1110  private class ComboLimitOrderLegParameters
1111  {
1112  public Security Security { get; set; }
1113  public Order Order { get; set; }
1114  public Prices Prices { get; set; }
1115 
1116  /// <summary>
1117  /// Gets the current price that would be paid/received for this leg based on the security price and the leg quantity
1118  /// </summary>
1119  public decimal Price
1120  {
1121  get
1122  {
1123  // we use the same, either low or high, for every leg depending on the combo direction
1124  var price = Order.GroupOrderManager.Direction == OrderDirection.Buy ? Prices.Low : Prices.High;
1125 
1126  // the limit price should be calculated using the ratios instead of the group quantities, like IB does
1127  var quantity = Order.Quantity.GetOrderLegRatio(Order.GroupOrderManager);
1128  if (Security.Symbol.SecurityType == SecurityType.Equity)
1129  {
1130  quantity /= 100;
1131  }
1132 
1133  return price * quantity;
1134  }
1135  }
1136  }
1137  }
1138 }