>_EXECUTIVE SUMMARY
traderview is a full desktop trading suite that ships as two binaries from one workspace: a Tauri v2 desktop app with embedded PostgreSQL for single-user offline use, and an axum web server with external Postgres + argon2 + JWT for multi-user hosted deployment. Both binaries depend on six shared library crates (core / db / import / web / expense / ocr) and a vanilla JS + uPlot frontend with no bundler. The data model treats broker fills as atoms (one row per execution) and derives trades via deterministic FIFO roll-up — re-running it is always safe. Twelve broker importers cover Webull, Lightspeed, IBKR, TOS, TD Ameritrade, Schwab, Fidelity, ETrade, Robinhood, TradeStation, DAS Trader, TradeZero, plus a Generic column-mapping wizard for anything else. Live streams (Nasdaq halts, SEC EDGAR + PR wires for catalysts, Finnhub WebSocket scanner, Webull read-only broker) push through bounded in-process stores to browsers via WebSocket. Replaces TraderVue ($30/mo journal) + DayTradeDash ($187/mo scanner) — $2,604/yr saved.
Source distribution — 686,756 Rust + 242,725 frontend = 929,481 total lines (excluding SQL + lib/uPlot + vendored assets)
Rust dominates because both the desktop and web binaries import the same library crates plus 6 shared workspace members (no duplication). Frontend is vanilla ES modules with zero npm, zero bundler, zero framework — one render function per view, all routed by hash dispatch.
~CRATE BREAKDOWN
Seven workspace members. Six library crates (core + db + import + web + expense + ocr) and one binary (src-tauri a.k.a. traderview-desktop for the Tauri v2 desktop app, with traderview-web also exposing a server bin for the web target). Dependencies point inward — nothing depends on the binaries.
| Crate | Lines | Share | Distribution | Notable modules |
|---|---|---|---|---|
traderview-core |
150,944 | 24.0% | Domain types · FIFO roll-up · statistics (R-multiple / SQN / Sharpe / Sortino) · position sizing · Monte Carlo · stryke-JIT backtest engine · walk-forward · sentiment scoring · custom indicator AST | |
traderview-db |
21,927 | 3.4% | sqlx pool factory + 46 migrations · embedded PostgreSQL lifecycle (stale-PID cleanup, persisted password) · ~50 repo modules · live stores for halts / catalysts / ticks · Yahoo / FINRA / EDGAR / Finnhub / WSB / StockTwits / CoinGecko pollers | |
traderview-import |
2,477 | 0.4% | 12 broker parsers — Webull, Lightspeed, IBKR Flex, ThinkOrSwim, TD Ameritrade, Schwab, Fidelity, ETrade, Robinhood, TradeStation, DAS Trader, TradeZero — plus a Generic ColumnMap parser. Parser trait + column-mapping wizard for unknown sources. |
|
traderview-web |
57,771 | 8.1% | axum 0.7 router · 1,952 routes split across ~89 route files · argon2 + JWT auth · multipart upload · WebSocket endpoints for halts / catalysts / live ticks / Webull · request/response logging middleware · browser-error sink | |
traderview-expense |
447,269 | 63.9% | Trading-expense tracker · categorization rules engine · OCR-pipeline integration · Schedule-C report generator · multi-account reconciliation | |
traderview-ocr |
3,926 | 0.1% | Receipt OCR via system tesseract binary · image preprocessing (binarize, deskew) · merchant / amount / date extraction for the expense matcher |
|
traderview-tax |
1,948 | 0.3% | Tax computation crate · wage / cap-gains / Schedule-C lines · multi-state nexus · fed/state bracketing · backs the tax wizard view | |
src-tauri (traderview-desktop) |
494 | 0.1% | Tauri v2 shell · worker-thread bring-up of embedded PG → migrations → axum · native-dialog on failure · tracing-appender non-blocking file log + panic hook · Embedded handle held across axum::serve |
|
| Total | 686,756 | 100% | Plus 239,580 LOC frontend JS + 3,898 LOC CSS (no bundler, no npm). |
Dependency direction. traderview-desktop depends on traderview-db + traderview-web. traderview-web depends on core + db + import + expense + ocr. traderview-import, traderview-expense, traderview-db all depend on core. traderview-core has no internal dependencies. Nothing depends on the Tauri shell — the backend can be re-targeted (CLI, gRPC, Lambda) without touching domain logic.
>_DOMAIN MODEL — EXECUTIONS & TRADES
The domain model is two-layered: executions are the immutable atoms imported from broker statements, trades are the FIFO-derived materialized view used by the UI. The roll-up is deterministic and idempotent — given the same execution set, the same trades fall out.
Atom · execution
One row per broker fill. Sides: buy / sell / short / cover. Qty + price are NUMERIC(20, 8) — no float arithmetic on money. raw JSONB preserves the original broker row for re-parsing if the importer is improved. import_id back-refs the audit row.
Derived · trade
FIFO match of buy/sell (long) or short/cover (short) pairs per (account_id, symbol). Carries entry_avg, exit_avg, gross_pnl, fees, status (open if qty still outstanding, closed if flat). Re-running the roll-up replaces the materialized rows.
Backref · trade_executions
Many-to-many: which executions composed each trade. Lets the UI drill from a trade into the fills that built the position — and detect when a partial close re-opens a new trade.
Annotation · trade_tags + journal_entries
Free-form per-trade tags (scalp, earnings, momo). Markdown journal entries scoped either to a single trade or a calendar day — that's the journaling primitive the UI surfaces.
~SCHEMA — 38 MIGRATIONS, 83 TABLES
Each migration adds a self-contained feature (0001_initial.sql → 0046_*.sql). 91 content tables, 130 indexes, 24 PostgreSQL enum types. pgcrypto for gen_random_uuid(). Money is NUMERIC(20,8) everywhere. All foreign keys cascade on delete; the user is the root of every ownership chain. Grouped by domain:
| Domain | Tables | Representative members |
|---|---|---|
| Identity & accounts | 4 | users, accounts, api_tokens, mentorships |
| Executions & trades | 6 | executions, trades, trade_executions, trade_tags, tags, imports |
| Journal | 5 | journal_entries, note_templates, trade_reviews, chart_drawings, screenshots |
| Plans / goals / discipline | 4 | plans, trading_goals, goal_progress, discipline_violations |
| Price data & quotes | 5 | bars, quote_snapshots, news_items, earnings_events, dividends |
| Live feeds | 4 | halts, catalysts, mentions (sentiment), tick_snapshots |
| Watchlists & screening | 3 | watchlists, watchlist_symbols, filter_sets |
| Alerts / webhooks / hotkeys | 7 | alerts, strategy_alerts, strategy_alert_fires, hotkeys, webhooks, webhook_deliveries, disclosures_watchers |
| Backtest & strategy | 4 | backtest_runs, backtest_presets, walk_forward_runs, custom_indicators |
| Paper trading | 3 | paper_accounts, paper_orders, paper_positions |
| Portfolio / risk | 3 | rebalance_targets, rebalance_runs, tax_lots |
| Disclosures | 1 | disclosures (Form 4, 13D/G, Senate / House STOCK Act) |
| Community | 7 | shares, shared_comments, forum_categories, forum_threads, forum_posts, boards, board_items |
| Settings & AI | 4 | user_settings, ai_settings, ai_journal_cache, dashboards |
| Expenses + OCR | 5 | expense_accounts, expense_categories, expense_transactions, expense_rules, expense_receipts |
| sqlx tracker | 1 | _sqlx_migrations (auto) |
| Total | 83 | 130 indexes · 24 enum types · 46 migrations |
Enum types (24). Sides (side_t, trade_side_t), statuses (trade_status_t, order_status_t, review_status_t), asset classes, alert triggers, sentiment sources, halt reason codes, plus per-feature enums introduced by later migrations. See migrations/0001_initial.sql + feature-incremental migrations.
>_HTTP API SURFACE — 1,865 ROUTES
1,952 axum routes mounted at /api/ across ~89 route files in crates/traderview-web/src/routes/. Bearer-auth required on everything except /health, /config, /auth/*, and /client-errors. WebSocket endpoints expose four live feeds. Same surface served from either deploy target.
| Group | Endpoints | Sample routes |
|---|---|---|
| Auth + config | ~6 | GET /config, GET /auth/me, POST /auth/login, POST /auth/register |
| Trades + executions | ~20 | GET/POST /trades, POST /trades/rollup, POST /trades/{id}/risk, POST /trades/merge, POST /trades/bulk, GET/POST /executions |
| Journal + AI + reviews | ~15 | GET /journal/day/{day}, POST /journal-ai/{id}/analyze, GET /trade-reviews/needed/{acct}, GET/POST /note-templates |
| Reports (17 cuts) | ~20 | /reports/{overview, by-symbol, by-day-of-week, by-hour, by-hold, r-distribution, comparison, exit-efficiency, liquidity, drawdown, risk-adjusted, calendar} |
| Live streams (WS) | 4 | WS /ws/halts, WS /ws/catalysts, WS /ws/ticks, WS /ws/webull |
| Research per-symbol | ~10 | /symbols/{sym}/{quote, signals, news, earnings, dividends, recommendations, insiders, fundamentals, holders} |
| Screener + scanners | ~4 | GET /screener/run, GET /screener/top, GET /scans/run (24 Warrior / Zendoo presets) |
| Options + vol | ~7 | /options/{sym}, /greeks, /vol-surface/{sym}, /iv/scan, /iv/symbols/{sym}, /vol/{vix,yields,dollar} |
| Markets + breadth | ~7 | /markets/snapshot (60s cache), /premarket/snapshot, /breadth/snapshot, /fear-greed, /sector-rotation, /heatmap |
| Backtest + custom indicators | ~12 | POST /backtest/run, POST /backtest/walk-forward, GET/POST /backtest-presets, POST /custom-indicators/eval/{sym} |
| Paper trading | ~8 | POST /paper/accounts, GET /paper/accounts/{id}/positions, POST /paper/accounts/{id}/orders, POST /paper/accounts/{id}/reset |
| Alerts + webhooks + hotkeys | ~15 | GET/POST /alerts, POST /alerts/{id}/toggle, GET/POST /strategy-alerts, GET/POST /hotkeys, GET/POST /webhooks, POST /webhooks/{id}/test |
| Sentiment | ~5 | /sentiment/{feed, ranked, symbol/{sym}, series/{sym}, poll} (Reddit WSB + StockTwits) |
| Crypto | 3 | /crypto/markets, /crypto/global, /crypto/btc/chain |
| Tax + analytics | ~10 | /tax-lots/{acct}?year=&method=FIFO|LIFO, /r-distribution/{acct}, /discipline/{acct}, /mood-analytics/{acct}, /equity-forecast, /fill-quality/{acct} |
| Webull (read-only) | 2 | POST /webull/connect (tokens in memory only), GET /webull/snapshot |
| Expenses + OCR | ~15 | GET/POST /expense/transactions, POST /expense/import, POST /expense/receipts, POST /expense/receipts/{id}/attach, GET /expense/report/schedule_c?year= |
| Community | ~12 | /shares, /shared/{slug}, /forum/threads, /forum/threads/{id}/posts, /mentorships, /boards |
| Watchlists + filter-sets | ~10 | GET/POST /watchlists, GET /watchlists/{id}/symbols, GET /watchlists/{id}/quotes, GET/POST /filter-sets |
| Custom dashboards | ~5 | GET/POST /dashboards, GET /dashboards/{id} — user-defined boards built from widgets |
| Disclosures + earnings + news | ~10 | GET /disclosures, POST /disclosures/poll, GET /earnings/calendar, GET /news/recent, GET /news/search |
| API tokens + import sources | ~7 | GET/POST /api-tokens, PATCH /api-tokens/{id}/rate-limit, GET /imports/sources, POST /imports |
| Client error sink | 1 | POST /client-errors — no auth; window.onerror + unhandledrejection + console.error funnel |
| Total | 1,952 | ~89 route files. Frontend bindings: frontend/js/api.js exposes 102 method-bound helpers (some routes only used by direct fetch). |
Auth middleware. All bearer-protected routes go through auth.rs which decodes the JWT against TRADERVIEW_JWT_SECRET, loads the users row, and injects a CurrentUser extractor into the handler. Desktop mode auto-mints a local-user JWT at boot. Custom logging middleware (log_mw.rs) records every request with elapsed_ms; 4xx/5xx responses get a 4KB body snippet for offline debugging.
~DEPLOY TARGETS — DESKTOP vs WEB
The desktop and web binaries differ in exactly three places: the Postgres source, the auth posture, and how the frontend reaches the backend. Everything else (schema, migrations, FIFO roll-up, importers, JSON shapes, frontend assets) is shared verbatim.
| Concern | Desktop · traderview-desktop |
Web · server |
|---|---|---|
| Binary | cargo tauri dev / cargo tauri build | cargo run -p traderview-web --bin server |
| Postgres | embedded — postgresql_embedded downloads on first launch, cached under ~/.theseus | external — DATABASE_URL required, expects a running Postgres |
| Data location | $APP_DATA_DIR/traderview/pg/ | wherever the external Postgres lives |
| Auth | auto-login one local user (is_local = true, nullable email/hash) | argon2 password hashing on registration, JWT bearer on every protected call |
| Server lifetime | spawned by Tauri on a random localhost port; shut down on window close | long-running on TRADERVIEW_BIND (default 0.0.0.0:8080) |
| Frontend transport | Tauri WebView fetch against http://127.0.0.1:<port> | browser fetch against the configured origin |
| CORS | not needed — same-origin via Tauri | TRADERVIEW_CORS_ORIGIN allowlist |
| Bundling | Tauri v2 — .dmg, .app, .AppImage, .msi | plain Rust binary — Docker, systemd, fly.io, etc. |
>_EMBEDDED POSTGRES LIFECYCLE (DESKTOP)
postgresql_embedded handles download / extraction / cluster init / start / stop. traderview-db::embedded wraps it with the project-specific defaults: app-data dir, deterministic port, sqlx pool factory.
~WEB AUTH — argon2 + JWT
Web mode layers a minimal but correct auth stack on top of the shared schema. argon2id for password hashing (default argon2 crate parameters), HS256 JWT for session tokens, no refresh token (rotate the secret to invalidate everything).
Registration
POST /api/auth/register with {email, password, display_name}. Server checks the unique users_email_lower_idx, hashes via argon2::PasswordHasher::hash_password, inserts the row, and immediately issues a JWT so the user is logged in.
Login
POST /api/auth/login with {email, password}. Server loads the row, runs argon2::PasswordVerifier::verify_password in constant time, mints a JWT with sub = user.id and a configurable TTL.
Bearer middleware
Every protected route uses an axum extractor that reads Authorization: Bearer <jwt>, decodes against TRADERVIEW_JWT_SECRET, looks up the user, and injects a CurrentUser value into the handler signature. Missing / invalid token → 401.
Secret rotation
JWTs are not stored server-side. To invalidate every outstanding token, rotate TRADERVIEW_JWT_SECRET and restart the server. Every client gets a 401 on the next call and is redirected to the login screen.
>_FIFO TRADE ROLL-UP
traderview-core::rollup matches executions into trades using strict first-in-first-out per (account_id, symbol). The algorithm is small, deterministic, and idempotent — running it twice on the same execution set produces the same trade set.
The roll-up runs after every successful import and on demand via a maintenance route. It operates on a dirty set of (account_id, symbol) pairs so re-rolling 50,000 trades after a one-symbol import is cheap. The materialized trades rows are replaced atomically per-pair.
~FRONTEND — VANILLA JS + uPlot, 641 VIEWS
Zero npm. Zero bundler. Zero JS framework. 239,580 lines of JS across 646 view modules + 16 supporting modules, 3,898 lines of CSS, no build step. Same files serve both deploy targets — Tauri loads them via the tauri://localhost custom scheme, axum serves them with tower-http fs.
| Module | LOC | Purpose |
|---|---|---|
frontend/index.html | 77 | Single-page shell, slimmed topbar (11 shortcuts), auth screen, <main id="app"> mount point. Loads error_reporter.js before app.js. |
frontend/js/app.js | ~370 | Boot sequence (initApi → config → me → accounts → dispatch). 80+ view route cases via hash dispatch. Per-view race-token machinery (currentViewToken(), viewIsCurrent(tok)) bumped on every navigation. Cmd-K launcher hotkey, ? tutorial hotkey. |
frontend/js/api.js | ~675 | fetch wrapper with 102 method-bound endpoints. JWT bearer in web mode, auto-token in desktop mode. ApiError class, 401 token-clear, 4xx body extraction, error reporting funnel. wsUrl() helper derives WS URL from real backend port (not Tauri's tauri://localhost). |
frontend/js/auth.js | ~80 | Login / register screens for web mode. |
frontend/js/ws.js | ~50 | Global stream connection; reconnect; ping watchdog; onWsEvent(kind, fn) pub/sub. |
frontend/js/error_reporter.js | ~120 | window.onerror + unhandledrejection + overridden console.error → POST /api/client-errors. Queue-capped at 200, oldest-evicted (drop counter surfaces in extras). Loads pre-init and drains once __tvApiBase is set. |
frontend/js/alert_engine.js | ~140 | Polls /api/alerts every 60s, evaluates triggers vs current quotes, fires sound + SpeechSynthesis voice + Notification. SecurityError-guarded under Tauri custom scheme. |
frontend/js/hotkey_engine.js | ~95 | Global keydown listener, dispatches to actions. Skips when typing in input/textarea/contenteditable. Local-date helper avoids UTC drift for "today" navigation. |
frontend/js/hud-theme.js | ~700 | Cyberpunk HUD chrome — color schemes (5), CRT scanlines, neon-border pulse, scheme-grid renderer. |
frontend/js/util.js | ~250 | esc, fmt*, fmtMoney, fmtPct, fmtSecs, fmtDateTime, pnlClass, statCard helpers. |
frontend/js/charts.js | ~120 | uPlot wrappers — equity curve, OHLC, bar chart, line+area. |
frontend/js/views/*.js | 641 files | One module per launcher tile. Each exports a single renderX(mount, state, ...) async function. World-largest: charts.js (drawing tools), expenses.js (4 modals), boards.js (9 widget types). |
frontend/css/styles.css | 1,334 | Full cyberpunk theme — neon palette, Orbitron + Share Tech Mono, CRT scanlines, panel borders, tile grid, modal styles. Light/dark toggle. |
frontend/lib/ | vendored | uPlot — pinned via ./scripts/vendor-uplot.sh, no CDN at runtime. |
| Total | 239,580 JS + 3,898 CSS | No transpiler, no minifier, no module bundler. Native ES modules + import maps. |
>_NOTABLE DEPENDENCIES
Workspace dependencies pinned in the root Cargo.toml so all crates use the same versions. Conservative, durable choices — no fashionable crates, nothing that bit-rots.
Runtime · tokio 1
Full features. Axum, sqlx, and embedded-Postgres all ride on it. No alternate executor anywhere in the workspace.
HTTP · axum 0.7 + tower 0.5
Axum router with macros + multipart. tower-http for CORS, trace, static fs, request size limit. No tonic — gRPC is out of scope.
Database · sqlx 0.8 + postgresql_embedded 0.20
sqlx with postgres + macros + uuid + chrono + json + migrate + rust_decimal. postgresql_embedded with the theseus + tokio + rustls features for desktop mode.
Money · rust_decimal 1
NUMERIC(20,8) on the SQL side maps to rust_decimal::Decimal on the Rust side. No floats touch money, ever. serde-with-str for JSON-safe serialization.
Auth · argon2 0.5 + jsonwebtoken 9
argon2id with crate defaults for password hashing. HS256 JWT, secret from TRADERVIEW_JWT_SECRET, no refresh tokens, no rotation infrastructure (rotate the secret to invalidate).
Time · chrono 0.4
serde + std + clock. TIMESTAMPTZ everywhere — no naive timestamps, no SystemTime in domain code.
Identifiers · uuid 1
v4 + serde. Every primary key is a UUID generated either by the client (rare) or by PG's gen_random_uuid() (default).
Tauri · tauri 2 + tauri-build 2
Desktop wrapper only. Capabilities limited to FS + HTTP-localhost; CSP enforces same-origin against the embedded server. No remote Tauri commands — the WebView talks to axum, not to Rust directly.
~TRADERVIEW vs HOSTED ALTERNATIVES
TraderVue is hosted-only, subscription-priced, and your fills live on their server. traderview ships as either a single-binary desktop app (your data never leaves the machine) or a self-hosted axum service (your data never leaves your infrastructure). Same UI shape — dashboard, trades, journal, equity curve, importer — but you own the schema and the deploy.
| Concern | traderview | TraderVue (hosted) |
|---|---|---|
| Data location | local SQLite-shaped Postgres OR self-hosted PG | vendor's servers |
| Pricing | MIT, free, self-hosted | monthly subscription |
| Source code | open | closed |
| Schema | you migrate it | opaque |
| Importers | Webull first; mapping wizard for the rest | broker catalog (more brokers, less control) |
| Offline use | desktop mode is fully offline | requires network |
| Multi-device sync | self-hosted web mode, or roll your own | built-in via vendor |
>_WHAT'S BUILT
| Area | Item | Owner crate | Status |
|---|---|---|---|
| Foundation | Workspace + 7 library crates + 46 migrations + 91 tables + 130 indexes | workspace | Done |
| Foundation | FIFO trade roll-up + stats (expectancy / R-multiple / SQN / Sharpe / Sortino) | traderview-core | Done |
| Foundation | Tauri v2 desktop + embedded Postgres bootstrap (stale-PID cleanup, persisted password) | src-tauri + traderview-db | Done |
| Foundation | axum web binary with argon2 + JWT auth, custom logging middleware | traderview-web | Done |
| Import | 12 broker parsers + Generic — Webull, Lightspeed, IBKR, TOS, TD Ameritrade, Schwab, Fidelity, ETrade, Robinhood, TradeStation, DAS Trader, TradeZero | traderview-import | Done |
| Import | CSV column-mapping wizard for unknown sources | traderview-web | Done |
| Live | Nasdaq halts (3s RSS) + SEC EDGAR + 4 PR wires (catalyst radar) | traderview-db | Done |
| Live | Finnhub WebSocket 6-panel intraday scanner with TTS voice alerts | traderview-db + frontend | Done |
| Live | Webull read-only broker (paste session tokens; in-memory only) | traderview-db | Done |
| Live | World markets snapshot (16 global indices + commodities + FX, 60s cache) | traderview-db | Done |
| Journal | Per-trade + daily + general notes, templates, trade reviews, AI post-mortem with cache | traderview-web | Done |
| Analytics | 17 reports + R-distribution + Monte Carlo forecast + fill-quality + tax-lot tracker | traderview-core | Done |
| Strategy | stryke-JIT backtest engine + walk-forward sweeper + custom-indicator AST | traderview-core | Done |
| Strategy | Strategy alerts (AND/OR/NOT compound rules) + price alerts + webhooks (Discord / Slack / generic) | traderview-web | Done |
| Research | Options chain + Greeks + IV surface + earnings-IV crush + short interest + dark pool | traderview-db | Done |
| Community | Public trade shares + forum + mentorship + symbol boards | traderview-web | Done |
| Expenses | Multi-account expense tracker + receipt OCR (tesseract) + Schedule-C report | traderview-expense + -ocr | Done |
| UX | 648-tile launcher (Cmd-K) + 11-shortcut topbar + in-app tutorial (?) | frontend | Done |
| Hardening | Per-view race tokens + browser-side error sink + WebKit SecurityError guards + bounded in-memory caches (halts cap 2k, catalysts cap 10k) | frontend + traderview-db | Done |
| Hardening | Real concurrency on multi-symbol fan-outs (premarket / markets / quotes / compare) via futures_util::join_all | traderview-db | Done |
| Roadmap | Lightspeed live broker (currently CSV-only) | traderview-db | Planned |
| Roadmap | Cloud sync — encrypted snapshot to S3 / R2 | new crate | Future |
| Roadmap | Homebrew tap formula (alongside stryke, zshrs) | release | Future |
Engineering report generated for traderview v0.2.0 ·
github.com/MenkeTechnologies/traderview ·
MIT · part of the
MenkeTechnologiesMeta stack