Skip to content

fix: run the option holdings sync in the brokerage behind an opt-out flag - #10030

Closed
Romazes wants to merge 2 commits into
QuantConnect:masterfrom
Romazes:bug-option-holdings-sync-in-brokerage
Closed

Romazes wants to merge 2 commits into
QuantConnect:masterfrom
Romazes:bug-option-holdings-sync-in-brokerage

Conversation

@Romazes

@Romazes Romazes commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Lean #9937 added a daily option holdings sync. Once a day, with the cash sync, Lean reads GetAccountHoldings() and books an exercise or an assignment for every option the algorithm holds and the brokerage list leaves out. A brokerage that reports every option position change itself gains nothing from that read, and an empty read books positions that are still open.

Interactive Brokers is such a brokerage: it raises OptionNotification from every portfolio row. Its GetAccountHoldings() is also empty after any reconnect (QuantConnect/Lean.Brokerages.InteractiveBrokers#289), so the sync after the nightly gateway restart booked a short SPXW put as expired while IB still held it. This happened on two sessions of one real-money deployment:

12:45:00 BrokerageTransactionHandler.SyncOptionHoldings(): syncing option holding: Symbol: SPXW  261102P07290000, Holdings: -1, Position: 0
12:45:00 LiveTradingResultHandler.OrderEvent(): Time: 11/02/2026 12:45:00 OrderID: 1 EventID: 1 Symbol: SPXW  261102P07290000 Status: Filled Quantity: 1 FillQuantity: 1 FillPrice: 0 Message: OTM. Underlying: 7580.07. Profit: +233.269700 IsAssignment: False BrokerId:
12:45:00 BrokerageTransactionHandler.SyncOptionHoldings(): Brokerage option holdings count 0. Exit

The change follows the cash sync pattern:

  • The sync moves from BrokerageTransactionHandler into Brokerage.PerformOptionHoldingsSync(IAlgorithm algorithm, DateTime currentTimeUtc), a public virtual bool like PerformCashSync, called from PerformCashSync once a day. The code is the same; a contract the brokerage no longer lists is raised as an OptionNotification with position 0, the event the handler already turns into an exercise or an assignment. It returns false only when the holdings read throws; PerformCashSync ignores the result, so a failed read is retried the next day and never counts as a failed cash sync, as before.
  • A brokerage that reports every option position change itself overrides the method and returns true. The HoldingsSyncEnabled flag from fix: let a brokerage opt out of the daily option holdings sync #10028 is removed; IBrokerage does not change.
  • Nobody overrides PerformCashSync, in Lean or in the brokerage plugins, so every brokerage keeps the sync as today. The IB plugin overrides PerformOptionHoldingsSync once a NuGet with this change is out.

Related PR(s)

#9937, #10028 (merged: the first shape, a flag read by the handler)

Related Issue

QuantConnect/Lean.Brokerages.InteractiveBrokers#289

Motivation and Context

On IB the sync only did harm. The algorithm traded as if the put were gone: IB rejected the next short put for margin, and the ETF sells at the close were sized without the put. The plugins that raise no option events (Schwab, Alpaca, Tastytrade, Tradier, TradeStation, WeBull) keep the sync.

Requires Documentation Change

No.

How Has This Been Tested?

BrokerageTransactionHandlerTests.ProcessSynchronousEventsSkipsTheOptionHoldingsSyncWhenTheBrokerageOverridesIt: a short SPY call the brokerage does not list. With the base method the sync books the assignment (1 ticket, option 0, underlying -600). With the test brokerage overriding the method away nothing is booked (0 tickets, option -7, underlying 100). The three sync tests from #9937 are unchanged; they drive ProcessSynchronousEvents(), which now reaches the sync through the base PerformCashSync.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • Refactor (non-breaking change which improves implementation)
  • Performance (non-breaking change which improves performance. Please add associated performance test and results)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Non-functional change (xml comments/documentation/etc)

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>

🤖 Generated with Claude Code

@Romazes Romazes self-assigned this Nov 4, 2026
…flag

- the opt-out has to be a protected virtual on the base brokerage, which the transaction handler cannot read; the sync moves next to the cash sync, where the brokerage already receives the algorithm once a day, and raises the option notification the handler already turns into an exercise or an assignment
- IB reports every option position change from its portfolio stream and its holdings read is empty after a reconnect, so it sets the flag to false once a NuGet with it ships; every other brokerage keeps the sync as today
- a protected flag next to the sync was one switch too many; a brokerage that reports every option position change itself now overrides PerformOptionHoldingsSync and returns true, the same shape as PerformCashSync
- PerformCashSync ignores the result, so a failed holdings read is retried the next day and never counts as a failed cash sync, as before
@Romazes Romazes closed this Nov 4, 2026
@Romazes
Romazes deleted the bug-option-holdings-sync-in-brokerage branch November 4, 2026 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant