Skip to main content

Asset types


Multi-currency architecture

Every holding has a currency field (the currency it’s traded in). The app displays everything in your chosen display currency (SGD, INR, or USD) using live exchange rates.

The effectiveCurrency pattern

Crypto is the edge case: CoinGecko always returns prices in USD regardless of what’s in the currency field. So the provider overrides it:
If displayCurrency == effectiveCurrency, the rate key resolves to e.g. "SGDSGD" which returns 1.0 — no conversion needed.

Shared forex rates provider

All portfolio providers share a single forex fetch per render cycle:
This results in exactly one forex HTTP call per portfolio screen load, regardless of how many different currencies are in the portfolio.

Format helpers

Mixing these up is the most common bug in portfolio display code — hero totals that run their own FX-summed loop use format(), per-holding tiles use formatConverted().

Live price fetching

Caching and deduplication

The MarketDataService has two layers of deduplication: 1. TTL cache (60 seconds)
2. In-flight Completer deduplication

Batch endpoint

The /market/batch endpoint combines stocks + crypto + forex into a single call:
Used by holdingsWithPricesProvider to hydrate the full portfolio in one round trip.

Asset-specific price logic

Fixed Deposits — accrual formula:
Metals — ETF proxies: GLD, SLV, PDBC are fetched as normal yfinance tickers (they’re ETFs, not spot commodity prices). getMetalPrice() maps semantic names:
Crypto — CoinGecko ID normalisation: CoinGecko IDs are lowercase (bitcoin, ethereum, solana). The holding’s symbol field is stored in uppercase by convention. The service normalises:

Portfolio calculations

Cost basis

avgPrice is stored in the holding’s original currency. FX conversion applied at display time.

Market value

currentPrice comes from the live price fetch. Falls back to avgPrice on fetch failure.

Return

Unrealised P&L per holding


Historical charts

The asset detail screen fetches OHLCV history for period selector tabs (1W / 1M / 3M / 6M / 1Y / ALL):
Backend maps period strings to yfinance parameters: Crypto history uses CoinGecko’s /coins/{id}/market_chart endpoint with equivalent day ranges.

Portfolio sparkline (14-day)

The dashboard hero sparkline aggregates the last 14 days of portfolio value:

Heatmap

The heatmap widget sizes each tile by portfolio allocation weight and colours by day change %: Tile size is proportional to currentValue / totalPortfolioValue.