Audit against the live feature set surfaced one missing entry and a few soft phrasings: - Free now lists "Ask follow-up questions on any past log" — the /api/chat endpoint has no paid gate (router-level require_token only), so it's available to every signed-in user. The landing-page screenshot already showed it; the pricing page didn't mention it. - "Per-group cross-asset summaries" → "Cross-asset indicator panels (equities, rates, FX, …) with a one-paragraph AI read on each tab" — more concrete about what the user actually sees. - "Novice / Intermediate reading levels" → spelled out what each does (Novice defines jargon; Intermediate is terse). - Free's exclusion list explicitly includes "Daily email digest" so the paid/free distinction reads cleanly without back-and-forth. - Paid's daily-digest bullet now leads with the word count target (~600 words) so the value is concrete, not abstract. - Encrypted cloud sync bullet now names the actual security model (PIN-derived in-browser + server-side outer wrap). Added a small "Invite a friend" footnote — the credit ledger and invite link both ship today; the rate kicks in with the payments rollout. Honest phrasing keeps it from looking like vaporware. Intro paragraph rewritten to lead with what's free (most of the editorial) rather than what paid extends, since the free tier is the entry point. |
||
|---|---|---|
| alembic | ||
| app | ||
| config | ||
| docs/superpowers | ||
| tasks | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| alembic.ini | ||
| docker-compose.override.yml | ||
| docker-compose.prod.yml | ||
| docker-compose.test.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| pyproject.toml | ||
| README.md | ||
Read the Markets
Containerised macro-strategy dashboard — hourly market data, RSS news, Trading 212 portfolio, and an AI-generated strategic log written by Cassandra, the in-product seer. Read-only by design.
Production:
- Landing: https://read.markets
- App: https://app.read.markets
The Python package is still named cassandra and several internal identifiers (cookie names, advisory-lock keys, CASSANDRA_TOKEN env var, CSS filename) keep the legacy name on purpose — renaming them would invalidate live sessions / locks / configs for no user benefit. See app/branding.py for the brand single-source-of-truth.
Quick start (local dev)
cp .env.example .env # fill in API keys; set CASSANDRA_TOKEN if exposing
docker compose up --build # db + app + scheduler + daily backup sidecar
open http://localhost:8000/ # or whichever CASSANDRA_PORT you set
docker-compose.override.yml is auto-loaded and adds the host port
binding so the app is reachable on localhost.
Production (VPS, NPM-fronted)
Always invoke with explicit -f flags — that way the dev override is
skipped and the prod overlay (no host port, joins the external
intranet Docker network, uvicorn on port 80) is applied:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
Point Nginx Proxy Manager at upstream readmarkets-app-1:80.
Architecture
- app (FastAPI + Jinja2 + HTMX) — web dashboard on port 8000
- scheduler (APScheduler) — hourly ingestion jobs (market, news, portfolio, AI log)
- db (MariaDB 11) — quotes, headlines, portfolio snapshots, strategic logs, job runs
- backup (sidecar) — daily mariadb-dump to
./backup/
See /home/gg/.claude/plans/ok-i-think-this-tidy-lake.md for the design plan.
Config
| File | Purpose |
|---|---|
config/default.toml |
Universal data tables: indicator groups, RSS feeds, keyword presets |
config/portfolio.toml |
User-specific portfolios (overrides default.toml) |
.env |
Secrets and runtime knobs — mounted read-only into containers |
Endpoints
GET /— dashboardGET /portfolio/{name}— portfolio detailGET /news— news feedGET /log— strategic-log archiveGET /api/health— job status (last success / failure per job)
All authenticated routes require Authorization: Bearer $CASSANDRA_TOKEN if the env is set; if unset, the app is open (LAN-only mode).