๐ Assets & Pricing
Global financial instruments and their pricing sources. Assets are shared across all users โ only the transactions referencing them are user-specific.
๐ ER Diagram
erDiagram
ASSET ||--o{ TRANSACTION : "referenced in"
ASSET ||--o{ PRICE_HISTORY : "has history"
ASSET ||--o| ASSET_PROVIDER_ASSIGNMENT : "priced by"
ASSET ||--o{ ASSET_EVENT : "has events"
ASSET_PROVIDER_ASSIGNMENT ||--o{ ASSET_EVENT : "generates"
ASSET {
int id PK
string display_name UK
string identifier_isin
string identifier_ticker
string asset_type "VARCHAR(32), AssetType value"
string currency
bool is_benchmark "shared, default false"
json classification_params
}
PRICE_HISTORY {
int asset_id FK
date date "UNIQUE with asset_id"
decimal open "nullable"
decimal high "nullable"
decimal low "nullable"
decimal close "nullable"
decimal volume "nullable"
decimal adjusted_close "nullable"
string currency
string source_plugin_key
}
ASSET_PROVIDER_ASSIGNMENT {
int id PK
int asset_id FK
string provider_code
string identifier
json provider_params
}
ASSET_EVENT {
int id PK
int asset_id FK
date date
enum type "DIVIDEND, INTEREST, PRICE_ADJUSTMENT, SPLIT, MATURITY_SETTLEMENT"
decimal value
string currency
int provider_assignment_id FK "nullable โ NULL = manual"
text notes "nullable"
}
๐ Tables
๐ฆ ASSET
Global definition of a financial instrument. display_name is unique; the identifier columns (identifier_isin, identifier_ticker, โฆ) are indexed but not unique. Each asset belongs to an Asset Type.
- ๐
classification_params(JSON): Stores flexible metadata like Sector, Geography, and Industry without requiring schema changes.- Distributions (
FAClassificationParams.geographic_areaand.sector_area,backend/app/schemas/assets.py:533-556) store their weights as fractions that sum to exactly 1, with 4 decimals. The UI works in percent: the distribution editor and the distribution CSV import accept a total within 0.005 points of 100, and divide each weight by 100 before saving (DistributionEditor.svelte:132,192,DistributionDataImportModal.svelte:35,42). - Backend check.
BaseDistribution._validate_and_normalize_weights(assets.py:352-431) rejects a sum farther than 0.01 from 1. Otherwise it renormalizes the weights, quantizes them to 4 decimals withROUND_HALF_EVEN, and puts the rounding residue on the smallest weight.
- Distributions (
- ๐ฐ
currency: The asset's native currency (e.g., USD for Apple, EUR for ASML). - ๐ท๏ธ
asset_type: OneAssetTypevalue โ see Asset types and subtypes below. - โญ
is_benchmark(BOOLEAN NOT NULL DEFAULT 0): The asset is offered as a comparison benchmark. The risk benchmark selector (BenchmarkSelect.svelte) and the chart's comparison-asset picker (SignalAssetParamControl.svelte) list flagged assets in a section of their own, apart from the other assets;GET /api/v1/assets/query?is_benchmark=true|falsefilters on the flag (omitted = both).- Shared, not per-user:
assetshas no owner column, so the flag is the same for every user. - Independent of
asset_type: any asset can be a benchmark, and anINDEXasset does not have to be one. - Seeded once: migration
004_release_1_2_0_schemaset the flag on the existingINDEXassets, only when it created the column โ a flag the user cleared later is never restored.
- Shared, not per-user:
๐ท๏ธ Asset types and subtypes
assets.asset_type stores exactly one AssetType value (backend/app/db/models.py):
| Level | Values |
|---|---|
| Base types | STOCK, ETF, BOND, CRYPTO, FUND, CROWDFUND, HOLD, COMMODITY, REAL_ESTATE, INDEX, OTHER |
ETF subtypes (family ETF) |
ETF_STOCK, ETF_BOND, ETF_COMMODITY, ETF_REAL_ESTATE, ETF_CRYPTO, ETF_MONETARY |
Crowdfunding subtype (family CROWDFUND) |
CROWDFUND_REAL_ESTATE |
A subtype says which base type the instrument contains: ETF_BOND is an ETF that holds bonds, CROWDFUND_REAL_ESTATE a crowdfunding loan backed by property. ETF_MONETARY (money-market funds) is the one subtype with no base-type counterpart. The plain ETF and CROWDFUND values stay valid, as the residual for mixed or unstated content.
- ๐๏ธ Stored as is. The column keeps the exact value and nothing rolls up in the database; the backend aggregates by the stored value too (the allocation by type,
DailyStateBuilder._distribute_allocation()infinancial_math/portfolio/daily_state.py). Grouping a subtype with its family is a frontend concern:ASSET_TYPE_FAMILY/assetTypeFamily()infrontend/src/lib/utils/assetTypes.tsfiles everyETF_*value underETFandCROWDFUND_REAL_ESTATEunderCROWDFUND, for the asset-type select and for both allocation charts (see the Allocation Panel). - ๐ซ
INDEXis the one value with backend behaviour: transactions on anINDEXasset are refused (transaction_batch_stages.py). It does not implyis_benchmark. - โ Adding a value needs no migration: the column is a plain
VARCHAR(32)with noCHECKconstraint (databases created before 22/09/2026 still declareVARCHAR(14), harmless on SQLite โ see Migrations). The frontend tables keyed on the enum must follow:frontend/src/lib/utils/__tests__/assetTypeTables.test.tsreads the enum frombackend/app/db/models.pyand fails when one of them misses the new value.
๐ PRICE_HISTORY
Daily OHLCV (Open, High, Low, Close, Volume) price data for each asset, one row per (asset_id, date). Populated by asset pricing providers. open, high, low and close are all nullable: the price resolver values a day from close, and uses the day's range only when open, high and low are all present.
๐ ASSET_PROVIDER_ASSIGNMENT
Decouples the asset from its data source. This table configures which provider to use for fetching prices and metadata.
- ๐ Example: "Use Yahoo Finance (
yfinance) for Apple (AAPL)" - โ๏ธ
provider_params(JSON): Provider-specific configuration (e.g., exchange suffix, custom identifier).
The provider system uses the Registry Pattern for extensibility.
๐
ASSET_EVENT
Asset-level events that affect pricing or generate distributions. Events are distinct from transactions:
- Events describe what happens to the asset globally (e.g., a dividend, a stock split).
- Transactions describe what happens in a user's portfolio (e.g., buy, sell).
| Event Type | Effect on Price | Description |
|---|---|---|
DIVIDEND |
Price drops by event value (ex-date) | Cash distribution from equity/ETF |
INTEREST |
Price drops by event value | Interest payment from debt/loan |
PRICE_ADJUSTMENT |
Algebraic change (+/-) | Non-cash value change (write-down, haircut, re-rating) |
SPLIT |
Changes quantity, not total value | Stock/unit split |
MATURITY_SETTLEMENT |
Final capital return | Asset reaches maturity โ no further calculations |
Deduplication strategy: events are upserted in place, per source, on (asset_id, date, type): a sync matches only the events of its own assignment, and a matched row keeps its id, so the transactions linked to it stay linked. Manual events (provider_assignment_id = NULL) are edited by id or matched on date and type, and a sync never deletes them. See Asset Events.
Indexes: (asset_id, date), (asset_id, type, date), (provider_assignment_id).
๐ Related Documentation
- ๐ Asset Types (Financial Theory) โ Stock, ETF, Bond, Crypto, etc.
- โ๏ธ Asset Architecture โ How asset prices are fetched and managed
- ๐ Asset Providers List โ Available pricing providers