3e0f289d51
- Bumped version numbers across Cargo.toml, Cargo.lock, pyproject.toml, and conda/meta.yaml to 1.1.3. - Added new features including American option pricing, digital options, extended Greeks, and historical volatility estimators. - Enhanced documentation and tests for new functionalities. - Updated CHANGELOG.md to reflect changes for version 1.1.3.
227 lines
7.4 KiB
Markdown
227 lines
7.4 KiB
Markdown
# Derivatives Analytics
|
||
|
||
`ferro-ta` ships a Rust-backed derivatives analytics layer focused on
|
||
research, simulation, and risk analysis. All functions are implemented in
|
||
Rust core and exposed to Python (via PyO3) and WebAssembly (via wasm-bindgen).
|
||
|
||
---
|
||
|
||
## Modules
|
||
|
||
### `ferro_ta.analysis.options`
|
||
|
||
| Category | Functions |
|
||
|---|---|
|
||
| **Pricing** | `black_scholes_price`, `black_76_price`, `option_price` |
|
||
| **Greeks** | `greeks`, `extended_greeks` |
|
||
| **Implied vol** | `implied_volatility`, `iv_rank`, `iv_percentile`, `iv_zscore` |
|
||
| **Digital options** | `digital_option_price`, `digital_option_greeks` |
|
||
| **American options** | `american_option_price`, `early_exercise_premium` |
|
||
| **Smile / surface** | `smile_metrics`, `term_structure_slope`, `expected_move` |
|
||
| **Chain helpers** | `label_moneyness`, `select_strike` |
|
||
| **Realised vol** | `close_to_close_vol`, `parkinson_vol`, `garman_klass_vol`, `rogers_satchell_vol`, `yang_zhang_vol` |
|
||
| **Vol cone** | `vol_cone` |
|
||
| **Diagnostics** | `put_call_parity_deviation` |
|
||
|
||
### `ferro_ta.analysis.futures`
|
||
|
||
- Synthetic forwards and parity diagnostics
|
||
- Basis, annualized basis, implied carry, carry spread
|
||
- Continuous contract stitching: weighted, back-adjusted, ratio-adjusted
|
||
- Curve analytics: calendar spreads, slope, contango summary
|
||
|
||
### `ferro_ta.analysis.options_strategy`
|
||
|
||
Typed strategy schemas: expiry selectors, strike selectors, multi-leg presets
|
||
(`STRADDLE`, `STRANGLE`, `IRON_CONDOR`, `BULL_CALL_SPREAD`, `BEAR_PUT_SPREAD`),
|
||
risk controls, cost assumptions, and simulation limits.
|
||
|
||
### `ferro_ta.analysis.derivatives_payoff`
|
||
|
||
Multi-leg payoff and Greeks aggregation supporting **option**, **future**, and
|
||
**stock** instrument types.
|
||
|
||
| Function | Description |
|
||
|---|---|
|
||
| `option_leg_payoff` | Expiry P/L for a single option leg |
|
||
| `futures_leg_payoff` | Linear P/L for a futures leg |
|
||
| `stock_leg_payoff` | Linear P/L for a stock/equity leg |
|
||
| `strategy_payoff` | Aggregate expiry payoff across all legs |
|
||
| `strategy_value` | Pre-expiry BSM mid-price value of a multi-leg strategy |
|
||
| `aggregate_greeks` | Portfolio-level Greeks across option, futures, and stock legs |
|
||
|
||
---
|
||
|
||
## Model conventions
|
||
|
||
| Parameter | Convention |
|
||
|---|---|
|
||
| `model="bsm"` | Underlying is spot; `carry` = continuous dividend yield |
|
||
| `model="black76"` | Underlying is the forward price |
|
||
| `volatility` / `rate` / `carry` | Decimal annual (e.g. `0.20` = 20 %, `0.05` = 5 %) |
|
||
| `time_to_expiry` | Years (e.g. `0.25` = 3 months) |
|
||
|
||
---
|
||
|
||
## Quick examples
|
||
|
||
### BSM pricing and Greeks
|
||
|
||
```python
|
||
from ferro_ta.analysis.options import greeks, implied_volatility, option_price
|
||
|
||
price = option_price(100.0, 100.0, 0.05, 1.0, 0.20, option_type="call")
|
||
iv = implied_volatility(price, 100.0, 100.0, 0.05, 1.0, option_type="call")
|
||
g = greeks(100.0, 100.0, 0.05, 1.0, 0.20, option_type="call")
|
||
print(price, iv, g.delta, g.gamma)
|
||
```
|
||
|
||
### Extended (second-order) Greeks
|
||
|
||
```python
|
||
from ferro_ta.analysis.options import extended_greeks
|
||
|
||
eg = extended_greeks(100.0, 100.0, 0.05, 1.0, 0.20, option_type="call")
|
||
print(eg.vanna, eg.volga, eg.charm, eg.speed, eg.color)
|
||
```
|
||
|
||
### Digital options
|
||
|
||
```python
|
||
from ferro_ta.analysis.options import digital_option_price, digital_option_greeks
|
||
|
||
# Cash-or-nothing call at ATM ≈ e^{-rT} * N(d2) ≈ 0.53
|
||
price = digital_option_price(100.0, 100.0, 0.05, 1.0, 0.20,
|
||
option_type="call", digital_type="cash_or_nothing")
|
||
g = digital_option_greeks(100.0, 100.0, 0.05, 1.0, 0.20,
|
||
option_type="call", digital_type="cash_or_nothing")
|
||
print(price, g.delta, g.gamma, g.vega)
|
||
```
|
||
|
||
### American options (BAW approximation)
|
||
|
||
```python
|
||
from ferro_ta.analysis.options import american_option_price, early_exercise_premium
|
||
|
||
# American put — may have meaningful early exercise premium
|
||
american = american_option_price(100.0, 100.0, 0.05, 1.0, 0.20, option_type="put")
|
||
premium = early_exercise_premium(100.0, 100.0, 0.05, 1.0, 0.20, option_type="put")
|
||
print(american, premium)
|
||
```
|
||
|
||
### Historical volatility estimators
|
||
|
||
```python
|
||
import numpy as np
|
||
from ferro_ta.analysis.options import (
|
||
close_to_close_vol, garman_klass_vol, parkinson_vol,
|
||
rogers_satchell_vol, yang_zhang_vol,
|
||
)
|
||
|
||
# Assume daily OHLC arrays of length N
|
||
open_p, high_p, low_p, close_p = ... # numpy arrays
|
||
|
||
ctc = close_to_close_vol(close_p, window=20) # close-only
|
||
park = parkinson_vol(high_p, low_p, window=20) # high-low
|
||
gk = garman_klass_vol(open_p, high_p, low_p, close_p, window=20)
|
||
rs = rogers_satchell_vol(open_p, high_p, low_p, close_p, window=20)
|
||
yz = yang_zhang_vol(open_p, high_p, low_p, close_p, window=20)
|
||
```
|
||
|
||
### Volatility cone
|
||
|
||
```python
|
||
from ferro_ta.analysis.options import vol_cone
|
||
|
||
cone = vol_cone(close_p, windows=(21, 42, 63, 126, 252))
|
||
# Overlay current IV against the cone to gauge richness/cheapness
|
||
for w, med in zip(cone.windows, cone.median):
|
||
print(f"window={int(w):3d} median_rv={med:.1%}")
|
||
```
|
||
|
||
### Put-call parity check
|
||
|
||
```python
|
||
from ferro_ta.analysis.options import option_price, put_call_parity_deviation
|
||
|
||
call = option_price(100.0, 100.0, 0.05, 1.0, 0.20, option_type="call")
|
||
put = option_price(100.0, 100.0, 0.05, 1.0, 0.20, option_type="put")
|
||
dev = put_call_parity_deviation(call, put, 100.0, 100.0, 0.05, 1.0)
|
||
# dev ≈ 0.0 for BSM-consistent prices; non-zero signals stale/mismatched quotes
|
||
```
|
||
|
||
### Expected move
|
||
|
||
```python
|
||
from ferro_ta.analysis.options import expected_move
|
||
|
||
lower, upper = expected_move(100.0, 0.20, days_to_expiry=30)
|
||
print(f"Expected ±1σ range: [{100+lower:.2f}, {100+upper:.2f}]")
|
||
```
|
||
|
||
### Multi-leg strategies with stock
|
||
|
||
```python
|
||
import numpy as np
|
||
from ferro_ta.analysis.derivatives_payoff import PayoffLeg, strategy_payoff, strategy_value
|
||
|
||
# Covered Call: long 100 shares + short 1 OTM call
|
||
spot_grid = np.linspace(80, 130, 100)
|
||
legs = [
|
||
PayoffLeg("stock", "long", entry_price=100.0),
|
||
PayoffLeg("option", "short", option_type="call",
|
||
strike=110.0, premium=3.0, volatility=0.20, time_to_expiry=0.25),
|
||
]
|
||
|
||
# Expiry P/L
|
||
payoff = strategy_payoff(spot_grid, legs=legs)
|
||
|
||
# Pre-expiry BSM value (T=3 months remaining)
|
||
value = strategy_value(spot_grid, legs=legs, time_to_expiry=0.25, volatility=0.20)
|
||
```
|
||
|
||
### Futures analytics
|
||
|
||
```python
|
||
from ferro_ta.analysis.futures import basis, curve_summary
|
||
|
||
print(basis(100.0, 103.0))
|
||
print(curve_summary(100.0, [0.1, 0.5, 1.0], [101.0, 102.0, 104.0]))
|
||
```
|
||
|
||
---
|
||
|
||
## Instrument types in `PayoffLeg` / `StrategyLeg`
|
||
|
||
| `instrument` | Required fields | Payoff |
|
||
|---|---|---|
|
||
| `"option"` | `option_type`, `strike`, `expiry_selector`, `strike_selector` | `max(φ(S−K), 0) − premium` |
|
||
| `"future"` | `entry_price` | `S − entry_price` |
|
||
| `"stock"` | `entry_price` | `S − entry_price` (identical to future, no margin) |
|
||
|
||
---
|
||
|
||
## Volatility estimator efficiency comparison
|
||
|
||
| Estimator | Relative efficiency vs close-to-close | Handles overnight gaps |
|
||
|---|---|---|
|
||
| Close-to-close | 1× (baseline) | N/A (uses close only) |
|
||
| Parkinson | ~5× | No |
|
||
| Garman-Klass | ~7.4× | No |
|
||
| Rogers-Satchell | ~8× | No |
|
||
| Yang-Zhang | ~14× | Yes |
|
||
|
||
*Use Yang-Zhang when you have overnight gaps (futures, crypto). Use Parkinson
|
||
or Garman-Klass for continuous trading sessions.*
|
||
|
||
---
|
||
|
||
## Notes
|
||
|
||
- All existing function names (`iv_rank`, `iv_percentile`, `iv_zscore`, `greeks`,
|
||
`option_price`, etc.) are preserved — fully backward compatible.
|
||
- The derivatives layer is analytics-only: no broker connectivity, order routing,
|
||
or execution workflow.
|
||
- WASM: all functions in this layer are also exported as WebAssembly bindings
|
||
(see `wasm/src/lib.rs`).
|