/// <summary> /// Market on Open Fill Model. Return an order event with the fill details /// </summary> /// <param name="asset">Asset we're trading with this order</param> /// <param name="order">Order to be filled</param> /// <returns>Order fill information detailing the average price and quantity filled.</returns> public virtual OrderEvent MarketOnOpenFill(Security asset, MarketOnOpenOrder order) { var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone); var fill = new OrderEvent(order, utcTime, 0); if (order.Status == OrderStatus.Canceled) { return(fill); } // MOO should never fill on the same bar or on stale data // Imagine the case where we have a thinly traded equity, ASUR, and another liquid // equity, say SPY, SPY gets data every minute but ASUR, if not on fill forward, maybe // have large gaps, in which case the currentBar.EndTime will be in the past // ASUR | | | [order] | | | | | | | // SPY | | | | | | | | | | | | | | | | | | | | var currentBar = asset.GetLastData(); var localOrderTime = order.Time.ConvertFromUtc(asset.Exchange.TimeZone); if (currentBar == null || localOrderTime >= currentBar.EndTime) { return(fill); } // if the MOO was submitted during market the previous day, wait for a day to turn over if (asset.Exchange.DateTimeIsOpen(localOrderTime) && localOrderTime.Date == asset.LocalTime.Date) { return(fill); } // wait until market open // make sure the exchange is open/normal market hours before filling if (!IsExchangeOpen(asset, false)) { return(fill); } fill.FillPrice = GetPricesCheckingPythonWrapper(asset, order.Direction).Open; 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; // assume the order completely filled fill.FillQuantity = order.Quantity; break; case OrderDirection.Sell: fill.FillPrice -= slip; // assume the order completely filled fill.FillQuantity = order.Quantity; break; } return(fill); }
/// <summary> /// Wrapper for <see cref = "IFillModel.MarketOnOpenFill(Security, MarketOnOpenOrder)" /> in Python /// </summary> /// <param name="asset">Asset we're trading with this order</param> /// <param name="order">Order to be filled</param> /// <returns>Order fill information detailing the average price and quantity filled.</returns> public OrderEvent MarketOnOpenFill(Security asset, MarketOnOpenOrder order) { using (Py.GIL()) { return(_model.MarketOnOpenFill(asset, order)); } }
public void PerformsMarketOnOpenUsingOpenPrice() { var reference = DateTime.Today.AddHours(9);// before market open var model = new SecurityTransactionModel(); var order = new MarketOnOpenOrder(Symbol, SecurityType.Equity, 100, reference, 1m); var config = new SubscriptionDataConfig(typeof(TradeBar), SecurityType.Equity, Symbol, Resolution.Minute, true, true, true, true, false, 0); var security = new Security(config, 1) { Exchange = new EquityExchange() }; var time = reference; security.SetMarketPrice(time, new TradeBar(time, Symbol, 1m, 2m, 0.5m, 1.33m, 100)); var fill = model.MarketOnOpenFill(security, order); Assert.AreEqual(0, fill.FillQuantity); // market opens after 30min, so this is just before market open time = reference.AddMinutes(29); security.SetMarketPrice(time, new TradeBar(time, Symbol, 1.33m, 2.75m, 1.15m, 1.45m, 100)); fill = model.MarketOnOpenFill(security, order); Assert.AreEqual(0, fill.FillQuantity); // market opens after 30min time = reference.AddMinutes(30); security.SetMarketPrice(time, new TradeBar(time, Symbol, 1.45m, 2.0m, 1.1m, 1.40m, 100)); fill = model.MarketOnOpenFill(security, order); Assert.AreEqual(order.Quantity, fill.FillQuantity); Assert.AreEqual(security.Open, fill.FillPrice); }
/// <summary> /// Market on Open Fill Model. Return an order event with the fill details /// </summary> /// <param name="asset">Asset we're trading with this order</param> /// <param name="order">Order to be filled</param> /// <returns>Order fill information detailing the average price and quantity filled.</returns> public override OrderEvent MarketOnOpenFill(Security asset, MarketOnOpenOrder order) { using (Py.GIL()) { return((_model.MarketOnOpenFill(asset, order) as PyObject).GetAndDispose <OrderEvent>()); } }
public void PerformsMarketOnOpenUsingOpenPrice() { var reference = new DateTime(2015, 06, 05, 9, 0, 0); // before market open var model = new SecurityTransactionModel(); var order = new MarketOnOpenOrder(Symbols.SPY, 100, reference); var config = CreateTradeBarConfig(Symbols.SPY); var security = new Security(SecurityExchangeHoursTests.CreateUsEquitySecurityExchangeHours(), config, new Cash(CashBook.AccountCurrency, 0, 1m), SymbolProperties.GetDefault(CashBook.AccountCurrency)); security.SetLocalTimeKeeper(TimeKeeper.GetLocalTimeKeeper(TimeZones.NewYork)); var time = reference; TimeKeeper.SetUtcDateTime(time.ConvertToUtc(TimeZones.NewYork)); security.SetMarketPrice(new TradeBar(time, Symbols.SPY, 1m, 2m, 0.5m, 1.33m, 100)); var fill = model.MarketOnOpenFill(security, order); Assert.AreEqual(0, fill.FillQuantity); // market opens after 30min, so this is just before market open time = reference.AddMinutes(29); TimeKeeper.SetUtcDateTime(time.ConvertToUtc(TimeZones.NewYork)); security.SetMarketPrice(new TradeBar(time, Symbols.SPY, 1.33m, 2.75m, 1.15m, 1.45m, 100)); fill = model.MarketOnOpenFill(security, order); Assert.AreEqual(0, fill.FillQuantity); // market opens after 30min time = reference.AddMinutes(30); TimeKeeper.SetUtcDateTime(time.ConvertToUtc(TimeZones.NewYork)); security.SetMarketPrice(new TradeBar(time, Symbols.SPY, 1.45m, 2.0m, 1.1m, 1.40m, 100)); fill = model.MarketOnOpenFill(security, order); Assert.AreEqual(order.Quantity, fill.FillQuantity); Assert.AreEqual(security.Open, fill.FillPrice); }
/// <summary> /// Market on Open Fill Model. Return an order event with the fill details /// </summary> /// <param name="security">Asset we're trading with this order</param> /// <param name="order">Order to be filled</param> /// <returns>Order fill informaton detailing the average price and quantity filled.</returns> public OrderEvent MarketOnOpenFill(Security security, MarketOnOpenOrder order) { var fill = new OrderEvent(order); if (fill.Status == OrderStatus.Canceled) { return(fill); } try { // if the MOO was submitted during market the previous day, wait for a day to turn over if (security.Exchange.DateTimeIsOpen(order.Time) && order.Time.Date == security.Time.Date) { return(fill); } // wait until market open if (!security.Exchange.ExchangeOpen) { return(fill); } order.Price = security.Open; order.Status = OrderStatus.Filled; //Calculate the model slippage: e.g. 0.01c var slip = GetSlippageApproximation(security, order); //Apply slippage switch (order.Direction) { case OrderDirection.Buy: order.Price += slip; break; case OrderDirection.Sell: order.Price -= slip; break; } //For backtesting, we assuming the order is 100% filled on first attempt. fill.FillPrice = order.Price; fill.FillQuantity = order.Quantity; fill.Status = order.Status; } catch (Exception err) { Log.Error(err); } return(fill); }
public void PerformsMarketOnOpenUsingOpenPrice() { var reference = new DateTime(2015, 06, 05, 9, 0, 0); // before market open var model = new ImmediateFillModel(); var order = new MarketOnOpenOrder(Symbols.SPY, 100, reference); var config = CreateTradeBarConfig(Symbols.SPY); var security = new Security( SecurityExchangeHoursTests.CreateUsEquitySecurityExchangeHours(), config, new Cash(Currencies.USD, 0, 1m), SymbolProperties.GetDefault(Currencies.USD), ErrorCurrencyConverter.Instance, RegisteredSecurityDataTypesProvider.Null, new SecurityCache() ); security.SetLocalTimeKeeper(TimeKeeper.GetLocalTimeKeeper(TimeZones.NewYork)); var time = reference; TimeKeeper.SetUtcDateTime(time.ConvertToUtc(TimeZones.NewYork)); security.SetMarketPrice(new TradeBar(time, Symbols.SPY, 1m, 2m, 0.5m, 1.33m, 100)); var fill = model.Fill(new FillModelParameters( security, order, new MockSubscriptionDataConfigProvider(config), Time.OneHour)).OrderEvent; Assert.AreEqual(0, fill.FillQuantity); // market opens after 30min, so this is just before market open time = reference.AddMinutes(29); TimeKeeper.SetUtcDateTime(time.ConvertToUtc(TimeZones.NewYork)); security.SetMarketPrice(new TradeBar(time, Symbols.SPY, 1.33m, 2.75m, 1.15m, 1.45m, 100)); fill = model.Fill(new FillModelParameters( security, order, new MockSubscriptionDataConfigProvider(config), Time.OneHour)).OrderEvent; Assert.AreEqual(0, fill.FillQuantity); // market opens after 30min time = reference.AddMinutes(30); TimeKeeper.SetUtcDateTime(time.ConvertToUtc(TimeZones.NewYork)); security.SetMarketPrice(new TradeBar(time, Symbols.SPY, 1.45m, 2.0m, 1.1m, 1.40m, 100)); fill = model.MarketOnOpenFill(security, order); Assert.AreEqual(order.Quantity, fill.FillQuantity); Assert.AreEqual(security.Open, fill.FillPrice); }
/// <summary> /// Market on open order implementation: Send a market order when the exchange opens /// </summary> /// <param name="symbol">The symbol to be ordered</param> /// <param name="quantity">The number of shares to required</param> /// <param name="tag">Place a custom order property or tag (e.g. indicator data).</param> /// <returns>The order ID</returns> public int MarketOnOpen(string symbol, int quantity, string tag = "") { var error = PreOrderChecks(symbol, quantity, OrderType.MarketOnOpen); if (error < 0) { return(error); } var security = Securities[symbol]; var order = new MarketOnOpenOrder(symbol, security.Type, quantity, Time, security.Price, tag); return(Transactions.AddOrder(order)); }
public void DeserializesMarketOnOpenOrder(Symbols.SymbolsKey key) { var expected = new MarketOnOpenOrder(Symbols.Lookup(key), 100, new DateTime(2015, 11, 23, 17, 15, 37), "now") { Id = 12345, Price = 209.03m, ContingentId = 123456, BrokerId = new List <string> { "727", "54970" } }; TestOrderType(expected); }
public void DeserializesMarketOnOpenOrder() { var expected = new MarketOnOpenOrder(Symbols.SPY, SecurityType.Equity, 100, new DateTime(2015, 11, 23, 17, 15, 37), "now") { Id = 12345, Price = 209.03m, ContingentId = 123456, BrokerId = new List <long> { 727, 54970 } }; TestOrderType(expected); }
public OrderEvent MarketOnOpenFill(Security asset, MarketOnOpenOrder order) { var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone); var fill = new OrderEvent(order, utcTime, 0); if (order.Status == OrderStatus.Canceled) { return(fill); } var currentBar = asset.GetLastData(); var localOrderTime = order.Time.ConvertFromUtc(asset.Exchange.TimeZone); if (currentBar == null || localOrderTime >= currentBar.EndTime) { return(fill); } if (asset.Exchange.DateTimeIsOpen(localOrderTime) && localOrderTime.Date == asset.LocalTime.Date) { return(fill); } if (!IsExchangeOpen(asset)) { return(fill); } fill.FillPrice = GetPrices(asset, order.Direction).Open; fill.Status = OrderStatus.Filled; var slip = asset.SlippageModel.GetSlippageApproximation(asset, order); switch (order.Direction) { case OrderDirection.Buy: fill.FillPrice += slip; break; case OrderDirection.Sell: fill.FillPrice -= slip; break; } if (fill.Status == OrderStatus.Filled) { fill.FillQuantity = order.Quantity; fill.OrderFee = asset.FeeModel.GetOrderFee(asset, order); } return(fill); }
public OrderEvent MarketOnOpenFill(Security asset, MarketOnOpenOrder order) { MarketOnOpenFillWasCalled = true; return(orderEvent); }
public void DeserializesNullLastFillTimeAndLastUpdateTime() { const string json = @"{ 'Type': 4, 'Id': 1, 'ContingentId': 0, 'BrokerId': [ '1' ], 'Symbol': { 'Value': 'SPY', 'ID': 'SPY R735QTJ8XC9X', 'Permtick': 'SPY' }, 'Price': 321.66, 'PriceCurrency': 'USD', 'Time': '2019-12-24T14:31:00Z', 'CreatedTime': '2019-12-24T14:31:00Z', 'LastUpdateTime': '2019-12-25T14:31:00Z', 'LastFillTime': null, 'Quantity': 1.0, 'Status': 3, 'TimeInForce': {}, 'Tag': '', 'Properties': { 'TimeInForce': {} }, 'SecurityType': 1, 'Direction': 0, 'AbsoluteQuantity': 1.0, 'Value': 321.66, 'OrderSubmissionData': { 'BidPrice': 321.4700, 'AskPrice': 321.4700, 'LastPrice': 321.4700 }, 'IsMarketable': false }"; const string json2 = @"{ 'Type': 4, 'Id': 1, 'ContingentId': 0, 'BrokerId': [ '1' ], 'Symbol': { 'Value': 'SPY', 'ID': 'SPY R735QTJ8XC9X', 'Permtick': 'SPY' }, 'Price': 321.66, 'PriceCurrency': 'USD', 'Time': '2019-12-24T14:31:00Z', 'CreatedTime': '2019-12-24T14:31:00Z', 'LastUpdateTime': null, 'LastFillTime': '2019-12-26T14:31:00Z', 'Quantity': 1.0, 'Status': 3, 'TimeInForce': {}, 'Tag': '', 'Properties': { 'TimeInForce': {} }, 'SecurityType': 1, 'Direction': 0, 'AbsoluteQuantity': 1.0, 'Value': 321.66, 'OrderSubmissionData': { 'BidPrice': 321.4700, 'AskPrice': 321.4700, 'LastPrice': 321.4700 }, 'IsMarketable': false }"; var time = DateTime.SpecifyKind(new DateTime(2019, 12, 24, 14, 31, 0), DateTimeKind.Utc); var fillTime = DateTime.SpecifyKind(new DateTime(2019, 12, 26, 14, 31, 0), DateTimeKind.Utc); var updateTime = DateTime.SpecifyKind(new DateTime(2019, 12, 25, 14, 31, 0), DateTimeKind.Utc); var expected1 = new MarketOnOpenOrder(Symbol.Create("SPY", SecurityType.Equity, Market.USA), 1m, time) { Id = 1, ContingentId = 0, BrokerId = new List <string> { "1" }, Price = 321.66m, PriceCurrency = "USD", LastFillTime = null, LastUpdateTime = updateTime, Status = OrderStatus.Filled, OrderSubmissionData = new OrderSubmissionData(321.47m, 321.47m, 321.47m), }; var expected2 = new MarketOnOpenOrder(Symbol.Create("SPY", SecurityType.Equity, Market.USA), 1m, time) { Id = 1, ContingentId = 0, BrokerId = new List <string> { "1" }, Price = 321.66m, PriceCurrency = "USD", LastFillTime = updateTime, LastUpdateTime = null, Status = OrderStatus.Filled, OrderSubmissionData = new OrderSubmissionData(321.47m, 321.47m, 321.47m), }; var actual1 = (MarketOnOpenOrder)DeserializeOrder <MarketOnOpenOrder>(json); var actual2 = (MarketOnOpenOrder)DeserializeOrder <MarketOnOpenOrder>(json2); TestOrderType(expected1); TestOrderType(expected2); TestOrderType(actual1); TestOrderType(actual2); }
public void RoundTripUsingJsonConverter(string timeInForceStr) { TimeInForce timeInForce = null; switch (timeInForceStr) { case "Day": timeInForce = TimeInForce.Day; break; case "GoodTilCanceled": timeInForce = TimeInForce.GoodTilCanceled; break; case "GoodTilDate": timeInForce = TimeInForce.GoodTilDate(DateTime.UtcNow); break; } var expected = new MarketOnOpenOrder(Symbol.Create("SPY", SecurityType.Equity, Market.USA), 1m, DateTime.UtcNow) { Id = 1, ContingentId = 0, BrokerId = new List <string> { "1" }, Price = 321.66m, PriceCurrency = "USD", LastFillTime = DateTime.UtcNow, LastUpdateTime = DateTime.UtcNow, CanceledTime = DateTime.UtcNow, Status = OrderStatus.Filled, OrderSubmissionData = new OrderSubmissionData(321.47m, 321.48m, 321.49m), Properties = { TimeInForce = timeInForce } }; var converter = new OrderJsonConverter(); var serialized = JsonConvert.SerializeObject(expected, converter); var actual = JsonConvert.DeserializeObject <Order>(serialized, converter); CollectionAssert.AreEqual(expected.BrokerId, actual.BrokerId); Assert.AreEqual(expected.ContingentId, actual.ContingentId); Assert.AreEqual(expected.Direction, actual.Direction); Assert.AreEqual(expected.TimeInForce.GetType(), actual.TimeInForce.GetType()); Assert.AreEqual(expected.Id, actual.Id); Assert.AreEqual(expected.Price, actual.Price); Assert.AreEqual(expected.PriceCurrency, actual.PriceCurrency); Assert.AreEqual(expected.SecurityType, actual.SecurityType); Assert.AreEqual(expected.Status, actual.Status); Assert.AreEqual(expected.Symbol, actual.Symbol); Assert.AreEqual(expected.Tag, actual.Tag); Assert.AreEqual(expected.Time, actual.Time); Assert.AreEqual(expected.CreatedTime, actual.CreatedTime); Assert.AreEqual(expected.LastFillTime, actual.LastFillTime); Assert.AreEqual(expected.LastUpdateTime, actual.LastUpdateTime); Assert.AreEqual(expected.CanceledTime, actual.CanceledTime); Assert.AreEqual(expected.Type, actual.Type); Assert.AreEqual(expected.Value, actual.Value); Assert.AreEqual(expected.Quantity, actual.Quantity); Assert.AreEqual(expected.TimeInForce.GetType(), actual.TimeInForce.GetType()); Assert.AreEqual(expected.Symbol.ID.Market, actual.Symbol.ID.Market); Assert.AreEqual(expected.OrderSubmissionData.AskPrice, actual.OrderSubmissionData.AskPrice); Assert.AreEqual(expected.OrderSubmissionData.BidPrice, actual.OrderSubmissionData.BidPrice); Assert.AreEqual(expected.OrderSubmissionData.LastPrice, actual.OrderSubmissionData.LastPrice); }
/// <summary> /// Create pending order based on order ticket /// </summary> /// <param name="ticket"></param> /// <returns></returns> public PendingOrder CreateOrder(OrderTicket ticket) { //Get correct ticket information if (ticket.Type != OrderTicketType.Submit) { return(null); } //Get new order ticket if (!(ticket is SubmitOrderTicket norderticket)) { ticket.SetResponse(OrderTicketResponse.Error(ticket.OrderId, OrderTicketResponseErrorCode.ProcessingError, "Incorrect ticket type received")); return(null); } //Check order type based on input Order order; switch (norderticket.OrderType) { case OrderType.Limit: order = new LimitOrder(norderticket.Security, norderticket.FundId, norderticket.Quantity, norderticket.LimitPrice, norderticket.CreatedUtc, norderticket.ExchangeName, norderticket.Comment) { InternalId = _portfolio.OrderTicketHandler.GetNextInternalOrderId() }; break; case OrderType.Market: order = new MarketOrder(norderticket.Security, norderticket.FundId, norderticket.Quantity, norderticket.CreatedUtc, norderticket.ExchangeName, norderticket.Comment) { InternalId = _portfolio.OrderTicketHandler.GetNextInternalOrderId() }; break; case OrderType.StopMarket: order = new StopMarketOrder(norderticket.Security, norderticket.FundId, norderticket.Quantity, norderticket.StopPrice, norderticket.CreatedUtc, norderticket.ExchangeName, norderticket.Comment) { InternalId = _portfolio.OrderTicketHandler.GetNextInternalOrderId() }; break; case OrderType.StopLimit: order = new StopLimitOrder(norderticket.Security, norderticket.FundId, norderticket.Quantity, norderticket.LimitPrice, norderticket.StopPrice, norderticket.CreatedUtc, norderticket.ExchangeName, norderticket.Comment) { InternalId = _portfolio.OrderTicketHandler.GetNextInternalOrderId() }; break; case OrderType.MarketOnOpen: order = new MarketOnOpenOrder(norderticket.Security, norderticket.FundId, norderticket.Quantity, norderticket.CreatedUtc, norderticket.ExchangeName, norderticket.Comment) { InternalId = _portfolio.OrderTicketHandler.GetNextInternalOrderId() }; break; case OrderType.MarketOnClose: order = new MarketOnCloseOrder(norderticket.Security, norderticket.FundId, norderticket.Quantity, norderticket.CreatedUtc, norderticket.ExchangeName, norderticket.Comment) { InternalId = _portfolio.OrderTicketHandler.GetNextInternalOrderId() }; break; default: ticket.SetResponse(OrderTicketResponse.Error(ticket.OrderId, OrderTicketResponseErrorCode.ProcessingError, $"Unknown order type {norderticket.OrderType} supplied")); return(null); } //Check if order type is supported bool issupported; try { issupported = _brokerModel.IsOrderTypeSupported(order.Type); } catch (Exception exc) { _log.Error(exc, $"Could not execute brokermodel function IsOrderTypeSupported due to exception"); throw exc; } //Create pending order return(new PendingOrder(_portfolio, ticket.FundId, order, norderticket.Comment, norderticket.Security.LastTickEventUtc, issupported)); }
/// <summary> /// Market on Open Fill Model. Return an order event with the fill details /// </summary> /// <param name="asset">Asset we're trading with this order</param> /// <param name="order">Order to be filled</param> /// <returns>Order fill informaton detailing the average price and quantity filled.</returns> public OrderEvent MarketOnOpenFill(Security asset, MarketOnOpenOrder order) { var fill = new OrderEvent(order); if (order.Status == OrderStatus.Canceled) { return(fill); } try { // MOO should never fill on the same bar or on stale data // Imagine the case where we have a thinly traded equity, ASUR, and another liquid // equity, say SPY, SPY gets data every minute but ASUR, if not on fill forward, maybe // have large gaps, in which case the currentBar.EndTime will be in the past // ASUR | | | [order] | | | | | | | // SPY | | | | | | | | | | | | | | | | | | | | var currentBar = asset.GetLastData(); var localOrderTime = order.Time.ConvertFromUtc(asset.Exchange.TimeZone); if (currentBar == null || localOrderTime >= currentBar.EndTime) { return(fill); } // if the MOO was submitted during market the previous day, wait for a day to turn over if (asset.Exchange.DateTimeIsOpen(localOrderTime) && localOrderTime.Date == asset.LocalTime.Date) { return(fill); } // wait until market open // make sure the exchange is open before filling if (!IsExchangeOpen(asset)) { return(fill); } order.Price = asset.Open; order.Status = OrderStatus.Filled; //Calculate the model slippage: e.g. 0.01c var slip = GetSlippageApproximation(asset, order); //Apply slippage switch (order.Direction) { case OrderDirection.Buy: order.Price += slip; break; case OrderDirection.Sell: order.Price -= slip; break; } //For backtesting, we assuming the order is 100% filled on first attempt. fill.FillPrice = order.Price; fill.FillQuantity = order.Quantity; fill.Status = order.Status; } catch (Exception err) { Log.Error(err); } return(fill); }
/// <summary> /// Market on Open Fill Model. Return an order event with the fill details /// </summary> /// <param name="asset">Asset we're trading with this order</param> /// <param name="order">Order to be filled</param> /// <returns>Order fill information detailing the average price and quantity filled.</returns> public OrderEvent MarketOnOpenFill(Security asset, MarketOnOpenOrder order) { var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone); var fill = new OrderEvent(order, utcTime, 0); if (order.Status == OrderStatus.Canceled) return fill; // MOO should never fill on the same bar or on stale data // Imagine the case where we have a thinly traded equity, ASUR, and another liquid // equity, say SPY, SPY gets data every minute but ASUR, if not on fill forward, maybe // have large gaps, in which case the currentBar.EndTime will be in the past // ASUR | | | [order] | | | | | | | // SPY | | | | | | | | | | | | | | | | | | | | var currentBar = asset.GetLastData(); var localOrderTime = order.Time.ConvertFromUtc(asset.Exchange.TimeZone); if (currentBar == null || localOrderTime >= currentBar.EndTime) return fill; // if the MOO was submitted during market the previous day, wait for a day to turn over if (asset.Exchange.DateTimeIsOpen(localOrderTime) && localOrderTime.Date == asset.LocalTime.Date) { return fill; } // wait until market open // make sure the exchange is open before filling if (!IsExchangeOpen(asset)) return fill; fill.FillPrice = asset.Open; 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 if (fill.Status == OrderStatus.Filled) { fill.FillQuantity = order.Quantity; fill.OrderFee = asset.FeeModel.GetOrderFee(asset, order); } return fill; }
/// <summary> /// Market on Open Fill Model. Return an order event with the fill details /// </summary> /// <param name="asset">Asset we're trading with this order</param> /// <param name="order">Order to be filled</param> /// <returns>Order fill information detailing the average price and quantity filled.</returns> public virtual OrderEvent MarketOnOpenFill(Security asset, MarketOnOpenOrder order) { var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone); var fill = new OrderEvent(order, utcTime, OrderFee.Zero); if (order.Status == OrderStatus.Canceled) { return(fill); } // MOO should never fill on the same bar or on stale data // Imagine the case where we have a thinly traded equity, ASUR, and another liquid // equity, say SPY, SPY gets data every minute but ASUR, if not on fill forward, maybe // have large gaps, in which case the currentBar.EndTime will be in the past // ASUR | | | [order] | | | | | | | // SPY | | | | | | | | | | | | | | | | | | | | var localOrderTime = order.Time.ConvertFromUtc(asset.Exchange.TimeZone); var endTime = DateTime.MinValue; var subscribedTypes = GetSubscribedTypes(asset); if (subscribedTypes.Contains(typeof(Tick))) { var primaryExchange = (byte)((Equity)asset).PrimaryExchange; var officialOpen = (uint)(TradeConditionFlags.Regular | TradeConditionFlags.OfficialOpen); var openingPrints = (uint)(TradeConditionFlags.Regular | TradeConditionFlags.OpeningPrints); // Get the first valid (non-zero) tick of trade type from an open market var trade = asset.Cache.GetAll <Tick>() .Where(x => !string.IsNullOrWhiteSpace(x.SaleCondition)) .FirstOrDefault(x => x.TickType == TickType.Trade && x.Price > 0 && x.ExchangeCode == primaryExchange && (x.ParsedSaleCondition == officialOpen || x.ParsedSaleCondition == openingPrints) && asset.Exchange.DateTimeIsOpen(x.Time)); if (trade != null) { endTime = trade.EndTime; fill.FillPrice = trade.Price; } } else if (subscribedTypes.Contains(typeof(TradeBar))) { var tradeBar = asset.Cache.GetData <TradeBar>(); if (tradeBar != null) { // If the order was placed during the bar aggregation, we cannot use its open price if (tradeBar.Time < localOrderTime) { return(fill); } // We need to verify whether the trade data is from the open market. if (tradeBar.Period < Resolution.Hour.ToTimeSpan() && !asset.Exchange.DateTimeIsOpen(tradeBar.Time)) { return(fill); } endTime = tradeBar.EndTime; fill.FillPrice = tradeBar.Open; } } if (localOrderTime >= endTime) { return(fill); } // if the MOO was submitted during market the previous day, wait for a day to turn over // The date of the order and the trade data end time cannot be the same. // Note that the security local time can be ahead of the data end time. if (asset.Exchange.DateTimeIsOpen(localOrderTime) && localOrderTime.Date == endTime.Date) { return(fill); } // wait until market open // make sure the exchange is open/normal market hours before filling if (!IsExchangeOpen(asset, false)) { return(fill); } // assume the order completely filled fill.FillQuantity = order.Quantity; 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; } return(fill); }
/// <summary> /// Market on Open Fill Model. Return an order event with the fill details /// </summary> /// <param name="asset">Asset we're trading with this order</param> /// <param name="order">Order to be filled</param> /// <returns>Order fill information detailing the average price and quantity filled.</returns> public OrderEvent MarketOnOpenFill(Security asset, MarketOnOpenOrder order) { return(_fillModel.MarketOnOpenFill(asset, order)); }
public override OrderEvent MarketOnOpenFill(Security asset, MarketOnOpenOrder order) { MarketOnOpenFillWasCalled = true; return(base.MarketOnOpenFill(asset, order)); }
/// <summary> /// Market on Open Fill Model. Return an order event with the fill details /// </summary> /// <param name="asset">Asset we're trading with this order</param> /// <param name="order">Order to be filled</param> /// <returns>Order fill information detailing the average price and quantity filled.</returns> public virtual OrderEvent MarketOnOpenFill(Security asset, MarketOnOpenOrder order) { var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone); var fill = new OrderEvent(order, utcTime, OrderFee.Zero); if (order.Status == OrderStatus.Canceled) { return(fill); } // MOO should never fill on the same bar or on stale data // Imagine the case where we have a thinly traded equity, ASUR, and another liquid // equity, say SPY, SPY gets data every minute but ASUR, if not on fill forward, maybe // have large gaps, in which case the currentBar.EndTime will be in the past // ASUR | | | [order] | | | | | | | // SPY | | | | | | | | | | | | | | | | | | | | var localOrderTime = order.Time.ConvertFromUtc(asset.Exchange.TimeZone); var endTime = DateTime.MinValue; var subscribedTypes = GetSubscribedTypes(asset); if (subscribedTypes.Contains(typeof(Tick))) { var primaryExchangeCode = ((Equity)asset).PrimaryExchange.Code; var officialOpen = (uint)(TradeConditionFlags.Regular | TradeConditionFlags.OfficialOpen); var openingPrints = (uint)(TradeConditionFlags.Regular | TradeConditionFlags.OpeningPrints); var trades = asset.Cache.GetAll <Tick>() .Where(x => x.TickType == TickType.Trade && x.Price > 0 && asset.Exchange.DateTimeIsOpen(x.Time)) .OrderBy(x => x.EndTime).ToList(); // Get the first valid (non-zero) tick of trade type from an open market var tick = trades .Where(x => !string.IsNullOrWhiteSpace(x.SaleCondition)) .FirstOrDefault(x => x.TickType == TickType.Trade && x.Price > 0 && x.ExchangeCode == primaryExchangeCode && (x.ParsedSaleCondition == officialOpen || x.ParsedSaleCondition == openingPrints) && asset.Exchange.DateTimeIsOpen(x.Time)); // If there is no OfficialOpen or OpeningPrints in the current list of trades, // we will wait for the next up to 1 minute before accepting the last trade without flags // We will give priority to trade then use quote to get the timestamp // If there are only quotes, we will need to test for the tick type before we assign the fill price if (tick == null) { var previousOpen = asset.Exchange.Hours .GetMarketHours(asset.LocalTime) .GetMarketOpen(TimeSpan.Zero, false); fill.Message = "No trade with the OfficialOpen or OpeningPrints flag within the 1-minute timeout."; tick = trades.LastOrDefault() ?? asset.Cache.GetAll <Tick>().LastOrDefault(); if ((tick?.EndTime.TimeOfDay - previousOpen)?.TotalMinutes < 1) { return(fill); } fill.Message += $" Fill with last {tick.TickType} data."; } endTime = tick?.EndTime ?? endTime; if (tick?.TickType == TickType.Trade) { fill.FillPrice = tick.Price; } } else if (subscribedTypes.Contains(typeof(TradeBar))) { var tradeBar = asset.Cache.GetData <TradeBar>(); if (tradeBar != null) { // If the order was placed during the bar aggregation, we cannot use its open price if (tradeBar.Time < localOrderTime) { return(fill); } // We need to verify whether the trade data is from the open market. if (tradeBar.Period < Resolution.Hour.ToTimeSpan() && !asset.Exchange.DateTimeIsOpen(tradeBar.Time)) { return(fill); } endTime = tradeBar.EndTime; fill.FillPrice = tradeBar.Open; } } else { fill.Message = $"Warning: No trade information available at {asset.LocalTime.ToStringInvariant()} {asset.Exchange.TimeZone}, order filled using Quote data"; } if (localOrderTime >= endTime) { return(fill); } // if the MOO was submitted during market the previous day, wait for a day to turn over // The date of the order and the trade data end time cannot be the same. // Note that the security local time can be ahead of the data end time. if (asset.Exchange.DateTimeIsOpen(localOrderTime) && localOrderTime.Date == endTime.Date) { return(fill); } // wait until market open // make sure the exchange is open/normal market hours before filling if (!IsExchangeOpen(asset, false)) { return(fill); } // assume the order completely filled fill.FillQuantity = order.Quantity; fill.Status = OrderStatus.Filled; //Calculate the model slippage: e.g. 0.01c var slip = asset.SlippageModel.GetSlippageApproximation(asset, order); var bestEffortMessage = ""; // If there is no trade information, get the bid or ask, then apply the slippage switch (order.Direction) { case OrderDirection.Buy: if (fill.FillPrice == 0) { fill.FillPrice = GetBestEffortAskPrice(asset, order.Time, out bestEffortMessage); fill.Message += bestEffortMessage; } fill.FillPrice += slip; break; case OrderDirection.Sell: if (fill.FillPrice == 0) { fill.FillPrice = GetBestEffortBidPrice(asset, order.Time, out bestEffortMessage); fill.Message += bestEffortMessage; } fill.FillPrice -= slip; break; } return(fill); }