diff --git a/Common/Brokerages/PublicBrokerageModel.cs b/Common/Brokerages/PublicBrokerageModel.cs index 34ce59eab824..63b13a08f6e4 100644 --- a/Common/Brokerages/PublicBrokerageModel.cs +++ b/Common/Brokerages/PublicBrokerageModel.cs @@ -96,6 +96,21 @@ public override bool CanSubmitOrder(Security security, Order order, out Brokerag return false; } + // Public.com only accepts Limit orders in the extended (outside regular trading hours) session. + if (order.Properties is PublicOrderProperties { OutsideRegularTradingHours: true } && order.Type != OrderType.Limit) + { + message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", + Messages.PublicBrokerageModel.ExtendedMarketOrderMustBeLimit(order)); + return false; + } + + if (order.Properties is PublicOrderProperties publicOrderProperties) + { + // A cash account has no margin buying power, so margin is always off there. + // On a margin account, keep an explicit choice and otherwise use margin by default. + publicOrderProperties.UseMargin = AccountType != AccountType.Cash && (publicOrderProperties.UseMargin ?? true); + } + // Public.com handles crossing a zero position natively, so the order is not split or rejected here. return base.CanSubmitOrder(security, order, out message); } diff --git a/Common/Messages/Messages.Brokerages.cs b/Common/Messages/Messages.Brokerages.cs index dcc74d469230..fbdb8ee51da0 100644 --- a/Common/Messages/Messages.Brokerages.cs +++ b/Common/Messages/Messages.Brokerages.cs @@ -628,6 +628,21 @@ public static string MarketOrdersNotSupportedOutsideRegularTradingHours() } } + /// + /// Provides user-facing messages for the class and its consumers or related classes + /// + public static class PublicBrokerageModel + { + /// + /// Returns a message explaining that orders for the extended market must be Limit orders with Day time-in-force. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ExtendedMarketOrderMustBeLimit(Orders.Order order) + { + return Invariant($"Orders for extended market must be of type '{nameof(OrderType.Limit)}' and with 'DAY' time-in-force, but {order.Type} was specified."); + } + } + /// /// Provides user-facing messages for the class and its consumers or related classes /// diff --git a/Common/Orders/PublicOrderProperties.cs b/Common/Orders/PublicOrderProperties.cs new file mode 100644 index 000000000000..a962f62dc7c5 --- /dev/null +++ b/Common/Orders/PublicOrderProperties.cs @@ -0,0 +1,40 @@ +/* + * 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. + * +*/ + +namespace QuantConnect.Orders +{ + /// + /// Represents the properties of an order in Public.com. + /// + public class PublicOrderProperties : OrderProperties + { + /// + /// If set to true, allows the order to trigger or fill outside of regular trading hours + /// (the extended session). + /// + /// + /// Applicable to day time-in-force equity orders only. + /// + public bool OutsideRegularTradingHours { get; set; } + + /// + /// Controls the buying power used by the order. + /// true uses margin buying power when the account allows it; false uses cash-only buying power. + /// When left null, the brokerage model fills it in from the account type before the order is sent. + /// + public bool? UseMargin { get; set; } + } +} diff --git a/Tests/Common/Brokerages/PublicBrokerageModelTests.cs b/Tests/Common/Brokerages/PublicBrokerageModelTests.cs index 706b65e34fe0..87ee718cbdb9 100644 --- a/Tests/Common/Brokerages/PublicBrokerageModelTests.cs +++ b/Tests/Common/Brokerages/PublicBrokerageModelTests.cs @@ -119,6 +119,28 @@ public void CanSubmitCrossZeroOrderReturnsTrue(OrderType orderType, decimal hold Assert.That(message, Is.Null); } + // A margin account keeps an explicit UseMargin choice and otherwise uses margin by default; + // a cash account has no margin buying power, so UseMargin is always forced off there. + [TestCase(AccountType.Margin, null, true)] + [TestCase(AccountType.Margin, true, true)] + [TestCase(AccountType.Margin, false, false)] + [TestCase(AccountType.Cash, null, false)] + [TestCase(AccountType.Cash, true, false)] + [TestCase(AccountType.Cash, false, false)] + public void CanSubmitOrderResolvesUseMarginFromAccountType(AccountType accountType, bool? requestedUseMargin, bool expectedUseMargin) + { + var brokerageModel = new PublicBrokerageModel(accountType); + var security = GetSecurityForType(SecurityType.Equity); + var properties = new PublicOrderProperties { UseMargin = requestedUseMargin }; + var order = new LimitOrder(security.Symbol, 1m, 100m, DateTime.UtcNow, properties: properties); + + var canSubmit = brokerageModel.CanSubmitOrder(security, order, out var message); + + Assert.That(canSubmit, Is.True); + Assert.That(message, Is.Null); + Assert.That(properties.UseMargin, Is.EqualTo(expectedUseMargin)); + } + [Test] public void CanUpdateOrderSingleOrderReturnsTrue() {