0027. Account Target Position Portfolio Exposure
Status
Accepted - implemented and amended
This ADR defines the target architecture for account target-position rows that can reference either direct assets or constructed portfolios.
Context
AccountAllocationModelTable, AccountTargetAllocationTable, and
PositionSetTable are core account concepts. They define:
AccountAllocationModelTable
reusable allocation model
AccountTargetAllocationTable
account -> account allocation-model mandate
PositionSetTable
concrete UTC-stamped target snapshot for one account target allocation
The actual target exposure rows currently live in TargetPositionsStorage and
are asset-only:
TargetPositionsStorage
time_index
position_set_uid -> PositionSetTable.uid
asset_identifier -> AssetTable.unique_identifier
weight_notional_exposure
constant_notional_exposure
single_asset_quantity
That shape is too narrow for account mandates that allocate to a constructed portfolio sleeve, for example:
60% direct BTC
40% PortfolioTable(uid=...)
Creating a synthetic AssetTable row for a portfolio is wrong because a
portfolio is not a traded/custodied asset. Using the optional portfolio
IndexTable linkage is also wrong because an index is a publication or
benchmark representation, not the target exposure object.
ADR 0019 keeps portfolio construction workflows in msm_portfolios.
PortfolioTable is core msm portfolio identity/reference data, while
msm_portfolios owns portfolio calculation DataNodes and strategies.
Decision
Target-position exposure will support exactly two target kinds:
asset
portfolio
Do not introduce a generic normalized PositionTargetTable. We do not expect
additional target kinds, and a normalized target table would add lookup
indirection without improving the current domain model.
Do not create portfolio assets. Do not use portfolio indices as account target position identities.
Instead, expand the target-position storage contract to carry an explicit target kind and canonical target UID:
TargetPositionsStorage
time_index
position_set_uid FK -> PositionSetTable.uid
target_type "asset" | "portfolio"
target_uid canonical UID for row identity
asset_uid nullable FK -> AssetTable.uid
portfolio_uid nullable FK -> PortfolioTable.uid
weight_notional_exposure
constant_notional_exposure
single_asset_quantity
metadata_json
The DataNode storage index is:
(time_index, position_set_uid, target_type, target_uid)
target_uid is intentionally duplicated from the concrete FK column so storage
identity is non-null and stable for both target kinds. The concrete FK columns
preserve relational integrity.
The row must satisfy exactly one of these shapes:
target_type = "asset"
target_uid = asset_uid
asset_uid is not null
portfolio_uid is null
target_type = "portfolio"
target_uid = portfolio_uid
portfolio_uid is not null
asset_uid is null
Package Boundary
The expanded storage table references PortfolioTable. Portfolio identity is a
core reference concept because account target allocations, virtual funds, and
portfolio calculation workflows all need to reference the same portfolio row.
Core msm still must not import msm_portfolios.
Therefore:
AccountTable,AccountGroupTable,AccountAllocationModelTable,AccountTargetAllocationTable,AccountHoldingsSetTable, andPositionSetTableremain in coremsm.PortfolioTableremains in coremsmas portfolio identity/reference data.- The expanded portfolio-aware target-position storage and its DataNode belong
to core
msm, because account target allocation is an account concept. msm_portfoliosowns portfolio calculation DataNodes, strategies, and signal metadata. ADR 0029 moves virtual-fund allocation state into coremsmaccount allocation.
This amends ADR 0019 by clarifying that account target-position registry rows and account target-position exposure storage remain core.
Relationship Diagram
Account Mandate Registry
+-------------------------------+
| AccountAllocationModelTable |
| MetaTable: AccountAllocation |
| Model |
|-------------------------------|
| uid PK |
| allocation_model_name unique |
+---------------+---------------+
|
| account_allocation_model_uid
v
+-------------------------------+ +-------------------------------+
| AccountTargetAllocationTable | | AccountTable |
| MetaTable: AccountTarget | | MetaTable: Account |
| Allocation | | |
|-------------------------------| |-------------------------------|
| uid PK | | uid PK |
| account_uid FK ---------------+------->| account identity |
| account_allocation_model_uid | +-------------------------------+
+---------------+---------------+
|
| account_target_allocation_uid
v
+-------------------------------+
| PositionSetTable |
| MetaTable: PositionSet |
|-------------------------------|
| uid PK |
| position_set_time UTC |
+---------------+---------------+
|
| position_set_uid
v
+-------------------------------+
| TargetPositionsStorage |
| PlatformTimeIndexMetaTable |
| owner: msm |
|-------------------------------|
| time_index |
| position_set_uid FK |
| target_type |
| target_uid |
| asset_uid nullable FK |
| portfolio_uid nullable FK |
| exposure columns |
+----------+----------------+---+
| |
| asset_uid | portfolio_uid
v v
+-------------------+ +-------------------+
| AssetTable | | PortfolioTable |
| MetaTable: Asset | | MetaTable: |
| owner: msm | | Portfolio |
+-------------------+ | owner: |
| msm |
+-------------------+
Exposure Semantics
Asset target rows represent direct target exposure to a specific AssetTable
row.
Portfolio target rows represent target exposure to a constructed
PortfolioTable row. They are not custody holdings. They must be expanded into
underlying asset exposure only when a downstream workflow needs asset-level
orders, risk, or account holdings simulation.
TargetPositionsStorage row
target_type = "portfolio"
portfolio_uid = P
|
v
resolve PortfolioTable(P)
|
v
read portfolio weights / latest portfolio composition
|
v
expand to asset-level exposure for execution, risk, or reporting
The target-position table stores mandate intent. It does not store expanded portfolio constituents.
Documentation Update Requirements
Implementing this architecture must update the documentation at the same time
as the schema, service, and example changes. The implementation is incomplete if
the docs still describe target positions as asset-only or still show
asset_identifier as the target-position storage identity.
Required documentation updates:
docs/knowledge/msm/accounts/index.mdmust explain that core account rows own account identity, account groups, allocation models, account target allocations, position sets, and portfolio-aware target exposure storage.docs/knowledge/msm/accounts/index.mdmust replace the asset-only target position diagrams with diagrams showingtarget_type,target_uid,asset_uid, andportfolio_uid.docs/knowledge/msm_portfolios/portfolios/index.mdmust explain howPortfolioTable.uidcan be referenced by account target-position exposure rows and how portfolio target rows are expanded into asset-level exposure only when downstream workflows request it.- The account workflow documentation and tutorial pages under
docs/tutorial/must show an example target set containing one direct asset target and one portfolio target. - The relevant packaged agent skills must be updated so future coding agents do
not recreate the old
asset_identifiertarget-position contract:.agents/skills/ms_markets/accounts/account_workflow/SKILL.mdand the portfolio workflow skill under.agents/skills/ms_markets/. - Examples must be updated with the documentation, not after it. The example should be the executable form of the documented relationship diagram.
- Account and portfolio examples should be chainable. The account workflow
should default to reusing the portfolio example output, then assign the
created
Portfolio.uidas a portfolio target position.
Non-Goals
- Do not create
AssetTablerows for portfolios. - Do not use
IndexTableorportfolio_index_uidas the target-position identity. - Do not introduce a generic
PositionTargetTable. - Do not support arbitrary future target kinds in this ADR.
- Do not keep backward-compatible
asset_identifierwrite paths when the storage contract is migrated. - Do not move core account registry MetaTables into
msm_portfolios.
Implementation Tasks
- [x] Move portfolio-aware target-position storage ownership to
core
msmtogether with account target-allocation registry rows. - [x] Replace
TargetPositionsStorage.asset_identifierwithtarget_type,target_uid,asset_uid, andportfolio_uid. - [x] Change
TargetPositionsStorage.__index_names__to["time_index", "position_set_uid", "target_type", "target_uid"]. - [x] Add SQL checks enforcing exactly one concrete target FK and consistency
between
target_type,target_uid,asset_uid, andportfolio_uid. - [x] Add indexes for
asset_uid,portfolio_uid, and(position_set_uid, target_type, target_uid). - [x] Update target-position frame builders to accept exactly one of
asset_uidorportfolio_uidand to populatetarget_typeandtarget_uid. - [x] Update target-position validation to reject
asset_identifierpayloads and any row with both or neither target FK. - [x] Update account target-position snapshot services to return target metadata for both asset and portfolio rows.
- [x] Add a portfolio-target expansion service that resolves
portfolio_uidto current portfolio weights only when a downstream workflow explicitly asks for asset-level exposure. - [x] Update account and portfolio examples to publish target rows containing both a direct asset target and a portfolio target.
- [x] Make the account example chain the reusable portfolio example by default
and use the resulting
Portfolio.uidas the portfolio target. - [x] Update account docs, portfolio docs, tutorial docs, examples, packaged skills, and ASCII diagrams according to the Documentation Update Requirements section.
- [x] Generate the Alembic migration under the active namespace version graph.
- [x] Add tests for asset target rows, portfolio target rows, invalid mixed target rows, storage index names, service payload validation, and portfolio expansion behavior.
Success Criteria
This ADR is complete only when:
- target-position rows can reference either
AssetTable.uidorPortfolioTable.uid; - target-position storage no longer uses
asset_identifier; - storage identity is
(time_index, position_set_uid, target_type, target_uid); - SQL and service validation enforce exactly one concrete target reference;
- core
msmdoes not importmsm_portfolios; - examples demonstrate one direct asset target and one portfolio target;
- docs make clear that portfolio target rows are mandate exposure, not custody holdings and not portfolio indices;
- account docs, portfolio docs, tutorial docs, examples, packaged skills, and
diagrams no longer describe target positions as asset-only or based on
asset_identifier.