- 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
594 lines
12 KiB
Markdown
594 lines
12 KiB
Markdown
# API Documentation
|
|
|
|
## Overview
|
|
|
|
Vault Dashboard exposes a small read-only HTTP API plus a WebSocket stream.
|
|
|
|
Base capabilities:
|
|
|
|
- health monitoring
|
|
- portfolio snapshot retrieval
|
|
- options chain retrieval
|
|
- strategy analysis retrieval
|
|
- periodic real-time portfolio updates over WebSocket
|
|
|
|
Unless noted otherwise, all responses are JSON.
|
|
|
|
---
|
|
|
|
## Conventions
|
|
|
|
### Base URL
|
|
|
|
Examples:
|
|
|
|
```text
|
|
http://localhost:8000
|
|
https://vault.example.com
|
|
```
|
|
|
|
### Content type
|
|
|
|
Responses use:
|
|
|
|
```http
|
|
Content-Type: application/json
|
|
```
|
|
|
|
### Authentication
|
|
|
|
There is currently **no application-layer authentication** on these endpoints.
|
|
|
|
If the deployment requires restricted access, enforce it at the network or reverse-proxy layer.
|
|
|
|
### Errors
|
|
|
|
The app currently relies mostly on framework defaults. Typical failures may include:
|
|
|
|
- `422 Unprocessable Entity` for invalid query parameters
|
|
- `500 Internal Server Error` for unexpected runtime issues
|
|
|
|
---
|
|
|
|
## HTTP endpoints
|
|
|
|
## 1. Health
|
|
|
|
### `GET /health`
|
|
|
|
Deployment and uptime check.
|
|
|
|
#### Query parameters
|
|
|
|
None.
|
|
|
|
#### Response schema
|
|
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"environment": "production",
|
|
"redis_enabled": false
|
|
}
|
|
```
|
|
|
|
#### Field definitions
|
|
|
|
- `status` (`string`): expected value is currently `"ok"`
|
|
- `environment` (`string`): runtime environment from `APP_ENV` or `ENVIRONMENT`
|
|
- `redis_enabled` (`boolean`): `true` when Redis is configured and connected
|
|
|
|
#### Example request
|
|
|
|
```bash
|
|
curl -fsS http://localhost:8000/health
|
|
```
|
|
|
|
#### Example response
|
|
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"environment": "development",
|
|
"redis_enabled": false
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Portfolio
|
|
|
|
### `GET /api/portfolio`
|
|
|
|
Returns a portfolio snapshot derived from the current quote for a symbol.
|
|
|
|
#### Query parameters
|
|
|
|
| Name | Type | Required | Default | Description |
|
|
|---|---|---:|---|---|
|
|
| `symbol` | string | no | `GLD` | Ticker symbol to analyze |
|
|
|
|
#### Response schema
|
|
|
|
```json
|
|
{
|
|
"symbol": "GLD",
|
|
"spot_price": 215.0,
|
|
"portfolio_value": 215000.0,
|
|
"loan_amount": 600000.0,
|
|
"ltv_ratio": 2.7907,
|
|
"updated_at": "2026-03-21T12:34:56.000000+00:00",
|
|
"source": "fallback"
|
|
}
|
|
```
|
|
|
|
#### Field definitions
|
|
|
|
- `symbol` (`string`): requested ticker, uppercased
|
|
- `spot_price` (`number`): latest spot/quote price
|
|
- `portfolio_value` (`number`): current modeled collateral value, currently `spot_price * 1000`
|
|
- `loan_amount` (`number`): modeled loan balance, currently fixed at `600000.0`
|
|
- `ltv_ratio` (`number`): `loan_amount / portfolio_value`
|
|
- `updated_at` (`string`, ISO 8601): response generation timestamp
|
|
- `source` (`string`): quote source such as `yfinance` or `fallback`
|
|
|
|
#### Example request
|
|
|
|
```bash
|
|
curl "http://localhost:8000/api/portfolio?symbol=GLD"
|
|
```
|
|
|
|
#### Example response
|
|
|
|
```json
|
|
{
|
|
"symbol": "GLD",
|
|
"spot_price": 215.0,
|
|
"portfolio_value": 215000.0,
|
|
"loan_amount": 600000.0,
|
|
"ltv_ratio": 2.7907,
|
|
"updated_at": "2026-03-21T12:34:56.000000+00:00",
|
|
"source": "fallback"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Options chain
|
|
|
|
### `GET /api/options`
|
|
|
|
Returns a simplified options chain snapshot for the symbol.
|
|
|
|
#### Query parameters
|
|
|
|
| Name | Type | Required | Default | Description |
|
|
|---|---|---:|---|---|
|
|
| `symbol` | string | no | `GLD` | Ticker symbol to analyze |
|
|
|
|
#### Response schema
|
|
|
|
```json
|
|
{
|
|
"symbol": "GLD",
|
|
"updated_at": "2026-03-21T12:34:56.000000+00:00",
|
|
"calls": [
|
|
{
|
|
"strike": 225.75,
|
|
"premium": 6.45,
|
|
"expiry": "2026-06-19"
|
|
}
|
|
],
|
|
"puts": [
|
|
{
|
|
"strike": 204.25,
|
|
"premium": 6.02,
|
|
"expiry": "2026-06-19"
|
|
}
|
|
],
|
|
"source": "fallback"
|
|
}
|
|
```
|
|
|
|
#### Array item schema
|
|
|
|
Each option row in `calls` or `puts` has:
|
|
|
|
- `strike` (`number`)
|
|
- `premium` (`number`)
|
|
- `expiry` (`string`, `YYYY-MM-DD`)
|
|
|
|
#### Field definitions
|
|
|
|
- `symbol` (`string`): requested ticker, uppercased
|
|
- `updated_at` (`string`, ISO 8601): response generation timestamp
|
|
- `calls` (`array<object>`): example call rows derived from spot
|
|
- `puts` (`array<object>`): example put rows derived from spot
|
|
- `source` (`string`): upstream quote source used to derive the chain
|
|
|
|
#### Notes
|
|
|
|
The current options chain is synthetic. It is not yet a full broker-grade chain feed.
|
|
|
|
#### Example request
|
|
|
|
```bash
|
|
curl "http://localhost:8000/api/options?symbol=GLD"
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Strategies
|
|
|
|
### `GET /api/strategies`
|
|
|
|
Returns strategy comparison data, recommendations by risk profile, and sensitivity analysis.
|
|
|
|
#### Query parameters
|
|
|
|
| Name | Type | Required | Default | Description |
|
|
|---|---|---:|---|---|
|
|
| `symbol` | string | no | `GLD` | Ticker symbol to analyze |
|
|
|
|
#### Top-level response schema
|
|
|
|
```json
|
|
{
|
|
"symbol": "GLD",
|
|
"updated_at": "2026-03-21T12:34:56.000000+00:00",
|
|
"paper_parameters": {},
|
|
"strategies": [],
|
|
"recommendations": {},
|
|
"sensitivity_analysis": {}
|
|
}
|
|
```
|
|
|
|
#### Top-level field definitions
|
|
|
|
- `symbol` (`string`): requested ticker, uppercased
|
|
- `updated_at` (`string`, ISO 8601): response generation timestamp
|
|
- `paper_parameters` (`object`): engine inputs used for the analysis
|
|
- `strategies` (`array<object>`): detailed strategy comparison rows
|
|
- `recommendations` (`object`): recommendation results keyed by risk profile
|
|
- `sensitivity_analysis` (`object`): recommendation changes across parameter shifts
|
|
|
|
### 4.1 `paper_parameters` schema
|
|
|
|
```json
|
|
{
|
|
"portfolio_value": 1000000.0,
|
|
"loan_amount": 600000.0,
|
|
"margin_call_threshold": 0.75,
|
|
"spot_price": 460.0,
|
|
"volatility": 0.16,
|
|
"risk_free_rate": 0.045
|
|
}
|
|
```
|
|
|
|
Fields:
|
|
|
|
- `portfolio_value` (`number`)
|
|
- `loan_amount` (`number`)
|
|
- `margin_call_threshold` (`number`)
|
|
- `spot_price` (`number`)
|
|
- `volatility` (`number`)
|
|
- `risk_free_rate` (`number`)
|
|
|
|
### 4.2 `strategies[]` schema
|
|
|
|
Each element in `strategies` is produced by `StrategySelectionEngine.compare_all_strategies()`:
|
|
|
|
```json
|
|
{
|
|
"name": "protective_put_atm",
|
|
"cost": {},
|
|
"protection": {},
|
|
"scenarios": [],
|
|
"score_inputs": {
|
|
"annual_cost": 0.0,
|
|
"hedged_ltv_at_threshold": 0.0
|
|
}
|
|
}
|
|
```
|
|
|
|
Fields:
|
|
|
|
- `name` (`string`): internal strategy identifier
|
|
- `cost` (`object`): strategy-specific cost payload
|
|
- `protection` (`object`): strategy-specific protection payload
|
|
- `scenarios` (`array<object>`): scenario analysis rows
|
|
- `score_inputs` (`object`): normalized inputs used for recommendation scoring
|
|
|
|
#### Protective put cost schema
|
|
|
|
Typical `cost` object for `protective_put_*`:
|
|
|
|
```json
|
|
{
|
|
"strategy": "protective_put_atm",
|
|
"label": "ATM",
|
|
"strike": 460.0,
|
|
"strike_pct": 1.0,
|
|
"premium_per_share": 21.1234,
|
|
"total_cost": 45920.43,
|
|
"cost_pct_of_portfolio": 0.04592,
|
|
"term_months": 12,
|
|
"annualized_cost": 45920.43,
|
|
"annualized_cost_pct": 0.04592
|
|
}
|
|
```
|
|
|
|
#### Protective put protection schema
|
|
|
|
```json
|
|
{
|
|
"strategy": "protective_put_atm",
|
|
"threshold_price": 368.0,
|
|
"strike": 460.0,
|
|
"portfolio_floor_value": 1000000.0,
|
|
"unhedged_ltv_at_threshold": 0.75,
|
|
"hedged_ltv_at_threshold": 0.652174,
|
|
"payoff_at_threshold": 200000.0,
|
|
"maintains_margin_call_buffer": true
|
|
}
|
|
```
|
|
|
|
#### Protective put scenario row schema
|
|
|
|
```json
|
|
{
|
|
"price_change_pct": -0.2,
|
|
"gld_price": 368.0,
|
|
"gold_value": 800000.0,
|
|
"option_payoff": 200000.0,
|
|
"hedge_cost": 45920.43,
|
|
"net_portfolio_value": 954079.57,
|
|
"unhedged_ltv": 0.75,
|
|
"hedged_ltv": 0.6,
|
|
"margin_call_without_hedge": true,
|
|
"margin_call_with_hedge": false
|
|
}
|
|
```
|
|
|
|
#### Laddered put cost schema
|
|
|
|
Typical `cost` object for `laddered_put_*`:
|
|
|
|
```json
|
|
{
|
|
"strategy": "laddered_put_50_50_atm_otm95",
|
|
"label": "50_50_ATM_OTM95",
|
|
"legs": [
|
|
{
|
|
"weight": 0.5,
|
|
"strike": 460.0,
|
|
"premium_per_share": 21.1234,
|
|
"weighted_cost": 22960.22
|
|
}
|
|
],
|
|
"blended_premium_per_share": 18.4567,
|
|
"blended_cost": 40123.45,
|
|
"cost_pct_of_portfolio": 0.040123,
|
|
"annualized_cost": 40123.45,
|
|
"annualized_cost_pct": 0.040123
|
|
}
|
|
```
|
|
|
|
#### Laddered put protection schema
|
|
|
|
```json
|
|
{
|
|
"strategy": "laddered_put_50_50_atm_otm95",
|
|
"threshold_price": 368.0,
|
|
"portfolio_floor_value": 975000.0,
|
|
"payoff_at_threshold": 175000.0,
|
|
"unhedged_ltv_at_threshold": 0.75,
|
|
"hedged_ltv_at_threshold": 0.615385,
|
|
"maintains_margin_call_buffer": true,
|
|
"legs": [
|
|
{
|
|
"weight": 0.5,
|
|
"strike": 460.0,
|
|
"weighted_payoff_at_threshold": 100000.0
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
#### Lease duration analysis cost schema
|
|
|
|
Typical `cost` object for `lease_duration_analysis`:
|
|
|
|
```json
|
|
{
|
|
"strategy": "lease_duration_analysis",
|
|
"comparison": [
|
|
{
|
|
"months": 3,
|
|
"strike": 460.0,
|
|
"premium_per_share": 9.1234,
|
|
"total_cost": 19833.48,
|
|
"annualized_cost": 79333.92,
|
|
"annualized_cost_pct": 0.079334,
|
|
"rolls_per_year": 4.0,
|
|
"recommended_roll_month": 2
|
|
}
|
|
],
|
|
"optimal_duration_months": 12,
|
|
"lowest_annual_cost": 45920.43,
|
|
"lowest_annual_cost_pct": 0.04592
|
|
}
|
|
```
|
|
|
|
#### Lease duration analysis protection schema
|
|
|
|
```json
|
|
{
|
|
"strategy": "lease_duration_analysis",
|
|
"threshold_price": 368.0,
|
|
"durations": [
|
|
{
|
|
"months": 12,
|
|
"payoff_at_threshold": 200000.0,
|
|
"hedged_ltv_at_threshold": 0.6,
|
|
"maintains_margin_call_buffer": true
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### 4.3 `recommendations` schema
|
|
|
|
The object contains keys:
|
|
|
|
- `conservative`
|
|
- `balanced`
|
|
- `cost_sensitive`
|
|
|
|
Each recommendation object has this shape:
|
|
|
|
```json
|
|
{
|
|
"risk_profile": "balanced",
|
|
"recommended_strategy": "laddered_put_50_50_atm_otm95",
|
|
"rationale": {
|
|
"portfolio_value": 1000000.0,
|
|
"loan_amount": 600000.0,
|
|
"margin_call_threshold": 0.75,
|
|
"spot_price": 460.0,
|
|
"volatility": 0.16,
|
|
"risk_free_rate": 0.045
|
|
},
|
|
"comparison_summary": [
|
|
{
|
|
"name": "protective_put_atm",
|
|
"annual_cost": 45920.43,
|
|
"hedged_ltv_at_threshold": 0.6
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### 4.4 `sensitivity_analysis` schema
|
|
|
|
```json
|
|
{
|
|
"volatility": [
|
|
{
|
|
"volatility": 0.12,
|
|
"recommended_strategy": "protective_put_otm_95"
|
|
}
|
|
],
|
|
"spot_price": [
|
|
{
|
|
"spot_price": 414.0,
|
|
"recommended_strategy": "protective_put_otm_95"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## WebSocket API
|
|
|
|
## Endpoint
|
|
|
|
### `WS /ws/updates`
|
|
|
|
Used for server-pushed real-time updates.
|
|
|
|
### Connection lifecycle
|
|
|
|
1. Client opens a WebSocket connection to `/ws/updates`
|
|
2. Server accepts the connection
|
|
3. Server immediately sends a `connected` event
|
|
4. Server periodically broadcasts `portfolio_update` events
|
|
5. Client may keep the connection alive by sending text frames
|
|
6. Server removes the connection when disconnected or on send failure
|
|
|
|
### Event: `connected`
|
|
|
|
Sent once after successful connection.
|
|
|
|
#### Schema
|
|
|
|
```json
|
|
{
|
|
"type": "connected",
|
|
"message": "Real-time updates enabled"
|
|
}
|
|
```
|
|
|
|
Fields:
|
|
|
|
- `type` (`string`): event name
|
|
- `message` (`string`): human-readable confirmation
|
|
|
|
### Event: `portfolio_update`
|
|
|
|
Broadcast on an interval controlled by `WEBSOCKET_INTERVAL_SECONDS`.
|
|
|
|
#### Schema
|
|
|
|
```json
|
|
{
|
|
"type": "portfolio_update",
|
|
"connections": 2,
|
|
"portfolio": {
|
|
"symbol": "GLD",
|
|
"spot_price": 215.0,
|
|
"portfolio_value": 215000.0,
|
|
"loan_amount": 600000.0,
|
|
"ltv_ratio": 2.7907,
|
|
"updated_at": "2026-03-21T12:34:56.000000+00:00",
|
|
"source": "fallback"
|
|
}
|
|
}
|
|
```
|
|
|
|
Fields:
|
|
|
|
- `type` (`string`): event name
|
|
- `connections` (`integer`): current number of connected WebSocket clients
|
|
- `portfolio` (`object`): same schema as `GET /api/portfolio`
|
|
|
|
### Example JavaScript client
|
|
|
|
```js
|
|
const ws = new WebSocket('ws://localhost:8000/ws/updates');
|
|
|
|
ws.onmessage = (event) => {
|
|
const payload = JSON.parse(event.data);
|
|
console.log(payload.type, payload);
|
|
};
|
|
|
|
ws.onopen = () => {
|
|
// optional keepalive or ping surrogate
|
|
setInterval(() => {
|
|
if (ws.readyState === WebSocket.OPEN) {
|
|
ws.send('ping');
|
|
}
|
|
}, 10000);
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## OpenAPI
|
|
|
|
Because the app is built on FastAPI, interactive docs are typically available at:
|
|
|
|
- `/docs`
|
|
- `/redoc`
|
|
|
|
This file is the human-oriented reference for payload semantics and current behavior.
|
|
|
|
## Notes and limitations
|
|
|
|
- Endpoints are read-only today
|
|
- There are no POST/PUT/DELETE endpoints yet
|
|
- The options chain is currently synthetic
|
|
- Strategy outputs are paper-analysis results, not execution instructions
|
|
- Symbol validation is minimal and currently delegated to downstream quote behavior
|