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.
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:
user_id
is
identity:
who
authenticated
and
who
acted.
customer_id
is
the
legal/compliance
counterparty.
account_id
is
the
execution,
ledger,
and
position
account.
margin_group_id
is
the
risk/collateral
pool.
ubo_user_id
should
remain
a
transitional
navigation/backfill
aid,
not
the unit
for
margin
or
equity.
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:
user_id.
account_id.
account_permissions.
trading_accounts.ubo_user_id
was
added
as
a
stepping-stone
owner
column.
The plan intentionally leaves cross-account margin open:
Leaning no - independent per account. Needs to become a stated invariant: it decides whether
current_balanceskeys onaccount_idalone or needs amargin_group_id.
This RFC makes that invariant explicit and defines how AX can relax it later without overloading UBO, user, or onboarding concepts.
The
codebase
is
already
moving
toward
account_id
for
money
state:
trading_accounts
and
account_permissions
exist.
trading_accounts.ubo_user_id
exists
and
is
nullable
for
system
accounts.
current_balances,
risk
snapshots,
deposit
wallets,
loss
limits,
and
similar money-bearing
rows
are
account-keyed
or
in
flight.
AccountRiskSnapshot.
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.
Public exchange and broker models mostly separate operational account hierarchy from margin pooling.
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 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 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.
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.
ubo_user_id.
account_permissions;
operational
access
remains
separate.
customersAX
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.
margin_groupsMargin,
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.
This supports the real cases AX needs:
ubo_user_id
is
transitionaltrading_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.
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.
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-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
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 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:
trading_accounts.customer_id
must
match
the
owning margin_groups.customer_id.
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.
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.
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.
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.
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.
Current onboarding is user-shaped:
onboarding_applications.user_id
is
unique.
beneficial_owners
hang
off
the
onboarding
application.
risk_assessments
contain
encrypted
customer_*
fields
but
are
not
a durable
customer
principal.
Target shape:
user.
customer.
customer_users
row.
beneficial_owners,
documents,
and
risk
assessments
move
from
being user-owned
to
customer/application-owned
over
time.
The migration can be incremental:
customers,
customer_users,
and
margin_groups.
trading_accounts.ubo_user_id.
customer_id
and
margin_group_id
to
new
account
creation.
customer_id.
ubo_user_id
compatibility-only.
| 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. |
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.
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.
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.
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.
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.
customer_id
null?
customer_users
permission
matrix
needed
immediately,
or
can
it
wait until
onboarding/admin
UX
moves
off
user_id?
margin_group_id
live
directly
on
trading_accounts,
or
should
AX
use a
history
table
so
account
movement
between
groups
is
auditable
by
time?
cross_account
mode?
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.