diff --git a/Common/Brokerages/BrokerageName.cs b/Common/Brokerages/BrokerageName.cs
index 7f6edaab7a5e..9a1b7a994c71 100644
--- a/Common/Brokerages/BrokerageName.cs
+++ b/Common/Brokerages/BrokerageName.cs
@@ -197,6 +197,11 @@ public enum BrokerageName
///
/// Transaction and submit/execution rules will use dYdX models
///
- DYDX
+ DYDX,
+
+ ///
+ /// Transaction and submit/execution rules will use Webull models
+ ///
+ Webull
}
}
diff --git a/Common/Brokerages/IBrokerageModel.cs b/Common/Brokerages/IBrokerageModel.cs
index 4544c388d8c1..c8c43f2ff69a 100644
--- a/Common/Brokerages/IBrokerageModel.cs
+++ b/Common/Brokerages/IBrokerageModel.cs
@@ -291,6 +291,9 @@ public static IBrokerageModel Create(IOrderProvider orderProvider, BrokerageName
case BrokerageName.DYDX:
return new dYdXBrokerageModel(accountType);
+ case BrokerageName.Webull:
+ return new WebullBrokerageModel(accountType);
+
default:
throw new ArgumentOutOfRangeException(nameof(brokerage), brokerage, null);
}
@@ -394,6 +397,9 @@ public static BrokerageName GetBrokerageName(IBrokerageModel brokerageModel)
case TastytradeBrokerageModel:
return BrokerageName.Tastytrade;
+ case WebullBrokerageModel:
+ return BrokerageName.Webull;
+
case DefaultBrokerageModel _:
return BrokerageName.Default;
diff --git a/Common/Brokerages/WebullBrokerageModel.cs b/Common/Brokerages/WebullBrokerageModel.cs
new file mode 100644
index 000000000000..f69870b647b1
--- /dev/null
+++ b/Common/Brokerages/WebullBrokerageModel.cs
@@ -0,0 +1,163 @@
+/*
+ * 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.Orders;
+using QuantConnect.Orders.Fees;
+using QuantConnect.Orders.TimeInForces;
+using QuantConnect.Securities;
+
+namespace QuantConnect.Brokerages
+{
+ ///
+ /// Represents a brokerage model specific to Webull.
+ ///
+ public class WebullBrokerageModel : DefaultBrokerageModel
+ {
+ ///
+ /// Maps each supported security type to the order types Webull allows for it.
+ ///
+ private static readonly Dictionary> _supportedOrderTypesBySecurityType =
+ new Dictionary>
+ {
+ {
+ SecurityType.Equity, new HashSet
+ {
+ OrderType.Market,
+ OrderType.Limit,
+ OrderType.StopMarket,
+ OrderType.StopLimit,
+ OrderType.TrailingStop
+ }
+ },
+ {
+ SecurityType.Option, new HashSet
+ {
+ OrderType.Market,
+ OrderType.Limit,
+ OrderType.StopMarket,
+ OrderType.StopLimit
+ }
+ },
+ {
+ SecurityType.IndexOption, new HashSet
+ {
+ OrderType.Market,
+ OrderType.Limit,
+ OrderType.StopMarket,
+ OrderType.StopLimit
+ }
+ },
+ {
+ SecurityType.Future, new HashSet
+ {
+ OrderType.Market,
+ OrderType.Limit,
+ OrderType.StopMarket,
+ OrderType.StopLimit,
+ OrderType.TrailingStop
+ }
+ },
+ {
+ SecurityType.Crypto, new HashSet
+ {
+ OrderType.Market,
+ OrderType.Limit,
+ OrderType.StopLimit
+ }
+ }
+ };
+
+ ///
+ /// Constructor for Webull brokerage model.
+ ///
+ /// Cash or Margin
+ public WebullBrokerageModel(AccountType accountType = AccountType.Margin)
+ : base(accountType)
+ {
+ }
+
+ ///
+ /// Provides the Webull fee model.
+ ///
+ /// Security
+ /// Webull fee model
+ public override IFeeModel GetFeeModel(Security security)
+ {
+ return new WebullFeeModel();
+ }
+
+ ///
+ /// Returns true if the brokerage could accept this order. This takes into account
+ /// order type, security type, and order size limits.
+ ///
+ ///
+ /// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit.
+ ///
+ /// The security of the order
+ /// The order to be processed
+ /// If this function returns false, a brokerage message detailing why the order may not be submitted
+ /// True if the brokerage could process the order, false otherwise
+ public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
+ {
+ message = default;
+
+ if (!_supportedOrderTypesBySecurityType.TryGetValue(security.Type, out var supportedOrderTypes))
+ {
+ message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
+ Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
+ return false;
+ }
+
+ if (!supportedOrderTypes.Contains(order.Type))
+ {
+ message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
+ Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, supportedOrderTypes));
+ return false;
+ }
+
+ // Options and IndexOptions have per-direction TimeInForce restrictions.
+ // https://developer.webull.com/apis/docs/trade-api/options#time-in-force
+ // - Sell orders: Day only
+ // - Buy orders: GoodTilCanceled only
+ if (security.Type == SecurityType.Option || security.Type == SecurityType.IndexOption)
+ {
+ if (order.Direction == OrderDirection.Sell && order.TimeInForce is not DayTimeInForce)
+ {
+ message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
+ Messages.WebullBrokerageModel.InvalidTimeInForceForOptionSellOrder(order));
+ return false;
+ }
+
+ if (order.Direction == OrderDirection.Buy && order.TimeInForce is not GoodTilCanceledTimeInForce)
+ {
+ message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
+ Messages.WebullBrokerageModel.InvalidTimeInForceForOptionBuyOrder(order));
+ return false;
+ }
+ }
+
+ if (order.Properties is WebullOrderProperties { OutsideRegularTradingHours: true } &&
+ security.Type != SecurityType.Equity)
+ {
+ message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
+ Messages.WebullBrokerageModel.OutsideRegularTradingHoursNotSupportedForSecurityType(security));
+ return false;
+ }
+
+ return base.CanSubmitOrder(security, order, out message);
+ }
+ }
+}
diff --git a/Common/Messages/Messages.Brokerages.cs b/Common/Messages/Messages.Brokerages.cs
index b1095cacbef5..30bd315e8a46 100644
--- a/Common/Messages/Messages.Brokerages.cs
+++ b/Common/Messages/Messages.Brokerages.cs
@@ -595,6 +595,39 @@ public static string UnsupportedOrderType(Orders.Order order)
}
}
+ ///
+ /// Provides user-facing messages for the class and its consumers or related classes
+ ///
+ public static class WebullBrokerageModel
+ {
+ ///
+ /// Returns a message explaining that Options and IndexOptions sell orders only support Day time in force.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string InvalidTimeInForceForOptionSellOrder(Orders.Order order)
+ {
+ return Invariant($"{order.Symbol.SecurityType} sell orders only support {nameof(DayTimeInForce)} time in force, but {order.TimeInForce.GetType().Name} was specified.");
+ }
+
+ ///
+ /// Returns a message explaining that Options and IndexOptions buy orders only support GoodTilCanceled time in force.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string InvalidTimeInForceForOptionBuyOrder(Orders.Order order)
+ {
+ return Invariant($"{order.Symbol.SecurityType} buy orders only support {nameof(GoodTilCanceledTimeInForce)} time in force, but {order.TimeInForce.GetType().Name} was specified.");
+ }
+
+ ///
+ /// Returns a message explaining that OutsideRegularTradingHours is only supported for Equity orders.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string OutsideRegularTradingHoursNotSupportedForSecurityType(Securities.Security security)
+ {
+ return Invariant($"{nameof(WebullOrderProperties.OutsideRegularTradingHours)} is only supported for {nameof(SecurityType.Equity)} orders, but {security.Type} was specified.");
+ }
+ }
+
///
/// Provides user-facing messages for the class and its consumers or related classes
///
diff --git a/Common/Orders/Fees/WebullFeeModel.cs b/Common/Orders/Fees/WebullFeeModel.cs
new file mode 100644
index 000000000000..45084010f4f9
--- /dev/null
+++ b/Common/Orders/Fees/WebullFeeModel.cs
@@ -0,0 +1,236 @@
+/*
+ * 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
+{
+ ///
+ /// Represents a fee model specific to Webull.
+ ///
+ ///
+ ///
+ /// Equity and standard options trades are commission-free on Webull.
+ /// Index options carry a flat $0.50 Webull contract fee plus a variable exchange proprietary fee
+ /// that depends on the underlying index symbol and the option's market price.
+ /// Cryptocurrency trades carry a 0.6% fee on the notional trade value.
+ ///
+ public class WebullFeeModel : FeeModel
+ {
+ ///
+ /// Webull contract fee applied to every index option contract, regardless of underlying.
+ ///
+ private const decimal _webullIndexOptionContractFee = 0.50m;
+
+ ///
+ /// Exchange proprietary fee for SPX options priced below $1.00.
+ ///
+ private const decimal _spxExchangeFeeBelow1 = 0.57m;
+
+ ///
+ /// Exchange proprietary fee for SPX options priced at or above $1.00.
+ ///
+ private const decimal _spxExchangeFeeAbove1 = 0.66m;
+
+ ///
+ /// Exchange proprietary fee for SPXW options priced below $1.00.
+ ///
+ private const decimal _spxwExchangeFeeBelow1 = 0.50m;
+
+ ///
+ /// Exchange proprietary fee for SPXW options priced at or above $1.00.
+ ///
+ private const decimal _spxwExchangeFeeAbove1 = 0.59m;
+
+ ///
+ /// VIX/VIXW exchange fee tier 1: option price at or below $0.10.
+ ///
+ private const decimal _vixExchangeFeeTier1 = 0.10m;
+
+ ///
+ /// VIX/VIXW exchange fee tier 2: option price between $0.11 and $0.99.
+ ///
+ private const decimal _vixExchangeFeeTier2 = 0.25m;
+
+ ///
+ /// VIX/VIXW exchange fee tier 3: option price between $1.00 and $1.99.
+ ///
+ private const decimal _vixExchangeFeeTier3 = 0.40m;
+
+ ///
+ /// VIX/VIXW exchange fee tier 4: option price at or above $2.00.
+ ///
+ private const decimal _vixExchangeFeeTier4 = 0.45m;
+
+ ///
+ /// XSP exchange fee for orders with fewer than 10 contracts.
+ ///
+ private const decimal _xspExchangeFeeSmall = 0.00m;
+
+ ///
+ /// XSP exchange fee for orders with 10 or more contracts.
+ ///
+ private const decimal _xspExchangeFeeLarge = 0.07m;
+
+ ///
+ /// DJX flat exchange proprietary fee per contract.
+ ///
+ private const decimal _djxExchangeFee = 0.18m;
+
+ ///
+ /// NDX/NDXP exchange fee for single-leg orders with premium below $25.
+ ///
+ private const decimal _ndxSingleLegFeeBelow25 = 0.50m;
+
+ ///
+ /// NDX/NDXP exchange fee for single-leg orders with premium at or above $25.
+ ///
+ private const decimal _ndxSingleLegFeeAbove25 = 0.75m;
+
+ ///
+ /// NDX/NDXP exchange fee for multi-leg orders with premium below $25.
+ ///
+ private const decimal _ndxMultiLegFeeBelow25 = 0.65m;
+
+ ///
+ /// NDX/NDXP exchange fee for multi-leg orders with premium at or above $25.
+ ///
+ private const decimal _ndxMultiLegFeeAbove25 = 0.90m;
+
+ ///
+ /// Crypto fee rate applied as a percentage of the notional trade value (0.6%).
+ ///
+ private const decimal _cryptoFeeRate = 0.006m;
+
+ ///
+ /// Gets the order fee for a given security and order.
+ ///
+ /// The parameters including the security and order details.
+ ///
+ /// for equity and standard options;
+ /// a per-contract fee for index options;
+ /// a percentage-of-notional fee for crypto.
+ ///
+ public override OrderFee GetOrderFee(OrderFeeParameters parameters)
+ {
+ switch (parameters.Security.Type)
+ {
+ case SecurityType.IndexOption:
+ return new OrderFee(new CashAmount(GetIndexOptionFee(parameters), Currencies.USD));
+ case SecurityType.Crypto:
+ var notional = parameters.Order.AbsoluteQuantity * parameters.Security.Price;
+ return new OrderFee(new CashAmount(notional * _cryptoFeeRate, Currencies.USD));
+ default:
+ // Equity and Option are commission-free on Webull.
+ return OrderFee.Zero;
+ }
+ }
+
+ ///
+ /// Calculates the total per-contract fee for an index option order.
+ /// The total fee = (exchange proprietary fee + Webull contract fee) × quantity.
+ ///
+ /// Order fee parameters containing the security and order.
+ /// Total fee amount in USD.
+ private static decimal GetIndexOptionFee(OrderFeeParameters parameters)
+ {
+ var order = parameters.Order;
+ var security = parameters.Security;
+ var quantity = order.AbsoluteQuantity;
+ var price = security.Price;
+ var underlying = security.Symbol.Underlying?.Value?.ToUpperInvariant() ?? string.Empty;
+ var isMultiLeg = order.Type == OrderType.ComboMarket
+ || order.Type == OrderType.ComboLimit
+ || order.Type == OrderType.ComboLegLimit;
+
+ var exchangeFee = GetIndexOptionExchangeFee(underlying, price, quantity, isMultiLeg);
+ return quantity * (exchangeFee + _webullIndexOptionContractFee);
+ }
+
+ ///
+ /// Returns the exchange proprietary fee per contract for an index option, based on
+ /// the underlying ticker, the option's current price, order quantity, and leg type.
+ ///
+ /// Uppercase underlying ticker (e.g. "SPX", "VIX").
+ /// Current market price of the option.
+ /// Absolute number of contracts in the order.
+ /// True when the order is a combo/multi-leg order.
+ /// Exchange fee per contract in USD.
+ private static decimal GetIndexOptionExchangeFee(string underlying, decimal price, decimal quantity, bool isMultiLeg)
+ {
+ switch (underlying)
+ {
+ case "SPX":
+ return price < 1m ? _spxExchangeFeeBelow1 : _spxExchangeFeeAbove1;
+
+ case "SPXW":
+ return price < 1m ? _spxwExchangeFeeBelow1 : _spxwExchangeFeeAbove1;
+
+ case "VIX":
+ case "VIXW":
+ return GetVixExchangeFee(price);
+
+ case "XSP":
+ return quantity < 10m ? _xspExchangeFeeSmall : _xspExchangeFeeLarge;
+
+ case "DJX":
+ return _djxExchangeFee;
+
+ case "NDX":
+ case "NDXP":
+ return GetNdxExchangeFee(price, isMultiLeg);
+
+ default:
+ return 0m;
+ }
+ }
+
+ ///
+ /// Returns the VIX/VIXW exchange fee for a simple order based on the option price tier.
+ ///
+ private static decimal GetVixExchangeFee(decimal price)
+ {
+ if (price <= 0.10m)
+ {
+ return _vixExchangeFeeTier1;
+ }
+
+ if (price <= 0.99m)
+ {
+ return _vixExchangeFeeTier2;
+ }
+
+ if (price <= 1.99m)
+ {
+ return _vixExchangeFeeTier3;
+ }
+
+ return _vixExchangeFeeTier4;
+ }
+
+ ///
+ /// Returns the NDX/NDXP exchange fee per contract based on premium tier and order leg type.
+ ///
+ private static decimal GetNdxExchangeFee(decimal price, bool isMultiLeg)
+ {
+ if (isMultiLeg)
+ {
+ return price < 25m ? _ndxMultiLegFeeBelow25 : _ndxMultiLegFeeAbove25;
+ }
+
+ return price < 25m ? _ndxSingleLegFeeBelow25 : _ndxSingleLegFeeAbove25;
+ }
+ }
+}
diff --git a/Common/Orders/WebullOrderProperties.cs b/Common/Orders/WebullOrderProperties.cs
new file mode 100644
index 000000000000..ce7932379a19
--- /dev/null
+++ b/Common/Orders/WebullOrderProperties.cs
@@ -0,0 +1,34 @@
+/*
+ * 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 Webull.
+ ///
+ public class WebullOrderProperties : OrderProperties
+ {
+ ///
+ /// If set to true, allows the order to trigger or fill outside of regular trading hours
+ /// (pre-market and after-hours sessions).
+ ///
+ ///
+ /// Applicable to Equity orders only. Extended-hours trading carries additional risks,
+ /// including lower liquidity and wider bid/ask spreads.
+ ///
+ public bool OutsideRegularTradingHours { get; set; }
+ }
+}
diff --git a/Data/symbol-properties/symbol-properties-database.csv b/Data/symbol-properties/symbol-properties-database.csv
index ef8d02d02d67..a075b2638537 100644
--- a/Data/symbol-properties/symbol-properties-database.csv
+++ b/Data/symbol-properties/symbol-properties-database.csv
@@ -10407,4 +10407,277 @@ dydx,ZETAUSD,cryptofuture,ZETA-USD,USD,1,0.0001,10,ZETA-USD,10.00000,10000000000
dydx,ZKUSD,cryptofuture,ZK-USD,USD,1,0.0001,10,ZK-USD,10.00000,10000000000,100000
dydx,ZORAUSD,cryptofuture,ZORA-USD,USD,1,0.00001,100,ZORA-USD,100.0000,100000000000,10000
dydx,ZROUSD,cryptofuture,ZRO-USD,USD,1,0.001,1,ZRO-USD,1.000000,1000000000,1000000
-dydx,ZRXUSD,cryptofuture,ZRX-USD,USD,1,0.0001,10,ZRX-USD,10.00000,10000000000,100000
\ No newline at end of file
+dydx,ZRXUSD,cryptofuture,ZRX-USD,USD,1,0.0001,10,ZRX-USD,10.00000,10000000000,100000
+
+
+webull,1INCHUSD,crypto,1INCHUSD,USD,1,0.001,0.01,1INCHUSD,0.01
+webull,A8USD,crypto,A8USD,USD,1,0.0001,0.01,A8USD,0.01
+webull,AAVEUSD,crypto,AAVEUSD,USD,1,0.01,0.00001,AAVEUSD,0.001
+webull,ABTUSD,crypto,ABTUSD,USD,1,0.0001,0.1,ABTUSD,0.1
+webull,ACHUSD,crypto,ACHUSD,USD,1,0.000001,0.1,ACHUSD,0.1
+webull,ACSUSD,crypto,ACSUSD,USD,1,0.0000001,1,ACSUSD,1
+webull,ACXUSD,crypto,ACXUSD,USD,1,0.0001,0.1,ACXUSD,0.1
+webull,ADAUSD,crypto,ADAUSD,USD,1,0.0001,0.00000001,ADAUSD,0.00000001
+webull,AERGOUSD,crypto,AERGOUSD,USD,1,0.0001,0.1,AERGOUSD,0.1
+webull,AGLDUSD,crypto,AGLDUSD,USD,1,0.0001,0.01,AGLDUSD,0.01
+webull,AIOZUSD,crypto,AIOZUSD,USD,1,0.0001,0.1,AIOZUSD,0.1
+webull,AKTUSD,crypto,AKTUSD,USD,1,0.001,0.01,AKTUSD,0.01
+webull,ALCXUSD,crypto,ALCXUSD,USD,1,0.01,0.0001,ALCXUSD,0.0001
+webull,ALEOUSD,crypto,ALEOUSD,USD,1,0.001,0.01,ALEOUSD,0.01
+webull,ALEPHUSD,crypto,ALEPHUSD,USD,1,0.0001,0.1,ALEPHUSD,0.1
+webull,ALGOUSD,crypto,ALGOUSD,USD,1,0.0001,0.001,ALGOUSD,0.1
+webull,ALICEUSD,crypto,ALICEUSD,USD,1,0.001,0.001,ALICEUSD,0.001
+webull,ALTUSD,crypto,ALTUSD,USD,1,0.00001,1,ALTUSD,1
+webull,AMPUSD,crypto,AMPUSD,USD,1,0.00001,1,AMPUSD,1
+webull,ANKRUSD,crypto,ANKRUSD,USD,1,0.00001,1,ANKRUSD,1
+webull,APEUSD,crypto,APEUSD,USD,1,0.001,0.001,APEUSD,0.01
+webull,API3USD,crypto,API3USD,USD,1,0.001,0.01,API3USD,0.01
+webull,APTUSD,crypto,APTUSD,USD,1,0.01,0.001,APTUSD,0.001
+webull,ARBUSD,crypto,ARBUSD,USD,1,0.0001,0.01,ARBUSD,0.01
+webull,ARKMUSD,crypto,ARKMUSD,USD,1,0.001,0.01,ARKMUSD,0.01
+webull,ARPAUSD,crypto,ARPAUSD,USD,1,0.0001,0.1,ARPAUSD,0.1
+webull,ASMUSD,crypto,ASMUSD,USD,1,0.00001,1,ASMUSD,1
+webull,ASTUSD,crypto,ASTUSD,USD,1,0.0001,0.1,ASTUSD,0.1
+webull,ATOMUSD,crypto,ATOMUSD,USD,1,0.001,0.001,ATOMUSD,0.01
+webull,AUCTIONUSD,crypto,AUCTIONUSD,USD,1,0.01,0.001,AUCTIONUSD,0.001
+webull,AUDIOUSD,crypto,AUDIOUSD,USD,1,0.0001,0.1,AUDIOUSD,0.1
+webull,AURORAUSD,crypto,AURORAUSD,USD,1,0.0001,0.01,AURORAUSD,0.01
+webull,AVAXUSD,crypto,AVAXUSD,USD,1,0.01,0.00000001,AVAXUSD,0.00000001
+webull,AVTUSD,crypto,AVTUSD,USD,1,0.01,0.01,AVTUSD,0.01
+webull,AXLUSD,crypto,AXLUSD,USD,1,0.0001,0.1,AXLUSD,0.1
+webull,AXSUSD,crypto,AXSUSD,USD,1,0.001,0.001,AXSUSD,0.001
+webull,BADGERUSD,crypto,BADGERUSD,USD,1,0.01,0.001,BADGERUSD,0.001
+webull,BALUSD,crypto,BALUSD,USD,1,0.0001,0.001,BALUSD,0.001
+webull,BANDUSD,crypto,BANDUSD,USD,1,0.001,0.01,BANDUSD,0.01
+webull,BATUSD,crypto,BATUSD,USD,1,0.00001,0.001,BATUSD,0.01
+webull,BCHUSD,crypto,BCHUSD,USD,1,0.01,0.00000001,BCHUSD,0.00000001
+webull,BERAUSD,crypto,BERAUSD,USD,1,0.001,0.001,BERAUSD,0.01
+webull,BICOUSD,crypto,BICOUSD,USD,1,0.0001,0.01,BICOUSD,0.01
+webull,BIGTIMEUSD,crypto,BIGTIMEUSD,USD,1,0.00001,1,BIGTIMEUSD,1
+webull,BLASTUSD,crypto,BLASTUSD,USD,1,0.00001,1,BLASTUSD,1
+webull,BLURUSD,crypto,BLURUSD,USD,1,0.0001,0.1,BLURUSD,0.1
+webull,BLZUSD,crypto,BLZUSD,USD,1,0.0001,0.001,BLZUSD,0.1
+webull,BNTUSD,crypto,BNTUSD,USD,1,0.0001,0.000001,BNTUSD,0.000001
+webull,BOBAUSD,crypto,BOBAUSD,USD,1,0.0001,0.1,BOBAUSD,0.1
+webull,BONKUSD,crypto,BONKUSD,USD,1,0.00000001,1,BONKUSD,1
+webull,BTCUSD,crypto,BTCUSD,USD,1,0.01,0.00000001,BTCUSD,0.00000001
+webull,BTRSTUSD,crypto,BTRSTUSD,USD,1,0.001,0.01,BTRSTUSD,0.01
+webull,C98USD,crypto,C98USD,USD,1,0.0001,0.01,C98USD,0.01
+webull,CBETHUSD,crypto,CBETHUSD,USD,1,0.01,0.00001,CBETHUSD,0.00001
+webull,CELRUSD,crypto,CELRUSD,USD,1,0.00001,1,CELRUSD,1
+webull,CGLDUSD,crypto,CGLDUSD,USD,1,0.001,0.01,CGLDUSD,0.01
+webull,CHZUSD,crypto,CHZUSD,USD,1,0.0001,0.1,CHZUSD,0.1
+webull,CLVUSD,crypto,CLVUSD,USD,1,0.0001,0.001,CLVUSD,0.01
+webull,COMPUSD,crypto,COMPUSD,USD,1,0.01,0.00001,COMPUSD,0.001
+webull,COOKIEUSD,crypto,COOKIEUSD,USD,1,0.00001,0.1,COOKIEUSD,0.1
+webull,CORECHAINUSD,crypto,CORECHAINUSD,USD,1,0.001,0.01,CORECHAINUSD,0.01
+webull,COSMOSDYDXUSD,crypto,COSMOSDYDXUSD,USD,1,0.0001,0.01,COSMOSDYDXUSD,0.01
+webull,COTIUSD,crypto,COTIUSD,USD,1,0.0001,0.1,COTIUSD,0.1
+webull,COWUSD,crypto,COWUSD,USD,1,0.0001,0.1,COWUSD,0.1
+webull,CROUSD,crypto,CROUSD,USD,1,0.0001,0.1,CROUSD,0.1
+webull,CRVUSD,crypto,CRVUSD,USD,1,0.0001,0.001,CRVUSD,0.01
+webull,CTSIUSD,crypto,CTSIUSD,USD,1,0.0001,0.1,CTSIUSD,0.1
+webull,CTXUSD,crypto,CTXUSD,USD,1,0.0001,0.001,CTXUSD,0.001
+webull,CVCUSD,crypto,CVCUSD,USD,1,0.0001,0.1,CVCUSD,0.1
+webull,CVXUSD,crypto,CVXUSD,USD,1,0.001,0.001,CVXUSD,0.001
+webull,DAIUSD,crypto,DAIUSD,USD,1,0.0001,0.00001,DAIUSD,0.00001
+webull,DASHUSD,crypto,DASHUSD,USD,1,0.01,0.001,DASHUSD,0.001
+webull,DEXTUSD,crypto,DEXTUSD,USD,1,0.0001,0.1,DEXTUSD,0.1
+webull,DIAUSD,crypto,DIAUSD,USD,1,0.00001,0.01,DIAUSD,0.01
+webull,DIMOUSD,crypto,DIMOUSD,USD,1,0.00001,0.1,DIMOUSD,0.1
+webull,DNTUSD,crypto,DNTUSD,USD,1,0.0001,0.1,DNTUSD,0.1
+webull,DOGEUSD,crypto,DOGEUSD,USD,1,0.00001,0.1,DOGEUSD,0.1
+webull,DOTUSD,crypto,DOTUSD,USD,1,0.001,0.00000001,DOTUSD,0.00000001
+webull,DRIFTUSD,crypto,DRIFTUSD,USD,1,0.001,0.01,DRIFTUSD,0.01
+webull,EDGEUSD,crypto,EDGEUSD,USD,1,0.00001,0.1,EDGEUSD,0.1
+webull,EGLDUSD,crypto,EGLDUSD,USD,1,0.01,0.001,EGLDUSD,0.001
+webull,EIGENUSD,crypto,EIGENUSD,USD,1,0.001,0.01,EIGENUSD,0.01
+webull,ELAUSD,crypto,ELAUSD,USD,1,0.001,0.01,ELAUSD,0.01
+webull,ENAUSD,crypto,ENAUSD,USD,1,0.0001,0.001,ENAUSD,0.1
+webull,ENSUSD,crypto,ENSUSD,USD,1,0.01,0.001,ENSUSD,0.001
+webull,ERNUSD,crypto,ERNUSD,USD,1,0.0001,0.001,ERNUSD,0.001
+webull,ETCUSD,crypto,ETCUSD,USD,1,0.01,0.00000001,ETCUSD,0.00000001
+webull,ETHFIUSD,crypto,ETHFIUSD,USD,1,0.001,0.01,ETHFIUSD,0.01
+webull,ETHUSD,crypto,ETHUSD,USD,1,0.01,0.00000001,ETHUSD,0.00000001
+webull,FARMUSD,crypto,FARMUSD,USD,1,0.01,0.001,FARMUSD,0.001
+webull,FARTCOINUSD,crypto,FARTCOINUSD,USD,1,0.0001,0.01,FARTCOINUSD,0.01
+webull,FETUSD,crypto,FETUSD,USD,1,0.0001,0.1,FETUSD,0.1
+webull,FIDAUSD,crypto,FIDAUSD,USD,1,0.0001,0.01,FIDAUSD,0.01
+webull,FILUSD,crypto,FILUSD,USD,1,0.001,0.001,FILUSD,0.001
+webull,FISUSD,crypto,FISUSD,USD,1,0.0001,0.1,FISUSD,0.1
+webull,FLOKIUSD,crypto,FLOKIUSD,USD,1,0.0000001,0.1,FLOKIUSD,1
+webull,FLRUSD,crypto,FLRUSD,USD,1,0.00001,1,FLRUSD,1
+webull,FORTHUSD,crypto,FORTHUSD,USD,1,0.0001,0.001,FORTHUSD,0.001
+webull,FORTUSD,crypto,FORTUSD,USD,1,0.0001,0.001,FORTUSD,0.01
+webull,FOXUSD,crypto,FOXUSD,USD,1,0.0001,0.1,FOXUSD,0.1
+webull,FXUSD,crypto,FXUSD,USD,1,0.0001,0.1,FXUSD,0.1
+webull,GFIUSD,crypto,GFIUSD,USD,1,0.0001,0.01,GFIUSD,0.01
+webull,GHSTUSD,crypto,GHSTUSD,USD,1,0.001,0.01,GHSTUSD,0.01
+webull,GIGAUSD,crypto,GIGAUSD,USD,1,0.00001,0.1,GIGAUSD,0.1
+webull,GLMUSD,crypto,GLMUSD,USD,1,0.0001,0.1,GLMUSD,0.1
+webull,GMTUSD,crypto,GMTUSD,USD,1,0.0001,0.01,GMTUSD,0.01
+webull,GNOUSD,crypto,GNOUSD,USD,1,0.01,0.0001,GNOUSD,0.0001
+webull,GODSUSD,crypto,GODSUSD,USD,1,0.00001,0.001,GODSUSD,0.01
+webull,GRTUSD,crypto,GRTUSD,USD,1,0.0001,0.001,GRTUSD,0.01
+webull,GSTUSD,crypto,GSTUSD,USD,1,0.000001,0.01,GSTUSD,0.01
+webull,GTCUSD,crypto,GTCUSD,USD,1,0.01,0.01,GTCUSD,0.01
+webull,GUSD,crypto,GUSD,USD,1,0.00001,1,GUSD,1
+webull,HBARUSD,crypto,HBARUSD,USD,1,0.00001,0.001,HBARUSD,0.1
+webull,HFTUSD,crypto,HFTUSD,USD,1,0.0001,0.01,HFTUSD,0.01
+webull,HIGHUSD,crypto,HIGHUSD,USD,1,0.001,0.01,HIGHUSD,0.01
+webull,HNTUSD,crypto,HNTUSD,USD,1,0.001,0.01,HNTUSD,0.01
+webull,HONEYUSD,crypto,HONEYUSD,USD,1,0.0001,0.1,HONEYUSD,0.1
+webull,ICPUSD,crypto,ICPUSD,USD,1,0.001,0.0001,ICPUSD,0.0001
+webull,IDEXUSD,crypto,IDEXUSD,USD,1,0.0001,0.1,IDEXUSD,0.1
+webull,ILVUSD,crypto,ILVUSD,USD,1,0.01,0.0001,ILVUSD,0.0001
+webull,IMXUSD,crypto,IMXUSD,USD,1,0.0001,0.001,IMXUSD,0.01
+webull,INDEXUSD,crypto,INDEXUSD,USD,1,0.01,0.001,INDEXUSD,0.001
+webull,INJUSD,crypto,INJUSD,USD,1,0.001,0.0001,INJUSD,0.01
+webull,INVUSD,crypto,INVUSD,USD,1,0.01,0.0001,INVUSD,0.0001
+webull,IOTXUSD,crypto,IOTXUSD,USD,1,0.00001,1,IOTXUSD,1
+webull,IOUSD,crypto,IOUSD,USD,1,0.001,0.01,IOUSD,0.01
+webull,IPUSD,crypto,IPUSD,USD,1,0.001,0.01,IPUSD,0.01
+webull,JASMYUSD,crypto,JASMYUSD,USD,1,0.00001,0.001,JASMYUSD,1
+webull,JITOSOLUSD,crypto,JITOSOLUSD,USD,1,0.01,0.0001,JITOSOLUSD,0.0001
+webull,JTOUSD,crypto,JTOUSD,USD,1,0.0001,0.001,JTOUSD,0.1
+webull,KAITOUSD,crypto,KAITOUSD,USD,1,0.0001,0.01,KAITOUSD,0.01
+webull,KARRATUSD,crypto,KARRATUSD,USD,1,0.0001,0.01,KARRATUSD,0.01
+webull,KERNELUSD,crypto,KERNELUSD,USD,1,0.0001,0.01,KERNELUSD,0.01
+webull,KRLUSD,crypto,KRLUSD,USD,1,0.0001,0.1,KRLUSD,0.1
+webull,KSMUSD,crypto,KSMUSD,USD,1,0.01,0.0001,KSMUSD,0.0001
+webull,L3USD,crypto,L3USD,USD,1,0.00001,0.1,L3USD,0.1
+webull,LAUSD,crypto,LAUSD,USD,1,0.0001,0.1,LAUSD,0.1
+webull,LCXUSD,crypto,LCXUSD,USD,1,0.0001,0.1,LCXUSD,0.1
+webull,LDOUSD,crypto,LDOUSD,USD,1,0.001,0.001,LDOUSD,0.01
+webull,LINKUSD,crypto,LINKUSD,USD,1,0.001,0.0001,LINKUSD,0.01
+webull,LOKAUSD,crypto,LOKAUSD,USD,1,0.0001,0.01,LOKAUSD,0.01
+webull,LPTUSD,crypto,LPTUSD,USD,1,0.01,0.001,LPTUSD,0.001
+webull,LQTYUSD,crypto,LQTYUSD,USD,1,0.0001,0.01,LQTYUSD,0.01
+webull,LRCUSD,crypto,LRCUSD,USD,1,0.0001,0.000001,LRCUSD,0.000001
+webull,LRDSUSD,crypto,LRDSUSD,USD,1,0.0001,0.01,LRDSUSD,0.01
+webull,LSETHUSD,crypto,LSETHUSD,USD,1,0.01,0.00001,LSETHUSD,0.00001
+webull,LTCUSD,crypto,LTCUSD,USD,1,0.01,0.00000001,LTCUSD,0.00000001
+webull,MAGICUSD,crypto,MAGICUSD,USD,1,0.0001,0.01,MAGICUSD,0.01
+webull,MANAUSD,crypto,MANAUSD,USD,1,0.0001,0.001,MANAUSD,0.01
+webull,MANTLEUSD,crypto,MANTLEUSD,USD,1,0.0001,0.01,MANTLEUSD,0.01
+webull,MASKUSD,crypto,MASKUSD,USD,1,0.01,0.01,MASKUSD,0.01
+webull,MATHUSD,crypto,MATHUSD,USD,1,0.0001,0.1,MATHUSD,0.1
+webull,MATICUSD,crypto,MATICUSD,USD,1,0.0001,0.1,MATICUSD,0.1
+webull,MDTUSD,crypto,MDTUSD,USD,1,0.00001,1,MDTUSD,1
+webull,METISUSD,crypto,METISUSD,USD,1,0.01,0.001,METISUSD,0.001
+webull,MEUSD,crypto,MEUSD,USD,1,0.001,0.01,MEUSD,0.01
+webull,MINAUSD,crypto,MINAUSD,USD,1,0.001,0.001,MINAUSD,0.001
+webull,MKRUSD,crypto,MKRUSD,USD,1,0.01,0.00000001,MKRUSD,0.000001
+webull,MLNUSD,crypto,MLNUSD,USD,1,0.01,0.001,MLNUSD,0.001
+webull,MNDEUSD,crypto,MNDEUSD,USD,1,0.00001,0.1,MNDEUSD,0.1
+webull,MOODENGUSD,crypto,MOODENGUSD,USD,1,0.0001,0.001,MOODENGUSD,0.01
+webull,MSOLUSD,crypto,MSOLUSD,USD,1,0.01,0.001,MSOLUSD,0.001
+webull,MUSEUSD,crypto,MUSEUSD,USD,1,0.001,0.001,MUSEUSD,0.001
+webull,NCTUSD,crypto,NCTUSD,USD,1,0.00001,1,NCTUSD,1
+webull,NEARUSD,crypto,NEARUSD,USD,1,0.001,0.001,NEARUSD,0.001
+webull,NEONUSD,crypto,NEONUSD,USD,1,0.00001,0.01,NEONUSD,0.01
+webull,NEWTUSD,crypto,NEWTUSD,USD,1,0.0001,0.01,NEWTUSD,0.01
+webull,NKNUSD,crypto,NKNUSD,USD,1,0.0001,0.1,NKNUSD,0.1
+webull,NMRUSD,crypto,NMRUSD,USD,1,0.01,0.001,NMRUSD,0.001
+webull,OGNUSD,crypto,OGNUSD,USD,1,0.00001,0.01,OGNUSD,0.01
+webull,OMNIUSD,crypto,OMNIUSD,USD,1,0.001,0.01,OMNIUSD,0.01
+webull,ONDOUSD,crypto,ONDOUSD,USD,1,0.00001,0.001,ONDOUSD,0.01
+webull,OPUSD,crypto,OPUSD,USD,1,0.001,0.001,OPUSD,0.01
+webull,ORCAUSD,crypto,ORCAUSD,USD,1,0.0001,0.001,ORCAUSD,0.01
+webull,OSMOUSD,crypto,OSMOUSD,USD,1,0.0001,0.01,OSMOUSD,0.01
+webull,OXTUSD,crypto,OXTUSD,USD,1,0.0001,1,OXTUSD,1
+webull,PAXGUSD,crypto,PAXGUSD,USD,1,0.01,0.00001,PAXGUSD,0.00001
+webull,PAXUSD,crypto,PAXUSD,USD,1,0.0001,0.01,PAXUSD,0.01
+webull,PENDLEUSD,crypto,PENDLEUSD,USD,1,0.001,0.01,PENDLEUSD,0.01
+webull,PENGUUSD,crypto,PENGUUSD,USD,1,0.00001,0.001,PENGUUSD,1
+webull,PEPEUSD,crypto,PEPEUSD,USD,1,0.00000001,1,PEPEUSD,1
+webull,PERPUSD,crypto,PERPUSD,USD,1,0.0001,0.001,PERPUSD,0.001
+webull,PIRATEUSD,crypto,PIRATEUSD,USD,1,0.0001,0.1,PIRATEUSD,0.1
+webull,PLUUSD,crypto,PLUUSD,USD,1,0.01,0.01,PLUUSD,0.01
+webull,PNGUSD,crypto,PNGUSD,USD,1,0.00001,1,PNGUSD,1
+webull,PNUTUSD,crypto,PNUTUSD,USD,1,0.0001,0.001,PNUTUSD,0.01
+webull,POLSUSD,crypto,POLSUSD,USD,1,0.0001,0.01,POLSUSD,0.01
+webull,POLUSD,crypto,POLUSD,USD,1,0.0001,0.001,POLUSD,0.01
+webull,PONDUSD,crypto,PONDUSD,USD,1,0.00001,1,PONDUSD,1
+webull,POPCATUSD,crypto,POPCATUSD,USD,1,0.0001,0.01,POPCATUSD,0.01
+webull,POWRUSD,crypto,POWRUSD,USD,1,0.0001,0.1,POWRUSD,0.1
+webull,PRCLUSD,crypto,PRCLUSD,USD,1,0.0001,0.1,PRCLUSD,0.1
+webull,PRIMEUSD,crypto,PRIMEUSD,USD,1,0.001,0.01,PRIMEUSD,0.01
+webull,PROUSD,crypto,PROUSD,USD,1,0.0001,0.01,PROUSD,0.01
+webull,PROVEUSD,crypto,PROVEUSD,USD,1,0.0001,0.01,PROVEUSD,0.01
+webull,PUMPUSD,crypto,PUMPUSD,USD,1,0.000001,1,PUMPUSD,1
+webull,PUNDIXUSD,crypto,PUNDIXUSD,USD,1,0.0001,0.01,PUNDIXUSD,0.01
+webull,PYRUSD,crypto,PYRUSD,USD,1,0.001,0.01,PYRUSD,0.01
+webull,QIUSD,crypto,QIUSD,USD,1,0.000001,1,QIUSD,1
+webull,QNTUSD,crypto,QNTUSD,USD,1,0.01,0.001,QNTUSD,0.001
+webull,RADUSD,crypto,RADUSD,USD,1,0.01,0.01,RADUSD,0.01
+webull,RAREUSD,crypto,RAREUSD,USD,1,0.0001,0.001,RAREUSD,0.1
+webull,RARIUSD,crypto,RARIUSD,USD,1,0.01,0.001,RARIUSD,0.001
+webull,REDUSD,crypto,REDUSD,USD,1,0.0001,0.01,REDUSD,0.01
+webull,REQUSD,crypto,REQUSD,USD,1,0.0001,1,REQUSD,1
+webull,REZUSD,crypto,REZUSD,USD,1,0.00001,1,REZUSD,1
+webull,RLCUSD,crypto,RLCUSD,USD,1,0.0001,0.01,RLCUSD,0.01
+webull,RONINUSD,crypto,RONINUSD,USD,1,0.001,0.01,RONINUSD,0.01
+webull,ROSEUSD,crypto,ROSEUSD,USD,1,0.00001,0.1,ROSEUSD,0.1
+webull,SAFEUSD,crypto,SAFEUSD,USD,1,0.0001,0.01,SAFEUSD,0.01
+webull,SANDUSD,crypto,SANDUSD,USD,1,0.0001,0.001,SANDUSD,0.01
+webull,SDUSD,crypto,SDUSD,USD,1,0.0001,0.01,SDUSD,0.01
+webull,SEIUSD,crypto,SEIUSD,USD,1,0.0001,0.001,SEIUSD,0.1
+webull,SHDWUSD,crypto,SHDWUSD,USD,1,0.001,0.01,SHDWUSD,0.01
+webull,SHIBUSD,crypto,SHIBUSD,USD,1,0.00000001,1,SHIBUSD,1
+webull,SHPINGUSD,crypto,SHPINGUSD,USD,1,0.000001,1,SHPINGUSD,1
+webull,SKYUSD,crypto,SKYUSD,USD,1,0.00001,0.1,SKYUSD,0.1
+webull,SNXUSD,crypto,SNXUSD,USD,1,0.001,0.001,SNXUSD,0.001
+webull,SOLUSD,crypto,SOLUSD,USD,1,0.01,0.00000001,SOLUSD,0.00000001
+webull,SPAUSD,crypto,SPAUSD,USD,1,0.000001,0.001,SPAUSD,1
+webull,SPELLUSD,crypto,SPELLUSD,USD,1,0.0000001,1,SPELLUSD,1
+webull,SPKUSD,crypto,SPKUSD,USD,1,0.00001,0.1,SPKUSD,0.1
+webull,STGUSD,crypto,STGUSD,USD,1,0.0001,0.1,STGUSD,0.1
+webull,STORJUSD,crypto,STORJUSD,USD,1,0.0001,0.01,STORJUSD,0.01
+webull,STRKUSD,crypto,STRKUSD,USD,1,0.001,0.001,STRKUSD,0.01
+webull,STXUSD,crypto,STXUSD,USD,1,0.0001,0.01,STXUSD,0.01
+webull,SUIUSD,crypto,SUIUSD,USD,1,0.0001,0.001,SUIUSD,0.1
+webull,SUPERUSD,crypto,SUPERUSD,USD,1,0.00001,0.01,SUPERUSD,0.01
+webull,SUSD,crypto,SUSD,USD,1,0.00001,0.1,SUSD,0.1
+webull,SUSHIUSD,crypto,SUSHIUSD,USD,1,0.0001,0.001,SUSHIUSD,0.01
+webull,SWELLUSD,crypto,SWELLUSD,USD,1,0.00001,1,SWELLUSD,1
+webull,SWFTCUSD,crypto,SWFTCUSD,USD,1,0.000001,0.001,SWFTCUSD,1
+webull,SXTUSD,crypto,SXTUSD,USD,1,0.0001,0.1,SXTUSD,0.1
+webull,SYRUPUSD,crypto,SYRUPUSD,USD,1,0.0001,0.001,SYRUPUSD,0.1
+webull,TAOUSD,crypto,TAOUSD,USD,1,0.01,0.00000001,TAOUSD,0.0001
+webull,TIAUSD,crypto,TIAUSD,USD,1,0.001,0.001,TIAUSD,0.01
+webull,TIMEUSD,crypto,TIMEUSD,USD,1,0.01,0.001,TIMEUSD,0.001
+webull,TNSRUSD,crypto,TNSRUSD,USD,1,0.001,0.01,TNSRUSD,0.01
+webull,TOSHIUSD,crypto,TOSHIUSD,USD,1,0.0000001,1,TOSHIUSD,1
+webull,TRACUSD,crypto,TRACUSD,USD,1,0.0001,0.1,TRACUSD,0.1
+webull,TRBUSD,crypto,TRBUSD,USD,1,0.01,0.001,TRBUSD,0.001
+webull,TRUMPUSD,crypto,TRUMPUSD,USD,1,0.01,0.0001,TRUMPUSD,0.001
+webull,TRUUSD,crypto,TRUUSD,USD,1,0.0001,0.1,TRUUSD,0.1
+webull,TUSD,crypto,TUSD,USD,1,0.00001,1,TUSD,1
+webull,UMAUSD,crypto,UMAUSD,USD,1,0.001,0.001,UMAUSD,0.001
+webull,UNIUSD,crypto,UNIUSD,USD,1,0.001,0.000001,UNIUSD,0.000001
+webull,USDCUSD,crypto,USD Coin,USD,1,0.00001,0.01,USDCUSD,0.000001
+webull,USDTUSD,crypto,USDTUSD,USD,1,0.00001,0.001,USDTUSD,0.01
+webull,VARAUSD,crypto,VARAUSD,USD,1,0.00001,1,VARAUSD,1
+webull,VETUSD,crypto,VETUSD,USD,1,0.00001,1,VETUSD,1
+webull,VTHOUSD,crypto,VTHOUSD,USD,1,0.000001,1,VTHOUSD,1
+webull,VVVUSD,crypto,VVVUSD,USD,1,0.01,0.001,VVVUSD,0.001
+webull,WAXLUSD,crypto,WAXLUSD,USD,1,0.0001,0.01,WAXLUSD,0.01
+webull,WCFGUSD,crypto,WCFGUSD,USD,1,0.001,0.01,WCFGUSD,0.01
+webull,WIFUSD,crypto,WIFUSD,USD,1,0.001,0.0001,WIFUSD,0.01
+webull,WLDUSD,crypto,WLDUSD,USD,1,0.001,0.001,WLDUSD,0.01
+webull,WLFIUSD,crypto,WLFIUSD,USD,1,0.00001,0.1,WLFIUSD,0.1
+webull,XCNUSD,crypto,XCNUSD,USD,1,0.00001,0.001,XCNUSD,0.1
+webull,XLMUSD,crypto,XLMUSD,USD,1,0.000001,0.00000001,XLMUSD,0.00000001
+webull,XRPUSD,crypto,XRPUSD,USD,1,0.0001,0.000001,XRPUSD,0.000001
+webull,XTZUSD,crypto,XTZUSD,USD,1,0.001,0.001,XTZUSD,0.01
+webull,XYOUSD,crypto,XYOUSD,USD,1,0.00001,0.1,XYOUSD,0.1
+webull,YFIUSD,crypto,YFIUSD,USD,1,0.01,0.00000001,YFIUSD,0.000001
+webull,ZECUSD,crypto,ZECUSD,USD,1,0.01,0.00001,ZECUSD,0.00001
+webull,ZENUSD,crypto,ZENUSD,USD,1,0.001,0.001,ZENUSD,0.001
+webull,ZETACHAINUSD,crypto,ZETACHAINUSD,USD,1,0.0001,0.01,ZETACHAINUSD,0.01
+webull,ZETAUSD,crypto,ZETAUSD,USD,1,0.0001,0.01,ZETAUSD,0.1
+webull,ZKUSD,crypto,ZKUSD,USD,1,0.00001,0.1,ZKUSD,0.1
+webull,ZORAUSD,crypto,ZORAUSD,USD,1,0.00001,0.001,ZORAUSD,1
+webull,ZROUSD,crypto,ZROUSD,USD,1,0.001,0.01,ZROUSD,0.01
+webull,ZRXUSD,crypto,ZRXUSD,USD,1,0.000001,0.00001,ZRXUSD,0.00001
diff --git a/Launcher/config.json b/Launcher/config.json
index cbac82fed394..b6f00f025854 100644
--- a/Launcher/config.json
+++ b/Launcher/config.json
@@ -255,6 +255,13 @@
"charles-schwab-authorization-code-from-url": "",
"charles-schwab-redirect-url": "",
+ // Webull configuration
+ "webull-api-url": "https://api.webull.com",
+ "webull-trade-grpc-url": "https://events-api.webull.com",
+ "webull-app-key": "",
+ "webull-app-secret": "",
+ "webull-account-id": "",
+
// Tastytrade configuration
"tastytrade-api-url": "",
"tastytrade-websocket-url": "",
@@ -758,6 +765,21 @@
"history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
},
+ // defines the 'live-webull' environment
+ "live-webull": {
+ "live-mode": true,
+
+ // real brokerage implementations require the BrokerageTransactionHandler
+ "live-mode-brokerage": "WebullBrokerage",
+ "data-queue-handler": [ "WebullBrokerage" ],
+ "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,
diff --git a/Tests/Brokerages/TestHelpers.cs b/Tests/Brokerages/TestHelpers.cs
index 58396da12747..eeb4a31aa1fa 100644
--- a/Tests/Brokerages/TestHelpers.cs
+++ b/Tests/Brokerages/TestHelpers.cs
@@ -58,9 +58,12 @@ private static SubscriptionDataConfig CreateConfig(string symbol, string market,
break;
case SecurityType.Option:
- case SecurityType.IndexOption:
actualSymbol = Symbols.CreateOptionSymbol(symbol, OptionRight.Call, 1000, new DateTime(2020, 3, 26));
break;
+ case SecurityType.IndexOption:
+ var index = Symbols.CreateIndexSymbol(symbol);
+ actualSymbol = Symbol.CreateOption(index, index.ID.Market, SecurityType.IndexOption.DefaultOptionStyle(), OptionRight.Call, 6500m, new(2026, 04, 13));
+ break;
default:
actualSymbol = Symbol.Create(symbol, securityType, market);
diff --git a/Tests/Common/Brokerages/WebullBrokerageModelTests.cs b/Tests/Common/Brokerages/WebullBrokerageModelTests.cs
new file mode 100644
index 000000000000..4db336cb4e3d
--- /dev/null
+++ b/Tests/Common/Brokerages/WebullBrokerageModelTests.cs
@@ -0,0 +1,284 @@
+/*
+ * 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 NUnit.Framework;
+using QuantConnect.Orders;
+using QuantConnect.Brokerages;
+using QuantConnect.Orders.Fees;
+using QuantConnect.Securities;
+using QuantConnect.Tests.Brokerages;
+
+namespace QuantConnect.Tests.Common.Brokerages
+{
+ [TestFixture]
+ public class WebullBrokerageModelTests
+ {
+ private readonly WebullBrokerageModel _brokerageModel = new WebullBrokerageModel();
+
+ // Equity: all five order types supported
+ [TestCase(SecurityType.Equity, OrderType.Market)]
+ [TestCase(SecurityType.Equity, OrderType.Limit)]
+ [TestCase(SecurityType.Equity, OrderType.StopMarket)]
+ [TestCase(SecurityType.Equity, OrderType.StopLimit)]
+ [TestCase(SecurityType.Equity, OrderType.TrailingStop)]
+ // Option: Market and TrailingStop are not supported
+ [TestCase(SecurityType.Option, OrderType.Limit)]
+ [TestCase(SecurityType.Option, OrderType.StopMarket)]
+ [TestCase(SecurityType.Option, OrderType.StopLimit)]
+ // IndexOption: same restrictions as Option
+ [TestCase(SecurityType.IndexOption, OrderType.Limit)]
+ [TestCase(SecurityType.IndexOption, OrderType.StopMarket)]
+ [TestCase(SecurityType.IndexOption, OrderType.StopLimit)]
+ // Future: all five order types supported
+ [TestCase(SecurityType.Future, OrderType.Market)]
+ [TestCase(SecurityType.Future, OrderType.Limit)]
+ [TestCase(SecurityType.Future, OrderType.StopMarket)]
+ [TestCase(SecurityType.Future, OrderType.StopLimit)]
+ [TestCase(SecurityType.Future, OrderType.TrailingStop)]
+ // Crypto: StopMarket and TrailingStop are not supported
+ [TestCase(SecurityType.Crypto, OrderType.Market)]
+ [TestCase(SecurityType.Crypto, OrderType.Limit)]
+ [TestCase(SecurityType.Crypto, OrderType.StopLimit)]
+ public void CanSubmitOrderValidSecurityAndOrderTypeReturnsTrue(SecurityType securityType, OrderType orderType)
+ {
+ // Arrange
+ var security = GetSecurityForType(securityType);
+ var order = CreateOrder(orderType, security.Symbol);
+
+ // Act
+ var canSubmit = _brokerageModel.CanSubmitOrder(security, order, out var message);
+
+ // Assert
+ Assert.That(canSubmit, Is.True);
+ Assert.That(message, Is.Null);
+ }
+
+ [TestCase(SecurityType.Forex)]
+ [TestCase(SecurityType.Cfd)]
+ public void CanSubmitOrderUnsupportedSecurityTypeReturnsFalse(SecurityType securityType)
+ {
+ // Arrange
+ var security = TestsHelpers.GetSecurity(securityType: securityType, symbol: "EURUSD", market: Market.Oanda);
+ var order = new MarketOrder(security.Symbol, 1m, DateTime.UtcNow);
+
+ // Act
+ var canSubmit = _brokerageModel.CanSubmitOrder(security, order, out var message);
+
+ // Assert
+ Assert.That(canSubmit, Is.False);
+ Assert.That(message, Is.Not.Null);
+ }
+
+ // Equity does not support exchange-session orders or combo orders
+ [TestCase(SecurityType.Equity, OrderType.MarketOnClose)]
+ [TestCase(SecurityType.Equity, OrderType.MarketOnOpen)]
+ [TestCase(SecurityType.Equity, OrderType.ComboMarket)]
+ // Option does not support TrailingStop
+ [TestCase(SecurityType.Option, OrderType.TrailingStop)]
+ // IndexOption has the same restrictions as Option
+ [TestCase(SecurityType.IndexOption, OrderType.TrailingStop)]
+ // Crypto does not support StopMarket or TrailingStop
+ [TestCase(SecurityType.Crypto, OrderType.StopMarket)]
+ [TestCase(SecurityType.Crypto, OrderType.TrailingStop)]
+ public void CanSubmitOrder_UnsupportedOrderTypeForSecurityType_ReturnsFalse(
+ SecurityType securityType, OrderType orderType)
+ {
+ // Arrange
+ var security = GetSecurityForType(securityType);
+ var order = CreateOrder(orderType, security.Symbol);
+
+ // Act
+ var canSubmit = _brokerageModel.CanSubmitOrder(security, order, out var message);
+
+ // Assert
+ Assert.That(canSubmit, Is.False);
+ Assert.That(message, Is.Not.Null);
+ }
+
+ // ── CanSubmitOrder — Option/IndexOption TimeInForce restrictions ────────
+ // https://developer.webull.com/apis/docs/trade-api/options#time-in-force
+ // Sell → Day only | Buy → GoodTilCanceled only
+
+ [TestCase(SecurityType.Option, OrderDirection.Sell)] // Sell + Day
+ [TestCase(SecurityType.Option, OrderDirection.Buy)] // Buy + GTC
+ [TestCase(SecurityType.IndexOption, OrderDirection.Sell)]
+ [TestCase(SecurityType.IndexOption, OrderDirection.Buy)]
+ public void CanSubmitOrderOptionOrderWithValidTimeInForceReturnsTrue(SecurityType securityType, OrderDirection direction)
+ {
+ // Arrange
+ var security = GetSecurityForType(securityType);
+ var tif = direction == OrderDirection.Sell
+ ? TimeInForce.Day
+ : TimeInForce.GoodTilCanceled;
+ var order = CreateLimitOrder(security.Symbol, direction, tif);
+
+ // Act
+ var canSubmit = _brokerageModel.CanSubmitOrder(security, order, out var message);
+
+ // Assert
+ Assert.That(canSubmit, Is.True);
+ Assert.That(message, Is.Null);
+ }
+
+ [TestCase(SecurityType.Option, OrderDirection.Sell)] // Sell + GTC → rejected
+ [TestCase(SecurityType.Option, OrderDirection.Buy)] // Buy + Day → rejected
+ [TestCase(SecurityType.IndexOption, OrderDirection.Sell)]
+ [TestCase(SecurityType.IndexOption, OrderDirection.Buy)]
+ public void CanSubmitOrderOptionOrderWithInvalidTimeInForceReturnsFalse(
+ SecurityType securityType, OrderDirection direction)
+ {
+ // Arrange
+ var security = GetSecurityForType(securityType);
+ // Deliberately use the wrong TIF for the direction
+ var tif = direction == OrderDirection.Sell
+ ? TimeInForce.GoodTilCanceled
+ : TimeInForce.Day;
+ var order = CreateLimitOrder(security.Symbol, direction, tif);
+
+ // Act
+ var canSubmit = _brokerageModel.CanSubmitOrder(security, order, out var message);
+
+ // Assert
+ Assert.That(canSubmit, Is.False);
+ Assert.That(message, Is.Not.Null);
+ Assert.That(message.Message, Does.Contain(tif.GetType().Name));
+ Assert.That(message.Message, Does.Contain(security.Type.ToString()));
+ }
+
+ // ── CanSubmitOrder — OutsideRegularTradingHours ──────────────────────────
+ // https://developer.webull.com/apis/docs/trade-api — Applicable to U.S. stock market orders only.
+
+ [Test]
+ public void CanSubmitOrderOutsideRegularTradingHoursOnEquityReturnsTrue()
+ {
+ // Arrange
+ var security = GetSecurityForType(SecurityType.Equity);
+ var properties = new WebullOrderProperties { OutsideRegularTradingHours = true };
+ var order = new MarketOrder(security.Symbol, 1m, DateTime.UtcNow, properties: properties);
+
+ // Act
+ var canSubmit = _brokerageModel.CanSubmitOrder(security, order, out var message);
+
+ // Assert
+ Assert.That(canSubmit, Is.True);
+ Assert.That(message, Is.Null);
+ }
+
+ [TestCase(SecurityType.Option)]
+ [TestCase(SecurityType.IndexOption)]
+ [TestCase(SecurityType.Future)]
+ [TestCase(SecurityType.Crypto)]
+ public void CanSubmitOrderOutsideRegularTradingHoursOnNonEquityReturnsFalse(SecurityType securityType)
+ {
+ // Arrange
+ var security = GetSecurityForType(securityType);
+ var properties = new WebullOrderProperties { OutsideRegularTradingHours = true };
+ var order = new LimitOrder(security.Symbol, 1m, 100m, DateTime.UtcNow, properties: properties);
+
+ // Act
+ var canSubmit = _brokerageModel.CanSubmitOrder(security, order, out var message);
+
+ // Assert
+ Assert.That(canSubmit, Is.False);
+ Assert.That(message, Is.Not.Null);
+ Assert.That(message.Message, Does.Contain(nameof(WebullOrderProperties.OutsideRegularTradingHours)));
+ Assert.That(message.Message, Does.Contain(securityType.ToString()));
+ }
+
+ [TestCase(SecurityType.Option)]
+ [TestCase(SecurityType.Future)]
+ [TestCase(SecurityType.Crypto)]
+ public void CanSubmitOrderOutsideRegularTradingHoursFalseOnNonEquityReturnsTrue(SecurityType securityType)
+ {
+ // Arrange
+ var security = GetSecurityForType(securityType);
+ var properties = new WebullOrderProperties { OutsideRegularTradingHours = false };
+ var order = new LimitOrder(security.Symbol, 1m, 100m, DateTime.UtcNow, properties: properties);
+
+ // Act
+ var canSubmit = _brokerageModel.CanSubmitOrder(security, order, out var message);
+
+ // Assert
+ Assert.That(canSubmit, Is.True);
+ Assert.That(message, Is.Null);
+ }
+
+ [Test]
+ public void GetFeeModelReturnsWebullFeeModel()
+ {
+ // Arrange
+ var security = TestsHelpers.GetSecurity(securityType: SecurityType.Equity, symbol: "AAPL", market: Market.USA);
+
+ // Act / Assert
+ Assert.That(_brokerageModel.GetFeeModel(security), Is.InstanceOf());
+ }
+
+ private static Security GetSecurityForType(SecurityType securityType)
+ {
+ switch (securityType)
+ {
+ case SecurityType.Future:
+ return TestsHelpers.GetSecurity(securityType: SecurityType.Future,
+ symbol: Futures.Indices.SP500EMini, market: Market.CME);
+ case SecurityType.Crypto:
+ return TestsHelpers.GetSecurity(securityType: SecurityType.Crypto,
+ symbol: "BTCUSD", market: Market.Coinbase);
+ case SecurityType.Forex:
+ case SecurityType.Cfd:
+ return TestsHelpers.GetSecurity(securityType: securityType,
+ symbol: "EURUSD", market: Market.Oanda);
+ case SecurityType.IndexOption:
+ return TestsHelpers.GetSecurity(securityType: SecurityType.IndexOption,
+ symbol: "SPX", market: Market.CBOE);
+ default:
+ return TestsHelpers.GetSecurity(securityType: securityType,
+ symbol: "AAPL", market: Market.USA);
+ }
+ }
+
+ private static LimitOrder CreateLimitOrder(Symbol symbol, OrderDirection direction, TimeInForce timeInForce)
+ {
+ var quantity = direction == OrderDirection.Buy ? 1m : -1m;
+ var properties = new OrderProperties { TimeInForce = timeInForce };
+ return new LimitOrder(symbol, quantity, 100m, DateTime.UtcNow, properties: properties);
+ }
+
+ private static Order CreateOrder(OrderType orderType, Symbol symbol)
+ {
+ switch (orderType)
+ {
+ case OrderType.Market:
+ return new MarketOrder(symbol, 1m, DateTime.UtcNow);
+ case OrderType.Limit:
+ return new LimitOrder(symbol, 1m, 100m, DateTime.UtcNow);
+ case OrderType.StopMarket:
+ return new StopMarketOrder(symbol, 1m, 100m, DateTime.UtcNow);
+ case OrderType.StopLimit:
+ return new StopLimitOrder(symbol, 1m, 105m, 100m, DateTime.UtcNow);
+ case OrderType.MarketOnClose:
+ return new MarketOnCloseOrder(symbol, 1m, DateTime.UtcNow);
+ case OrderType.MarketOnOpen:
+ return new MarketOnOpenOrder(symbol, 1m, DateTime.UtcNow);
+ case OrderType.TrailingStop:
+ return new TrailingStopOrder(symbol, 1m, 100m, 1m, false, DateTime.UtcNow);
+ case OrderType.ComboMarket:
+ return new ComboMarketOrder(symbol, 1m, DateTime.UtcNow, new GroupOrderManager(1, 1, 1m));
+ default:
+ throw new ArgumentOutOfRangeException(nameof(orderType), orderType, null);
+ }
+ }
+ }
+}
diff --git a/Tests/Common/Orders/Fees/WebullFeeModelTests.cs b/Tests/Common/Orders/Fees/WebullFeeModelTests.cs
new file mode 100644
index 000000000000..37fc62c37c31
--- /dev/null
+++ b/Tests/Common/Orders/Fees/WebullFeeModelTests.cs
@@ -0,0 +1,204 @@
+/*
+ * 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 System.Collections.Generic;
+using NUnit.Framework;
+using QuantConnect.Data;
+using QuantConnect.Data.Market;
+using QuantConnect.Orders;
+using QuantConnect.Orders.Fees;
+using QuantConnect.Securities;
+using QuantConnect.Securities.Crypto;
+using QuantConnect.Tests.Common.Securities;
+
+namespace QuantConnect.Tests.Common.Orders.Fees
+{
+ [TestFixture]
+ public class WebullFeeModelTests
+ {
+ private readonly WebullFeeModel _feeModel = new WebullFeeModel();
+
+ private static IEnumerable ZeroFeeSecurities()
+ {
+ var equity = SecurityTests.GetSecurity();
+ equity.SetMarketPrice(new Tick(DateTime.UtcNow, equity.Symbol, 100m, 100m));
+ yield return equity;
+ yield return CreateSecurity(SecurityType.Option, 5m, "AAPL");
+ }
+
+ ///
+ /// Equity and non-index options are commission-free on Webull.
+ ///
+ [TestCaseSource(nameof(ZeroFeeSecurities))]
+ public void GetOrderFeeReturnsZeroForFreeAssets(Security security)
+ {
+ var order = new MarketOrder(security.Symbol, 10m, DateTime.UtcNow);
+
+ var fee = _feeModel.GetOrderFee(new OrderFeeParameters(security, order));
+
+ Assert.That(fee.Value.Amount, Is.EqualTo(0m));
+ }
+
+ ///
+ /// SPX/SPXW exchange fee tiers (per contract) + Webull $0.50/contract:
+ /// SPX price < $1 -> $0.57 + $0.50 = $1.07
+ /// SPX price >= $1 -> $0.66 + $0.50 = $1.16
+ /// SPXW price < $1 -> $0.50 + $0.50 = $1.00
+ /// SPXW price >= $1 -> $0.59 + $0.50 = $1.09
+ ///
+ [TestCase("SPX", 0.50, 2, 2.14, Description = "SPX price < $1 -> $1.07/contract")]
+ [TestCase("SPX", 1.50, 3, 3.48, Description = "SPX price >= $1 -> $1.16/contract")]
+ [TestCase("SPXW", 0.80, 1, 1.00, Description = "SPXW price < $1 -> $1.00/contract")]
+ [TestCase("SPXW", 2.00, 4, 4.36, Description = "SPXW price >= $1 -> $1.09/contract")]
+ public void GetOrderFeeSpxPriceTierReturnsCorrectFee(string ticker, decimal price, decimal quantity, decimal expectedFee)
+ {
+ var security = CreateSecurity(SecurityType.IndexOption, price, ticker);
+ var order = new MarketOrder(security.Symbol, quantity, DateTime.UtcNow);
+
+ var fee = _feeModel.GetOrderFee(new OrderFeeParameters(security, order));
+
+ Assert.That(fee.Value.Amount, Is.EqualTo(expectedFee));
+ }
+
+ ///
+ /// VIX exchange fee tiers (per contract) + Webull $0.50/contract:
+ /// Tier 1: price ≤ $0.10 -> $0.10 + $0.50 = $0.60
+ /// Tier 2: price $0.11–$0.99 -> $0.25 + $0.50 = $0.75
+ /// Tier 3: price $1.00–$1.99 -> $0.40 + $0.50 = $0.90
+ /// Tier 4: price >= $2.00 -> $0.45 + $0.50 = $0.95
+ ///
+ [TestCase(0.05, 4, 2.40, Description = "Tier 1: price ≤ $0.10 -> $0.60/contract")]
+ [TestCase(0.50, 2, 1.50, Description = "Tier 2: price $0.11–$0.99 -> $0.75/contract")]
+ [TestCase(1.50, 1, 0.90, Description = "Tier 3: price $1.00–$1.99 -> $0.90/contract")]
+ [TestCase(3.00, 5, 4.75, Description = "Tier 4: price >= $2.00 -> $0.95/contract")]
+ public void GetOrderFeeVixPriceTierReturnsCorrectFee(decimal price, decimal quantity, decimal expectedFee)
+ {
+ var security = CreateSecurity(SecurityType.IndexOption, price, "VIX");
+ var order = new MarketOrder(security.Symbol, quantity, DateTime.UtcNow);
+
+ var fee = _feeModel.GetOrderFee(new OrderFeeParameters(security, order));
+
+ Assert.That(fee.Value.Amount, Is.EqualTo(expectedFee));
+ }
+
+ ///
+ /// VIXW uses identical tier schedule to VIX; verify tier 2.
+ ///
+ [Test]
+ public void GetOrderFeeVixwPriceTier2MatchesVixFee()
+ {
+ var vix = CreateSecurity(SecurityType.IndexOption, 0.50m, "VIX");
+ var vixw = CreateSecurity(SecurityType.IndexOption, 0.50m, "VIXW");
+ var order = new MarketOrder(vix.Symbol, 2m, DateTime.UtcNow);
+
+ var vixFee = _feeModel.GetOrderFee(new OrderFeeParameters(vix, order));
+ var vixwFee = _feeModel.GetOrderFee(new OrderFeeParameters(vixw, new MarketOrder(vixw.Symbol, 2m, DateTime.UtcNow)));
+
+ Assert.That(vixFee.Value.Amount, Is.EqualTo(vixwFee.Value.Amount));
+ }
+
+ ///
+ /// Index option fee schedule (per contract) + Webull $0.50/contract:
+ /// XSP qty < 10 -> $0.00 + $0.50 = $0.50
+ /// XSP qty >= 10 -> $0.07 + $0.50 = $0.57
+ /// DJX flat -> $0.18 + $0.50 = $0.68
+ /// NDX price < $25 -> $0.50 + $0.50 = $1.00 (NDXP shares the same schedule)
+ /// NDX price >= $25 -> $0.75 + $0.50 = $1.25 (NDXP shares the same schedule)
+ ///
+ [TestCase("XSP", 1.00, 5, 2.50, Description = "XSP qty < 10 -> $0.50/contract")]
+ [TestCase("XSP", 1.00, 10, 5.70, Description = "XSP qty >= 10 -> $0.57/contract")]
+ [TestCase("DJX", 2.00, 2, 1.36, Description = "DJX flat -> $0.68/contract")]
+ [TestCase("NDX", 10.00, 3, 3.00, Description = "NDX price < $25 -> $1.00/contract")]
+ [TestCase("NDX", 50.00, 2, 2.50, Description = "NDX price >= $25 -> $1.25/contract")]
+ [TestCase("NDXP", 10.00, 3, 3.00, Description = "NDXP price < $25 matches NDX schedule")]
+ [TestCase("NDXP", 50.00, 2, 2.50, Description = "NDXP price >= $25 matches NDX schedule")]
+ public void GetOrderFeeIndexOptionReturnsCorrectFee(string ticker, decimal price, decimal quantity, decimal expectedFee)
+ {
+ var security = CreateSecurity(SecurityType.IndexOption, price, ticker);
+ var order = new MarketOrder(security.Symbol, quantity, DateTime.UtcNow);
+
+ var fee = _feeModel.GetOrderFee(new OrderFeeParameters(security, order));
+
+ Assert.That(fee.Value.Amount, Is.EqualTo(expectedFee));
+ }
+
+ ///
+ /// Crypto fee = 0.6% of notional (quantity × price).
+ /// 2 BTC × $50,000 = $100,000 notional -> fee = $600.
+ ///
+ [Test]
+ public void GetOrderFeeCryptoReturnsPointSixPercentOfNotional()
+ {
+ var btcusd = CreateSecurity(SecurityType.Crypto, 50_000m);
+ var order = new MarketOrder(btcusd.Symbol, 2m, DateTime.UtcNow);
+
+ var fee = _feeModel.GetOrderFee(new OrderFeeParameters(btcusd, order));
+
+ // 2 * 50000 * 0.006 = 600
+ Assert.That(fee.Value.Amount, Is.EqualTo(600m));
+ Assert.That(fee.Value.Currency, Is.EqualTo(Currencies.USD));
+ }
+
+ ///
+ /// Creates a test security of the requested priced at .
+ /// Supported types: , , .
+ /// For option types identifies the underlying symbol;
+ /// it is ignored for (BTC/USD is always used).
+ ///
+ private static Security CreateSecurity(SecurityType securityType, decimal price, string ticker = "AAPL")
+ {
+ if (securityType == SecurityType.Crypto)
+ {
+ var btcusd = new Crypto(
+ SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
+ new Cash(Currencies.USD, 0, 1m),
+ new Cash("BTC", 0, price),
+ new SubscriptionDataConfig(typeof(TradeBar), Symbols.BTCUSD, Resolution.Minute,
+ TimeZones.Utc, TimeZones.Utc, true, false, false),
+ new SymbolProperties("BTCUSD", Currencies.USD, 1, 0.01m, 0.00000001m, string.Empty),
+ ErrorCurrencyConverter.Instance,
+ RegisteredSecurityDataTypesProvider.Null);
+ btcusd.SetMarketPrice(new Tick(DateTime.UtcNow, btcusd.Symbol, price, price));
+ return btcusd;
+ }
+
+ var isIndex = securityType == SecurityType.IndexOption;
+ var underlying = Symbol.Create(ticker, isIndex ? SecurityType.Index : SecurityType.Equity, Market.USA);
+ var symbol = Symbol.CreateOption(
+ underlying, Market.USA,
+ isIndex ? OptionStyle.European : OptionStyle.American,
+ OptionRight.Call,
+ isIndex ? 1000m : 150m,
+ new DateTime(2026, 6, 20));
+
+ var config = new SubscriptionDataConfig(
+ typeof(TradeBar), symbol, Resolution.Minute,
+ TimeZones.Utc, TimeZones.Utc, false, true, false);
+
+ var security = new Security(
+ SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
+ config,
+ new Cash(Currencies.USD, 0, 1m),
+ SymbolProperties.GetDefault(Currencies.USD),
+ ErrorCurrencyConverter.Instance,
+ RegisteredSecurityDataTypesProvider.Null,
+ new SecurityCache());
+
+ security.SetMarketPrice(new Tick(DateTime.UtcNow, symbol, price, price));
+ return security;
+ }
+ }
+}