尚未检测到更新。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.