RFC: Customers and Margin Groups

Date: 2026-06-19

Status: Draft

Scope: the ownership and margin grouping model for multiple AX trading accounts under the same legal or beneficial owner. This RFC extends the multi-accounts migration plan; it does not change the current per-account risk implementation by itself.

1. Summary

AX should introduce a first-class customer concept now, but should not use customer_id as the margin key. The margin grouping unit should be an explicit margin_group_id:

customer -> margin_group -> trading_account

Every account starts in its own margin group, preserving today's per-account margin, equity, and liquidation semantics. AX can later opt several accounts into one margin group only when the legal agreement, risk approval, liquidation policy, and implementation all support cross-collateralization.

The important distinction:

ubo_user_id should remain a transitional navigation/backfill aid, not the unit for margin or equity.

2. Background

The original accounts proposal separated user accounts from trading accounts so that one user could access several accounts, and several users could access one account with different permissions. It also noted the missing piece: if AX wants to monitor LTV, maintenance margin, equity, or liquidation across a set of accounts, that set has to be built explicitly.

The multi-accounts plan has mostly answered the identity/ownership/membership split:

The plan intentionally leaves cross-account margin open:

Leaning no - independent per account. Needs to become a stated invariant: it decides whether current_balances keys on account_id alone or needs a margin_group_id.

This RFC makes that invariant explicit and defines how AX can relax it later without overloading UBO, user, or onboarding concepts.

3. Current Implementation Notes

The codebase is already moving toward account_id for money state:

One critical caveat remains: order-gateway is not yet safe for real second accounts. It authorizes an optional account_id, but the hot path still uses the authenticating user_id for EP3 identity, open-order margin, position cache lookups, and OG margin publication to the default account. Before enabling a customer with more than one tradable account, order-gateway must carry the resolved account_id end to end.

That prerequisite exists regardless of whether AX stays per-account or adds margin groups.

4. External Survey

Public exchange and broker models mostly separate operational account hierarchy from margin pooling.

Deribit

Deribit subaccounts are created under a main account to organize trading and manage risk separately. Account summaries are retrieved per account or subaccount and expose balance, equity, available funds, initial margin, and maintenance margin. Margin model changes target the authenticated account or a specified subaccount. Transfers move funds between subaccounts or between the main account and a subaccount.

Deribit is the closest crypto derivatives analogue: a subaccount is a separate risk unit unless the platform explicitly applies a chosen margin model inside that account.

Binance

Binance exposes master/subaccount APIs for creating virtual subaccounts, enabling margin or futures per subaccount, querying subaccount futures position risk, and transferring assets between master and subaccounts. The API shape is per subaccount for risk and per transfer for funding, not implicit UBO-level netting.

Coinbase Prime

Coinbase Prime markets the opposite institutional model: unified risk and cross-margining across spot and derivatives in one prime brokerage platform. This is an explicit financing/cross-margin product, not a side effect of a user owning multiple accounts.

Interactive Brokers

IBKR advisor and institutional structures distinguish master/advisor users, client accounts, fund accounts, and proprietary trading group subaccounts. The master account is an operational and reporting control plane. It is not a safe stand-in for the legal owner or for a collateral pool across all underlying accounts.

5. Goals

  1. Preserve current per-account margin behavior by default.
  2. Make the grouping unit explicit before any customer has multiple accounts.
  3. Introduce a durable legal/compliance customer concept instead of stretching onboarding rows or ubo_user_id.
  4. Allow future cross-margin accounts without another identity rekey.
  5. Keep the hot trading path simple for the common case: one account, one margin group.

6. Non-Goals

7. Decisions

D1: Add customers

AX should add a first-class customers table for legal/compliance ownership. A customer is the entity AX onboarded and contracts with: individual, corporation, LLC, partnership, trust, fund, or other institution.

customer replaces the overloaded use of user as the customer record over time. It does not replace user as identity.

D2: Add margin_groups

Margin, equity sharing, and liquidation coupling are keyed by margin_group_id, not by customer_id, user_id, or ubo_user_id.

Default: one margin group per account.

D3: A customer may own several margin groups

This supports the real cases AX needs:

D4: ubo_user_id is transitional

trading_accounts.ubo_user_id is useful during the migration because current onboarding is user-shaped. It should become either a derived compatibility view or a nullable legacy column after customer_id exists.

It must not be used to net margin. A UBO can control multiple unrelated legal entities, and a legal entity can have multiple beneficial owners.

D5: Account-level snapshots remain first-class

Even when a margin group contains multiple accounts, account-level balances, positions, fills, orders, and statements remain account-keyed. Group-level risk is an additional view and hot-path input, not a replacement for account accounting.

8. Proposed Domain Model

Customer

customer is the onboarded legal or natural person.

Candidate schema:

CREATE TABLE customers (
    id CHAR(16) PRIMARY KEY,
    display_name TEXT NOT NULL,
    customer_type TEXT NOT NULL,
    primary_user_id CHAR(16) REFERENCES users(id),
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

primary_user_id is a convenience pointer for legacy signup and admin UX, not authorization. Customer-level permissions should be modeled separately when AX needs them.

Customer Users

Customer-level access is not the same as account trading permissions. A user may be allowed to update KYC or view documents without being allowed to trade an account.

Candidate schema:

CREATE TABLE customer_users (
    customer_id CHAR(16) NOT NULL REFERENCES customers(id),
    user_id CHAR(16) NOT NULL REFERENCES users(id),
    can_view_kyc BOOLEAN NOT NULL DEFAULT FALSE,
    can_update_kyc BOOLEAN NOT NULL DEFAULT FALSE,
    can_manage_accounts BOOLEAN NOT NULL DEFAULT FALSE,
    updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (customer_id, user_id)
);

This is intentionally separate from account_permissions.

Margin Groups

margin_groups names the collateral pool and liquidation unit.

Candidate schema:

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')
    )
);

isolated_account is the default mode. cross_account requires explicit risk approval and a liquidation playbook.

Trading Accounts

Trading accounts gain both legal owner and margin group pointers:

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

Invariants:

9. Margin Semantics

Per-Account Default

For the default one-account group:

group_equity = account_equity
group_initial_margin = account_initial_margin
group_maintenance_margin = account_maintenance_margin

This is today's behavior under a more explicit key.

Cross-Account Group

For a multi-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 margin model may initially be conservative and additive:

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

That gives shared equity without cross-account position offsets. Cross-account offsets should be a later, explicit portfolio-margin feature.

Admission Control

Order admission should check the margin group that contains the order's account_id.

For one-account groups, this degenerates to today's per-account check.

For cross-account groups, the order gateway needs either:

The second is harder to make correct under sharding. The preferred design is a group-level risk snapshot written by risk-engine and consumed by order-gateway.

Liquidation

The liquidation unit is the margin group. If accounts share equity, AX must be allowed to liquidate any account in the group according to the group's liquidation policy.

Therefore a cross-account group requires explicit customer agreement and admin approval. It is not a UI toggle.

10. Risk Snapshot Shape

Keep account snapshots:

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

Add group snapshots only when a group contains more than one account or when a consumer asks for group-level risk:

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,
}

Do not remove account risk snapshots. Account-level reporting, statements, and debugging need them even under cross-margin.

11. Onboarding Impact

Current onboarding is user-shaped:

Target shape:

The migration can be incremental:

  1. Add customers, customer_users, and margin_groups.
  2. Backfill one customer per non-null trading_accounts.ubo_user_id.
  3. Backfill one margin group per trading account.
  4. Add customer_id and margin_group_id to new account creation.
  5. Repoint onboarding and admin views to customer_id.
  6. Make ubo_user_id compatibility-only.

12. Scheme Comparison

Scheme Grouping unit Behavior Assessment
Per-account margin account_id Each account has isolated equity, IM/MM, and liquidation. Best immediate behavior.
Customer aggregate monitor customer_id Margin stays per-account, but exposure/LTV/loss limits can be monitored across all customer accounts. Good near-term addition.
Customer-level cross-margin customer_id Every account under a customer shares equity. Too coarse; rejects valid isolated strategy/accounting needs.
Explicit margin groups margin_group_id Accounts share equity only when assigned to the same group. Recommended end state.
UBO-level cross-margin ubo_user_id All accounts owned by one UBO share equity. Rejected; legally and operationally wrong.

13. Rollout Plan

Phase 0: State the invariant

Document that all accounts are independently margined unless they share an explicit margin group. Until margin_groups exists, every account is treated as its own implicit margin group.

Phase 1: Add customer and margin-group tables

Add the schema with a one-customer-per-current-UBO backfill and one-margin- group-per-account backfill. This should be additive and should not change any hot-path behavior.

Phase 2: Account-key order-gateway

Carry the resolved account_id through order placement, cancel/replace, position cache lookups, open-order margin, EP3 participant selection, event routing, and OG margin publication.

This is a prerequisite for any real second account, independent of cross-margin.

Phase 3: Customer-level views and monitors

Add customer pages and risk-monitor aggregate checks that sum or list the customer's accounts. These should be monitoring and admin controls only, not order admission.

Phase 4: Explicit cross-account margin groups

Allow approved accounts under the same customer to share one group. Start with conservative additive margin and shared equity; defer cross-account position offsets until portfolio margin infrastructure exists.

14. Open Questions

15. Recommendation

Adopt customer -> margin_group -> trading_account as the end-state model.

Implement customer and one-account margin_groups additively before the first real multi-account customer. Keep margin per account by default. Do not net by UBO. Do not net by customer implicitly. Cross-account margin should be an explicit risk product represented by margin_group_id.