SoW: Desk management / Margin groups

Master: SoW: Desk management. Not scheduled. No step of the master SoW needs this work.

Scope: margin grouping and margin behavior for multiple AX trading accounts. Customer account access owns customers, membership, access management, and trading_accounts.customer_id. This SoW does not redefine those records.

1. Summary

AX must not use a customer, user, or UBO as an implicit margin key. The margin and liquidation unit is an explicit margin_group_id:

customer -> margin_group -> trading_account

Every account starts in its own margin group. This preserves current per-account margin, equity, and liquidation behavior. AX can put several accounts in one group only after the legal agreement, risk approval, liquidation policy, and implementation support cross-collateralization.

Common customer ownership does not permit implicit netting.

2. Background

AX already separates user identity from account permissions and account money state. Customer account access adds the durable customer owner and membership model. This SoW supplies the remaining risk invariant: accounts are independently margined unless an explicit approved margin group joins them.

Before AX enables a second tradable account, order-gateway must carry the resolved account_id through EP3 identity, open-order margin, position cache lookups, event routing, and margin publication. This prerequisite applies to isolated and cross-account margin. Ticket W5.6 in Accounts and transfers verifies it.

Public exchange and broker models also separate operational account hierarchy from collateral pooling. Subaccounts commonly remain separate risk units. Unified or cross-margin products make pooling an explicit contract and risk feature.

3. Goals

  1. Preserve per-account margin by default.
  2. Make the collateral and liquidation unit explicit.
  3. Support future approved cross-account groups without another identity migration.
  4. Keep account-level balances, positions, fills, orders, statements, and risk snapshots first-class.
  5. Keep the common one-account group simple on the trading hot path.

4. Non-goals

5. Decisions

D1: Use an explicit margin-group key

Margin, shared equity, and liquidation coupling use margin_group_id, not customer_id or user_id.

D2: One account is the default group

Each account starts in a separate group. Adding the schema does not change margin behavior.

D3: One customer can own several groups

The same customer can have isolated strategy accounts and, where approved, a separate cross-account pool. Common ownership alone does not combine them.

D4: Account snapshots stay first-class

Group risk is an additional hot-path input and view. It does not replace account accounting, history, statements, or diagnostics.

D5: The margin group is the liquidation unit

If accounts share equity, AX can liquidate accounts in the group under the approved group policy. Cross-account mode is an admin-controlled risk product, not a customer UI toggle.

6. Domain model

Customer account access supplies customers, customer_members, and trading_accounts.customer_id. This SoW adds only margin-group state:

CREATE TABLE margin_groups (
    id CHAR(16) PRIMARY KEY,
    customer_id CHAR(16) NOT NULL REFERENCES customers(id),
    name TEXT NOT NULL,
    mode TEXT NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT margin_group_mode_valid CHECK (
        mode IN ('isolated_account', 'cross_account')
    )
);

ALTER TABLE trading_accounts
    ADD COLUMN margin_group_id CHAR(16) REFERENCES margin_groups(id);

Invariants:

7. Margin semantics

Per-account default

group_equity = account_equity
group_initial_margin = account_initial_margin
group_maintenance_margin = account_maintenance_margin

This is current behavior under an explicit key.

Cross-account group

group_equity = sum(account balances + unrealized PnL)
group_initial_margin = margin_model(all group positions, all group open orders)
group_maintenance_margin = margin_model(all group positions)
group_available_initial_margin = group_equity - group_initial_margin
group_available_maintenance_margin = group_equity - group_maintenance_margin

The first model can be conservative and additive:

group_initial_margin = sum(account_initial_margin)
group_maintenance_margin = sum(account_maintenance_margin)

This shares equity but gives no cross-account position offset. Portfolio-margin offsets require a separate approval and design.

Admission control

Order admission checks the margin group that contains the order's account_id. A one-account group reduces to the current per-account check.

For a cross-account group, risk-engine publishes a coherent group-level snapshot. Order-gateway consumes that snapshot and adds the group's open-order overlay. Missing, stale, or version-conflicting required group state blocks new exposure.

Liquidation

The liquidation unit is the margin group. Cross-account activation requires a customer agreement, an admin approval, and a tested liquidation policy. The policy must state account selection, order cancellation, close-out ordering, recovery after restart, and the treatment of partial venue failure.

8. Risk snapshot shape

Keep account snapshots:

risk:{account_id}
og:margin:{account_id}

Add group snapshots only when required:

risk_group:{margin_group_id}
og:margin_group:{margin_group_id}

Candidate public type:

struct MarginGroupRiskSnapshot {
    margin_group_id: String,
    customer_id: String,
    account_ids: Vec<String>,
    equity: Decimal,
    initial_margin_required_total: Decimal,
    maintenance_margin_required: Decimal,
    initial_margin_available: Decimal,
    maintenance_margin_available: Decimal,
}

Account snapshots remain available under cross-margin for statements, reporting, reconciliation, and debugging.

9. Scheme comparison

Scheme Grouping unit Behavior Assessment
Per-account margin account_id Isolated equity, margin, and liquidation. Required default.
Customer aggregate monitor customer_id Observes related accounts without sharing buying power. Useful control, not margin pooling.
Customer-level cross-margin customer_id Every customer account shares equity. Rejected as too coarse.
Explicit margin groups margin_group_id Only approved group members share equity. Recommended end state.
Applicant-level cross-margin the former ubo_user_id All accounts for one applicant share equity. Rejected, and the column is removed by the access SoW.

10. Rollout

  1. State the invariant. Treat every account as its own implicit group until the schema exists.
  2. Add margin-group storage. Backfill one isolated group per customer account. Use the customer mapping from step 2 of the master SoW. Do not create or change membership.
  3. Finish account-key order-gateway. Carry account_id through every enabled order, margin, venue, and event path.
  4. Publish group risk. Add versioned group snapshots and monitoring while every group still has one account. Verify that results equal account risk.
  5. Enable approved cross-account groups. Start with additive margin and shared equity. Require admission, liquidation, restart, and reconciliation evidence before activation.

11. Open questions

12. Recommendation

Adopt explicit margin groups. Keep one account per group by default. Use the customer model from Customer account access, but do not use customer ownership as a margin decision. Cross-account margin is an explicit risk product with its own admission, liquidation, recovery, and approval rules.