Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
74c8aa3
fa-v2: define implementation invariants
codex Jul 28, 2026
4e881fa
fa-v2: port account service contracts
codex Jul 28, 2026
6624dc3
fa-v2: verify Tier 1 account snapshot wiring
codex Jul 28, 2026
7622e6c
fa-v2: checkpoint Tier 2 group mutation
codex Jul 28, 2026
e7539d1
fa-v2: demonstrate snapshot reconciliation
codex Jul 28, 2026
16121d3
Remove orphaned configured snapshot refresh API
codex Jul 28, 2026
8833f16
Make FA demo reconciliation causally fresh
codex Jul 29, 2026
b10cd34
Document brokerage-defined account ID casing
codex Jul 29, 2026
2408c7b
Remove LEAN production source inspection test
codex Jul 29, 2026
3b0d485
Gate account mutations until algorithm lock
codex Jul 29, 2026
d40d75c
Clarify configured snapshot discovery scope
codex Jul 29, 2026
1a09f7b
Demonstrate alias-driven FA group movement
codex Jul 29, 2026
01911c6
Schedule algorithm-owned FA snapshot refreshes
codex Jul 29, 2026
a9b1e53
Document lot-aligned FA allocation totals
codex Jul 29, 2026
3f742af
Validate FA sample allocation by group method
codex Jul 29, 2026
0fc61aa
Fix FA demo order reconciliation state
codex Jul 29, 2026
0bee75e
Serialize FA sample refresh ownership
codex Jul 29, 2026
dc7783f
Clarify FA demo saved-method requirements
codex Jul 29, 2026
59396d9
Decouple FA sample refreshes from live data
codex Jul 29, 2026
7bcca36
Fix FA sample state machines
codex Jul 30, 2026
6bcfb1c
Harden brokerage account mutation gating
codex Jul 30, 2026
654c672
Correct FA sample terminal verification
codex Jul 30, 2026
b654179
Keep FA engine wiring within budget
codex Jul 30, 2026
79191e0
Preserve FA allocations during membership repair
codex Jul 30, 2026
3d4a1ad
Refresh before retrying FA group assignment
codex Jul 30, 2026
9884a4c
Document unified exact FA percentage semantics
codex Jul 30, 2026
fd49ad5
Harden FA lifecycle and reconciliation
codex Jul 30, 2026
725b42e
Restore legacy FA demos and add unified samples
codex Jul 30, 2026
20c6921
Complete FA engine and sample lifecycle corrections
codex Jul 30, 2026
ab3f4f1
Clarify unified FA allocation method guidance
codex Jul 30, 2026
e7cf4cc
Remove FA mutation synchronizer decorator
codex Jul 30, 2026
48efa08
Document FA mutation teardown acceptance
codex Jul 30, 2026
acdb366
Align PctChange guidance and harden FA tests
codex Aug 5, 2026
218545c
Merge remote-tracking branch 'origin/master' into fa-v2
codex Aug 5, 2026
102ce5b
Harden FA public contracts and lifecycle wiring
codex Aug 6, 2026
89fd167
Clarify exact IB percentage order properties
codex Aug 6, 2026
93ab146
Simplify and harden FA assignment samples
codex Aug 6, 2026
bb860bc
Simplify and harden unified FA samples
codex Aug 6, 2026
755bf64
Disable unified FA retry after reconciled position changes
codex Aug 6, 2026
612548f
Require 100 percent for the first FA group member
codex Aug 6, 2026
e1490a3
Document exact FA percentage property reuse
codex Aug 6, 2026
42d3290
Merge remote-tracking branch 'upstream/master' into fa-v2
codex Aug 6, 2026
7c2d7da
Harden Financial Advisor sample reconciliation
codex Aug 6, 2026
4a7807a
Merge remote-tracking branch 'upstream/master' into fa-v2
codex Aug 6, 2026
3565f18
Clarify Financial Advisor configuration comments
codex Aug 8, 2026
d4f61b0
Clarify unified FA deployment prerequisites
codex Aug 9, 2026
1b0806b
Merge remote-tracking branch 'origin/master' into fa-v2
codex Aug 11, 2026
7c9ac49
Finalize Financial Advisor API and remove test-only samples
codex Aug 12, 2026
fb29fe9
Merge remote-tracking branch 'origin/master' into fa-v2
codex Aug 12, 2026
56a1d46
Block FA mutations during warm-up
codex Sep 8, 2026
52c9981
Merge remote-tracking branch 'origin/master' into feature-9707-fav2
codex Sep 8, 2026
cce1b06
Merge remote-tracking branch 'upstream/master' into feature-9707-fav2
codex Sep 11, 2026
855bcf3
Merge remote-tracking branch 'upstream/master' into feature-9707-fav2
codex Sep 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Algorithm/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
Expand All @@ -7,11 +8,13 @@
[assembly: AssemblyTitle("QuantConnect.Algorithm")]
[assembly: AssemblyProduct("QuantConnect.Algorithm")]
[assembly: AssemblyCulture("")]
[assembly: InternalsVisibleTo("QuantConnect.Lean.Engine")]
[assembly: InternalsVisibleTo("QuantConnect.Tests")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("5396be58-69fe-437e-8dd1-92c2aa7e9f14")]
[assembly: Guid("5396be58-69fe-437e-8dd1-92c2aa7e9f14")]
455 changes: 455 additions & 0 deletions Algorithm/QCAlgorithm.BrokerageAccounts.cs

Large diffs are not rendered by default.

86 changes: 86 additions & 0 deletions Algorithm/QCAlgorithm.Python.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,92 @@ public partial class QCAlgorithm
{
private readonly Dictionary<IntPtr, PythonIndicator> _pythonIndicators = new Dictionary<IntPtr, PythonIndicator>();

/// <summary>
/// Requests an asynchronous replacement of an existing account group's allocation vector from a Python
/// dictionary using the version tokens from the supplied observed snapshot.
/// </summary>
/// <remarks>
/// Account-group mutations are available only after algorithm initialization completes and warm-up finishes.
/// Account-identifier case handling is brokerage-defined. A provider may reject identifiers that differ only
/// by case as duplicates.
/// A request issued during or after <see cref="OnEndOfAlgorithm"/> may be accepted but is not
/// guaranteed to reach the broker or publish a result. Algorithms must not request configuration
/// mutations during teardown.
/// </remarks>
/// <param name="groupName">Existing managed account group to update.</param>
/// <param name="accountAllocationValues">
/// Python dictionary containing the complete allocation vector, keyed by account identifier. Values must be
/// convertible to <see cref="decimal"/>.
/// </param>
/// <param name="observedSnapshot">Ready snapshot observed while calculating the requested values.</param>
/// <returns>
/// True when the request was accepted for asynchronous processing while the algorithm is running;
/// otherwise, false.
/// </returns>
[DocumentationAttribute(LiveTrading)]
public bool RequestBrokerageAccountGroupAllocationUpdate(
string groupName,
PyObject accountAllocationValues,
BrokerageAccountSnapshot observedSnapshot)
{
ArgumentNullException.ThrowIfNull(accountAllocationValues);
ArgumentNullException.ThrowIfNull(observedSnapshot);

var allocations = new Dictionary<string, decimal>(StringComparer.Ordinal);
using (Py.GIL())
{
if (!PyDict.IsDictType(accountAllocationValues))
{
throw new ArgumentException(
"Account allocation values must be a Python dictionary.",
nameof(accountAllocationValues));
}

using var iterator = accountAllocationValues.GetIterator();
foreach (PyObject key in iterator)
{
using (key)
{
if (!PyString.IsStringType(key))
{
throw new ArgumentException(
"Account allocation dictionary keys must be strings.",
nameof(accountAllocationValues));
}

var accountId = (string)key.AsManagedObject(typeof(string));
ValidateBrokerageIdentifier(
accountId,
nameof(accountAllocationValues));
using var value = accountAllocationValues.GetItem(key);
decimal allocation;
try
{
allocation = (decimal)value.AsManagedObject(typeof(decimal));
}
catch (Exception exception)
{
throw new ArgumentException(
$"Allocation value for account '{accountId}' must be numeric.",
nameof(accountAllocationValues),
exception);
}
if (!allocations.TryAdd(accountId, allocation))
{
throw new ArgumentException(
$"Allocation account identifier '{accountId}' is duplicated.",
nameof(accountAllocationValues));
}
}
}
}

return RequestBrokerageAccountGroupAllocationUpdate(
groupName,
allocations,
observedSnapshot);
}

/// <summary>
/// PandasConverter for this Algorithm
/// </summary>
Expand Down
19 changes: 18 additions & 1 deletion AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ namespace QuantConnect.AlgorithmFactory.Python.Wrappers
/// <summary>
/// Creates and wraps the algorithm written in python.
/// </summary>
public class AlgorithmPythonWrapper : BasePythonWrapper<IAlgorithm>, IAlgorithm
public class AlgorithmPythonWrapper : BasePythonWrapper<IAlgorithm>, IAlgorithm,
IBrokerageAccountServiceConsumer
{
private readonly dynamic _onData;
private readonly dynamic _onMarginCall;
Expand Down Expand Up @@ -679,6 +680,22 @@ public void OnEndOfTimeStep()
/// </summary>
public bool GetLocked() => _baseAlgorithm.GetLocked();

[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1033:Interface methods should be callable by child types",
Justification = "Engine-only service injection must not be exposed to Python algorithms.")]
void IBrokerageAccountServiceConsumer.SetBrokerageAccountStateProvider(IBrokerageAccountStateProvider provider) =>
((IBrokerageAccountServiceConsumer)_baseAlgorithm).SetBrokerageAccountStateProvider(provider);

[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1033:Interface methods should be callable by child types",
Justification = "Engine-only service injection must not be exposed to Python algorithms.")]
void IBrokerageAccountServiceConsumer.SetBrokerageAccountGroupManager(IBrokerageAccountGroupManager manager) =>
((IBrokerageAccountServiceConsumer)_baseAlgorithm).SetBrokerageAccountGroupManager(manager);

[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1033:Interface methods should be callable by child types",
Justification = "Engine-only service injection must not be exposed to Python algorithms.")]
void IBrokerageAccountServiceConsumer.SetBrokerageAccountGroupAllocationManager(
IBrokerageAccountGroupAllocationManager manager) =>
((IBrokerageAccountServiceConsumer)_baseAlgorithm).SetBrokerageAccountGroupAllocationManager(manager);

/// <summary>
/// Gets a read-only dictionary with all current parameters
/// </summary>
Expand Down
95 changes: 95 additions & 0 deletions Common/Brokerages/BrokerageAccountCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* 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 System.Collections.ObjectModel;
using System.Linq;

namespace QuantConnect.Brokerages
{
internal static class BrokerageAccountCollection
{
public static IReadOnlyList<string> CopyIdentifiers(
IEnumerable<string> identifiers,
string parameterName)
{
var result = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var identifier in identifiers ?? Enumerable.Empty<string>())
{
ValidateIdentifier(identifier, parameterName);
if (!result.Add(identifier))
{
throw new ArgumentException(
$"Identifier '{identifier}' is duplicated.",
parameterName);
}
}
return Array.AsReadOnly(
result.OrderBy(identifier => identifier, StringComparer.OrdinalIgnoreCase).ToArray());
}

public static IReadOnlyDictionary<string, TValue> CopyDictionary<TValue>(
IReadOnlyDictionary<string, TValue> source,
string parameterName,
Func<TValue, string> valueIdentifier = null,
StringComparer comparer = null)
{
var result = new Dictionary<string, TValue>(
comparer ?? StringComparer.OrdinalIgnoreCase);
if (source == null)
{
return new ReadOnlyDictionary<string, TValue>(result);
}

foreach (var pair in source)
{
ValidateIdentifier(pair.Key, parameterName);
var identifier = pair.Key;
if (valueIdentifier != null)
{
var expectedIdentifier = valueIdentifier(pair.Value);
if (string.IsNullOrEmpty(expectedIdentifier) ||
!identifier.Equals(expectedIdentifier, StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException(
$"Dictionary key '{identifier}' does not match its value identifier '{expectedIdentifier}'.",
parameterName);
}
}

if (!result.TryAdd(identifier, pair.Value))
{
throw new ArgumentException(
$"Dictionary identifier '{identifier}' is duplicated.",
parameterName);
}
}

return new ReadOnlyDictionary<string, TValue>(result);
}

public static void ValidateIdentifier(string identifier, string parameterName)
{
if (string.IsNullOrWhiteSpace(identifier) ||
!identifier.Equals(identifier.Trim(), StringComparison.Ordinal))
{
throw new ArgumentException(
"Identifiers must be non-empty and contain no leading or trailing whitespace.",
parameterName);
}
}
}
}
88 changes: 88 additions & 0 deletions Common/Brokerages/BrokerageAccountDirectoryEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* 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.Collections.Generic;
using Newtonsoft.Json;

namespace QuantConnect.Brokerages
{
/// <summary>
/// Immutable directory entry for an account discovered through a multi-account brokerage connection.
/// </summary>
public class BrokerageAccountDirectoryEntry
{
/// <summary>
/// Gets the brokerage account identifier.
/// </summary>
public string AccountId { get; }

/// <summary>
/// Gets the account's relationship to the brokerage connection.
/// </summary>
public BrokerageAccountRelationship Relationship { get; }

/// <summary>
/// Gets every discovered group containing the account.
/// </summary>
public IReadOnlyList<string> GroupNames { get; }

/// <summary>
/// Gets the brokerage-reported account type when account state was collected and the brokerage supplied it;
/// otherwise, an empty string.
/// </summary>
public string AccountType { get; }

/// <summary>
/// Gets the brokerage-provided account-family code, when available.
/// </summary>
public string FamilyCode { get; }

/// <summary>
/// Gets the brokerage-provided account alias, when available.
/// </summary>
public string AccountAlias { get; }

/// <summary>
/// Initializes an immutable brokerage account directory entry.
/// </summary>
/// <param name="accountId">Brokerage account identifier.</param>
/// <param name="relationship">Relationship to the brokerage connection.</param>
/// <param name="groupNames">Discovered groups containing the account.</param>
/// <param name="accountType">
/// Brokerage-reported account type, or an empty string when not collected or not supplied.
/// </param>
/// <param name="familyCode">Brokerage-provided account-family code.</param>
/// <param name="accountAlias">Brokerage-provided account alias.</param>
[JsonConstructor]
public BrokerageAccountDirectoryEntry(
string accountId,
BrokerageAccountRelationship relationship,
IEnumerable<string> groupNames,
string accountType = "",
string familyCode = "",
string accountAlias = "")
{
BrokerageAccountCollection.ValidateIdentifier(accountId, nameof(accountId));
AccountId = accountId;
Relationship = relationship;
GroupNames = BrokerageAccountCollection.CopyIdentifiers(
groupNames,
nameof(groupNames));
AccountType = accountType?.Trim() ?? string.Empty;
FamilyCode = familyCode?.Trim() ?? string.Empty;
AccountAlias = accountAlias?.Trim() ?? string.Empty;
}
}
}
Loading