Portfolio Tracker Implementation in Mobile Crypto App
Portfolio tracker — a screen where user sees total asset value, distribution and dynamics over period. Seems simple: sum balance × price. In practice: multi-currency, historical prices for P&L, data aggregation from different exchanges and wallets, real-time update without battery degradation.
Data Sources
Three types of data sources for portfolio:
Manual input — user adds assets themselves. Simple, works without API keys, but becomes outdated immediately.
Exchange API — via read-only API keys. Binance, OKX, Bybit, Coinbase have /account/balances or equivalent. Important: accept only read-only keys, explicitly block trading ability. On UI — warning that key must be without withdrawal rights.
On-chain data — public wallet addresses, balances read via RPC. For EVM: eth_getBalance for native currency, balanceOf(address) via eth_call for ERC-20 tokens. For multiple tokens simultaneously — Multicall3:
// Multicall3 — one request to get balances of 50+ tokens
final multicall = DeployedContract(
ContractAbi.fromJson(multicall3Abi, 'Multicall3'),
EthereumAddress.fromHex('0xcA11bde05977b3631167028862bE2a173976CA11'),
);
final calls = tokenAddresses.map((token) => [
token, // target
false, // allowFailure
balanceOfCalldata(walletAddr), // callData: balanceOf(address)
]).toList();
final result = await ethClient.call(
contract: multicall,
function: multicall.function('aggregate3'),
params: [calls],
);
Real-Time Prices
CoinGecko /coins/markets?ids=bitcoin,ethereum,... — free limit 30 req/min for Demo API, sufficient for polling every 30 seconds. For real-time without polling — WebSocket: Binance wss://stream.binance.com/ws/!miniTicker@arr gives price updates for all pairs.
Key question: update all asset prices simultaneously or subscribe only to specific tickers. Scheduled polling is simpler and more predictable for battery. WebSocket is faster but needs reconnection management.
On Flutter: Timer.periodic for polling, web_socket_channel for WebSocket. For background updates — WorkManager (Android) / BGAppRefreshTask (iOS), but iOS strictly limits background update frequency.
P&L Calculation
Total P&L = (current portfolio value) - (value at purchase). For manual input — user enters average purchase price. For exchange API — calculate from trade history by FIFO:
class PnLCalculator {
// Purchase queue (FIFO) for cost basis calculation
final _buyQueue = Queue<({double price, double quantity})>();
double _totalCost = 0;
double _totalQuantity = 0;
void addBuy(double price, double quantity) {
_buyQueue.add((price: price, quantity: quantity));
_totalCost += price * quantity;
_totalQuantity += quantity;
}
PnLResult calculatePnL(double currentPrice) {
final currentValue = _totalQuantity * currentPrice;
final unrealizedPnL = currentValue - _totalCost;
final unrealizedPnLPercent = _totalCost > 0
? (unrealizedPnL / _totalCost) * 100
: 0.0;
return PnLResult(
unrealizedPnL: unrealizedPnL,
unrealizedPnLPercent: unrealizedPnLPercent,
avgEntryPrice: _totalCost / _totalQuantity,
);
}
}
Asset Distribution Visualization
Pie chart / Donut chart with percentages per asset. Problem: 50 assets make pie unreadable. Solution: show top-5 by share, rest — "Others". On segment tap — drill-down to that category asset list.
On Flutter fl_chart PieChart with PieChartSectionData:
List<PieChartSectionData> buildSections(List<Asset> assets, double totalValue) {
final sorted = [...assets]..sort((a, b) => b.currentValue.compareTo(a.currentValue));
final top5 = sorted.take(5).toList();
final othersValue = sorted.skip(5).fold(0.0, (sum, a) => sum + a.currentValue);
final sections = top5.map((asset) => PieChartSectionData(
value: asset.currentValue / totalValue * 100,
title: '${(asset.currentValue / totalValue * 100).toStringAsFixed(1)}%',
color: _colorForAsset(asset.symbol),
radius: 60,
)).toList();
if (othersValue > 0) {
sections.add(PieChartSectionData(
value: othersValue / totalValue * 100,
title: 'Others',
color: Colors.grey,
radius: 60,
));
}
return sections;
}
Historical Portfolio Value Graph
Value chart for 24h / 7d / 30d / 1y. This requires historical prices of all assets at each moment — expensive query. CoinGecko /coins/{id}/market_chart?vs_currency=usd&days=30 — per asset separately.
Optimization: don't request historical data on every open. Cache with 1-hour TTL, update on asset set change or explicit pull-to-refresh.
Multi-Currency
Users expect value in their fiat currency (USD, EUR, RUB). CoinGecko supports vs_currencies parameter. Fiat/fiat conversion rate (USD → RUB) — via separate currency API or Forex feed.
Save user's preferred currency in SharedPreferences / UserDefaults, on change — recalculate without re-requesting CoinGecko (just multiply by rate).
Scope of Work
- Manual asset input + exchange addition via API keys
- On-chain monitoring via Multicall (for EVM wallets)
- Real-time prices (polling or WebSocket)
- P&L calculation (unrealized, realized — per requirements)
- Donut chart asset distribution
- Historical LineChart with timeframes
- Multi-currency (USD/EUR/others)
Timeframe
MVP with manual input, prices and pie chart: 2–3 weeks. Full tracker with exchange API, on-chain monitoring, historical chart and P&L: 6–10 weeks. Cost calculated individually after requirements analysis.







