Reporting#

Reporting is the read side of the system: everything the dashboard, blotter and history views show. It lives in one place, services/reporting.py (ReportingService), separate from the services that write (booking, loading, P&L, risk).

Principles#

  • Read persisted results, never recompute. P&L and risk screens read the EOD tables written by the batch (see P&L and the EOD batch and Risk calculations). A report shows what was calculated on that date, independent of later trades or price changes.

  • Compose across databases at the application layer. There are no cross-database joins; the service reads each database in its own session and combines the results in Python.

  • One entry point. The web layer calls ReportingService only for reads, so reads can later be pointed at a read replica without touching the write path.

  • Reports are views, not tables. The blotter in particular has no table of its own; it is a query over trades.

Reports#

Method

Content

Source

Ordering and size

dashboard()

Latest P&L (5 rows), latest risk metrics (5), recent breaches (10)

pnl_eod, risk_metrics_eod, limit_breaches

Newest first

blotter(limit)

Trades joined with instrument description and asset_class

trades (trade DB) and instruments (reference DB)

trade_date desc, trade_id desc; 200 by default

prices(limit)

Daily bars including source and data_origin

prices_eod

px_date desc across all symbols; 200 by default

pnl_history(limit)

EOD P&L rows

pnl_eod

calc_date desc, id desc; 200 by default

risk_overview(limit)

All limits, recent metrics, recent breaches

position_limits, risk_metrics_eod, limit_breaches

Metrics and breaches newest first; 200 by default

The blotter join#

blotter loads recent trades from the trade database, loads all instruments from the reference database, and pairs them by symbol into BlotterRow(trade, instrument). A trade whose instrument is missing still appears, with empty instrument fields. This is the application-layer equivalent of the foreign key that cannot cross databases (see Database).

Return types and serialization#

The service returns detached ORM objects (sessions use expire_on_commit=False, so loaded attributes remain readable after the session closes) and small dataclasses (Dashboard, RiskOverview, BlotterRow). Turning them into JSON is the web layer’s job (web/serializers.py; see Web layer), so reporting has no knowledge of HTTP.

Known limitations#

  • No pagination or filtering. Lists are capped (200) and ordered newest first. Date, symbol, book and asset-class filters are the natural next addition.

  • Not a single snapshot. Each report reads its databases in separate transactions, so the dashboard can show P&L from one EOD run next to risk from another if a batch runs in between.

  • Simulated data is labelled on prices only. prices exposes data_origin; P&L and risk rows do not yet (see Simulated data: why and how).

  • Per-currency amounts. Reports show P&L in each instrument’s currency with no aggregation across currencies.

  • No charts or HTML. Output is JSON; see Web layer.