Initial commit: Vault Dashboard for options hedging
- 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
This commit is contained in:
437
docs/ARCHITECTURE.md
Normal file
437
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,437 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user