Skip to main content

Layers & Ports

The code is organised by feature, and inside a feature in three levels:

  1. HTTP adapters (routers/) — parse the request, call the level below, translate errors into HTTP statuses.
  2. Application logic — use cases (use_cases/) or feature services (historical/, technical/, news/, valuation/, monitoring/, macro/).
  3. Adapters to the outside — SQLAlchemy repositories (database/), Redis (cache/), providers (financials/providers/, news/providers/, historical/providers.py).
┌──────────────── routers/ (FastAPI) ────────────────┐
│ parse request → call use case → map errors │
└──────────────────────────┬─────────────────────────┘
▼
┌──────── use_cases/ — depends on ports only ────────┐
│ GetFundamentals, GetDeepFundamentals, GetQuote… │
│ use_cases/ports.py: repository, cache, providers │
└──────────────────────────┬─────────────────────────┘
▼ implemented by
┌──── database/, cache/, financials/providers/ ──────┐
│ SQLAlchemy, Redis, HTTP │
└────────────────────────────────────────────────────┘

How far each feature follows it​

FeatureRouterApplication logicBehind ports?
Fundamentalsrouters/fundamentals.pyuse_cases/fundamentals.pyYes (use_cases/ports.py)
Specialised providersrouters/specialized.pyuse_cases/specialized.pyYes
Realtimerouters/realtime.pyuse_cases/realtime.pyPartly — the WebSocket protocol is in the router
Technical indicatorsrouters/technical.pytechnical/indicator_service.pyYes (technical/contracts.py)
Monitoringrouters/monitoring.pymonitoring/Partly — the canary and the validation layer use monitoring/ports.py; the read queries of the routes are written in the router
History and EODrouters/historical.py, routers/assets.pyhistorical/ingestion_service.py, database/query.pyNo
Valuationrouters/valuation.pyvaluation/dcf_service.pyNo
Newsrouters/news.pynews/news_service.pyNo
Macro, operationsrouters/macro.py, routers/admin.pymacro/, database/maintenance.py, cache/No

The use-case layer is the target model; the other features call their services directly.

Rules held by tests​

  • technical/ imports neither FastAPI, SQLAlchemy, Redis nor the ORM models; monitoring/ neither SQLAlchemy nor the ORM models (tests/test_exception_boundaries.py).
  • Blocking code (SQLAlchemy sessions, pandas, yfinance) is reached through concurrency.run_sync() from the asynchronous code (tests/test_async_boundary.py) — see Concurrency.
  • Routers translate application errors with routers/errors.py.

Error mapping​

Application error (use_cases/errors.py)HTTP status
InvalidInput400 Bad Request
ResourceNotFound404 Not Found
DependencyUnavailable503 Service Unavailable
UpstreamFailure500 Internal Server Error

The technical indicators have their own errors: unknown indicator 400, no prices 404, too few bars 422.