- FastAPI + NiceGUI web application - QuantLib-based Black-Scholes pricing with Greeks - Protective put, laddered, and LEAPS strategies - Real-time WebSocket updates - TradingView-style charts via Lightweight-Charts - Docker containerization - GitLab CI/CD pipeline for VPS deployment - VPN-only access configuration
438 lines
10 KiB
Markdown
438 lines
10 KiB
Markdown
# Architecture
|
|
|
|
## Overview
|
|
|
|
Vault Dashboard is a FastAPI application with NiceGUI pages for the frontend, a lightweight API layer for market and strategy data, a strategy engine for paper hedge comparisons, and an optional Redis cache.
|
|
|
|
At runtime the app exposes:
|
|
|
|
- HTML/UI pages via NiceGUI
|
|
- REST-style JSON endpoints under `/api`
|
|
- a health endpoint at `/health`
|
|
- a WebSocket feed at `/ws/updates`
|
|
|
|
The system is currently optimized for research, visualization, and paper analysis of Lombard-loan hedging strategies rather than live trade execution.
|
|
|
|
---
|
|
|
|
## System components
|
|
|
|
### 1. Application entry point
|
|
|
|
**File:** `app/main.py`
|
|
|
|
Responsibilities:
|
|
|
|
- create the FastAPI app
|
|
- load environment-driven settings
|
|
- configure CORS
|
|
- initialize cache and data services during lifespan startup
|
|
- start a background publisher task for WebSocket updates
|
|
- mount NiceGUI onto the app
|
|
- expose `/health` and `/ws/updates`
|
|
|
|
### 2. API layer
|
|
|
|
**File:** `app/api/routes.py`
|
|
|
|
Responsibilities:
|
|
|
|
- expose JSON endpoints under `/api`
|
|
- resolve the shared `DataService` from application state
|
|
- provide read-only portfolio, options, and strategy data
|
|
|
|
Current endpoints:
|
|
|
|
- `GET /api/portfolio`
|
|
- `GET /api/options`
|
|
- `GET /api/strategies`
|
|
|
|
### 3. UI layer
|
|
|
|
**Files:** `app/pages/*.py`, `app/components/*.py`
|
|
|
|
Responsibilities:
|
|
|
|
- render dashboard pages using NiceGUI
|
|
- present charts, tables, strategy views, and scenario widgets
|
|
- consume data generated within the app and, in production, align with API/WebSocket-backed state
|
|
|
|
Representative pages:
|
|
|
|
- `app/pages/overview.py`
|
|
- `app/pages/options.py`
|
|
- `app/pages/hedge.py`
|
|
- `app/pages/settings.py`
|
|
|
|
### 4. Data service
|
|
|
|
**File:** `app/services/data_service.py`
|
|
|
|
Responsibilities:
|
|
|
|
- fetch quote data
|
|
- build a synthetic options chain response
|
|
- build portfolio snapshots
|
|
- invoke the strategy engine and shape strategy comparison responses
|
|
- cache results when Redis is available
|
|
|
|
Data source behavior:
|
|
|
|
- primary live quote source: `yfinance` when installed and reachable
|
|
- fallback quote source: static fallback data if live fetch fails
|
|
|
|
### 5. Cache service
|
|
|
|
**File:** `app/services/cache.py`
|
|
|
|
Responsibilities:
|
|
|
|
- provide async JSON get/set operations
|
|
- wrap Redis without making the rest of the app depend directly on Redis primitives
|
|
- degrade gracefully when Redis is unavailable
|
|
|
|
The app remains functional without Redis; caching is optional.
|
|
|
|
### 6. Strategy engine
|
|
|
|
**Files:**
|
|
|
|
- `app/strategies/engine.py`
|
|
- `app/strategies/base.py`
|
|
- `app/strategies/protective_put.py`
|
|
- `app/strategies/laddered_put.py`
|
|
- `app/strategies/lease.py`
|
|
|
|
Responsibilities:
|
|
|
|
- construct standardized paper strategies for comparison
|
|
- calculate cost and protection metrics
|
|
- run scenario analysis across price shocks
|
|
- recommend a strategy by risk profile
|
|
- run simple sensitivity analysis
|
|
|
|
### 7. Domain models
|
|
|
|
**Files:**
|
|
|
|
- `app/models/portfolio.py`
|
|
- `app/models/option.py`
|
|
- `app/models/strategy.py`
|
|
|
|
Responsibilities:
|
|
|
|
- represent Lombard-backed portfolios
|
|
- represent option contracts and Greeks
|
|
- represent multi-leg hedging structures
|
|
- enforce validation rules at object boundaries
|
|
|
|
### 8. Pricing layer
|
|
|
|
**Files:** `app/core/pricing/*.py`
|
|
|
|
Responsibilities:
|
|
|
|
- compute option prices and Greeks
|
|
- support Black-Scholes-based valuation inputs used by the research strategies
|
|
|
|
---
|
|
|
|
## High-level data flow
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
A[Browser / NiceGUI Client] -->|HTTP| B[FastAPI + NiceGUI app]
|
|
A -->|WebSocket /ws/updates| B
|
|
B --> C[API routes]
|
|
B --> D[ConnectionManager + background publisher]
|
|
C --> E[DataService]
|
|
D --> E
|
|
E --> F[CacheService]
|
|
F -->|optional| G[(Redis)]
|
|
E --> H[yfinance]
|
|
E --> I[StrategySelectionEngine]
|
|
I --> J[ProtectivePutStrategy]
|
|
I --> K[LadderedPutStrategy]
|
|
I --> L[LeaseStrategy]
|
|
J --> M[Pricing + models]
|
|
K --> M
|
|
L --> M
|
|
```
|
|
|
|
### Request/response flow
|
|
|
|
1. Client sends an HTTP request to an API endpoint or loads a NiceGUI page
|
|
2. FastAPI resolves shared app services from `app.state`
|
|
3. `DataService` checks Redis cache first when enabled
|
|
4. If cache misses, `DataService` fetches or builds the payload:
|
|
- quote via `yfinance` or fallback
|
|
- synthetic options chain
|
|
- strategy comparison via `StrategySelectionEngine`
|
|
5. Response is returned as JSON or used by the UI
|
|
6. Background task periodically broadcasts portfolio snapshots over WebSocket
|
|
|
|
---
|
|
|
|
## Runtime lifecycle
|
|
|
|
### Startup
|
|
|
|
When the app starts:
|
|
|
|
1. environment variables are loaded into `Settings`
|
|
2. `CacheService` is created and attempts Redis connection
|
|
3. `DataService` is initialized
|
|
4. `ConnectionManager` is initialized
|
|
5. background task `publish_updates()` starts
|
|
6. NiceGUI is mounted on the FastAPI app
|
|
|
|
### Steady state
|
|
|
|
During normal operation:
|
|
|
|
- API requests are served on demand
|
|
- WebSocket clients stay connected to `/ws/updates`
|
|
- every `WEBSOCKET_INTERVAL_SECONDS`, the app publishes a fresh portfolio payload
|
|
- Redis caches repeated quote/portfolio/options requests when configured
|
|
|
|
### Shutdown
|
|
|
|
On shutdown:
|
|
|
|
- publisher task is cancelled
|
|
- cache connection is closed
|
|
- FastAPI lifecycle exits cleanly
|
|
|
|
---
|
|
|
|
## Strategy engine design
|
|
|
|
## Core design goals
|
|
|
|
The strategy subsystem is built to compare paper hedging approaches for a Lombard loan secured by gold exposure. It emphasizes:
|
|
|
|
- deterministic calculations
|
|
- shared configuration across strategies
|
|
- comparable output shapes
|
|
- easy extension for new strategies
|
|
|
|
### Base contract
|
|
|
|
**File:** `app/strategies/base.py`
|
|
|
|
All strategies implement:
|
|
|
|
- `name`
|
|
- `calculate_cost()`
|
|
- `calculate_protection()`
|
|
- `get_scenarios()`
|
|
|
|
All strategies receive a shared `StrategyConfig` containing:
|
|
|
|
- `portfolio`
|
|
- `spot_price`
|
|
- `volatility`
|
|
- `risk_free_rate`
|
|
|
|
### Portfolio construction
|
|
|
|
**File:** `app/strategies/engine.py`
|
|
|
|
`StrategySelectionEngine` builds a canonical research portfolio using:
|
|
|
|
- portfolio value
|
|
- loan amount
|
|
- margin call threshold
|
|
- spot price
|
|
- volatility
|
|
- risk-free rate
|
|
|
|
The engine converts these into a validated `LombardPortfolio`, then instantiates a suite of candidate strategies.
|
|
|
|
### Candidate strategies
|
|
|
|
Current strategy set:
|
|
|
|
1. `protective_put_atm`
|
|
2. `protective_put_otm_95`
|
|
3. `protective_put_otm_90`
|
|
4. `laddered_put_50_50_atm_otm95`
|
|
5. `laddered_put_33_33_33_atm_otm95_otm90`
|
|
6. `lease_duration_analysis`
|
|
|
|
### Strategy outputs
|
|
|
|
Each strategy returns three complementary views:
|
|
|
|
#### Cost view
|
|
|
|
Examples:
|
|
|
|
- premium per share
|
|
- total hedge cost
|
|
- annualized cost
|
|
- cost as percentage of portfolio
|
|
- weighted leg costs for ladders
|
|
|
|
#### Protection view
|
|
|
|
Examples:
|
|
|
|
- threshold price where margin stress occurs
|
|
- payoff at threshold
|
|
- hedged LTV at threshold
|
|
- whether the strategy maintains a buffer below margin-call LTV
|
|
- floor value implied by option strikes
|
|
|
|
#### Scenario view
|
|
|
|
Examples:
|
|
|
|
- underlying price change percentage
|
|
- simulated spot price
|
|
- unhedged vs hedged LTV
|
|
- option payoff
|
|
- hedge cost
|
|
- net portfolio value
|
|
- margin call triggered or avoided
|
|
|
|
### Recommendation model
|
|
|
|
`StrategySelectionEngine.recommend()` scores strategies using a small heuristic.
|
|
|
|
Risk profiles:
|
|
|
|
- `conservative`: prioritize lower hedged LTV, then lower annual cost
|
|
- `cost_sensitive`: prioritize lower annual cost, then lower hedged LTV
|
|
- `balanced`: combine hedged LTV and normalized annual cost
|
|
|
|
This is a ranking heuristic, not an optimizer or live execution model.
|
|
|
|
### Sensitivity analysis
|
|
|
|
The engine also reruns recommendations across:
|
|
|
|
- multiple volatility assumptions
|
|
- multiple spot-price assumptions
|
|
|
|
This helps identify whether a recommendation is robust to input changes.
|
|
|
|
---
|
|
|
|
## Data flow diagram for strategy computation
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Client
|
|
participant API as /api/strategies
|
|
participant DS as DataService
|
|
participant SE as StrategySelectionEngine
|
|
participant S as Strategy implementations
|
|
|
|
Client->>API: GET /api/strategies?symbol=GLD
|
|
API->>DS: get_strategies(symbol)
|
|
DS->>DS: get_quote(symbol)
|
|
DS->>SE: create engine with spot and research parameters
|
|
SE->>S: compare_all_strategies()
|
|
S-->>SE: cost/protection/scenario payloads
|
|
SE->>SE: recommend() by risk profile
|
|
SE->>SE: sensitivity_analysis()
|
|
SE-->>DS: comparison + recommendations + sensitivity
|
|
DS-->>API: JSON response
|
|
API-->>Client: strategies payload
|
|
```
|
|
|
|
---
|
|
|
|
## API endpoints
|
|
|
|
### Health
|
|
|
|
- `GET /health`
|
|
|
|
Purpose:
|
|
|
|
- liveness/readiness-style check for deploy validation
|
|
|
|
Returns:
|
|
|
|
- application status
|
|
- current environment
|
|
- whether Redis is enabled
|
|
|
|
### Portfolio API
|
|
|
|
- `GET /api/portfolio?symbol=GLD`
|
|
|
|
Purpose:
|
|
|
|
- return a current portfolio snapshot derived from the latest quote
|
|
|
|
### Options API
|
|
|
|
- `GET /api/options?symbol=GLD`
|
|
|
|
Purpose:
|
|
|
|
- return a simplified options chain snapshot for the selected symbol
|
|
|
|
### Strategies API
|
|
|
|
- `GET /api/strategies?symbol=GLD`
|
|
|
|
Purpose:
|
|
|
|
- return strategy comparisons, recommendations, and sensitivity analysis
|
|
|
|
### WebSocket updates
|
|
|
|
- `WS /ws/updates`
|
|
|
|
Purpose:
|
|
|
|
- push periodic `portfolio_update` messages to connected clients
|
|
|
|
---
|
|
|
|
## Deployment architecture
|
|
|
|
Production deployment currently assumes:
|
|
|
|
- containerized app on a VPS
|
|
- image stored in GitLab Container Registry
|
|
- deployment initiated by GitLab CI/CD over SSH
|
|
- optional Redis, depending on runtime configuration
|
|
- VPN-restricted network access preferred
|
|
- reverse proxy/TLS termination recommended in front of the app
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
A[GitLab CI/CD] -->|build + push| B[GitLab Container Registry]
|
|
A -->|SSH deploy| C[VPS]
|
|
B -->|docker pull| C
|
|
C --> D[vault-dash container]
|
|
E[VPN / Reverse Proxy] --> D
|
|
```
|
|
|
|
---
|
|
|
|
## Architectural constraints and assumptions
|
|
|
|
- Strategy calculations are currently research-oriented, not broker-executed trades
|
|
- Quote retrieval is best-effort and may fall back to static data
|
|
- Options chain payloads are synthetic examples, not a full market data feed
|
|
- Redis is optional and the app must work without it
|
|
- WebSocket updates currently publish portfolio snapshots only
|
|
- NiceGUI and API routes run in the same Python application process
|
|
|
|
## Near-term extension points
|
|
|
|
Likely future architecture additions:
|
|
|
|
- real broker integration for positions and option chains
|
|
- persistent storage for scenarios, settings, and user sessions
|
|
- reverse proxy configuration in deployment Compose files
|
|
- authenticated API access
|
|
- OAuth provider integration over HTTPS
|
|
- richer WebSocket event types
|