diff --git a/03 Writing Algorithms/22 Trading and Orders/09 Order Properties/99 Examples.html b/03 Writing Algorithms/22 Trading and Orders/09 Order Properties/99 Examples.html index 27d1715d36..7efdd57d2c 100644 --- a/03 Writing Algorithms/22 Trading and Orders/09 Order Properties/99 Examples.html +++ b/03 Writing Algorithms/22 Trading and Orders/09 Order Properties/99 Examples.html @@ -107,8 +107,8 @@
The following algorithm forms an equal-weight portfolio at the start of each week with the 10 most liquid US Equities. It demonstrates how order properties can give financial advisors easy control over client funds.
+The following algorithm targets an aggregate equal-weight portfolio of 10 liquid US Equities and routes its orders to an Interactive Brokers Allocation Group saved with the Equal method. SetHoldingsset_holdings sizes each aggregate parent order; IB applies the saved method to its managed accounts. Use account snapshots for per-account state.
public class OrderPropertiesAlgorithm : QCAlgorithm
{
@@ -121,8 +121,7 @@ Example 2: Financial Advisor Managing Client Funds
DefaultOrderProperties = new InteractiveBrokersOrderProperties
{
FaGroup = "TestGroupEQ", // FA group
- FaMethod = "NetLiq", // Allocation by net liquidation
- Account = "FA123456" // FA account
+ FaMethod = "Equal" // Match the group's saved method
};
// Add a universe that selects the 10 most liquid US Equities at the start of each week.
@@ -151,9 +150,8 @@ Example 2: Financial Advisor Managing Client Funds
# Define the order properties to trade with an FA group.
self.default_order_properties = InteractiveBrokersOrderProperties()
- self.default_order_properties.fa_group = "TestGroupEQ" # FA group
- self.default_order_properties.fa_method = "EqualQuantity" # Allocation by net liquidation
- self.default_order_properties.account = "DU123456" # FA account
+ self.default_order_properties.fa_group = "TestGroupEQ" # FA group
+ self.default_order_properties.fa_method = "Equal" # Match the saved method
# Add a universe that selects the 10 most liquid US Equities at the start of each week.
spy = Symbol.create('SPY', SecurityType.EQUITY, Market.USA)
diff --git a/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/05 Account Snapshots.php b/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/05 Account Snapshots.php
new file mode 100644
index 0000000000..12c853c769
--- /dev/null
+++ b/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/05 Account Snapshots.php
@@ -0,0 +1 @@
+
diff --git a/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/06 Group Assignments.php b/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/06 Group Assignments.php
new file mode 100644
index 0000000000..667fbb01dd
--- /dev/null
+++ b/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/06 Group Assignments.php
@@ -0,0 +1 @@
+
diff --git a/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/07 Allocation Updates.php b/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/07 Allocation Updates.php
new file mode 100644
index 0000000000..2c3dc70f43
--- /dev/null
+++ b/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/07 Allocation Updates.php
@@ -0,0 +1 @@
+
diff --git a/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/08 Configuration and Limitations.php b/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/08 Configuration and Limitations.php
new file mode 100644
index 0000000000..2f53fed615
--- /dev/null
+++ b/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/08 Configuration and Limitations.php
@@ -0,0 +1 @@
+
diff --git a/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/09 Examples.php b/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/09 Examples.php
new file mode 100644
index 0000000000..96c70e3b84
--- /dev/null
+++ b/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/09 Examples.php
@@ -0,0 +1 @@
+
diff --git a/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/metadata.json b/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/metadata.json
index 4eca05ad1c..8f6efdd158 100644
--- a/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/metadata.json
+++ b/03 Writing Algorithms/22 Trading and Orders/14 Financial Advisors/metadata.json
@@ -1,9 +1,9 @@
{
"type": "metadata",
"values": {
- "description": "Financial Advisor accounts enable certified professionals to use a single trading algorithm to manage several client accounts.",
- "keywords": "Interactive Brokers integration, Financial Advisor accounts, Financial Advisor group orders, manage client accounts, Account Groups in Trader Workstation, allocation methods, net liquidation value of each account",
- "og:description": "Financial Advisor accounts enable certified professionals to use a single trading algorithm to manage several client accounts.",
+ "description": "Financial Advisor accounts let you monitor managed accounts, route group orders, and manage existing IB Allocation Groups from one algorithm.",
+ "keywords": "Interactive Brokers integration, Financial Advisor accounts, Financial Advisor group orders, brokerage account snapshots, managed account state, Account Groups in Trader Workstation, group assignments, allocation updates, allocation methods, Financial Advisor examples",
+ "og:description": "Financial Advisor accounts let you monitor managed accounts, route group orders, and manage existing IB Allocation Groups from one algorithm.",
"og:title": "Financial Advisors - Documentation QuantConnect.com",
"og:type": "website",
"og:site_name": "Financial Advisors - QuantConnect.com",
diff --git a/03 Writing Algorithms/24 Reality Modeling/05 Brokerages/02 Supported Models/02 Interactive Brokers/18 Financial Advisors.php b/03 Writing Algorithms/24 Reality Modeling/05 Brokerages/02 Supported Models/02 Interactive Brokers/18 Financial Advisors.php
index 84ac584b7c..b63ea75051 100644
--- a/03 Writing Algorithms/24 Reality Modeling/05 Brokerages/02 Supported Models/02 Interactive Brokers/18 Financial Advisors.php
+++ b/03 Writing Algorithms/24 Reality Modeling/05 Brokerages/02 Supported Models/02 Interactive Brokers/18 Financial Advisors.php
@@ -1,5 +1,7 @@
-IB supports FA accounts for Trading Firm and Institution organizations. FA accounts enable certified professionals to use a single trading algorithm to manage several client accounts.
+IB supports FA accounts for Trading Firm or Institution organizations. FA accounts enable certified professionals to use a single trading algorithm to manage several client accounts.
+
+For account snapshots, asynchronous group management, configuration, and operational limitations, see the complete Financial Advisors guide.
diff --git a/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/05 Account Snapshots.php b/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/05 Account Snapshots.php
new file mode 100644
index 0000000000..12c853c769
--- /dev/null
+++ b/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/05 Account Snapshots.php
@@ -0,0 +1 @@
+
diff --git a/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/06 Group Assignments.php b/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/06 Group Assignments.php
new file mode 100644
index 0000000000..667fbb01dd
--- /dev/null
+++ b/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/06 Group Assignments.php
@@ -0,0 +1 @@
+
diff --git a/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/07 Allocation Updates.php b/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/07 Allocation Updates.php
new file mode 100644
index 0000000000..2c3dc70f43
--- /dev/null
+++ b/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/07 Allocation Updates.php
@@ -0,0 +1 @@
+
diff --git a/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/08 Configuration and Limitations.php b/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/08 Configuration and Limitations.php
new file mode 100644
index 0000000000..2f53fed615
--- /dev/null
+++ b/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/08 Configuration and Limitations.php
@@ -0,0 +1 @@
+
diff --git a/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/09 Examples.php b/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/09 Examples.php
new file mode 100644
index 0000000000..96c70e3b84
--- /dev/null
+++ b/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/09 Examples.php
@@ -0,0 +1 @@
+
diff --git a/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/metadata.json b/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/metadata.json
index c8f423e8f2..ea00cda2f6 100644
--- a/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/metadata.json
+++ b/03 Writing Algorithms/40 Live Trading/04 Trading and Orders/14 Financial Advisors/metadata.json
@@ -1,9 +1,9 @@
{
"type": "metadata",
"values": {
- "description": "Financial Advisor accounts enable certified professionals to use a single trading algorithm to manage several client accounts.",
- "keywords": "Interactive Brokers, Financial Advisor group order, Account Groups in Trader Workstation, Allocation Methods, net liquidation value, organization tier",
- "og:description": "Financial Advisor accounts enable certified professionals to use a single trading algorithm to manage several client accounts.",
+ "description": "Financial Advisor accounts let you monitor managed accounts, route group orders, and manage existing IB Allocation Groups from one algorithm.",
+ "keywords": "Interactive Brokers, Financial Advisor group orders, brokerage account snapshots, managed account state, Account Groups in Trader Workstation, group assignments, allocation updates, allocation methods, Financial Advisor examples, organization tier",
+ "og:description": "Financial Advisor accounts let you monitor managed accounts, route group orders, and manage existing IB Allocation Groups from one algorithm.",
"og:title": "Financial Advisors - Documentation QuantConnect.com",
"og:type": "website",
"og:site_name": "Financial Advisors - QuantConnect.com",
diff --git a/Resources/brokerages/interactive-brokers/orders.php b/Resources/brokerages/interactive-brokers/orders.php
index 7197bf837e..d6514aeb77 100644
--- a/Resources/brokerages/interactive-brokers/orders.php
+++ b/Resources/brokerages/interactive-brokers/orders.php
@@ -214,7 +214,7 @@
Order Properties
-= $writingAlgorithms ? "The InteractiveBrokersBrokerageModel supports custom order properties." : "We model custom order properties from the IB API." ?> The following table describes the members of the InteractiveBrokersOrderProperties object that you can set to customize order execution. The table does not include the methods for FA accounts.
+= $writingAlgorithms ? "The InteractiveBrokersBrokerageModel supports custom order properties." : "We model custom order properties from the IB API." ?> The following table describes the general members of the InteractiveBrokersOrderProperties object that you can set to customize order execution. For FA-specific routing and percentage properties, see Group Routing and Allocation Methods.
| Method | +Description | +
|---|---|
RequestBrokerageAccountSnapshotRefresh()request_brokerage_account_snapshot_refresh() |
+ Requests provider-default discovery. For an unfiltered IB deployment, this requests complete discovery. A configured IB group filter keeps the result scoped, so always inspect IsCompleteis_complete. |
+
RequestBrokerageAccountSnapshotRefresh(groupNames, additionalAccountIds = null)request_brokerage_account_snapshot_refresh(group_names, additional_account_ids=None) |
+ Requests financial state for the selected groups and optional managed accounts outside those groups. The snapshot still includes complete discovered topology in AllGroupsall_groups. Additional accounts require at least one explicit group. With an IB group filter, request only that group and don't pass additional accounts; an out-of-scope request isn't accepted. |
+
The scoped overload publishes IsComplete == falseis_complete == False even if the selected groups happen to contain every account. The parameterless overload can also be incomplete when the provider has a configured scope such as an IB group filter.
For both methods, trueTrue means the provider accepted or coalesced the asynchronous request. It doesn't mean collection completed. The provider exposes one snapshot publication stream rather than a result handle for each call. Equivalent requests can coalesce, differing queued scopes can merge, and a differing request can supersede active collection before it publishes. Coordinate refresh requests in one place and verify that the resulting snapshot contains every requested group and account.
Capture the current generation before the call, then poll for a Ready snapshot with a greater generation and the required scope. A fast request can reach a terminal status before your next read, so don't require observing Refreshing. A failure or disconnect can publish Failed or Stale without advancing the generation. Your algorithm must enforce its own observation timeout. Reaching that deadline stops the algorithm's polling policy; it doesn't cancel accepted provider work.
Invalid null scopes, blank or duplicate identifiers, and additional accounts without an explicit group throw synchronously. Handle those input errors separately from a falseFalse return, which means the request wasn't accepted for asynchronous processing.
The following example schedules scoped discovery every minute and complete discovery every third tick. Both the scheduled callback and OnDataon_data poll the result, so processing continues when no market data arrives. It serializes their shared refresh state so only one callback processes a generation. The five-minute observation deadline is illustrative; tune it to the selected scope and deployment.
private readonly object _refreshLock = new();
+private long _refreshGeneration = -1;
+private DateTime _refreshDeadlineUtc;
+private int _refreshTick;
+private bool _refreshExpectedComplete;
+
+public override void Initialize()
+{
+ Schedule.On(
+ DateRules.EveryDay(),
+ TimeRules.Every(TimeSpan.FromMinutes(1)),
+ RefreshSnapshot);
+}
+
+private void RefreshSnapshot()
+{
+ PollSnapshotRefresh();
+ lock (_refreshLock)
+ {
+ if (_refreshGeneration >= 0)
+ {
+ return;
+ }
+
+ var snapshot = BrokerageAccountSnapshot;
+ var complete = ++_refreshTick % 3 == 0;
+ var accepted = complete
+ ? RequestBrokerageAccountSnapshotRefresh()
+ : RequestBrokerageAccountSnapshotRefresh(new[] { "TestGroupEQ" });
+ if (accepted)
+ {
+ _refreshGeneration = snapshot.Generation;
+ _refreshExpectedComplete = complete;
+ _refreshDeadlineUtc = UtcTime.AddMinutes(5);
+ }
+ else
+ {
+ Debug("The FA snapshot refresh wasn't accepted.");
+ }
+ }
+}
+
+public override void OnData(Slice slice)
+{
+ PollSnapshotRefresh();
+}
+
+private void PollSnapshotRefresh()
+{
+ BrokerageAccountSnapshot snapshotToUse = null;
+ string errorMessage = null;
+ lock (_refreshLock)
+ {
+ if (_refreshGeneration < 0)
+ {
+ return;
+ }
+
+ var snapshot = BrokerageAccountSnapshot;
+ if (snapshot.IsReady && snapshot.Generation > _refreshGeneration)
+ {
+ _refreshGeneration = -1;
+ var hasRequestedScope = _refreshExpectedComplete
+ ? snapshot.IsComplete
+ : snapshot.Groups.ContainsKey("TestGroupEQ");
+ if (hasRequestedScope)
+ {
+ snapshotToUse = snapshot;
+ }
+ else
+ {
+ errorMessage = "The FA snapshot doesn't contain the requested scope.";
+ }
+ }
+ else if (snapshot.Status == BrokerageAccountSnapshotStatus.Failed ||
+ snapshot.Status == BrokerageAccountSnapshotStatus.Stale ||
+ snapshot.Status == BrokerageAccountSnapshotStatus.Unavailable)
+ {
+ _refreshGeneration = -1;
+ errorMessage = $"The FA snapshot refresh ended with status {snapshot.Status}.";
+ }
+ else if (UtcTime >= _refreshDeadlineUtc)
+ {
+ _refreshGeneration = -1;
+ errorMessage = $"The FA snapshot refresh timed out with status {snapshot.Status}.";
+ }
+ }
+
+ if (snapshotToUse != null)
+ {
+ // Use the new snapshot outside the refresh-state lock.
+ }
+ else if (errorMessage != null)
+ {
+ Error(errorMessage);
+ }
+}
+ from threading import Lock
+
+def initialize(self) -> None:
+ self._refresh_lock = Lock()
+ self._refresh_generation = None
+ self._refresh_tick = 0
+ self._refresh_expected_complete = False
+ self.schedule.on(
+ self.date_rules.every_day(),
+ self.time_rules.every(timedelta(minutes=1)),
+ self._refresh_snapshot)
+
+def _refresh_snapshot(self) -> None:
+ self._poll_snapshot_refresh()
+ with self._refresh_lock:
+ if self._refresh_generation is not None:
+ return
+
+ snapshot = self.brokerage_account_snapshot
+ self._refresh_tick += 1
+ complete = self._refresh_tick % 3 == 0
+ accepted = (
+ self.request_brokerage_account_snapshot_refresh()
+ if complete else
+ self.request_brokerage_account_snapshot_refresh(["TestGroupEQ"])
+ )
+ if accepted:
+ self._refresh_generation = snapshot.generation
+ self._refresh_expected_complete = complete
+ self._refresh_deadline_utc = self.utc_time + timedelta(minutes=5)
+ else:
+ self.debug("The FA snapshot refresh wasn't accepted.")
+
+def on_data(self, slice: Slice) -> None:
+ self._poll_snapshot_refresh()
+
+def _poll_snapshot_refresh(self) -> None:
+ snapshot_to_use = None
+ error_message = None
+ with self._refresh_lock:
+ if self._refresh_generation is None:
+ return
+
+ snapshot = self.brokerage_account_snapshot
+ if snapshot.is_ready and snapshot.generation > self._refresh_generation:
+ self._refresh_generation = None
+ has_requested_scope = (
+ snapshot.is_complete
+ if self._refresh_expected_complete else
+ any(group.name.lower() == "testgroupeq"
+ for group in list(snapshot.groups.values))
+ )
+ if has_requested_scope:
+ snapshot_to_use = snapshot
+ else:
+ error_message = "The FA snapshot doesn't contain the requested scope."
+ elif snapshot.status in [
+ BrokerageAccountSnapshotStatus.FAILED,
+ BrokerageAccountSnapshotStatus.STALE,
+ BrokerageAccountSnapshotStatus.UNAVAILABLE]:
+ self._refresh_generation = None
+ error_message = f"The FA snapshot refresh ended with status {snapshot.status}."
+ elif self.utc_time >= self._refresh_deadline_utc:
+ self._refresh_generation = None
+ error_message = f"The FA snapshot refresh timed out with status {snapshot.status}."
+
+ if snapshot_to_use is not None:
+ # Use the new snapshot outside the refresh-state lock.
+ pass
+ elif error_message is not None:
+ self.error(error_message)
+BrokerageAccountSnapshotStatus |
+ Description | +
|---|---|
UnavailableUNAVAILABLE | The brokerage doesn't expose account-level state, or no request has produced a snapshot. |
RefreshingREFRESHING | An accepted refresh is queued or running. This publication doesn't advance the generation. |
ReadyREADY | The latest refresh completed successfully. |
StaleSTALE | The account state is no longer current, such as after a disconnect. Last-known data, if any, can remain in the snapshot. |
FailedFAILED | The latest refresh failed. Last-known data can remain in the snapshot. |
BrokerageAccountSnapshot has get-only properties and copies its input collections into read-only collections. Its public members are:
| Member | Type | Description |
|---|---|---|
UnavailableUNAVAILABLE | BrokerageAccountSnapshot | The shared no-snapshot result. |
Statusstatus | BrokerageAccountSnapshotStatus | The current publication status. |
Generationgeneration | longint | A monotonically increasing successful-snapshot generation. |
AsOfUtcas_of_utc | DateTimedatetime | The UTC publication time. It isn't a causal execution timestamp. |
LastSuccessfulUpdateUtclast_successful_update_utc | DateTimedatetime | The UTC time of the last successful refresh. |
CollectionStartedUtccollection_started_utc | DateTimedatetime | The UTC time collection started, when available. |
Groupsgroups | IReadOnlyDictionary<string, BrokerageAccountGroup>read-only CLR mapping | The groups selected for financial-state collection. |
AllGroupsall_groups | IReadOnlyDictionary<string, BrokerageAccountGroup>read-only CLR mapping | The complete group topology supplied by the provider, including groups outside a scoped refresh. |
PrimaryAccountIdprimary_account_id | stringstr | The primary account configured for the connection. |
ManagedAccountIdsmanaged_account_ids | IReadOnlyList<string>read-only CLR collection | Every identifier returned by managed-account discovery. |
AccountDirectoryaccount_directory | IReadOnlyDictionary<string, BrokerageAccountDirectoryEntry>read-only CLR mapping | Discovered accounts and identity metadata. Scoped entries can exist without state in Accountsaccounts. |
Accountsaccounts | IReadOnlyDictionary<string, BrokerageAccountState>read-only CLR mapping | The account states collected for the request scope. |
UnassignedAccountIdsunassigned_account_ids | IReadOnlyList<string>read-only CLR collection | Managed subaccounts outside every visible group. Scoped discovery only collects their state when explicitly requested. |
MembershipHashmembership_hash | stringstr | An opaque version of the selected group names, allocation methods, and membership, and of the managed-account universe, account aliases, and family codes. It excludes per-account allocation values. Use it through the mutation methods by passing the observed snapshot. |
GroupConfigurationVersiongroup_configuration_version | stringstr | An opaque brokerage-wide version of the complete group configuration, including per-account allocation values. |
ErrorMessageerror_message | stringstr | A provider diagnostic for the current status. |
IsReadyis_ready | boolbool | True only when Status == Readystatus == READY. |
IsCompleteis_complete | boolbool | Whether the provider reports complete group topology and eligible managed-account state. |
HasUnmappedPositionshas_unmapped_positions | boolbool | Whether any collected account has a position that LEAN couldn't map to a Symbol. |
BrokerageAccountSnapshot(...) | Constructor | Creates a provider snapshot from the preceding fields. PrimaryAccountId, ManagedAccountIds, AllGroups, AccountDirectory, IsComplete, and CollectionStartedUtc are optional constructor inputs. The constructor validates required collections, nonnegative generation, and timestamps for a Ready result. |
The values in a snapshot use the following public types. Their properties are get-only and their collection inputs are copied:
+ +| Type | Member | Description |
|---|---|---|
BrokerageAccountGroup | Namename | The canonical group name. |
AllocationMethodallocation_method | The saved TWS allocation method. | |
AccountIdsaccount_ids | The canonical, sorted member identifiers. | |
AccountAllocationValuesaccount_allocation_values | The saved per-member values. This mapping can be empty for broker-computed methods. | |
BrokerageAccountGroup(...) | Constructs a group from a name, allocation method, members, and optional values. An allocation key must identify a member. | |
BrokerageAccountDirectoryEntry | AccountIdaccount_id | The account identifier. |
Relationshiprelationship | The account's API-topology classification. | |
GroupNamesgroup_names | The visible groups that contain the account. | |
AccountTypeaccount_type | The brokerage account type when state was collected and IB supplied it; otherwise, an empty string. | |
FamilyCodefamily_code | The brokerage family-code value, or an empty string. | |
AccountAliasaccount_alias | The brokerage account alias, or an empty string. | |
BrokerageAccountDirectoryEntry(...) | Constructs an entry from the account, relationship, groups, and optional identity metadata. | |
BrokerageAccountState | AccountIdaccount_id | The managed account identifier. |
GroupNamesgroup_names | The visible groups that contain the account. | |
AccountTypeaccount_type | The account type, or an empty string. | |
NetLiquidationnet_liquidation | The account net liquidation value, or null when IB omits it. | |
TotalCashValuetotal_cash_value | The total cash value, or null when IB omits it. | |
AvailableFundsavailable_funds | The available funds, or null when IB omits them. | |
ExcessLiquidityexcess_liquidity | The excess liquidity, or null when IB omits it. | |
BuyingPowerbuying_power | The buying power, or null when IB omits it. | |
ValuationCurrencyvaluation_currency | The currency for the scalar account values, or an empty string. | |
CashBalancescash_balances | The brokerage-reported cash balances by currency. See Configuration and Limitations for the named-group summary path's accepted zero-net non-base-currency limitation. | |
Positionspositions | The positions mapped to LEAN symbols. | |
UnmappedPositionsunmapped_positions | The brokerage positions LEAN couldn't safely map. | |
HasUnmappedPositionshas_unmapped_positions | Whether the unmapped-position collection is nonempty. | |
BrokerageAccountState(...) | Constructs an account state from its identity, nullable scalar values, cash, mapped positions, and optional unmapped positions. | |
BrokerageAccountPosition | Symbolsymbol | The mapped LEAN symbol. |
Quantityquantity | The exact decimal position quantity. | |
AveragePriceaverage_price | The average position price. | |
ModelCodemodel_code | The IB model code, or an empty string. | |
BrokerageAccountPosition(...) | Constructs a mapped position and requires a non-null Symbol. | |
BrokerageAccountUnmappedPosition | BrokerageContractIdbrokerage_contract_id | The brokerage contract identifier. |
BrokerageSymbolbrokerage_symbol | The brokerage symbol. | |
LocalSymbollocal_symbol | The brokerage local symbol. | |
BrokerageSecurityTypebrokerage_security_type | The brokerage security-type code. | |
Currencycurrency | The contract currency. | |
Exchangeexchange | The contract exchange. | |
PrimaryExchangeprimary_exchange | The primary exchange. | |
TradingClasstrading_class | The brokerage trading class. | |
Expirationexpiration | The raw brokerage expiration text. | |
Strikestrike | The normalized decimal strike, or zero when normalization fails. | |
BrokerageStrikebrokerage_strike | The raw brokerage strike retained when normalization fails. | |
Rightright | The brokerage option-right text. | |
Multipliermultiplier | The brokerage contract multiplier. | |
Quantityquantity | The exact decimal position quantity. | |
AveragePriceaverage_price | The normalized average price, or zero when normalization fails. | |
BrokerageAverageCostbrokerage_average_cost | The raw brokerage average cost retained when normalization fails. | |
ModelCodemodel_code | The IB model code, or an empty string. | |
ErrorMessageerror_message | The mapping or normalization diagnostic. | |
BrokerageAccountUnmappedPosition(...) | Constructs a diagnostic position from raw brokerage identity, economics, and optional error fields. |
BrokerageAccountRelationship describes API topology, not legal ownership. UnknownUNKNOWN means the provider couldn't classify the account. PrimaryPRIMARY is the connection's configured account. AggregateAGGREGATE is an aggregate/master row that isn't a managed child. ManagedMANAGED is a managed subaccount.
In Python, these read-only CLR mappings and collections aren't native dict or list objects. Use indexing to retrieve a known key or project a mapping's values property, for example list(snapshot.groups.values). CLR decimal values in the published models project through Python.NET as Python float values. Use a suitable tolerance, and don't assume converting the float back to Decimal recovers the exact original value.
The Engine wires the snapshot property and refresh methods before Initializeinitialize, so you can read or request a snapshot during initialization. Unsupported brokerages and backtests expose Unavailable, and request methods return false.
IBrokerageAccountStateProvider is the optional brokerage capability behind the QCAlgorithm property and methods. GetAccountSnapshot()get_account_snapshot() reads the latest immutable value without an external request. RequestAccountSnapshotRefresh(groupNames, additionalAccountIds)request_account_snapshot_refresh(group_names, additional_account_ids) requests an asynchronous refresh. Its Boolean result means accepted or coalesced, not completed; consumers poll GetAccountSnapshot()get_account_snapshot() with the same generation, status, and timeout rules described above. Brokerage integrations implement this interface; algorithms normally use the QCAlgorithm members.
LEAN supports several allocation methods for FA group orders. If you intend to use the same group allocation method for every order, set the DefaultOrderPropertiesdefault_order_properties of your algorithm, which sets the order properties for all of your orders.
An Allocation Group stores an allocation method in TWS. When unified groups are enabled, set FaGroupfa_group and leave FaMethodfa_method empty to use that saved method. Before you submit an implicit-method order, request a snapshot and wait for a Ready result that contains the group.
public override void Initialize()
-{
- // Set the default order properties
- DefaultOrderProperties = new InteractiveBrokersOrderProperties()
- {
- FaGroup = "TestGroupEQ",
- FaMethod = "Equal",
- Account = "DU123456"
- };
-}
+ private bool _ordered;
public override void OnData(Slice slice)
{
- // Use default order order properties
- LimitOrder(_symbol, quantity, limitPrice);
-}
- def initialize(self) -> None:
- # Set the default order properties
- self.default_order_properties = InteractiveBrokersOrderProperties()
- self.default_order_properties.fa_group = "TestGroupEQ"
- self.default_order_properties.fa_method = "Equal"
- self.default_order_properties.account = "DU123456"
-
-def on_data(self, slice: Slice) -> None:
- # Use default order order properties
- LimitOrder(_symbol, quantity, limitPrice);
-
- To adjust the order properties of an order, change the DefaultOrderPropertiesdefault_order_properties or pass an order properties object to the order method.
- The following sections explain the FA group allocation methods.
-
This group allocation method distributes shares equally between all accounts in the group. When you use this method, you need to specify an order quantity.
- -For example, say your Account Group includes four accounts and you place an order to buy 400 shares of a stock. In this case, each account receives 100 shares. If your Account Group includes six accounts, each account receives 66 shares, and then 1 share is allocated to each account until all are distributed. After you submit the order, your algorithm receives order events to track the order progress. When all the shares are bought, the order status is OrderStatus.FilledOrderStatus.FILLED. If one of the accounts in the group can't afford 10 of the shares it needs to buy, 10 shares are cancelled and you'll only end up buying 390 shares in total.
LimitOrder(
- _symbol, quantity, limitPrice,
- orderProperties: new InteractiveBrokersOrderProperties
- {
- FaMethod = "Equal"
+ var snapshot = BrokerageAccountSnapshot;
+ if (_ordered || !snapshot.IsReady ||
+ !snapshot.AllGroups.TryGetValue("TestGroupEQ", out var group) ||
+ !group.AllocationMethod.Equals("Equal", StringComparison.OrdinalIgnoreCase))
+ {
+ return;
}
-);
- order_properties = InteractiveBrokersOrderProperties() -order_properties.fa_method = "Equal" -self.limit_order(self._symbol, quantity, limit_price, order_properties=order_properties)-
This group allocation method distributes shares based on the net liquidation value of each account. The system calculates ratios based on the net liquidation value in each account and allocates shares based on these ratios. When you use this method, you need to specify an order quantity.
- -For example, say your account group includes three accounts, A, B and C with Net Liquidation values of $25,000, $50,000 and $100,000, respectively. In this case, the system calculates a ratio of 1:2:4. If you place an order for 700 shares of a stock, it allocates 100 shares to Client A, 200 shares to Client B, and 400 shares to Client C.
- -LimitOrder(_symbol, quantity, limitPrice,
- orderProperties: new InteractiveBrokersOrderProperties
- {
- FaMethod = "NetLiq"
- }
-);
- order_properties = InteractiveBrokersOrderProperties() -order_properties.fa_method = "NetLiq" -self.limit_order(self._symbol, quantity, limit_price, order_properties=order_properties)+ var properties = new InteractiveBrokersOrderProperties + { + FaGroup = group.Name + }; + MarketOrder(_symbol, 100, orderProperties: properties); + _ordered = true; +} +
def on_data(self, slice: Slice) -> None: + snapshot = self.brokerage_account_snapshot + group = next( + (group for group in list(snapshot.all_groups.values) + if group.name.lower() == "testgroupeq"), + None) + if (self._ordered or not snapshot.is_ready or group is None + or group.allocation_method.lower() != "equal"): + return + + properties = InteractiveBrokersOrderProperties() + properties.fa_group = group.name + self.market_order(self._symbol, 100, order_properties=properties) + self._ordered = True
This group allocation method distributes shares based on the amount of available equity in each account. The system calculates ratios based on the available equity in each account and allocates shares based on these ratios. When you use this method, you need to specify an order quantity.
- -For example, say your account group includes three accounts, A, B and C with available equity of $25,000, $50,000 and $100,000, respectively. In this case, the system calculates a ratio of 1:2:4. If you place an order for 700 shares of a stock, it allocates 100 shares to Client A, 200 shares to Client B, and 400 shares to Client C.
+Initialize _orderedself._ordered to false and request the group snapshot before this handler runs. See Account Snapshots for the complete asynchronous request pattern.
| Saved method | +Allocation | +Order requirement | +
|---|---|---|
Equal |
+ IB divides the parent quantity as evenly as possible among group members. | +Submit a nonzero parent quantity. | +
NetLiq |
+ IB weights members by their net liquidation values. | +Submit a nonzero parent quantity. | +
AvailableEquity |
+ IB weights members by their available equity. | +Submit a nonzero parent quantity. | +
Ratio |
+ IB weights members by the group's saved positive ratios. | +Submit a nonzero parent quantity and leave the order-level method empty. | +
Percent |
+ IB weights members by saved positive percentages that total 100. | +Submit a nonzero parent quantity and leave the order-level method empty. This method differs from PctChange. |
+
ContractsOrShares |
+ IB uses the group's saved quantity for each member. | +Leave the order-level method empty. Saved values must be nonnegative with a positive total. The absolute parent quantity must equal that total and satisfy the security's lot size. Saved values can be fractional when the total is lot-aligned. | +
LEAN normalizes the legacy EqualQuantity spelling to Equal. Unified-group routing doesn't support MonetaryAmount or an unknown saved method.
Leaving FaMethodfa_method empty is the recommended unified route and is required for ContractsOrShares, Ratio, and Percent. For Equal, NetLiq, or AvailableEquity, you can explicitly set the same method that the group has saved. When a Ready snapshot is available, LEAN rejects an explicit method that differs from the saved method.
An implicit method always requires a Ready snapshot that contains the group. An explicit supported computed method on an explicitly named group can pass admission while the snapshot isn't ready, but LEAN can't perform saved-method, membership, or saved-vector checks in that state. A previously detected malformed or unsupported topology continues to block new group-order placements until a successful refresh clears it. Route exclusivity, group-filter checks, and the active-mutation gate still apply. Order updates repeat the applicable route and current-state checks, so a group that drifts after placement can cause an update to be rejected.
PctChange changes existing positions by a signed integer percentage supplied through FaPercentagefa_percentage; a negative value reduces them. LEAN retains this pre-existing route only when unified groups are disabled. It forwards the percentage through IB's FA percentage field and sends a zero IB parent quantity. Unified-group routing rejects both an explicit PctChange order and a group whose saved method is PctChange because IB determines the aggregate quantity after submission and LEAN can't safely account for split fills in its parent-order model.
LimitOrder(_symbol, quantity, limitPrice,
- orderProperties: new InteractiveBrokersOrderProperties
- {
- FaMethod = "AvailableEquity"
- }
-);
- order_properties = InteractiveBrokersOrderProperties() -order_properties.fa_method = "AvailableEquity" -self.limit_order(self._symbol, quantity, limit_price, order_properties=order_properties)+
var properties = new InteractiveBrokersOrderProperties
+{
+ FaGroup = "LegacyGroup",
+ FaMethod = "PctChange",
+ FaPercentage = 5
+};
+MarketOrder(_symbol, 1, orderProperties: properties);
+ properties = InteractiveBrokersOrderProperties() +properties.fa_group = "LegacyGroup" +properties.fa_method = "PctChange" +properties.fa_percentage = 5 +self.market_order(self._symbol, 1, order_properties=properties)
The nonzero LEAN quantity creates the parent order for the legacy path. Don't use this example when ib-financial-advisors-unified-groups-enabled is true. To use unified groups, change the saved group method in TWS to one of the supported methods and follow the saved-method example above.
An allocation update replaces every saved per-account value for one existing Allocation Group. It doesn't change group membership. Use a Ready snapshot whose selected Groupsgroups contains the target, and calculate a complete vector whose keys exactly match its current members. Seeing the target only in AllGroupsall_groups isn't sufficient.
C# supports IReadOnlyDictionary<string, decimal> and IEnumerable<KeyValuePair<string, decimal>> overloads of RequestBrokerageAccountGroupAllocationUpdate. Python requires a native dict with string account keys and values convertible to a CLR decimal; it doesn't accept an arbitrary mapping object.
A null snapshot or allocation input and invalid identifiers throw synchronously. A non-Ready snapshot, missing version tokens, unavailable manager, or inactive mutation lifecycle returns falseFalse. Provider validation of topology, methods, complete membership, and allocation values can also reject or throw before asynchronous work is accepted. Reading BrokerageAccountGroupAllocationUpdatebrokerage_account_group_allocation_update performs no external request and returns the latest published result, or Unavailable before a result exists.
Mutation requests return falseFalse during Initializeinitialize. Request them only during the normal algorithm run after initialization. Don't request them from OnEndOfAlgorithmon_end_of_algorithm or teardown because a request can be accepted before the run exits without being guaranteed to reach IB or publish a result.
The following example replaces a two-member Percent group's complete vector:
private long _allocationGeneration = -1;
+private string _allocationGroupName;
+private string _allocationMembershipHash;
+private string _allocationGroupConfigurationVersion;
+private DateTime _allocationDeadlineUtc;
+
+private void RequestAllocationUpdate()
+{
+ if (_allocationGeneration >= 0)
+ {
+ return;
+ }
+
+ var snapshot = BrokerageAccountSnapshot;
+ if (!snapshot.IsReady ||
+ !snapshot.Groups.TryGetValue("WeightedGroup", out var group) ||
+ !string.Equals(group.AllocationMethod, "Percent", StringComparison.OrdinalIgnoreCase) ||
+ group.AccountIds.Count != 2)
+ {
+ return;
+ }
+
+ var values = new Dictionary<string, decimal>
+ {
+ [group.AccountIds[0]] = 60m,
+ [group.AccountIds[1]] = 40m
+ };
+ var generation = BrokerageAccountGroupAllocationUpdate.Generation;
+ if (RequestBrokerageAccountGroupAllocationUpdate(
+ group.Name,
+ values,
+ snapshot))
+ {
+ _allocationGeneration = generation;
+ _allocationGroupName = group.Name;
+ _allocationMembershipHash = snapshot.MembershipHash;
+ _allocationGroupConfigurationVersion = snapshot.GroupConfigurationVersion;
+ _allocationDeadlineUtc = UtcTime.AddMinutes(5);
+ }
+ else
+ {
+ Debug("The FA allocation update wasn't accepted.");
+ }
+
+}
+ def initialize(self) -> None:
+ self._allocation_generation = None
+
+def _request_allocation_update(self) -> None:
+ if self._allocation_generation is not None:
+ return
+
+ snapshot = self.brokerage_account_snapshot
+ group = next(
+ (group for group in list(snapshot.groups.values)
+ if group.name.lower() == "weightedgroup"),
+ None)
+ if (not snapshot.is_ready or group is None
+ or group.allocation_method.lower() != "percent"
+ or len(group.account_ids) != 2):
+ return
+
+ values = {
+ group.account_ids[0]: 60,
+ group.account_ids[1]: 40
+ }
+ generation = self.brokerage_account_group_allocation_update.generation
+ if self.request_brokerage_account_group_allocation_update(
+ group.name, values, snapshot):
+ self._allocation_generation = generation
+ self._allocation_group_name = group.name
+ self._allocation_membership_hash = snapshot.membership_hash
+ self._allocation_group_configuration_version = snapshot.group_configuration_version
+ self._allocation_deadline_utc = self.utc_time + timedelta(minutes=5)
+ else:
+ self.debug("The FA allocation update wasn't accepted.")
+After the same snapshot, method, and membership checks shown above, you can make the single C# request through the enumerable overload:
+ +private bool RequestAllocationUpdateWithEnumerable(
+ BrokerageAccountGroup group,
+ BrokerageAccountSnapshot snapshot)
+{
+ IEnumerable<KeyValuePair<string, decimal>> values = new[]
+ {
+ new KeyValuePair<string, decimal>(group.AccountIds[0], 60m),
+ new KeyValuePair<string, decimal>(group.AccountIds[1], 40m)
+ };
+ return RequestBrokerageAccountGroupAllocationUpdate(
+ group.Name, values, snapshot);
+}
+ # Python uses the native dict overload shown above.+
Each overload returns trueTrue when the provider accepts the asynchronous request, not when IB confirms the update. Acceptance publishes a newer-generation Pending result, and the terminal result replaces it at the same mutation generation. Capture the current allocation-result generation before calling. Poll BrokerageAccountGroupAllocationUpdatebrokerage_account_group_allocation_update for a greater generation that matches the group and expected membership and configuration versions, then require Succeeded or Failed. A fast operation can publish a terminal result before your next read. Confirmation can stall or the account snapshot can become Failed or Stale, so enforce an algorithm-owned observation timeout. Reaching that deadline doesn't cancel accepted provider work. The example above uses an illustrative five-minute deadline; tune it to the deployment.
private void PollAllocationUpdate()
+{
+ if (_allocationGeneration < 0)
+ {
+ return;
+ }
+
+ var result = BrokerageAccountGroupAllocationUpdate;
+ var matches = result.Generation > _allocationGeneration &&
+ _allocationGroupName.Equals(
+ result.GroupName,
+ StringComparison.OrdinalIgnoreCase) &&
+ result.ExpectedMembershipHash == _allocationMembershipHash &&
+ result.ExpectedGroupConfigurationVersion == _allocationGroupConfigurationVersion;
+ if (matches && result.IsCompleted)
+ {
+ _allocationGeneration = -1;
+ if (result.Status != BrokerageAccountGroupAllocationUpdateStatus.Succeeded)
+ {
+ Error(result.ErrorMessage);
+ }
+ }
+ else if (UtcTime >= _allocationDeadlineUtc)
+ {
+ _allocationGeneration = -1;
+ Error("The FA allocation update timed out.");
+ }
+}
+ def _poll_allocation_update(self) -> None:
+ if self._allocation_generation is None:
+ return
+
+ result = self.brokerage_account_group_allocation_update
+ matches = (
+ result.generation > self._allocation_generation
+ and result.group_name.lower() == self._allocation_group_name.lower()
+ and result.expected_membership_hash == self._allocation_membership_hash
+ and result.expected_group_configuration_version == self._allocation_group_configuration_version
+ )
+ if matches and result.is_completed:
+ self._allocation_generation = None
+ if result.status != BrokerageAccountGroupAllocationUpdateStatus.SUCCEEDED:
+ self.error(result.error_message)
+ elif self.utc_time >= self._allocation_deadline_utc:
+ self._allocation_generation = None
+ self.error("The FA allocation update timed out.")
+Call PollAllocationUpdate_poll_allocation_update from OnDataon_data or a scheduled callback while the request is outstanding.
| Saved method | Complete-vector rule |
|---|---|
ContractsOrShares | Every value must be greater than or equal to zero. |
Ratio | Every value must be greater than zero. |
Percent | Every value must be greater than zero, and the values must total exactly 100. |
Equal, NetLiq, AvailableEquity, PctChange, or another method | Complete saved-vector replacement isn't supported. |
After a successful ContractsOrShares update, confirm the readback and submit a parent order whose absolute quantity equals the saved vector's total. The parent quantity must also satisfy the security's lot size.
The published BrokerageAccountGroupAllocationUpdate result has get-only properties and copied read-only dictionaries. It has the following public members:
| Member | Description |
|---|---|
UnavailableUNAVAILABLE | The shared result when the capability is unsupported or no request has produced a result. |
Statusstatus | The allocation-update status. |
Generationgeneration | The monotonically increasing update-result generation. |
AsOfUtcas_of_utc | The UTC publication time. |
GroupNamegroup_name | The existing group being updated. |
AllocationMethodallocation_method | The method observed at admission and confirmed after success. |
RequestedAccountAllocationValuesrequested_account_allocation_values | The requested complete vector. |
ResultingAccountAllocationValuesresulting_account_allocation_values | The complete vector confirmed by readback for a succeeded result. |
ExpectedMembershipHashexpected_membership_hash | The membership version supplied by the request. |
ResultingMembershipHashresulting_membership_hash | The membership version after confirmed readback. |
ExpectedGroupConfigurationVersionexpected_group_configuration_version | The complete configuration version supplied by the request. |
ResultingGroupConfigurationVersionresulting_group_configuration_version | The complete version after confirmed readback. |
ErrorMessageerror_message | A provider diagnostic or failure message. |
IsPendingis_pending | Whether the status is Pending. |
IsCompletedis_completed | Whether the status isn't Pending. This value is also true for Unavailable, so require a matching newer generation before using it. |
BrokerageAccountGroupAllocationUpdate(...) | The public constructor creates a provider result from the preceding operation fields. It validates the nonnegative generation and group identifier and copies both allocation dictionaries. |
BrokerageAccountGroupAllocationUpdateStatus has four values. UnavailableUNAVAILABLE means the capability or a result isn't available. PendingPENDING means the provider accepted the request and hasn't published a terminal result. SucceededSUCCEEDED means the provider confirmed the requested saved vector and refreshed account state. FailedFAILED means the operation didn't complete with the confirmed requested vector; inspect the error message.
IBrokerageAccountGroupAllocationManager is the optional brokerage capability behind these QCAlgorithm members. GetAccountGroupAllocationUpdate()get_account_group_allocation_update() reads the latest result. RequestAccountGroupAllocationUpdate(groupName, accountAllocationValues, expectedMembershipHash, expectedGroupConfigurationVersion)request_account_group_allocation_update(group_name, account_allocation_values, expected_membership_hash, expected_group_configuration_version) submits an optimistically versioned request. Its Boolean result means accepted, not completed; consumers poll the getter for a matching newer result and enforce a timeout. QCAlgorithm integration also requires the brokerage to implement IBrokerageAccountStateProvider. Algorithms normally pass an observed snapshot to the QCAlgorithm method.
The unified FA features are opt-in live-trading capabilities. They require compatible LEAN, Interactive Brokers brokerage, and IBAutomater versions that expose these settings and APIs.
+ +Set the following keys in your Interactive Brokers deployment brokerage settings. For a direct local LEAN source deployment, add them to Launcher/config.json:
+ +{
+ "ib-financial-advisors-group-filter": "",
+ "ib-financial-advisors-unified-groups-enabled": true,
+ "ib-financial-advisors-group-management-enabled": false
+}
+| Setting | Description | Default |
|---|---|---|
ib-financial-advisors-group-filter |
+ Limits selected financial-state collection, mutations, and group-order routing to one group. AllGroupsall_groups can still expose discovered topology outside this scope. Leave the setting empty to collect financial state for and trade multiple groups, and set FaGroupfa_group on group orders. |
+ Empty | +
ib-financial-advisors-unified-groups-enabled |
+ Enables unified Allocation Group discovery, validation, snapshots, and group routing. It also requests the selected state of the IB Gateway Use Account Groups with Allocation Methods check box before the brokerage connects. | +false |
+
ib-financial-advisors-group-management-enabled |
+ Enables assignment and saved-allocation updates for existing groups. This setting requires unified groups. | +false |
+
The Boolean settings use strict Boolean text. A missing or empty value defaults to false, and an invalid value prevents brokerage creation. The settings are fixed before IB Gateway starts, so you must redeploy to change them. Don't try to set them from Initializeinitialize or an algorithm parameter.
IBAutomater treats ib-financial-advisors-unified-groups-enabled as the desired state of the recognized Use Account Groups with Allocation Methods check box. When the value is true, it selects the check box and verifies the selected state. When the value is false or omitted, it retains the established behavior of deselecting and verifying a recognized check box. IBAutomater applies the same desired state after a Gateway restart.
When unified groups are requested, startup fails and the Gateway stops if the check box is absent, ambiguous, or disabled while unchecked. Under either requested state, startup also fails if a present control doesn't retain the requested state. Selecting this setting doesn't prove that the connection is an FA master, that any group exists, or that the account has the necessary IB permissions. The account must still be recognized as an FA master and its groups must satisfy the requirements on this page.
+ +Before you enable unified groups on an existing deployment, cancel or otherwise resolve open orders for groups saved with PctChange. Unified mode rejects new saved or explicit PctChange orders, and a recovered open order with an omitted method doesn't contain enough information for LEAN to classify its fill accounting safely.
The brokerage doesn't perform periodic snapshot refreshes. Schedule refresh requests in your algorithm and choose a cadence that fits the strategy and IB pacing budget. A practical starting point is about 60 seconds for scoped group state and 180 seconds for complete account state. Enforce a maximum age with CollectionStartedUtccollection_started_utc, LastSuccessfulUpdateUtclast_successful_update_utc, and AsOfUtcas_of_utc. An algorithm-owned observation timeout doesn't cancel accepted brokerage work, so size it for the requested scope, IB latency, and any provider recovery attempts.
A disconnect publishes Stale. After a confirmed physical reconnect, the brokerage issues one refresh with the most recently requested scope only if the algorithm previously had an accepted snapshot request in that service session. This is reconnect recovery, not a periodic cadence. An algorithm that never requests snapshots creates no automatic snapshot traffic.
CollectionStartedUtccollection_started_utc and AsOfUtcas_of_utc bound that window. A snapshot can combine identity data sampled near the start with financial data sampled later.Ready if group configuration, managed accounts, aliases, or family codes change during collection.CashBalancescash_balances when that currency nets to exactly zero across a collected group. Base-currency cash remains available. Use a collection and validation policy appropriate for currency-sensitive decisions.Positionspositions collection doesn't positively prove that IB observed the account as flat. Independently confirm flatness when that distinction controls an irreversible action.All is reserved, without regard to case, for IB's all-account API requests. Rename an Allocation Group with that name before you request snapshots or mutations.IsCompleteis_complete remains false even for the parameterless refresh overload.IsCompleteis_complete reports provider-assessed topology and account-state collection completeness. It doesn't mean every discovered group's saved method is executable or mutable.Ready publication until you correct it in TWS. In scoped discovery, an invalid unselected group can remain visible in AllGroupsall_groups, but it isn't usable for mutation. Selecting it makes the refresh fail until you correct it in TWS.OnEndOfAlgorithmon_end_of_algorithm or teardown because an accepted request isn't guaranteed to reach IB or publish a result.The Examples page contains minimal unified and legacy group-order algorithms. Replace their group names, saved-method assumptions, quantities, and freshness policy before you use them with a live account.
+ +The new QCAlgorithm properties reserve brokerage_account_snapshot, brokerage_account_group_assignment, and brokerage_account_group_allocation_update. Don't assign algorithm state to these names because they are read-only descriptors.
IBrokerageAccountServiceConsumer is a public Engine and brokerage integration contract. Its members are SetBrokerageAccountStateProvider(IBrokerageAccountStateProvider provider)set_brokerage_account_state_provider(provider), SetBrokerageAccountGroupManager(IBrokerageAccountGroupManager manager)set_brokerage_account_group_manager(manager), and SetBrokerageAccountGroupAllocationManager(IBrokerageAccountGroupAllocationManager manager)set_brokerage_account_group_allocation_manager(manager). The standard Engine supplies supported services before algorithm initialization and can pass null for unsupported managers. QCAlgorithm implements the setters explicitly and exposes no public mutation-activation hook, so ordinary algorithms should use the QCAlgorithm properties and methods instead. A custom consumer must define and enforce its own mutation lifecycle.
The following examples show the two group-routing modes. Replace the group names and quantities, confirm the saved group method in TWS, and test with an IB paper account before you trade live.
+ +This algorithm requests one group, waits for a newer Ready snapshot, verifies that the group is saved with Equal, and submits an aggregate parent order with an empty order-level method. Enable ib-financial-advisors-unified-groups-enabled in the deployment settings before IB Gateway starts. The request return value means accepted or coalesced, not completed. The example uses an illustrative five-minute observation deadline, which doesn't cancel accepted provider work; tune it to the selected scope and deployment.
public class UnifiedFinancialAdvisorGroupAlgorithm : QCAlgorithm
+{
+ private const string GroupName = "TestGroupEQ";
+ private Symbol _symbol;
+ private long _refreshGeneration = -1;
+ private DateTime _refreshDeadlineUtc;
+ private int _submitted;
+ private bool _stopped;
+
+ public override void Initialize()
+ {
+ SetStartDate(2024, 9, 1);
+ SetEndDate(2024, 9, 5);
+ SetBrokerageModel(
+ BrokerageName.InteractiveBrokersBrokerage,
+ AccountType.Margin);
+ _symbol = AddEquity("SPY", Resolution.Minute).Symbol;
+
+ if (!LiveMode)
+ {
+ return;
+ }
+
+ Schedule.On(
+ DateRules.EveryDay(),
+ TimeRules.Every(TimeSpan.FromMinutes(1)),
+ RefreshOrPoll);
+ RequestSnapshot();
+ }
+
+ public override void OnData(Slice slice)
+ {
+ PollSnapshot();
+ }
+
+ private void RefreshOrPoll()
+ {
+ PollSnapshot();
+ if (System.Threading.Volatile.Read(ref _submitted) == 0 &&
+ !_stopped && _refreshGeneration < 0)
+ {
+ RequestSnapshot();
+ }
+ }
+
+ private void RequestSnapshot()
+ {
+ var snapshot = BrokerageAccountSnapshot;
+ if (RequestBrokerageAccountSnapshotRefresh(new[] { GroupName }))
+ {
+ _refreshGeneration = snapshot.Generation;
+ _refreshDeadlineUtc = UtcTime.AddMinutes(5);
+ }
+ else
+ {
+ Debug("The FA snapshot refresh wasn't accepted.");
+ }
+ }
+
+ private void PollSnapshot()
+ {
+ if (System.Threading.Volatile.Read(ref _submitted) != 0 ||
+ _stopped || _refreshGeneration < 0)
+ {
+ return;
+ }
+
+ var snapshot = BrokerageAccountSnapshot;
+ if (snapshot.IsReady && snapshot.Generation > _refreshGeneration)
+ {
+ _refreshGeneration = -1;
+ if (!snapshot.Groups.TryGetValue(GroupName, out var group) ||
+ !group.AllocationMethod.Equals(
+ "Equal", StringComparison.OrdinalIgnoreCase))
+ {
+ _stopped = true;
+ Error($"{GroupName} isn't a selected Equal group.");
+ return;
+ }
+
+ var properties = new InteractiveBrokersOrderProperties
+ {
+ FaGroup = group.Name
+ // Leave FaMethod empty to use the saved method.
+ };
+ if (System.Threading.Interlocked.CompareExchange(
+ ref _submitted, 1, 0) != 0)
+ {
+ return;
+ }
+ MarketOrder(_symbol, 10, orderProperties: properties);
+ }
+ else if (snapshot.Status == BrokerageAccountSnapshotStatus.Failed ||
+ snapshot.Status == BrokerageAccountSnapshotStatus.Stale)
+ {
+ _refreshGeneration = -1;
+ Error($"The FA snapshot refresh ended with {snapshot.Status}.");
+ }
+ else if (UtcTime >= _refreshDeadlineUtc)
+ {
+ _refreshGeneration = -1;
+ Error("The FA snapshot refresh timed out.");
+ }
+ }
+}
+ from threading import Lock
+
+class UnifiedFinancialAdvisorGroupAlgorithm(QCAlgorithm):
+ _group_name = "TestGroupEQ"
+
+ def initialize(self) -> None:
+ self.set_start_date(2024, 9, 1)
+ self.set_end_date(2024, 9, 5)
+ self.set_brokerage_model(
+ BrokerageName.INTERACTIVE_BROKERS_BROKERAGE,
+ AccountType.MARGIN)
+ self._symbol = self.add_equity("SPY", Resolution.MINUTE).symbol
+ self._refresh_generation = None
+ self._submitted = False
+ self._stopped = False
+ self._submission_lock = Lock()
+
+ if not self.live_mode:
+ return
+
+ self.schedule.on(
+ self.date_rules.every_day(),
+ self.time_rules.every(timedelta(minutes=1)),
+ self._refresh_or_poll)
+ self._request_snapshot()
+
+ def on_data(self, slice: Slice) -> None:
+ self._poll_snapshot()
+
+ def _refresh_or_poll(self) -> None:
+ self._poll_snapshot()
+ if (not self._submitted and not self._stopped
+ and self._refresh_generation is None):
+ self._request_snapshot()
+
+ def _request_snapshot(self) -> None:
+ snapshot = self.brokerage_account_snapshot
+ if self.request_brokerage_account_snapshot_refresh([self._group_name]):
+ self._refresh_generation = snapshot.generation
+ self._refresh_deadline_utc = self.utc_time + timedelta(minutes=5)
+ else:
+ self.debug("The FA snapshot refresh wasn't accepted.")
+
+ def _poll_snapshot(self) -> None:
+ if self._submitted or self._stopped or self._refresh_generation is None:
+ return
+
+ snapshot = self.brokerage_account_snapshot
+ if snapshot.is_ready and snapshot.generation > self._refresh_generation:
+ self._refresh_generation = None
+ group = next(
+ (group for group in list(snapshot.groups.values)
+ if group.name.lower() == self._group_name.lower()),
+ None)
+ if group is None or group.allocation_method.lower() != "equal":
+ self._stopped = True
+ self.error(f"{self._group_name} isn't a selected Equal group.")
+ return
+
+ properties = InteractiveBrokersOrderProperties()
+ properties.fa_group = group.name
+ # Leave fa_method empty to use the saved method.
+ with self._submission_lock:
+ if self._submitted or self._stopped:
+ return
+ self._submitted = True
+ self.market_order(self._symbol, 10, order_properties=properties)
+ elif snapshot.status in [
+ BrokerageAccountSnapshotStatus.FAILED,
+ BrokerageAccountSnapshotStatus.STALE]:
+ self._refresh_generation = None
+ self.error(f"The FA snapshot refresh ended with {snapshot.status}.")
+ elif self.utc_time >= self._refresh_deadline_utc:
+ self._refresh_generation = None
+ self.error("The FA snapshot refresh timed out.")
+The snapshot timestamps bound collection but don't prove that a specific execution is present. If your strategy depends on child-account effects, add the terminal-order reconciliation described in Group Routing.
+ +This algorithm uses the pre-existing group-order route with unified groups disabled. It explicitly names the Equal method and doesn't use snapshots or mutations.
public class LegacyFinancialAdvisorGroupAlgorithm : QCAlgorithm
+{
+ private Symbol _symbol;
+ private bool _submitted;
+
+ public override void Initialize()
+ {
+ SetStartDate(2024, 9, 1);
+ SetEndDate(2024, 9, 5);
+ SetBrokerageModel(
+ BrokerageName.InteractiveBrokersBrokerage,
+ AccountType.Margin);
+ _symbol = AddEquity("SPY", Resolution.Minute).Symbol;
+ }
+
+ public override void OnData(Slice slice)
+ {
+ if (_submitted || !LiveMode)
+ {
+ return;
+ }
+
+ var properties = new InteractiveBrokersOrderProperties
+ {
+ FaGroup = "TestGroupEQ",
+ FaMethod = "Equal"
+ };
+ _submitted = true;
+ MarketOrder(_symbol, 10, orderProperties: properties);
+ }
+}
+ class LegacyFinancialAdvisorGroupAlgorithm(QCAlgorithm):
+ def initialize(self) -> None:
+ self.set_start_date(2024, 9, 1)
+ self.set_end_date(2024, 9, 5)
+ self.set_brokerage_model(
+ BrokerageName.INTERACTIVE_BROKERS_BROKERAGE,
+ AccountType.MARGIN)
+ self._symbol = self.add_equity("SPY", Resolution.MINUTE).symbol
+ self._submitted = False
+
+ def on_data(self, slice: Slice) -> None:
+ if self._submitted or not self.live_mode:
+ return
+
+ properties = InteractiveBrokersOrderProperties()
+ properties.fa_group = "TestGroupEQ"
+ properties.fa_method = "Equal"
+ self._submitted = True
+ self.market_order(self._symbol, 10, order_properties=properties)
+Group assignments move a managed account from every current Allocation Group into one existing group, or leave it unassigned. The operation doesn't move cash or positions, create a group, delete a group, or replace a group's complete saved allocation vector.
+ +Call RequestBrokerageAccountGroupAssignment(accountId, targetGroupName, targetAllocationValue, observedSnapshot)request_brokerage_account_group_assignment(account_id, target_group_name, target_allocation_value, observed_snapshot) with the Ready snapshot on which you based the change. Pass the exact empty string as the target to remove the account from every group.
The snapshot must be complete, or its selected Groupsgroups scope must include the destination and every current source group that contains the account. Merely seeing a group in AllGroupsall_groups isn't sufficient. Refresh the complete mutation scope before requesting a cross-group move.
The method returns trueTrue when the request is accepted for asynchronous processing, not when IB confirms it. Acceptance publishes a newer-generation Pending result, and the terminal result replaces it at the same mutation generation. Capture BrokerageAccountGroupAssignment.Generationbrokerage_account_group_assignment.generation before the call. Poll the property for a greater generation that matches the account and target group, then require Succeeded or Failed. Publication can move to the terminal result before your next read. Confirmation can stall or the account snapshot can become Failed or Stale, so your algorithm must enforce its own observation timeout. Reaching that deadline doesn't cancel accepted provider work. The example below uses an illustrative five-minute deadline; tune it to the deployment.
A null snapshot and invalid identifiers throw synchronously. A non-Ready snapshot, missing version tokens, unavailable manager, or inactive mutation lifecycle returns falseFalse. Provider validation of scope, topology, and allocation values can also reject or throw before asynchronous work is accepted.
Mutation requests return falseFalse during Initializeinitialize. Request them only during the normal algorithm run after initialization. Don't request them from OnEndOfAlgorithmon_end_of_algorithm or teardown because a request can be accepted before the run exits without being guaranteed to reach IB or publish a result. Unsupported brokerages and backtests expose Unavailable and return false.
The following example assumes TargetGroup is saved with the Equal method:
private long _assignmentGeneration = -1;
+private string _assignmentAccountId;
+private string _assignmentTargetGroupName;
+private DateTime _assignmentDeadlineUtc;
+
+private void RequestAssignment(
+ string accountId,
+ BrokerageAccountSnapshot snapshot)
+{
+ if (!snapshot.IsReady || _assignmentGeneration >= 0)
+ {
+ return;
+ }
+
+ var generation = BrokerageAccountGroupAssignment.Generation;
+ var accepted = RequestBrokerageAccountGroupAssignment(
+ accountId,
+ "TargetGroup",
+ null, // Equal doesn't take a saved allocation value.
+ snapshot);
+ if (accepted)
+ {
+ _assignmentGeneration = generation;
+ _assignmentAccountId = accountId;
+ _assignmentTargetGroupName = "TargetGroup";
+ _assignmentDeadlineUtc = UtcTime.AddMinutes(5);
+ }
+ else
+ {
+ Debug("The FA group assignment wasn't accepted.");
+ }
+}
+
+private void PollAssignment()
+{
+ if (_assignmentGeneration < 0)
+ {
+ return;
+ }
+
+ var result = BrokerageAccountGroupAssignment;
+ var matches = result.Generation > _assignmentGeneration &&
+ _assignmentAccountId.Equals(
+ result.AccountId,
+ StringComparison.OrdinalIgnoreCase) &&
+ _assignmentTargetGroupName.Equals(
+ result.TargetGroupName,
+ StringComparison.OrdinalIgnoreCase);
+ if (matches && result.IsCompleted)
+ {
+ _assignmentGeneration = -1;
+ if (result.Status != BrokerageAccountGroupAssignmentStatus.Succeeded)
+ {
+ Error(result.ErrorMessage);
+ }
+ }
+ else if (UtcTime >= _assignmentDeadlineUtc)
+ {
+ _assignmentGeneration = -1;
+ Error("The FA group assignment timed out.");
+ }
+}
+ def initialize(self) -> None:
+ self._assignment_generation = None
+ self._assignment_target_group_name = "TargetGroup"
+
+def _request_assignment(
+ self, account_id: str, snapshot: BrokerageAccountSnapshot) -> None:
+ if not snapshot.is_ready or self._assignment_generation is not None:
+ return
+
+ generation = self.brokerage_account_group_assignment.generation
+ accepted = self.request_brokerage_account_group_assignment(
+ account_id,
+ self._assignment_target_group_name,
+ None, # Equal doesn't take a saved allocation value.
+ snapshot)
+ if accepted:
+ self._assignment_generation = generation
+ self._assignment_account_id = account_id
+ self._assignment_deadline_utc = self.utc_time + timedelta(minutes=5)
+ else:
+ self.debug("The FA group assignment wasn't accepted.")
+
+def _poll_assignment(self) -> None:
+ if self._assignment_generation is None:
+ return
+
+ result = self.brokerage_account_group_assignment
+ matches = (
+ result.generation > self._assignment_generation
+ and result.account_id.lower() == self._assignment_account_id.lower()
+ and result.target_group_name.lower() == self._assignment_target_group_name.lower()
+ )
+ if matches and result.is_completed:
+ self._assignment_generation = None
+ if result.status != BrokerageAccountGroupAssignmentStatus.SUCCEEDED:
+ self.error(result.error_message)
+ elif self.utc_time >= self._assignment_deadline_utc:
+ self._assignment_generation = None
+ self.error("The FA group assignment timed out.")
+Call PollAssignment_poll_assignment from OnDataon_data or a scheduled callback while the request is outstanding.
The destination group's saved allocation method determines whether targetAllocationValuetarget_allocation_value is required when adding an account:
| Saved method | Value rule |
|---|---|
Equal, NetLiq, or AvailableEquity | Pass nullNone. |
ContractsOrShares | Pass a value greater than or equal to zero. |
Ratio | Pass a value greater than zero. |
Percent | Pass exactly 100 for the sole first member of an empty group. Pass a value greater than zero and less than 100 when adding to a nonempty group; existing values are proportionally normalized into the remaining percentage. |
PctChange or another method | Membership mutation isn't supported. |
When an account already belongs to a user-specified destination group, pass no value to preserve its saved allocation. Removing an account from a saved Percent source group proportionally renormalizes the remaining positive values to total 100. IB doesn't allow an operation that removes the final member of a source group. Add another member first, or manage group creation and deletion in TWS.
Reading BrokerageAccountGroupAssignmentbrokerage_account_group_assignment performs no external request. It returns the latest published BrokerageAccountGroupAssignment result, whose properties are get-only and whose public members are:
| Member | Description |
|---|---|
UnavailableUNAVAILABLE | The shared result when the capability is unsupported or no request has produced a result. |
Statusstatus | The assignment status. |
Generationgeneration | The monotonically increasing assignment-result generation. |
AsOfUtcas_of_utc | The UTC publication time. |
AccountIdaccount_id | The managed account being assigned. |
TargetGroupNametarget_group_name | The requested destination, or an empty string for global unassignment. |
TargetAllocationValuetarget_allocation_value | The optional requested value for a user-specified destination. |
PreviousGroupNamesprevious_group_names | The account's groups before the operation. |
ResultingGroupNamesresulting_group_names | The groups confirmed by readback for a succeeded result. |
ExpectedMembershipHashexpected_membership_hash | The membership version supplied by the request. |
ResultingMembershipHashresulting_membership_hash | The membership version after confirmed readback. |
ExpectedGroupConfigurationVersionexpected_group_configuration_version | The complete group-configuration version supplied by the request. |
ResultingGroupConfigurationVersionresulting_group_configuration_version | The version after confirmed readback. |
ErrorMessageerror_message | A provider diagnostic or failure message. |
IsPendingis_pending | Whether the status is Pending. |
IsCompletedis_completed | Whether the status isn't Pending. This value is also true for Unavailable, so require a matching newer generation before using it. |
BrokerageAccountGroupAssignment(...) | The public constructor creates a provider result from the preceding operation fields, including the optional target allocation value. It validates the nonnegative generation, required account identifier, any nonempty target identifier, and copied group-name collections. |
BrokerageAccountGroupAssignmentStatus has four values. UnavailableUNAVAILABLE means the capability or a result isn't available. PendingPENDING means the provider accepted the request and hasn't published a terminal result. SucceededSUCCEEDED means the provider confirmed the requested topology and refreshed account state. FailedFAILED means the operation didn't complete with confirmed requested topology; inspect the error message.
IBrokerageAccountGroupManager is the optional brokerage capability behind these QCAlgorithm members. GetAccountGroupAssignment()get_account_group_assignment() reads the latest result. RequestAccountGroupAssignment(accountId, targetGroupName, expectedMembershipHash, expectedGroupConfigurationVersion, targetAllocationValue = null)request_account_group_assignment(account_id, target_group_name, expected_membership_hash, expected_group_configuration_version, target_allocation_value=None) submits an optimistically versioned request. Its Boolean result means accepted, not completed; consumers poll the getter for a matching newer result and enforce a timeout. QCAlgorithm integration also requires the brokerage to implement IBrokerageAccountStateProvider. Algorithms normally pass an observed snapshot to the QCAlgorithm method instead of handling version strings directly.
Financial Advisor accounts enable certified professionals to use a single trading algorithm to manage several client accounts. Our Interactive Brokers integration enables you to place FA group orders if your IB account code starts with F, FA, or I.
+Financial Advisor (FA) accounts enable certified professionals to use one live algorithm to monitor and trade several managed accounts. The Interactive Brokers integration supports direct managed-account orders and orders for existing Allocation Groups.
+ +The optional unified-groups features also expose read-only published per-account snapshots and asynchronous operations for changing existing group membership and saved allocation values. These features support IB's current Use Account Groups with Allocation Methods model. They don't support legacy separate Allocation Profiles.
+ +To place trades using a subset of client accounts, create Account Groups in Trader Workstation and then define the InteractiveBrokersOrderProperties when you create orders.
To trade through an existing subset of managed accounts, create an Allocation Group in Trader Workstation. Set the FaGroupfa_group property of an InteractiveBrokersOrderProperties object to the group name.
| Property | FA behavior | Default |
|---|---|---|
Accountaccount | Routes the order directly to one managed account. Don't set FaGroupfa_group or FaProfilefa_profile on the same order. | Empty |
FaGroupfa_group | Routes the order to an existing Allocation Group. In unified mode, leave it empty only when the deployment group filter supplies the route. | Empty |
FaMethodfa_method | Specifies an explicit calculated method for legacy or explicitly named group routing. In unified mode, a nonempty method must match the saved method when a Ready snapshot is available. Leave it empty to use the saved method. Unified saved ContractsOrShares, Ratio, and Percent groups require an empty value. | Empty |
FaPercentagefa_percentage | Supplies the integer percentage for the legacy PctChange route when unified groups are disabled. | 0 |
FaProfilefa_profile | Routes through a legacy Allocation Profile. Unified groups don't support profiles. | Empty |
The following unified-group example uses the group's saved method. Request the group scope and wait for a Ready snapshot that contains the group before you submit this implicit-method order.
var properties = new InteractiveBrokersOrderProperties
+{
+ // Leave FaMethod empty to use the saved method.
+ FaGroup = "TestGroupEQ"
+};
+MarketOrder(_symbol, 100, orderProperties: properties);
+ properties = InteractiveBrokersOrderProperties() +# Leave fa_method empty to use the saved method. +properties.fa_group = "TestGroupEQ" +self.market_order(self._symbol, 100, order_properties=properties)+
To route an order directly to one managed account, set Accountaccount instead.
DefaultOrderProperties = new InteractiveBrokersOrderProperties ++var properties = new InteractiveBrokersOrderProperties { - FaGroup = "TestGroupEQ", - FaMethod = "Equal", Account = "DU123456" -};-self.default_order_properties = InteractiveBrokersOrderProperties() -self.default_order_properties.fa_group = "TestGroupEQ" -self.default_order_properties.fa_method = "Equal" -self.default_order_properties.account = "DU123456"+}; +MarketOrder(_symbol, 10, orderProperties: properties);
properties = InteractiveBrokersOrderProperties() +properties.account = "DU123456" +self.market_order(self._symbol, 10, order_properties=properties)
SecurityHolding objects aggregate your positions across all the account groups. If you have two groups where group A has 10 shares of SPY and group B has -10 shares of SPY, then self.portfolio["SPY"].quantityPortfolio["SPY"].Quantity is zero.
Accountaccount, FaGroupfa_group, and FaProfilefa_profile are mutually exclusive routes. A nonempty account takes precedence, clears the group and method, and bypasses group snapshot and mutation-gate validation. Unified groups don't support legacy separate Allocation Profiles.
Treat a submitted order's FA routing properties as immutable. Create a new InteractiveBrokersOrderProperties object for a different route instead of changing its account, group, method, or percentage after submission.
In unified mode, every leg of a combo order must resolve to the same account, group, and allocation method. Unified account and group routing doesn't apply to option exercise orders. IB routes an exercise through the connection's configured account.
+ +The ib-financial-advisors-group-filter setting limits selected financial-state collection, mutations, and group-order routing to one group. AllGroupsall_groups can still expose discovered topology outside this scope. If you configure the filter, an order can omit FaGroupfa_group to use that group. When unified groups are enabled, LEAN trims the configured value, compares explicit groups without regard to case, and requires an explicitly named group to match the filter. Leave the setting empty to collect financial state for and trade multiple groups, and set the group on each group order.
A filter-routed order uses the group's saved method, so a supported order-level FaMethodfa_method doesn't override it. Explicit PctChange is rejected in unified mode. The brokerage doesn't refresh snapshots during order admission. Request the filtered group and wait for a Ready snapshot that contains it before placing the order.
For an explicitly named group in unified mode, a nonempty method outside the supported unified set, including PctChange, is rejected before submission. A direct-account route bypasses group-method validation, and a filter-routed order uses the saved method as described above. For supported method and saved-vector rules, see Allocation Methods.
On an FA master with unified groups enabled, the configured filter also classifies an order without InteractiveBrokersOrderProperties as a group order. LEAN applies the master execution to the aggregate parent order and doesn't count child allocations as extra parent fills. A non-FA connection or a deployment with unified groups disabled retains legacy execution handling.
LEAN publishes aggregate parent order events for an FA group order. It doesn't publish a child execution event for each managed account. After a parent order reaches a terminal status, request newer brokerage account snapshots for the original group. In an unfiltered deployment, include any original member that no longer belongs to the group as an additional account. A filtered deployment can't request additional accounts outside its group. Reconcile only a later Ready snapshot that contains every required account.
Unified direct-account and supported group routes preserve IB's decimal execution quantities in LEAN's fill accounting. The parent event remains aggregate for a group route, and the account snapshot remains the authority for each child account.
+ +A newer snapshot generation or timestamp doesn't prove that IB's account-position stream contains a recent execution. Continue until a qualifying snapshot shows the expected account-level effect, or stop after an algorithm-owned timeout for manual review.
+ +Position reconciliation can't distinguish an allocation fill from another trade in the same account and symbol that later offsets it. Keep manual and external trading in that symbol inactive while your retry policy depends on an unchanged position.
+ +SecurityHolding objects aggregate positions across managed accounts. If one group holds 10 shares of SPY and another holds -10, then Portfolio["SPY"].Quantityself.portfolio["SPY"].quantity is zero. Portfolio helpers retain aggregate symbol and lot-size semantics. Use BrokerageAccountSnapshotbrokerage_account_snapshot as the authority for per-account and fractional FA inventory.
To use FA group orders through our Interactive Brokers integration, you need to connect as a member of a Trading Firm and Institution organization. If you aren't currently on either of these tiers, upgrade your organization.
+To use FA group orders through our Interactive Brokers integration, connect as a member of a Trading Firm or Institution organization. If your organization isn't on one of these tiers, upgrade your organization.
+ +The account snapshot and group-management APIs are live-trading features. They require LEAN and IB brokerage versions that support these APIs. Configure TWS to use current Allocation Groups, not legacy separate Allocation Profiles.
diff --git a/project-templates/csharp/financial-advisors/Main.cs b/project-templates/csharp/financial-advisors/Main.cs index 56f0d5502d..c5b6676c97 100644 --- a/project-templates/csharp/financial-advisors/Main.cs +++ b/project-templates/csharp/financial-advisors/Main.cs @@ -76,8 +76,7 @@ public override void Initialize() DefaultOrderProperties = new InteractiveBrokersOrderProperties() { FaGroup = "TestGroupEQ", - FaMethod = "Equal", - Account = "DU123456" + FaMethod = "Equal" }; // Request SPY data to trade. diff --git a/project-templates/csharp/templates.json b/project-templates/csharp/templates.json index 52123d7cd1..1fb6d38637 100644 --- a/project-templates/csharp/templates.json +++ b/project-templates/csharp/templates.json @@ -339,7 +339,7 @@ { "name": "Financial Advisors", "folder": "financial-advisors", - "description": "Demonstrates Interactive Brokers Financial Advisor group/account allocations via order properties along with live brokerage messages.", + "description": "Demonstrates Interactive Brokers Financial Advisor group allocation through order properties along with live brokerage messages.", "tags": ["interactive-brokers", "order-properties", "financial-advisor", "live-trading", "notifications", "Equity", "brokerage-messages"] }, { diff --git a/project-templates/python/financial-advisors/main.py b/project-templates/python/financial-advisors/main.py index e873d1df77..dac02f758b 100644 --- a/project-templates/python/financial-advisors/main.py +++ b/project-templates/python/financial-advisors/main.py @@ -17,7 +17,6 @@ def initialize(self) -> None: self.default_order_properties = InteractiveBrokersOrderProperties() self.default_order_properties.fa_group = "TestGroupEQ" self.default_order_properties.fa_method = "Equal" - self.default_order_properties.account = "DU123456" # Request SPY data to trade. self.add_equity("SPY") @@ -49,4 +48,4 @@ def on_brokerage_message(self, message_event: BrokerageMessageEvent) -> None: self._notify_all(f"Brokerage Message", str(message_event)) case _: self.log(str(message_event)) - #endregion \ No newline at end of file + #endregion diff --git a/project-templates/python/templates.json b/project-templates/python/templates.json index f2f769ee4f..3549c5d617 100644 --- a/project-templates/python/templates.json +++ b/project-templates/python/templates.json @@ -349,7 +349,7 @@ { "name": "Financial Advisors", "folder": "financial-advisors", - "description": "Demonstrates Interactive Brokers Financial Advisor group/account allocations via order properties along with live brokerage messages.", + "description": "Demonstrates Interactive Brokers Financial Advisor group allocation through order properties along with live brokerage messages.", "tags": ["interactive-brokers", "order-properties", "financial-advisor", "live-trading", "notifications", "Equity", "brokerage-messages"] }, { diff --git a/project-templates/templates.json b/project-templates/templates.json index a3cda7133f..13bf94dde5 100644 --- a/project-templates/templates.json +++ b/project-templates/templates.json @@ -425,7 +425,7 @@ { "name": "Financial Advisors", "folder": "financial-advisors", - "description": "Demonstrates Interactive Brokers Financial Advisor group/account allocations via order properties along with live brokerage messages.", + "description": "Demonstrates Interactive Brokers Financial Advisor group allocation through order properties along with live brokerage messages.", "tags": ["interactive-brokers", "order-properties", "financial-advisor", "live-trading", "notifications", "Equity", "brokerage-messages"], "projectIdCs": 31884441, "projectIdPy": 31853653