Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Common/Brokerages/BrokerageName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,11 @@ public enum BrokerageName
/// <summary>
/// Transaction and submit/execution rules will use Webull models
/// </summary>
Webull
Webull,

/// <summary>
/// Transaction and submit/execution rules will use Public.com models
/// </summary>
Public
}
}
6 changes: 6 additions & 0 deletions Common/Brokerages/IBrokerageModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ public static IBrokerageModel Create(IOrderProvider orderProvider, BrokerageName
case BrokerageName.Webull:
return new WebullBrokerageModel(accountType);

case BrokerageName.Public:
return new PublicBrokerageModel(accountType);

default:
throw new ArgumentOutOfRangeException(nameof(brokerage), brokerage, null);
}
Expand Down Expand Up @@ -400,6 +403,9 @@ public static BrokerageName GetBrokerageName(IBrokerageModel brokerageModel)
case WebullBrokerageModel:
return BrokerageName.Webull;

case PublicBrokerageModel:
return BrokerageName.Public;

case DefaultBrokerageModel _:
return BrokerageName.Default;

Expand Down
125 changes: 125 additions & 0 deletions Common/Brokerages/PublicBrokerageModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* 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 QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using System.Collections.Generic;

namespace QuantConnect.Brokerages
{
/// <summary>
/// Represents a brokerage model specific to Public.com.
/// </summary>
public class PublicBrokerageModel : DefaultBrokerageModel
{
/// <summary>
/// The security types supported by Public.com.
/// </summary>
private readonly HashSet<SecurityType> _supportSecurityTypes = new(
new[]
{
SecurityType.Equity,
SecurityType.Option,
SecurityType.IndexOption,
SecurityType.Crypto
});

/// <summary>
/// The order types supported by the <see cref="CanSubmitOrder"/> operation in Public.com.
/// Multi-leg combos are limit only.
/// </summary>
private readonly HashSet<OrderType> _supportOrderTypes = new(
new[]
{
OrderType.Market,
OrderType.Limit,
OrderType.StopMarket,
OrderType.StopLimit,
OrderType.ComboLimit
});

/// <summary>
/// Constructor for Public.com brokerage model
/// </summary>
/// <param name="accountType">Cash or Margin</param>
public PublicBrokerageModel(AccountType accountType = AccountType.Margin)
: base(accountType)
{
}

/// <summary>
/// Provides the Public.com fee model
/// </summary>
/// <param name="security">Security</param>
/// <returns>Public.com fee model</returns>
public override IFeeModel GetFeeModel(Security security)
{
return new PublicFeeModel();
}

/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account order type, security type.
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
message = default;

if (!_supportSecurityTypes.Contains(security.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
return false;
}

if (!_supportOrderTypes.Contains(order.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, _supportOrderTypes));
return false;
}

// Public.com handles crossing a zero position natively, so the order is not split or rejected here.
return base.CanSubmitOrder(security, order, out message);
}

/// <summary>
/// Returns true if the brokerage would allow updating the order as specified by the request.
/// Public.com has no multi-leg replace endpoint, so combo orders cannot be updated.
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be updated</param>
/// <param name="request">The requested update to be made to the order</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
/// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
{
if (order.GroupOrderManager != null)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
"Public.com does not support updating combo (multi-leg) orders.");
return false;
}

message = null;
return true;
}
}
}
131 changes: 131 additions & 0 deletions Common/Orders/Fees/PublicFeeModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* 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 QuantConnect.Securities;

namespace QuantConnect.Orders.Fees
{
/// <summary>
/// Represents a fee model specific to Public.com.
/// </summary>
/// <see href="https://public.com/disclosures/fee-schedule"/>
/// <remarks>
/// Equity trades are free during regular market hours and $2.99 per trade during extended hours.
/// Options on stocks and ETFs are free; index options cost $0.50 per contract.
/// Crypto trades carry a fee that depends on the order amount in USD.
/// The model uses the regular member tier and does not detect OTC stocks.
/// </remarks>
public class PublicFeeModel : FeeModel
{
/// <summary>
/// Flat per-trade fee for US-listed equity trades placed during extended market hours.
/// </summary>
private const decimal _extendedHoursEquityFee = 2.99m;

/// <summary>
/// Per-contract fee for index options (regular member tier).
/// </summary>
private const decimal _indexOptionContractFee = 0.50m;

/// <summary>
/// Crypto fee charged on orders above the flat-tier range: 1.25% of the order amount.
/// </summary>
private const decimal _cryptoPercentFee = 0.0125m;

/// <summary>
/// Gets the order fee for a given security and order.
/// </summary>
/// <param name="parameters">The parameters including the security and order details.</param>
/// <returns>A <see cref="OrderFee"/> in USD for the order.</returns>
public override OrderFee GetOrderFee(OrderFeeParameters parameters)
{
var order = parameters.Order;
var security = parameters.Security;

decimal fee;
switch (security.Type)
{
case SecurityType.Equity:
fee = GetEquityFee(security, order);
break;

case SecurityType.IndexOption:
fee = order.AbsoluteQuantity * _indexOptionContractFee;
break;

case SecurityType.Crypto:
fee = GetCryptoFee(security, order);
break;

default:
// Options on stocks and ETFs are commission-free on Public.com.
return OrderFee.Zero;
}

return new OrderFee(new CashAmount(fee, Currencies.USD));
}

/// <summary>
/// Returns the equity fee: free during regular market hours, a flat fee during extended hours.
/// </summary>
/// <param name="security">The traded security.</param>
/// <param name="order">The order, whose time decides whether the trade is during regular hours.</param>
/// <returns>The equity fee in USD.</returns>
private static decimal GetEquityFee(Security security, Order order)
{
var localOrderTime = order.Time.ConvertFromUtc(security.Exchange.TimeZone);
var isRegularHours = security.Exchange.Hours.IsOpen(localOrderTime, extendedMarketHours: false);
return isRegularHours ? 0m : _extendedHoursEquityFee;
}

/// <summary>
/// Returns the crypto fee for the order, tiered by the order amount in USD.
/// </summary>
/// <param name="security">The traded security.</param>
/// <param name="order">The order being placed.</param>
/// <returns>The crypto fee in USD.</returns>
private static decimal GetCryptoFee(Security security, Order order)
{
var orderAmount = security.Price * security.SymbolProperties.ContractMultiplier * order.AbsoluteQuantity;

if (orderAmount <= 10m)
{
return 0.49m;
}
if (orderAmount <= 25m)
{
return 0.69m;
}
if (orderAmount <= 50m)
{
return 1.19m;
}
if (orderAmount <= 100m)
{
return 1.69m;
}
if (orderAmount <= 250m)
{
return 3.29m;
}
if (orderAmount <= 500m)
{
return 6.29m;
}
return orderAmount * _cryptoPercentFee;
}
}
}
21 changes: 21 additions & 0 deletions Launcher/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,12 @@
"tastytrade-password": "",
"tastytrade-account-number": "",

// Public.com configuration
"public-api-url": "https://api.public.com",
"public-secret-key": "",
// Users can have multiple different accounts
"public-account-number": "",

// Exante trading configuration
// client-id, application-id, shared-key are required to access Exante REST API
// Exante generates them at https://exante.eu/clientsarea/dashboard/ after adding an application
Expand Down Expand Up @@ -780,6 +786,21 @@
"history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
},

// defines the 'live-public' environment
"live-public": {
"live-mode": true,

// real brokerage implementations require the BrokerageTransactionHandler
"live-mode-brokerage": "PublicBrokerage",
"data-queue-handler": [ "PublicBrokerage" ],
"setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
"result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
"data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
"real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
"transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
"history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
},

// defines the 'live-dydx' environment
"live-dydx": {
"live-mode": true,
Expand Down
Loading
Loading