Skip to content

Create data subscriptions for changed user defined universe members - #9935

Merged
Martin-Molinero merged 3 commits into
masterfrom
kronos/sync-user-defined-member-subscriptions
Oct 29, 2026
Merged

Martin-Molinero merged 3 commits into
masterfrom
kronos/sync-user-defined-member-subscriptions

Conversation

@quantconnect-kronos

@quantconnect-kronos quantconnect-kronos Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adding a new resolution to a security that is already in the user defined universe (AddEquity("SPY", Resolution.Tick) after AddEquity("SPY", Resolution.Minute)), or removing it and re-adding it at another resolution in the same time step, recorded the new configuration on Security.Subscriptions but never created its data subscription: the algorithm kept the old bars and got no ticks.

The member stays selected in both cases, so Universe.PerformSelection returns Unchanged and ApplyUniverseSelection returns early. Even when it didn't, the "find new selections" loop skips existing members, and on the remove and re-add path the removal loop keeps the member, so its replaced minute feed stays.

Changes:

  • UserDefinedUniverse.Add(SubscriptionDataConfig) records a symbol as a changed member when it adds a configuration for a symbol that is already a member. Re-adding a configuration that was pending removal (remove and re-add at the same resolution) just takes it off the pending list, since its subscription is still running. RemoveAndKeepTrack clears the record.
  • The new internal method UserDefinedUniverse.TryTakeChangedMembers returns those members, plus the configurations pending removal that they replaced.
  • ApplyUniverseSelection takes the changed members of a user defined universe before the unchanged check. It removes this universe's request for the replaced configurations, then adds the member's requested subscriptions through the same code new selections use. That code is now in AddSubscriptions, so it isn't duplicated.

Only members whose configurations actually changed are touched. Re-requesting every selected member on each selection would add work to every user defined selection, including selector universes that fire at their resolution, and it would re-create subscriptions that already finished, for example when a security's data ends before the algorithm does.

This builds on #9930 by @Divyansh151005, cherry-picked with authorship kept: its unit tests and regression algorithm are reused as they are. The engine change is replaced by the targeted tracking described above.

Related Issue

Closes #9900

Motivation and Context

Switching a held symbol to tick resolution did nothing and gave no warning, in both backtesting and live trading.

Requires Documentation Change

No.

How Has This Been Tested?

  • UniverseSelectionTests.AddingResolutionToExistingUserDefinedSecurityCreatesDataSubscription and RemoveAndReaddAtNewResolutionInOneTimeStepReplacesDataSubscription (from Create data subscriptions for re-selected user defined members #9930): fail on master, pass with the change.

  • New UniverseSelectionTests.RemoveAndReaddAtSameResolutionInOneTimeStepKeepsDataSubscription: removing and re-adding at the same resolution keeps the existing subscriptions. Passes on master and with the change.

  • UserDefinedUniverseResolutionChangeRegressionAlgorithm (from Create data subscriptions for re-selected user defined members #9930, AIG/BAC 2013-10-07): fails on master with Expected AIG ticks after adding the tick resolution and passes with the change, with 307,906 data points.

  • BacktestingResultHandlerTests.ResolutionSwitchingAlgorithmSamplesNotMisalignedAbsolute is updated. ResolutionSwitchingAlgorithm removes SPY (Daily) at the end of 10/9 and re-adds it at Minute resolution from OnSecuritiesChanged, which is this exact scenario. Before, the minute subscription was never created: the liquidation filled at 10/10 16:00 on the daily bar and the re-buy at 10/11 16:00. Now the minute feed takes over and both fill at 10/10 09:31. The 10/10 - 10/11 performance changes from 0.009923 to 0.020927 and the 10/11 sample from 0.007854 to 0.005907. The benchmark values are unchanged. SamplesNotMisalignedRelative("ResolutionSwitchingAlgorithm", false) still holds: the equity series has no 10/11 8PM sample, and 10/10 - 10/11 still diverges from the benchmark by more than the 0.0005 tolerance.

Full unit test suite (Linux, .NET 10):

dotnet test QuantConnect.Tests.dll --blame-hang-timeout 300seconds --filter "TestCategory!=TravisExclude&TestCategory!=ResearchRegressionTests&TestCategory!=RegressionTests"
Failed: 1, Passed: 39162, Skipped: 97, Total: 39260

The one failure is IsolatorTests.WorksCorrectlyUsingWorker, a 0.1 second time limit test. It timed out under load and passes when re-run (IsolatorTests: 6/6 passed). The regression algorithms are covered by the separate regression check.

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

Divyansh151005 and others added 2 commits October 22, 2026 20:27
Adding a new resolution to a security already in the user defined
universe, or removing and re-adding it at a new resolution in the same
time step, kept the same selected symbols. Universe.PerformSelection then
returned Unchanged and ApplyUniverseSelection skipped existing members, so
the new configurations never reached the data manager and the old feed
kept running.

ApplyUniverseSelection now syncs the selected members of a user defined
universe before the unchanged check: it adds the requested configurations
the data feed does not have yet and removes this universe's request for
the ones it no longer requests. Other universes keep the current path.

Fixes #9900.
The user defined universe now tracks the members that get a new
configuration while they stay selected, a new resolution or a removal and
re-addition in the same time step. ApplyUniverseSelection adds their
requested subscriptions and drops the replaced ones, reusing the add path
of new selections, instead of re-requesting every selected member on each
selection, which would also re-create subscriptions that already finished.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@quantconnect-kronos quantconnect-kronos Bot added the kronos:review Request review label Oct 22, 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 Oct 22, 2026

@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 engine change looks right to me, but CI is red. The failing test still expects the old, buggy behavior, so it needs updating.

Failing check: Build & Test Lean fails on BacktestingResultHandlerTests.ResolutionSwitchingAlgorithmSamplesNotMisalignedAbsolute (Tests/Engine/Results/BacktestingResultHandlerTests.cs:461). The expected daily performance for 10/10 to 10/11 is 0.009923037, but the run gives 0.020927.

I ran ResolutionSwitchingAlgorithm on master and on this branch and compared the logs. It is the scenario from #9900: it removes SPY (Daily) in OnEndOfDay on 10/9, then calls AddEquity("SPY", Resolution.Minute) from OnSecuritiesChanged.

  • master: no minute subscription is ever created. The daily feed keeps running. The liquidation fills at 10/10 16:00 on the daily bar, and the re-buy fills at 10/11 16:00 for $146.0354.
  • this branch: at 10/9 20:00 the daily config is removed and SPY Minute TradeBar/QuoteBar are added. The liquidation and the re-buy both fill at 10/10 09:31 ($144.6693 / $144.652).

So the new number comes from the fix itself, not from a regression. The test's own comment already says "On 2013-10-10, we switch from Daily Resolution to Minute", which never actually happened on master. Please:

  • update the expected performance values, and any other values in that test that change, to what the fixed engine produces;
  • update the comment table and the per-line comments (// 10/9 - 10/10 <- RemoveSecurity liquidation ..., etc.) so they describe the minute feed taking over on 10/10;
  • check SamplesNotMisalignedRelative("ResolutionSwitchingAlgorithm", false) still expresses what is intended. It passes locally, but its premise is the same misalignment.

Testing evidence: the --filter in the description leaves out QuantConnect.Tests.Engine.Results, which is why this was missed. Please run the full QuantConnect.Tests.dll suite, or at least add Engine.Results, and post the result.

Code: no other blocking findings.

  • In UserDefinedUniverse.Add, tracking members whose configs changed and having TryTakeChangedMembers take the replaced pending configs is a narrow, sensible approach. It runs under the existing lock, and the per-selection cost when nothing changed is a type check plus one lock.
  • Extracting AddSubscriptions removes the duplication instead of copying it. Calling AddedSubscriptionRequest again for configs that already exist is harmless, because InternalSubscriptionManager already guards on alreadyInternal.
  • Regression, Research Regression, Syntax, Report Generator and Python venv checks pass. Benchmarks was still running when I checked.

This changes which data subscriptions user algorithms get at runtime when they add a resolution or re-add a symbol, so I'm marking it high risk.

@quantconnect-kronos-reviewer quantconnect-kronos-reviewer Bot added risk:high High risk and removed kronos:review:working review: in progress labels Oct 22, 2026
ResolutionSwitchingAlgorithm removes SPY (Daily) at the end of 10/9 and
re-adds it at Minute resolution. The minute subscription is now created,
so the liquidation and the re-buy fill at the 10/10 open instead of on
the daily bars, changing the 10/10 - 10/11 and 10/11 performance samples.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@quantconnect-kronos

Copy link
Copy Markdown
Contributor Author

Thanks. Pushed c2b5ebf, which updates ResolutionSwitchingAlgorithmSamplesNotMisalignedAbsolute to match the fixed engine.

  • Expected values: the daily performance for 10/10 - 10/11 changes from 0.009923037 to 0.020927 and for 10/11 from 0.007853915 to 0.00590684. The equity performance for 10/10 - 10/11 changes from 0.009923037498293092 to 0.020927004568931092. The benchmark values are unchanged.
  • Comments: I replaced the frame table with the frame the test now produces. The per-line comments now say SPY is removed at the end of 10/9 and re-added at Minute resolution, and that the minute feed takes over on 10/10. I confirmed the fill times with a temporary order event log (not committed): the buy at 10/8 16:00, then the liquidation at 10/10 09:31 (144.6693) and the re-buy at 10/10 09:31 (144.6520).
  • SamplesNotMisalignedRelative("ResolutionSwitchingAlgorithm", false): I left it as is. It still holds on two counts. The daily-resampled equity series has 5 values against 6 performance samples, because there is no 10/11 8PM equity point. And 10/10 - 10/11 still differs from the benchmark by about 0.0012 (0.020927 against 0.022166), more than the 0.0005 tolerance.
  • Full suite: dotnet test QuantConnect.Tests.dll --filter "TestCategory!=TravisExclude&TestCategory!=ResearchRegressionTests&TestCategory!=RegressionTests" gave Failed: 1, Passed: 39162, Skipped: 97. The one failure was IsolatorTests.WorksCorrectlyUsingWorker, which has a 0.1 second time limit. It timed out under the load of the full run and passed on a re-run (IsolatorTests 6/6). I've updated the PR description with these results.

@quantconnect-kronos quantconnect-kronos Bot added the kronos:review Request review label Oct 22, 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 Oct 22, 2026
@quantconnect-kronos-reviewer
quantconnect-kronos-reviewer Bot marked this pull request as ready for review October 22, 2026 21:59

@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.

My earlier request is addressed in c2b5ebf, and Build & Test Lean now passes.

  • ResolutionSwitchingAlgorithmSamplesNotMisalignedAbsolute: the expected 10/10 - 10/11 values (0.020927) and 10/11 values (0.00590684) match what I saw running ResolutionSwitchingAlgorithm on this branch: the liquidation and the re-buy both fill at the 10/10 09:31 open after SPY is re-added at Minute resolution. The benchmark expectations are unchanged. The frame table and per-line comments now describe the minute feed taking over, instead of the old misaligned daily fills.
  • SamplesNotMisalignedRelative("ResolutionSwitchingAlgorithm", false): the reasoning for leaving it unchanged holds up. There's no 10/11 8PM equity point, and 10/10 - 10/11 still differs from the benchmark by more than the tolerance.
  • Testing: the full QuantConnect.Tests.dll run is now in the description. Its only failure is the timing-sensitive IsolatorTests.WorksCorrectlyUsingWorker, which passes on a re-run.
  • Code: the engine and UserDefinedUniverse changes haven't changed since my last review, and I have no further findings on them.

CI: Build & Test, Regression, Research Regression, Syntax, Report Generator and Python venv checks pass. Benchmarks is still running.

I'm keeping this high risk: it changes which data subscriptions user algorithms get at runtime when they add a resolution or re-add a symbol.

@quantconnect-kronos-reviewer quantconnect-kronos-reviewer Bot removed the kronos:review:working review: in progress label Oct 22, 2026
@quantconnect-kronos quantconnect-kronos Bot added the kronos:develop:idle develop: paused label Oct 22, 2026
@Martin-Molinero Martin-Molinero added kronos:review Request review and removed kronos:develop:idle develop: paused labels Oct 23, 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 Oct 23, 2026

@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.

No new commits since my last review at c2b5ebf. Every check now passes, Benchmarks included.

I went back over the engine change and found nothing new:

  • Remove and re-add at a new resolution in one time step: RemoveAndKeepTrack moves the old configs to the pending removal list and clears the changed-member record. The re-added config isn't pending, so the symbol is recorded as changed. TryTakeChangedMembers then hands back the old configs as replaced, and ApplyUniverseSelection drops this universe's request for them before it adds the new ones.
  • Re-add at the same resolution: the config comes off the pending list and the member isn't marked as changed, so its running subscription is kept. RemoveAndReaddAtSameResolutionInOneTimeStepKeepsDataSubscription covers this.
  • Consistency: RemoveMember goes through RemoveAndKeepTrack, which clears the changed-member record. A recorded symbol therefore can't outlive its membership, and replaced configs can't be dropped from the pending list without being removed from the data feed.
  • Cost: when nothing changed, a selection pays for a type check and one lock.

I'm keeping this high risk: it changes which data subscriptions user algorithms get at runtime when they add a resolution or re-add a symbol.

@quantconnect-kronos-reviewer quantconnect-kronos-reviewer Bot removed the kronos:review:working review: in progress label Oct 23, 2026
@Martin-Molinero Martin-Molinero added the kronos:review Request review label Oct 29, 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 Oct 29, 2026

@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.

No new commits since my last review at c2b5ebf, and every check passes.

Master has moved 12 commits ahead since this branch was cut. None of them touch the files this PR changes (UserDefinedUniverse, UniverseSelection, UniverseSelectionTests, BacktestingResultHandlerTests). GitHub reports the PR as mergeable with a clean merge state. The one change on master that affects the new regression algorithm is the sharding in RegressionTests.cs. It splits the discovered cases by index, so UserDefinedUniverseResolutionChangeRegressionAlgorithm is still picked up with no registration needed.

My earlier findings stand, and I have nothing new on the engine change.

I'm keeping this as risk:high because it changes which data subscriptions user algorithms get at runtime when they add a resolution or re-add a symbol.

@quantconnect-kronos-reviewer quantconnect-kronos-reviewer Bot removed the kronos:review:working review: in progress label Oct 29, 2026
@Martin-Molinero
Martin-Molinero merged commit 8af34e3 into master Oct 29, 2026
8 checks passed
@Martin-Molinero
Martin-Molinero deleted the kronos/sync-user-defined-member-subscriptions branch October 29, 2026 13:45
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.

AddEquity with a new resolution on an existing user-defined security (or remove + re-add in one time step) never creates the data subscription

2 participants