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:
593
docs/API.md
Normal file
593
docs/API.md
Normal file
@@ -0,0 +1,593 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user