Asset types
| Type | Examples | Price source | Notes |
|---|---|---|---|
| Stock | AAPL, MSFT, RELIANCE.NS | Yahoo Finance (yfinance) | Suffix determines exchange |
| Crypto | BTC, ETH, SOL | CoinGecko API | Always priced in USD |
| ETF | SPY, QQQ, CSPX.L, ES3.SI | Yahoo Finance | |
| REIT | CICT.SI, PLD | Yahoo Finance | |
| Commodity | GLD, SLV | Yahoo Finance | Traded as ETFs, not spot |
| Bond | — | Manual (avg price) | No live pricing |
| Fixed Deposit | — | Calculated | Interest accrual formula |
| Real Estate | — | Manual | No live pricing |
Multi-currency architecture
Every holding has acurrency 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 thecurrency field. So the provider overrides it:
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:Format helpers
format(), per-holding tiles use formatConverted().
Live price fetching
Caching and deduplication
TheMarketDataService has two layers of deduplication:
1. TTL cache (60 seconds)
Batch endpoint
The/market/batch endpoint combines stocks + crypto + forex into a single call:
holdingsWithPricesProvider to hydrate the full portfolio in one round trip.
Asset-specific price logic
Fixed Deposits — accrual formula:getMetalPrice() maps semantic names:
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):| Period label | yfinance period | yfinance interval |
|---|---|---|
| 1W | 5d | 1h |
| 1M | 1mo | 1d |
| 3M | 3mo | 1d |
| 6M | 6mo | 1d |
| 1Y | 1y | 1wk |
| ALL | max | 1mo |
/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 %:| Day change | Colour |
|---|---|
| > +2% | #10B981 bright |
| +0.5% to +2% | #10B981 muted |
| -0.5% to +0.5% | #F59E0B amber |
| -0.5% to -2% | #EF4444 muted |
| < -2% | #EF4444 bright |
currentValue / totalPortfolioValue.