<commit_before>/*
 * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
 * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

using System;
using System.Collections.Generic;
using QuantConnect.Orders.Fills;
using QuantConnect.Securities;
using static QuantConnect.StringExtensions;

namespace QuantConnect.Orders.Fees
{
    /// <summary>
    /// Provides the default implementation of <see cref="IFeeModel"/>
    /// </summary>
    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<string, Func<decimal, decimal, CashAmount>> _optionFee =
            new Dictionary<string, Func<decimal, decimal, CashAmount>>();

        private readonly Dictionary<string, CashAmount> _futureFee =
            //                                                               IB fee + exchange fee
            new Dictionary<string, CashAmount> { { Market.USA, new CashAmount(0.85m + 1, "USD") } };

        /// <summary>
        /// Initializes a new instance of the <see cref="ImmediateFillModel"/>
        /// </summary>
        /// <param name="monthlyForexTradeAmountInUSDollars">Monthly FX dollar volume traded</param>
        /// <param name="monthlyOptionsTradeAmountInContracts">Monthly options contracts traded</param>
        public InteractiveBrokersFeeModel(decimal monthlyForexTradeAmountInUSDollars = 0, decimal monthlyOptionsTradeAmountInContracts = 0)
        {
            ProcessForexRateSchedule(monthlyForexTradeAmountInUSDollars, out _forexCommissionRate, out _forexMinimumOrderFee);
            Func<decimal, decimal, CashAmount> optionsCommissionFunc;
            ProcessOptionsRateSchedule(monthlyOptionsTradeAmountInContracts, out optionsCommissionFunc);
            // only USA for now
            _optionFee.Add(Market.USA, optionsCommissionFunc);
        }

        /// <summary>
        /// Gets the order fee associated with the specified order. This returns the cost
        /// of the transaction in the account currency
        /// </summary>
        /// <param name="parameters">A <see cref="OrderFeeParameters"/> object
        /// containing the security and order</param>
        /// <returns>The cost of the order in units of the account currency</returns>
        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;
                }
            }

            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<decimal, decimal, CashAmount> optionsCommissionFunc;
                    if (!_optionFee.TryGetValue(market, out optionsCommissionFunc))
                    {
                        throw new KeyNotFoundException($"InteractiveBrokersFeeModel(): unexpected option Market {market}");
                    }
                    // applying commission function to the order
                    var optionFee = optionsCommissionFunc(order.AbsoluteQuantity, order.Price);
                    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)
                    {
                        // just in case...
                        market = Market.USA;
                    }

                    CashAmount feeRatePerContract;
                    if (!_futureFee.TryGetValue(market, out feeRatePerContract))
                    {
                        throw new KeyNotFoundException($"InteractiveBrokersFeeModel(): unexpected future Market {market}");
                    }
                    feeResult = order.AbsoluteQuantity * feeRatePerContract.Amount;
                    feeCurrency = feeRatePerContract.Currency;
                    break;

                case SecurityType.Equity:
                    EquityFee equityFee;
                    switch (market)
                    {
                        case Market.USA:
                            equityFee = new EquityFee("USD", feePerShare: 0.005m, minimumFee: 1, maximumFeeRate: 0.005m);
                            break;
                        default:
                            throw new KeyNotFoundException($"InteractiveBrokersFeeModel(): unexpected equity Market {market}");
                    }
                    var tradeValue = Math.Abs(order.GetValue(security));
<commit_msg>Adds Market.HKFE to InteractiveBrokersFeeModel (#6127)<commit_after>/*
 * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
 * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

using System;
using System.Collections.Generic;
using QuantConnect.Orders.Fills;
using QuantConnect.Securities;
using static QuantConnect.StringExtensions;

namespace QuantConnect.Orders.Fees
{
    /// <summary>
    /// Provides the default implementation of <see cref="IFeeModel"/>
    /// </summary>
    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<string, Func<decimal, decimal, CashAmount>> _optionFee =
            new Dictionary<string, Func<decimal, decimal, CashAmount>>();

        private readonly Dictionary<string, CashAmount> _futureFee =
            //                                                               IB fee + exchange fee
            new Dictionary<string, CashAmount> { { Market.USA, new CashAmount(0.85m + 1, "USD") } };

        /// <summary>
        /// Initializes a new instance of the <see cref="ImmediateFillModel"/>
        /// </summary>
        /// <param name="monthlyForexTradeAmountInUSDollars">Monthly FX dollar volume traded</param>
        /// <param name="monthlyOptionsTradeAmountInContracts">Monthly options contracts traded</param>
        public InteractiveBrokersFeeModel(decimal monthlyForexTradeAmountInUSDollars = 0, decimal monthlyOptionsTradeAmountInContracts = 0)
        {
            ProcessForexRateSchedule(monthlyForexTradeAmountInUSDollars, out _forexCommissionRate, out _forexMinimumOrderFee);
            Func<decimal, decimal, CashAmount> optionsCommissionFunc;
            ProcessOptionsRateSchedule(monthlyOptionsTradeAmountInContracts, out optionsCommissionFunc);
            // only USA for now
            _optionFee.Add(Market.USA, optionsCommissionFunc);
        }

        /// <summary>
        /// Gets the order fee associated with the specified order. This returns the cost
        /// of the transaction in the account currency
        /// </summary>
        /// <param name="parameters">A <see cref="OrderFeeParameters"/> object
        /// containing the security and order</param>
        /// <returns>The cost of the order in units of the account currency</returns>
        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;
                }
            }

            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<decimal, decimal, CashAmount> optionsCommissionFunc;
                    if (!_optionFee.TryGetValue(market, out optionsCommissionFunc))
                    {
                        throw new KeyNotFoundException($"InteractiveBrokersFeeModel(): unexpected option Market {market}");
                    }
                    // applying commission function to the order
                    var optionFee = optionsCommissionFunc(order.AbsoluteQuantity, order.Price);
                    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.HKFE)
                    {
                        // just in case...
                        market = Market.USA;
                    }

                    CashAmount feeRatePerContract;
                    if (!_futureFee.TryGetValue(market, out feeRatePerContract))
                    {
                        throw new KeyNotFoundException($"InteractiveBrokersFeeModel(): unexpected future Market {market}");
                    }
                    feeResult = order.AbsoluteQuantity * feeRatePerContract.Amount;
                    feeCurrency = feeRatePerContract.Currency;
                    break;

                case SecurityType.Equity:
                    EquityFee equityFee;
                    switch (market)
                    {
                        case Market.USA:
                            equityFee = new EquityFee("USD", feePerShare: 0.005m, minimumFee: 1, maximumFeeRate: 0.005m);
                            break;
                        default:
                            throw new KeyNotFoundException($"InteractiveBrokersFeeModel(): unexpected equity Market {market}");
                    }
                    var tradeValue = Math.Abs(order.GetValue(security));

{'fraction_non_alphanumeric': 0.07537432766390463, 'fraction_numerical': 0.004797208896641954, 'mean_word_length': 1.8551566715086119, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 53, 'https://': 2, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6712478', 'n_tokens_mistral': 3316, 'n_tokens_neox': 3082, 'n_words': 948}