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);