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:
Bu5hm4nn
2026-03-21 19:21:40 +01:00
commit 00a68bc767
63 changed files with 6239 additions and 0 deletions

424
docs/STRATEGIES.md Normal file
View File

@@ -0,0 +1,424 @@
# Strategy Documentation
## Overview
Vault Dashboard currently documents and compares hedging approaches for a Lombard-style loan backed by gold exposure. The implementation focuses on paper analysis using option pricing, LTV protection metrics, and scenario analysis.
The strategy subsystem currently includes:
- protective puts
- laddered puts
- lease/LEAPS duration analysis
This document focuses on the two primary hedge structures requested here:
- protective put
- laddered put
---
## Common portfolio assumptions
The default research engine in `app/strategies/engine.py` uses:
- portfolio value: `1,000,000`
- loan amount: `600,000`
- margin-call threshold: `0.75`
- spot price: `460`
- volatility: `0.16`
- risk-free rate: `0.045`
From these values, the portfolio is modeled as a `LombardPortfolio`:
- gold ounces = `portfolio_value / spot_price`
- initial LTV = `loan_amount / portfolio_value`
- margin call price = `loan_amount / (margin_call_ltv * gold_ounces)`
These assumptions create the common basis used to compare all strategies.
---
## Protective put
## What it is
A protective put is the simplest downside hedge in the project.
Structure:
- long the underlying collateral exposure implicitly represented by the gold-backed portfolio
- buy one put hedge sized to the portfolio's underlying units
In this codebase, `ProtectivePutStrategy` creates a single long put whose strike is defined as a percentage of spot.
Examples currently used by the engine:
- ATM protective put: strike = `100%` of spot
- 95% OTM protective put: strike = `95%` of spot
- 90% OTM protective put: strike = `90%` of spot
## Why use it
A protective put sets a floor on downside beyond the strike, helping reduce the chance that falling collateral value pushes the portfolio above the margin-call LTV.
## How it is implemented
**File:** `app/strategies/protective_put.py`
Main properties:
- `hedge_units`: `portfolio.gold_value / spot_price`
- `strike`: `spot_price * strike_pct`
- `term_years`: `months / 12`
A put contract is priced with Black-Scholes inputs:
- current spot
- strike
- time to expiry
- risk-free rate
- volatility
- option type = `put`
The resulting `OptionContract` uses:
- `quantity = 1.0`
- `contract_size = hedge_units`
That means one model contract covers the full portfolio exposure in underlying units.
## Protective put payoff intuition
At expiry:
- if spot is above strike, the put expires worthless
- if spot is below strike, payoff rises linearly as `strike - spot`
Total gross payoff is:
```text
max(strike - spot, 0) * hedge_units
```
## Protective put trade-offs
Advantages:
- simple to explain
- clear downside floor
- strongest protection when strike is high
Costs:
- premium can be expensive, especially at-the-money and for longer tenor
- full notional protection may overspend relative to a client's risk budget
- upside is preserved, but cost drags returns
---
## Laddered put
## What it is
A laddered put splits the hedge across multiple put strikes instead of buying the full hedge at one strike.
Structure:
- multiple long put legs
- each leg covers a weighted fraction of the total hedge
- lower strikes usually reduce premium while preserving some tail protection
Examples currently used by the engine:
- `50/50` ATM + 95% OTM
- `33/33/33` ATM + 95% OTM + 90% OTM
## Why use it
A ladder can reduce hedge cost versus a full ATM protective put, while still providing meaningful protection as the underlying falls.
This is useful when:
- full-cost protection is too expensive
- some drawdown can be tolerated before the hedge fully engages
- the client wants a better cost/protection balance
## How it is implemented
**File:** `app/strategies/laddered_put.py`
A `LadderSpec` defines:
- `weights`
- `strike_pcts`
- `months`
Validation rules:
- number of weights must equal number of strikes
- weights must sum to `1.0`
Each leg is implemented by internally creating a `ProtectivePutStrategy`, then weighting its premium and payoff.
## Ladder payoff intuition
Each leg pays off independently:
```text
max(leg_strike - spot, 0) * hedge_units * weight
```
Total ladder payoff is the sum across legs.
Relative to a single-strike hedge:
- protection turns on in stages
- blended premium is lower when some legs are farther OTM
- downside support is smoother but less absolute near the first loss zone than a full ATM hedge
## Ladder trade-offs
Advantages:
- lower blended hedge cost
- more flexible cost/protection shaping
- better fit for cost-sensitive clients
Costs and limitations:
- weaker immediate protection than a fully ATM hedge
- more complex to explain to users
- floor value depends on weight distribution across strikes
---
## Cost calculations
## Protective put cost calculation
`ProtectivePutStrategy.calculate_cost()` returns:
- `premium_per_share`
- `total_cost`
- `cost_pct_of_portfolio`
- `term_months`
- `annualized_cost`
- `annualized_cost_pct`
### Formula summary
Let:
- `P` = option premium per underlying unit
- `U` = hedge units
- `T` = term in years
- `V` = portfolio value
Then:
```text
total_cost = P * U
cost_pct_of_portfolio = total_cost / V
annualized_cost = total_cost / T
annualized_cost_pct = annualized_cost / V
```
Because the model contract size equals the full hedge units, the total premium directly represents the whole-portfolio hedge cost.
## Laddered put cost calculation
`LadderedPutStrategy.calculate_cost()` computes weighted leg costs.
For each leg `i`:
- `weight_i`
- `premium_i`
- `hedge_units`
Leg cost:
```text
leg_cost_i = premium_i * hedge_units * weight_i
```
Blended totals:
```text
blended_cost = sum(leg_cost_i)
blended_premium_per_share = sum(premium_i * weight_i)
annualized_cost = blended_cost / term_years
cost_pct_of_portfolio = blended_cost / portfolio_value
annualized_cost_pct = annualized_cost / portfolio_value
```
## Why annualized cost matters
The engine compares strategies with different durations, especially in `LeaseStrategy`. Annualizing allows the system to compare short-dated and long-dated hedges on a common yearly basis.
---
## Protection calculations
## Margin-call threshold price
The project defines the collateral price that would trigger a margin call as:
```text
margin_call_price = loan_amount / (margin_call_ltv * gold_ounces)
```
This is a key reference point for all protection calculations.
## Protective put protection calculation
At the threshold price:
1. compute the put payoff
2. add that payoff to the stressed collateral value
3. recompute LTV on the hedged collateral
Formulas:
```text
payoff_at_threshold = max(strike - threshold_price, 0) * hedge_units
hedged_value_at_threshold = gold_value_at_threshold + payoff_at_threshold
hedged_ltv_at_threshold = loan_amount / hedged_value_at_threshold
```
The strategy is flagged as maintaining a margin buffer when:
```text
hedged_ltv_at_threshold < margin_call_ltv
```
## Laddered put protection calculation
For a ladder, threshold payoff is the weighted sum of all leg payoffs:
```text
weighted_payoff_i = max(strike_i - threshold_price, 0) * hedge_units * weight_i
payoff_at_threshold = sum(weighted_payoff_i)
hedged_value_at_threshold = gold_value_at_threshold + payoff_at_threshold
hedged_ltv_at_threshold = loan_amount / hedged_value_at_threshold
```
The ladder's implied floor value is approximated as the weighted strike coverage:
```text
portfolio_floor_value = sum(strike_i * hedge_units * weight_i)
```
---
## Scenario analysis methodology
## Scenario grid
The current scenario engine in `ProtectivePutStrategy` uses a fixed price-change grid:
```text
-60%, -50%, -40%, -30%, -20%, -10%, 0%, +10%, +20%, +30%, +40%, +50%
```
For each change:
```text
scenario_price = spot_price * (1 + change)
```
Negative or zero prices are ignored.
## Metrics produced per scenario
For each scenario, the strategy computes:
- scenario spot price
- unhedged gold value
- option payoff
- hedge cost
- net portfolio value after hedge cost
- unhedged LTV
- hedged LTV
- whether a margin call occurs without the hedge
- whether a margin call occurs with the hedge
### Protective put scenario formulas
Let `S` be scenario spot.
```text
gold_value = gold_ounces * S
option_payoff = max(strike - S, 0) * hedge_units
hedged_collateral = gold_value + option_payoff
net_portfolio_value = gold_value + option_payoff - hedge_cost
unhedged_ltv = loan_amount / gold_value
hedged_ltv = loan_amount / hedged_collateral
```
Margin-call flags:
```text
margin_call_without_hedge = unhedged_ltv >= margin_call_ltv
margin_call_with_hedge = hedged_ltv >= margin_call_ltv
```
### Laddered put scenario formulas
For ladders:
```text
option_payoff = sum(max(strike_i - S, 0) * hedge_units * weight_i)
hedged_collateral = gold_value + option_payoff
net_portfolio_value = gold_value + option_payoff - blended_cost
```
All other LTV and margin-call logic is the same.
## Interpretation methodology
Scenario analysis is used to answer four practical questions:
1. **Cost:** How much premium is paid upfront?
2. **Activation:** At what downside level does protection meaningfully start?
3. **Buffer:** Does the hedge keep LTV below the margin-call threshold under stress?
4. **Efficiency:** How much protection is obtained per dollar of annualized hedge cost?
This is why each strategy exposes both:
- a `calculate_protection()` summary around the threshold price
- a full `get_scenarios()` table across broad upside/downside moves
---
## Comparing protective puts vs laddered puts
| Dimension | Protective put | Laddered put |
|---|---|---|
| Structure | Single put strike | Multiple weighted put strikes |
| Simplicity | Highest | Moderate |
| Upfront cost | Usually higher | Usually lower |
| Near-threshold protection | Stronger if ATM-heavy | Depends on ladder weights |
| Tail downside protection | Strong | Strong, but blended |
| Customization | Limited | High |
| Best fit | conservative protection | balanced or cost-sensitive protection |
---
## Important limitations
- The strategy engine is currently research-oriented, not an execution engine
- Black-Scholes assumptions simplify real-world market behavior
- Transaction costs, slippage, taxes, liquidity, and early exercise effects are not modeled here
- The API payloads should be treated as analytical outputs, not trade recommendations
- For non-`GLD` symbols, the engine currently still uses research-style assumptions rather than a complete live instrument-specific calibration
## Future strategy extensions
Natural follow-ups for this subsystem:
- collars and financed hedges
- partial notional hedging
- dynamic re-hedging rules
- volatility surface-based pricing
- broker-native contract sizing and expiries
- user-configurable scenario grids