Skip to content

Commit 2608782

Browse files
committed
Initial commit 📈
0 parents  commit 2608782

84 files changed

Lines changed: 19904 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/FUNDING.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ko_fi: itskovacs

.github/sc_browse.png

253 KB
Loading

.github/sc_dashboard.png

164 KB
Loading

.github/sc_dashboard_chart.png

192 KB
Loading

.github/sc_ticker1.png

166 KB
Loading

.github/sc_ticker2.png

150 KB
Loading
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: Build and Deploy Docker
2+
3+
on:
4+
push:
5+
tags:
6+
- '*'
7+
8+
env:
9+
REGISTRY: ghcr.io
10+
IMAGE_NAME: ${{ github.repository }}
11+
12+
13+
jobs:
14+
build:
15+
runs-on: ubuntu-latest
16+
permissions:
17+
contents: read
18+
packages: write
19+
id-token: write
20+
21+
steps:
22+
- name: Checkout repository
23+
uses: actions/checkout@v4
24+
25+
- name: Install cosign
26+
if: github.event_name != 'pull_request'
27+
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0
28+
with:
29+
cosign-release: 'v2.2.4'
30+
31+
- name: Set up Docker Buildx
32+
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
33+
34+
- name: Log into registry ${{ env.REGISTRY }}
35+
if: github.event_name != 'pull_request'
36+
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
37+
with:
38+
registry: ${{ env.REGISTRY }}
39+
username: ${{ github.actor }}
40+
password: ${{ secrets.GITHUB_TOKEN }}
41+
42+
- name: Extract Docker metadata
43+
id: meta
44+
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
45+
with:
46+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
47+
tags: |
48+
type=semver,pattern={{version}}
49+
type=semver,pattern={{major}}.{{minor}}
50+
type=semver,pattern={{major}}
51+
type=raw,value=latest
52+
53+
- name: Build and push Docker image
54+
id: build-and-push
55+
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
56+
with:
57+
context: .
58+
push: ${{ github.event_name != 'pull_request' }}
59+
tags: ${{ steps.meta.outputs.tags }}
60+
labels: ${{ steps.meta.outputs.labels }}
61+
cache-from: type=gha
62+
cache-to: type=gha,mode=max
63+
platforms: linux/amd64,linux/arm64
64+
65+
- name: Sign the published Docker image
66+
if: ${{ github.event_name != 'pull_request' }}
67+
env:
68+
TAGS: ${{ steps.meta.outputs.tags }}
69+
DIGEST: ${{ steps.build-and-push.outputs.digest }}
70+
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}

CONTRIBUTING.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Contributing
2+
3+
Thank you for your interest in contributing! Pull requests, bug fixes, and improvements are very welcome.
4+
5+
6+
## Maintainer availability
7+
8+
This project is maintained by a single person in their spare time. Please be patient with response times; while I try to be responsive, things can occasionally slip through. Friendly reminders are appreciated.
9+
10+
11+
## Code of conduct
12+
13+
Please be respectful and constructive in all interactions. We are all humans contributing in our free time, kindness makes collaboration easier and more enriching.
14+
15+
16+
## Pull requests
17+
18+
- Keep PRs focused and atomic (one feature/fix per PR).
19+
- Link the related Issue in the description.
20+
- Include context on *what* changed and *why*.
21+
- **UI Changes**: Please include screenshots or a recording.
22+
23+
24+
## Security
25+
26+
For security reports, please refer to [SECURITY.md](https://github.com/itskovacs/stonks/blob/main/SECURITY.md).

Dockerfile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Node builder
2+
FROM node:22 AS build
3+
WORKDIR /app
4+
COPY src/package*.json ./
5+
RUN npm install
6+
COPY src .
7+
RUN npm run build
8+
9+
# Server
10+
FROM python:3.14-slim
11+
LABEL maintainer="github.com/itskovacs"
12+
LABEL description="Minimalist personal portfolio tracker"
13+
WORKDIR /app
14+
COPY backend .
15+
RUN pip install uv
16+
RUN uv pip install --no-cache-dir -r requirements.txt --system
17+
COPY --from=build /app/dist/stonks/browser frontend
18+
EXPOSE 8000
19+
CMD ["fastapi", "run", "/app/main.py", "--host", "0.0.0.0", "--port", "8000"]

README.md

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
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+
[![Sponsor](https://img.shields.io/badge/sponsor-30363D?style=for-the-badge&logo=GitHub-Sponsors)](https://ko-fi.com/itskovacs)
7+
![Status](https://img.shields.io/badge/status-active-success?style=for-the-badge)
8+
[![GitHub Issues](https://img.shields.io/github/issues/itskovacs/stonks?style=for-the-badge&color=ededed)](https://github.com/itskovacs/stonks/issues)
9+
10+
</div>
11+
12+
<div align="center">
13+
14+
![Stonks](./.github/sc_dashboard.png)
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+
| ![](./.github/sc_dashboard.png) | ![](./.github/sc_ticker1.png) |
67+
| ![](./.github/sc_dashboard_chart.png) | ![](./.github/sc_ticker2.png) |
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

Comments
 (0)