Virtual Portfolio Design
User-owned reporting overlays that aggregate real portfolios and books across the book tree
Table of Contents
Related Plans
- Workspace Design — workspaces reference a portfolio via
scope_portfolio_idto define their trade scope. Virtual portfolios are the mechanism that lets traders compose a cross-desk scope without restructuring real books. This is a soft dependency of the workspace plan: workspaces work with real books as scope; virtual portfolios extend the scope options.
Background
The product backlog contains two relevant stories:
- Add virtual portfolio tree support (backlog line 1240): junction table
ores_refdata_virtual_portfolio_books,is_virtualflag,PortfolioBookTreeMdiWindowintegration, trade table filtering. - Investigate virtual portfolio arbitrary hierarchy construction (backlog
line 5701): whether
parent_portfolio_idis sufficient; cross-legal-entity considerations; UI hierarchy selection.
This plan resolves both stories with the following design decisions made: separate tree for virtual portfolios; user ownership with explicit sharing; members may be real portfolios or books; cross-party membership allowed.
Problem Statement
The real portfolio tree is a strict hierarchy: Firm → Division → Desk →
Portfolio → Book. A book has exactly one parent_portfolio_id, reflecting
legal/organisational ownership. Restructuring real books has legal,
accounting, and access-control implications.
A trader frequently needs a view that crosses this hierarchy: "show me all EUR trades across my Rates and FX books", or "show me everything I personally manage". A virtual portfolio is a read-only reporting overlay — a named collection of real portfolios and/or books drawn from anywhere in the tree. It owns no trades and imposes no organisational structure.
Cross-Party Membership
Virtual portfolios may contain books and portfolios from different legal entities (parties). Real-world use cases:
- Fund manager: manages positions across Fund A and Fund B (separate legal entities) for a consolidated DV01 hedge view.
- Group risk function: central risk team needs group-level consolidated exposure across multiple legal entities.
- Multi-entity desk: trades booked in different entities for regulatory reasons; trader needs a single P&L view.
Cross-party membership is permitted. Book visibility is enforced by the
existing RLS policies on ores_trading_books_tbl — books the user cannot see
are silently excluded from the virtual portfolio's effective membership.
The UI shows a visual indicator ("N of M members visible") when effective
membership is smaller than defined membership.
Design
Virtual portfolios are roots in their own tree
Virtual portfolios do not appear in the system portfolio tree used by real portfolios and system report definitions. They are always root nodes, displayed in a separate "My Virtual Portfolios" section of the portfolio explorer. This separation ensures that:
- System report definitions (which aggregate by real portfolio hierarchy) are not confused by user-defined views.
- The system tree remains a clean representation of organisational ownership.
- Real portfolios cannot have virtual portfolios as children — this is structurally impossible since virtual portfolios live in a separate context.
Virtual portfolios have no parent_portfolio_id in the system tree.
Grouping of virtual portfolios (e.g. "My FX views", "My Risk views") may be
added later as a separate organisational layer within the virtual portfolio
section; it is not part of this design.
Members: real portfolios and books
A virtual portfolio's members may be any combination of:
- Real portfolios (including all books beneath them, resolved recursively)
- Real books (directly)
Virtual portfolios may not be members of another virtual portfolio. This avoids recursive resolution complexity. The constraint is enforced at write time.
Two junction tables to maintain clean FK semantics:
-- Virtual portfolio → real portfolio membership create table ores_trading_vp_portfolio_members_tbl ( virtual_portfolio_id uuid not null references ores_trading_portfolios_tbl(id), portfolio_id uuid not null references ores_trading_portfolios_tbl(id), primary key (virtual_portfolio_id, portfolio_id), constraint vp_portfolio_members_no_virtual_member check ( (select not is_virtual from ores_trading_portfolios_tbl where id = portfolio_id) ), constraint vp_portfolio_members_owner_is_virtual check ( (select is_virtual from ores_trading_portfolios_tbl where id = virtual_portfolio_id) ) ); -- Virtual portfolio → book membership create table ores_trading_vp_book_members_tbl ( virtual_portfolio_id uuid not null references ores_trading_portfolios_tbl(id), book_id uuid not null references ores_trading_books_tbl(id), primary key (virtual_portfolio_id, book_id), constraint vp_book_members_owner_is_virtual check ( (select is_virtual from ores_trading_portfolios_tbl where id = virtual_portfolio_id) ) );
Portfolio table changes
alter table ores_trading_portfolios_tbl add column is_virtual boolean not null default false, add column owner_user_id text null; -- owner_user_id is set (non-null) for all virtual portfolios. -- Real portfolios always have owner_user_id = null. -- Real portfolios may not reference virtual portfolios as parent. -- (Structural separation means this cannot happen via the UI, but -- enforce at DB level for integrity.) alter table ores_trading_portfolios_tbl add constraint portfolios_no_virtual_parent check ( parent_portfolio_id is null or (select not is_virtual from ores_trading_portfolios_tbl p2 where p2.id = parent_portfolio_id) );
User ownership and sharing
Virtual portfolios are owned by the user who created them. The owner may grant other users read or manage access.
create table ores_trading_vp_permissions_tbl ( virtual_portfolio_id uuid not null references ores_trading_portfolios_tbl(id), grantee_user_id text not null, permission text not null check (permission in ('read', 'manage')), granted_by text not null, granted_at timestamptz not null default now(), primary key (virtual_portfolio_id, grantee_user_id) );
Access rules:
- Owner (
owner_user_id): full read + manage + delete + share. - manage grantee: can edit members and grant
readto others; cannot delete or grantmanage. - read grantee: can see the virtual portfolio and use it as a workspace scope; cannot edit members.
- Users with no row in the permissions table cannot see the virtual portfolio at all (enforced by RLS on the portfolios table for virtual rows).
Resolving a virtual portfolio to its effective books
-- Step 1: collect direct book members select book_id from ores_trading_vp_book_members_tbl where virtual_portfolio_id = :vp_id union -- Step 2: collect books under portfolio members (recursive) with recursive descendants(portfolio_id) as ( select pm.portfolio_id from ores_trading_vp_portfolio_members_tbl pm where pm.virtual_portfolio_id = :vp_id union all select p.id from ores_trading_portfolios_tbl p join descendants d on p.parent_portfolio_id = d.portfolio_id ) select b.id as book_id from ores_trading_books_tbl b join descendants d on b.parent_portfolio_id = d.portfolio_id; -- RLS on ores_trading_books_tbl silently excludes books the user cannot see.
The result is the effective book set. For workspace scope, this query replaces the real-portfolio recursive descent used for non-virtual portfolios.
UI
Portfolio explorer — separate virtual portfolio section
The PortfolioBookTreeMdiWindow shows two distinct sections:
▼ PORTFOLIOS
▶ Firm
▶ Division A
▶ EUR Rates Desk
■ EUR Book 1
■ EUR Book 2
▶ FX Desk
...
▼ MY VIRTUAL PORTFOLIOS
▼ EUR + FX Combined [2 portfolios, 5 books] ⚠ 1 member not visible
▶ EUR Rates Desk (real portfolio)
▶ FX Desk (real portfolio)
■ Special Book (direct book member)
▼ My Overnight Hedges [3 books]
■ ...
- Virtual portfolio nodes use a distinct icon (
briefcase_20_regular). ⚠ N member(s) not visiblebadge when effective membership < defined membership (cross-party books the user lacks access to).- Selecting a virtual portfolio node filters the trade table to its effective books, identical in behaviour to selecting a real portfolio node.
Virtual portfolio editor
Accessible via right-click on any virtual portfolio node or "New Virtual Portfolio" action.
- Name field.
- Members panel: searchable picker showing both real portfolios (with full
path
Firm / Division / Desk / Portfolio) and books. Selected members listed with remove button. - Sharing panel: list of grantees with permission level; add/remove grants.
- Effective membership count shown live as members are added/removed.
Workspace scope picker
When setting a workspace's scope_portfolio_id, the picker shows:
- Real portfolios (labelled with full path)
- User's virtual portfolios (labelled "[VP] name")
Both in a unified searchable list, clearly distinguished.
Implementation Phases
Phase 1: Schema
- Add
is_virtualandowner_user_idto portfolios table. - Add no-virtual-parent constraint.
- Create
ores_trading_vp_portfolio_members_tblandores_trading_vp_book_members_tbl. - Create
ores_trading_vp_permissions_tbl. - Add RLS policy on portfolios table: virtual rows visible only to owner and permission grantees.
- Add full SQL machinery: triggers, NOTIFY.
- Run
recreate_database.sh; validate schemas.
Phase 2: Domain and service layer
- Extend portfolio domain type with
is_virtual,owner_user_idfields. - New domain type
virtual_portfolio_members(portfolio IDs + book IDs). - NATS endpoints:
portfolio.v1.virtual.createportfolio.v1.virtual.update-membersportfolio.v1.virtual.update-permissionsportfolio.v1.virtual.deleteportfolio.v1.virtual.list— list visible virtual portfolios for callerportfolio.v1.virtual.resolve— expand to effective book IDs
- Extend
portfolio.v1.listto include virtual portfolios (flagged).
Phase 3: UI
PortfolioBookTreeMdiWindow: separate virtual portfolio section; distinct icon; "N member(s) not visible" badge; book/portfolio leaves.- Virtual portfolio editor dialog (members + sharing).
- Trade table filtering for virtual portfolio node selection.
- Workspace scope picker: unified real + virtual list.
Open Questions
- Grouping of virtual portfolios: the current design has no nesting within the virtual portfolio section. If users accumulate many virtual portfolios, a flat list becomes unwieldy. Grouping (folders or tags) can be added later without changing the underlying schema.
- Stale indicator: when a member book or portfolio is added/removed while the trade table is open, the view should prompt a refresh. Define the NOTIFY payload and UI response.
- Shared virtual portfolios in workspace scope: if user A creates a virtual
portfolio and grants user B read access, can user B use it as a workspace
scope? Proposed: yes, if B has at least
readpermission.