VEEMANDocs
DocumentationSupportOpen Dashboard

Getting started

  • Introduction
  • Quick start
  • Core concepts

Importing data

  • Supported formats
  • How import works
  • Splitting a file
  • Troubleshooting

Building your portfolio

  • Workspace & equity
  • Weights & capital
  • Weekday filter
  • Keyframes
  • Currencies & FX
  • Library
  • Strike guard
  • Variants & comparison
  • History (undo/redo)
  • Shared spaces

Analytics

  • Metrics reference
  • Charts (deep-dive)
  • Monthly P&L
  • Correlations
  • Comparison
  • Monte Carlo
  • Equity Control
  • Optimization
  • IBKR reconciliation
  • Veeman AI

Research

  • Research overview
  • Define
  • Composing a backtest
  • How the engine trades
  • Results & archive
  • Parameter study
  • Rewind
  • Tactics
  • Indicators

Reference

  • Keyboard & command palette
  • Exporting data
  • Settings

Technical

  • Architecture
  • API reference
  • Security
  • Your data & privacy
  • Risk disclosure
← PreviousSettingsNext →API reference

Architecture

VEEMAN is a conventional three-tier web application. This page explains what runs where and how a request flows — useful if you're evaluating the system or just curious.

Components

LayerTechnologyHosted on
FrontendNext.js (App Router), React, TypeScript, Tailwind, RechartsVercel
BackendFastAPI (Python), NumPy / pandas / SciPy for the mathsRender (Docker)
Auth & databaseSupabase (Postgres + Auth)Supabase

The frontend renders the UI and charts; the backend owns the analytical engines (equity aggregation, metrics, Monte Carlo, correlations, risk sizing); Supabase handles identity and persistent storage.

Request flow

Browser ──▶ Next.js (Vercel) ──▶ FastAPI (Render) ──▶ Session manager (in-memory)
                                       │                       │
                                       ▼                       ▼
                               Auth verification          Math engines
                              (Supabase JWT)            (NumPy/pandas/SciPy)
                                       │
                                       ▼
                                Storage (files / Postgres)
  1. The browser calls the API with a bearer token.
  2. The backend verifies the token and resolves your user_id.
  3. The request is served from your in-memory session (or persisted storage).
  4. Engines compute the result and return JSON; the frontend draws it.

No broker, no orders

There is no connection to any broker or exchange, no order routing, no live market data and no fund custody. The backend only ever processes the historical P/L you upload and, in Research, a historical options dataset. See Security.

Sessions

Your working set lives in an in-memory session keyed to your user_id:

  • Time-to-live: 24 hours, swept periodically.
  • Isolated — a session can only be read by the user who owns it.
  • Held in RAM — a backend restart (a deploy, or an idle instance waking back up) empties it.

Session drafts (autosave)

Because the process does not live as long as the work, every session is continuously mirrored to a draft in storage, and rebuilt from it on demand. When a request arrives for a session id that is no longer in memory, the backend restores it from the draft under the same id — so the client's stored session id keeps working and the restart is invisible.

The draft is split in two records, because the cost is all in the P/L series:

  • a light record — the recipe (capital, mode, weights, visibility, sizing, costs, groups, weekday filters) plus provenance and the unsaved-changes marker. A few KB, rewritten on every change.
  • a heavy record — the strategies with their series embedded and the trade-level companion CSVs. Rewritten only when the data itself changes: an upload, a confirmed import, an update, a delete or a rename.

A background loop compares the live session with what was last written and flushes only the difference, roughly every two seconds and once more on shutdown — so a graceful restart loses nothing and no keystroke ever waits on a database write. Drafts expire after 7 days; beyond the 5 most recent, dormant ones are pruned — drafts of sessions still live in memory are exempt.

This is why the app distinguishes the temporary session from things you explicitly save or freeze: a draft protects work in progress, it does not file it. See Core concepts.

Storage

Persistence goes through one storage abstraction with two backends:

  • File storage — local development; JSON and CSV under a per-user folder.
  • Postgres storage — production; a single user_data table keyed by (user_id, kind, name).

The kind discriminates the data: portfolios, strategies, variants, history, drafts (the session autosave above), the Research kinds (backtests, research_models, replay_journals, tactics, research_studies, indicators), and the trade-level CSVs used by per-strategy sizing. Either backend is strictly per-user.

Research

Research is part of the same backend code, switched on by a flag, and usually runs on a dedicated server next to the main one, because it needs what the main backend doesn't have: the historical options dataset on local disk (minute-level chains in Parquet files) and the CPU to run backtests in parallel.

Browser ──▶ Next.js ──▶ Main backend      /api/analysis/*   sessions, library, AI
                   └──▶ Research server   /api/research/*   dataset, engine, studies
                              │
                              ▼
              Same Supabase tokens · same database
  • Same identity, same storage. The Research server verifies the same tokens and writes to the same database, so a backtest saved there appears at once in the Analysis library.
  • The session stays on one process. Moving a backtest into an Analysis session is a route of the main backend, so a session never exists in two copies.
  • Jobs. A backtest is submitted and polled (a few seconds on the full history; identical recipes on the same data are served from a cache). A parameter study runs in a background thread with a checkpoint in storage: it survives closing the page and, after a restart, resumes from the evaluations already done. Heavy jobs are never refused: beyond the free compute slots they wait in a queue that rotates between users.
  • Reproducibility. The dataset has a content-derived version stamped into every saved backtest; the engine is a pure function of the recipe and the data.

The frontend shows Research only in builds where it is enabled; without it, the /research routes answer 404.

Authentication (dual-mode)

The backend accepts two auth regimes so the same code runs locally and in production:

  • Supabase (production) — an Authorization: Bearer <jwt> header, verified by shared secret (HS256) or by Supabase's public keys (JWKS). Your user_id is the Supabase UUID.
  • Legacy cookie (local dev) — a backend-signed JWT cookie; the user_id is a username.

Routes are protected on both ends: the frontend guards /analysis/* and /research/*, and every backend route requires a verified identity. More in Security.

Deployment

Frontend deploys to Vercel on push; the backend is a Docker image on Render; Supabase provides auth and the database. Configuration is entirely via environment variables — no secrets in the code.