diff --git a/Algorithm.CSharp/PeggedToMidpointRegressionAlgorithm.cs b/Algorithm.CSharp/PeggedToMidpointRegressionAlgorithm.cs new file mode 100644 index 000000000000..919294d53b9f --- /dev/null +++ b/Algorithm.CSharp/PeggedToMidpointRegressionAlgorithm.cs @@ -0,0 +1,145 @@ +/* + * 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.Collections.Generic; +using QuantConnect.Data; +using QuantConnect.Interfaces; +using QuantConnect.Orders; + +namespace QuantConnect.Algorithm.CSharp +{ + /// + /// Basic algorithm demonstrating how to place Pegged-to-Midpoint orders. + /// + /// + /// + /// + public class PeggedToMidpointRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition + { + private Symbol _symbol; + private OrderTicket _buyOrderTicket; + private OrderTicket _sellOrderTicket; + protected virtual bool AsynchronousOrders => false; + + public override void Initialize() + { + SetStartDate(2013, 10, 07); + SetEndDate(2013, 10, 11); + SetCash(100000); + _symbol = AddEquity("SPY").Symbol; + } + + public override void OnData(Slice slice) + { + if (!slice.ContainsKey(_symbol)) + { + return; + } + + if (_buyOrderTicket == null) + { + _buyOrderTicket = PeggedToMidpointOrder(_symbol, 1, limitPrice: 0m, limitPriceOffset: 0m, + asynchronous: AsynchronousOrders); + } + else if (_sellOrderTicket == null && Portfolio.Invested) + { + _sellOrderTicket = PeggedToMidpointOrder(_symbol, -1, limitPrice: 0m, limitPriceOffset: 0m, + asynchronous: AsynchronousOrders); + } + } + + public override void OnOrderEvent(OrderEvent orderEvent) + { + if (orderEvent.Status != OrderStatus.Filled) + { + return; + } + + var order = Transactions.GetOrderById(orderEvent.OrderId); + if (order is not Orders.PeggedToMidpointOrder) + { + throw new RegressionTestException($"Expected PeggedToMidpointOrder but got {order.GetType().Name}"); + } + } + + public override void OnEndOfAlgorithm() + { + if (_sellOrderTicket?.Status != OrderStatus.Filled) + { + throw new RegressionTestException("Expected sell PeggedToMidpoint order to be filled by end of algorithm."); + } + } + + /// + /// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm. + /// + public bool CanRunLocally => true; + + /// + /// This is used by the regression test system to indicate which languages this algorithm is written in. + /// + public List Languages { get; } = new() { Language.CSharp, Language.Python }; + + /// + /// Data Points count of all timeslices of algorithm + /// + public long DataPoints => 3943; + + /// + /// Data Points count of the algorithm history + /// + public int AlgorithmHistoryDataPoints => 0; + + /// + /// Final status of the algorithm + /// + public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed; + + /// + /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm + /// + public Dictionary ExpectedStatistics => new Dictionary + { + {"Total Orders", "2"}, + {"Average Win", "0%"}, + {"Average Loss", "0.00%"}, + {"Compounding Annual Return", "-0.198%"}, + {"Drawdown", "0.000%"}, + {"Expectancy", "-1"}, + {"Start Equity", "100000"}, + {"End Equity", "99997.46"}, + {"Net Profit", "-0.003%"}, + {"Sharpe Ratio", "-108.977"}, + {"Sortino Ratio", "0"}, + {"Probabilistic Sharpe Ratio", "1.216%"}, + {"Loss Rate", "100%"}, + {"Win Rate", "0%"}, + {"Profit-Loss Ratio", "0"}, + {"Alpha", "-0.009"}, + {"Beta", "0"}, + {"Annual Standard Deviation", "0"}, + {"Annual Variance", "0"}, + {"Information Ratio", "-8.915"}, + {"Tracking Error", "0.222"}, + {"Treynor Ratio", "-32.005"}, + {"Total Fees", "$2.00"}, + {"Estimated Strategy Capacity", "$17000000.00"}, + {"Lowest Capacity Asset", "SPY R735QTJ8XC9X"}, + {"Portfolio Turnover", "0.06%"}, + {"Drawdown Recovery", "0"}, + {"OrderListHash", "d310603dec0f507a6b9b8a9eb21c4717"} + }; + } +} diff --git a/Algorithm.Python/PeggedToMidpointRegressionAlgorithm.py b/Algorithm.Python/PeggedToMidpointRegressionAlgorithm.py new file mode 100644 index 000000000000..0ca5df963c54 --- /dev/null +++ b/Algorithm.Python/PeggedToMidpointRegressionAlgorithm.py @@ -0,0 +1,52 @@ +# 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. + +from AlgorithmImports import * + +### +### Basic algorithm demonstrating how to place Pegged-to-Midpoint orders. +### +### +### +### +class PeggedToMidpointRegressionAlgorithm(QCAlgorithm): + + _buy_order_ticket = None + _sell_order_ticket = None + + def initialize(self): + self.set_start_date(2013, 10, 7) + self.set_end_date(2013, 10, 11) + self.set_cash(100000) + self._symbol = self.add_equity("SPY").symbol + + def on_data(self, data): + if not data.contains_key(self._symbol): + return + + if self._buy_order_ticket is None: + self._buy_order_ticket = self.pegged_to_midpoint_order(self._symbol, 1, limit_price=0, limit_price_offset=0) + elif self._sell_order_ticket is None and self.portfolio.invested: + self._sell_order_ticket = self.pegged_to_midpoint_order(self._symbol, -1, limit_price=0, limit_price_offset=0) + + def on_order_event(self, order_event): + if order_event.status != OrderStatus.FILLED: + return + + order = self.transactions.get_order_by_id(order_event.order_id) + if not isinstance(order, PeggedToMidpointOrder): + raise AssertionError(f"Expected PeggedToMidpointOrder but got {type(order).__name__}") + + def on_end_of_algorithm(self): + if self._sell_order_ticket is None or self._sell_order_ticket.status != OrderStatus.FILLED: + raise AssertionError("Expected sell PeggedToMidpoint order to be filled by end of algorithm.") diff --git a/Algorithm/QCAlgorithm.Trading.cs b/Algorithm/QCAlgorithm.Trading.cs index 2956504f09b9..9b257dbfd25d 100644 --- a/Algorithm/QCAlgorithm.Trading.cs +++ b/Algorithm/QCAlgorithm.Trading.cs @@ -723,6 +723,64 @@ public OrderTicket LimitIfTouchedOrder(Symbol symbol, decimal quantity, decimal return SubmitOrderRequest(request); } + /// + /// Send a pegged-to-midpoint order to the transaction handler: + /// + /// Symbol for the asset + /// Quantity of shares for the order + /// Optional limit price cap (buy) or floor (sell) + /// Offset from the midpoint + /// Send the order asynchronously (false). Otherwise we'll block until it is fully submitted + /// String tag for the order (optional) + /// The order properties to use. Defaults to + /// The order ticket instance. + public OrderTicket PeggedToMidpointOrder(Symbol symbol, int quantity, decimal limitPrice, decimal limitPriceOffset, + bool asynchronous = false, string tag = "", IOrderProperties orderProperties = null) + { + return PeggedToMidpointOrder(symbol, (decimal)quantity, limitPrice, limitPriceOffset, asynchronous, tag, orderProperties); + } + + /// + /// Send a pegged-to-midpoint order to the transaction handler: + /// + /// Symbol for the asset + /// Quantity of shares for the order + /// Optional limit price cap (buy) or floor (sell) + /// Offset from the midpoint + /// Send the order asynchronously (false). Otherwise we'll block until it is fully submitted + /// String tag for the order (optional) + /// The order properties to use. Defaults to + /// The order ticket instance. + public OrderTicket PeggedToMidpointOrder(Symbol symbol, double quantity, decimal limitPrice, decimal limitPriceOffset, + bool asynchronous = false, string tag = "", IOrderProperties orderProperties = null) + { + return PeggedToMidpointOrder(symbol, quantity.SafeDecimalCast(), limitPrice, limitPriceOffset, asynchronous, tag, orderProperties); + } + + /// + /// Send a pegged-to-midpoint order to the transaction handler: + /// + /// Symbol for the asset + /// Quantity of shares for the order + /// Optional limit price cap (buy) or floor (sell) + /// Offset from the midpoint + /// Send the order asynchronously (false). Otherwise we'll block until it is fully submitted + /// String tag for the order (optional) + /// The order properties to use. Defaults to + /// The order ticket instance. + [DocumentationAttribute(TradingAndOrders)] + public OrderTicket PeggedToMidpointOrder(Symbol symbol, decimal quantity, decimal limitPrice, decimal limitPriceOffset, + bool asynchronous = false, string tag = "", IOrderProperties orderProperties = null) + { + var security = Securities[symbol]; + var request = CreateSubmitOrderRequest(OrderType.PeggedToMidpoint, security, quantity, tag, + limitPrice: limitPrice, triggerPrice: limitPriceOffset, + properties: orderProperties ?? DefaultOrderProperties?.Clone(), + asynchronous: asynchronous); + + return SubmitOrderRequest(request); + } + /// /// Send an exercise order to the transaction handler /// diff --git a/Common/Brokerages/InteractiveBrokersBrokerageModel.cs b/Common/Brokerages/InteractiveBrokersBrokerageModel.cs index 80f193f6eafb..c2a5939fe177 100644 --- a/Common/Brokerages/InteractiveBrokersBrokerageModel.cs +++ b/Common/Brokerages/InteractiveBrokersBrokerageModel.cs @@ -83,6 +83,7 @@ public class InteractiveBrokersBrokerageModel : DefaultBrokerageModel OrderType.StopLimit, OrderType.TrailingStop, OrderType.LimitIfTouched, + OrderType.PeggedToMidpoint, OrderType.ComboMarket, OrderType.ComboLimit, OrderType.ComboLegLimit, diff --git a/Common/Orders/Fills/FillModel.cs b/Common/Orders/Fills/FillModel.cs index 2ea61602b6b0..1d16feb9f038 100644 --- a/Common/Orders/Fills/FillModel.cs +++ b/Common/Orders/Fills/FillModel.cs @@ -118,6 +118,11 @@ public virtual Fill Fill(FillModelParameters parameters) ? PythonWrapper.ComboLegLimitFill(parameters.Order, parameters) : ComboLegLimitFill(parameters.Order, parameters); break; + case OrderType.PeggedToMidpoint: + orderEvents.Add(PythonWrapper != null + ? PythonWrapper.PeggedToMidpointFill(parameters.Security, parameters.Order as PeggedToMidpointOrder) + : PeggedToMidpointFill(parameters.Security, parameters.Order as PeggedToMidpointOrder)); + break; default: throw new ArgumentOutOfRangeException(); } @@ -644,6 +649,59 @@ public virtual OrderEvent LimitIfTouchedFill(Security asset, LimitIfTouchedOrder return fill; } + /// + /// Pegged to midpoint fill model. Fills at the NBBO midpoint adjusted by . + /// If is set it acts as a cap for buy orders and a floor for sell orders; + /// the order will not fill if the effective midpoint price would exceed the cap (buy) or fall below the floor (sell). + /// + /// Security asset we're filling + /// Order packet to model + /// Order fill information detailing the average price and quantity filled. + public virtual OrderEvent PeggedToMidpointFill(Security asset, PeggedToMidpointOrder order) + { + var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone); + var fill = new OrderEvent(order, utcTime, OrderFee.Zero); + + if (order.Status == OrderStatus.Canceled) return fill; + + if (!IsExchangeOpen(asset)) return fill; + + var askPrice = GetAskPrice(asset, out var askEndTime); + var bidPrice = GetBidPrice(asset, out var bidEndTime); + var pricesEndTime = askEndTime > bidEndTime ? askEndTime : bidEndTime; + + // Do not fill on stale data + if (pricesEndTime <= order.Time) return fill; + + var midpoint = (bidPrice + askPrice) / 2m; + + switch (order.Direction) + { + case OrderDirection.Buy: + { + var fillPrice = midpoint + order.LimitPriceOffset; + // LimitPrice acts as a cap: skip fill if effective price exceeds it + if (order.LimitPrice > 0 && fillPrice > order.LimitPrice) break; + fill.Status = OrderStatus.Filled; + fill.FillPrice = fillPrice; + fill.FillQuantity = order.Quantity; + break; + } + case OrderDirection.Sell: + { + var fillPrice = midpoint - order.LimitPriceOffset; + // LimitPrice acts as a floor: skip fill if effective price falls below it + if (order.LimitPrice > 0 && fillPrice < order.LimitPrice) break; + fill.Status = OrderStatus.Filled; + fill.FillPrice = fillPrice; + fill.FillQuantity = order.Quantity; + break; + } + } + + return fill; + } + /// /// Default limit order fill model in the base security class. /// diff --git a/Common/Orders/Order.cs b/Common/Orders/Order.cs index dfddcba0655c..87d41ed6ec4c 100644 --- a/Common/Orders/Order.cs +++ b/Common/Orders/Order.cs @@ -472,6 +472,10 @@ private static Order CreateOrder(int orderId, OrderType type, Symbol symbol, dec order = new ComboMarketOrder(symbol, quantity, time, groupOrderManager, tag, properties); break; + case OrderType.PeggedToMidpoint: + order = new PeggedToMidpointOrder(symbol, quantity, limitPrice, triggerPrice, time, tag, properties); + break; + default: throw new ArgumentOutOfRangeException(); } diff --git a/Common/Orders/OrderJsonConverter.cs b/Common/Orders/OrderJsonConverter.cs index 96b64eb2a5ad..e43fa22beed3 100644 --- a/Common/Orders/OrderJsonConverter.cs +++ b/Common/Orders/OrderJsonConverter.cs @@ -319,6 +319,14 @@ private static Order CreateOrder(OrderType orderType, JObject jObject) }; break; + case OrderType.PeggedToMidpoint: + order = new PeggedToMidpointOrder + { + LimitPrice = jObject["limitPrice"]?.Value() ?? jObject["LimitPrice"]?.Value() ?? default(decimal), + LimitPriceOffset = jObject["limitPriceOffset"]?.Value() ?? jObject["LimitPriceOffset"]?.Value() ?? default(decimal) + }; + break; + default: throw new ArgumentOutOfRangeException(); } diff --git a/Common/Orders/OrderTypes.cs b/Common/Orders/OrderTypes.cs index d37be01542ef..80c53065d07f 100644 --- a/Common/Orders/OrderTypes.cs +++ b/Common/Orders/OrderTypes.cs @@ -78,7 +78,12 @@ public enum OrderType /// /// Trailing Stop Order Type - (11) /// - TrailingStop + TrailingStop, + + /// + /// Pegged to Midpoint Order Type - (12) + /// + PeggedToMidpoint } /// diff --git a/Common/Orders/PeggedToMidpointOrder.cs b/Common/Orders/PeggedToMidpointOrder.cs new file mode 100644 index 000000000000..36e18ec33718 --- /dev/null +++ b/Common/Orders/PeggedToMidpointOrder.cs @@ -0,0 +1,105 @@ +/* + * 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 Newtonsoft.Json; +using QuantConnect.Interfaces; +using QuantConnect.Securities; + +namespace QuantConnect.Orders +{ + /// + /// Pegged to midpoint order type definition + /// + public class PeggedToMidpointOrder : Order + { + /// + /// Limit price for this order. + /// + [JsonProperty(PropertyName = "limitPrice")] + public decimal LimitPrice { get; internal set; } + + /// + /// Offset for this order. + /// + [JsonProperty(PropertyName = "limitPriceOffset")] + public decimal LimitPriceOffset { get; internal set; } + + /// + /// Pegged To Midpoint Order Type + /// + public override OrderType Type + { + get { return OrderType.PeggedToMidpoint; } + } + + /// + /// Added a default constructor for JSON Deserialization: + /// + public PeggedToMidpointOrder() + { + } + + /// + /// New Pegged To Midpoint order constructor + /// + /// Symbol asset we're seeking to trade + /// Quantity of the asset we're seeking to trade + /// Time the order was placed + /// Price the order should be filled at if a limit order + /// Offset to the midpoint + /// User defined data tag for this order + /// The order properties for this order + public PeggedToMidpointOrder(Symbol symbol, decimal quantity, decimal limitPrice, decimal limitPriceOffset, DateTime time, string tag = "", IOrderProperties properties = null) + : base(symbol, quantity, time, tag, properties) + { + LimitPrice = limitPrice; + LimitPriceOffset = limitPriceOffset; + } + + /// + /// Gets the order value in units of the security's quote currency + /// + /// The security matching this order's symbol + protected override decimal GetValueImpl(Security security) + { + return LimitOrder.CalculateOrderValue(Quantity, LimitPrice, security.Price); + } + + /// + /// Modifies the state of this order to match the update request + /// + /// The request to update this order object + public override void ApplyUpdateOrderRequest(UpdateOrderRequest request) + { + base.ApplyUpdateOrderRequest(request); + if (request.LimitPrice.HasValue) + { + LimitPrice = request.LimitPrice.Value; + } + } + + /// + /// Creates a deep-copy clone of this order + /// + /// A copy of this order + public override Order Clone() + { + var order = new PeggedToMidpointOrder { LimitPrice = LimitPrice, LimitPriceOffset = LimitPriceOffset }; + CopyTo(order); + return order; + } + } +} diff --git a/Common/Python/FillModelPythonWrapper.cs b/Common/Python/FillModelPythonWrapper.cs index 0c4ba6c5b08c..0ed3fe1e8b2d 100644 --- a/Common/Python/FillModelPythonWrapper.cs +++ b/Common/Python/FillModelPythonWrapper.cs @@ -140,6 +140,17 @@ public override OrderEvent TrailingStopFill(Security asset, TrailingStopOrder or return _model.InvokeMethod(nameof(TrailingStopFill), asset, order); } + /// + /// Pegged to Midpoint Fill Model. Return an order event with the fill details. + /// + /// Asset we're trading this order + /// Pegged to Midpoint Order to fill + /// Order fill information detailing the average price and quantity filled. + public override OrderEvent PeggedToMidpointFill(Security asset, PeggedToMidpointOrder order) + { + return _model.InvokeMethod(nameof(PeggedToMidpointFill), asset, order); + } + /// /// Default combo market fill model for the base security class. Fills at the last traded price for each leg. /// diff --git a/Tests/Brokerages/PeggedToMidpointOrderTestParameters.cs b/Tests/Brokerages/PeggedToMidpointOrderTestParameters.cs new file mode 100644 index 000000000000..dee5d2659a72 --- /dev/null +++ b/Tests/Brokerages/PeggedToMidpointOrderTestParameters.cs @@ -0,0 +1,87 @@ +/* + * 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 QuantConnect.Interfaces; +using QuantConnect.Orders; + +namespace QuantConnect.Tests.Brokerages +{ + public class PeggedToMidpointOrderTestParameters : OrderTestParameters + { + private readonly decimal _limitPrice; + private readonly decimal _limitPriceOffset; + + public PeggedToMidpointOrderTestParameters( + Symbol symbol, + decimal limitPrice, + decimal limitPriceOffset = 0m, + IOrderProperties properties = null, + OrderSubmissionData orderSubmissionData = null) + : base(symbol, properties, orderSubmissionData) + { + _limitPrice = limitPrice; + _limitPriceOffset = limitPriceOffset; + } + + public override Order CreateShortOrder(decimal quantity) + { + return new PeggedToMidpointOrder(Symbol, -Math.Abs(quantity), _limitPrice, _limitPriceOffset, DateTime.UtcNow, + properties: Properties) + { + Status = OrderStatus.New, + OrderSubmissionData = OrderSubmissionData, + PriceCurrency = GetSymbolProperties(Symbol).QuoteCurrency + }; + } + + public override Order CreateLongOrder(decimal quantity) + { + return new PeggedToMidpointOrder(Symbol, Math.Abs(quantity), _limitPrice, _limitPriceOffset, DateTime.UtcNow, + properties: Properties) + { + Status = OrderStatus.New, + OrderSubmissionData = OrderSubmissionData, + PriceCurrency = GetSymbolProperties(Symbol).QuoteCurrency + }; + } + + public override bool ModifyOrderToFill(IBrokerage brokerage, Order order, decimal lastMarketPrice) + { + var peg = (PeggedToMidpointOrder)order; + var previous = peg.LimitPrice; + // Adjust the limit price cap/floor toward market price to allow the peg to fill + if (order.Quantity > 0) + { + peg.LimitPrice = Math.Max(peg.LimitPrice, lastMarketPrice); + } + else + { + peg.LimitPrice = Math.Min(peg.LimitPrice, lastMarketPrice); + } + return peg.LimitPrice != previous; + } + + // PEG MID orders are submitted to the exchange and won't fill immediately in test + public override OrderStatus ExpectedStatus => OrderStatus.Submitted; + + public override bool ExpectedCancellationResult => true; + + public override string ToString() + { + return $"{OrderType.PeggedToMidpoint}: {SecurityType}, {Symbol}"; + } + } +} diff --git a/Tests/Common/Brokerages/InteractiveBrokersBrokerageModelTests.cs b/Tests/Common/Brokerages/InteractiveBrokersBrokerageModelTests.cs index fecf694da5d8..5e03c55d4165 100644 --- a/Tests/Common/Brokerages/InteractiveBrokersBrokerageModelTests.cs +++ b/Tests/Common/Brokerages/InteractiveBrokersBrokerageModelTests.cs @@ -229,6 +229,19 @@ public void CanSubmitCfdOrder() Assert.IsTrue(canSubmit); } + [TestCase("SPY", SecurityType.Equity)] + [TestCase("SPY", SecurityType.Option)] + public void CanSubmitPeggedToMidpointOrder(string ticker, SecurityType securityType) + { + var algo = new AlgorithmStub(); + var security = algo.AddSecurity(securityType, ticker); + var order = new PeggedToMidpointOrder(security.Symbol, 1, 100m, 0.01m, new DateTime(2024, 1, 1)); + + var result = _interactiveBrokersBrokerageModel.CanSubmitOrder(security, order, out var message); + + Assert.IsTrue(result); + } + private static List GetUnsupportedOptions() { // Index option diff --git a/Tests/Common/Orders/OrderTests.cs b/Tests/Common/Orders/OrderTests.cs index 4d5d65d81079..5ed657acd384 100644 --- a/Tests/Common/Orders/OrderTests.cs +++ b/Tests/Common/Orders/OrderTests.cs @@ -80,6 +80,15 @@ public void TrailingStopOrder_UpdatesStopPriceIfNecessary(OrderDirection directi } } + [Test] + public void PeggedToMidpointOrder_ClonesCorrectly() + { + var order = new PeggedToMidpointOrder(Symbols.SPY, 100, 10, 2, DateTime.Now); + var clone = (PeggedToMidpointOrder)order.Clone(); + Assert.AreEqual(order.LimitPrice, clone.LimitPrice); + Assert.AreEqual(order.LimitPriceOffset, clone.LimitPriceOffset); + } + private static TestCaseData[] GetValueTestParameters() { const decimal delta = 1m; @@ -165,6 +174,8 @@ private static TestCaseData[] GetValueTestParameters() new ValueTestParameters("EquityShortTrailingStopOrderPriceMinusDelta", equity, new TrailingStopOrder(Symbols.SPY, -quantity, priceMinusDelta, 0.1m, true, time), -quantity*price), new ValueTestParameters("EquityLongLimitIfTouchedOrder", equity, new LimitIfTouchedOrder(Symbols.SPY, quantity, 1.5m*pricePlusDelta, priceMinusDelta, time), quantity*priceMinusDelta), new ValueTestParameters("EquityShortLimitIfTouchedOrder", equity, new LimitIfTouchedOrder(Symbols.SPY, -quantity, .5m*priceMinusDelta, pricePlusDelta, time), -quantity*pricePlusDelta), + new ValueTestParameters("EquityLongPeggedToMidpointOrder", equity, new PeggedToMidpointOrder(Symbols.SPY, quantity, priceMinusDelta, 0, time), quantity*priceMinusDelta), + new ValueTestParameters("EquityShortPeggedToMidpointOrder", equity, new PeggedToMidpointOrder(Symbols.SPY, -quantity, pricePlusDelta, 0, time), -quantity*pricePlusDelta), // forex orders new ValueTestParameters("ForexLongMarketOrder", forex, new MarketOrder(Symbols.EURGBP, quantity, time), quantity*price*forex.QuoteCurrency.ConversionRate), diff --git a/Tests/Engine/BrokerageTransactionHandlerTests/BrokerageTransactionHandlerTests.cs b/Tests/Engine/BrokerageTransactionHandlerTests/BrokerageTransactionHandlerTests.cs index ffb9fc557da2..d664bccbcf1c 100644 --- a/Tests/Engine/BrokerageTransactionHandlerTests/BrokerageTransactionHandlerTests.cs +++ b/Tests/Engine/BrokerageTransactionHandlerTests/BrokerageTransactionHandlerTests.cs @@ -89,6 +89,7 @@ private static SubmitOrderRequest MakeOrderRequest(Security security, OrderType OrderType.ComboLimit => new SubmitOrderRequest(OrderType.ComboLimit, security.Type, security.Symbol, 1, 295, 0, date, "", groupOrderManager: groupOrderManager), OrderType.ComboLegLimit => new SubmitOrderRequest(OrderType.ComboLegLimit, security.Type, security.Symbol, 1, 295, 0, date, "", groupOrderManager: groupOrderManager), OrderType.TrailingStop => new SubmitOrderRequest(OrderType.TrailingStop, security.Type, security.Symbol, 1, 305, 0, 305, date, ""), + OrderType.PeggedToMidpoint => new SubmitOrderRequest(OrderType.PeggedToMidpoint, security.Type, security.Symbol, 1, 0, 300, 0, date, ""), _ => throw new ArgumentOutOfRangeException(nameof(orderType), orderType, null) }; }