DipWise is a full-stack investment analytics monorepo: compare ETFs/stocks, detect historical drawdowns from rolling highs, backtest “buy the dip” vs dollar-cost averaging, inspect moving averages, and (with an account) track portfolios, watchlists, and alerts. Market data is persisted in PostgreSQL and can be refreshed from Yahoo Finance (default) or optional Polygon / Alpha Vantage providers.
Disclaimer: DipWise is for educational and historical backtesting only. Nothing here is financial advice. Past performance does not guarantee future results.
- What’s implemented
- What’s left / gaps
- Architecture
- Quick start
- Environment variables
- API client codegen
- Tests
- API overview
- Further reading
- Dip detection — Rolling high over configurable calendar-window lookback; drawdown threshold (fraction 0–1) defines a dip; first day of each “episode” in the user’s date range.
- Backtest — Fixed notional per dip vs DCA baseline over the same cash deployment schedule; per-dip holding-period returns (e.g. 30 / 90 / 180 / 365 / 730 trading days).
- Analysis endpoint —
POST /analysis/dipsacceptsDipAnalysisRequest(symbol, date range, threshold, investment amount, lookback,holding_period_daysarray). Public (no auth required).
GET /prices/{symbol}— Daily OHLCV +adj_closefromdaily_prices, with provider backfill when data is missing or stale (PRICE_STALE_DAYS).- Optional SMA / EMA — Query params
sma_periodsandema_periods(repeatable ints, clamped). Server loads warmup history beforestartso indicators are valid from the first day in range. Response includes legacysma_100/ema_100when period100is requested, plussma_by_period/ema_by_periodmaps for all requested periods. POST /prices/{symbol}/refresh— Force re-fetch for a range (same indicator query params supported).- Implementation —
apps/api/app/services/indicator_service.py(pandas rolling mean + EWMA span); logic wired inapps/api/app/routers/prices.py.
- Types —
dip_threshold(drawdown vs rolling high, same idea as backtester),price_below(absolute close),sma_cross(close crosses above or below a simple moving average of configurable period). - Storage —
params_jsononalerts(Alembic revision002) for SMA cross (kind: "sma_cross",period,direction). - Evaluation — Celery
run_check_alerts(scheduled) runscheck_alerts; at most onealert_eventper alert per calendar day.
- Celery worker + Redis — Price ingestion for symbols referenced by active alerts/watchlists plus
SPY; alert checking on a beat schedule. - Docker Compose —
api,web,postgres,redis,celery_worker,celery_beat;migrateone-off runsalembic upgrade headbefore app containers.
- JWT — Register / login; bearer token used by the generated client from
localStorage(dipwise_token). - Frontend —
apps/web/src/App.tsx: Portfolio, Watchlist, and Alerts sit underRequireAuth. Dashboard, Compare, Moving Averages, Dip Backtester, Reports, and/loginare public for browsing and analysis.
| Route | Purpose |
|---|---|
/ |
Dashboard with quick links |
/compare |
Up to 6 symbols; normalized performance (rebased to 100); optional 100-day SMA and EMA overlays (rebased) |
/moving-averages |
Multi-symbol table: last bar, configurable SMA/EMA period (preset days), % vs SMA |
/backtester |
Dip Backtester: percent dip threshold UI, preset holding-period chips (30 / 90 / 180 / 365 / 730), responsive ticker search, two-column filter layout on large screens, helper copy, Recharts price/dip/SMA+EMA chart with Brush (range zoom), save run (authenticated) to saved_backtests, interactive cards on results |
/reports |
CSV / PDF export from dip analysis |
/login |
Auth |
/portfolio, /watchlist, /alerts |
Authenticated CRUD-style flows (see gaps below) |
Stack details: TanStack Query, React Hook Form + Zod, Recharts, shared types from @dipwise/shared (Orval).
POST /reports/dips/csvandPOST /reports/dips/pdf— Same body as/analysis/dips; public.
GET /news/{symbol}— WhenNEWS_API_KEYis set; requires bearer in the API. Backtester shows headlines when the user is signed in.
- OpenAPI → TypeScript —
npm run codegen:apiexports spec and runs Orval intopackages/shared. - Backend tests —
pytestunderapps/api/app/tests/(health, auth, dip detection, prices + indicators, SMA cross alerts, etc.). - Frontend tests — Vitest + Testing Library in
apps/web(npm test); smoke tests for auth routing (RequireAuth).
These are known follow-ups—not an exhaustive product roadmap.
- Keep
docs/api.mdanddocs/architecture.mdaligned when you add endpoints or change routing.
- Portfolio — Further broker-grade reporting (cost basis lots, dividends) if needed beyond transactions + positions.
- Alerts — No email/push delivery yet; events are stored and visible in-app via
GET /alerts/events.
- Production — See checklist in
infra/docker/docker-compose.prod.example.yml; secrets, TLS, managed DB/Redis.
- Corporate actions — Adjusted close depends on provider quality; no separate validation pipeline in-app.
- Staleness —
PRICE_STALE_DAYSdrives refresh; real-time quotes are out of scope.
apps/web/ React + TypeScript + Vite frontend
apps/api/ FastAPI + SQLAlchemy backend
packages/shared/ OpenAPI + Orval-generated client (`openapi.json`, `api.ts`)
infra/docker/ Dockerfiles and Compose
docs/ Architecture, data model, API notes
The backend uses routers → services → models; Pydantic schemas validate I/O.
Deeper detail: docs/architecture.md · ERD / tables: docs/data-model.md · HTTP reference (partial): docs/api.md
cp .env.example .env
docker compose up --build| Service | URL / note |
|---|---|
| Frontend | http://localhost:5173 |
| API | http://localhost:8000 |
| Swagger | http://localhost:8000/docs |
| PostgreSQL (host) | localhost:5433 by default (POSTGRES_PUBLISH_PORT) so it does not clash with a local Postgres on 5432 |
Migrations: The migrate job applies alembic upgrade head (includes revision 002 for alerts.params_json) before dependent services start.
Auth: Create an account at /login to use Portfolio, Watchlist, and Alerts. Other analysis pages work without signing in.
Backend:
cd apps/api
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
alembic upgrade head
uvicorn app.main:app --reload --port 8000Frontend:
cd apps/web
npm install
npm run devYou need PostgreSQL and Redis locally; point DATABASE_URL and REDIS_URL in .env accordingly.
| Variable | Description | Default / notes |
|---|---|---|
POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB |
Database identity | See .env.example |
POSTGRES_PUBLISH_PORT |
Host port for Postgres | 5433 |
DATABASE_URL |
SQLAlchemy URL | Set for compose or local |
REDIS_URL |
Celery broker | e.g. redis://redis:6379/0 |
JWT_SECRET_KEY |
Signing key for JWT | Change in production |
MARKET_DATA_PROVIDER |
yahoo · polygon · alphavantage |
yahoo |
MARKET_DATA_API_KEY |
Polygon / Alpha Vantage | Empty for Yahoo |
NEWS_API_KEY |
NewsAPI.org | Optional |
VITE_API_BASE_URL |
API origin for the SPA | http://localhost:8000 |
- Strong
POSTGRES_PASSWORD,JWT_SECRET_KEY, and Redis credentials. - TLS at the edge; do not expose Postgres/Redis publicly.
- Prefer managed Postgres/Redis for production.
- See infra/docker/docker-compose.prod.example.yml as a starting sketch only.
After changing FastAPI routes or schemas:
# From repo root; Python 3 with API deps available
npm run codegen:apiRuns apps/api/scripts/export_openapi.py and Orval in packages/shared.
cd apps/api && python3 -m pytest app/tests/ -qcd apps/web && npm testFor watch mode during frontend work: cd apps/web && npm run test:watch.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /health |
No | Liveness |
| POST | /auth/register |
No | Create user |
| POST | /auth/login |
No | JWT |
| GET | /auth/me |
Yes | Profile |
| POST | /analysis/dips |
No | Dip detection + backtest |
| GET | /prices/{symbol} |
No | Daily bars; optional sma_periods, ema_periods; maps sma_by_period / ema_by_period |
| POST | /prices/{symbol}/refresh |
No | Force provider refresh |
| GET | /assets/, /assets/{symbol} |
No | Tracked assets |
| GET | /news/{symbol} |
Yes | Headlines if configured |
| GET/POST | /portfolios/… |
Yes | Portfolios; POST/GET …/{id}/transactions |
| GET/POST | /alerts/… |
Yes | Alerts (dip_threshold, price_below, sma_cross) |
| GET | /alerts/events |
Yes | Recent alert firings for the user |
| GET/POST | /saved-backtests/… |
Yes | Persisted backtest snapshots |
| GET/POST/DELETE | /watchlists/… |
Yes | Watchlists |
| POST | /reports/dips/csv, /reports/dips/pdf |
No | Exports |
Full reference: docs/api.md (update as endpoints evolve).
- docs/architecture.md — Workers, external APIs, codegen.
- docs/data-model.md — Tables and relationships.
- docs/api.md — Endpoint reference (keep in sync with OpenAPI).
Prior roadmap ideas (still valid): outbound alert channels (email/webhook); deeper news/NLP; E2E tests (e.g. Playwright).