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; + } + } +}