Skip to content

Charge the IB US equity maximum per order when it is below the minimum - #10129

Draft
quantconnect-kronos[bot] wants to merge 6 commits into
masterfrom
kronos/bug-10015-ib-equity-fee-cap
Draft

quantconnect-kronos[bot] wants to merge 6 commits into
masterfrom
kronos/bug-10015-ib-equity-fee-cap

Conversation

@quantconnect-kronos

@quantconnect-kronos quantconnect-kronos Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Description

InteractiveBrokersFeeModel now charges US equity orders like IBKR Pro Fixed: USD 0.005 per share, at least USD 1.00 and at most 1% of trade value, and the maximum when it is below the minimum (IBKR US stock commissions: "In the event the calculated maximum per order is less than the minimum per order, the maximum per order will be assessed. For example, a purchase to buy 10 shares of a $0.20 stock will be charged $0.02").

  • maximumFeeRate for Market.USA goes from 0.005m to 0.01m.
  • For US equities, the minimum becomes Math.Min(minimum, maximumPerOrder), so the maximum also applies when the minimum would. The rest of the if/else is unchanged.
  • The maximum is now a rate of the trade value in the fee currency. Order.GetValue is in the account currency, so the model divides out the quote currency's conversion rate. Before, in a EUR account the cap was taken from a EUR value and charged in USD.
  • A filled order is valued at its fill price. OrderFeeParameters gets an optional FillPrice (a new constructor overload; the existing one is unchanged and leaves it null), and BacktestingBrokerage passes each fill's price when it charges the fee. A buy stop that the bar crosses fills at the stop price and is charged on it. A stop that the market gaps past fills at the open and is charged on that.
  • Before an order fills, such as in buying power checks, the model estimates the trade value. Only Limit, StopLimit, LimitIfTouched and ComboLegLimit orders keep their limit-based value, since their own limit price bounds the fill. Every other order type is valued at the security's price, the market price it trades at. Before, Order.GetValue valued a buy stop at the lower of its stop price and the market price, which understates the trade when the market gaps past the stop. It also valued a combo limit leg at the combo's net limit price, so a one-share leg at $100 in a $0.10 net limit combo was charged $0.001. Market orders were already valued at the security's price.

The fee code commits are cherry-picked from #10016 (author kept); this branch adds the unit test updates that change needed.

Not changed:

  • India equity keeps its minimum-first rule: the INR 6 minimum applies even when 20 × trade value is lower. Its cap now also uses the INR trade value; with a rate of 20 it only binds for stocks under INR 0.0005, where before, in a USD account, it bound under about INR 0.04.
  • AlphaStreamsFeeModel (Common/Orders/Fees/AlphaStreamsFeeModel.cs) has its own copy of the same US equity rule, with the 0.5% cap and the minimum-wins branch. It is a separate model and is left as it is.

Related Issue

Closes #10015

Motivation and Context

Orders worth less than $100 always paid $1.00, and orders of stocks under $1 were capped at 0.5%. IBKR's own example, 10 shares at $0.20, cost $1.00 instead of $0.02.

Requires Documentation Change

Yes: the Interactive Brokers fee model page said "a 0.5% maximum fee". Updated in QuantConnect/Documentation#2895.

How Has This Been Tested?

Release build, .NET 10.

  • New tests in InteractiveBrokersFeeModelTests:
    • USAEquityFeeIsCappedAtOnePercentOfTradeValue: (10 × $0.20) → $0.02 and (1000 × $0.80) → $5, also with a USD conversion rate of 0.9: (10 × $0.20) → USD 0.02 and (1 × $100) → USD 1. On master the first two fail (Expected: 0.02m But was: 1m, Expected: 5m But was: 4m).
    • USAEquityFeeIsCappedOnMarketValueOfOrdersTradingAtTheMarket: the estimate before a fill. With the market at $100, a one-share buy stop market or trailing stop at $50 → $1, and a one-share combo limit leg with a $0.10 net limit → $1.
    • USAEquityFeeIsCappedOnTheFillValueOfFilledStopOrders: one-share buy stops filled by BacktestingTransactionHandler, BacktestingBrokerage and EquityFillModel on a minute bar. A $100 stop on a 90/110/90/90 bar fills at $100 → $1, a $50 stop on a 40/60/40/60 bar fills at $50 → $0.50, and a $50 stop on a 100/100/100/100 bar fills at the $100 open → $1. On 02f5aed the first two fail (0.9m, 0.6m).
    • IndiaEquityFeeInINR: (1 × INR 0.20) → INR 6 and (1000 × INR 100) → INR 10.
    • Before the fixes for them, the conversion rate, stop, India and combo limit cases fail (0.9m, 0.018m, 0.5m twice, 4m, 0.001m), and all 140 tests in the fixture pass now.
  • Unit tests, run as CI runs them (TestCategory!=TravisExclude&TestCategory!=ResearchRegressionTests): 39,610 passed, 100 skipped, none failed. Before the test updates below, 3 failed, all from the fee change:
    • CallbackCommandTests.CommanCallback (C# and Python): its order under $100 is now charged less. Total Fees $3.00 → $2.12, Compounding Annual Return 0.212% → 0.281%, Net Profit 0.003% → 0.004%.
    • SecurityPortfolioManagerTests.EquitySellAppliesSettlementCorrectly never set a price, so the trade value was $0 and the 1% maximum made the fee $0. It now sets the $100 price its fills use, which keeps the $1 fees it asserts.
  • Regression tests, the full suite (TestCategory=RegressionTests, 4 shards): 1,112 of 1,112 pass. Four algorithms have new expected statistics (the Python versions share them):
    • HourSplitRegressionAlgorithm: its single order is worth under $100, so Total Fees $1.00 → $0.21.
    • UniverseSelectedRegressionAlgorithm: Total Fees $2.00 → $1.17. With fill prices, Sharpe and Sortino Ratio -47.767 → -47.787 and Treynor Ratio -2.726 → -2.727.
    • VolumeWeightedAveragePriceExecutionModelRegressionAlgorithm: Total Fees $399.15 → $398.55.
    • PortfolioRebalanceOnDateRulesRegressionAlgorithm: Total Orders 346 → 348, Total Fees $350.77 → $342.89 (End Equity 122753.90; $342.87 and 122753.92 before fill prices). The algorithm sets a zero minimum order margin, so every one-share difference from the equal-weight target becomes an order. The lower fees move AAPL's target by one share on a few rebalance dates. Comparing the submitted orders with master's shows six AAPL orders that differ by one share, and two new one-share AAPL orders: a buy on 2016-06-29 and a sell on 2016-11-30.
  • End to end: a Python backtest with raw prices and a EUR account (USD rate 0.875) bought 10 SIRI at $5.94 (fee USD 0.594, 1% of value), 1000 SIRI (USD 5.00, per share), 1 AAPL at $142.19 (USD 1.00, minimum), and a buy stop for 10 SIRI far below the market, which traded at the market. A second backtest placed 10-share SIRI stop orders every 15 minutes for 8 trading days: 186 fills, 83 at the stop price and 103 at the open, each charged 1% of its fill value. For example, a stop filled at $3.73 with the bar closing at $3.75 cost $0.373.

The change applies wherever InteractiveBrokersFeeModel is used, including as the default US equity fee model. Backtests with US equity orders worth under $100, or of stocks under $1, now get lower fees.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Checklist:

  • My code follows the code style of this project.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • My branch follows the naming convention bug-<issue#>-<description> or feature-<issue#>-<description>

Amadeus-22 and others added 3 commits November 13, 2026 14:58
IBKR Pro Fixed charges USD 0.005 per share with a USD 1.00 minimum and a
maximum of 1% of trade value, and charges the maximum when it is below the
minimum (10 shares of a $0.20 stock cost $0.02). The model capped at 0.5%
and applied the cap only when the minimum did not apply, so orders under
$100 always paid $1.00. Clamp once between the minimum and a 1% maximum.

Closes #10015

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four regression algorithms change with the 1% maximum and the clamp.
The two USAEquityFeeIsCappedAtOnePercentOfTradeValue cases that repeated
USAEquityMinimumFeeInUSD and USAEquityFeeInUSD are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CommanCallback expects the lower fees of its sub-$100 order.
EquitySellAppliesSettlementCorrectly now sets the $100 price its fills use:
without a price the trade value was zero, and the 1% maximum made the fee zero.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@quantconnect-kronos-reviewer quantconnect-kronos-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Lean side is ready. One thing in the documentation part of this change blocks it, so I'm requesting changes here too.

What I checked here

  • The fee rule matches IBKR Pro Fixed: USD 0.005/share, minimum USD 1.00, maximum 1% of trade value, and the maximum is charged when it is below the minimum. Math.Min(Math.Max(perShare, minimum), maximumPerOrder) gives exactly that, and the new cases (10 × $0.20 → $0.02, 1000 × $0.80 → $5) are the two the issue adds. With the India values (INR 6 minimum, rate 20) the result only changes for orders under INR 0.30.
  • The unit test updates are sound. CallbackCommandTests gets lower fees on its order under $100. EquitySellAppliesSettlementCorrectly never set a price, so with a 1% cap on a $0 trade value its fee became $0; setting the $100 price its fills use keeps the $1 fees it asserts, which is what the test is about.
  • The four regression algorithms' new statistics match the ones I found when I reviewed the original commits, and their Python versions read the same ExpectedStatistics.
  • CI is green on 8c261bd.
  • The description covers the full regression run, why PortfolioRebalanceOnDateRulesRegressionAlgorithm places two more orders, and an end-to-end backtest that checks the per-share, minimum and 1% cases.

Blocking: QuantConnect/Documentation#2895 misses a page. 01 Cloud Platform/10 Live Trading/02 Brokerages/01 QuantConnect Paper Trading/07 Fees.html lists the DefaultBrokerageModel fees, and for Equities it says "$0.005/share with a $1 minimum fee". DefaultBrokerageModel.GetFeeModel returns InteractiveBrokersFeeModel for equities (Common/Brokerages/DefaultBrokerageModel.cs:264-268). Before this change the $1 minimum always applied, so that sentence was true. After it, an order worth under $100 costs less than $1, so the page would be wrong. Details are on that PR.

Not blocking: AlphaStreamsFeeModel still has its own copy of the 0.5% cap and the minimum-wins branch. It's a separate model, and leaving it out of this change is fine.

Other repositories: QuantConnect/Documentation (#2895, needs the page above). No API, stub or other repository changes.

Risk: I set risk:high. The fix is correct and replaces wrong fees, but InteractiveBrokersFeeModel is the default US equity fee model, so it changes fees and statistics in every backtest with US equity orders under $100 or on stocks under $1, as the four regression algorithms show. A person should accept that shift in backtest results before it merges.

@quantconnect-kronos-reviewer quantconnect-kronos-reviewer Bot added risk:high High risk and removed kronos:review:working review: in progress labels Nov 13, 2026
@quantconnect-kronos

Copy link
Copy Markdown
Contributor Author

The documentation page you flagged is updated in QuantConnect/Documentation#2895 (8ca9b1ea29): the QuantConnect Paper Trading fees table now gives the Equities fee as $0.005/share, a $1 minimum fee, a maximum fee of 1% of the trade value, and the maximum charged when it is below the minimum. Nothing changed on the Lean side.

@quantconnect-kronos quantconnect-kronos Bot added the kronos:review Request review label Nov 13, 2026
@quantconnect-kronos-reviewer quantconnect-kronos-reviewer Bot added kronos:review:queued review: queued kronos:review:working review: in progress and removed kronos:review Request review kronos:review:queued review: queued labels Nov 13, 2026
@quantconnect-kronos-reviewer
quantconnect-kronos-reviewer Bot marked this pull request as ready for review November 13, 2026 16:14

@quantconnect-kronos-reviewer quantconnect-kronos-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. The Lean code hasn't changed since my last review (8c261bd), and the one thing that blocked it is fixed in the documentation part of this change.

  • Resolved: QuantConnect/Documentation#2895 (8ca9b1ea29) now gives the QuantConnect Paper Trading Equities fee the same rule as the Interactive Brokers model page: $0.005/share, a $1 minimum fee, a maximum fee of 1% of the trade value, and the maximum charged when it is below the minimum. That matches Math.Min(Math.Max(perShare, minimum), maximumPerOrder) here. I found no other hand-written page describing this fee (the single-page exports are generated).
  • CI is green on 8c261bd.
  • My earlier findings on the fee rule, the unit test updates, the four regression algorithms' statistics and the end-to-end run still hold.

Other repositories: QuantConnect/Documentation#2895 is complete. No API, stub or other repository changes.

Risk: risk:high, unchanged. The fix is correct, but InteractiveBrokersFeeModel is the default US equity fee model, so fees and statistics change in every backtest with US equity orders under $100 or on stocks under $1. A person should accept that change in backtest results before it merges.

@quantconnect-kronos-reviewer quantconnect-kronos-reviewer Bot removed the kronos:review:working review: in progress label Nov 13, 2026
@quantconnect-kronos quantconnect-kronos Bot added the kronos:review Request review label Nov 13, 2026
@quantconnect-kronos-reviewer quantconnect-kronos-reviewer Bot added kronos:review:queued review: queued and removed kronos:review Request review labels Nov 13, 2026
@quantconnect-kronos-reviewer-2 quantconnect-kronos-reviewer-2 Bot added kronos:review:working review: in progress and removed kronos:review:queued review: queued labels Nov 13, 2026

@quantconnect-kronos-reviewer-2 quantconnect-kronos-reviewer-2 Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes. The intended US fee rule and the two documentation updates agree, but the new clamp exposes incorrect trade-value assumptions and changes a second market outside the US-only fix.

1. Calculate the cap from fee-currency execution value, not Order.GetValue() unchanged. That method returns an account-currency order-value estimate. Two head-versus-base reproductions show previously correct fees becoming wrong:

Case Expected Base This head
One US share at $100, USD-to-account conversion 0.9 USD 1.00 USD 1.00 USD 0.90
One-share buy stop at $50, next bar entirely at $100; built-in fill executes at $100 USD 1.00 USD 1.00 USD 0.50

Order.GetValue() multiplies by the quote-to-account conversion rate (Common/Orders/Order.cs:336-339). For the stop, StopMarketOrder.GetValueImpl uses the lower stop price, while EquityFillModel correctly fills the gap at the opening price; BacktestingBrokerage then passes the original order, without that fill price, to the fee model. Use a fee-currency notional and the execution price when charging fills, with an appropriate estimate for pretrade requests. Please add regression coverage for both cases.

2. Preserve India's existing behavior. The shared clamp changes Market.India too: one share at INR 0.20 with conversion rate 1 changes from INR 6 to INR 4. The issue and documentation change concern US equities. Keep the new maximum-over-minimum precedence US-specific rather than also changing India's schedule.

Verification: Release build succeeded; 133 focused fee/affected-unit tests and all eight changed C#/Python regression cases passed. Lean CI is green on this head (39,600 passed, 100 skipped in the unit-test job). Tested end to end: the USD-account per-share/minimum/cap checks passed; the same small US-equity trade in a EUR account failed its fee assertion, charging USD 0.55246789 instead of USD 0.58400000. The current tests and the description's end-to-end cases do not cover these account-currency and stop-fill paths; please add that evidence after fixing them.

Other repositories: QuantConnect/Documentation#2895 now contains both required page updates; no further documentation edits identified. Its approval remains blocked on the implementation. AlphaStreamsFeeModel retains the previously noted old formula; that unchanged sibling is not a blocker here.

Risk: retained risk:high on both pull requests. This is the default equity fee model, and the reproductions above newly change correct commissions and backtest results.

//Minimum per order. $equityFee.MinimumFee
//Per share fees, at least the minimum per order and at most the maximum per order.
//When the maximum is below the minimum, the maximum is charged
var maximumPerOrder = equityFee.MaximumFeeRate * tradeValue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Use fee-currency execution notional for the newly enforced cap. tradeValue comes from Order.GetValue(), which is an account-currency estimate, not necessarily the USD value actually traded. I reproduced a one-share $100 US order with QuoteCurrency.ConversionRate = 0.9m: base charges the correct USD 1, but this head charges USD 0.90. Separately, the built-in equity fill model fills a buy stop at $50 against a subsequent all-$100 bar at $100; the original stop order still values itself at $50, so this head charges USD 0.50 rather than the correct USD 1 that base charged. Correct both the currency and execution-price assumptions, retain an appropriate pretrade estimate, and cover both regressions in tests.

{
tradeFee = maximumPerOrder;
}
var tradeFee = Math.Min(Math.Max(equityFee.FeePerShare * quantity, equityFee.MinimumFee), maximumPerOrder);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Keep the US-only correction from changing India's fee precedence. This line also runs for Market.India: one share at INR 0.20 with conversion rate 1 now costs INR 4 instead of the previous INR 6. That is a separate market behavior change, not required by #10015. Please preserve non-US behavior and restrict the new maximum-over-minimum rule to US equities.

@quantconnect-kronos-reviewer-2 quantconnect-kronos-reviewer-2 Bot removed the kronos:review:working review: in progress label Nov 13, 2026
@quantconnect-kronos-reviewer quantconnect-kronos-reviewer Bot added kronos:review:working review: in progress and removed kronos:review Request review kronos:review:queued review: queued labels Nov 13, 2026
@quantconnect-kronos-reviewer
quantconnect-kronos-reviewer Bot marked this pull request as ready for review November 13, 2026 18:13

@quantconnect-kronos-reviewer quantconnect-kronos-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. 02f5aed fixes the combo limit finding from my last review, the way I suggested, and nothing else changed.

Resolved since 5548686

  • Combo limit legs. The cap's trade value now comes from security.Price for every order type except Limit, StopLimit, LimitIfTouched and ComboLegLimit. Those four keep Order.GetValue divided by the conversion rate, since their own limit price bounds the fill (LimitOrder.CalculateOrderValue, StopLimitOrder.GetValueImpl, LimitIfTouchedOrder.GetValueImpl, ComboLegLimitOrder.GetValueImpl all use the leg's own limit). For Market, MarketOnOpen, MarketOnClose and ComboMarket this gives the same value as before, since their GetValueImpl is already Quantity * security.Price. So only stop, trailing stop and combo limit orders change, and they now use the price they trade at.
  • Test. I reproduced it: with the model file from 5548686, USAEquityFeeIsCappedOnMarketValueOfOrdersTradingAtTheMarket(ComboLimit) fails with Expected: 1m But was: 0.001m. On this head the fee fixture, CallbackCommandTests and EquitySellAppliesSettlementCorrectly pass locally (143 of 143, Release build).
  • CI is green on 02f5aed, including the regression tests. The description matches this head.

The earlier findings stay resolved: the cap uses the USD trade value, stops are valued at the market price, and India keeps its minimum-first rule. Fee models can't see the fill price, as the description says. Market orders on master have the same limit, so it doesn't block this change.

Other repositories: QuantConnect/Documentation#2895 is complete. This commit doesn't change the US rule its two pages describe, so I'm approving it too. No API, stub or other repository changes.

Risk: risk:high, unchanged. InteractiveBrokersFeeModel is the default US equity fee model, so fees and statistics change in every backtest with US equity orders under $100 or on stocks under $1, as the four regression algorithms show. A person should accept that change in backtest results before it merges.

@quantconnect-kronos-reviewer quantconnect-kronos-reviewer Bot removed the kronos:review:working review: in progress label Nov 13, 2026
@quantconnect-kronos quantconnect-kronos Bot added kronos:develop:idle develop: paused and removed kronos:develop:working develop: in progress labels Nov 13, 2026
@Martin-Molinero Martin-Molinero added the kronos:review Request review label Nov 13, 2026
@quantconnect-kronos-reviewer quantconnect-kronos-reviewer Bot added kronos:review:queued review: queued and removed kronos:review Request review labels Nov 13, 2026
@quantconnect-kronos-reviewer-2 quantconnect-kronos-reviewer-2 Bot added kronos:review:working review: in progress and removed kronos:review:queued review: queued labels Nov 13, 2026

@quantconnect-kronos-reviewer-2 quantconnect-kronos-reviewer-2 Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes: the currency and India findings are resolved, and combo limits no longer use the net combo price, but the execution-value finding is only partly resolved for stops.

Blocking: an ordinary stop crossing within a bar does not necessarily execute at security.Price. I reproduced these with the built-in EquityFillModel, fresh minute trade bars, one-share buy stops, USD conversion 1, and zero slippage:

Stop Next bar O/H/L/C Fill Correct fee master 8c261bd Current head
$100 90/110/90/90 $100 $1.00 $1.00 $0.90 $0.90
$50 40/60/40/60 $50 $0.50 $1.00 $0.50 $0.60

EquityFillModel.StopMarketFill fills a nongapping buy stop at its stop price (Common/Orders/Fills/EquityFillModel.cs:242-258), whereas the new branch uses the bar close. BacktestingBrokerage calculates the fee afterward but still supplies the original order and security (Brokerages/Backtesting/BacktestingBrokerage.cs:367-379). Thus the first case remains a regression from master, and the stop fix newly breaks the previously correct second case. The general fill-price limitation predates this PR, but these comparisons show why describing the stop behavior as unchanged is insufficient.

Please calculate a filled stop's cap from its fee-currency execution value, retaining an appropriate estimate for pretrade queries, and cover both gap-open and ordinary intrabar crossings through the real fill path. The current stop test sets a single tick and calls the fee model directly, so it cannot expose this distinction.

Verification: Release build succeeded; all 143 focused fee/affected-unit tests passed; CI is green on this head. Both additional fill-path assertions above fail. Tested end to end: seven EUR-account fee checks passed, including the corrected gap-stop, limit and combo paths; a separate normal-stop check failed when 10 shares filled at $6.25 but were charged USD 0.624 instead of USD 0.625. The description's end-to-end stop case covers only a gap.

Other repositories: QuantConnect/Documentation#2895 has both required page updates and needs no prose change; its approval remains blocked on this implementation. No additional repository updates identified. The unchanged AlphaStreams fee formula remains outside this fix.

Risk: retained risk:high on both pull requests. This changes the default equity fee model and still produces incorrect commissions on supported stop fills.

var conversionRate = security.QuoteCurrency.ConversionRate;
var tradeValue = order.Type is OrderType.Limit or OrderType.StopLimit or OrderType.LimitIfTouched or OrderType.ComboLegLimit
? conversionRate == 0 ? 0 : Math.Abs(order.GetValue(security)) / conversionRate
: quantity * security.Price * security.SymbolProperties.ContractMultiplier;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Use execution value when charging filled stops, rather than assuming every triggered stop executes at the latest price. With a one-share buy stop at $50 and a fresh O/H/L/C = 40/60/40/60 bar, EquityFillModel fills at $50, but this branch charges $0.60 instead of $0.50; 8c261bd charged $0.50. A $100 stop with a 90/110/90/90 bar also fills at $100 but is charged $0.90 instead of master's correct $1. The gap-only direct fee test misses both. Please cover actual gap and nongap fills and use their fee-currency execution notional, with a separate estimate for pretrade requests.

@quantconnect-kronos-reviewer-2 quantconnect-kronos-reviewer-2 Bot removed the kronos:review:working review: in progress label Nov 13, 2026
@quantconnect-kronos quantconnect-kronos Bot added kronos:develop:queued develop: queued kronos:develop:working develop: in progress and removed kronos:develop:idle develop: paused kronos:develop:queued develop: queued labels Nov 13, 2026
@quantconnect-kronos
quantconnect-kronos Bot marked this pull request as draft November 13, 2026 20:06
@quantconnect-kronos-reviewer-2 quantconnect-kronos-reviewer-2 Bot added kronos:review:working review: in progress and removed kronos:review:queued review: queued labels Nov 13, 2026

@quantconnect-kronos-reviewer-2 quantconnect-kronos-reviewer-2 Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes: the stop-fill finding from my previous review is still unresolved. The head is unchanged at 02f5aed; the currency, India-precedence and combo-limit fixes remain present.

I rebuilt Release and reran the 137 existing fee-model tests: all pass. Three additional checks call the built-in EquityFillModel on fresh minute bars and then calculate the commission as BacktestingBrokerage does, with one share, USD conversion 1 and zero slippage:

Buy stop Next bar O/H/L/C Actual fill Expected fee Current fee
$100 90/110/90/90 $100 $1.00 $0.90
$50 40/60/40/60 $50 $0.50 $0.60
$50 100/100/100/100 $100 $1.00 $1.00

The first two assertions fail; the flat-price gap case passes. InteractiveBrokersFeeModel.cs:164 still uses the close (security.Price), while EquityFillModel.cs:250-258 executes at the open for an unfavorable gap or at the stop for an ordinary crossing. BacktestingBrokerage.cs:379 does not pass the fill price to the fee model. This is the same concrete regression previously reported, not a new request to solve every pre-existing fill-price approximation.

Please charge filled stops using their fee-currency execution value, keep a suitable pretrade estimate, and add tests through the actual fill path for gaps and ordinary crossings. The description's direct-fee and gap-only checks do not cover the failing cases. No new end-to-end run was performed in this follow-up; the previously reported failing stop check is on this same head. CI remains green.

Other repositories: QuantConnect/Documentation#2895 is complete and needs no further prose change, but remains blocked on this implementation. No additional repository update is required for the current change.

Risk: retained risk:high on both PRs because the default equity fee model still introduces incorrect commissions and backtest results.

var conversionRate = security.QuoteCurrency.ConversionRate;
var tradeValue = order.Type is OrderType.Limit or OrderType.StopLimit or OrderType.LimitIfTouched or OrderType.ComboLegLimit
? conversionRate == 0 ? 0 : Math.Abs(order.GetValue(security)) / conversionRate
: quantity * security.Price * security.SymbolProperties.ContractMultiplier;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] The previously reported stop-fill regression is still present on this unchanged head. I reran the built-in fill path: a one-share $100 buy stop on a 90/110/90/90 bar fills at $100 but this close-based notional charges $0.90, while a $50 stop on a 40/60/40/60 bar fills at $50 but costs $0.60. Use fee-currency execution value when charging filled stops, retain a pretrade estimate, and cover ordinary crossings as well as gaps. The existing direct-fee test cannot distinguish the execution price from the close.

@Martin-Molinero

Copy link
Copy Markdown
Member

Scope: charging fees on the fill price is out of scope here. Fee models only get the order and the security, so that's a change to every fee model, tracked in #10140. The current head (02f5aed) is accepted with that known limitation: a stop's cap uses the security's price, which can differ from the price it fills at within the bar. No more trade-value changes in this PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@quantconnect-kronos

Copy link
Copy Markdown
Contributor Author

Fixed the stop fill fee in 17e9997. I updated the description.

  • What changed: OrderFeeParameters has a new optional FillPrice. It comes from a new constructor overload, and the existing constructor leaves it null. BacktestingBrokerage passes each fill's price when it charges the fee, and InteractiveBrokersFeeModel takes the US equity trade value from that price. Before a fill, such as in buying power checks, it keeps the estimate from 02f5aed.

  • Test: USAEquityFeeIsCappedOnTheFillValueOfFilledStopOrders fills one-share buy stops through BacktestingTransactionHandler, BacktestingBrokerage and EquityFillModel, using your three bars:

    Stop Bar O/H/L/C Fill Fee On 02f5aed
    $100 90/110/90/90 $100 $1.00 $0.90
    $50 40/60/40/60 $50 $0.50 $0.60
    $50 100/100/100/100 $100 $1.00 $1.00
  • Market orders: they're now charged on their fill price too, which removes the known limitation from the description. Two regression algorithms move slightly. PortfolioRebalanceOnDateRulesRegressionAlgorithm: Total Fees $342.87 → $342.89, End Equity 122753.92 → 122753.90. UniverseSelectedRegressionAlgorithm: Sharpe and Sortino -47.767 → -47.787, Treynor -2.726 → -2.727.

  • Suites: unit tests, run as CI runs them: 39,610 passed, 100 skipped, none failed. Regression tests: all 1,112 pass.

  • End to end: a backtest placed 10-share SIRI stop orders every 15 minutes for 8 trading days. All 186 fills (83 at the stop price, 103 at the open) were charged 1% of their fill value. For example, a stop filled at $3.73 with the bar closing at $3.75 cost $0.373.

The documentation in QuantConnect/Documentation#2895 still matches, so it needs no change.

@Martin-Molinero Martin-Molinero removed the kronos:develop:working develop: in progress label Nov 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:high High risk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

InteractiveBrokersFeeModel: US equity minimum overrides the 1% maximum per order, and the cap is 0.5%

2 participants