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.
翻訳待ち
No updates detected yet. GitHub releases will appear here.
Loading the full README. The saved excerpt is shown below.
A keyboard-driven stock screener. Type a ticker, hit Enter, get live price
data, charts, analyst consensus, technicals, and news. No account, no friction.
I built this because every screener I found was either too bloated, behind a
paywall, or slow. The terminal aesthetic isn't decoration: j/k move through
the list, Enter opens, s stars.Read the full README ↗A keyboard-driven stock screener. Type a ticker, hit Enter, get live price data, charts, analyst consensus, technicals, and news. No account, no friction.
I built this because every screener I found was either too bloated, behind a
paywall, or slow. The terminal aesthetic isn't decoration: j/k move through
the list, Enter opens, s stars.
| Layer | Technology |
|---|---|
| Frontend | React 19 + Vite |
| Backend | FastAPI (Python 3.12) |
| Cache | Redis: response cache and token bucket rate limiter |
| Database | PostgreSQL, month-partitioned OHLCV history |
| Data sources | Finnhub, Yahoo Finance, FMP, Alpha Vantage, Stooq, Twelve Data |
| Deployment | Docker Compose |
Every upstream call passes through two layers of Redis.
Response cache. Entries are keyed cache:{TICKER}:{resource}. The provider
that answered is not part of the key, because /api/stock and /api/candle
fall through several providers and keying by source means the next request
looks under a different key and misses. Which source served a payload travels
inside the payload.
TTLs are set per resource, since a live quote and a company profile go stale on different timescales: 30s for quotes, 5min for candles, 15min for news, an hour for fundamentals and analyst data, a day for profiles.
Concurrent misses for one key collapse onto a single upstream call. One caller wins a short Redis lock and fetches; the rest wait briefly for its result, then fetch for themselves rather than blocking. Without this, a popular ticker expiring sends one request per in-flight client and spends the budget in a burst.
Token bucket rate limiter. Each source gets a bucket holding a token count and a refill timestamp. A Lua script runs the read-refill-write sequence in one round trip, so concurrent callers cannot oversubscribe it, whether they are async tasks in one process or separate uvicorn workers. Both the refilled balance and the timestamp are written on every call, including denials: persisting only the timestamp restarts the refill clock while discarding the tokens it earned, which lets a client that retries starve its own bucket.
When a source is exhausted the request falls through to the next one in the
chain. When they are all exhausted the endpoint returns 429 with a
Retry-After computed from the bucket.
Price history. After a candle fetch, OHLCV rows are written to Postgres in
the background. The table is range-partitioned by month on timestamp, and the
partition for a row's month is created on demand, so a backfill or a long-lived
instance does not run past the end of its partitions. The primary key is
(ticker, source, timestamp) and writes upsert onto it, so re-viewing a chart
corrects existing bars instead of appending another copy of the history.
Stock quote: Finnhub → Yahoo Finance → Twelve Data
Candle/chart: Finnhub → Yahoo Finance → Stooq → Twelve Data
News: Yahoo Finance → Finnhub
Profile: Yahoo Finance + Finnhub (parallel, merged)
Enrich: FMP + Alpha Vantage (parallel)
| Method | Path | Description |
|---|---|---|
| GET | /api/stock?symbol= | Single quote |
| GET | /api/quotes?symbols= | Batch quotes |
| GET | /api/candle?symbol=&finnhubSymbol=&range= | OHLCV chart data |
| GET | /api/search?q= | Ticker search |
| GET | /api/profile?symbol=&finnhubSymbol= | Company profile |
| GET | /api/analyst?symbol= | Analyst recommendations and sentiment |
| GET | /api/news?symbol=&finnhubSymbol= | Recent news |
| GET | /api/fundamentals?symbol= | Key statistics and ratios |
| GET | /api/metrics?symbol= | Per-ticker Finnhub metrics |
| GET | /api/enrich?symbol= | FMP and Alpha Vantage enrichment |
| POST | /api/analyze | AI analysis, proxied to OpenAI |
| GET | /metrics | Cache hit rate and per-source counters |
| GET | /health | Liveness, with Redis and Postgres checked separately |
| GET | /history/{ticker}?start=&end=&limit=&offset= | Stored OHLCV from Postgres |
range accepts 1mo, 3mo, 6mo, 1y or 2y. Anything else is a 422.
Symbols are pattern-validated before they reach a cache key or an upstream
query string. An exhausted rate limit is a 429 with Retry-After, never an
empty 200.
/history returns at most 1000 rows by default and 10000 with an explicit
limit, with hasMore telling you whether to page.
docker compose up
That brings up Redis on 6379, PostgreSQL on 5432 (user, password and database
all markr), and the API on 8000. The partitioned price_history table is
created on first startup. The API waits for both dependencies to report
healthy before starting.
This works without any configuration. Yahoo Finance and Stooq need no credentials, so quotes and charts resolve through them.
Finnhub is the primary quote source and its free tier is enough:
cp .env.example .env
FINNHUB_KEY=your_key_here
# Optional, for richer data
FMP_KEY=
AV_KEY=
TWELVE_DATA_KEY=
# Optional, for the AI panel. Visitors can supply their own key instead.
OPENAI_KEY=
Then docker compose up --build.
npm ci
npm run dev
Vite proxies /api, /metrics, /health and /history to port 8000. Open
http://localhost:5173.
Defaults shown. All of these can be set in .env or the environment.
This is the beginning of the README. Read the rest at the original. Read the full README ↗