
Truequity
I hold positions across Indonesian stocks, US equities, and crypto. Checking how the whole thing was doing meant opening three different apps and reconciling everything in a spreadsheet I'd keep up for a few weeks then abandon. Nothing existing took IDX seriously and also felt calm enough to use daily, so I shipped one. Truequity tracks IDX stocks (with proper LOT handling), US equities, and crypto on a single dark dashboard, benchmarks the whole portfolio against IHSG, S&P 500, and Bitcoin on the same chart, and uses Claude Vision to read broker screenshots so I never have to type a trade again. Built with Next.js 16, Supabase, and TanStack Query.
01 /The Problem
Indonesian retail investors who hold a mix of local stocks, US equities, and crypto have no single source of truth for their portfolio. Global trackers ignore IDX or treat it as a special case with manual workarounds. Brokerage apps each cover their own slice well but make money when users trade more, not when users reflect. Spreadsheets rot inside two months. The result is the daily fragmentation tax: open the Indonesian broker app first, then the US broker, then the crypto exchange, then do arithmetic in your head to figure out where you stand. On top of that, every Indonesian multi-asset investor is constantly translating between IDR for local positions and USD for crypto and US stocks, which makes total portfolio value genuinely hard to know without sitting down with a calculator.
“You can't manage what you can't see.”
02 /The Solution
Unified Dashboard
IDX stocks, US equities, crypto, and idle cash on one screen. Toggle between USD and IDR; every value updates within a second.
Benchmark Chart
Time-weighted portfolio return overlaid against IHSG, S&P 500, or Bitcoin on the same line. Both start at zero on day one so the comparison is honest, not flattering.
AI Screenshot Extraction
Drop a broker or exchange screenshot. Claude Vision pulls ticker, quantity, price, and date in about three seconds. You check the form and save. The model never submits for you.
Realized P&L Calendar
Heatmap of realized gains and losses per trading day, computed client-side from sell records using weighted-average cost basis. Hover (desktop) or scroll the activity feed (mobile) for the per-trade breakdown.
Cash Ledger
Idle cash tracked alongside positions in both currencies. Buys and sells with cash impact debit and credit automatically; everything else is a manual deposit or withdrawal.
Truequity is one calm dashboard with four jobs: track everything in one place, tell me whether I'm beating the market, kill the typing on transaction entry, and respect Indonesian conventions natively. It is deliberately monitoring-only. No buy buttons, no trade prompts, no gamification, no encouraging copy when the portfolio is down. The whole thing is built to answer one question without making the user work for it: how is the portfolio doing this week, and against what?
03 /Technical Recap
Next.js 16 App Router with Supabase handling Postgres, Auth, and Row Level Security. Every external API call (CoinGecko, Yahoo Finance, Anthropic) routes through server-side API routes so keys stay on the server and responses can be cached. TanStack Query manages client-side caching with aggressive stale times to stay inside CoinGecko's 10K/month free tier during beta. The benchmark chart accumulates user transactions into daily portfolio values, then converts both portfolio and benchmark to percentage returns from a shared start date so the two lines are directly comparable instead of the usual apples-to-oranges overlay. Realized P&L runs entirely client-side: sell records are processed chronologically, each matched against accumulated cost basis per ticker using weighted-average, with everything normalised to IDR via the live exchange rate.
export function usePrices(tickers: PriceTicker[]) {
return useQuery({
queryKey: ["prices", tickers],
queryFn: async () => {
const res = await fetch("/api/prices", {
method: "POST",
body: JSON.stringify({ tickers }),
})
return res.json() as Promise<PriceMap>
},
staleTime: 60_000, // 60s, respects CoinGecko free tier
refetchInterval: 60_000,
enabled: tickers.length > 0,
})
}
export function useExchangeRate() {
return useQuery({
queryKey: ["exchange-rate"],
queryFn: () => fetch("/api/exchange-rate").then(r => r.json()),
staleTime: 24 * 60 * 60 * 1000, // 24h cache, IDR/USD is slow-moving
})
}