diff --git a/Algorithm.CSharp/InteractiveBrokersTieredFeeModelAlgorithm.cs b/Algorithm.CSharp/InteractiveBrokersTieredFeeModelAlgorithm.cs new file mode 100644 index 000000000000..e1d8a3aa9b86 --- /dev/null +++ b/Algorithm.CSharp/InteractiveBrokersTieredFeeModelAlgorithm.cs @@ -0,0 +1,130 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System.Collections.Generic; +using QuantConnect.Data; +using QuantConnect.Interfaces; +using QuantConnect.Orders.Fees; + +namespace QuantConnect.Algorithm.CSharp +{ + /// + /// Test algorithm using + /// + public class InteractiveBrokersTieredFeeModelAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition + { + private Symbol _spy, _aig, _bac; + private IFeeModel _feeModel = new InteractiveBrokersTieredFeeModel(); + + /// + /// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized. + /// + public override void Initialize() + { + SetStartDate(2013, 10, 7); //Set Start Date + SetEndDate(2013, 10, 10); //Set End Date + SetCash(1000000000); //Set Strategy Cash + + // Set the fee model to be shared by all securities to accurately track the volume/value traded to select the correct tiered fee structure. + SetSecurityInitializer((security) => security.SetFeeModel(_feeModel)); + + _spy = AddEquity("SPY", Resolution.Minute, extendedMarketHours: true).Symbol; + _aig = AddEquity("AIG", Resolution.Minute, extendedMarketHours: true).Symbol; + _bac = AddEquity("BAC", Resolution.Minute, extendedMarketHours: true).Symbol; + } + + public override void OnData(Slice slice) + { + // Order at different time for various order type to elicit different fee structure. + if (slice.Time.Hour == 9 && slice.Time.Minute == 0) + { + MarketOnOpenOrder(_spy, 30000); + MarketOnOpenOrder(_aig, 30000); + MarketOnOpenOrder(_bac, 30000); + } + else if (slice.Time.Hour == 10 && slice.Time.Minute == 0) + { + MarketOrder(_spy, 30000); + MarketOrder(_aig, 30000); + MarketOrder(_bac, 30000); + } + else if (slice.Time.Hour == 15 && slice.Time.Minute == 30) + { + MarketOnCloseOrder(_spy, -60000); + MarketOnCloseOrder(_aig, -60000); + MarketOnCloseOrder(_bac, -60000); + } + } + + /// + /// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm. + /// + public bool CanRunLocally { get; } = true; + + /// + /// This is used by the regression test system to indicate which languages this algorithm is written in. + /// + public List Languages { get; } = new() { Language.CSharp, Language.Python }; + + /// + /// Data Points count of all timeslices of algorithm + /// + public long DataPoints => 23076; + + /// + /// Data Points count of the algorithm history + /// + public int AlgorithmHistoryDataPoints => 0; + + /// + /// Final status of the algorithm + /// + public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed; + + /// + /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm + /// + public Dictionary ExpectedStatistics => new Dictionary + { + {"Total Orders", "36"}, + {"Average Win", "0.00%"}, + {"Average Loss", "0.00%"}, + {"Compounding Annual Return", "-2.237%"}, + {"Drawdown", "0.000%"}, + {"Expectancy", "-0.486"}, + {"Start Equity", "1000000000"}, + {"End Equity", "999762433.94"}, + {"Net Profit", "-0.024%"}, + {"Sharpe Ratio", "-8.397"}, + {"Sortino Ratio", "-11.384"}, + {"Probabilistic Sharpe Ratio", "0%"}, + {"Loss Rate", "75%"}, + {"Win Rate", "25%"}, + {"Profit-Loss Ratio", "1.06"}, + {"Alpha", "-0.035"}, + {"Beta", "0.009"}, + {"Annual Standard Deviation", "0.003"}, + {"Annual Variance", "0"}, + {"Information Ratio", "-5.78"}, + {"Tracking Error", "0.269"}, + {"Treynor Ratio", "-2.319"}, + {"Total Fees", "$185772.29"}, + {"Estimated Strategy Capacity", "$11000000.00"}, + {"Lowest Capacity Asset", "AIG R735QTJ8XC9X"}, + {"Portfolio Turnover", "2.37%"}, + {"OrderListHash", "d35a4e91c145a100d4bffb7c0fc0ff35"} + }; + } +} diff --git a/Algorithm.Python/InteractiveBrokersTieredFeeModelAlgorithm.py b/Algorithm.Python/InteractiveBrokersTieredFeeModelAlgorithm.py new file mode 100644 index 000000000000..a9227e3c2150 --- /dev/null +++ b/Algorithm.Python/InteractiveBrokersTieredFeeModelAlgorithm.py @@ -0,0 +1,49 @@ +# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. +# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from AlgorithmImports import * +from Orders.Fees.InteractiveBrokersTieredFeeModel import InteractiveBrokersTieredFeeModel + +### +### Test algorithm using "InteractiveBrokersTieredFeeModel" +### +class InteractiveBrokersTieredFeeModelAlgorithm(QCAlgorithm): + fee_model = InteractiveBrokersTieredFeeModel() + + def initialize(self): + ''' Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.''' + self.set_start_date(2013, 10, 7) #Set Start Date + self.set_end_date(2013, 10, 10) #Set End Date + self.set_cash(1000000000) #Set Strategy Cash + + # Set the fee model to be shared by all securities to accurately track the volume/value traded to select the correct tiered fee structure. + self.set_security_initializer(lambda security: security.set_fee_model(self.fee_model)) + + self.spy = self.add_equity("SPY", Resolution.MINUTE, extended_market_hours=True).symbol + self.aig = self.add_equity("AIG", Resolution.MINUTE, extended_market_hours=True).symbol + self.bac = self.add_equity("BAC", Resolution.MINUTE, extended_market_hours=True).symbol + + def on_data(self, slice: Slice) -> None: + # Order at different time for various order type to elicit different fee structure. + if slice.time.hour == 9 and slice.time.minute == 0: + self.market_on_open_order(self.spy, 30000) + self.market_on_open_order(self.aig, 30000) + self.market_on_open_order(self.bac, 30000) + elif slice.time.hour == 10 and slice.time.minute == 0: + self.market_order(self.spy, 30000) + self.market_order(self.aig, 30000) + self.market_order(self.bac, 30000) + elif slice.time.hour == 15 and slice.time.minute == 30: + self.market_on_close_order(self.spy, -60000) + self.market_on_close_order(self.aig, -60000) + self.market_on_close_order(self.bac, -60000) diff --git a/Common/Orders/Fees/InteractiveBrokersFeeHelper.cs b/Common/Orders/Fees/InteractiveBrokersFeeHelper.cs new file mode 100644 index 000000000000..95d0bb8fd269 --- /dev/null +++ b/Common/Orders/Fees/InteractiveBrokersFeeHelper.cs @@ -0,0 +1,584 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using QuantConnect.Securities; +using System.Collections.Generic; + +namespace QuantConnect.Orders.Fees +{ + /// + /// Helper class to supplement fee calculation in and + /// + internal static class InteractiveBrokersFeeHelper + { + /// + /// Determines which tier an account falls into based on the monthly trading volume of Equities (in shares) + /// + /// https://www.interactivebrokers.com/en/pricing/commissions-stocks.php?re=amer + internal static void ProcessEquityRateSchedule(decimal monthlyEquityTradeVolume, out decimal commissionRate) + { + commissionRate = 0.0005m; + if (monthlyEquityTradeVolume <= 300000) + { + commissionRate = 0.0035m; + } + else if (monthlyEquityTradeVolume <= 3000000) + { + commissionRate = 0.002m; + } + else if (monthlyEquityTradeVolume <= 20000000) + { + commissionRate = 0.0015m; + } + else if (monthlyEquityTradeVolume <= 100000000) + { + commissionRate = 0.001m; + } + } + + /// + /// Determines which tier an account falls into based on the monthly trading volume of Futures (in contracts) + /// + /// https://www.interactivebrokers.com/en/pricing/commissions-futures.php?re=amer + internal static void ProcessFutureRateSchedule(decimal monthlyFutureTradeVolume, out int commissionTier) + { + commissionTier = 3; + if (monthlyFutureTradeVolume <= 1000) + { + commissionTier = 0; + } + else if (monthlyFutureTradeVolume <= 10000) + { + commissionTier = 1; + } + else if (monthlyFutureTradeVolume <= 20000) + { + commissionTier = 2; + } + } + + /// + /// Determines which tier an account falls into based on the monthly trading volume of Forex + /// + /// https://www.interactivebrokers.com/en/pricing/commissions-spot-currencies.php?re=amer + internal static void ProcessForexRateSchedule(decimal monthlyForexTradeAmountInUSDollars, out decimal commissionRate, out decimal minimumOrderFee) + { + const decimal bp = 0.0001m; + if (monthlyForexTradeAmountInUSDollars <= 1000000000) // 1 billion + { + commissionRate = 0.20m * bp; + minimumOrderFee = 2.00m; + } + else if (monthlyForexTradeAmountInUSDollars <= 2000000000) // 2 billion + { + commissionRate = 0.15m * bp; + minimumOrderFee = 1.50m; + } + else if (monthlyForexTradeAmountInUSDollars <= 5000000000) // 5 billion + { + commissionRate = 0.10m * bp; + minimumOrderFee = 1.25m; + } + else + { + commissionRate = 0.08m * bp; + minimumOrderFee = 1.00m; + } + } + + /// + /// Determines which tier an account falls into based on the monthly trading volume of Options + /// + /// https://www.interactivebrokers.com/en/pricing/commissions-options.php?re=amer + internal static void ProcessOptionsRateSchedule(decimal monthlyOptionsTradeAmountInContracts, out Func optionsCommissionFunc) + { + if (monthlyOptionsTradeAmountInContracts <= 10000) + { + optionsCommissionFunc = (orderSize, premium) => + { + var commissionRate = premium >= 0.1m ? 0.65m : (0.05m <= premium && premium < 0.1m ? 0.5m : 0.25m); + return new CashAmount(Math.Max(orderSize * commissionRate, 1.0m), Currencies.USD); + }; + } + else if (monthlyOptionsTradeAmountInContracts <= 50000) + { + optionsCommissionFunc = (orderSize, premium) => + { + var commissionRate = premium >= 0.05m ? 0.5m : 0.25m; + return new CashAmount(Math.Max(orderSize * commissionRate, 1.0m), Currencies.USD); + }; + } + else if (monthlyOptionsTradeAmountInContracts <= 100000) + { + optionsCommissionFunc = (orderSize, premium) => + { + var commissionRate = 0.25m; + return new CashAmount(Math.Max(orderSize * commissionRate, 1.0m), Currencies.USD); + }; + } + else + { + optionsCommissionFunc = (orderSize, premium) => + { + var commissionRate = 0.15m; + return new CashAmount(Math.Max(orderSize * commissionRate, 1.0m), Currencies.USD); + }; + } + } + + /// + /// Determines which tier an account falls into based on the monthly trading volume of Crypto + /// + /// https://www.interactivebrokers.com/en/pricing/commissions-cryptocurrencies.php?re=amer + internal static void ProcessCryptoRateSchedule(decimal monthlyCryptoTradeAmountInUSDollars, out decimal commissionRate) + { + if (monthlyCryptoTradeAmountInUSDollars <= 100000) + { + commissionRate = 0.0018m; + } + else if (monthlyCryptoTradeAmountInUSDollars <= 1000000) + { + commissionRate = 0.0015m; + } + else + { + commissionRate = 0.0012m; + } + } + + /// + /// Calculate the transaction fee of a Forex order + /// + /// The traded value of the transaction + internal static decimal CalculateForexFee(Security security, Order order, decimal forexCommissionRate, + decimal forexMinimumOrderFee, out decimal fee, out string currency) + { + // get the total order value in the account currency + var totalOrderValue = Math.Abs(order.GetValue(security)); + var baseFee = forexCommissionRate*totalOrderValue; + + fee = Math.Max(forexMinimumOrderFee, baseFee); + // IB Forex fees are all in USD + currency = Currencies.USD; + + return totalOrderValue; + } + + /// + /// Calculate the transaction fee of an Option order + /// + /// The traded value of the transaction + internal static decimal CalculateOptionFee(Security security, Order order, decimal quantity, string market, + Dictionary> feeRef, out decimal fee, out string currency) + { + Func optionsCommissionFunc; + if (!feeRef.TryGetValue(market, out optionsCommissionFunc)) + { + throw new KeyNotFoundException(Messages.InteractiveBrokersFeeModel.UnexpectedOptionMarket(market)); + } + // applying commission function to the order + var orderPrice = GetPotentialOrderPrice(order, security); + var optionFee = optionsCommissionFunc(quantity, orderPrice); + + fee = optionFee.Amount; + currency = optionFee.Currency; + + return orderPrice * quantity; + } + + /// + /// Calculate the transaction fee of a Future or FOP order + /// + internal static void CalculateFutureFopFee(Security security, decimal quantity, string market, + Dictionary> feeRef, out decimal fee, out string currency) + { + // The futures options fee model is exactly the same as futures' fees on IB. + if (market == Market.Globex || market == Market.NYMEX + || market == Market.CBOT || market == Market.ICE + || market == Market.CFE || market == Market.COMEX + || market == Market.CME || market == Market.NYSELIFFE) + { + // just in case... + market = Market.USA; + } + + if (!feeRef.TryGetValue(market, out var feeRatePerContractFunc)) + { + throw new KeyNotFoundException(Messages.InteractiveBrokersFeeModel.UnexpectedFutureMarket(market)); + } + + var feeRatePerContract = feeRatePerContractFunc(security); + fee = quantity * feeRatePerContract.Amount; + currency = feeRatePerContract.Currency; + } + + /// + /// Calculate the transaction fee of an Equity order + /// + /// Commission part of the transaction cost + internal static void CalculateEquityFee(decimal quantity, decimal tradeValue, string market, + decimal usFeeRate, decimal usMinimumFee, out decimal fee, out string currency) + { + EquityFee equityFee; + switch (market) + { + case Market.USA: + equityFee = new EquityFee(Currencies.USD, feePerShare: usFeeRate, minimumFee: usMinimumFee, maximumFeeRate: 0.01m); + break; + case Market.India: + equityFee = new EquityFee(Currencies.INR, feePerShare: 0.01m, minimumFee: 6, maximumFeeRate: 20); + break; + default: + throw new KeyNotFoundException(Messages.InteractiveBrokersFeeModel.UnexpectedEquityMarket(market)); + } + + //Per share fees + var tradeFee = equityFee.FeePerShare * quantity; + + //Maximum Per Order: equityFee.MaximumFeeRate + //Minimum per order. $equityFee.MinimumFee + var maximumPerOrder = equityFee.MaximumFeeRate * tradeValue; + if (tradeFee < equityFee.MinimumFee) + { + tradeFee = equityFee.MinimumFee; + } + else if (tradeFee > maximumPerOrder) + { + tradeFee = maximumPerOrder; + } + + currency = equityFee.Currency; + //Always return a positive fee. + fee = Math.Abs(tradeFee); + } + + /// + /// Calculate the transaction fee of a Cfd order + /// + internal static void CalculateCfdFee(Security security, Order order, out decimal fee, out string currency) + { + var value = Math.Abs(order.GetValue(security)); + fee = 0.00002m * value; // 0.002% + currency = security.QuoteCurrency.Symbol; + + var minimumFee = security.QuoteCurrency.Symbol switch + { + "JPY" => 40.0m, + "HKD" => 10.0m, + _ => 1.0m + }; + fee = Math.Max(fee, minimumFee); + } + + /// + /// Calculate the transaction fee of a Crypto order + /// + /// The traded value of the transaction + internal static decimal CalculateCryptoFee(Security security, Order order, decimal cryptoCommissionRate, + decimal cryptoMinimumOrderFee, out decimal fee, out string currency) + { + // get the total trade value in the USD + var totalTradeValue = Math.Abs(order.GetValue(security)); + var cryptoFee = cryptoCommissionRate*totalTradeValue; + // 1% maximum fee + fee = Math.Max(Math.Min(totalTradeValue * 0.01m, cryptoMinimumOrderFee), cryptoFee); + // IB Crypto fees are all in USD + currency = Currencies.USD; + + return totalTradeValue; + } + + /// + /// See https://www.hkex.com.hk/Services/Rules-and-Forms-and-Fees/Fees/Listed-Derivatives/Trading/Transaction?sc_lang=en + /// + internal static CashAmount HongKongFutureFees(Security security) + { + if (security.Symbol.ID.Symbol.Equals("HSI", StringComparison.InvariantCultureIgnoreCase)) + { + // IB fee + exchange fee + return new CashAmount(30 + 10, Currencies.HKD); + } + + decimal ibFeePerContract; + switch (security.QuoteCurrency.Symbol) + { + case Currencies.CNH: + ibFeePerContract = 13; + break; + case Currencies.HKD: + ibFeePerContract = 20; + break; + case Currencies.USD: + ibFeePerContract = 2.40m; + break; + default: + throw new ArgumentException(Messages.InteractiveBrokersFeeModel.HongKongFutureFeesUnexpectedQuoteCurrency(security)); + } + + // let's add a 50% extra charge for exchange fees + return new CashAmount(ibFeePerContract * 1.5m, security.QuoteCurrency.Symbol); + } + + internal static CashAmount EUREXFutureFees(Security security) + { + IDictionary fees, exchangeFees; + decimal ibFeePerContract, exchangeFeePerContract; + string symbol; + + switch (security.Symbol.SecurityType) + { + case SecurityType.Future: + fees = _eurexFuturesFees; + exchangeFees = _eurexFuturesExchangeFees; + symbol = security.Symbol.ID.Symbol; + break; + default: + throw new ArgumentException(Messages.InteractiveBrokersFeeModel.EUREXFutureFeesUnsupportedSecurityType(security)); + } + + if (!fees.TryGetValue(symbol, out ibFeePerContract)) + { + ibFeePerContract = 1.00m; + } + + if (!exchangeFees.TryGetValue(symbol, out exchangeFeePerContract)) + { + exchangeFeePerContract = 0.00m; + } + + // Add exchange fees + IBKR regulatory fee (0.02) + return new CashAmount(ibFeePerContract + exchangeFeePerContract + 0.02m, Currencies.EUR); + } + + /// + /// Get the exchange fees of an Equity trade. + /// + /// Refer to https://www.interactivebrokers.com/en/pricing/commissions-stocks.php, section United States - Third Party Fees. + internal static decimal GetEquityExchangeFee(Order order, Exchange exchange, decimal tradeValue, decimal commission) + { + var pennyStock = order.Price < 1m; + + switch (order.Type, pennyStock) + { + case (OrderType.MarketOnOpen, true): + if (exchange == Exchange.AMEX) + { + return order.AbsoluteQuantity * 0.0005m; + } + else if (exchange == Exchange.ARCA) + { + return tradeValue * 0.001m; + } + else if (exchange == Exchange.BATS) + { + return order.AbsoluteQuantity * 0.00075m; + } + else if (exchange == Exchange.NYSE) + { + return tradeValue * 0.0030m + commission * 0.000175m; + } + return tradeValue * 0.0030m; + + case (OrderType.MarketOnOpen, false): + if (exchange == Exchange.AMEX) + { + return order.AbsoluteQuantity * 0.0005m; + } + else if (exchange == Exchange.BATS) + { + return order.AbsoluteQuantity * 0.00075m; + } + else if (exchange == Exchange.NYSE) + { + return order.AbsoluteQuantity * 0.0010m + commission * 0.000175m; + } + return order.AbsoluteQuantity * 0.0015m; + + case (OrderType.MarketOnClose, true): + if (exchange == Exchange.AMEX) + { + return order.AbsoluteQuantity * 0.0005m; + } + else if (exchange == Exchange.ARCA) + { + return tradeValue * 0.001m; + } + else if (exchange == Exchange.BATS) + { + return order.AbsoluteQuantity * 0.0010m; + } + else if (exchange == Exchange.NYSE) + { + return tradeValue * 0.0030m + commission * 0.000175m; + } + return tradeValue * 0.0030m; + + case (OrderType.MarketOnClose, false): + if (exchange == Exchange.AMEX) + { + return order.AbsoluteQuantity * 0.0005m; + } + else if (exchange == Exchange.ARCA) + { + return order.AbsoluteQuantity * 0.0012m; + } + else if (exchange == Exchange.BATS) + { + return order.AbsoluteQuantity * 0.0010m; + } + else if (exchange == Exchange.NYSE) + { + return order.AbsoluteQuantity * 0.0010m + commission * 0.000175m; + } + return order.AbsoluteQuantity * 0.0015m; + + case (OrderType.Market, true): + case (OrderType.Limit, true): + case (OrderType.LimitIfTouched, true): + case (OrderType.StopMarket, true): + case (OrderType.StopLimit, true): + case (OrderType.TrailingStop, true): + if (exchange == Exchange.AMEX) + { + return tradeValue * 0.0025m; + } + else if (exchange == Exchange.NYSE) + { + return tradeValue * 0.0030m + commission * 0.000175m; + } + return tradeValue * 0.0030m; + + default: + if (exchange == Exchange.NYSE) + { + return order.AbsoluteQuantity * 0.0030m + commission * 0.000175m; + } + return order.AbsoluteQuantity * 0.0030m; + } + } + + /// + /// Approximates the order's price based on the order type + /// + internal static decimal GetPotentialOrderPrice(Order order, Security security) + { + decimal price = 0; + switch (order.Type) + { + case OrderType.TrailingStop: + price = (order as TrailingStopOrder).StopPrice; + break; + case OrderType.StopMarket: + price = (order as StopMarketOrder).StopPrice; + break; + case OrderType.ComboMarket: + case OrderType.MarketOnOpen: + case OrderType.MarketOnClose: + case OrderType.Market: + if (order.Direction == OrderDirection.Buy) + { + price = security.BidPrice; + } + else + { + price = security.AskPrice; + } + break; + case OrderType.ComboLimit: + price = (order as ComboLimitOrder).GroupOrderManager.LimitPrice; + break; + case OrderType.ComboLegLimit: + price = (order as ComboLegLimitOrder).LimitPrice; + break; + case OrderType.StopLimit: + price = (order as StopLimitOrder).LimitPrice; + break; + case OrderType.LimitIfTouched: + price = (order as LimitIfTouchedOrder).LimitPrice; + break; + case OrderType.Limit: + price = (order as LimitOrder).LimitPrice; + break; + } + + return price; + } + + internal static readonly Dictionary UsaFuturesExchangeFees = new() + { + // E-mini Futures + { "ES", 1.28m }, { "NQ", 1.28m }, { "YM", 1.28m }, { "RTY", 1.28m }, { "EMD", 1.28m }, + // Micro E-mini Futures + { "MYM", 0.30m }, { "M2K", 0.30m }, { "MES", 0.30m }, { "MNQ", 0.30m }, { "2YY", 0.30m }, { "5YY", 0.30m }, { "10Y", 0.30m }, + { "30Y", 0.30m }, { "MCL", 0.30m }, { "MGC", 0.30m }, { "SIL", 0.30m }, + // Cryptocurrency Futures + { "BTC", 6m }, { "MBT", 2.5m }, { "ETH", 4m }, { "MET", 0.20m }, + // E-mini FX (currencies) Futures + { "E7", 0.85m }, { "J7", 0.85m }, + // Micro E-mini FX (currencies) Futures + { "M6E", 0.24m }, { "M6A", 0.24m }, { "M6B", 0.24m }, { "MCD", 0.24m }, { "MJY", 0.24m }, { "MSF", 0.24m }, { "M6J", 0.24m }, + { "MIR", 0.24m }, { "M6C", 0.24m }, { "M6S", 0.24m }, { "MNH", 0.24m }, + }; + + internal static readonly Dictionary UsaFutureOptionsExchangeFees = new() + { + // E-mini Future Options + { "ES", 0.55m }, { "NQ", 0.55m }, { "YM", 0.55m }, { "RTY", 0.55m }, { "EMD", 0.55m }, + // Micro E-mini Future Options + { "MYM", 0.20m }, { "M2K", 0.20m }, { "MES", 0.20m }, { "MNQ", 0.20m }, { "2YY", 0.20m }, { "5YY", 0.20m }, { "10Y", 0.20m }, + { "30Y", 0.20m }, { "MCL", 0.20m }, { "MGC", 0.20m }, { "SIL", 0.20m }, + // Cryptocurrency Future Options + { "BTC", 5m }, { "MBT", 2.5m }, { "ETH", 4m }, { "MET", 0.20m }, + }; + + /// + /// Reference at https://www.interactivebrokers.com/en/pricing/commissions-futures-europe.php?re=europe + /// + private static readonly Dictionary _eurexFuturesFees = new() + { + // Futures + { "FESX", 1.00m }, + }; + + private static readonly Dictionary _eurexFuturesExchangeFees = new() + { + // Futures + { "FESX", 0.00m }, + }; + + /// + /// Helper class to handle IB Equity fees + /// + private class EquityFee + { + public string Currency { get; } + public decimal FeePerShare { get; } + public decimal MinimumFee { get; } + public decimal MaximumFeeRate { get; } + + public EquityFee(string currency, + decimal feePerShare, + decimal minimumFee, + decimal maximumFeeRate) + { + Currency = currency; + FeePerShare = feePerShare; + MinimumFee = minimumFee; + MaximumFeeRate = maximumFeeRate; + } + } + } +} diff --git a/Common/Orders/Fees/InteractiveBrokersFeeModel.cs b/Common/Orders/Fees/InteractiveBrokersFeeModel.cs index 296a304f4a6a..49f8d266656d 100644 --- a/Common/Orders/Fees/InteractiveBrokersFeeModel.cs +++ b/Common/Orders/Fees/InteractiveBrokersFeeModel.cs @@ -25,8 +25,10 @@ namespace QuantConnect.Orders.Fees /// public class InteractiveBrokersFeeModel : FeeModel { + private const decimal CryptoMinimumOrderFee = 1.75m; private readonly decimal _forexCommissionRate; private readonly decimal _forexMinimumOrderFee; + private readonly decimal _cryptoCommissionRate; // option commission function takes number of contracts and the size of the option premium and returns total commission private readonly Dictionary> _optionFee = @@ -38,12 +40,12 @@ public class InteractiveBrokersFeeModel : FeeModel /// #pragma warning restore CS1570 private readonly Dictionary> _futureFee = - // IB fee + exchange fee + // IB fee + exchange fee new() { { Market.USA, UnitedStatesFutureFees }, - { Market.HKFE, HongKongFutureFees }, - { Market.EUREX, EUREXFutureFees } + { Market.HKFE, InteractiveBrokersFeeHelper.HongKongFutureFees }, + { Market.EUREX, InteractiveBrokersFeeHelper.EUREXFutureFees } }; /// @@ -51,13 +53,15 @@ public class InteractiveBrokersFeeModel : FeeModel /// /// Monthly FX dollar volume traded /// Monthly options contracts traded - public InteractiveBrokersFeeModel(decimal monthlyForexTradeAmountInUSDollars = 0, decimal monthlyOptionsTradeAmountInContracts = 0) + /// Monthly Crypto dollar volume traded (in USD) + public InteractiveBrokersFeeModel(decimal monthlyForexTradeAmountInUSDollars = 0, decimal monthlyOptionsTradeAmountInContracts = 0, decimal monthlyCryptoTradeAmountInUSDollars = 0) { - ProcessForexRateSchedule(monthlyForexTradeAmountInUSDollars, out _forexCommissionRate, out _forexMinimumOrderFee); + InteractiveBrokersFeeHelper.ProcessForexRateSchedule(monthlyForexTradeAmountInUSDollars, out _forexCommissionRate, out _forexMinimumOrderFee); Func optionsCommissionFunc; - ProcessOptionsRateSchedule(monthlyOptionsTradeAmountInContracts, out optionsCommissionFunc); + InteractiveBrokersFeeHelper.ProcessOptionsRateSchedule(monthlyOptionsTradeAmountInContracts, out optionsCommissionFunc); // only USA for now _optionFee.Add(Market.USA, optionsCommissionFunc); + InteractiveBrokersFeeHelper.ProcessCryptoRateSchedule(monthlyCryptoTradeAmountInUSDollars, out _cryptoCommissionRate); } /// @@ -92,96 +96,30 @@ public override OrderFee GetOrderFee(OrderFeeParameters parameters) switch (security.Type) { case SecurityType.Forex: - // get the total order value in the account currency - var totalOrderValue = order.GetValue(security); - var fee = Math.Abs(_forexCommissionRate*totalOrderValue); - feeResult = Math.Max(_forexMinimumOrderFee, fee); - // IB Forex fees are all in USD - feeCurrency = Currencies.USD; + InteractiveBrokersFeeHelper.CalculateForexFee(security, order, _forexCommissionRate, _forexMinimumOrderFee, out feeResult, out feeCurrency); break; case SecurityType.Option: case SecurityType.IndexOption: - Func optionsCommissionFunc; - if (!_optionFee.TryGetValue(market, out optionsCommissionFunc)) - { - throw new KeyNotFoundException(Messages.InteractiveBrokersFeeModel.UnexpectedOptionMarket(market)); - } - // applying commission function to the order - var optionFee = optionsCommissionFunc(quantity, GetPotentialOrderPrice(order, security)); - feeResult = optionFee.Amount; - feeCurrency = optionFee.Currency; + InteractiveBrokersFeeHelper.CalculateOptionFee(security, order, quantity, market, _optionFee, out feeResult, out feeCurrency); break; case SecurityType.Future: case SecurityType.FutureOption: - // The futures options fee model is exactly the same as futures' fees on IB. - if (market == Market.Globex || market == Market.NYMEX - || market == Market.CBOT || market == Market.ICE - || market == Market.CFE || market == Market.COMEX - || market == Market.CME || market == Market.NYSELIFFE) - { - // just in case... - market = Market.USA; - } - - if (!_futureFee.TryGetValue(market, out var feeRatePerContractFunc)) - { - throw new KeyNotFoundException(Messages.InteractiveBrokersFeeModel.UnexpectedFutureMarket(market)); - } - - var feeRatePerContract = feeRatePerContractFunc(security); - feeResult = quantity * feeRatePerContract.Amount; - feeCurrency = feeRatePerContract.Currency; + InteractiveBrokersFeeHelper.CalculateFutureFopFee(security, quantity, market, _futureFee, out feeResult, out feeCurrency); break; case SecurityType.Equity: - EquityFee equityFee; - switch (market) - { - case Market.USA: - equityFee = new EquityFee(Currencies.USD, feePerShare: 0.005m, minimumFee: 1, maximumFeeRate: 0.005m); - break; - case Market.India: - equityFee = new EquityFee(Currencies.INR, feePerShare: 0.01m, minimumFee: 6, maximumFeeRate: 20); - break; - default: - throw new KeyNotFoundException(Messages.InteractiveBrokersFeeModel.UnexpectedEquityMarket(market)); - } var tradeValue = Math.Abs(order.GetValue(security)); - - //Per share fees - var tradeFee = equityFee.FeePerShare * quantity; - - //Maximum Per Order: equityFee.MaximumFeeRate - //Minimum per order. $equityFee.MinimumFee - var maximumPerOrder = equityFee.MaximumFeeRate * tradeValue; - if (tradeFee < equityFee.MinimumFee) - { - tradeFee = equityFee.MinimumFee; - } - else if (tradeFee > maximumPerOrder) - { - tradeFee = maximumPerOrder; - } - - feeCurrency = equityFee.Currency; - //Always return a positive fee. - feeResult = Math.Abs(tradeFee); + InteractiveBrokersFeeHelper.CalculateEquityFee(quantity, tradeValue, market, 0.005m, 1m, out feeResult, out feeCurrency); break; case SecurityType.Cfd: - var value = Math.Abs(order.GetValue(security)); - feeResult = 0.00002m * value; // 0.002% - feeCurrency = security.QuoteCurrency.Symbol; - - var minimumFee = security.QuoteCurrency.Symbol switch - { - "JPY" => 40.0m, - "HKD" => 10.0m, - _ => 1.0m - }; - feeResult = Math.Max(feeResult, minimumFee); + InteractiveBrokersFeeHelper.CalculateCfdFee(security, order, out feeResult, out feeCurrency); + break; + + case SecurityType.Crypto: + InteractiveBrokersFeeHelper.CalculateCryptoFee(security, order, _cryptoCommissionRate, CryptoMinimumOrderFee, out feeResult, out feeCurrency); break; default: @@ -194,123 +132,6 @@ public override OrderFee GetOrderFee(OrderFeeParameters parameters) feeCurrency)); } - /// - /// Approximates the order's price based on the order type - /// - protected static decimal GetPotentialOrderPrice(Order order, Security security) - { - decimal price = 0; - switch (order.Type) - { - case OrderType.TrailingStop: - price = (order as TrailingStopOrder).StopPrice; - break; - case OrderType.StopMarket: - price = (order as StopMarketOrder).StopPrice; - break; - case OrderType.ComboMarket: - case OrderType.MarketOnOpen: - case OrderType.MarketOnClose: - case OrderType.Market: - decimal securityPrice; - if (order.Direction == OrderDirection.Buy) - { - price = security.BidPrice; - } - else - { - price = security.AskPrice; - } - break; - case OrderType.ComboLimit: - price = (order as ComboLimitOrder).GroupOrderManager.LimitPrice; - break; - case OrderType.ComboLegLimit: - price = (order as ComboLegLimitOrder).LimitPrice; - break; - case OrderType.StopLimit: - price = (order as StopLimitOrder).LimitPrice; - break; - case OrderType.LimitIfTouched: - price = (order as LimitIfTouchedOrder).LimitPrice; - break; - case OrderType.Limit: - price = (order as LimitOrder).LimitPrice; - break; - } - - return price; - } - - /// - /// Determines which tier an account falls into based on the monthly trading volume - /// - private static void ProcessForexRateSchedule(decimal monthlyForexTradeAmountInUSDollars, out decimal commissionRate, out decimal minimumOrderFee) - { - const decimal bp = 0.0001m; - if (monthlyForexTradeAmountInUSDollars <= 1000000000) // 1 billion - { - commissionRate = 0.20m * bp; - minimumOrderFee = 2.00m; - } - else if (monthlyForexTradeAmountInUSDollars <= 2000000000) // 2 billion - { - commissionRate = 0.15m * bp; - minimumOrderFee = 1.50m; - } - else if (monthlyForexTradeAmountInUSDollars <= 5000000000) // 5 billion - { - commissionRate = 0.10m * bp; - minimumOrderFee = 1.25m; - } - else - { - commissionRate = 0.08m * bp; - minimumOrderFee = 1.00m; - } - } - - /// - /// Determines which tier an account falls into based on the monthly trading volume - /// - private static void ProcessOptionsRateSchedule(decimal monthlyOptionsTradeAmountInContracts, out Func optionsCommissionFunc) - { - if (monthlyOptionsTradeAmountInContracts <= 10000) - { - optionsCommissionFunc = (orderSize, premium) => - { - var commissionRate = premium >= 0.1m ? - 0.65m : - (0.05m <= premium && premium < 0.1m ? 0.5m : 0.25m); - return new CashAmount(Math.Max(orderSize * commissionRate, 1.0m), Currencies.USD); - }; - } - else if (monthlyOptionsTradeAmountInContracts <= 50000) - { - optionsCommissionFunc = (orderSize, premium) => - { - var commissionRate = premium >= 0.05m ? 0.5m : 0.25m; - return new CashAmount(Math.Max(orderSize * commissionRate, 1.0m), Currencies.USD); - }; - } - else if (monthlyOptionsTradeAmountInContracts <= 100000) - { - optionsCommissionFunc = (orderSize, premium) => - { - var commissionRate = 0.25m; - return new CashAmount(Math.Max(orderSize * commissionRate, 1.0m), Currencies.USD); - }; - } - else - { - optionsCommissionFunc = (orderSize, premium) => - { - var commissionRate = 0.15m; - return new CashAmount(Math.Max(orderSize * commissionRate, 1.0m), Currencies.USD); - }; - } - } - private static CashAmount UnitedStatesFutureFees(Security security) { IDictionary fees, exchangeFees; @@ -321,12 +142,12 @@ private static CashAmount UnitedStatesFutureFees(Security security) { case SecurityType.Future: fees = _usaFuturesFees; - exchangeFees = _usaFuturesExchangeFees; + exchangeFees = InteractiveBrokersFeeHelper.UsaFuturesExchangeFees; symbol = security.Symbol.ID.Symbol; break; case SecurityType.FutureOption: fees = _usaFutureOptionsFees; - exchangeFees = _usaFutureOptionsExchangeFees; + exchangeFees = InteractiveBrokersFeeHelper.UsaFutureOptionsExchangeFees; symbol = security.Symbol.Underlying.ID.Symbol; break; default: @@ -347,68 +168,6 @@ private static CashAmount UnitedStatesFutureFees(Security security) return new CashAmount(ibFeePerContract + exchangeFeePerContract + 0.02m, Currencies.USD); } - /// - /// See https://www.hkex.com.hk/Services/Rules-and-Forms-and-Fees/Fees/Listed-Derivatives/Trading/Transaction?sc_lang=en - /// - private static CashAmount HongKongFutureFees(Security security) - { - if (security.Symbol.ID.Symbol.Equals("HSI", StringComparison.InvariantCultureIgnoreCase)) - { - // IB fee + exchange fee - return new CashAmount(30 + 10, Currencies.HKD); - } - - decimal ibFeePerContract; - switch (security.QuoteCurrency.Symbol) - { - case Currencies.CNH: - ibFeePerContract = 13; - break; - case Currencies.HKD: - ibFeePerContract = 20; - break; - case Currencies.USD: - ibFeePerContract = 2.40m; - break; - default: - throw new ArgumentException(Messages.InteractiveBrokersFeeModel.HongKongFutureFeesUnexpectedQuoteCurrency(security)); - } - - // let's add a 50% extra charge for exchange fees - return new CashAmount(ibFeePerContract * 1.5m, security.QuoteCurrency.Symbol); - } - - private static CashAmount EUREXFutureFees(Security security) - { - IDictionary fees, exchangeFees; - decimal ibFeePerContract, exchangeFeePerContract; - string symbol; - - switch (security.Symbol.SecurityType) - { - case SecurityType.Future: - fees = _eurexFuturesFees; - exchangeFees = _eurexFuturesExchangeFees; - symbol = security.Symbol.ID.Symbol; - break; - default: - throw new ArgumentException(Messages.InteractiveBrokersFeeModel.EUREXFutureFeesUnsupportedSecurityType(security)); - } - - if (!fees.TryGetValue(symbol, out ibFeePerContract)) - { - ibFeePerContract = 1.00m; - } - - if (!exchangeFees.TryGetValue(symbol, out exchangeFeePerContract)) - { - exchangeFeePerContract = 0.00m; - } - - // Add exchange fees + IBKR regulatory fee (0.02) - return new CashAmount(ibFeePerContract + exchangeFeePerContract + 0.02m, Currencies.EUR); - } - /// /// Reference at https://www.interactivebrokers.com/en/pricing/commissions-futures.php?re=amer /// @@ -426,15 +185,6 @@ private static CashAmount EUREXFutureFees(Security security) { "MIR", 0.15m }, { "M6C", 0.15m }, { "M6S", 0.15m }, { "MNH", 0.15m }, }; - /// - /// Reference at https://www.interactivebrokers.com/en/pricing/commissions-futures-europe.php?re=europe - /// - private static readonly Dictionary _eurexFuturesFees = new() - { - // Futures - { "FESX", 1.00m }, - }; - private static readonly Dictionary _usaFutureOptionsFees = new() { // Micro E-mini Future Options @@ -443,60 +193,5 @@ private static CashAmount EUREXFutureFees(Security security) // Cryptocurrency Future Options { "BTC", 5m }, { "MBT", 1.25m }, { "ETH", 3m }, { "MET", 0.10m }, }; - - private static readonly Dictionary _usaFuturesExchangeFees = new() - { - // E-mini Futures - { "ES", 1.28m }, { "NQ", 1.28m }, { "YM", 1.28m }, { "RTY", 1.28m }, { "EMD", 1.28m }, - // Micro E-mini Futures - { "MYM", 0.30m }, { "M2K", 0.30m }, { "MES", 0.30m }, { "MNQ", 0.30m }, { "2YY", 0.30m }, { "5YY", 0.30m }, { "10Y", 0.30m }, - { "30Y", 0.30m }, { "MCL", 0.30m }, { "MGC", 0.30m }, { "SIL", 0.30m }, - // Cryptocurrency Futures - { "BTC", 6m }, { "MBT", 2.5m }, { "ETH", 4m }, { "MET", 0.20m }, - // E-mini FX (currencies) Futures - { "E7", 0.85m }, { "J7", 0.85m }, - // Micro E-mini FX (currencies) Futures - { "M6E", 0.24m }, { "M6A", 0.24m }, { "M6B", 0.24m }, { "MCD", 0.24m }, { "MJY", 0.24m }, { "MSF", 0.24m }, { "M6J", 0.24m }, - { "MIR", 0.24m }, { "M6C", 0.24m }, { "M6S", 0.24m }, { "MNH", 0.24m }, - }; - - private static readonly Dictionary _eurexFuturesExchangeFees = new() - { - // Futures - { "FESX", 0.00m }, - }; - - private static readonly Dictionary _usaFutureOptionsExchangeFees = new() - { - // E-mini Future Options - { "ES", 0.55m }, { "NQ", 0.55m }, { "YM", 0.55m }, { "RTY", 0.55m }, { "EMD", 0.55m }, - // Micro E-mini Future Options - { "MYM", 0.20m }, { "M2K", 0.20m }, { "MES", 0.20m }, { "MNQ", 0.20m }, { "2YY", 0.20m }, { "5YY", 0.20m }, { "10Y", 0.20m }, - { "30Y", 0.20m }, { "MCL", 0.20m }, { "MGC", 0.20m }, { "SIL", 0.20m }, - // Cryptocurrency Future Options - { "BTC", 5m }, { "MBT", 2.5m }, { "ETH", 4m }, { "MET", 0.20m }, - }; - - /// - /// Helper class to handle IB Equity fees - /// - private class EquityFee - { - public string Currency { get; } - public decimal FeePerShare { get; } - public decimal MinimumFee { get; } - public decimal MaximumFeeRate { get; } - - public EquityFee(string currency, - decimal feePerShare, - decimal minimumFee, - decimal maximumFeeRate) - { - Currency = currency; - FeePerShare = feePerShare; - MinimumFee = minimumFee; - MaximumFeeRate = maximumFeeRate; - } - } } } diff --git a/Common/Orders/Fees/InteractiveBrokersTieredFeeModel.cs b/Common/Orders/Fees/InteractiveBrokersTieredFeeModel.cs new file mode 100644 index 000000000000..37cbff7a8cb0 --- /dev/null +++ b/Common/Orders/Fees/InteractiveBrokersTieredFeeModel.cs @@ -0,0 +1,287 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using QuantConnect.Securities; +using QuantConnect.Securities.Equity; +using System.Collections.Generic; +using System.Linq; + +namespace QuantConnect.Orders.Fees +{ + /// + /// Provides the implementation of for Interactive Brokers Tiered Fee Structure + /// + public class InteractiveBrokersTieredFeeModel : FeeModel + { + private const decimal EquityMinimumOrderFee = 0.35m; + private const decimal CryptoMinimumOrderFee = 1.75m; + private decimal _equityCommissionRate; + private int _futureCommissionTier; + private decimal _forexCommissionRate; + private decimal _forexMinimumOrderFee; + private decimal _cryptoCommissionRate; + #pragma warning disable CS1570 + /// + /// Reference at https://www.interactivebrokers.com/en/index.php?f=commission&p=futures1 + /// + #pragma warning restore CS1570 + private Dictionary> _futureFee; + // option commission function takes number of contracts and the size of the option premium and returns total commission + private readonly Dictionary> _optionFee = + new Dictionary>(); + private Dictionary _monthlyTradeVolume; + private DateTime _lastOrderTime = DateTime.MinValue; + // List of Option exchanges susceptible to pay ORF regulatory fee. + private static readonly List _optionExchangesOrfFee = new() { Market.CBOE, Market.USA }; + + /// + /// Initializes a new instance of the + /// + /// Monthly Equity shares traded + /// Monthly Future contracts traded + /// Monthly FX dollar volume traded + /// Monthly options contracts traded + /// Monthly Crypto dollar volume traded (in USD) + public InteractiveBrokersTieredFeeModel(decimal monthlyEquityTradeVolume = 0, decimal monthlyFutureTradeVolume = 0, decimal monthlyForexTradeAmountInUSDollars = 0, + decimal monthlyOptionsTradeAmountInContracts = 0, decimal monthlyCryptoTradeAmountInUSDollars = 0) + { + ReprocessRateSchedule(monthlyEquityTradeVolume, monthlyFutureTradeVolume, monthlyForexTradeAmountInUSDollars, monthlyOptionsTradeAmountInContracts, monthlyCryptoTradeAmountInUSDollars); + // IB fee + exchange fee + _futureFee = new() + { + { Market.USA, UnitedStatesFutureFees }, + { Market.HKFE, InteractiveBrokersFeeHelper.HongKongFutureFees }, + { Market.EUREX, InteractiveBrokersFeeHelper.EUREXFutureFees } + }; + + _monthlyTradeVolume = new() + { + { SecurityType.Equity, monthlyEquityTradeVolume }, + { SecurityType.Future, monthlyFutureTradeVolume }, + { SecurityType.Forex, monthlyForexTradeAmountInUSDollars }, + { SecurityType.Option, monthlyOptionsTradeAmountInContracts }, + { SecurityType.Crypto, monthlyCryptoTradeAmountInUSDollars }, + }; + } + + /// + /// Reprocess the rate schedule based on the current traded volume in various assets. + /// + /// Monthly Equity shares traded + /// Monthly Future contracts traded + /// Monthly FX dollar volume traded + /// Monthly options contracts traded + /// Monthly Crypto dollar volume traded (in USD) + private void ReprocessRateSchedule(decimal monthlyEquityTradeVolume, decimal monthlyFutureTradeVolume, decimal monthlyForexTradeAmountInUSDollars, + decimal monthlyOptionsTradeAmountInContracts, decimal monthlyCryptoTradeAmountInUSDollars) + { + InteractiveBrokersFeeHelper.ProcessEquityRateSchedule(monthlyEquityTradeVolume, out _equityCommissionRate); + InteractiveBrokersFeeHelper.ProcessFutureRateSchedule(monthlyFutureTradeVolume, out _futureCommissionTier); + InteractiveBrokersFeeHelper.ProcessForexRateSchedule(monthlyForexTradeAmountInUSDollars, out _forexCommissionRate, out _forexMinimumOrderFee); + Func optionsCommissionFunc; + InteractiveBrokersFeeHelper.ProcessOptionsRateSchedule(monthlyOptionsTradeAmountInContracts, out optionsCommissionFunc); + _optionFee[Market.USA] = optionsCommissionFunc; + InteractiveBrokersFeeHelper.ProcessCryptoRateSchedule(monthlyCryptoTradeAmountInUSDollars, out _cryptoCommissionRate); + } + + /// + /// Gets the order fee associated with the specified order. This returns the cost + /// of the transaction in the account currency + /// + /// A object + /// containing the security and order + /// The cost of the order in units of the account currency + public override OrderFee GetOrderFee(OrderFeeParameters parameters) + { + var order = parameters.Order; + var security = parameters.Security; + + // Reset monthly trade value tracker when month rollover. + if (_lastOrderTime.Month != order.Time.Month && _lastOrderTime != DateTime.MinValue) + { + _monthlyTradeVolume = _monthlyTradeVolume.ToDictionary(kvp => kvp.Key, _ => 0m); + } + // Reprocess the rate schedule based on the current traded volume in various assets. + ReprocessRateSchedule(_monthlyTradeVolume[SecurityType.Equity], _monthlyTradeVolume[SecurityType.Future], _monthlyTradeVolume[SecurityType.Forex], + _monthlyTradeVolume[SecurityType.Option], _monthlyTradeVolume[SecurityType.Crypto]); + + // Option exercise for equity options is free of charge + if (order.Type == OrderType.OptionExercise) + { + var optionOrder = (OptionExerciseOrder)order; + + // For Futures Options, contracts are charged the standard commission at expiration of the contract. + // Read more here: https://www1.interactivebrokers.com/en/index.php?f=14718#trading-related-fees + if (optionOrder.Symbol.ID.SecurityType == SecurityType.Option) + { + return OrderFee.Zero; + } + } + + var quantity = order.AbsoluteQuantity; + decimal feeResult; + string feeCurrency; + var market = security.Symbol.ID.Market; + switch (security.Type) + { + case SecurityType.Forex: + // Update the monthly value traded + _monthlyTradeVolume[SecurityType.Forex] += InteractiveBrokersFeeHelper.CalculateForexFee(security, order, _forexCommissionRate, _forexMinimumOrderFee, out feeResult, out feeCurrency); + break; + + case SecurityType.Option: + case SecurityType.IndexOption: + var orderPrice = InteractiveBrokersFeeHelper.CalculateOptionFee(security, order, quantity, market, _optionFee, out feeResult, out feeCurrency); + // Regulatory Fee: Options Regulatory Fee (ORF) + FINRA Consolidated Audit Trail Fees + var regulatory = _optionExchangesOrfFee.Contains(market) ? + (0.01915m + 0.0048m) * quantity : + 0.0048m * quantity; + // Transaction Fees: SEC Transaction Fee + FINRA Trading Activity Fee (only charge on sell) + var transaction = order.Direction == OrderDirection.Sell ? + 0.0000278m * Math.Abs(order.GetValue(security)) + 0.00279m * quantity : + 0m; + // Clearing Fee + var clearing = Math.Min(0.02m * quantity, 55m); + + feeResult += regulatory + transaction + clearing; + + // Update the monthly value traded + _monthlyTradeVolume[SecurityType.Option] += quantity * orderPrice; + break; + + case SecurityType.Future: + case SecurityType.FutureOption: + InteractiveBrokersFeeHelper.CalculateFutureFopFee(security, quantity, market, _futureFee, out feeResult, out feeCurrency); + // Update the monthly contracts traded + _monthlyTradeVolume[SecurityType.Future] += quantity; + break; + + case SecurityType.Equity: + var tradeValue = Math.Abs(order.GetValue(security)); + InteractiveBrokersFeeHelper.CalculateEquityFee(quantity, tradeValue, market, _equityCommissionRate, EquityMinimumOrderFee, out feeResult, out feeCurrency); + + // Tiered fee model has the below extra cost. + // FINRA Trading Activity Fee only applies to sale of security. + var finraTradingActivityFee = order.Direction == OrderDirection.Sell ? Math.Min(8.3m, quantity * 0.000166m) : 0m; + // Regulatory Fees. + var regulatoryFee = tradeValue * 0.0000278m // SEC Transaction Fee + + finraTradingActivityFee // FINRA Trading Activity Fee + + quantity * 0.000048m; // FINRA Consolidated Audit Trail Fees + // Clearing Fee: NSCC, DTC Fees. + var clearingFee = Math.Min(quantity * 0.0002m, tradeValue * 0.005m); + // Exchange related handling fees. + var exchangeFee = InteractiveBrokersFeeHelper.GetEquityExchangeFee(order, (security as Equity).PrimaryExchange, tradeValue, feeResult); + // FINRA Pass Through Fees. + var passThroughFee = Math.Min(8.3m, feeResult * 0.00056m); + + feeResult += regulatoryFee + clearingFee + exchangeFee + passThroughFee; + + // Update the monthly volume shares traded + _monthlyTradeVolume[SecurityType.Equity] += quantity; + break; + + case SecurityType.Cfd: + InteractiveBrokersFeeHelper.CalculateCfdFee(security, order, out feeResult, out feeCurrency); + break; + + case SecurityType.Crypto: + // Update the monthly value traded + _monthlyTradeVolume[SecurityType.Crypto] += InteractiveBrokersFeeHelper.CalculateCryptoFee(security, order, _cryptoCommissionRate, CryptoMinimumOrderFee, out feeResult, out feeCurrency); + break; + + default: + // unsupported security type + throw new ArgumentException(Messages.FeeModel.UnsupportedSecurityType(security)); + } + + _lastOrderTime = order.Time; + + return new OrderFee(new CashAmount( + feeResult, + feeCurrency)); + } + + private CashAmount UnitedStatesFutureFees(Security security) + { + IDictionary fees; + IDictionary exchangeFees; + decimal[] ibFeePerContract; + decimal exchangeFeePerContract; + string symbol; + + switch (security.Symbol.SecurityType) + { + case SecurityType.Future: + fees = _usaFuturesFees; + exchangeFees = InteractiveBrokersFeeHelper.UsaFuturesExchangeFees; + symbol = security.Symbol.ID.Symbol; + break; + case SecurityType.FutureOption: + fees = _usaFutureOptionsFees; + exchangeFees = InteractiveBrokersFeeHelper.UsaFutureOptionsExchangeFees; + symbol = security.Symbol.Underlying.ID.Symbol; + break; + default: + throw new ArgumentException(Messages.InteractiveBrokersFeeModel.UnitedStatesFutureFeesUnsupportedSecurityType(security)); + } + + if (!fees.TryGetValue(symbol, out ibFeePerContract)) + { + ibFeePerContract = new[] { 0.85m, 0.65m, 0.45m, 0.25m }; + } + + if (!exchangeFees.TryGetValue(symbol, out exchangeFeePerContract)) + { + exchangeFeePerContract = 1.60m; + } + + // Add exchange fees + IBKR regulatory fee (0.02) + return new CashAmount(ibFeePerContract[_futureCommissionTier] + exchangeFeePerContract + 0.02m, Currencies.USD); + } + + /// + /// Reference at https://www.interactivebrokers.com/en/pricing/commissions-futures.php?re=amer + /// + private static readonly Dictionary _usaFuturesFees = new() + { + // Micro E-mini Futures + { "MYM", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, { "M2K", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, { "MES", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, + { "MNQ", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, { "2YY", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, { "5YY", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, + { "10Y", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, { "30Y", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, { "MCL", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, + { "MGC", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, { "SIL", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, + // Cryptocurrency Futures + { "BTC", new decimal[] { 5m, 5m, 5m, 5m } }, { "MBT", new decimal[] { 2.25m, 2.25m, 2.25m, 2.25m } }, { "ETH", new decimal[] { 3m, 3m, 3m, 3m } }, { "MET", new decimal[] { 0.2m, 0.2m, 0.2m, 0.2m } }, + // E-mini FX (currencies) Futures + { "E7", new decimal[] { 0.5m, 0.4m, 0.3m, 0.15m } }, { "J7", new decimal[] { 0.5m, 0.4m, 0.3m, 0.15m } }, + // Micro E-mini FX (currencies) Futures + { "M6E", new decimal[] { 0.15m, 0.12m, 0.08m, 0.05m } }, { "M6A", new decimal[] { 0.15m, 0.12m, 0.08m, 0.05m } }, { "M6B", new decimal[] { 0.15m, 0.12m, 0.08m, 0.05m } }, + { "MCD", new decimal[] { 0.15m, 0.12m, 0.08m, 0.05m } }, { "MJY", new decimal[] { 0.15m, 0.12m, 0.08m, 0.05m } }, { "MSF", new decimal[] { 0.15m, 0.12m, 0.08m, 0.05m } }, + { "M6J", new decimal[] { 0.15m, 0.12m, 0.08m, 0.05m } }, { "MIR", new decimal[] { 0.15m, 0.12m, 0.08m, 0.05m } }, { "M6C", new decimal[] { 0.15m, 0.12m, 0.08m, 0.05m } }, + { "M6S", new decimal[] { 0.15m, 0.12m, 0.08m, 0.05m } }, { "MNH", new decimal[] { 0.15m, 0.12m, 0.08m, 0.05m } }, + }; + + private static readonly Dictionary _usaFutureOptionsFees = new() + { + // Micro E-mini Future Options + { "MYM", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, { "M2K", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, { "MES", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, + { "MNQ", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, { "2YY", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, { "5YY", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, + { "10Y", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, { "30Y", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, { "MCL", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, + { "MGC", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, { "SIL", new decimal[] { 0.25m, 0.2m, 0.15m, 0.1m } }, + // Cryptocurrency Future Options + { "BTC", new decimal[] { 5m, 5m, 5m, 5m } }, { "MBT", new decimal[] { 1.25m, 1.25m, 1.25m, 1.25m } }, { "ETH", new decimal[] { 3m, 3m, 3m, 3m } }, { "MET", new decimal[] { 0.1m, 0.1m, 0.1m, 0.1m } }, + }; + } +} diff --git a/Common/Orders/Fees/InteractiveBrokersTieredFeeModel.py b/Common/Orders/Fees/InteractiveBrokersTieredFeeModel.py new file mode 100644 index 000000000000..26cf4e385c4f --- /dev/null +++ b/Common/Orders/Fees/InteractiveBrokersTieredFeeModel.py @@ -0,0 +1,554 @@ +# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. +# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from AlgorithmImports import * + +### +### Provides the implementation of "IFeeModel" for Interactive Brokers Tiered Fee Structure +### +class InteractiveBrokersTieredFeeModel(FeeModel): + equity_minimum_order_fee = 0.35 + equity_commission_rate = None + future_commission_tier = None + forex_commission_rate = None + forex_minimum_order_fee = None + crypto_commission_rate = None + crypto_minimum_order_fee = 1.75 + # option commission function takes number of contracts and the size of the option premium and returns total commission + option_fee = {} + last_order_time = datetime.min + # List of Option exchanges susceptible to pay ORF regulatory fee. + option_exchanges_orf_fee = [Market.CBOE, Market.USA] + + def __init__(self, monthly_equity_trade_volume: float = 0, monthly_future_trade_volume: float = 0, monthly_forex_trade_amount_in_us_dollars: float = 0, + monthly_options_trade_amount_in_contracts: float = 0, monthly_crypto_trade_amount_in_us_dollars: float = 0) -> None: + '''Initializes a new instance of the "InteractiveBrokersTieredFeeModel" + Args: + monthly_equity_trade_volume: Monthly Equity shares traded + monthly_future_trade_volume: Monthly Future contracts traded + monthly_forex_trade_amount_in_us_dollars: Monthly FX dollar volume traded + monthly_options_trade_amount_in_contracts: Monthly options contracts traded + monthly_crypto_trade_amount_in_us_dollars: Monthly Crypto dollar volume traded (in USD)''' + self.reprocess_rate_schedule(monthly_equity_trade_volume, + monthly_future_trade_volume, + monthly_forex_trade_amount_in_us_dollars, + monthly_options_trade_amount_in_contracts, + monthly_crypto_trade_amount_in_us_dollars) + + # IB fee + exchange fee + # Reference at https://www.interactivebrokers.com/en/index.php?f=commission&p=futures1 + self.future_fee = { + Market.USA: self.united_states_future_fees, + Market.HKFE: self.hong_kong_future_fees, + Market.EUREX: self.eurex_future_fees + } + + self.monthly_trade_volume = { + SecurityType.EQUITY: monthly_equity_trade_volume, + SecurityType.FUTURE: monthly_future_trade_volume, + SecurityType.FOREX: monthly_forex_trade_amount_in_us_dollars, + SecurityType.OPTION: monthly_options_trade_amount_in_contracts, + SecurityType.CRYPTO: monthly_crypto_trade_amount_in_us_dollars + } + + def reprocess_rate_schedule(self, monthly_equity_trade_volume: float, monthly_future_trade_volume: float, monthly_forex_trade_amount_in_us_dollars: float, + monthly_options_trade_amount_in_contracts: float, monthly_crypto_trade_amount_in_us_dollars: float) -> None: + '''Reprocess the rate schedule based on the current traded volume in various assets. + Args: + monthly_equity_trade_volume: Monthly Equity shares traded + monthly_future_trade_volume: Monthly Future contracts traded + monthly_forex_trade_amount_in_us_dollars: Monthly FX dollar volume traded + monthly_options_trade_amount_in_contracts: Monthly options contracts traded + monthly_crypto_trade_amount_in_us_dollars: Monthly Crypto dollar volume traded (in USD)''' + self.equity_commission_rate = self.process_equity_rate_schedule(monthly_equity_trade_volume) + self.future_commission_tier = self.process_future_rate_schedule(monthly_future_trade_volume) + self.forex_commission_rate, self.forex_minimum_order_fee = self.process_forex_rate_schedule(monthly_forex_trade_amount_in_us_dollars) + self.option_fee[Market.USA] = self.process_option_rate_schedule(monthly_options_trade_amount_in_contracts) + self.crypto_commission_rate = self.process_crypto_rate_schedule(monthly_crypto_trade_amount_in_us_dollars) + + def process_equity_rate_schedule(self, monthly_equity_trade_volume: float) -> float: + '''Determines which tier an account falls into based on the monthly trading volume of Equities (in shares) + Args: + monthly_equity_trade_volume: Monthly Equity shares traded + Remarks: + https://www.interactivebrokers.com/en/pricing/commissions-stocks.php?re=amer + Return: + Commission rate per each share of equity trade''' + if monthly_equity_trade_volume <= 300000: + return 0.0035 + elif monthly_equity_trade_volume <= 3000000: + return 0.002 + elif monthly_equity_trade_volume <= 20000000: + return 0.0015 + elif monthly_equity_trade_volume <= 100000000: + return 0.001 + return 0.0005 + + def process_future_rate_schedule(self, monthly_future_trade_volume: float) -> int: + '''Determines which tier an account falls into based on the monthly trading volume of Futures (in contracts) + Args: + monthly_future_trade_volume: Monthly Future contracts traded + Remarks: + https://www.interactivebrokers.com/en/pricing/commissions-futures.php?re=amer + Return: + Commission tier of Future & FOP trades''' + if monthly_future_trade_volume <= 1000: + return 0 + elif monthly_future_trade_volume <= 10000: + return 1 + elif monthly_future_trade_volume <= 20000: + return 2 + return 3 + + def process_forex_rate_schedule(self, monthly_forex_trade_amount_in_us_dollars: float) -> Tuple[float, float]: + '''Determines which tier an account falls into based on the monthly trading volume of Forex + Args: + monthly_forex_trade_amount_in_us_dollars: Monthly FX dollar volume traded + Remarks: + https://www.interactivebrokers.com/en/pricing/commissions-spot-currencies.php?re=amer + Return: + Commission rate per each dollar of forex traded''' + bp = 0.0001 + if monthly_forex_trade_amount_in_us_dollars <= 1000000000: + return 0.2 * bp, 2 + elif monthly_forex_trade_amount_in_us_dollars <= 2000000000: + return 0.15 * bp, 1.5 + elif monthly_forex_trade_amount_in_us_dollars <= 5000000000: + return 0.1 * bp, 1.25 + return 0.08 * bp, 1 + + def process_option_rate_schedule(self, monthly_options_trade_amount_in_contracts: float) -> Callable[[float, float], CashAmount]: + '''Determines which tier an account falls into based on the monthly trading volume of Options + Args: + monthly_options_trade_amount_in_contracts: Monthly options contracts traded + Remarks: + https://www.interactivebrokers.com/en/pricing/commissions-options.php?re=amer + Return: + Function to calculate the commission rate per each each option contract traded''' + if monthly_options_trade_amount_in_contracts <= 10000: + return lambda order_size, premium: CashAmount(max(order_size * (0.65 if premium >= 0.1 else (0.25 if premium < 0.05 else 0.5)), 1), Currencies.USD) + elif monthly_options_trade_amount_in_contracts <= 50000: + return lambda order_size, premium: CashAmount(max(order_size * (0.5 if premium >= 0.05 else 0.25), 1), Currencies.USD) + elif monthly_options_trade_amount_in_contracts <= 100000: + return lambda order_size, _: CashAmount(max(order_size * 0.25, 1), Currencies.USD) + return lambda order_size, _: CashAmount(max(order_size * 0.15, 1), Currencies.USD) + + def process_crypto_rate_schedule(self, monthly_crypto_trade_amount_in_us_dollars: float) -> float: + '''Determines which tier an account falls into based on the monthly trading volume of Crypto + Args: + monthly_crypto_trade_amount_in_us_dollars: Monthly Crypto dollar volume traded (in USD) + Remarks: + https://www.interactivebrokers.com/en/pricing/commissions-cryptocurrencies.php?re=amer + Return: + Commission rate of crypto trades''' + if monthly_crypto_trade_amount_in_us_dollars <= 100000: + return 0.0018 + elif monthly_crypto_trade_amount_in_us_dollars <= 1000000: + return 0.0015 + return 0.0012 + + def get_order_fee(self, parameters: OrderFeeParameters) -> OrderFee: + '''Gets the order fee associated with the specified order. This returns the cost of the transaction in the account currency + Args: + parameters: An "OrderFeeParameters" object containing the security and order + Return: + The cost of the order in units of the account currency''' + order = parameters.order + security = parameters.security + + # Reset monthly trade value tracker when month rollover. + if self.last_order_time.month != order.time.month and self.last_order_time != datetime.min: + self.monthly_trade_volume = {key: 0 for key in self.monthly_trade_volume.keys()} + # Reprocess the rate schedule based on the current traded volume in various assets. + self.reprocess_rate_schedule(self.monthly_trade_volume[SecurityType.EQUITY], + self.monthly_trade_volume[SecurityType.FUTURE], + self.monthly_trade_volume[SecurityType.FOREX], + self.monthly_trade_volume[SecurityType.OPTION], + self.monthly_trade_volume[SecurityType.CRYPTO]) + + # Option exercise for equity options is free of charge + if order.type == OrderType.OPTION_EXERCISE: + # For Futures Options, contracts are charged the standard commission at expiration of the contract. + # Read more here: https://www1.interactivebrokers.com/en/index.php?f=14718#trading-related-fees + if order.symbol.id.security_type == SecurityType.OPTION: + return OrderFee.ZERO + + quantity = order.absolute_quantity + market = security.symbol.id.market + + if security.Type == SecurityType.FOREX: + fee_result, fee_currency, trade_value = self.calculate_forex_fee(security, order, self.forex_commission_rate, self.forex_minimum_order_fee) + + # Update the monthly value traded + self.monthly_trade_volume[SecurityType.FOREX] += trade_value + + elif security.Type == SecurityType.OPTION or security.Type == SecurityType.INDEX_OPTION: + fee_result, fee_currency, order_price = self.calculate_option_fee(security, order, quantity, market, self.option_fee) + # Regulatory Fee: Options Regulatory Fee (ORF) + FINRA Consolidated Audit Trail Fees + regulatory = ((0.01915 + 0.0048) if market in self.option_exchanges_orf_fee else 0.0048) * quantity + # Transaction Fees: SEC Transaction Fee + FINRA Trading Activity Fee (only charge on sell) + transaction = 0.0000278 * abs(order.get_value(security)) + 0.00279 * quantity if order.direction == OrderDirection.SELL else 0 + # Clearing Fee + clearing = min(0.02 * quantity, 55) + + fee_result += regulatory + transaction + clearing + + # Update the monthly value traded + self.monthly_trade_volume[SecurityType.OPTION] += quantity * order_price + + elif security.Type == SecurityType.FUTURE or security.Type == SecurityType.FUTURE_OPTION: + fee_result, fee_currency = self.calculate_future_fop_fee(security, quantity, market, self.future_fee) + + # Update the monthly value traded + self.monthly_trade_volume[SecurityType.FUTURE] += quantity + + elif security.Type == SecurityType.EQUITY: + trade_value = abs(order.get_value(security)) + fee_result, fee_currency = self.calculate_equity_fee(quantity, trade_value, market, self.equity_commission_rate, self.equity_minimum_order_fee) + + # Tiered fee model has the below extra cost. + # FINRA Trading Activity Fee only applies to sale of security. + finra_trading_activity_fee = min(8.3, quantity * 0.000166) if order.direction == OrderDirection.SELL else 0 + # Regulatory Fees: SEC Transaction Fee + FINRA Trading Activity Fee + FINRA Consolidated Audit Trail Fees. + regulatory = trade_value * 0.0000278 \ + + finra_trading_activity_fee \ + + quantity * 0.000048 + # Clearing Fee: NSCC, DTC Fees. + clearing = min(0.0002 * quantity, 0.005 * trade_value) + # Exchange related handling fees. + exchange = self.get_equity_exchange_fee(order, security.primary_exchange, trade_value, fee_result) + # FINRA Pass Through Fees. + pass_through = min(8.3, fee_result * 0.00056) + + fee_result += regulatory + exchange + clearing + pass_through + + # Update the monthly value traded + self.monthly_trade_volume[SecurityType.EQUITY] += quantity + + elif security.Type == SecurityType.CFD: + fee_result, fee_currency = self.calculate_cfd_fee(security, order) + + elif security.Type == SecurityType.CRYPTO: + fee_result, fee_currency, trade_value = self.calculate_crypto_fee(security, order, self.crypto_commission_rate, self.crypto_minimum_order_fee) + + # Update the monthly value traded + self.monthly_trade_volume[SecurityType.CRYPTO] += trade_value + + else: + # unsupported security type + raise ArgumentException(Messages.FeeModel.unsupported_security_type(security)) + + self.last_order_time = order.time + + return OrderFee(CashAmount(fee_result, fee_currency)) + + def calculate_forex_fee(self, security: Security, order: Order, forex_commission_rate: float, forex_minimum_order_fee: float) -> Tuple[float, str, float]: + '''Calculate the transaction fee of a Forex order + Return: + The fee, fee currency, and traded value of the transaction''' + # get the total order value in the account currency + total_order_value = abs(order.get_value(security)) + base_fee = forex_commission_rate * total_order_value + + fee = max(forex_minimum_order_fee, base_fee) + # IB Forex fees are all in USD + return fee, Currencies.USD, total_order_value + + def calculate_option_fee(self, security: Security, order: Order, quantity: float, market: str, fee_ref: Dict[str, Callable[[float, float], CashAmount]]) -> Tuple[float, str, float]: + '''Calculate the transaction fee of an Option order + Return: + The fee, fee currency, and traded value of the transaction''' + option_commission_func = fee_ref.get(market) + if not option_commission_func: + raise Exception(Messages.InteractiveBrokersFeeModel.unexpected_option_market(market)) + # applying commission function to the order + order_price = self.get_potential_order_price(order, security) + option_fee = option_commission_func(quantity, order_price) + + return option_fee.amount, option_fee.currency, order_price * quantity + + def calculate_future_fop_fee(self, security: Security, quantity: float, market: str, fee_ref: Dict[str, Callable[[Security], CashAmount]]) -> Tuple[float, str]: + '''Calculate the transaction fee of a Future or FOP order + Return: + The fee, and fee currency of the transaction''' + # The futures options fee model is exactly the same as futures' fees on IB. + if market == Market.GLOBEX or market == Market.NYMEX or market == Market.CBOT or market == Market.ICE \ + or market == Market.CFE or market == Market.COMEX or market == Market.CME or market == Market.NYSELIFFE: + market = Market.USA + + fee_rate_per_contract_func = fee_ref.get(market) + if not fee_rate_per_contract_func: + raise Exception(Messages.InteractiveBrokersFeeModel.unexpected_future_market(market)) + + fee_rate_per_contract = fee_rate_per_contract_func(security) + fee = quantity * fee_rate_per_contract.amount + return fee, fee_rate_per_contract.currency + + def calculate_equity_fee(self, quantity: float, trade_value: float, market: str, us_fee_rate: float, us_minimum_fee: float) -> Tuple[float, str]: + '''Calculate the transaction fee of an Equity order + Return: + The fee, and fee currency of the transaction''' + if market == Market.USA: + equity_fee = EquityFee(Currencies.USD, fee_per_share=us_fee_rate, minimum_fee=us_minimum_fee, maximum_fee_rate=0.01) + elif market == Market.India: + equity_fee = EquityFee(Currencies.INR, fee_per_share=0.01, minimum_fee=6, maximum_fee_rate=20) + else: + raise Exception(Messages.InteractiveBrokersFeeModel.unexpected_equity_market(market)) + + # Per share fees + trade_fee = equity_fee.fee_per_share * quantity + + # Maximum Per Order: equity_fee.maximum_fee_rate + # Minimum per order. $equity_fee.minimum_fee + maximum_per_order = equity_fee.maximum_fee_rate * trade_value + if trade_fee < equity_fee.minimum_fee: + trade_fee = equity_fee.minimum_fee + elif trade_fee > maximum_per_order: + trade_fee = maximum_per_order + + return abs(trade_fee), equity_fee.currency + + def calculate_cfd_fee(self, security: Security, order: Order) -> Tuple[float, str, float]: + '''Calculate the transaction fee of a CFD order + Return: + The fee, and fee currency of the transaction''' + value = abs(order.get_value(security)) + fee = 0.00002 * value + currency = security.quote_currency.symbol + minimum_fee = 40 if currency == "JPY" else (10 if currency == "HKD" else 1) + + return max(fee, minimum_fee), currency + + def calculate_crypto_fee(self, security: Security, order: Order, crypto_commission_rate: float, crypto_minimum_order_fee: float) -> Tuple[float, str, float]: + '''Calculate the transaction fee of a Crypto order + Return: + The fee, fee currency, and traded value of the transaction''' + # get the total order value in the account currency + total_order_value = abs(order.get_value(security)) + base_fee = crypto_commission_rate * total_order_value + # 1% maximum fee + fee = max(crypto_minimum_order_fee, min(0.01 * total_order_value, base_fee)) + # IB Crypto fees are all in USD + return fee, Currencies.USD, total_order_value + + def united_states_future_fees(self, security: Security) -> CashAmount: + if security.symbol.security_type == SecurityType.FUTURE: + fees = self.usa_futures_fees + exchange_fees = self.usa_futures_exchange_fees + symbol = security.symbol.id.symbol + elif security.symbol.security_type == SecurityType.FUTURE_OPTION: + fees = self.usa_future_options_fees + exchange_fees = self.usa_future_options_exchange_fees + symbol = security.symbol.underlying.id.symbol + else: + raise ArgumentException(Messages.InteractiveBrokersFeeModel.united_states_future_fees_unsupported_security_type(security)) + + fee_per_contract = fees.get(symbol) + if not fee_per_contract: + fee_per_contract = [0.85, 0.65, 0.45, 0.25] + + exchange_fee_per_contract = exchange_fees.get(symbol) + if not exchange_fee_per_contract: + exchange_fee_per_contract = 1.6 + + # Add exchange fees + IBKR regulatory fee (0.02) + return CashAmount(fee_per_contract[self.future_commission_tier] + exchange_fee_per_contract + 0.02, Currencies.USD) + + def hong_kong_future_fees(self, security: Security) -> CashAmount: + '''See https://www.hkex.com.hk/Services/Rules-and-Forms-and-Fees/Fees/Listed-Derivatives/Trading/Transaction?sc_lang=en''' + if security.symbol.id.symbol.lower() == "hsi": + # IB fee + exchange fee + return CashAmount(30 + 10, Currencies.HKD) + + currency = security.quote_currency.symbol + if currency == Currencies.CNH: + fee_per_contract = 13 + elif currency == Currencies.HKD: + fee_per_contract = 20 + elif currency == Currencies.USD: + fee_per_contract = 2.4 + else: + raise ArgumentException(Messages.InteractiveBrokersFeeModel.hong_kong_future_fees_unexpected_quote_currency(security)) + + # let's add a 50% extra charge for exchange fees + return CashAmount(fee_per_contract * 1.5, currency) + + def eurex_future_fees(self, security: Security) -> CashAmount: + if security.symbol.security_type == SecurityType.FUTURE: + fees = self.eurex_futures_fees + exchange_fees = self.eurex_futures_exchange_fees + symbol = security.symbol.id.symbol + else: + raise ArgumentException(Messages.InteractiveBrokersFeeModel.eurex_future_fees_unsupported_security_type(security)) + + fee_per_contract = fees.get(symbol) + if not fee_per_contract: + fee_per_contract = 1 + + exchange_fee_per_contract = exchange_fees.get(symbol) + if not exchange_fee_per_contract: + exchange_fee_per_contract = 0 + + # Add exchange fees + IBKR regulatory fee (0.02) + return CashAmount(fee_per_contract + exchange_fee_per_contract + 0.02, Currencies.EUR) + + def get_equity_exchange_fee(self, order: Order, exchange: Exchange, trade_value: float, commission: float) -> float: + '''Get the exchange fees of an Equity trade. + Remarks: + Refer to https://www.interactivebrokers.com/en/pricing/commissions-stocks.php, section United States - Third Party Fees. + Return: + Exchange fee of the Equity transaction''' + penny_stock = order.price < 1 + if order.type == OrderType.MARKET_ON_OPEN: + if exchange == Exchange.AMEX: + return order.absolute_quantity * 0.0005 + elif exchange == Exchange.BATS: + return order.absolute_quantity * 0.00075 + elif penny_stock: + if exchange == Exchange.ARCA: + return trade_value * 0.001 + elif exchange == Exchange.NYSE: + return trade_value * 0.003 + commission * 0.000175 + return trade_value * 0.003 + else: + if exchange == Exchange.NYSE: + return order.absolute_quantity * 0.001 + commission * 0.000175 + return order.absolute_quantity * 0.0015 + elif order.type == OrderType.MARKET_ON_CLOSE: + if exchange == Exchange.AMEX: + return order.absolute_quantity * 0.0005 + elif exchange == Exchange.BATS: + return order.absolute_quantity * 0.001 + if penny_stock: + if exchange == Exchange.ARCA: + return trade_value * 0.001 + elif exchange == Exchange.NYSE: + return trade_value * 0.003 + commission * 0.000175 + return trade_value * 0.003 + else: + if exchange == Exchange.ARCA: + return order.absolute_quantity * 0.0012 + elif exchange == Exchange.NYSE: + return order.absolute_quantity * 0.001 + commission * 0.000175 + return order.absolute_quantity * 0.0015 + elif penny_stock: + if exchange == Exchange.AMEX: + return trade_value * 0.0025 + elif exchange == Exchange.NYSE: + return trade_value * 0.003 + commission * 0.000175 + return trade_value * 0.003 + elif exchange == Exchange.NYSE: + return order.absolute_quantity * 0.003 + commission * 0.000175 + return order.absolute_quantity * 0.003 + + def get_potential_order_price(self, order: Order, security: Security) -> float: + '''Approximates the order's price based on the order type''' + if order.type == OrderType.TRAILING_STOP or order.type == OrderType.STOP_MARKET: + return order.stop_price + elif order.type == OrderType.COMBO_MARKET or order.type == OrderType.MARKET_ON_OPEN \ + or order.type == OrderType.MARKET_ON_CLOSE or order.type == OrderType.MARKET: + if order.direction == OrderDirection.BUY: + return security.bid_price + return security.ask_price + elif order.type == OrderType.COMBO_LEG_LIMIT or order.type == OrderType.STOP_LIMIT \ + or order.type == OrderType.LIMIT_IF_TOUCHED or order.type == OrderType.LIMIT: + return order.limit_price + elif order.type == OrderType.COMBO_LIMIT: + return order.group_order_manager.limit_price + return 0 + + @property + def usa_futures_fees(self) -> Dict[str, float]: + '''Reference at https://www.interactivebrokers.com/en/pricing/commissions-futures.php?re=amer''' + return { + # Micro E-mini Futures + "MYM": [0.25, 0.2, 0.15, 0.1], "M2K": [0.25, 0.2, 0.15, 0.1], "MES": [0.25, 0.2, 0.15, 0.1], + "MNQ": [0.25, 0.2, 0.15, 0.1], "2YY": [0.25, 0.2, 0.15, 0.1], "5YY": [0.25, 0.2, 0.15, 0.1], + "10Y": [0.25, 0.2, 0.15, 0.1], "30Y": [0.25, 0.2, 0.15, 0.1], "MCL": [0.25, 0.2, 0.15, 0.1], + "MGC": [0.25, 0.2, 0.15, 0.1], "SIL": [0.25, 0.2, 0.15, 0.1], + # Cryptocurrency Futures + "BTC": [5, 5, 5, 5], "MBT": [2.25, 2.25, 2.25, 2.25], "ETH": [3, 3, 3, 3], "MET": [0.2, 0.2, 0.2, 0.2], + # E-mini FX (currencies) Futures + "E7": [0.5, 0.4, 0.3, 0.15], "J7": [0.5, 0.4, 0.3, 0.15], + # Micro E-mini FX (currencies) Futures + "M6E": [0.15, 0.12, 0.08, 0.05], "M6A": [0.15, 0.12, 0.08, 0.05], "M6B": [0.15, 0.12, 0.08, 0.05], + "MCD": [0.15, 0.12, 0.08, 0.05], "MJY": [0.15, 0.12, 0.08, 0.05], "MSF": [0.15, 0.12, 0.08, 0.05], + "M6J": [0.15, 0.12, 0.08, 0.05], "MIR": [0.15, 0.12, 0.08, 0.05], "M6C": [0.15, 0.12, 0.08, 0.05], + "M6S": [0.15, 0.12, 0.08, 0.05], "MNH": [0.15, 0.12, 0.08, 0.05] + } + + @property + def usa_future_options_fees(self) -> Dict[str, float]: + return { + # Micro E-mini Future Options + "MYM": [0.25, 0.2, 0.15, 0.1], "M2K": [0.25, 0.2, 0.15, 0.1], "MES": [0.25, 0.2, 0.15, 0.1], + "MNQ": [0.25, 0.2, 0.15, 0.1], "2YY": [0.25, 0.2, 0.15, 0.1], "5YY": [0.25, 0.2, 0.15, 0.1], + "10Y": [0.25, 0.2, 0.15, 0.1], "30Y": [0.25, 0.2, 0.15, 0.1], "MCL": [0.25, 0.2, 0.15, 0.1], + "MGC": [0.25, 0.2, 0.15, 0.1], "SIL": [0.25, 0.2, 0.15, 0.1], + # Cryptocurrency Future Options + "BTC": [5, 5, 5, 5], "MBT": [1.25, 1.25, 1.25, 1.25], "ETH": [3, 3, 3, 3], "MET": [0.1, 0.1, 0.1, 0.1] + } + + @property + def usa_futures_exchange_fees(self) -> Dict[str, float]: + return { + # E-mini Futures + "ES": 1.28, "NQ": 1.28, "YM": 1.28, "RTY": 1.28, "EMD": 1.28, + # Micro E-mini Futures + "MYM": 0.30, "M2K": 0.30, "MES": 0.30, "MNQ": 0.30, "2YY": 0.30, "5YY": 0.30, "10Y": 0.30, + "30Y": 0.30, "MCL": 0.30, "MGC": 0.30, "SIL": 0.30, + # Cryptocurrency Futures + "BTC": 6, "MBT": 2.5, "ETH": 4, "MET": 0.20, + # E-mini FX (currencies) Futures + "E7": 0.85, "J7": 0.85, + # Micro E-mini FX (currencies) Futures + "M6E": 0.24, "M6A": 0.24, "M6B": 0.24, "MCD": 0.24, "MJY": 0.24, "MSF": 0.24, "M6J": 0.24, + "MIR": 0.24, "M6C": 0.24, "M6S": 0.24, "MNH": 0.24 + } + + @property + def usa_future_options_exchange_fees(self) -> Dict[str, float]: + return { + # E-mini Future Options + "ES": 0.55, "NQ": 0.55, "YM": 0.55, "RTY": 0.55, "EMD": 0.55, + # Micro E-mini Future Options + "MYM": 0.20, "M2K": 0.20, "MES": 0.20, "MNQ": 0.20, "2YY": 0.20, "5YY": 0.20, "10Y": 0.20, + "30Y": 0.20, "MCL": 0.20, "MGC": 0.20, "SIL": 0.20, + # Cryptocurrency Future Options + "BTC": 5, "MBT": 2.5, "ETH": 4, "MET": 0.20 + } + + @property + def eurex_futures_fees(self) -> Dict[str, float]: + '''Reference at https://www.interactivebrokers.com/en/pricing/commissions-futures-europe.php?re=europe''' + return { + # Futures + "FESX": 1 + } + + @property + def eurex_futures_exchange_fees(self) -> Dict[str, float]: + return { + # Futures + "FESX": 0 + } + + +class EquityFee: + '''Helper class to handle IB Equity fees''' + + def __init__(self, currency: str, fee_per_share: float, minimum_fee: float, maximum_fee_rate: float) -> None: + self.currency = currency + self.fee_per_share = fee_per_share + self.minimum_fee = minimum_fee + self.maximum_fee_rate = maximum_fee_rate diff --git a/Common/QuantConnect.csproj b/Common/QuantConnect.csproj index 088dfd992381..c438ad020749 100644 --- a/Common/QuantConnect.csproj +++ b/Common/QuantConnect.csproj @@ -63,8 +63,12 @@ PreserveNewest true - - PreserveNewest - + + PreserveNewest + true + + + PreserveNewest + diff --git a/Tests/Common/Orders/Fees/InteractiveBrokersFeeModelTests.cs b/Tests/Common/Orders/Fees/InteractiveBrokersFeeModelTests.cs index 508bde6d796e..063e6af3b17b 100644 --- a/Tests/Common/Orders/Fees/InteractiveBrokersFeeModelTests.cs +++ b/Tests/Common/Orders/Fees/InteractiveBrokersFeeModelTests.cs @@ -24,6 +24,7 @@ using QuantConnect.Securities; using QuantConnect.Securities.Cfd; using QuantConnect.Securities.Crypto; +using QuantConnect.Securities.CryptoFuture; using QuantConnect.Securities.Forex; using QuantConnect.Securities.Future; using QuantConnect.Securities.FutureOption; @@ -82,7 +83,7 @@ public void USAFutureFee(Symbol symbol, decimal expectedFee) ErrorCurrencyConverter.Instance, RegisteredSecurityDataTypesProvider.Null, new SecurityCache()); - var security = (Security) (symbol.SecurityType == SecurityType.Future + var security = (Security)(symbol.SecurityType == SecurityType.Future ? future : new FutureOption(symbol, SecurityExchangeHours.AlwaysOpen(tz), @@ -278,6 +279,35 @@ public void ForexFee_NonUSD() Assert.AreEqual(2m, fee.Value.Amount); } + [TestCase(1, 1)] + [TestCase(2, 1.75)] + [TestCase(100, 18)] + public void CryptoFee(decimal orderSize, decimal expectedFee) + { + var tz = TimeZones.Utc; + + var security = new Crypto( + SecurityExchangeHours.AlwaysOpen(tz), + new Cash("USD", 0, 1), + new Cash("BTC", 0, 0), + new SubscriptionDataConfig(typeof(TradeBar), Symbols.BTCUSD, Resolution.Minute, tz, tz, true, false, false), + new SymbolProperties("BTCUSD", "USD", 1, 0.0001m, 0.0001m, string.Empty), + ErrorCurrencyConverter.Instance, + RegisteredSecurityDataTypesProvider.Null + ); + security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, 100, 100)); + + var fee = _feeModel.GetOrderFee( + new OrderFeeParameters( + security, + new MarketOrder(security.Symbol, orderSize, DateTime.UtcNow) + ) + ); + + Assert.AreEqual(Currencies.USD, fee.Value.Currency); + Assert.AreEqual(expectedFee, fee.Value.Amount); + } + [Test] public void GetOrderFeeThrowsForUnsupportedSecurityType() { @@ -285,22 +315,23 @@ public void GetOrderFeeThrowsForUnsupportedSecurityType() () => { var tz = TimeZones.NewYork; - var security = new Crypto( - Symbols.BTCUSD, + var security = new CryptoFuture( + Symbols.BTCUSD_Future, SecurityExchangeHours.AlwaysOpen(tz), - new Cash("USD", 0, 0), + new Cash("USD", 0, 1), new Cash("BTC", 0, 0), SymbolProperties.GetDefault("USD"), ErrorCurrencyConverter.Instance, RegisteredSecurityDataTypesProvider.Null, new SecurityCache() ); - security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, 12000, 12000)); + var time = new DateTime(2018, 2, 1); + security.SetMarketPrice(new Tick(time, security.Symbol, 12000, 12000)); _feeModel.GetOrderFee( new OrderFeeParameters( security, - new MarketOrder(security.Symbol, 1, DateTime.UtcNow) + new MarketOrder(security.Symbol, 1, time) ) ); }); diff --git a/Tests/Common/Orders/Fees/InteractiveBrokersTieredFeeModelTests.cs b/Tests/Common/Orders/Fees/InteractiveBrokersTieredFeeModelTests.cs new file mode 100644 index 000000000000..9dae800f5bb2 --- /dev/null +++ b/Tests/Common/Orders/Fees/InteractiveBrokersTieredFeeModelTests.cs @@ -0,0 +1,871 @@ +/* + * 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.Linq; +using Moq; +using NUnit.Framework; +using QuantConnect.Data; +using QuantConnect.Data.Market; +using QuantConnect.Orders; +using QuantConnect.Orders.Fees; +using QuantConnect.Securities; +using QuantConnect.Securities.Cfd; +using QuantConnect.Securities.Crypto; +using QuantConnect.Securities.CryptoFuture; +using QuantConnect.Securities.Equity; +using QuantConnect.Securities.Forex; +using QuantConnect.Securities.Future; +using QuantConnect.Securities.FutureOption; +using QuantConnect.Securities.Option; + +namespace QuantConnect.Tests.Common.Orders.Fees +{ + [TestFixture] + public class InteractiveBrokersTieredFeeModelTests + { + private static IFeeModel GetFeeModel(int tier = 0) + { + switch(tier) + { + case 1: + return new InteractiveBrokersTieredFeeModel(500000m, 1500m, 1.5e9m, 25000m, 500000m); + case 2: + return new InteractiveBrokersTieredFeeModel(5000000m, 15000m, 2.5e9m, 75000m, 1500000m); + case 3: + return new InteractiveBrokersTieredFeeModel(50000000m, 150000m, 5.5e9m, 150000m, 1500000m); + case 4: + return new InteractiveBrokersTieredFeeModel(500000000m, 150000m, 5.5e9m, 150000m, 1500000m); + default: + return new InteractiveBrokersTieredFeeModel(); + } + } + + [Test] + public void USAEquityMinimumFeeInUSD() + { + var security = new Equity( + Symbols.SPY, + SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork), + new Cash(Currencies.USD, 0, 1m), + SymbolProperties.GetDefault(Currencies.USD), + ErrorCurrencyConverter.Instance, + RegisteredSecurityDataTypesProvider.Null, + new SecurityCache() + ); + security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, 1, 1)); + + var model = GetFeeModel(); + var fee = model.GetOrderFee( + new OrderFeeParameters( + security, + new MarketOrder(security.Symbol, 1, DateTime.UtcNow) + ) + ); + + Assert.AreEqual(Currencies.USD, fee.Value.Currency); + Assert.AreEqual(0.3534718m, fee.Value.Amount); + } + + [TestCase(0, 0.1, OrderType.Market, 1.55134)] + [TestCase(0, 0.1, OrderType.MarketOnOpen, 1.55134)] + [TestCase(0, 0.1, OrderType.MarketOnClose, 1.55134)] + [TestCase(0, 100, OrderType.Market, 306.52996)] + [TestCase(0, 100, OrderType.MarketOnOpen, 306.52996)] + [TestCase(0, 100, OrderType.MarketOnClose, 306.52996)] + [TestCase(1, 0.1, OrderType.Market, 1.55134)] + [TestCase(1, 0.1, OrderType.MarketOnOpen, 1.55134)] + [TestCase(1, 0.1, OrderType.MarketOnClose, 1.55134)] + [TestCase(1, 100, OrderType.Market, 305.02912)] + [TestCase(1, 100, OrderType.MarketOnOpen, 305.02912)] + [TestCase(1, 100, OrderType.MarketOnClose, 305.02912)] + [TestCase(2, 0.1, OrderType.Market, 1.55134)] + [TestCase(2, 0.1, OrderType.MarketOnOpen, 1.55134)] + [TestCase(2, 0.1, OrderType.MarketOnClose, 1.55134)] + [TestCase(2, 100, OrderType.Market, 304.52884)] + [TestCase(2, 100, OrderType.MarketOnOpen, 304.52884)] + [TestCase(2, 100, OrderType.MarketOnClose, 304.52884)] + [TestCase(3, 0.1, OrderType.Market, 1.55134)] + [TestCase(3, 0.1, OrderType.MarketOnOpen, 1.55134)] + [TestCase(3, 0.1, OrderType.MarketOnClose, 1.55134)] + [TestCase(3, 100, OrderType.Market, 304.02856)] + [TestCase(3, 100, OrderType.MarketOnOpen, 304.02856)] + [TestCase(3, 100, OrderType.MarketOnClose, 304.02856)] + [TestCase(4, 0.1, OrderType.Market, 1.05106)] + [TestCase(4, 0.1, OrderType.MarketOnOpen, 1.05106)] + [TestCase(4, 0.1, OrderType.MarketOnClose, 1.05106)] + [TestCase(4, 100, OrderType.Market, 303.52828)] + [TestCase(4, 100, OrderType.MarketOnOpen, 303.52828)] + [TestCase(4, 100, OrderType.MarketOnClose, 303.52828)] + public void USAEquityFeeInUsdByTiers(int tier, decimal price, OrderType orderType, decimal expectedFee) + { + var security = new Equity( + Symbols.SPY, + SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork), + new Cash(Currencies.USD, 0, 1m), + SymbolProperties.GetDefault(Currencies.USD), + ErrorCurrencyConverter.Instance, + RegisteredSecurityDataTypesProvider.Null, + new SecurityCache() + ); + security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, price, price)); + + Order order; + switch (orderType) + { + case OrderType.MarketOnOpen: + order = new MarketOnOpenOrder(security.Symbol, 1000, DateTime.UtcNow); + break; + + case OrderType.MarketOnClose: + order = new MarketOnCloseOrder(security.Symbol, 1000, DateTime.UtcNow); + break; + + default: + order = new MarketOrder(security.Symbol, 1000, DateTime.UtcNow); + break; + } + var model = GetFeeModel(tier); + var fee = model.GetOrderFee( + new OrderFeeParameters( + security, + order + ) + ); + + Assert.AreEqual(Currencies.USD, fee.Value.Currency); + Assert.AreEqual(expectedFee, fee.Value.Amount); + } + + [TestCaseSource(nameof(USAFuturesFeeTestCases))] + public void USAFutureFeeByTier(int tier, Symbol symbol, decimal expectedFee) + { + var tz = TimeZones.NewYork; + var future = new Future(symbol, + SecurityExchangeHours.AlwaysOpen(tz), + new Cash("USD", 0, 0), + SymbolProperties.GetDefault("USD"), + ErrorCurrencyConverter.Instance, + RegisteredSecurityDataTypesProvider.Null, + new SecurityCache()); + var security = (Security) (symbol.SecurityType == SecurityType.Future + ? future + : new FutureOption(symbol, + SecurityExchangeHours.AlwaysOpen(tz), + new Cash("USD", 0, 0), + new OptionSymbolProperties(SymbolProperties.GetDefault("USD")), + ErrorCurrencyConverter.Instance, + RegisteredSecurityDataTypesProvider.Null, + new SecurityCache(), + future)); + var time = new DateTime(2022, 8, 18); + security.SetMarketPrice(new Tick(time, security.Symbol, 100, 100)); + var model = GetFeeModel(tier); + var fee = model.GetOrderFee(new OrderFeeParameters(security, new MarketOrder(security.Symbol, 1000, time))); + + Assert.AreEqual(Currencies.USD, fee.Value.Currency); + Assert.AreEqual(1000 * expectedFee, fee.Value.Amount); + } + + [TestCase("USD", 70000, 0.00002 * 70000)] + [TestCase("USD", 100000, 0.00002 * 100000)] + [TestCase("USD", 10000, 1)] // The calculated fee will be under 1, but the minimum fee is 1 USD + [TestCase("JPY", 3000000, 0.00002 * 3000000)] + [TestCase("JPY", 1000000, 40)]// The calculated fee will be under 40, but the minimum fee is 40 JPY + [TestCase("HKD", 600000, 0.00002 * 600000)] + [TestCase("HKD", 200000, 10)]// The calculated fee will be under 10, but the minimum fee is 10 HKD + public void CalculatesCFDFee(string quoteCurrency, decimal price, decimal expectedFee) + { + var security = new Cfd(Symbols.DE10YBEUR, + SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork), + new Cash(quoteCurrency, 0, 0), + SymbolProperties.GetDefault(quoteCurrency), + ErrorCurrencyConverter.Instance, + RegisteredSecurityDataTypesProvider.Null, + new SecurityCache()); + security.QuoteCurrency.ConversionRate = 1; + + + security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, price, price)); + + var order = new MarketOrder(security.Symbol, 1, DateTime.UtcNow); + var model = GetFeeModel(); + var fee = model.GetOrderFee(new OrderFeeParameters(security, order)); + + Assert.AreEqual(quoteCurrency, fee.Value.Currency); + Assert.AreEqual(expectedFee, fee.Value.Amount); + } + + // Tier 1 + [TestCase(0, OrderType.ComboMarket, 0.01, 293.95)] + [TestCase(0, OrderType.ComboLimit, 0.01, 293.95)] + [TestCase(0, OrderType.ComboLegLimit, 0.01, 293.95)] + [TestCase(0, OrderType.Limit, 0.01, 293.95)] + [TestCase(0, OrderType.StopLimit, 0.01, 293.95)] + [TestCase(0, OrderType.LimitIfTouched, 0.01, 293.95)] + [TestCase(0, OrderType.StopMarket, 0.01, 293.95)] + [TestCase(0, OrderType.TrailingStop, 0.01, 293.95)] + [TestCase(0, OrderType.Market, 0.01, 293.95)] + [TestCase(0, OrderType.MarketOnClose, 0.01, 293.95)] + [TestCase(0, OrderType.MarketOnOpen, 0.01, 293.95)] + [TestCase(0, OrderType.ComboMarket, 0.2, 693.95)] + [TestCase(0, OrderType.ComboLimit, 0.2, 693.95)] + [TestCase(0, OrderType.ComboLegLimit, 0.2, 693.95)] + [TestCase(0, OrderType.Limit, 0.2, 693.95)] + [TestCase(0, OrderType.StopLimit, 0.2, 693.95)] + [TestCase(0, OrderType.LimitIfTouched, 0.2, 693.95)] + [TestCase(0, OrderType.StopMarket, 0.2, 693.95)] + [TestCase(0, OrderType.TrailingStop, 0.2, 693.95)] + [TestCase(0, OrderType.Market, 0.2, 693.95)] + [TestCase(0, OrderType.MarketOnClose, 0.2, 693.95)] + [TestCase(0, OrderType.MarketOnOpen, 0.2, 693.95)] + [TestCase(0, OrderType.ComboMarket, 0.07, 543.95)] + [TestCase(0, OrderType.ComboLimit, 0.07, 543.95)] + [TestCase(0, OrderType.ComboLegLimit, 0.07, 543.95)] + [TestCase(0, OrderType.Limit, 0.07, 543.95)] + [TestCase(0, OrderType.StopLimit, 0.07, 543.95)] + [TestCase(0, OrderType.LimitIfTouched, 0.07, 543.95)] + [TestCase(0, OrderType.StopMarket, 0.07, 543.95)] + [TestCase(0, OrderType.TrailingStop, 0.07, 543.95)] + [TestCase(0, OrderType.Market, 0.07, 543.95)] + [TestCase(0, OrderType.MarketOnClose, 0.07, 543.95)] + [TestCase(0, OrderType.MarketOnOpen, 0.07, 543.95)] + [TestCase(0, OrderType.ComboMarket, -0.01, 293.95)] + [TestCase(0, OrderType.ComboLimit, -0.01, 293.95)] + [TestCase(0, OrderType.ComboLegLimit, -0.01, 293.95)] + [TestCase(0, OrderType.Limit, -0.01, 293.95)] + [TestCase(0, OrderType.StopLimit, -0.01, 293.95)] + [TestCase(0, OrderType.LimitIfTouched, -0.01, 293.95)] + [TestCase(0, OrderType.StopMarket, -0.01, 293.95)] + [TestCase(0, OrderType.TrailingStop, -0.01, 293.95)] + [TestCase(0, OrderType.Market, -0.01, 293.95)] + [TestCase(0, OrderType.MarketOnClose, -0.01, 293.95)] + [TestCase(0, OrderType.MarketOnOpen, -0.01, 293.95)] + // Tier 2 + [TestCase(1, OrderType.Market, 0.01, 293.95)] + [TestCase(1, OrderType.Market, 0.2, 543.95)] + [TestCase(1, OrderType.Market, 0.07, 543.95)] + [TestCase(1, OrderType.Market, -0.01, 293.95)] + // Tier 3 + [TestCase(2, OrderType.Market, 0.01, 293.95)] + [TestCase(2, OrderType.Market, 0.2, 293.95)] + [TestCase(2, OrderType.Market, 0.07, 293.95)] + [TestCase(2, OrderType.Market, -0.01, 293.95)] + // Tier 4 + [TestCase(3, OrderType.Market, 0.01, 193.95)] + [TestCase(3, OrderType.Market, 0.2, 193.95)] + [TestCase(3, OrderType.Market, 0.07, 193.95)] + [TestCase(3, OrderType.Market, -0.01, 193.95)] + public void USAOptionFeeByTier(int tier, OrderType orderType, double price, double expectedFees) + { + var optionPrice = (decimal)price; + var tz = TimeZones.NewYork; + var security = new Option(Symbols.SPY_C_192_Feb19_2016, + SecurityExchangeHours.AlwaysOpen(tz), + new Cash("USD", 0, 0), + new OptionSymbolProperties(SymbolProperties.GetDefault("USD")), + ErrorCurrencyConverter.Instance, + RegisteredSecurityDataTypesProvider.Null, + new SecurityCache(), + null + ); + security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, optionPrice, 0)); + var order = (new Mock()).Object; + var groupOrderManager = new GroupOrderManager(0, 2, 10); + + switch (orderType) + { + case OrderType.ComboMarket: + order = new ComboMarketOrder(security.Symbol, 1000, DateTime.UtcNow, groupOrderManager); + break; + case OrderType.ComboLimit: + order = new ComboLimitOrder(security.Symbol, 1000, optionPrice, DateTime.UtcNow, groupOrderManager); + break; + case OrderType.ComboLegLimit: + order = new ComboLegLimitOrder(security.Symbol, 1000, optionPrice, DateTime.UtcNow, groupOrderManager); + break; + case OrderType.Limit: + order = new LimitOrder(security.Symbol, 1000, optionPrice, DateTime.UtcNow); + break; + case OrderType.StopLimit: + order = new StopLimitOrder(security.Symbol, 1000, optionPrice, optionPrice, DateTime.UtcNow); + break; + case OrderType.LimitIfTouched: + order = new LimitIfTouchedOrder(security.Symbol, 1000, optionPrice, optionPrice, DateTime.UtcNow); + break; + case OrderType.StopMarket: + order = new StopMarketOrder(security.Symbol, 1000, optionPrice, DateTime.UtcNow); + break; + case OrderType.TrailingStop: + order = new TrailingStopOrder(security.Symbol, 1000, optionPrice, optionPrice, false, DateTime.UtcNow); + break; + case OrderType.Market: + order = new MarketOrder(security.Symbol, 1000, DateTime.UtcNow); + break; + case OrderType.MarketOnClose: + order = new MarketOnCloseOrder(security.Symbol, 1000, DateTime.UtcNow); + break; + case OrderType.MarketOnOpen: + order = new MarketOnOpenOrder(security.Symbol, 1000, DateTime.UtcNow); + break; + } + + var model = GetFeeModel(tier); + var fee = model.GetOrderFee( + new OrderFeeParameters( + security, + order + ) + ); + + Assert.AreEqual(Currencies.USD, fee.Value.Currency); + Assert.AreEqual((decimal)expectedFees, fee.Value.Amount); + } + + [Test] + public void USAOptionMinimumFee() + { + var tz = TimeZones.NewYork; + var security = new Option(Symbols.SPY_C_192_Feb19_2016, + SecurityExchangeHours.AlwaysOpen(tz), + new Cash("USD", 0, 0), + new OptionSymbolProperties(SymbolProperties.GetDefault("USD")), + ErrorCurrencyConverter.Instance, + RegisteredSecurityDataTypesProvider.Null, + new SecurityCache(), + null + ); + security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, 100, 100)); + + var model = GetFeeModel(); + var fee = model.GetOrderFee( + new OrderFeeParameters( + security, + new MarketOrder(security.Symbol, 1, DateTime.UtcNow) + ) + ); + + Assert.AreEqual(Currencies.USD, fee.Value.Currency); + Assert.AreEqual(1.04395m, fee.Value.Amount); + } + + [TestCase(0, 100, 2)] + [TestCase(1, 100, 1.5)] + [TestCase(2, 100, 1.25)] + [TestCase(3, 100, 1)] + [TestCase(0, 10000000, 40000)] + [TestCase(1, 10000000, 30000)] + [TestCase(2, 10000000, 20000)] + [TestCase(3, 10000000, 16000)] + public void ForexFeeByTier(int tier, decimal orderSize, decimal expectedFee) + { + var tz = TimeZones.NewYork; + var security = new Forex( + SecurityExchangeHours.AlwaysOpen(tz), + new Cash("GBP", 0, 2), + new Cash("EUR", 0, 0), + new SubscriptionDataConfig(typeof(TradeBar), Symbols.EURGBP, Resolution.Minute, tz, tz, true, false, false), + new SymbolProperties("EURGBP", "GBP", 1, 0.01m, 0.00000001m, string.Empty), + ErrorCurrencyConverter.Instance, + RegisteredSecurityDataTypesProvider.Null + ); + security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, 100, 100)); + + var model = GetFeeModel(tier); + var fee = model.GetOrderFee( + new OrderFeeParameters( + security, + new MarketOrder(security.Symbol, orderSize, DateTime.UtcNow) + ) + ); + + Assert.AreEqual(Currencies.USD, fee.Value.Currency); + Assert.AreEqual(expectedFee, fee.Value.Amount); + } + + [TestCase(0, 1, 1)] + [TestCase(1, 1, 1)] + [TestCase(2, 1, 1)] + [TestCase(0, 2, 1.75)] + [TestCase(1, 2, 1.75)] + [TestCase(2, 2, 1.75)] + [TestCase(0, 100, 18)] + [TestCase(1, 100, 15)] + [TestCase(2, 100, 12)] + public void CryptoFeeByTier(int tier, decimal orderSize, decimal expectedFee) + { + var tz = TimeZones.NewYork; + var security = new Crypto( + Symbols.BTCUSD, + SecurityExchangeHours.AlwaysOpen(tz), + new Cash("USD", 0, 1), + new Cash("BTC", 0, 0), + SymbolProperties.GetDefault("USD"), + ErrorCurrencyConverter.Instance, + RegisteredSecurityDataTypesProvider.Null, + new SecurityCache() + ); + security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, 100, 100)); + + var model = GetFeeModel(tier); + var fee = model.GetOrderFee( + new OrderFeeParameters( + security, + new MarketOrder(security.Symbol, orderSize, DateTime.UtcNow) + ) + ); + + Assert.AreEqual(Currencies.USD, fee.Value.Currency); + Assert.AreEqual(expectedFee, fee.Value.Amount); + } + + [Test] + public void GetOrderFeeThrowsForUnsupportedSecurityType() + { + Assert.Throws( + () => + { + var tz = TimeZones.NewYork; + var security = new CryptoFuture( + Symbols.BTCUSD_Future, + SecurityExchangeHours.AlwaysOpen(tz), + new Cash("USD", 0, 0), + new Cash("BTC", 0, 0), + SymbolProperties.GetDefault("USD"), + ErrorCurrencyConverter.Instance, + RegisteredSecurityDataTypesProvider.Null, + new SecurityCache() + ); + var date = new DateTime(2018, 2, 1); + security.SetMarketPrice(new Tick(date, security.Symbol, 12000, 12000)); + + GetFeeModel().GetOrderFee( + new OrderFeeParameters( + security, + new MarketOrder(security.Symbol, 1, date) + ) + ); + }); + } + + [Test] + public void MonthlyRollingTierChangeTest() + { + var security = new Equity( + Symbols.SPY, + SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork), + new Cash(Currencies.USD, 0, 1m), + SymbolProperties.GetDefault(Currencies.USD), + ErrorCurrencyConverter.Instance, + RegisteredSecurityDataTypesProvider.Null, + new SecurityCache() + ); + security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, 100, 100)); + + // Tier 1 + var feeModel = GetFeeModel(); + var fee = feeModel.GetOrderFee( + new OrderFeeParameters( + security, + new MarketOrder(security.Symbol, 300000, DateTime.UtcNow) + ) + ); + + Assert.AreEqual(Currencies.USD, fee.Value.Currency); + Assert.AreEqual(91958.988m, fee.Value.Amount); + + // Tier 2 + fee = feeModel.GetOrderFee( + new OrderFeeParameters( + security, + new MarketOrder(security.Symbol, 3000000 - 300000, DateTime.UtcNow) + ) + ); + + Assert.AreEqual(Currencies.USD, fee.Value.Currency); + Assert.AreEqual(827630.892m, fee.Value.Amount); + + // Tier 3 + fee = feeModel.GetOrderFee( + new OrderFeeParameters( + security, + new MarketOrder(security.Symbol, 20000000 - 3000000, DateTime.UtcNow) + ) + ); + + Assert.AreEqual(Currencies.USD, fee.Value.Currency); + Assert.AreEqual(5185484.3m, fee.Value.Amount); + + // Tier 4 + fee = feeModel.GetOrderFee( + new OrderFeeParameters( + security, + new MarketOrder(security.Symbol, 100000000 - 20000000, DateTime.UtcNow) + ) + ); + + Assert.AreEqual(Currencies.USD, fee.Value.Currency); + Assert.AreEqual(24362248.3, fee.Value.Amount); + + // Tier 5 + fee = feeModel.GetOrderFee( + new OrderFeeParameters( + security, + new MarketOrder(security.Symbol, 300000, DateTime.UtcNow) + ) + ); + + Assert.AreEqual(Currencies.USD, fee.Value.Currency); + Assert.AreEqual(91208.568, fee.Value.Amount); + + // Reset to tier 1 on next month + fee = feeModel.GetOrderFee( + new OrderFeeParameters( + security, + new MarketOrder(security.Symbol, 300000, DateTime.UtcNow.AddMonths(1)) // Roll 1 month + ) + ); + + Assert.AreEqual(Currencies.USD, fee.Value.Currency); + Assert.AreEqual(91958.988m, fee.Value.Amount); + } + + private static TestCaseData[] USAFuturesFeeTestCases() + { + return new[] + { + // E-mini Futures + new { Tier = 0, Symbol = Futures.Indices.Dow30EMini, Type = SecurityType.Future, ExpectedFee = 2.15m }, + new { Tier = 0, Symbol = Futures.Indices.Russell2000EMini, Type = SecurityType.Future, ExpectedFee = 2.15m }, + new { Tier = 0, Symbol = Futures.Indices.SP500EMini, Type = SecurityType.Future, ExpectedFee = 2.15m }, + new { Tier = 0, Symbol = Futures.Indices.NASDAQ100EMini, Type = SecurityType.Future, ExpectedFee = 2.15m }, + new { Tier = 1, Symbol = Futures.Indices.Dow30EMini, Type = SecurityType.Future, ExpectedFee = 1.95m }, + new { Tier = 1, Symbol = Futures.Indices.Russell2000EMini, Type = SecurityType.Future, ExpectedFee = 1.95m }, + new { Tier = 1, Symbol = Futures.Indices.SP500EMini, Type = SecurityType.Future, ExpectedFee = 1.95m }, + new { Tier = 1, Symbol = Futures.Indices.NASDAQ100EMini, Type = SecurityType.Future, ExpectedFee = 1.95m }, + new { Tier = 2, Symbol = Futures.Indices.Dow30EMini, Type = SecurityType.Future, ExpectedFee = 1.75m }, + new { Tier = 2, Symbol = Futures.Indices.Russell2000EMini, Type = SecurityType.Future, ExpectedFee = 1.75m }, + new { Tier = 2, Symbol = Futures.Indices.SP500EMini, Type = SecurityType.Future, ExpectedFee = 1.75m }, + new { Tier = 2, Symbol = Futures.Indices.NASDAQ100EMini, Type = SecurityType.Future, ExpectedFee = 1.75m }, + new { Tier = 3, Symbol = Futures.Indices.Dow30EMini, Type = SecurityType.Future, ExpectedFee = 1.55m }, + new { Tier = 3, Symbol = Futures.Indices.Russell2000EMini, Type = SecurityType.Future, ExpectedFee = 1.55m }, + new { Tier = 3, Symbol = Futures.Indices.SP500EMini, Type = SecurityType.Future, ExpectedFee = 1.55m }, + new { Tier = 3, Symbol = Futures.Indices.NASDAQ100EMini, Type = SecurityType.Future, ExpectedFee = 1.55m }, + // E-mini Future options + new { Tier = 0, Symbol = Futures.Indices.Dow30EMini, Type = SecurityType.FutureOption, ExpectedFee = 1.42m }, + new { Tier = 0, Symbol = Futures.Indices.Russell2000EMini, Type = SecurityType.FutureOption, ExpectedFee = 1.42m }, + new { Tier = 0, Symbol = Futures.Indices.SP500EMini, Type = SecurityType.FutureOption, ExpectedFee = 1.42m }, + new { Tier = 0, Symbol = Futures.Indices.NASDAQ100EMini, Type = SecurityType.FutureOption, ExpectedFee = 1.42m }, + new { Tier = 1, Symbol = Futures.Indices.Dow30EMini, Type = SecurityType.FutureOption, ExpectedFee = 1.22m }, + new { Tier = 1, Symbol = Futures.Indices.Russell2000EMini, Type = SecurityType.FutureOption, ExpectedFee = 1.22m }, + new { Tier = 1, Symbol = Futures.Indices.SP500EMini, Type = SecurityType.FutureOption, ExpectedFee = 1.22m }, + new { Tier = 1, Symbol = Futures.Indices.NASDAQ100EMini, Type = SecurityType.FutureOption, ExpectedFee = 1.22m }, + new { Tier = 2, Symbol = Futures.Indices.Dow30EMini, Type = SecurityType.FutureOption, ExpectedFee = 1.02m }, + new { Tier = 2, Symbol = Futures.Indices.Russell2000EMini, Type = SecurityType.FutureOption, ExpectedFee = 1.02m }, + new { Tier = 2, Symbol = Futures.Indices.SP500EMini, Type = SecurityType.FutureOption, ExpectedFee = 1.02m }, + new { Tier = 2, Symbol = Futures.Indices.NASDAQ100EMini, Type = SecurityType.FutureOption, ExpectedFee = 1.02m }, + new { Tier = 3, Symbol = Futures.Indices.Dow30EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.82m }, + new { Tier = 3, Symbol = Futures.Indices.Russell2000EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.82m }, + new { Tier = 3, Symbol = Futures.Indices.SP500EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.82m }, + new { Tier = 3, Symbol = Futures.Indices.NASDAQ100EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.82m }, + // Micro E-mini Futures + new { Tier = 0, Symbol = Futures.Indices.MicroDow30EMini, Type = SecurityType.Future, ExpectedFee = 0.57m }, + new { Tier = 0, Symbol = Futures.Indices.MicroRussell2000EMini, Type = SecurityType.Future, ExpectedFee = 0.57m }, + new { Tier = 0, Symbol = Futures.Indices.MicroSP500EMini, Type = SecurityType.Future, ExpectedFee = 0.57m }, + new { Tier = 0, Symbol = Futures.Indices.MicroNASDAQ100EMini, Type = SecurityType.Future, ExpectedFee = 0.57m }, + new { Tier = 0, Symbol = Futures.Financials.MicroY2TreasuryBond, Type = SecurityType.Future, ExpectedFee = 0.57m }, + new { Tier = 0, Symbol = Futures.Financials.MicroY5TreasuryBond, Type = SecurityType.Future, ExpectedFee = 0.57m }, + new { Tier = 0, Symbol = Futures.Financials.MicroY10TreasuryNote, Type = SecurityType.Future, ExpectedFee = 0.57m }, + new { Tier = 0, Symbol = Futures.Financials.MicroY30TreasuryBond, Type = SecurityType.Future, ExpectedFee = 0.57m }, + new { Tier = 0, Symbol = Futures.Metals.MicroGold, Type = SecurityType.Future, ExpectedFee = 0.57m }, + new { Tier = 0, Symbol = Futures.Metals.MicroSilver, Type = SecurityType.Future, ExpectedFee = 0.57m }, + new { Tier = 0, Symbol = Futures.Energy.MicroCrudeOilWTI, Type = SecurityType.Future, ExpectedFee = 0.57m }, + new { Tier = 1, Symbol = Futures.Indices.MicroDow30EMini, Type = SecurityType.Future, ExpectedFee = 0.52m }, + new { Tier = 1, Symbol = Futures.Indices.MicroRussell2000EMini, Type = SecurityType.Future, ExpectedFee = 0.52m }, + new { Tier = 1, Symbol = Futures.Indices.MicroSP500EMini, Type = SecurityType.Future, ExpectedFee = 0.52m }, + new { Tier = 1, Symbol = Futures.Indices.MicroNASDAQ100EMini, Type = SecurityType.Future, ExpectedFee = 0.52m }, + new { Tier = 1, Symbol = Futures.Financials.MicroY2TreasuryBond, Type = SecurityType.Future, ExpectedFee = 0.52m }, + new { Tier = 1, Symbol = Futures.Financials.MicroY5TreasuryBond, Type = SecurityType.Future, ExpectedFee = 0.52m }, + new { Tier = 1, Symbol = Futures.Financials.MicroY10TreasuryNote, Type = SecurityType.Future, ExpectedFee = 0.52m }, + new { Tier = 1, Symbol = Futures.Financials.MicroY30TreasuryBond, Type = SecurityType.Future, ExpectedFee = 0.52m }, + new { Tier = 1, Symbol = Futures.Metals.MicroGold, Type = SecurityType.Future, ExpectedFee = 0.52m }, + new { Tier = 1, Symbol = Futures.Metals.MicroSilver, Type = SecurityType.Future, ExpectedFee = 0.52m }, + new { Tier = 1, Symbol = Futures.Energy.MicroCrudeOilWTI, Type = SecurityType.Future, ExpectedFee = 0.52m }, + new { Tier = 2, Symbol = Futures.Indices.MicroDow30EMini, Type = SecurityType.Future, ExpectedFee = 0.47m }, + new { Tier = 2, Symbol = Futures.Indices.MicroRussell2000EMini, Type = SecurityType.Future, ExpectedFee = 0.47m }, + new { Tier = 2, Symbol = Futures.Indices.MicroSP500EMini, Type = SecurityType.Future, ExpectedFee = 0.47m }, + new { Tier = 2, Symbol = Futures.Indices.MicroNASDAQ100EMini, Type = SecurityType.Future, ExpectedFee = 0.47m }, + new { Tier = 2, Symbol = Futures.Financials.MicroY2TreasuryBond, Type = SecurityType.Future, ExpectedFee = 0.47m }, + new { Tier = 2, Symbol = Futures.Financials.MicroY5TreasuryBond, Type = SecurityType.Future, ExpectedFee = 0.47m }, + new { Tier = 2, Symbol = Futures.Financials.MicroY10TreasuryNote, Type = SecurityType.Future, ExpectedFee = 0.47m }, + new { Tier = 2, Symbol = Futures.Financials.MicroY30TreasuryBond, Type = SecurityType.Future, ExpectedFee = 0.47m }, + new { Tier = 2, Symbol = Futures.Metals.MicroGold, Type = SecurityType.Future, ExpectedFee = 0.47m }, + new { Tier = 2, Symbol = Futures.Metals.MicroSilver, Type = SecurityType.Future, ExpectedFee = 0.47m }, + new { Tier = 2, Symbol = Futures.Energy.MicroCrudeOilWTI, Type = SecurityType.Future, ExpectedFee = 0.47m }, + new { Tier = 3, Symbol = Futures.Indices.MicroDow30EMini, Type = SecurityType.Future, ExpectedFee = 0.42m }, + new { Tier = 3, Symbol = Futures.Indices.MicroRussell2000EMini, Type = SecurityType.Future, ExpectedFee = 0.42m }, + new { Tier = 3, Symbol = Futures.Indices.MicroSP500EMini, Type = SecurityType.Future, ExpectedFee = 0.42m }, + new { Tier = 3, Symbol = Futures.Indices.MicroNASDAQ100EMini, Type = SecurityType.Future, ExpectedFee = 0.42m }, + new { Tier = 3, Symbol = Futures.Financials.MicroY2TreasuryBond, Type = SecurityType.Future, ExpectedFee = 0.42m }, + new { Tier = 3, Symbol = Futures.Financials.MicroY5TreasuryBond, Type = SecurityType.Future, ExpectedFee = 0.42m }, + new { Tier = 3, Symbol = Futures.Financials.MicroY10TreasuryNote, Type = SecurityType.Future, ExpectedFee = 0.42m }, + new { Tier = 3, Symbol = Futures.Financials.MicroY30TreasuryBond, Type = SecurityType.Future, ExpectedFee = 0.42m }, + new { Tier = 3, Symbol = Futures.Metals.MicroGold, Type = SecurityType.Future, ExpectedFee = 0.42m }, + new { Tier = 3, Symbol = Futures.Metals.MicroSilver, Type = SecurityType.Future, ExpectedFee = 0.42m }, + new { Tier = 3, Symbol = Futures.Energy.MicroCrudeOilWTI, Type = SecurityType.Future, ExpectedFee = 0.42m }, + // Micro E-mini Future options + new { Tier = 0, Symbol = Futures.Indices.MicroDow30EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.47m }, + new { Tier = 0, Symbol = Futures.Indices.MicroRussell2000EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.47m }, + new { Tier = 0, Symbol = Futures.Indices.MicroSP500EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.47m }, + new { Tier = 0, Symbol = Futures.Indices.MicroNASDAQ100EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.47m }, + new { Tier = 0, Symbol = Futures.Financials.MicroY2TreasuryBond, Type = SecurityType.FutureOption, ExpectedFee = 0.47m }, + new { Tier = 0, Symbol = Futures.Financials.MicroY5TreasuryBond, Type = SecurityType.FutureOption, ExpectedFee = 0.47m }, + new { Tier = 0, Symbol = Futures.Financials.MicroY10TreasuryNote, Type = SecurityType.FutureOption, ExpectedFee = 0.47m }, + new { Tier = 0, Symbol = Futures.Financials.MicroY30TreasuryBond, Type = SecurityType.FutureOption, ExpectedFee = 0.47m }, + new { Tier = 0, Symbol = Futures.Metals.MicroGold, Type = SecurityType.FutureOption, ExpectedFee = 0.47m }, + new { Tier = 0, Symbol = Futures.Metals.MicroSilver, Type = SecurityType.FutureOption, ExpectedFee = 0.47m }, + new { Tier = 0, Symbol = Futures.Energy.MicroCrudeOilWTI, Type = SecurityType.FutureOption, ExpectedFee = 0.47m }, + new { Tier = 1, Symbol = Futures.Indices.MicroDow30EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.42m }, + new { Tier = 1, Symbol = Futures.Indices.MicroRussell2000EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.42m }, + new { Tier = 1, Symbol = Futures.Indices.MicroSP500EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.42m }, + new { Tier = 1, Symbol = Futures.Indices.MicroNASDAQ100EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.42m }, + new { Tier = 1, Symbol = Futures.Financials.MicroY2TreasuryBond, Type = SecurityType.FutureOption, ExpectedFee = 0.42m }, + new { Tier = 1, Symbol = Futures.Financials.MicroY5TreasuryBond, Type = SecurityType.FutureOption, ExpectedFee = 0.42m }, + new { Tier = 1, Symbol = Futures.Financials.MicroY10TreasuryNote, Type = SecurityType.FutureOption, ExpectedFee = 0.42m }, + new { Tier = 1, Symbol = Futures.Financials.MicroY30TreasuryBond, Type = SecurityType.FutureOption, ExpectedFee = 0.42m }, + new { Tier = 1, Symbol = Futures.Metals.MicroGold, Type = SecurityType.FutureOption, ExpectedFee = 0.42m }, + new { Tier = 1, Symbol = Futures.Metals.MicroSilver, Type = SecurityType.FutureOption, ExpectedFee = 0.42m }, + new { Tier = 1, Symbol = Futures.Energy.MicroCrudeOilWTI, Type = SecurityType.FutureOption, ExpectedFee = 0.42m }, + new { Tier = 2, Symbol = Futures.Indices.MicroDow30EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.37m }, + new { Tier = 2, Symbol = Futures.Indices.MicroRussell2000EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.37m }, + new { Tier = 2, Symbol = Futures.Indices.MicroSP500EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.37m }, + new { Tier = 2, Symbol = Futures.Indices.MicroNASDAQ100EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.37m }, + new { Tier = 2, Symbol = Futures.Financials.MicroY2TreasuryBond, Type = SecurityType.FutureOption, ExpectedFee = 0.37m }, + new { Tier = 2, Symbol = Futures.Financials.MicroY5TreasuryBond, Type = SecurityType.FutureOption, ExpectedFee = 0.37m }, + new { Tier = 2, Symbol = Futures.Financials.MicroY10TreasuryNote, Type = SecurityType.FutureOption, ExpectedFee = 0.37m }, + new { Tier = 2, Symbol = Futures.Financials.MicroY30TreasuryBond, Type = SecurityType.FutureOption, ExpectedFee = 0.37m }, + new { Tier = 2, Symbol = Futures.Metals.MicroGold, Type = SecurityType.FutureOption, ExpectedFee = 0.37m }, + new { Tier = 2, Symbol = Futures.Metals.MicroSilver, Type = SecurityType.FutureOption, ExpectedFee = 0.37m }, + new { Tier = 2, Symbol = Futures.Energy.MicroCrudeOilWTI, Type = SecurityType.FutureOption, ExpectedFee = 0.37m }, + new { Tier = 3, Symbol = Futures.Indices.MicroDow30EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.32m }, + new { Tier = 3, Symbol = Futures.Indices.MicroRussell2000EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.32m }, + new { Tier = 3, Symbol = Futures.Indices.MicroSP500EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.32m }, + new { Tier = 3, Symbol = Futures.Indices.MicroNASDAQ100EMini, Type = SecurityType.FutureOption, ExpectedFee = 0.32m }, + new { Tier = 3, Symbol = Futures.Financials.MicroY2TreasuryBond, Type = SecurityType.FutureOption, ExpectedFee = 0.32m }, + new { Tier = 3, Symbol = Futures.Financials.MicroY5TreasuryBond, Type = SecurityType.FutureOption, ExpectedFee = 0.32m }, + new { Tier = 3, Symbol = Futures.Financials.MicroY10TreasuryNote, Type = SecurityType.FutureOption, ExpectedFee = 0.32m }, + new { Tier = 3, Symbol = Futures.Financials.MicroY30TreasuryBond, Type = SecurityType.FutureOption, ExpectedFee = 0.32m }, + new { Tier = 3, Symbol = Futures.Metals.MicroGold, Type = SecurityType.FutureOption, ExpectedFee = 0.32m }, + new { Tier = 3, Symbol = Futures.Metals.MicroSilver, Type = SecurityType.FutureOption, ExpectedFee = 0.32m }, + new { Tier = 3, Symbol = Futures.Energy.MicroCrudeOilWTI, Type = SecurityType.FutureOption, ExpectedFee = 0.32m }, + // Cryptocurrency futures + new { Tier = 0, Symbol = Futures.Currencies.BTC, Type = SecurityType.Future, ExpectedFee = 11.02m }, + new { Tier = 0, Symbol = Futures.Currencies.ETH, Type = SecurityType.Future, ExpectedFee = 7.02m }, + new { Tier = 0, Symbol = Futures.Currencies.MicroBTC, Type = SecurityType.Future, ExpectedFee = 4.77m }, + new { Tier = 0, Symbol = Futures.Currencies.MicroEther, Type = SecurityType.Future, ExpectedFee = 0.42m }, + new { Tier = 1, Symbol = Futures.Currencies.BTC, Type = SecurityType.Future, ExpectedFee = 11.02m }, + new { Tier = 1, Symbol = Futures.Currencies.ETH, Type = SecurityType.Future, ExpectedFee = 7.02m }, + new { Tier = 1, Symbol = Futures.Currencies.MicroBTC, Type = SecurityType.Future, ExpectedFee = 4.77m }, + new { Tier = 1, Symbol = Futures.Currencies.MicroEther, Type = SecurityType.Future, ExpectedFee = 0.42m }, + new { Tier = 2, Symbol = Futures.Currencies.BTC, Type = SecurityType.Future, ExpectedFee = 11.02m }, + new { Tier = 2, Symbol = Futures.Currencies.ETH, Type = SecurityType.Future, ExpectedFee = 7.02m }, + new { Tier = 2, Symbol = Futures.Currencies.MicroBTC, Type = SecurityType.Future, ExpectedFee = 4.77m }, + new { Tier = 2, Symbol = Futures.Currencies.MicroEther, Type = SecurityType.Future, ExpectedFee = 0.42m }, + new { Tier = 3, Symbol = Futures.Currencies.BTC, Type = SecurityType.Future, ExpectedFee = 11.02m }, + new { Tier = 3, Symbol = Futures.Currencies.ETH, Type = SecurityType.Future, ExpectedFee = 7.02m }, + new { Tier = 3, Symbol = Futures.Currencies.MicroBTC, Type = SecurityType.Future, ExpectedFee = 4.77m }, + new { Tier = 3, Symbol = Futures.Currencies.MicroEther, Type = SecurityType.Future, ExpectedFee = 0.42m }, + // Cryptocurrency future options + new { Tier = 0, Symbol = Futures.Currencies.BTC, Type = SecurityType.FutureOption, ExpectedFee = 10.02m }, + new { Tier = 0, Symbol = Futures.Currencies.ETH, Type = SecurityType.FutureOption, ExpectedFee = 7.02m }, + new { Tier = 0, Symbol = Futures.Currencies.MicroBTC, Type = SecurityType.FutureOption, ExpectedFee = 3.77m }, + new { Tier = 0, Symbol = Futures.Currencies.MicroEther, Type = SecurityType.FutureOption, ExpectedFee = 0.32m }, + new { Tier = 1, Symbol = Futures.Currencies.BTC, Type = SecurityType.FutureOption, ExpectedFee = 10.02m }, + new { Tier = 1, Symbol = Futures.Currencies.ETH, Type = SecurityType.FutureOption, ExpectedFee = 7.02m }, + new { Tier = 1, Symbol = Futures.Currencies.MicroBTC, Type = SecurityType.FutureOption, ExpectedFee = 3.77m }, + new { Tier = 1, Symbol = Futures.Currencies.MicroEther, Type = SecurityType.FutureOption, ExpectedFee = 0.32m }, + new { Tier = 2, Symbol = Futures.Currencies.BTC, Type = SecurityType.FutureOption, ExpectedFee = 10.02m }, + new { Tier = 2, Symbol = Futures.Currencies.ETH, Type = SecurityType.FutureOption, ExpectedFee = 7.02m }, + new { Tier = 2, Symbol = Futures.Currencies.MicroBTC, Type = SecurityType.FutureOption, ExpectedFee = 3.77m }, + new { Tier = 2, Symbol = Futures.Currencies.MicroEther, Type = SecurityType.FutureOption, ExpectedFee = 0.32m }, + new { Tier = 3, Symbol = Futures.Currencies.BTC, Type = SecurityType.FutureOption, ExpectedFee = 10.02m }, + new { Tier = 3, Symbol = Futures.Currencies.ETH, Type = SecurityType.FutureOption, ExpectedFee = 7.02m }, + new { Tier = 3, Symbol = Futures.Currencies.MicroBTC, Type = SecurityType.FutureOption, ExpectedFee = 3.77m }, + new { Tier = 3, Symbol = Futures.Currencies.MicroEther, Type = SecurityType.FutureOption, ExpectedFee = 0.32m }, + // E-mini FX (currencies) Futures + new { Tier = 0, Symbol = Futures.Currencies.EuroFXEmini, Type = SecurityType.Future, ExpectedFee = 1.37m }, + new { Tier = 0, Symbol = Futures.Currencies.JapaneseYenEmini, Type = SecurityType.Future, ExpectedFee = 1.37m }, + new { Tier = 1, Symbol = Futures.Currencies.EuroFXEmini, Type = SecurityType.Future, ExpectedFee = 1.27m }, + new { Tier = 1, Symbol = Futures.Currencies.JapaneseYenEmini, Type = SecurityType.Future, ExpectedFee = 1.27m }, + new { Tier = 2, Symbol = Futures.Currencies.EuroFXEmini, Type = SecurityType.Future, ExpectedFee = 1.17m }, + new { Tier = 2, Symbol = Futures.Currencies.JapaneseYenEmini, Type = SecurityType.Future, ExpectedFee = 1.17m }, + new { Tier = 3, Symbol = Futures.Currencies.EuroFXEmini, Type = SecurityType.Future, ExpectedFee = 1.02m }, + new { Tier = 3, Symbol = Futures.Currencies.JapaneseYenEmini, Type = SecurityType.Future, ExpectedFee = 1.02m }, + // Micro E-mini FX (currencies) Futures + new { Tier = 0, Symbol = Futures.Currencies.MicroAUD, Type = SecurityType.Future, ExpectedFee = 0.41m }, + new { Tier = 0, Symbol = Futures.Currencies.MicroEUR, Type = SecurityType.Future, ExpectedFee = 0.41m }, + new { Tier = 0, Symbol = Futures.Currencies.MicroGBP, Type = SecurityType.Future, ExpectedFee = 0.41m }, + new { Tier = 0, Symbol = Futures.Currencies.MicroCADUSD, Type = SecurityType.Future, ExpectedFee = 0.41m }, + new { Tier = 0, Symbol = Futures.Currencies.MicroJPY, Type = SecurityType.Future, ExpectedFee = 0.41m }, + new { Tier = 0, Symbol = Futures.Currencies.MicroCHF, Type = SecurityType.Future, ExpectedFee = 0.41m }, + new { Tier = 0, Symbol = Futures.Currencies.MicroUSDJPY, Type = SecurityType.Future, ExpectedFee = 0.41m }, + new { Tier = 0, Symbol = Futures.Currencies.MicroINRUSD, Type = SecurityType.Future, ExpectedFee = 0.41m }, + new { Tier = 0, Symbol = Futures.Currencies.MicroCAD, Type = SecurityType.Future, ExpectedFee = 0.41m }, + new { Tier = 0, Symbol = Futures.Currencies.MicroUSDCHF, Type = SecurityType.Future, ExpectedFee = 0.41m }, + new { Tier = 0, Symbol = Futures.Currencies.MicroUSDCNH, Type = SecurityType.Future, ExpectedFee = 0.41m }, + new { Tier = 1, Symbol = Futures.Currencies.MicroAUD, Type = SecurityType.Future, ExpectedFee = 0.38m }, + new { Tier = 1, Symbol = Futures.Currencies.MicroEUR, Type = SecurityType.Future, ExpectedFee = 0.38m }, + new { Tier = 1, Symbol = Futures.Currencies.MicroGBP, Type = SecurityType.Future, ExpectedFee = 0.38m }, + new { Tier = 1, Symbol = Futures.Currencies.MicroCADUSD, Type = SecurityType.Future, ExpectedFee = 0.38m }, + new { Tier = 1, Symbol = Futures.Currencies.MicroJPY, Type = SecurityType.Future, ExpectedFee = 0.38m }, + new { Tier = 1, Symbol = Futures.Currencies.MicroCHF, Type = SecurityType.Future, ExpectedFee = 0.38m }, + new { Tier = 1, Symbol = Futures.Currencies.MicroUSDJPY, Type = SecurityType.Future, ExpectedFee = 0.38m }, + new { Tier = 1, Symbol = Futures.Currencies.MicroINRUSD, Type = SecurityType.Future, ExpectedFee = 0.38m }, + new { Tier = 1, Symbol = Futures.Currencies.MicroCAD, Type = SecurityType.Future, ExpectedFee = 0.38m }, + new { Tier = 1, Symbol = Futures.Currencies.MicroUSDCHF, Type = SecurityType.Future, ExpectedFee = 0.38m }, + new { Tier = 1, Symbol = Futures.Currencies.MicroUSDCNH, Type = SecurityType.Future, ExpectedFee = 0.38m }, + new { Tier = 2, Symbol = Futures.Currencies.MicroAUD, Type = SecurityType.Future, ExpectedFee = 0.34m }, + new { Tier = 2, Symbol = Futures.Currencies.MicroEUR, Type = SecurityType.Future, ExpectedFee = 0.34m }, + new { Tier = 2, Symbol = Futures.Currencies.MicroGBP, Type = SecurityType.Future, ExpectedFee = 0.34m }, + new { Tier = 2, Symbol = Futures.Currencies.MicroCADUSD, Type = SecurityType.Future, ExpectedFee = 0.34m }, + new { Tier = 2, Symbol = Futures.Currencies.MicroJPY, Type = SecurityType.Future, ExpectedFee = 0.34m }, + new { Tier = 2, Symbol = Futures.Currencies.MicroCHF, Type = SecurityType.Future, ExpectedFee = 0.34m }, + new { Tier = 2, Symbol = Futures.Currencies.MicroUSDJPY, Type = SecurityType.Future, ExpectedFee = 0.34m }, + new { Tier = 2, Symbol = Futures.Currencies.MicroINRUSD, Type = SecurityType.Future, ExpectedFee = 0.34m }, + new { Tier = 2, Symbol = Futures.Currencies.MicroCAD, Type = SecurityType.Future, ExpectedFee = 0.34m }, + new { Tier = 2, Symbol = Futures.Currencies.MicroUSDCHF, Type = SecurityType.Future, ExpectedFee = 0.34m }, + new { Tier = 2, Symbol = Futures.Currencies.MicroUSDCNH, Type = SecurityType.Future, ExpectedFee = 0.34m }, + new { Tier = 3, Symbol = Futures.Currencies.MicroAUD, Type = SecurityType.Future, ExpectedFee = 0.31m }, + new { Tier = 3, Symbol = Futures.Currencies.MicroEUR, Type = SecurityType.Future, ExpectedFee = 0.31m }, + new { Tier = 3, Symbol = Futures.Currencies.MicroGBP, Type = SecurityType.Future, ExpectedFee = 0.31m }, + new { Tier = 3, Symbol = Futures.Currencies.MicroCADUSD, Type = SecurityType.Future, ExpectedFee = 0.31m }, + new { Tier = 3, Symbol = Futures.Currencies.MicroJPY, Type = SecurityType.Future, ExpectedFee = 0.31m }, + new { Tier = 3, Symbol = Futures.Currencies.MicroCHF, Type = SecurityType.Future, ExpectedFee = 0.31m }, + new { Tier = 3, Symbol = Futures.Currencies.MicroUSDJPY, Type = SecurityType.Future, ExpectedFee = 0.31m }, + new { Tier = 3, Symbol = Futures.Currencies.MicroINRUSD, Type = SecurityType.Future, ExpectedFee = 0.31m }, + new { Tier = 3, Symbol = Futures.Currencies.MicroCAD, Type = SecurityType.Future, ExpectedFee = 0.31m }, + new { Tier = 3, Symbol = Futures.Currencies.MicroUSDCHF, Type = SecurityType.Future, ExpectedFee = 0.31m }, + new { Tier = 3, Symbol = Futures.Currencies.MicroUSDCNH, Type = SecurityType.Future, ExpectedFee = 0.31m }, + // Other futures + new { Tier = 0, Symbol = Futures.Metals.MicroGoldTAS, Type = SecurityType.Future, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Metals.MicroPalladium, Type = SecurityType.Future, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Energy.MicroGasoilZeroPointOnePercentBargesFOBARAPlatts, Type = SecurityType.Future, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Energy.MicroEuropeanThreePointFivePercentFuelOilCargoesFOBMedPlatts, Type = SecurityType.Future, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Energy.MicroCoalAPIFivefobNewcastleArgusMcCloskey, Type = SecurityType.Future, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Energy.MicroSingaporeFuelOil380CSTPlatts, Type = SecurityType.Future, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Energy.MicroEuropeanThreePointFivePercentOilBargesFOBRdamPlatts, Type = SecurityType.Future, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Energy.MicroEuropeanFOBRdamMarineFuelZeroPointFivePercentBargesPlatts, Type = SecurityType.Future, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Energy.MicroSingaporeFOBMarineFuelZeroPointFivePercetPlatts, Type = SecurityType.Future, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Currencies.USD, Type = SecurityType.Future, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Currencies.CAD, Type = SecurityType.Future, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Currencies.EUR, Type = SecurityType.Future, ExpectedFee = 2.47m }, + new { Tier = 1, Symbol = Futures.Metals.MicroGoldTAS, Type = SecurityType.Future, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Metals.MicroPalladium, Type = SecurityType.Future, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Energy.MicroGasoilZeroPointOnePercentBargesFOBARAPlatts, Type = SecurityType.Future, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Energy.MicroEuropeanThreePointFivePercentFuelOilCargoesFOBMedPlatts, Type = SecurityType.Future, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Energy.MicroCoalAPIFivefobNewcastleArgusMcCloskey, Type = SecurityType.Future, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Energy.MicroSingaporeFuelOil380CSTPlatts, Type = SecurityType.Future, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Energy.MicroEuropeanThreePointFivePercentOilBargesFOBRdamPlatts, Type = SecurityType.Future, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Energy.MicroEuropeanFOBRdamMarineFuelZeroPointFivePercentBargesPlatts, Type = SecurityType.Future, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Energy.MicroSingaporeFOBMarineFuelZeroPointFivePercetPlatts, Type = SecurityType.Future, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Currencies.USD, Type = SecurityType.Future, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Currencies.CAD, Type = SecurityType.Future, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Currencies.EUR, Type = SecurityType.Future, ExpectedFee = 2.27m }, + new { Tier = 2, Symbol = Futures.Metals.MicroGoldTAS, Type = SecurityType.Future, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Metals.MicroPalladium, Type = SecurityType.Future, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Energy.MicroGasoilZeroPointOnePercentBargesFOBARAPlatts, Type = SecurityType.Future, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Energy.MicroEuropeanThreePointFivePercentFuelOilCargoesFOBMedPlatts, Type = SecurityType.Future, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Energy.MicroCoalAPIFivefobNewcastleArgusMcCloskey, Type = SecurityType.Future, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Energy.MicroSingaporeFuelOil380CSTPlatts, Type = SecurityType.Future, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Energy.MicroEuropeanThreePointFivePercentOilBargesFOBRdamPlatts, Type = SecurityType.Future, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Energy.MicroEuropeanFOBRdamMarineFuelZeroPointFivePercentBargesPlatts, Type = SecurityType.Future, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Energy.MicroSingaporeFOBMarineFuelZeroPointFivePercetPlatts, Type = SecurityType.Future, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Currencies.USD, Type = SecurityType.Future, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Currencies.CAD, Type = SecurityType.Future, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Currencies.EUR, Type = SecurityType.Future, ExpectedFee = 2.07m }, + new { Tier = 3, Symbol = Futures.Metals.MicroGoldTAS, Type = SecurityType.Future, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Metals.MicroPalladium, Type = SecurityType.Future, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Energy.MicroGasoilZeroPointOnePercentBargesFOBARAPlatts, Type = SecurityType.Future, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Energy.MicroEuropeanThreePointFivePercentFuelOilCargoesFOBMedPlatts, Type = SecurityType.Future, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Energy.MicroCoalAPIFivefobNewcastleArgusMcCloskey, Type = SecurityType.Future, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Energy.MicroSingaporeFuelOil380CSTPlatts, Type = SecurityType.Future, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Energy.MicroEuropeanThreePointFivePercentOilBargesFOBRdamPlatts, Type = SecurityType.Future, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Energy.MicroEuropeanFOBRdamMarineFuelZeroPointFivePercentBargesPlatts, Type = SecurityType.Future, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Energy.MicroSingaporeFOBMarineFuelZeroPointFivePercetPlatts, Type = SecurityType.Future, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Currencies.USD, Type = SecurityType.Future, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Currencies.CAD, Type = SecurityType.Future, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Currencies.EUR, Type = SecurityType.Future, ExpectedFee = 1.87m }, + // Other future options + new { Tier = 0, Symbol = Futures.Metals.MicroGoldTAS, Type = SecurityType.FutureOption, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Metals.MicroPalladium, Type = SecurityType.FutureOption, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Energy.MicroGasoilZeroPointOnePercentBargesFOBARAPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Energy.MicroEuropeanThreePointFivePercentFuelOilCargoesFOBMedPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Energy.MicroCoalAPIFivefobNewcastleArgusMcCloskey, Type = SecurityType.FutureOption, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Energy.MicroSingaporeFuelOil380CSTPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Energy.MicroEuropeanThreePointFivePercentOilBargesFOBRdamPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Energy.MicroEuropeanFOBRdamMarineFuelZeroPointFivePercentBargesPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Energy.MicroSingaporeFOBMarineFuelZeroPointFivePercetPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Currencies.USD, Type = SecurityType.FutureOption, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Currencies.CAD, Type = SecurityType.FutureOption, ExpectedFee = 2.47m }, + new { Tier = 0, Symbol = Futures.Currencies.EUR, Type = SecurityType.FutureOption, ExpectedFee = 2.47m }, + new { Tier = 1, Symbol = Futures.Metals.MicroGoldTAS, Type = SecurityType.FutureOption, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Metals.MicroPalladium, Type = SecurityType.FutureOption, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Energy.MicroGasoilZeroPointOnePercentBargesFOBARAPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Energy.MicroEuropeanThreePointFivePercentFuelOilCargoesFOBMedPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Energy.MicroCoalAPIFivefobNewcastleArgusMcCloskey, Type = SecurityType.FutureOption, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Energy.MicroSingaporeFuelOil380CSTPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Energy.MicroEuropeanThreePointFivePercentOilBargesFOBRdamPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Energy.MicroEuropeanFOBRdamMarineFuelZeroPointFivePercentBargesPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Energy.MicroSingaporeFOBMarineFuelZeroPointFivePercetPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Currencies.USD, Type = SecurityType.FutureOption, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Currencies.CAD, Type = SecurityType.FutureOption, ExpectedFee = 2.27m }, + new { Tier = 1, Symbol = Futures.Currencies.EUR, Type = SecurityType.FutureOption, ExpectedFee = 2.27m }, + new { Tier = 2, Symbol = Futures.Metals.MicroGoldTAS, Type = SecurityType.FutureOption, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Metals.MicroPalladium, Type = SecurityType.FutureOption, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Energy.MicroGasoilZeroPointOnePercentBargesFOBARAPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Energy.MicroEuropeanThreePointFivePercentFuelOilCargoesFOBMedPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Energy.MicroCoalAPIFivefobNewcastleArgusMcCloskey, Type = SecurityType.FutureOption, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Energy.MicroSingaporeFuelOil380CSTPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Energy.MicroEuropeanThreePointFivePercentOilBargesFOBRdamPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Energy.MicroEuropeanFOBRdamMarineFuelZeroPointFivePercentBargesPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Energy.MicroSingaporeFOBMarineFuelZeroPointFivePercetPlatts, Type = SecurityType.FutureOption, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Currencies.USD, Type = SecurityType.FutureOption, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Currencies.CAD, Type = SecurityType.FutureOption, ExpectedFee = 2.07m }, + new { Tier = 2, Symbol = Futures.Currencies.EUR, Type = SecurityType.FutureOption, ExpectedFee = 2.07m }, + new { Tier = 3, Symbol = Futures.Metals.MicroGoldTAS, Type = SecurityType.FutureOption, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Metals.MicroPalladium, Type = SecurityType.FutureOption, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Energy.MicroGasoilZeroPointOnePercentBargesFOBARAPlatts, Type = SecurityType.FutureOption, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Energy.MicroEuropeanThreePointFivePercentFuelOilCargoesFOBMedPlatts, Type = SecurityType.FutureOption, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Energy.MicroCoalAPIFivefobNewcastleArgusMcCloskey, Type = SecurityType.FutureOption, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Energy.MicroSingaporeFuelOil380CSTPlatts, Type = SecurityType.FutureOption, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Energy.MicroEuropeanThreePointFivePercentOilBargesFOBRdamPlatts, Type = SecurityType.FutureOption, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Energy.MicroEuropeanFOBRdamMarineFuelZeroPointFivePercentBargesPlatts, Type = SecurityType.FutureOption, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Energy.MicroSingaporeFOBMarineFuelZeroPointFivePercetPlatts, Type = SecurityType.FutureOption, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Currencies.USD, Type = SecurityType.FutureOption, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Currencies.CAD, Type = SecurityType.FutureOption, ExpectedFee = 1.87m }, + new { Tier = 3, Symbol = Futures.Currencies.EUR, Type = SecurityType.FutureOption, ExpectedFee = 1.87m }, + }.Select(x => + { + var symbol = Symbols.CreateFutureSymbol(x.Symbol, SecurityIdentifier.DefaultDate); + if (x.Type == SecurityType.FutureOption) + { + symbol = Symbols.CreateFutureOptionSymbol(symbol, OptionRight.Call, 0m, SecurityIdentifier.DefaultDate); + } + + return new TestCaseData(x.Tier, symbol, x.ExpectedFee); + }).ToArray(); + } + } +} diff --git a/Tests/Symbols.cs b/Tests/Symbols.cs index 8c1bb767f07c..f07727ed1d0b 100644 --- a/Tests/Symbols.cs +++ b/Tests/Symbols.cs @@ -72,6 +72,8 @@ public static class Symbols public static readonly Symbol Future_ESZ18_Dec2018 = CreateFutureSymbol(Futures.Indices.SP500EMini, new DateTime(2018, 12, 21)); public static readonly Symbol Future_CLF19_Jan2019 = CreateFutureSymbol("CL", new DateTime(2018, 12, 19)); + public static readonly Symbol BTCUSD_Future = CreateCryptoFutureSymbol("BTCUSD"); + public static readonly Symbol SPX = CreateIndexSymbol("SPX"); public static readonly ImmutableArray All = @@ -188,6 +190,16 @@ public static Symbol CreateFuturesCanonicalSymbol(string ticker) return Symbol.Create(ticker, SecurityType.Future, market, "/" + ticker); } + public static Symbol CreateCryptoFutureSymbol(string ticker) + { + string market; + if (!SymbolPropertiesDatabase.FromDataFolder().TryGetMarket(ticker, SecurityType.CryptoFuture, out market)) + { + market = DefaultBrokerageModel.DefaultMarketMap[SecurityType.CryptoFuture]; + } + return Symbol.Create(ticker, SecurityType.CryptoFuture, market, "/" + ticker); + } + internal static Symbol CreateIndexSymbol(string ticker) { return Symbol.Create(ticker, SecurityType.Index, Market.USA);