/*
* 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.Orders.Fills;
using System.Collections.Generic;
namespace QuantConnect.Orders.Fees
{
///
/// Provides the default implementation of
///
public class InteractiveBrokersFeeModel : FeeModel
{
private readonly decimal _forexCommissionRate;
private readonly decimal _forexMinimumOrderFee;
// option commission function takes number of contracts and the size of the option premium and returns total commission
private readonly Dictionary> _optionFee =
new Dictionary>();
#pragma warning disable CS1570
///
/// Reference at https://www.interactivebrokers.com/en/index.php?f=commission&p=futures1
///
#pragma warning restore CS1570
private readonly Dictionary> _futureFee =
// IB fee + exchange fee
new()
{
{ Market.USA, UnitedStatesFutureFees },
{ Market.HKFE, HongKongFutureFees },
{ Market.EUREX, EUREXFutureFees }
};
///
/// Initializes a new instance of the
///
/// Monthly FX dollar volume traded
/// Monthly options contracts traded
public InteractiveBrokersFeeModel(decimal monthlyForexTradeAmountInUSDollars = 0, decimal monthlyOptionsTradeAmountInContracts = 0)
{
ProcessForexRateSchedule(monthlyForexTradeAmountInUSDollars, out _forexCommissionRate, out _forexMinimumOrderFee);
Func optionsCommissionFunc;
ProcessOptionsRateSchedule(monthlyOptionsTradeAmountInContracts, out optionsCommissionFunc);
// only USA for now
_optionFee.Add(Market.USA, optionsCommissionFunc);
}
///
/// 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;
// 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:
// 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;
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;
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;
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);
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);
break;
default:
// unsupported security type
throw new ArgumentException(Messages.FeeModel.UnsupportedSecurityType(security));
}
return new OrderFee(new CashAmount(
feeResult,
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;
decimal ibFeePerContract, exchangeFeePerContract;
string symbol;
switch (security.Symbol.SecurityType)
{
case SecurityType.Future:
fees = _usaFuturesFees;
exchangeFees = _usaFuturesExchangeFees;
symbol = security.Symbol.ID.Symbol;
break;
case SecurityType.FutureOption:
fees = _usaFutureOptionsFees;
exchangeFees = _usaFutureOptionsExchangeFees;
symbol = security.Symbol.Underlying.ID.Symbol;
break;
default:
throw new ArgumentException(Messages.InteractiveBrokersFeeModel.UnitedStatesFutureFeesUnsupportedSecurityType(security));
}
if (!fees.TryGetValue(symbol, out ibFeePerContract))
{
ibFeePerContract = 0.85m;
}
if (!exchangeFees.TryGetValue(symbol, out exchangeFeePerContract))
{
exchangeFeePerContract = 1.60m;
}
// Add exchange fees + IBKR regulatory fee (0.02)
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
///
private static readonly Dictionary _usaFuturesFees = new()
{
// Micro E-mini Futures
{ "MYM", 0.25m }, { "M2K", 0.25m }, { "MES", 0.25m }, { "MNQ", 0.25m }, { "2YY", 0.25m }, { "5YY", 0.25m }, { "10Y", 0.25m },
{ "30Y", 0.25m }, { "MCL", 0.25m }, { "MGC", 0.25m }, { "SIL", 0.25m },
// Cryptocurrency Futures
{ "BTC", 5m }, { "MBT", 2.25m }, { "ETH", 3m }, { "MET", 0.20m }, { "MIB", 2.25m }, { "MRB", 0.20m },
// E-mini FX (currencies) Futures
{ "E7", 0.50m }, { "J7", 0.50m },
// Micro E-mini FX (currencies) Futures
{ "M6E", 0.15m }, { "M6A", 0.15m }, { "M6B", 0.15m }, { "MCD", 0.15m }, { "MJY", 0.15m }, { "MSF", 0.15m }, { "M6J", 0.15m },
{ "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
{ "MYM", 0.25m }, { "M2K", 0.25m }, { "MES", 0.25m }, { "MNQ", 0.25m }, { "2YY", 0.25m }, { "5YY", 0.25m }, { "10Y", 0.25m },
{ "30Y", 0.25m }, { "MCL", 0.25m }, { "MGC", 0.25m }, { "SIL", 0.25m },
// Cryptocurrency Future Options
{ "BTC", 5m }, { "MBT", 1.25m }, { "ETH", 3m }, { "MET", 0.10m }, { "MIB", 1.25m }, { "MRB", 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 }, { "MIB", 2.5m }, { "MRB", 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 }, { "MIB", 2.5m }, { "MRB", 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;
}
}
}
}