Target architecture sketch

Status: proposal for implementation, 2026-11-13. Today the repository contains an interactive mockup and documentation, with no backend, database schema, Compose deployment or Python application scaffold. This sketch translates the product vision into an implementable design; it does not claim these components exist. GUIDELINES and the crypto policy remain mandatory.

1. Shape and technology choices

Build a modular Python application with a shared domain core and separately runnable API and engine. Use a small uv workspace and build a separate image for each application component. Start with three Compose services: api, engine, and one PostgreSQL database. Add a separate research process/image when running long backtests alongside live trading; CPU-heavy work must not run on the order-processing event loop.

Choice Purpose and boundary
HTML, custom CSS, plain JavaScript Buildless frontend; reusable controls, hub navigation, charts and API requests. Serve it from the API image under the same origin. The mockup remains a separate demo artifact.
FastAPI at the HTTP edge Proposed routing, request validation and resource lifecycle. Pydantic handles DTOs/settings; domain services receive typed immutable values and ports.
Python engine Market ingestion, forward paper runs, live execution and reconciliation, independent of an open browser. One active execution engine per exchange account.
PostgreSQL One transactional source for configurations, durable commands, accounting, orders and evidence. Concurrent API/engine access and atomic capital reservations justify a database service.
Optional research worker Bounded backtest jobs over stored data. No trading credentials, order-submission capability or authority to change real balances.

Use database-backed work records initially; a broker, cache cluster, frontend build chain and separate web server are not needed for the proposed workload. Pin Python, dependencies and image versions when introducing their executable configuration. No backend dependencies are installed by this document.

[Diagram]

Only the API's private ingress is reachable from the LAN. Do not publish database, engine or research ports, or add public ingress. External exchange connectivity is outbound. LAN access still requires authentication, TLS and authorization.

2. Modules, ownership and dependency direction

Proposed layout; create packages as their implementation becomes necessary:

packages/
  trader-core/src/trader_core/
    models.py                 # IDs, assets, quantities, immutable commands/results
    ports.py                  # capability ABCs, contracts and safe exceptions
    strategies/               # pure algorithms and typed parameter definitions
    catalog.py                # configuration/version rules
    runs.py                   # lifecycle and execution specifications
    execution.py              # policy/risk decisions and order planning
    accounting.py             # allocations, reservations and fill application rules
    analysis.py               # versioned performance calculations
  trader-adapters/src/trader_adapters/
    postgres/                 # transactional persistence and read projections
    coinbase/                 # account, market and order adapters
    simulation.py             # explicit fill simulator, never a live gateway
  trader-api/src/trader_api/
    main.py                   # create_app(settings), dependency wiring, lifespan
    routes/  schemas.py  settings.py  static/
  trader-engine/src/trader_engine/
    __main__.py  worker.py  settings.py
  trader-research/src/trader_research/   # add when backtest isolation is needed
tests/
  contracts/  fakes/  core/  api/  engine/  e2e/
deployment/                   # private Compose configuration and release wiring

The API and workers compose capabilities; domain services import domain models and ports, never SDKs, adapters, FastAPI or Pydantic. Keep algorithms pure: market observations + parameter/state snapshot → decision + next immutable strategy state. No network access, credentials or order submission inside a strategy. Persist state needed for restart; a process-local object is not the authoritative portfolio.

[Diagram]

These names identify responsibilities, not a requirement for an interface per table. Define a port where a capability boundary is useful. API wiring has no OrderGateway; research wiring has no live gateway or trading key. Engine paper-run wiring receives a simulator and virtual accounting scope. All external adapter implementations and meaningful fakes pass the same capability contract tests, including failures, timeouts and cleanup.

Capability Contract that matters
Catalog Save a new immutable configuration version; reject stale edits using an expected revision; resolve an exact algorithm/version.
CommandStore Durable acceptance and explicit claim/completion; at-least-once processing, bounded retry/recovery, stable idempotency key and rejection of key reuse with a different payload.
TradingLedger Complete atomic mutations: fund a run, reserve an intent, apply a unique fill, release a confirmed reservation. Never expose a series of independently committing balance updates.
OrderGateway / AccountReader Role-specific submit/cancel/query and snapshot/history capabilities; distinguish rejection, unavailability and an unknown outcome.
MarketFeed / MarketHistory Timestamped observations with gap/freshness semantics; no fabricated continuity after reconnect.
FillSimulator / ResultStore Reproducible simulated fills and immutable observations labelled by mode, dataset, model and calculation version.

Use explicit validated constructor arguments and composition roots. Settings hold secrets as SecretStr; ports return safe domain exceptions. Resources start explicitly, close idempotently under cancellation, and have bounded waits. The engine owns its asynchronous loops with TaskGroup; an execution-critical failure closes the submission gate and exits for controlled recovery.

3. Domain identities and persistence

Record Important identity and invariant
StrategyDefinition Strategy ID, implementation version/code revision, parameter schema version, supported universe. Algorithms are registered in code; the browser edits parameters.
Configuration / ConfigurationVersion Configuration ID and name; append-only numbered parameter snapshots. Editing or duplicating never mutates an existing version.
Run Run ID, exact configuration and implementation versions, immutable mode and execution specification, mutable lifecycle. Promotion creates a new run.
ExchangeAccount / Asset / Product Exchange/account identity; asset/network identity and canonical product metadata. A display ticker is not an identity or proof of tradability.
Allocation / Reservation / LedgerEntry Live account, run, asset, quantity, reason and source event. Reservations are a subset of allocations. Paper accounting is a separate scope.
Command / OrderIntent / Order / Fill Stable command and client-order IDs, exchange order/fill IDs, deduplication keys, transitions and reconciliation evidence.
Observation / AnalysisResult Run and actual covered window, market dataset, fees/slippage model, valuation basis and calculation version.
PolicyRevision / AssetClassification Profile, sources, effective/review dates and allow/deny/review decisions; frozen evidence on each execution decision.

Use deeply immutable, typed Python values (frozen=True, slots=True, tuples, distinct ID/quantity types). Use Decimal and database exact numeric values for money, prices and quantities; send decimal strings across JSON. Validate exchange increments/minimums explicitly. Time values are timezone-aware UTC; reporting timezone is separate. The mock's JavaScript numbers are demonstration data.

Run mode (backtest, paper, live), lifecycle (starting, running, paused, blocked, stopped) and evidence availability are separate concepts. New runs have no metrics until their own observations exist. Store parameter, algorithm, instrument, policy and simulation versions needed to reproduce results. An upgrade that cannot resolve an active run's implementation version blocks that run instead of silently substituting new code.

Use foreign keys, uniqueness constraints and explicit transaction locks for cross-process invariants. Versioned SQL migrations and restore-tested backups are part of the database implementation. Corrected financial records use linked adjustments; do not rewrite historical fills or silently remove discrepancies.

4. From a hub action to a running strategy

  1. Configure: API validates a strategy-specific schema and saves a new version with optimistic concurrency. Existing run references remain fixed.
  2. Test: request a paper run or backtest with an exact version and data/model specification. A durable job creates an independent run; paper execution cannot debit live funds or invoke OrderGateway.
  3. Preview live allocation: backend returns the exact version, account, permitted asset, decimal amount, risk limits, policy revision, balance snapshot and an expiry. A preview does not reserve capital.
  4. Confirm: accept an authenticated, CSRF-protected command with an idempotency key. HTTP 202 means durably queued, not running; database failure means failure/503. UI polls command status and shows pending/blocked outcomes.
  5. Fund and start: engine rechecks current balances, classifications, permissions, data freshness, reconciliation state and effective global, strategy and run limits. Atomically allocate assets and create the run under an account/asset transaction. Concurrent requests cannot spend the same free quantity. A stale preview may be rejected; it is not an execution authority.
  6. Observe: append decisions, blocked reasons, fills and costs to the run's evidence. Performance queries compare actual matching windows and common benchmarks; missing coverage stays missing.

Proposed HTTP surface: strategy/configuration queries and version creation; run/result queries; paper/backtest requests; live-allocation previews and confirmation; pause/halt/resume/cancel commands; command status; portfolio, activity, connection and policy queries. Begin with same-origin JSON and bounded polling. The browser displays server state; it never computes authoritative free capital, chooses an order route or stores exchange credentials.

5. Execution and accounting safeguards

The engine is the sole writer of live allocations, reservations, orders and fill-derived accounting. Enforce one active executor per account operationally and through database ownership checks; do not deploy overlapping live engines. Losing database access/ownership blocks new submissions. Local locks coordinate tasks but do not replace transactional cross-process invariants.

For each live decision: evaluate → check policy/risk → reserve quantity including fee exposure and persist an order intent → submit with the persisted client ID → reconcile outcome. Commit intent/reservation before the external request; do not hold a database transaction open over a network call. Replayed commands and fills are idempotent. A fill and its ledger/reservation changes commit atomically; partial fills retain the remaining reservation.

A timeout after submission is unknown, not a rejection. Preserve the client ID, query/reconcile the existing order and fills, and block conflicting reuse of funds. Never generate a new order ID merely to retry an unknown result. Coinbase documents returning the existing order for a reused client_order_id; verify this behavior in the adapter contract and retain recovery logic rather than claiming end-to-end exactly-once execution. See Create Order.

On startup or feed interruption, close the execution gate and reconcile account balances, open orders and fills against persisted state. WebSocket observations need gap detection and REST recovery; absence from a feed is not proof of cancellation. See Coinbase's current WebSocket endpoints. Unexplained balances and external/manual orders are visible restricted holdings, not allocatable funds. A dedicated account reduces attribution ambiguity. Persist and reconcile observed exchange events even when they fall outside the permitted trading universe, including unexpected conversions and fee assets. Mark their treatment for review. Preserve undecodable events durably for investigation and block affected execution until their accounting is resolved.

reconciled holdings = free treasury + restricted unallocated holdings
                    + sum(run available assets + run order reservations)

With no restrictions, this reduces to the mock's free + allocated view. Value each asset once at one stated snapshot; exchange and run breakdowns are views of the same holdings. Never add reservations or paper balances to total wealth.

Halt requests persist a close-only safety flag with their command; the engine checks it before dispatch. UI distinguishes requested halt, engine-acknowledged halt and orders needing reconciliation. A request can race an in-flight order. Pause/halt never implies cancellation, liquidation, released capital or a fixed loss guarantee. Cancellation is a separate confirmed transition. Resume requires current reconciliation/policy/data checks and preserves individually paused runs. Only the engine can reopen the gate; the API cannot authorize an execution bypass. No best-effort/drop-oldest queues for orders, fills or accounting.

The crypto policy applies at execution, including every route leg and fee asset. Unknown/stale classification blocks live execution. PLN valuation, trading P&L and tax records are distinct. A tax export preserves evidence for review; it does not establish a universal tax exemption.

6. Analysis and the UI mapping

Mockup area Backend responsibility
Performance Read projections over run observations; consistent period, unit, benchmark, fees and formula version; show sample coverage and freshness.
Strategy hub / Overview Versioned strategy catalogue and supported parameter schema.
Configurations Append-only configuration versions and edit-conflict handling.
Runs & results Lifecycle, frozen execution specification, samples, state and capital attribution.
Deployment Preview, explicit confirmation, durable command, authoritative funding and policy/risk gates.
Portfolio Reconciled per-asset ledger, real allocations, reservations and restricted balances.
Activity / Rules & connections Business audit events, command outcomes, sanitized connection health and policy review state.

Use the same pure algorithm for replay and forward evaluation. Backtests use fixed datasets and explicit fee/slippage/latency assumptions; paper runs use observed markets and simulated fills; live runs use actual fills. Version metric definitions, sampling and annualization; account for deposits/reallocations before presenting returns. Keep heavy calculations out of execution loops. Read projections may be rebuilt; the critical ledger may not be dropped.

7. Deployment, security and delivery order

Use the self-hosted GitHub runner and Infisical for CI/CD secrets. Build component images tagged with the same reviewed source revision. API database permissions cover configuration, command acceptance and read models; engine permissions cover trading mutations. Research permissions cover market history, research jobs and simulation results. Supply trading keys only to engine wiring, with withdrawals disabled. Secrets never enter frontend assets, images or logs.

Authenticate the single owner with a server-side session, secure/HttpOnly cookies, CSRF protection and same-origin requests over private HTTPS. Configure private TLS ingress explicitly; choose its implementation with the home-network setup. Close the execution gate before deployment, drain/reconcile in-flight work, back up and migrate the database once, then restart with startup reconciliation. A code rollback does not undo exchange transactions or database migrations.

  1. Read-only foundation: workspace, private API/UI, database/migrations, authentication, Coinbase account/product readers, ledger reconciliation and lifecycle/contract tests. No execution keys or live submission enabled.
  2. Research: versioned configurations and strategy implementations, stored market observations, paper/backtest modes and trustworthy comparison results. Introduce the isolated research worker before long jobs coexist with live work.
  3. Controlled live execution: durable commands/intents, atomic reservations, reviewed policy/fees, idempotency, partial-fill/reconnect/restart recovery, halt/resume and user-confirmed allocations.
  4. Additional exchanges: adapters and contract suites, unified exposure and richer analysis, after the single-account accounting path is reliable.

8. Verification and extension points

Goal Where behavior belongs
Add an algorithm Pure implementation and parameter model in trader_core/strategies; register its version at the composition root.
Add an exchange Role-specific adapters behind existing account/market/order ports; run the same contract suites.
Change risk/legal rules Core decision services and versioned policy data; enforce in engine, expose reason codes to UI.
Add a metric Versioned analysis calculation, read projection and metric definition; never rewrite raw evidence.
Add a UI feature Buildless static UI + typed HTTP DTO/use case; keep money decisions in backend services.

Before live execution, require observable tests for concurrent allocation, duplicate commands/fills, unknown submission outcomes, partial fills, stale metadata, feed gaps, cancellation races, ownership loss and crash/restart recovery. Test paper/live isolation, real Postgres transactions and no secret leakage. A new external adapter needs the port's shared contract suite; mocks of its internal SDK do not establish that contract. Keep at least one real-adapter end-to-end path with exchange HTTP/WebSocket behavior supplied by a local test server; tests must never place real exchange orders. Verify SIGTERM/cancellation cleanup and database backup restoration.

When the scaffold exists, run frozen uv installation, the configured pre-commit checks, then pytest/contract/integration tests, plus Compose config validation. These are proposed gates, not checks currently configured or passed. Application logs use safe constant messages and structured error types; audit records are separate from logs. Document low-cardinality metrics for execution health, feed freshness, pending commands, reconciliation and blocked orders.

Technical references checked for this sketch: FastAPI static files and lifespan, and PostgreSQL transaction locking and constraints. These support implementation mechanisms; the component split and ownership rules above are project design decisions.