Status: RELEASED. Pre-1.0: the data model is exercised against real data before any v1.0, and a minor version may break; this one does not.
Changes from 0.3.0 (additive, the rest clarifies what 0.3.0 left unsaid):
from and until, the
interval during which the share holds, so a share that changes (a gift, a
marriage, a buy-in) is recorded as history rather than rewritten. Two rows for
the same subject and holding MUST NOT overlap (section 5.7).closedAt, nor before its openedAt; a producer SHOULD
still emit a final valuation at closure (sections 5.3, 6, 14).amount raises the
holding’s value under its sign convention: on a liability, a drawdown is positive
and a repayment negative. The legs of one transferId net to zero once each is
signed by its holding’s nature (section 5.6).parentId
loop is an integrity error. In the derivation, each holding is signed by its own
nature: a liability held inside an asset account is subtracted, not added
(sections 4, 6).date format on top of the
YYYY-MM-DD pattern, so 2026-02-30 is refused as a timestamp without a zone
already was (section 3).A document valid under 0.3.0 stays valid here unless it carried a date that does not exist.
Asset Notation is an open, portable data format for a person’s or household’s assets and liabilities over time: the accounts, positions, properties, debts and their values, across institutions and asset classes, in a single self-describing document that any application can read and write. The consolidated net worth is one thing it makes derivable, but the format itself is the broader notation: it records what you own and owe, not a dashboard.
It exists to fill a concrete gap: today wealth data is locked inside each aggregator and institution, with no neutral interchange format. Asset Notation is the neutral payload. It does not replace bank-messaging standards (ISO 20022) or brokerage feeds; it consolidates their outputs into one portable picture the owner controls.
In scope (v0.4): subjects/owners, institutions, holdings (the single generic asset-or-liability entity), optional instruments for market-priced positions, valuations over time, optional transactions, ownership shares.
Out of scope (v0.4): distribution of market-data and FX rate tables, tax computation, budgeting categories, performance analytics, real-time messaging. These belong in tools or in namespaced extensions, not in the core. A record MAY however capture the single FX rate it was booked at (sections 5.5, 5.6); what stays out of scope is serving rates as data.
kind
says what flavour it is; a nature (asset or liability) says which side of the
balance sheet it sits on. There is no separate account / holding / liability
hierarchy to learn or to migrate.kind / source
values are preserved, not rejected. App-specific data lives in namespaced
extensions, keeping the core small. New kinds and new fields never force a migration.YYYY-MM-DD) that exist: the schema
refuses 2026-02-30. Timestamps are RFC 3339
(2026-06-24T20:00:00Z), UTC unless an offset is given. Where a field records when
something held or happened - a valuation’s asOf, a transaction’s date - it MAY be
either a date or a timestamp; consumers SHOULD compare them as instants, treating a
bare date as the start of that day, UTC.EUR, USD).FR, US).{ "amount": string, "currency": string }, where amount is
a base-10 decimal string (e.g. "1234.56", "-50.00"). Producers MUST NOT emit
floating-point numbers for monetary values. Quantities (share counts, units) are
also decimal strings to avoid precision loss, and so are ownership shares
(section 5.7).id, a string unique within its collection. An
id is opaque and stable across exports of the same logical thing (a UUID or a
stable slug is RECOMMENDED). External identifiers (ISIN, IBAN, BIC, LEI, …) live in
an ids object and are never used as the id.A document is a single JSON object:
{
"assetnotation": "0.4.0",
"generatedAt": "2026-07-08T09:00:00Z",
"generator": { "name": "example", "version": "1.0.0" },
"baseCurrency": "EUR",
"subjects": [],
"institutions": [],
"holdings": [],
"instruments": [],
"valuations": [],
"transactions": [],
"ownership": [],
"extensions": {}
}
| Field | Req | Type | Notes |
|---|---|---|---|
assetnotation |
MUST | string | Spec version major.minor.patch of this document. |
generatedAt |
MUST | timestamp | When the document was produced. |
generator |
MAY | object | { name, version } of the producing app. |
baseCurrency |
SHOULD | ISO 4217 | Reporting currency for derived totals. |
subjects |
SHOULD | Subject[] | Owners. If omitted, a single implicit owner is assumed. |
institutions |
MAY | Institution[] | Banks, brokers, lenders. |
holdings |
MUST | Holding[] | The assets and liabilities. The heart of the document. |
instruments |
MAY | Instrument[] | Market-priced securities referenced by holdings. |
valuations |
SHOULD | Valuation[] | Time-stamped values of holdings. |
transactions |
MAY | Transaction[] | Optional movement history. |
ownership |
MAY | Ownership[] | Shares. Defaults to 100% to the primary subject. |
extensions |
MAY | object | Namespaced app-specific data (section 10). |
Collections are unordered sets keyed by id. This maps directly onto a
schema-agnostic store of named collections (section 12).
Document integrity. An id MUST be unique within its collection. References
(holdingId, parentId, institutionId, instrumentId, subjectId,
counterpartHoldingId, a link’s holdingId) SHOULD resolve to an entity in the same
document, but MAY dangle: any subset of a valid document is itself valid (section 11),
so a partial disclosure can omit a referenced entity. A consumer MUST tolerate an
unresolved reference - treat the relation as unknown - and MUST NOT reject the
document for it. A chain of parentId MUST be acyclic: a holding is never its own
ancestor, since the derivation of section 6 walks the chain and a loop has no root.
The reference validator rejects duplicate ids and cycles, and reports unresolved
references informatively (section 13).
A person or legal entity that can own assets and owe liabilities.
| Field | Req | Type | Notes |
|---|---|---|---|
id |
MUST | string | |
type |
MUST | enum | person, entity. |
name |
SHOULD | string | Display name. |
kind |
MAY | enum | e.g. self, spouse, child, company, trust, sci, joint. Open. |
country |
MAY | ISO 3166 | Tax residence or registration. |
ids |
MAY | object | e.g. { "lei": "..." } for legal entities. |
A bank, broker, insurer, exchange, custodian or lender.
| Field | Req | Type | Notes |
|---|---|---|---|
id |
MUST | string | |
name |
SHOULD | string | |
kind |
MAY | string | Open. RECOMMENDED: bank, broker, insurer, exchange, custodian, other. A consumer MUST treat an unknown kind generically. |
country |
MAY | ISO 3166 | |
ids |
MAY | object | { "bic"?: ISO 9362, "lei"?: ISO 17442 }. |
Anything of value, owned or owed. A bank account, a savings book, a brokerage position, a directly-held house or car, a mortgage or a consumer loan are all Holdings. Its values over time are Valuations (5.5); its movements, if any, are Transactions (5.6).
| Field | Req | Type | Notes |
|---|---|---|---|
id |
MUST | string | |
kind |
MUST | string | Open: checking, savings, brokerage, retirement, life_insurance, crypto_wallet, cash, real_estate, vehicle, valuable, mortgage, loan, credit_line… A consumer MUST accept an unknown kind and render it generically. |
nature |
MUST | enum | asset or liability. Drives the sign in net worth. |
label |
SHOULD | string | Display name. |
currency |
SHOULD | ISO 4217 | The holding’s native currency. |
parentId |
MAY | ref | The containing holding (a stock in a fund, a fund in a securities account). A holding with no parent is a root. |
institutionId |
MAY | ref | -> Institution. |
ids |
MAY | object | { "iban"?: ISO 13616, "bic"?, "number"? }. number SHOULD be masked. |
instrumentId |
MAY | ref | -> Instrument, for a market-priced position. |
quantity |
MAY | decimal string | Units held, when instrumentId is set. |
costBasis |
MAY | Money | Total acquisition cost of the current position, in any currency. Informative: a tool MAY maintain it from transactions. A corporate action (5.6) changes quantity but leaves costBasis unchanged. |
links |
MAY | Link[] | Relations to other holdings (below). |
attributes |
MAY | object | Type-specific scalars the core does not model (a loan’s rate, a property’s address…). Forward-compatible: producers put extra kind-specific data here, not in core fields. |
openedAt / closedAt |
MAY | date | The holding exists from openedAt inclusive to closedAt exclusive; outside that interval it contributes nothing to a derived total (section 6). A producer SHOULD emit a final valuation at closure (a zero balance, the payoff of a loan), because a consumer that reads the last value alone would otherwise show a balance that no longer exists. |
A Link is { "rel": string, "holdingId": ref }. rel is an open vocabulary:
funded_by (a card -> its account), financed_by (a property -> its mortgage),
held_in (a position -> its account), cash_of (a cash account -> the holding it
serves: the settlement cash paired with a securities account, physically separate but
logically one envelope). Links express the relationships a 360 view needs without
nesting.
Sign convention: a holding’s values are its native signed amounts. An
account-style holding carries a signed balance (a negative balance is an overdraft or
a card debt, and lowers net worth on its own). A loan-style holding carries a
positive outstanding and is marked nature: "liability", so net worth subtracts
it. This keeps both representations consistent (section 6). A liability’s values MUST
be the outstanding amount as stated by the institution (typically positive): a
producer MUST NOT negate a value to express what nature already expresses, since the
double negative would flip the sign back.
A market-priced thing referenced by a holding, so the shared price lives once. Only needed for securities and similar; a house is just a holding with a value.
| Field | Req | Type | Notes |
|---|---|---|---|
id |
MUST | string | |
name |
SHOULD | string | |
kind |
MUST | string | Open. RECOMMENDED: security, cash, crypto, commodity, other. A consumer MUST treat an unknown kind generically. |
ids |
MAY | object | { "isin"?: ISO 6166, "figi"?, "ticker"?, "mic"?: ISO 10383 }. |
currency |
MAY | ISO 4217 | Pricing currency. |
The value of a holding, as of a date, from a stated source. Valuations form a time series; multiple per holding are expected (a statement each month, an appraisal each year, the amortized balance of a loan over its life).
| Field | Req | Type | Notes |
|---|---|---|---|
id |
MUST | string | |
holdingId |
MUST | ref | -> Holding. |
asOf |
MUST | date/timestamp | When the value held true. |
value |
MUST | Money | The total value of the holding (signed per 5.3). |
unitPrice |
MAY | Money | For an instrument-priced holding: price per unit. |
quantity |
MAY | decimal string | Units held as of asOf, making the valuation a self-sufficient position snapshot. |
fx |
MAY | FxRate | The conversion rate captured when the record was made (below). |
source |
SHOULD | string | Open: purchase, statement, market, estimate, appraisal, repayment, manual, derived… |
confidence |
MAY | number | 0..1. An estimate is softer than a statement. |
An FxRate is { "base": ISO 4217, "rate": decimal string }: one unit of the
record’s currency equals rate units of base, at the record’s date. It freezes the
conversion the producer actually used, so a foreign-currency record keeps its exact
counter-value even when historical rate feeds differ or disappear.
When quantity and unitPrice are both present and their product disagrees with
value (statement rounding), value takes precedence: it is the institution’s
stated total. A valuation whose asOf lies in the future is a projection (a
loan’s amortization schedule, a vesting plan), not a fact; a producer SHOULD mark it
with a projective source (projected, schedule) so consumers can keep facts and
forecasts apart.
A movement on a holding. Optional: a document MAY carry valuations only. Semantics align loosely with ISO 20022 / OFX.
| Field | Req | Type | Notes |
|---|---|---|---|
id |
MUST | string | |
holdingId |
MUST | ref | -> Holding. |
date |
MUST | date/timestamp | The day, or a full RFC 3339 instant when the exact time matters (a trade execution, an on-chain settlement). |
amount |
MUST* | Money | Signed from the holding’s own perspective, in the direction of its value (below). *Not required when type is corporate_action (below). |
type |
MAY | string | Open. RECOMMENDED: transfer, trade, fee, dividend, interest, income, expense, corporate_action, other. A consumer MUST treat an unknown type generically. |
factor |
MAY* | decimal string | Quantity multiplier applied by a corporate action: "2" for a 2-for-1 split, "0.5" for a reverse split, an exchange ratio for a merger. *Required when type is corporate_action. |
fx |
MAY | FxRate | The conversion rate captured at booking time (5.5). |
transferId |
MAY | string | Opaque id shared by the legs of one internal movement (below). |
description |
MAY | string | |
category |
MAY | string | Open budgeting category. |
counterpartHoldingId |
MAY | ref | The other side of the flow: a transfer target, the loan repaid, the asset funded. |
instrumentId / quantity |
MAY | ref / decimal string | For trades; for a corporate action, the instrument affected (and optionally the resulting instrument of a merger via a new instrumentId on the holding). |
Sign. A positive amount raises the holding’s value as section 5.3 states it,
a negative one lowers it. On an asset, an inflow is positive and an outflow negative.
On a liability, whose value is its positive outstanding, a drawdown or a capitalized
interest is positive and a repayment is negative. A repayment of 990 from a current
account to a mortgage is therefore -990 on the account and -990 on the mortgage:
each leg lowers its own holding’s value, and the owner’s net worth is unchanged.
Corporate actions. A split, reverse split or merger changes the number of units
held without any cash moving and without changing the capital invested. It is recorded
as a corporate_action transaction carrying a factor and no required amount:
{ "id": "t2", "holdingId": "h3", "date": "2026-06-16", "type": "corporate_action",
"factor": "2", "description": "2-for-1 stock split" }
After the event the holding’s quantity reflects the new unit count and costBasis
is unchanged. An amount MAY still appear (cash in lieu of fractional units). A
consumer that ignores corporate actions still derives correct totals from valuations;
the event exists so that unit counts, cost basis and per-unit history remain
explainable.
Internal movements. When both legs of one internal movement of value appear in the
document - the debit and credit of a transfer between two holdings, a trade and its
cash settlement - they SHOULD share a transferId, an opaque id meaningful only
within the document. Each leg’s amount is signed from its own holding’s perspective,
and the legs of one movement SHOULD net to zero once each leg is signed by its
holding’s nature (+1 asset, -1 liability) and converted by any captured FX: that
is the net-worth effect of the move, zero by definition. A consumer
computing external flows (income, spending, savings rate) SHOULD net or exclude the
transactions grouped by a transferId: without pairing, every internal transfer
double-counts as both an expense and an income. counterpartHoldingId names the other
side; transferId pairs the exact records.
Who owns how much of what. Enables joint accounts, family consolidation and holding
structures. If ownership is omitted, every holding is owned 100% by the primary
subject: the first entry of subjects, or the single implicit owner when subjects
is omitted too.
| Field | Req | Type | Notes |
|---|---|---|---|
subjectId |
MUST | ref | -> Subject. |
holdingId |
MUST | ref | -> Holding. |
share |
MUST | decimal string | In [0, 1], e.g. "0.5", "0.333333". Shares of one holding SHOULD sum to <= 1 on any day; exact thirds do not exist in decimal, so a producer SHOULD round down. |
from |
MAY | date | First day the share holds, inclusive. Absent: since always. |
until |
MAY | date | Day the share stops holding, exclusive. Absent: still holds. |
Ownership is history, like valuations: a share that changes is recorded by closing
the old row with until and adding a new one with from, never by rewriting
(section 11). Two rows for the same subjectId and holdingId MUST NOT overlap in
time; the reference validator rejects an overlap (section 13). The share of a holding
owned by a subject as of a date T is the share of the row whose interval contains T,
or zero when there is none.
A standard models facts; tools compute. Net worth for a set of subjects S as of date T:
netWorth(S, T) =
sum over LEAF holdings h (no holding names h as parentId) of
sign(h.nature) * ownValue(h, T) * ownedShare(root(h), S, T)
where ownValue(h, T) = 0 when T < h.openedAt or T >= h.closedAt,
else latestValue(h, T);
sign(asset) = +1, sign(liability) = -1;
root(h) = the top of h's parentId chain, which is finite (section 4);
all converted to baseCurrency using an FX rate as of T.
A container (a fund, a securities account) is therefore counted through its leaves, never twice, and each leaf carries its own sign: a margin loan held inside a brokerage account is subtracted, whatever the account’s nature. Ownership is read on the root of the chain, since a position is owned through the account that holds it. A holding that exists at T but has no valuation dated on or before T contributes zero.
latestValue(h, T) is the value of the valuation of h with the greatest asOf <=
T. A richer tool MAY refine an account’s value between statements using its
transactions; this reference derivation uses valuations only. When a record carries a
captured fx (5.5), a tool SHOULD use that rate to convert that record, and an
external rate otherwise; rate tables are not distributed by Asset Notation (out of
scope). ownedShare(h, S, T) sums, over h’s ownership rows whose subject is in S and
whose interval (from inclusive, until exclusive, section 5.7) contains T, the
share decimal strings parsed exactly; with no ownership at all, it is 1 for the
primary subject. This is non-normative: implementations MAY derive differently, but
SHOULD document how.
| Concept | Standard |
|---|---|
| Currency | ISO 4217 |
| Date / time | ISO 8601 / RFC 3339 |
| Country | ISO 3166-1 alpha-2 |
| Language (in extensions) | BCP 47 |
| Security | ISIN (ISO 6166); FIGI, ticker as alternates |
| Exchange | MIC (ISO 10383) |
| Institution | BIC (ISO 9362), LEI (ISO 17442) |
| Bank account | IBAN (ISO 13616) |
Producers SHOULD populate ids with whichever external identifiers they have.
Consumers MUST NOT assume any ids entry is present.
Money is { "amount": "<decimal string>", "currency": "<ISO 4217>" }. The decimal
string is base-10, optional leading -, . as the decimal separator, no thousands
separators, no currency symbol. Rationale: binary floats cannot represent most decimal
cash values exactly; ISO 20022 likewise uses decimal.
assetnotation field carries the spec version as major.minor.patch.
Compatibility is determined by major.minor; the patch is editorial (clarifications
that do not change the data model).major it does not support. A consumer
SHOULD accept a higher minor and MUST ignore fields it does not recognize.extensions namespaces.kind and source values, and new enum values, MAY appear in a minor version; a
consumer MUST treat an unknown one generically rather than failing.ownership.share changed
from a JSON number to a decimal string, so a document carrying ownership must
quote its shares to validate as 0.3.0. Everything else is additive: the opened
vocabularies accept every previously-valid value, and a 0.2.0 consumer reading a
0.3.0 document per the rules above ignores transferId and a valuation’s
quantity.from and until on ownership rows. The
schema also refuses a calendar date that does not exist, which 0.3.0 accepted by
oversight. The rest of 0.4.0 states what 0.3.0 left unsaid (closed holdings, the
sign of a transaction on a liability, acyclic parents, a child’s own sign); a
document is not changed by it, a derived total may be.App-specific or domain-specific data that is not part of the core MUST NOT be placed in core fields. Three mechanisms, from most to least scoped:
attributes object for kind-specific scalars (a loan’s rate, a
property’s surface). Already part of the core, so no namespace needed.extensions object, keyed by a namespace (reverse-DNS or app id):
"extensions": { "com.example": { ... } }.x- prefixed field on any entity for small additions: "x-color": "...".This keeps the core small and stable while letting tools carry their own state (for example an app’s own categorization rules) without polluting the standard.
Asset Notation documents are plaintext and inherently sensitive. Confidentiality, integrity and tamper-evidence are the responsibility of the container that carries the document (for example an encrypted, authenticated container), not of this schema.
Records are append-only by convention. Past valuations and transactions are facts:
a producer SHOULD correct history by adding a record (a new valuation that supersedes
by asOf, a corrective transaction), not by rewriting an old one. Rewriting silently
changes every view derived from the document; detecting such a rewrite remains the
container’s job (versioned or content-addressed storage), not the schema’s.
Asset Notation is a payload schema, independent of how a document is stored or moved. A document MAY live in a plain file, inside a versioned and optionally encrypted container, in a ZIP, or be served over an API; none of that is the schema’s concern.
Because a document is a set of collections keyed by id (subjects, institutions,
holdings, valuations, transactions, ownership), it maps one-to-one onto any
document or key-value store. A producer whose internal model is Asset-Notation-shaped
can therefore emit a conformant document directly from its own state, reconciling
entities by id.
The JSON Schema (schema/<version>/) is the machine-readable contract. Its strictness
is deliberate and follows one rule, so that “valid” means the same thing to every
implementation:
money ({ amount, currency }) and fxRate
({ base, rate }) set additionalProperties: false. They are complete, universal
shapes with no room to grow: an unexpected key is a mistake, and the schema rejects it.
This catches the common producer bug - a misspelled or misplaced monetary field - at
the boundary.additionalProperties: true. This is
the forward-compatibility guarantee of principle 6 and section 9 made concrete: a newer
producer MAY add a field, and an older validator MUST NOT reject the document for it.
Structured app-specific data still belongs in the scoped channels of section 10
(attributes, extensions, x-), never as a loose core key.ids,
the keys that never carry sensitive account data - bic, isin, lei, mic -
MUST match their ISO patterns when present. The maskable keys (iban, number;
section 11) stay free-form: a redacted value is a feature, not a typo. The ids
objects themselves stay open to unknown keys.The consequence, stated plainly: open validation does not catch a misspelled
optional field on an entity (a stray amoutn on a transaction validates as an unknown
field and is preserved untouched). The schema guarantees the structure and types of the
fields it knows, and the closure of value objects; it does not guarantee that a producer
spelled every optional field correctly. Producers are responsible for that, and SHOULD
validate their output against both the schema and their own fixtures.
A stricter, closed-world profile (additionalProperties: false everywhere, extensions
allowed only through the declared channels) would turn every such typo into an error. It
is intentionally not adopted in 0.x: it would reject documents that are valid today
(any document carrying an extra field), so it is a breaking change reserved for a future
major version. Until then, strictness is exactly this: closed value objects, open
entities.
Beyond the schema, the reference validator enforces the document-level invariants a
JSON Schema cannot express: a duplicate id within a collection, a parentId chain
that loops, and two ownership rows of one subject on one holding whose intervals
overlap are errors; an unresolved reference is reported informatively but permitted
(section 4), and so are shares of one holding summing above 1 on some day.
assetnotation and generatedAt; MUST give every
entity an id unique within its collection; MUST give every holding a kind and a
nature; MUST represent money, quantities and ownership shares as decimal strings
with ISO 4217 currencies and dates as ISO 8601; MUST give a corporate_action
transaction a factor; MUST keep parentId chains acyclic and ownership intervals
of one subject on one holding disjoint; SHOULD pair the legs of an internal
movement with a transferId netting to zero; SHOULD populate valuations so
totals can be derived, and emit a final one when a holding closes.kind/source/enum value generically, and MUST
reject an unsupported major version.{ "assetnotation": "0.4.0", "generatedAt": "<ts>", "holdings": [] }.{ "num": 1, "den": 3 }) could join as an
alternative representation. Not before demand. Ownership carries a time interval
since 0.4.0; rights that differ in kind (below) still do not.share cannot express rights that differ in kind and carry their own
time-evolving values. Needs a rights model if real demand appears.instruments stay optional, or is the position model (instrument + quantity)
worth promoting once securities are first-class in implementations?A household: one person, a checking account with a cash balance, a brokerage position in an ETF, a directly-owned apartment financed by a mortgage, and the month’s mortgage payment recorded on both sides.
{
"assetnotation": "0.4.0",
"generatedAt": "2026-07-08T09:00:00Z",
"generator": { "name": "example", "version": "1.0.0" },
"baseCurrency": "EUR",
"subjects": [
{ "id": "s1", "type": "person", "name": "A. Doe", "country": "FR" }
],
"institutions": [
{ "id": "i1", "name": "Example Bank", "kind": "bank", "ids": { "bic": "EXAMFRPPXXX" } },
{ "id": "i2", "name": "Example Broker", "kind": "broker" }
],
"holdings": [
{ "id": "h1", "label": "Current account", "kind": "checking", "nature": "asset", "currency": "EUR", "institutionId": "i1", "ids": { "iban": "FR76 XXXX XXXX XX" } },
{ "id": "h2", "label": "World ETF", "kind": "brokerage", "nature": "asset", "currency": "EUR", "institutionId": "i2", "instrumentId": "n1", "quantity": "40", "costBasis": { "amount": "3936.00", "currency": "EUR" } },
{ "id": "h3", "label": "12 Main St", "kind": "real_estate", "nature": "asset", "currency": "EUR", "links": [{ "rel": "financed_by", "holdingId": "h4" }] },
{ "id": "h4", "label": "Mortgage", "kind": "mortgage", "nature": "liability", "currency": "EUR", "institutionId": "i1", "attributes": { "rate": 1.8, "startDate": "2022-01-01", "endDate": "2042-01-01", "payment": "990.00" } }
],
"instruments": [
{ "id": "n1", "name": "World ETF", "kind": "security", "ids": { "isin": "IE00B4L5Y983" }, "currency": "EUR" }
],
"valuations": [
{ "id": "v1", "holdingId": "h1", "asOf": "2026-06-24", "value": { "amount": "4210.55", "currency": "EUR" }, "source": "statement" },
{ "id": "v2", "holdingId": "h2", "asOf": "2026-06-24", "value": { "amount": "4920.00", "currency": "EUR" }, "unitPrice": { "amount": "123.00", "currency": "EUR" }, "source": "market" },
{ "id": "v3", "holdingId": "h3", "asOf": "2026-06-01", "value": { "amount": "300000.00", "currency": "EUR" }, "source": "estimate", "confidence": 0.5 },
{ "id": "v4", "holdingId": "h4", "asOf": "2026-06-24", "value": { "amount": "176400.00", "currency": "EUR" }, "source": "statement" }
],
"transactions": [
{ "id": "t1", "holdingId": "h1", "date": "2026-06-05", "type": "transfer", "amount": { "amount": "-990.00", "currency": "EUR" }, "counterpartHoldingId": "h4", "transferId": "x1", "description": "Mortgage payment" },
{ "id": "t2", "holdingId": "h4", "date": "2026-06-05", "type": "transfer", "amount": { "amount": "-990.00", "currency": "EUR" }, "counterpartHoldingId": "h1", "transferId": "x1", "description": "Mortgage payment" }
],
"ownership": [
{ "subjectId": "s1", "holdingId": "h1", "share": "1" },
{ "subjectId": "s1", "holdingId": "h2", "share": "1" },
{ "subjectId": "s1", "holdingId": "h3", "share": "1" },
{ "subjectId": "s1", "holdingId": "h4", "share": "1" }
]
}
Net worth as of 2026-06-24: 4210.55 + 4920.00 + 300000.00 - 176400.00 = 132730.55 EUR.
The payment x1 lowers the account by 990 and the outstanding by 990: signed by
nature, (+1)(-990) + (-1)(-990) = 0, the owner’s net worth is unchanged by it.
A multi-currency brokerage with a settlement cash account, a captured FX rate, a pair
of trade legs sharing a transferId and a stock split is worked through in
examples/brokerage.json.