Open Net Worth
Open Net Worth is an open-source personal AI wealth assistant you run yourself: one ledger for your whole financial life, worked by an AI agent you control.
翻訳待ち
Free S&P 500 dollar-cost-averaging calculator. Replay a 401(k)-style contribution plan, with an employer match, on real SPY daily closes to see what it would be worth today.
翻訳待ち
金融HTML · JavaScriptGitHub @00xJSオンライン所有者未確認
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Loading the full README. The saved excerpt is shown below.
Dollar Cost Averaging (DCA) is an investment strategy where an investor divides up the total amount to be invested across periodic purchases of a target asset, to reduce the impact of volatility on the overall purchase. It is also how most people already invest in the stock market: a 401(k) takes a fixed contribution from every paycheck, often topped up by an employer match, and buys the same fund on the same schedule whatever the price.Read the full README ↗Dollar Cost Averaging (DCA) is an investment strategy where an investor divides up the total amount to be invested across periodic purchases of a target asset, to reduce the impact of volatility on the overall purchase. It is also how most people already invest in the stock market: a 401(k) takes a fixed contribution from every paycheck, often topped up by an employer match, and buys the same fund on the same schedule whatever the price.
This project replays that 401(k)-style plan against real historical prices for SPY — the SPDR S&P 500 ETF Trust, which has tracked the S&P 500 since January 1993 — so you can see what a given contribution schedule would have produced.
S&P 500 DCA Calculator — a browser-based simulator. Set a start date, how often you contribute, how much, an optional employer match and whether dividends are reinvested, and the portfolio value, return figures, growth chart and full purchase history update live as you go.
Dividends are reinvested by default. The prices are SPY's actual traded closes — adjusted for splits, not for dividends — which is precisely what makes reinvestment something the model can do explicitly rather than assume. Reinvest dividends is on out of the box, because that is what a 401(k) fund does with a distribution. Untick it and every figure is price return only, which understates a long plan by roughly 1–2% a year. The page says which of the two it is showing, everywhere it matters.
Price data is pre-baked static JSON committed to this repo — the browser never calls a market-data API.
| Source | Yahoo Finance SPY via yfinance |
| Coverage | 1993-01-29 (SPY's first trading day) → today (8,400+ daily closes) |
| Prices | Actual closing prices in USD — split-adjusted, not dividend-adjusted |
| Dividends | Cash distributions per share, by ex-dividend date — quarterly, 1993 → today |
| Schedule | Weekdays, 21:00 UTC (after the US market close) |
| Credentials | None — no API keys, no repo secrets |
| Script | scripts/fetch_spy_prices.py |
| Output | data/spy-prices.json |
The scheduled Action (.github/workflows/update-spy-data.yml) runs the script, commits data/spy-prices.json if anything changed, and Netlify auto-deploys that commit. It can also be started by hand from the Actions tab. This means:
yfinance needs no key, so there is nothing to expireyfinance library. If it breaks or stalls, the workflow commits nothing, the site keeps serving the last good file, and the header chip turns amber once the newest close is more than three days old. The chip allows five days, so weekends and a single market holiday don't trip it.netlify.toml is load-bearing twice:
publish = "." serves the repo root, which is what makes the page's absolute /data/spy-prices.json request resolve.must-revalidate header on that path is what stops the CDN serving yesterday's prices after a data deploy.├── index.html # the calculator — markup, inline CSS and JS, no build step
├── netlify.toml # publish root + no-cache header for the data file
├── data/
│ └── spy-prices.json # auto-generated, weekdays
├── scripts/
│ └── fetch_spy_prices.py # yfinance SPY → daily closes
├── tests/
│ ├── engine.test.mjs # simulate(), xirr(), the formatters
│ ├── timezone.test.mjs # same plan, three timezones
│ ├── harness.mjs # pulls the inline <script> into a vm
│ └── fixtures/
│ └── prices.json # committed synthetic price series
└── .github/
└── workflows/
├── update-spy-data.yml # weekday cron
└── test.yml # node --test on push and PR
The page fetches /data/spy-prices.json by absolute path, and browsers block
fetch() on file:// outright — so opening index.html by double-clicking it
will always end in the "price data could not be loaded" error. Serve the directory instead:
python3 -m http.server 8000
Then open http://localhost:8000. Any static server works; it just has to serve
the repo root so /data/… resolves. No build step, no package install, no API keys
— the price file is committed, so there is nothing to fetch first. (The chart itself
loads Chart.js from jsDelivr; without it, the figures and purchase history still render.)
Rebuilding the price data is only needed for fresher numbers than the committed file, and needs no credentials:
pip install yfinance && python3 scripts/fetch_spy_prices.py
That rewrites data/spy-prices.json in place — exactly what the scheduled Action does.
data/spy-prices.json (~275 KB) is a standalone, reusable artifact:
{
"generated": "2026-09-15T05:27:33Z", // UTC build time
"source": "Yahoo Finance / yfinance — SPY daily closes (not dividend-adjusted)",
"count": 8463, // length of `prices` only
"prices": [ { "ts": 728265600, "price": 43.94 } ], // ts = UNIX SECONDS (UTC midnight of the trading date), ascending
"dividends": [ { "ts": 732499200, "amount": 0.213 } ] // ts = UNIX SECONDS (UTC midnight of the EX-DIVIDEND date), ascending
}
price is SPY's closing price in USD, rounded to the cent. Days the market was closed
have no entry; a purchase that lands on one fills at the most recent earlier close.
This is the beginning of the README. Read the rest at the original. Read the full README ↗