- 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
10 KiB
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
/healthand/ws/updates
2. API layer
File: app/api/routes.py
Responsibilities:
- expose JSON endpoints under
/api - resolve the shared
DataServicefrom application state - provide read-only portfolio, options, and strategy data
Current endpoints:
GET /api/portfolioGET /api/optionsGET /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.pyapp/pages/options.pyapp/pages/hedge.pyapp/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:
yfinancewhen 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.pyapp/strategies/base.pyapp/strategies/protective_put.pyapp/strategies/laddered_put.pyapp/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.pyapp/models/option.pyapp/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
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
- Client sends an HTTP request to an API endpoint or loads a NiceGUI page
- FastAPI resolves shared app services from
app.state DataServicechecks Redis cache first when enabled- If cache misses,
DataServicefetches or builds the payload:- quote via
yfinanceor fallback - synthetic options chain
- strategy comparison via
StrategySelectionEngine
- quote via
- Response is returned as JSON or used by the UI
- Background task periodically broadcasts portfolio snapshots over WebSocket
Runtime lifecycle
Startup
When the app starts:
- environment variables are loaded into
Settings CacheServiceis created and attempts Redis connectionDataServiceis initializedConnectionManageris initialized- background task
publish_updates()starts - 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:
namecalculate_cost()calculate_protection()get_scenarios()
All strategies receive a shared StrategyConfig containing:
portfoliospot_pricevolatilityrisk_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:
protective_put_atmprotective_put_otm_95protective_put_otm_90laddered_put_50_50_atm_otm95laddered_put_33_33_33_atm_otm95_otm90lease_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 costcost_sensitive: prioritize lower annual cost, then lower hedged LTVbalanced: 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
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_updatemessages 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
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