RiskEngine is a production-style backend service that simulates a small market risk platform. It supports portfolio management, market data ingestion, VaR/Expected Shortfall calculations, scenario analysis, calculation history, audit logs, and scheduled background data updates.
- .NET 8
- ASP.NET Core Web API
- Entity Framework Core + PostgreSQL
- FluentValidation
- xUnit + integration tests with
WebApplicationFactory - Swagger/OpenAPI
- Serilog structured logging
- Docker + docker-compose
- Portfolio CRUD with position management
- Market data ingestion (JSON series + CSV import)
- Risk metrics:
- Historical simulation Value at Risk (VaR)
- Expected Shortfall (ES)
- Portfolio annualized volatility
- Deterministic stress loss
- Marginal contribution to portfolio risk per position
- Scenario management + scenario run API
- Calculation history + idempotent risk request handling
- Audit log persistence for calculation requests/failures
- Background hosted job for daily mock market data import
- API key protection (
X-Api-Key) - Health check endpoint (
/health) and Prometheus metrics endpoint (/metrics)
The solution uses a layered modular architecture:
src/RiskEngine.Domain- Core entities and enums (
Portfolio,Position,MarketDataPoint,StressScenario,RiskCalculationRequest,RiskCalculationResult,AuditLog)
- Core entities and enums (
src/RiskEngine.Application- DTOs, validators, service interfaces, business services, and risk math logic
- Keeps risk modelling separate from HTTP concerns
src/RiskEngine.Infrastructure- EF Core
DbContext, repository implementations, seed logic, background hosted services
- EF Core
src/RiskEngine.Api- Controllers, middleware, API key security, startup composition, Swagger
tests/RiskEngine.UnitTests- Unit tests for risk math logic
tests/RiskEngine.IntegrationTests- End-to-end API integration tests
- VaR / ES use historical simulation over aligned historical return series.
- Confidence levels are configurable in
appsettings.json(95%and99%by default). - Expected Shortfall is computed as the average loss in the tail beyond VaR.
- Volatility is sample standard deviation annualized with
sqrt(252). - Stress scenarios are deterministic shocks by asset class plus optional per-asset overrides.
At startup, the API seeds:
- Demo portfolio:
AAPL,MSFT,BUND10Y,EURUSD - Multiple predefined stress scenarios
- 300 business-day mock return series
- Start PostgreSQL (or use docker-compose below).
- Update connection string and API key if needed:
src/RiskEngine.Api/appsettings.json
- Run API:
dotnet run --project src/RiskEngine.ApiDefault local API key:
riskengine-local-key
docker compose up --buildServices:
- API:
http://localhost:8080 - PostgreSQL:
localhost:5432
Swagger UI:
http://localhost:8080/swagger
curl -X POST http://localhost:8080/api/portfolios \
-H "Content-Type: application/json" \
-H "X-Api-Key: riskengine-local-key" \
-d '{
"name": "Graduate Risk Portfolio",
"baseCurrency": "USD",
"positions": [
{ "assetSymbol": "AAPL", "quantity": 50, "marketValue": 95000, "assetClass": 1, "currency": "USD" },
{ "assetSymbol": "MSFT", "quantity": 40, "marketValue": 80000, "assetClass": 1, "currency": "USD" }
]
}'curl -X POST http://localhost:8080/api/portfolios/{portfolioId}/positions \
-H "Content-Type: application/json" \
-H "X-Api-Key: riskengine-local-key" \
-d '{
"assetSymbol": "EURUSD",
"quantity": 100000,
"marketValue": 110000,
"assetClass": 3,
"currency": "USD"
}'curl -X POST http://localhost:8080/api/market-data \
-H "Content-Type: application/json" \
-H "X-Api-Key: riskengine-local-key" \
-d '{
"assetSymbol": "AAPL",
"pointType": 1,
"values": [
{ "observationDate": "2025-10-01", "value": 0.0123 },
{ "observationDate": "2025-10-02", "value": -0.0081 }
]
}'curl -X POST http://localhost:8080/api/market-data/import/csv \
-H "Content-Type: application/json" \
-H "X-Api-Key: riskengine-local-key" \
-d "{\"pointType\":1,\"csvContent\":\"$(cat samples/market-data-returns.csv | sed ':a;N;$!ba;s/\n/\\n/g')\"}"curl -X POST http://localhost:8080/api/risk/calculate \
-H "Content-Type: application/json" \
-H "X-Api-Key: riskengine-local-key" \
-H "Idempotency-Key: calc-portfolio-1-2026-04-12" \
-d '{
"portfolioId": "{portfolioId}",
"confidenceLevel": 0.95
}'curl -X GET http://localhost:8080/api/risk/results/{resultId} \
-H "X-Api-Key: riskengine-local-key"curl -X POST http://localhost:8080/api/scenarios/run \
-H "Content-Type: application/json" \
-H "X-Api-Key: riskengine-local-key" \
-d '{
"portfolioId": "{portfolioId}"
}'curl -X GET "http://localhost:8080/api/audit-logs?limit=100" \
-H "X-Api-Key: riskengine-local-key"POST /api/portfoliosGET /api/portfolios/{id}POST /api/portfolios/{id}/positionsPOST /api/market-dataPOST /api/market-data/import/csvPOST /api/risk/calculateGET /api/risk/results/{id}GET /api/risk/historyPOST /api/scenariosPOST /api/scenarios/runGET /api/audit-logsGET /healthGET /metrics
Key sections in src/RiskEngine.Api/appsettings.json:
ConnectionStrings:PostgresSecurity:ApiKeyRiskCalculationMinimumObservationCountAllowedConfidenceLevels
BackgroundJobs- mock market data import interval/symbols
dotnet test RiskEngine.slnCurrent coverage includes:
- Risk calculation math unit tests
- Integration tests for portfolio/risk endpoints
- Edge cases: invalid inputs, empty portfolios, unknown assets
Risk systems are core infrastructure in modern financial markets, especially for brokers, CCP/clearing workflows, and post-trade controls where capital, margin, and stress resilience must be computed reliably and repeatedly.
This project demonstrates the backend engineering concerns behind that reality: deterministic calculations, strict validation, traceability through audit logs, idempotent request handling, resilience through health/metrics, and reproducible containerized deployment. Those are the same reliability and correctness themes that matter in real financial infrastructure, where failures are operationally expensive and risk visibility must be timely and explainable.