Web layer#
A Flask application exposing the system as JSON. It is a thin layer: it parses and
validates requests, calls a service, and serializes the result. Business rules live
in domain/ and services/, never in routes.
Code: src/trade_engine/web/. Launcher: scripts/run_app.py.
Structure#
Module |
Responsibility |
|---|---|
|
|
|
One blueprint per resource: |
|
Request parsing and validation at the boundary |
|
Explicit JSON shape of each persisted row |
|
Maps application errors to HTTP responses |
|
|
Dependency injection. create_app takes a ServiceContainer (the composition
root in container.py) or builds one from the environment, and stores it on
app.extensions. Routes obtain services through get_services(); nothing is
global. Tests pass a container pointing at a temporary root.
Endpoints#
Bodies may be JSON or form-encoded. A JSON body that is not an object is rejected.
Method and path |
Purpose |
Body |
Success response |
|---|---|---|---|
|
Dashboard |
none |
|
|
Recent trades with instrument description and asset class |
none |
list of trades |
|
Book a trade |
see below |
|
|
Recent price bars, including |
none |
list of bars |
|
Load a price CSV |
see below |
|
|
EOD P&L history |
none |
list of P&L rows |
|
Run EOD P&L |
optional |
|
|
Limits, risk metrics, breaches |
none |
|
|
Run EOD risk |
optional |
|
Reads come from reporting; the runs come from P&L and
risk. When calc_date is omitted it defaults to the latest price date,
as in scripts/run_eod.py.
POST /blotter fields#
Field |
Required |
Rule |
|---|---|---|
|
yes |
|
|
yes |
finite numbers greater than zero |
|
no (default |
upper-cased; must be a known, active instrument |
|
no (default today) |
|
|
no |
|
|
no |
must equal the instrument’s quote currency; defaults to it |
|
no (default |
upper-cased; must exist |
|
no (default |
must exist |
|
no |
up to 512 characters |
Status is not accepted: new trades are always ACTIVE. See
Trade database (trade.db) for the booking rules.
POST /market fields#
Field |
Required |
Rule |
|---|---|---|
|
yes |
|
|
no (default |
upper-cased |
|
no (default |
a |
|
no (default |
provenance label, up to 128 characters |
Files under data/generated can only be loaded as SIMULATED. See
Market database (market.db) and Simulated data: why and how.
The request stages the file in Redis and then processes the queue before
responding, so a 400 is still returned for an invalid file (the batch is moved to
the rejected stream, not retried) and Redis must be reachable.
Validation and errors#
Input is validated at this boundary and again by the domain and services, so no
caller can bypass the rules. Failures return JSON {"error": "<message>"}:
Status |
Raised for |
|---|---|
400 |
Invalid input: bad date or number, unknown side, missing |
404 |
A CSV that does not exist. The message is deliberately generic because the underlying one contains a server path |
409 |
Market data unavailable (for example running EOD before any prices are loaded, or a traded symbol with no price) |
422 |
Unsupported instrument, such as a non-USD instrument in the risk run |
503 |
The Redis staging area is unreachable. The message is deliberately generic |
500 |
Anything unexpected; Flask’s default handler returns no internals |
Security posture#
Implemented:
CSV loads are restricted to
data/rawanddata/generated, and..traversal is rejected because paths are resolved before the check.The launcher binds to
127.0.0.1, and debug mode is off unlessFLASK_DEBUG=1.Error responses do not leak server paths.
Not implemented (required before real use): authentication and authorization,
CSRF protection, TLS, rate limiting, and a production WSGI server instead of
Flask’s development server. State-changing endpoints (POST) are currently open
to anyone who can reach the port, and POST /market should become an admin-only
action or a batch process.
Current limitations#
JSON only: there are no HTML pages, forms or charts yet.
List endpoints are capped and not paginated (see Reporting).
POST /pnlandPOST /riskrun synchronously inside the request; there is no job queue, scheduler or run-locking (see P&L and the EOD batch).
Adding an endpoint#
Put the behaviour in a service (and pure logic in
domain/if it is a calculation); do not put it in the route.Add request parsing to
web/schemas.pyand the JSON shape toweb/serializers.py.Add a blueprint module under
web/routes/and list it inALL_BLUEPRINTSinweb/routes/__init__.py.Raise
DataValidationError(or anotherTradeEngineError) for expected failures soweb/errors.pyturns them into responses.Add a test in
tests/test_web.pyusing the injected container.