尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證StackWorth:美股 ETF、台股、加密貨幣跨帳戶的個人投資組合追蹤,TWD 計價,XIRR/TWR。Next.js + Supabase
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗English | 繁體中文
A personal portfolio tracker for a long-term investor holding US ETFs, Taiwan stocks, crypto, and manual assets across multiple accounts — everything in one TWD-denominated dashboard.
Live app: https://portfolio-tracker-two-rho.vercel.app · Public demo (no login): https://portfolio-tracker-two-rho.vercel.app/demo · Full reference: docs/REFERENCE.md
Personal finance tool. Not investment advice, not affiliated with any institution, not a commercial product. The interface is in Traditional Chinese.
Public demo, generated data. Light and dark themes follow your GitHub setting.
Holding assets across markets and currencies means brokerage apps only ever show fragments. Spreadsheets can't pull live prices or keep a clean contribution history. What I wanted did not exist in one place: XIRR and TWR side by side, computed with correct sign conventions, scoped to the portfolio I actually hold today, in my own base currency.
One computation pipeline, two callers. All dashboard math lives in buildDashboardData, a pure function with no I/O. The production page feeds it Supabase rows; the public /demo route feeds it deterministically generated data. The demo is therefore not a mockup — XIRR, TWR, Sharpe, and drawdown on the demo page are computed by the exact code that runs on my real portfolio.
XIRR and TWR use opposite cashflow conventions, on purpose. XIRR follows the investor's wallet (investment = negative, terminal portfolio value appended as positive); TWR follows the portfolio (contribution = positive, terminal value excluded because it is already embedded in the last snapshot — including it would zero out the final sub-period). The sign flip happens once, at one documented point, with regression tests pinning the terminal-value misuse case.
The XIRR solver validates its residual before returning a rate. Every exit path of the Newton-Raphson loop checks |NPV(rate)| against a scale-relative tolerance; oscillation, flat derivatives, and hitting the iteration cap all return null instead of a residual value.
Dashboard scope is the active portfolio. Summary values, the trend curve, performance metrics, income stats, and allocation share one active-account boundary. Showing archived accounts only expands the holdings ledger; it cannot change the dashboard totals or appear as a value drop in TWR.
Risk metrics preserve cashflow and time boundaries. Max drawdown is calculated from the cashflow-adjusted TWR index, so withdrawals do not look like market losses. Sharpe converts irregular snapshot intervals to equivalent daily returns and annualizes on calendar days because the portfolio includes assets that trade on weekends.
Recurring execution is ledger-backed and idempotent. Cron and manual execution fetch the latest quote, then call one Postgres RPC. The function locks the plan and account, derives the quantity increment from current database values, writes the transaction and snapshot, records the scheduled run, and advances the plan in one transaction. A unique (plan_id, scheduled_date) key makes retries and double clicks harmless.
Failure states are explicit. Benchmark fetchers return empty on failure and the chart bridges gaps with a dashed segment; enabled comparison lines share one start date before they are normalized to 100; CoinGecko's history cap renders BTC as a late-starting series; the service worker caches only an offline page, since showing stale financial numbers is worse than failing to load; a data-health card in settings turns red when the price cron misses a run.
Decision context is write-once. A decision stores the price, FX rate, position, allocation, cost, unrealized P&L, and portfolio value seen at creation. Database triggers reject later edits to that context while still allowing the thesis, review date, and other user-authored fields to be corrected.
Historical pages never borrow today's quote. Replay uses the latest snapshot available on or before the selected date, labels carried-forward values with their source date, and exposes missing data. The attribution equation keeps a visible scale-relative residual instead of hiding an incomplete split.
/decisions, optionally link it to an activity transaction, preserve the original context, schedule a review, and record outcome and process separately./history to inspect holdings, archived boundaries, price dates, allocation, currency exposure, and a reconciling change breakdown./whatif to combine price and FX shocks, then model an external proposed purchase without writing to the ledger./reports/monthly, review returns, attribution, allocation, decisions, gaps, and data health, then use the browser's print-to-PDF flow.Asia/Taipei explicitly; Vercel runs in UTC, and a one-day shift in snapshot dates corrupts day-change and TWR.https://portfolio-tracker-two-rho.vercel.app/demo — no account needed.
Generated data: an 18-month DCA history with a seeded pseudo-random walk, a planted correction regime, one realized loss, dividends and interest. Deterministic per day, so reviewers see the same numbers on revisit. The production dashboard holds personal financial data, so every screenshot in this repository comes from the demo.
| Historical replay and attribution | Stress test and anti-FOMO check |
|---|---|
The demo flows are /demo/decisions, /demo/history, /demo/whatif, and /demo/report. They use deterministic fixtures only and never query an authenticated user's portfolio.
Amount masking remains available in the authenticated app. Printed monthly reports state whether amounts were visible when the report was generated; check that state before saving a PDF on a shared device.
git clone https://github.com/wppypewo/portfolio-tracker
cd portfolio-tracker
npm install
cp .env.local.example .env.local # fill in; see docs/REFERENCE.md for the variable table
npm run dev
Gates: npm run lint / npm run typecheck / npm run test:unit / TEST_DATABASE_URL=... npm run test:integration / npm run build.
Running in production (Vercel + Supabase, single user). CI runs lint / typecheck / unit tests / Postgres integration / build on every push to main. The current release is v1.3.0 (2026-10-25): tiered DCA amounts, cost-basis correction, the public /about page, MFA enforced at the database layer, and a set of Asia/Taipei date fixes. Each release lists the SQL migrations it needs; the full order is in supabase/README.md.
Known limits: replay and reports use bounded snapshot queries and surface truncation or missing prices; review performance depends on stored daily snapshots; browser printing is the PDF path; authenticated create/review flows require a configured Supabase project. See docs/REFERENCE.md and CHANGELOG.md.
Built by @wppypewo as a daily-use tool for a multi-asset, multi-currency portfolio.
MIT.