|
| 1 | +<p align="center"><img width="120" src="./src/public/favicon.png"></p> |
| 2 | +<h2 align="center">Stonks</h2> |
| 3 | + |
| 4 | +<div align="center"> |
| 5 | + |
| 6 | +[](https://ko-fi.com/itskovacs) |
| 7 | + |
| 8 | +[](https://github.com/itskovacs/stonks/issues) |
| 9 | + |
| 10 | +</div> |
| 11 | + |
| 12 | +<div align="center"> |
| 13 | + |
| 14 | + |
| 15 | +*Dashboard with mocked data, available in [demo](https://itskovacs-stonks.netlify.app/)* |
| 16 | + |
| 17 | +</div> |
| 18 | + |
| 19 | + |
| 20 | +## Stonks? |
| 21 | + |
| 22 | +Stonks is a minimalist personal portfolio tracker built around one idea: **less is more**. |
| 23 | + |
| 24 | +No broker integrations, no algorithmic trading. You add your trades and get clean data back for your portfolio: current prices, unrealized gains, allocation breakdowns, and a basic stock analysis page for any ticker you want to research. |
| 25 | + |
| 26 | +It does one thing and does it well: it tells you where your money is and how it's doing, with just enough market data (and pseudo-computed scores) to make informed decisions without overwhelming you. |
| 27 | + |
| 28 | +No telemetry. No tracking. No ads. Free, forever. |
| 29 | + |
| 30 | +> [!IMPORTANT] |
| 31 | +> **Keep in mind that this app is strictly for education, not financial advice. Investing involves risk, so always do your own research and invest responsibly!** |
| 32 | +
|
| 33 | +## 🌱 Getting Started <a name = "getting_started"></a> |
| 34 | + |
| 35 | +Deployment is designed to be simple using Docker. |
| 36 | + |
| 37 | +### Option 1: Docker Compose (Recommended) |
| 38 | + |
| 39 | +Use the `docker-compose.yml` file provided in this repository. No changes are required, though you may customize it to suit your needs. |
| 40 | + |
| 41 | +Run the container: |
| 42 | + |
| 43 | +```bash |
| 44 | +docker-compose up -d |
| 45 | +``` |
| 46 | + |
| 47 | +### Option 2: Docker Run |
| 48 | + |
| 49 | +```bash |
| 50 | +# Ensure you have the latest image |
| 51 | +docker pull ghcr.io/itskovacs/stonks:1 |
| 52 | + |
| 53 | +# Run the container |
| 54 | +docker run -d -p 8080:8000 -v ./storage:/app/storage ghcr.io/itskovacs/stonks:1 |
| 55 | +``` |
| 56 | + |
| 57 | + |
| 58 | +## 📸 Demo <a name = "demo"></a> |
| 59 | + |
| 60 | +A demo is available at [itskovacs-stonks.netlify.app](https://itskovacs-stonks.netlify.app/). |
| 61 | + |
| 62 | +<div align="center"> |
| 63 | + |
| 64 | +| | | |
| 65 | +|:-------:|:-------:| |
| 66 | +|  |  | |
| 67 | +|  |  | |
| 68 | + |
| 69 | +</div> |
| 70 | + |
| 71 | +<br> |
| 72 | + |
| 73 | +<div align="center"> |
| 74 | + |
| 75 | +Made with ❤️ in BZH |
| 76 | + |
| 77 | +<a href='https://ko-fi.com/itskovacs' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi1.png' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a> |
| 78 | +</div> |
| 79 | + |
| 80 | + |
| 81 | +## Data Sources |
| 82 | + |
| 83 | +All market data is sourced from Yahoo Finance (using **yfinance** library) and free public RSS feeds. No paid API keys are required. |
| 84 | + |
| 85 | +Prices and fundamental data are TTL-cached in a local SQLite store to avoid redundant fetches: |
| 86 | + |
| 87 | +| Data type | Cache TTL | |
| 88 | +| ------------------------------------ | --------- | |
| 89 | +| Quote info (price, ratios, metadata) | 1 hour | |
| 90 | +| Intraday / 5-day history | 1 hour | |
| 91 | +| Daily history (includes today) | 4 hours | |
| 92 | +| Historical range (past end date) | 90 days | |
| 93 | +| Earnings calendar, analyst targets | 24 hours | |
| 94 | +| Insider purchases, sector weightings | 24 hours | |
| 95 | +| Financials, dividends, splits | 90 days | |
| 96 | + |
| 97 | +--- |
| 98 | + |
| 99 | +## API Reference |
| 100 | + |
| 101 | +All endpoints are prefixed with `/api`. Every route except `/auth/login` and `/auth/register` requires authentication. |
| 102 | + |
| 103 | +``` |
| 104 | +Authorization: Bearer <access_token> |
| 105 | +``` |
| 106 | + |
| 107 | +Tokens are JWT. By default, access tokens expire in 30 minutes; refresh tokens in 24 hours. |
| 108 | + |
| 109 | +--- |
| 110 | + |
| 111 | +### Authentication |
| 112 | + |
| 113 | +| Method | Endpoint | Body | Returns | |
| 114 | +| ------ | ---------------- | ------------------------ | --------------------------------- | |
| 115 | +| POST | `/auth/login` | `{ username, password }` | `{ access_token, refresh_token }` | |
| 116 | +| POST | `/auth/register` | `{ username, password }` | `{ access_token, refresh_token }` | |
| 117 | +| POST | `/auth/refresh` | `{ refresh_token }` | `{ access_token, refresh_token }` | |
| 118 | + |
| 119 | +**Constraints:** username 1–19 chars `[a-zA-Z0-9_-]`. |
| 120 | +`/auth/refresh` rejects access tokens — the `typ` claim must be `"refresh"`. |
| 121 | + |
| 122 | +--- |
| 123 | + |
| 124 | +### Dashboard |
| 125 | + |
| 126 | +| Method | Endpoint | Query params | Returns | |
| 127 | +| ------ | ---------------------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | |
| 128 | +| GET | `/profile/dashboard` | — | Watchlist rows, all positions with live P&L, envelope summaries, full transaction history, and aggregate totals | |
| 129 | +| GET | `/profile/envelope/overview` | `period` — `1w / 1mo / 3mo / 6mo / ytd / 1y / 3y` | Daily mark-to-market equity chart per envelope, event timeline, and period stats (volatility, best/worst day, trade count, deposits, dividends) | |
| 130 | + |
| 131 | +**Dashboard totals** include: total portfolio value, total cash, total cost basis, unrealized P&L, 1-day change, and rolling 90-day net deposits and dividend income. |
| 132 | + |
| 133 | +--- |
| 134 | + |
| 135 | +### Watchlist |
| 136 | + |
| 137 | +| Method | Endpoint | Params / Body | Returns | |
| 138 | +| ------ | ----------------------------- | --------------------------------------- | --------------------------------------------------------------------- | |
| 139 | +| GET | `/profile/watchlist/trending` | — | Up to 8 trending US tickers with live price and 7-day history | |
| 140 | +| GET | `/profile/watchlist/search` | `q` (string), `limit` (1–20, default 8) | Tickers matching the query, enriched with live price and daily change | |
| 141 | +| POST | `/profile/watchlist/add` | `{ ticker }` | `{ status, ticker }` — snapshot of the added ticker | |
| 142 | +| POST | `/profile/watchlist/remove` | `{ ticker }` | `{ status, watchlist }` — updated list of ticker symbols | |
| 143 | + |
| 144 | +--- |
| 145 | + |
| 146 | +### Envelopes |
| 147 | + |
| 148 | +| Method | Endpoint | Body | Returns | |
| 149 | +| ------ | ------------------------- | ------------------ | ------------------------------------------------ | |
| 150 | +| POST | `/profile/envelopes/add` | `{ name, color? }` | `{ status, message }` | |
| 151 | +| PUT | `/profile/envelopes/{id}` | `{ name, color? }` | `{ status, message }` — rename or recolor | |
| 152 | +| DELETE | `/profile/envelopes/{id}` | — | `{ status, message }` — cascades to transactions | |
| 153 | + |
| 154 | +--- |
| 155 | + |
| 156 | +### Transactions |
| 157 | + |
| 158 | +| Method | Endpoint | Body | Returns | |
| 159 | +| ------ | ---------------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------------- | |
| 160 | +| POST | `/profile/transactions` | `{ type, price, envelope_name, ticker?, shares?, fees?, date?, note? }` | `{ status, message, transaction }` — the created transaction object | |
| 161 | +| DELETE | `/profile/transactions/{id}` | — | `{}` — also reverses the cash effect on the envelope's running balance | |
| 162 | + |
| 163 | +**Transaction types and total computation:** |
| 164 | + |
| 165 | +| Type | Cash effect | `total` formula | |
| 166 | +| ---------- | ----------- | ------------------------------------------------- | |
| 167 | +| `DEPOSIT` | `+total` | `abs(price)` | |
| 168 | +| `WITHDRAW` | `−total` | `abs(price)` | |
| 169 | +| `DIVIDEND` | `+total` | `shares × price` if shares > 0, else `abs(price)` | |
| 170 | +| `BUY` | `−total` | `(shares × price) + fees` | |
| 171 | +| `SELL` | `+total` | `(shares × price) − fees` | |
| 172 | + |
| 173 | +--- |
| 174 | + |
| 175 | +### Stock Research |
| 176 | + |
| 177 | +| Method | Endpoint | Params | Returns | |
| 178 | +| ------ | ------------------------ | --------------------------------------------------- | ---------------------------------------------------------------------- | |
| 179 | +| GET | `/stock/report/{ticker}` | path: ticker symbol | Full research report (see below) | |
| 180 | +| GET | `/stock/chart/{ticker}` | `period` — `1d / 1w / 1m / 3m / 6m / ytd / 1y / 5y` | OHLCV price chart with annotations (dividends, splits, earnings dates) | |
| 181 | + |
| 182 | +**Stock report payload fields:** |
| 183 | + |
| 184 | +| Field | Description | |
| 185 | +| ------------------- | --------------------------------------------------------------------------------------------- | |
| 186 | +| `stock_bar` | Current price, 1-day change, YTD return, 52-week high/low, beta, RSI, SMA 50/200, volume | |
| 187 | +| `risk_gauge` | Composite 0–100 risk score with per-component breakdown and label | |
| 188 | +| `kpi_strip` | Key financial ratios formatted for display (P/E, EV/EBITDA, ROE, etc.) | |
| 189 | +| `valuation_grid` | Valuation metrics with green/amber/red status and a 0–100 sub-score | |
| 190 | +| `health_grid` | Balance sheet and return metrics with status and sub-score | |
| 191 | +| `growth_grid` | Revenue, earnings, and margin trend metrics with status and sub-score | |
| 192 | +| `score_breakdown` | Weighted composite factor score (valuation 35%, health 35%, growth 30%) with letter grade | |
| 193 | +| `quarterly_trend` | Up to 4 trailing quarters: revenue, net income, EPS, EPS surprise %, gross margin | |
| 194 | +| `earnings_update` | Last and next earnings dates, EPS and revenue actuals vs estimates, analyst count | |
| 195 | +| `catalysts_risks` | Identified catalyst and risk factors with severity labels | |
| 196 | +| `rating_verdict` | Analyst consensus rating, price target, upside %, signal bars, and confidence level | |
| 197 | +| `signals` | Six technical indicator signals (BUY/HOLD/SELL) with raw values and aggregate summary | |
| 198 | +| `insider_activity` | 6-month buy/sell transaction counts and share totals from SEC insider filings via yfinance | |
| 199 | +| `sector_weightings` | Sector allocation map for ETFs (empty object for equities) | |
| 200 | +| `news` | Up to 10 recent headlines from Yahoo Finance, Google News, and Seeking Alpha with sentiment | |
| 201 | +| `user_positions` | The authenticated user's transactions for this ticker (BUY, SELL, DIVIDEND) | |
| 202 | +| `wac_by_envelope` | Weighted average cost recap per envelope: current shares held, avg cost, and total cost basis | |
| 203 | +| `in_watchlist` | Whether the ticker is in the current user's watchlist | |
| 204 | + |
| 205 | +--- |
| 206 | + |
| 207 | +### News |
| 208 | + |
| 209 | +| Method | Endpoint | Query params | Returns | |
| 210 | +| ------ | ---------------- | -------------------------- | -------------------------------------------------------------------------------- | |
| 211 | +| GET | `/news/{ticker}` | `limit` (1–50, default 20) | Headlines from Yahoo Finance, Google News, and Seeking Alpha with sentiment tags | |
| 212 | + |
| 213 | +Sentiment is rule-based (positive / negative / neutral) using a curated lexicon applied to the headline text. Results are deduplicated and sorted by publication date. |
0 commit comments