From ae864e1350dbddfe6cf930b3920d7120c86e51bf Mon Sep 17 00:00:00 2001 From: LouisSzeto Date: Tue, 14 Jan 2025 12:49:48 +0800 Subject: [PATCH] Fix bug in repeated count and other misc --- ...teractiveBrokersTieredFeeModelAlgorithm.cs | 45 ++++++++++--------- ...teractiveBrokersTieredFeeModelAlgorithm.py | 7 +-- .../Fees/InteractiveBrokersFeeHelper.cs | 9 ++-- .../Fees/InteractiveBrokersTieredFeeModel.cs | 30 ++++++++++--- .../Fees/InteractiveBrokersTieredFeeModel.py | 22 ++++++--- Launcher/config.json | 2 +- .../Fees/InteractiveBrokersFeeModelTests.cs | 25 ++++++----- .../InteractiveBrokersTieredFeeModelTests.cs | 45 ++++++++++++------- 8 files changed, 118 insertions(+), 67 deletions(-) diff --git a/Algorithm.CSharp/InteractiveBrokersTieredFeeModelAlgorithm.cs b/Algorithm.CSharp/InteractiveBrokersTieredFeeModelAlgorithm.cs index b045c2683a63..1e06b9b155d1 100644 --- a/Algorithm.CSharp/InteractiveBrokersTieredFeeModelAlgorithm.cs +++ b/Algorithm.CSharp/InteractiveBrokersTieredFeeModelAlgorithm.cs @@ -77,7 +77,8 @@ public override void OnOrderEvent(OrderEvent orderEvent) { // Assert if the monthly traded volume is correct in the fee model. _monthlyTradedVolume += orderEvent.AbsoluteFillQuantity; - var modelTradedVolume = _feeModel.MonthTradedVolume[SecurityType.Equity]; + // Month volume will update only at the next scan, so we add this fill quantity. + var modelTradedVolume = _feeModel.MonthTradedVolume[SecurityType.Equity] + orderEvent.AbsoluteFillQuantity; if (_monthlyTradedVolume != modelTradedVolume) { throw new Exception($"Monthly traded volume is incorrect - Actual: {_monthlyTradedVolume} - Model: {modelTradedVolume}"); @@ -116,32 +117,32 @@ public override void OnOrderEvent(OrderEvent orderEvent) 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"}, + {"Average Win", "0.02%"}, + {"Average Loss", "-0.05%"}, + {"Compounding Annual Return", "-26.786%"}, + {"Drawdown", "0.600%"}, + {"Expectancy", "-0.658"}, {"Start Equity", "1000000000"}, - {"End Equity", "999762433.94"}, - {"Net Profit", "-0.024%"}, - {"Sharpe Ratio", "-8.397"}, - {"Sortino Ratio", "-11.384"}, + {"End Equity", "996730872.35"}, + {"Net Profit", "-0.327%"}, + {"Sharpe Ratio", "-7.559"}, + {"Sortino Ratio", "-9.624"}, {"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"}, + {"Profit-Loss Ratio", "0.37"}, + {"Alpha", "-0.358"}, + {"Beta", "0.098"}, + {"Annual Standard Deviation", "0.027"}, + {"Annual Variance", "0.001"}, + {"Information Ratio", "-7.11"}, + {"Tracking Error", "0.245"}, + {"Treynor Ratio", "-2.105"}, + {"Total Fees", "$4126697.73"}, + {"Estimated Strategy Capacity", "$180000000.00"}, {"Lowest Capacity Asset", "AIG R735QTJ8XC9X"}, - {"Portfolio Turnover", "2.37%"}, - {"OrderListHash", "d35a4e91c145a100d4bffb7c0fc0ff35"} + {"Portfolio Turnover", "40.56%"}, + {"OrderListHash", "bd43a7c7f61e734a7dbc06180af8fc36"} }; } } diff --git a/Algorithm.Python/InteractiveBrokersTieredFeeModelAlgorithm.py b/Algorithm.Python/InteractiveBrokersTieredFeeModelAlgorithm.py index cccc2d30e888..7b8c67a0844d 100644 --- a/Algorithm.Python/InteractiveBrokersTieredFeeModelAlgorithm.py +++ b/Algorithm.Python/InteractiveBrokersTieredFeeModelAlgorithm.py @@ -50,9 +50,10 @@ def on_data(self, slice: Slice) -> None: self.market_on_close_order(self.bac, -60000) def on_order_event(self, order_event: OrderEvent) -> None: - if order_event.status != OrderStatus.FILLED: + if order_event.status == OrderStatus.FILLED: # Assert if the monthly traded volume is correct in the fee model. self.monthly_traded_volume += order_event.absolute_fill_quantity - model_traded_volume = self.fee_model.monthly_trade_volume[SecurityType.EQUITY] + # Month volume will update only at the next scan, so we add this fill quantity. + model_traded_volume = self.fee_model.monthly_trade_volume[SecurityType.EQUITY] + order_event.absolute_fill_quantity if self.monthly_traded_volume != model_traded_volume: - raise Exception(f"Monthly traded volume is incorrect - Actual: {self.monthly_traded_volume} - Model: {model_traded_volume}") \ No newline at end of file + raise Exception(f"Monthly traded volume is incorrect - Actual: {self.monthly_traded_volume} - Model: {model_traded_volume}") diff --git a/Common/Orders/Fees/InteractiveBrokersFeeHelper.cs b/Common/Orders/Fees/InteractiveBrokersFeeHelper.cs index 50bb708cf5e4..c7000db54504 100644 --- a/Common/Orders/Fees/InteractiveBrokersFeeHelper.cs +++ b/Common/Orders/Fees/InteractiveBrokersFeeHelper.cs @@ -166,7 +166,7 @@ internal static void ProcessCryptoRateSchedule(decimal monthlyCryptoTradeAmountI 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 + // get the total order value in USD. var totalOrderValue = Math.Abs(order.GetValue(security)); var baseFee = forexCommissionRate*totalOrderValue; @@ -270,8 +270,9 @@ internal static void CalculateEquityFee(decimal quantity, decimal tradeValue, st /// internal static void CalculateCfdFee(Security security, Order order, out decimal fee, out string currency) { - var value = order.AbsoluteQuantity * order.Price; - fee = 0.00002m * value; // 0.002% + var securityPrice = order.Quantity > 0 ? security.AskPrice : security.BidPrice; + var value = order.AbsoluteQuantity * (securityPrice == 0m ? security.Price : securityPrice); + fee = 0.0001m * value; // 0.01% currency = security.QuoteCurrency.Symbol; var minimumFee = security.QuoteCurrency.Symbol switch @@ -294,7 +295,7 @@ internal static decimal CalculateCryptoFee(Security security, Order order, decim var totalTradeValue = Math.Abs(order.GetValue(security)); var cryptoFee = cryptoCommissionRate*totalTradeValue; // 1% maximum fee - fee = Math.Max(Math.Min(totalTradeValue * 0.01m, cryptoFee), cryptoMinimumOrderFee); + fee = Math.Max(Math.Min(totalTradeValue * 0.01m, cryptoMinimumOrderFee), cryptoFee); // IB Crypto fees are all in USD currency = Currencies.USD; diff --git a/Common/Orders/Fees/InteractiveBrokersTieredFeeModel.cs b/Common/Orders/Fees/InteractiveBrokersTieredFeeModel.cs index 15b4caebd35a..87ee28bb836a 100644 --- a/Common/Orders/Fees/InteractiveBrokersTieredFeeModel.cs +++ b/Common/Orders/Fees/InteractiveBrokersTieredFeeModel.cs @@ -46,6 +46,7 @@ public class InteractiveBrokersTieredFeeModel : FeeModel // List of Option exchanges susceptible to pay ORF regulatory fee. private static readonly List _optionExchangesOrfFee = new() { Market.CBOE, Market.USA }; private Dictionary _monthlyTradeVolume; + private Dictionary _volumeByOrder = new(); /// /// The traded volume by security type for selecting the corresponding tier on the current month. @@ -85,6 +86,20 @@ public InteractiveBrokersTieredFeeModel(decimal monthlyEquityTradeVolume = 0, de }; } + private void UpdateMonthVolume() + { + foreach (var (order, volume) in new Dictionary(_volumeByOrder)) + { + // Tier only changed by the filled volume. + if (order.Status == OrderStatus.Filled) + { + _monthlyTradeVolume[order.SecurityType] += volume; + // Remove the processed order. + _volumeByOrder.Remove(order); + } + } + } + /// /// Reprocess the rate schedule based on the current traded volume in various assets. /// @@ -117,10 +132,13 @@ public override OrderFee GetOrderFee(OrderFeeParameters parameters) var order = parameters.Order; var security = parameters.Security; + // Update the monthly volume with filled quantity. + UpdateMonthVolume(); // Reset monthly trade value tracker when month rollover. if (_lastOrderTime.Month != order.Time.Month && _lastOrderTime != DateTime.MinValue) { _monthlyTradeVolume = _monthlyTradeVolume.ToDictionary(kvp => kvp.Key, _ => 0m); + _volumeByOrder.Clear(); } // Reprocess the rate schedule based on the current traded volume in various assets. ReprocessRateSchedule(_monthlyTradeVolume[SecurityType.Equity], _monthlyTradeVolume[SecurityType.Future], _monthlyTradeVolume[SecurityType.Forex], @@ -147,7 +165,8 @@ public override OrderFee GetOrderFee(OrderFeeParameters parameters) { case SecurityType.Forex: // Update the monthly value traded - _monthlyTradeVolume[SecurityType.Forex] += InteractiveBrokersFeeHelper.CalculateForexFee(security, order, _forexCommissionRate, _forexMinimumOrderFee, out feeResult, out feeCurrency); + var forexTradedValue = InteractiveBrokersFeeHelper.CalculateForexFee(security, order, _forexCommissionRate, _forexMinimumOrderFee, out feeResult, out feeCurrency); + _volumeByOrder[order] = forexTradedValue; break; case SecurityType.Option: @@ -167,14 +186,14 @@ public override OrderFee GetOrderFee(OrderFeeParameters parameters) feeResult += regulatory + transaction + clearing; // Update the monthly value traded - _monthlyTradeVolume[SecurityType.Option] += quantity * orderPrice; + _volumeByOrder[order] = 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; + _volumeByOrder[order] = quantity; break; case SecurityType.Equity: @@ -198,7 +217,7 @@ public override OrderFee GetOrderFee(OrderFeeParameters parameters) feeResult += regulatoryFee + clearingFee + exchangeFee + passThroughFee; // Update the monthly volume shares traded - _monthlyTradeVolume[SecurityType.Equity] += quantity; + _volumeByOrder[order] = quantity; break; case SecurityType.Cfd: @@ -206,8 +225,9 @@ public override OrderFee GetOrderFee(OrderFeeParameters parameters) break; case SecurityType.Crypto: + var cryptoTradedValue = InteractiveBrokersFeeHelper.CalculateCryptoFee(security, order, _cryptoCommissionRate, CryptoMinimumOrderFee, out feeResult, out feeCurrency); // Update the monthly value traded - _monthlyTradeVolume[SecurityType.Crypto] += InteractiveBrokersFeeHelper.CalculateCryptoFee(security, order, _cryptoCommissionRate, CryptoMinimumOrderFee, out feeResult, out feeCurrency); + _volumeByOrder[order] = cryptoTradedValue; break; default: diff --git a/Common/Orders/Fees/InteractiveBrokersTieredFeeModel.py b/Common/Orders/Fees/InteractiveBrokersTieredFeeModel.py index 26cf4e385c4f..7039031cd57d 100644 --- a/Common/Orders/Fees/InteractiveBrokersTieredFeeModel.py +++ b/Common/Orders/Fees/InteractiveBrokersTieredFeeModel.py @@ -29,6 +29,7 @@ class InteractiveBrokersTieredFeeModel(FeeModel): last_order_time = datetime.min # List of Option exchanges susceptible to pay ORF regulatory fee. option_exchanges_orf_fee = [Market.CBOE, Market.USA] + volume_by_order = {} 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: @@ -60,6 +61,14 @@ def __init__(self, monthly_equity_trade_volume: float = 0, monthly_future_trade_ SecurityType.OPTION: monthly_options_trade_amount_in_contracts, SecurityType.CRYPTO: monthly_crypto_trade_amount_in_us_dollars } + + def update_month_volume(self) -> None: + for order, volume in self.volume_by_order.copy().items(): + # Tier only changed by the filled volume. + if order.status == OrderStatus.FILLED: + self.monthly_trade_volume[order.security_type] += volume + # Remove the processed order. + self.volume_by_order.pop(order) 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: @@ -166,9 +175,12 @@ def get_order_fee(self, parameters: OrderFeeParameters) -> OrderFee: order = parameters.order security = parameters.security + # Update the monthly volume with filled quantity. + self.update_month_volume() # 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()} + self.volume_by_order = {} # 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], @@ -190,7 +202,7 @@ def get_order_fee(self, parameters: OrderFeeParameters) -> OrderFee: 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 + self.volume_by_order[order] = 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) @@ -204,13 +216,13 @@ def get_order_fee(self, parameters: OrderFeeParameters) -> OrderFee: fee_result += regulatory + transaction + clearing # Update the monthly value traded - self.monthly_trade_volume[SecurityType.OPTION] += quantity * order_price + self.volume_by_order[order] = 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 + self.volume_by_order[order] = quantity elif security.Type == SecurityType.EQUITY: trade_value = abs(order.get_value(security)) @@ -233,7 +245,7 @@ def get_order_fee(self, parameters: OrderFeeParameters) -> OrderFee: fee_result += regulatory + exchange + clearing + pass_through # Update the monthly value traded - self.monthly_trade_volume[SecurityType.EQUITY] += quantity + self.volume_by_order[order] = quantity elif security.Type == SecurityType.CFD: fee_result, fee_currency = self.calculate_cfd_fee(security, order) @@ -242,7 +254,7 @@ def get_order_fee(self, parameters: OrderFeeParameters) -> OrderFee: 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 + self.volume_by_order[order] = trade_value else: # unsupported security type diff --git a/Launcher/config.json b/Launcher/config.json index a4134d047cef..e5e7ca0a56a7 100644 --- a/Launcher/config.json +++ b/Launcher/config.json @@ -9,7 +9,7 @@ "environment": "backtesting", // "live-paper", "backtesting", "live-interactive", "live-interactive-iqfeed" // algorithm class selector - "algorithm-type-name": "BasicTemplateFrameworkAlgorithm", + "algorithm-type-name": "InteractiveBrokersTieredFeeModelAlgorithm", // Algorithm language selector - options CSharp, Python "algorithm-language": "CSharp", diff --git a/Tests/Common/Orders/Fees/InteractiveBrokersFeeModelTests.cs b/Tests/Common/Orders/Fees/InteractiveBrokersFeeModelTests.cs index 063e6af3b17b..054879c115ef 100644 --- a/Tests/Common/Orders/Fees/InteractiveBrokersFeeModelTests.cs +++ b/Tests/Common/Orders/Fees/InteractiveBrokersFeeModelTests.cs @@ -101,14 +101,18 @@ public void USAFutureFee(Symbol symbol, decimal expectedFee) 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) + [TestCase("USD", 70000, 1, 0.0001 * 70001)] + [TestCase("USD", 100000, 1, 0.0001 * 100001)] + [TestCase("USD", 70000, -1, 0.0001 * 69999)] + [TestCase("USD", 100000, -1, 0.0001 * 99999)] + [TestCase("USD", 100, 1, 1)] // The calculated fee will be under 1, but the minimum fee is 1 USD + [TestCase("JPY", 3000000, 1, 0.0001 * 3000001)] + [TestCase("JPY", 3000000, -1, 0.0001 * 2999999)] + [TestCase("JPY", 10000, 1, 40)]// The calculated fee will be under 40, but the minimum fee is 40 JPY + [TestCase("HKD", 600000, 1, 0.0001 * 600001)] + [TestCase("HKD", 600000, -1, 0.0001 * 599999)] + [TestCase("HKD", 2000, 1, 10)]// The calculated fee will be under 10, but the minimum fee is 10 HKD + public void CalculatesCFDFee(string quoteCurrency, decimal price, decimal quantity, decimal expectedFee) { var security = new Cfd(Symbols.DE10YBEUR, SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork), @@ -119,10 +123,9 @@ public void CalculatesCFDFee(string quoteCurrency, decimal price, decimal expect new SecurityCache()); security.QuoteCurrency.ConversionRate = 1; + security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, price-1m, price+1m)); - security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, price, price)); - - var order = new MarketOrder(security.Symbol, 1, DateTime.UtcNow); + var order = new MarketOrder(security.Symbol, quantity, DateTime.UtcNow); var fee = _feeModel.GetOrderFee(new OrderFeeParameters(security, order)); Assert.AreEqual(quoteCurrency, fee.Value.Currency); diff --git a/Tests/Common/Orders/Fees/InteractiveBrokersTieredFeeModelTests.cs b/Tests/Common/Orders/Fees/InteractiveBrokersTieredFeeModelTests.cs index 9dae800f5bb2..621fbe18b4cf 100644 --- a/Tests/Common/Orders/Fees/InteractiveBrokersTieredFeeModelTests.cs +++ b/Tests/Common/Orders/Fees/InteractiveBrokersTieredFeeModelTests.cs @@ -179,14 +179,18 @@ public void USAFutureFeeByTier(int tier, Symbol symbol, decimal expectedFee) 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) + [TestCase("USD", 70000, 1, 0.0001 * 70001)] + [TestCase("USD", 100000, 1, 0.0001 * 100001)] + [TestCase("USD", 70000, -1, 0.0001 * 69999)] + [TestCase("USD", 100000, -1, 0.0001 * 99999)] + [TestCase("USD", 100, 1, 1)] // The calculated fee will be under 1, but the minimum fee is 1 USD + [TestCase("JPY", 3000000, 1, 0.0001 * 3000001)] + [TestCase("JPY", 3000000, -1, 0.0001 * 2999999)] + [TestCase("JPY", 10000, 1, 40)]// The calculated fee will be under 40, but the minimum fee is 40 JPY + [TestCase("HKD", 600000, 1, 0.0001 * 600001)] + [TestCase("HKD", 600000, -1, 0.0001 * 599999)] + [TestCase("HKD", 2000, 1, 10)]// The calculated fee will be under 10, but the minimum fee is 10 HKD + public void CalculatesCFDFee(string quoteCurrency, decimal price, decimal quantity, decimal expectedFee) { var security = new Cfd(Symbols.DE10YBEUR, SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork), @@ -197,10 +201,9 @@ public void CalculatesCFDFee(string quoteCurrency, decimal price, decimal expect new SecurityCache()); security.QuoteCurrency.ConversionRate = 1; + security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, price - 1m, price + 1m)); - security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, price, price)); - - var order = new MarketOrder(security.Symbol, 1, DateTime.UtcNow); + var order = new MarketOrder(security.Symbol, quantity, DateTime.UtcNow); var model = GetFeeModel(); var fee = model.GetOrderFee(new OrderFeeParameters(security, order)); @@ -476,59 +479,69 @@ public void MonthlyRollingTierChangeTest() // Tier 1 var feeModel = GetFeeModel(); + var order = new MarketOrder(security.Symbol, 300000, DateTime.UtcNow); var fee = feeModel.GetOrderFee( new OrderFeeParameters( security, - new MarketOrder(security.Symbol, 300000, DateTime.UtcNow) + order ) ); Assert.AreEqual(Currencies.USD, fee.Value.Currency); Assert.AreEqual(91958.988m, fee.Value.Amount); + order.Status = OrderStatus.Filled; // Tier 2 + order = new MarketOrder(security.Symbol, 3000000 - 300000, DateTime.UtcNow); fee = feeModel.GetOrderFee( new OrderFeeParameters( security, - new MarketOrder(security.Symbol, 3000000 - 300000, DateTime.UtcNow) + order ) ); Assert.AreEqual(Currencies.USD, fee.Value.Currency); Assert.AreEqual(827630.892m, fee.Value.Amount); + order.Status = OrderStatus.Filled; // Tier 3 + order = new MarketOrder(security.Symbol, 20000000 - 3000000, DateTime.UtcNow); fee = feeModel.GetOrderFee( new OrderFeeParameters( security, - new MarketOrder(security.Symbol, 20000000 - 3000000, DateTime.UtcNow) + order ) ); Assert.AreEqual(Currencies.USD, fee.Value.Currency); Assert.AreEqual(5185484.3m, fee.Value.Amount); + order.Status = OrderStatus.Filled; // Tier 4 + order = new MarketOrder(security.Symbol, 100000000 - 20000000, DateTime.UtcNow); fee = feeModel.GetOrderFee( new OrderFeeParameters( security, - new MarketOrder(security.Symbol, 100000000 - 20000000, DateTime.UtcNow) + order ) ); Assert.AreEqual(Currencies.USD, fee.Value.Currency); Assert.AreEqual(24362248.3, fee.Value.Amount); + order.Status = OrderStatus.Filled; // Tier 5 + order = new MarketOrder(security.Symbol, 300000, DateTime.UtcNow); fee = feeModel.GetOrderFee( new OrderFeeParameters( security, - new MarketOrder(security.Symbol, 300000, DateTime.UtcNow) + order ) ); Assert.AreEqual(Currencies.USD, fee.Value.Currency); Assert.AreEqual(91208.568, fee.Value.Amount); + order.Status = OrderStatus.Filled; // Reset to tier 1 on next month fee = feeModel.GetOrderFee(