/// <summary> /// Limit if Touched Fill Model. Return an order event with the fill details. /// </summary> /// <param name="asset">Asset we're trading this order</param> /// <param name="order"><see cref="LimitIfTouchedOrder"/> Order to Check, return filled if true</param> /// <returns>Order fill information detailing the average price and quantity filled.</returns> public override OrderEvent LimitIfTouchedFill(Security asset, LimitIfTouchedOrder order) { using (Py.GIL()) { return((_model.LimitIfTouchedFill(asset, order) as PyObject).GetAndDispose <OrderEvent>()); } }
public void DeserializesLimitIfTouchedOrder(Symbols.SymbolsKey key) { var expected = new LimitIfTouchedOrder(Symbols.Lookup(key), 100, 210.10m, 200.23m, new DateTime(2015, 11, 23, 17, 15, 37), "now") { Id = 12345, Price = 209.03m, ContingentId = 123456, BrokerId = new List <string> { "727", "54970" } }; var actual = TestOrderType(expected); Assert.AreEqual(expected.TriggerPrice, actual.TriggerPrice); Assert.AreEqual(expected.LimitPrice, actual.LimitPrice); }
public void PerformsLimitIfTouchedFillSell() { var model = new ImmediateFillModel(); var order = new LimitIfTouchedOrder(Symbols.SPY, -100, 101.5m, 105m, Noon); var configTradeBar = CreateTradeBarConfig(Symbols.SPY); var configQuoteBar = new SubscriptionDataConfig(configTradeBar, typeof(QuoteBar)); var configProvider = new MockSubscriptionDataConfigProvider(configQuoteBar); var security = new Security( SecurityExchangeHoursTests.CreateUsEquitySecurityExchangeHours(), configTradeBar, new Cash(Currencies.USD, 0, 1m), SymbolProperties.GetDefault(Currencies.USD), ErrorCurrencyConverter.Instance, RegisteredSecurityDataTypesProvider.Null, new SecurityCache() ); // Sets price at time zero security.SetLocalTimeKeeper(TimeKeeper.GetLocalTimeKeeper(TimeZones.NewYork)); security.SetMarketPrice(new TradeBar(Noon, Symbols.SPY, 100m, 100m, 90m, 90m, 100)); configProvider.SubscriptionDataConfigs.Add(configTradeBar); var fill = model.Fill(new FillModelParameters( security, order, configProvider, Time.OneHour)).OrderEvent; Assert.AreEqual(0, fill.FillQuantity); Assert.AreEqual(0, fill.FillPrice); Assert.AreEqual(OrderStatus.None, fill.Status); // Time jump => trigger touched but not limit security.SetMarketPrice(new TradeBar(Noon, Symbols.SPY, 102m, 103m, 102m, 102m, 100)); security.SetMarketPrice(new QuoteBar(Noon, Symbols.SPY, new Bar(101m, 102m, 100m, 100m), 100, // Bid bar new Bar(103m, 104m, 102m, 102m), 100) // Ask bar ); Assert.AreEqual(0, fill.FillQuantity); Assert.AreEqual(0, fill.FillPrice); Assert.AreEqual(OrderStatus.None, fill.Status); fill = model.Fill(new FillModelParameters( security, order, configProvider, Time.OneHour)).OrderEvent; Assert.AreEqual(0, fill.FillQuantity); Assert.AreEqual(0, fill.FillPrice); Assert.AreEqual(OrderStatus.None, fill.Status); // Time jump => limit reached, holdings sold security.SetMarketPrice(new TradeBar(Noon, Symbols.SPY, 103m, 108m, 103m, 105m, 100)); security.SetMarketPrice(new QuoteBar(Noon, Symbols.SPY, new Bar(103m, 106m, 103m, 105m), 100, // Bid bar new Bar(103m, 108m, 103m, 105m), 100) // Ask bar ); fill = model.LimitIfTouchedFill(security, order); // this fills worst case scenario Assert.AreEqual(order.Quantity, fill.FillQuantity); Assert.AreEqual(order.LimitPrice, fill.FillPrice); Assert.AreEqual(OrderStatus.Filled, fill.Status); }
/// <summary> /// Default limit if touched fill model implementation in base class security. /// </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> /// There is no good way to model limit orders with OHLC because we never know whether the market has /// gapped past our fill price. We have to make the assumption of a fluid, high volume market. /// /// With Limit if Touched orders, whether or not a trigger is surpassed is determined by the high (low) /// of the previous tradebar when making a sell (buy) request. Following the behaviour of /// <see cref="StopLimitFill"/>, current quote information is used when determining fill parameters /// (e.g., price, quantity) as the tradebar containing the incoming data is not yet consolidated. /// This conservative approach, however, can lead to trades not occuring as would be expected when /// compared to future consolidated data. /// </remarks> public virtual OrderEvent LimitIfTouchedFill(Security asset, LimitIfTouchedOrder 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, Parameters.ConfigProvider .GetSubscriptionDataConfigs(asset.Symbol) .IsExtendedMarketHours())) { return(fill); } // Get the range of prices in the last bar: var tradeHigh = 0m; var tradeLow = 0m; var endTimeUtc = DateTime.MinValue; var subscribedTypes = GetSubscribedTypes(asset); if (subscribedTypes.Contains(typeof(Tick))) { var trade = asset.Cache.GetAll <Tick>().LastOrDefault(x => x.TickType == TickType.Trade && x.Price > 0); if (trade != null) { tradeHigh = trade.Price; tradeLow = trade.Price; endTimeUtc = trade.EndTime.ConvertToUtc(asset.Exchange.TimeZone); } } else if (subscribedTypes.Contains(typeof(TradeBar))) { var tradeBar = asset.Cache.GetData <TradeBar>(); if (tradeBar != null) { tradeHigh = tradeBar.High; tradeLow = tradeBar.Low; endTimeUtc = tradeBar.EndTime.ConvertToUtc(asset.Exchange.TimeZone); } } // do not fill on stale data if (endTimeUtc <= order.Time) { return(fill); } //Check if the limit if touched order was filled: switch (order.Direction) { case OrderDirection.Buy: //-> 1.2 Buy: If Price below Trigger, Buy: if (tradeLow <= order.TriggerPrice || order.TriggerTouched) { order.TriggerTouched = true; var askCurrent = GetBestEffortAskPrice(asset, order.Time, out var fillMessage); if (askCurrent <= order.LimitPrice) { fill.Status = OrderStatus.Filled; fill.FillPrice = Math.Min(askCurrent, order.LimitPrice); fill.FillQuantity = order.Quantity; fill.Message = fillMessage; } } break; case OrderDirection.Sell: //-> 1.2 Sell: If Price above Trigger, Sell: if (tradeHigh >= order.TriggerPrice || order.TriggerTouched) { order.TriggerTouched = true; var bidCurrent = GetBestEffortBidPrice(asset, order.Time, out var fillMessage); if (bidCurrent >= order.LimitPrice) { fill.Status = OrderStatus.Filled; fill.FillPrice = Math.Max(bidCurrent, order.LimitPrice); fill.FillQuantity = order.Quantity; fill.Message = fillMessage; } } break; } return(fill); }
public void LimitIfTouchedOrder_SetsDefaultTag(string tag) { var order = new LimitIfTouchedOrder(Symbols.SPY, 1m, 123.4567m, 122.4567m, DateTime.Today, tag); Assert.AreEqual(Invariant($"Trigger Price: {order.TriggerPrice:C} Limit Price: {order.LimitPrice:C}"), order.Tag); }
/// <summary> /// Default limit if touched fill model implementation in base class security. (Limit If Touched Order Type) /// </summary> /// <param name="asset"></param> /// <param name="order"></param> /// <returns>Order fill information detailing the average price and quantity filled.</returns> /// <remarks> /// There is no good way to model limit orders with OHLC because we never know whether the market has /// gapped past our fill price. We have to make the assumption of a fluid, high volume market. /// /// With Limit if Touched orders, whether or not a trigger is surpassed is determined by the high (low) /// of the previous tradebar when making a sell (buy) request. Following the behaviour of /// <see cref="StopLimitFill"/>, current quote information is used when determining fill parameters /// (e.g., price, quantity) as the tradebar containing the incoming data is not yet consolidated. /// This conservative approach, however, can lead to trades not occuring as would be expected when /// compared to future consolidated data. /// </remarks> public virtual OrderEvent LimitIfTouchedFill(Security asset, LimitIfTouchedOrder 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, Parameters.ConfigProvider .GetSubscriptionDataConfigs(asset.Symbol) .IsExtendedMarketHours())) { return(fill); } // Get the range of prices in the last bar: var tradeHigh = 0m; var tradeLow = 0m; var pricesEndTime = DateTime.MinValue; var subscribedTypes = GetSubscribedTypes(asset); if (subscribedTypes.Contains(typeof(Tick))) { var trade = GetPricesCheckingPythonWrapper(asset, order.Direction); if (trade != null) { tradeHigh = trade.Current; tradeLow = trade.Current; pricesEndTime = trade.EndTime.ConvertToUtc(asset.Exchange.TimeZone); } } else if (subscribedTypes.Contains(typeof(TradeBar))) { var tradeBar = asset.Cache.GetData <TradeBar>(); if (tradeBar != null) { tradeHigh = tradeBar.High; tradeLow = tradeBar.Low; pricesEndTime = tradeBar.EndTime.ConvertToUtc(asset.Exchange.TimeZone); } } // do not fill on stale data if (pricesEndTime <= order.Time) { return(fill); } switch (order.Direction) { case OrderDirection.Sell: if (tradeHigh >= order.TriggerPrice || order.TriggerTouched) { order.TriggerTouched = true; //-> 1.1 Limit surpassed: Sell. if (GetAskPrice(asset, out pricesEndTime) >= order.LimitPrice) { fill.Status = OrderStatus.Filled; fill.FillPrice = order.LimitPrice; // assume the order completely filled fill.FillQuantity = order.Quantity; } } break; case OrderDirection.Buy: if (tradeLow <= order.TriggerPrice || order.TriggerTouched) { order.TriggerTouched = true; //-> 1.2 Limit surpassed: Buy. if (GetBidPrice(asset, out pricesEndTime) <= order.LimitPrice) { fill.Status = OrderStatus.Filled; fill.FillPrice = order.LimitPrice; // assume the order completely filled fill.FillQuantity = order.Quantity; } } break; } return(fill); }
public override OrderEvent LimitIfTouchedFill(Security asset, LimitIfTouchedOrder order) { LimitIfTouchFillWasCalled = true; return(base.LimitIfTouchedFill(asset, order)); }