98 lines
2.9 KiB
Python
98 lines
2.9 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
import pandas as pd
|
|
import pytest
|
|
|
|
from app.core.calculations import option_row_greeks
|
|
from app.core.pricing.black_scholes import (
|
|
DEFAULT_RISK_FREE_RATE,
|
|
DEFAULT_VOLATILITY,
|
|
BlackScholesInputs,
|
|
black_scholes_price_and_greeks,
|
|
)
|
|
from app.services.cache import CacheService
|
|
from app.services.data_service import DataService
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"row, expected_volatility",
|
|
[
|
|
(
|
|
{
|
|
"strike": 460.0,
|
|
"expiry": "2026-06-19",
|
|
"type": "put",
|
|
"impliedVolatility": 0.24,
|
|
},
|
|
0.24,
|
|
),
|
|
(
|
|
{
|
|
"strike": 460.0,
|
|
"expiry": "2026-06-19",
|
|
"type": "put",
|
|
"impliedVolatility": 0.0,
|
|
},
|
|
DEFAULT_VOLATILITY,
|
|
),
|
|
],
|
|
)
|
|
def test_option_row_greeks_uses_live_iv_or_falls_back(row: dict[str, object], expected_volatility: float) -> None:
|
|
valuation_date = datetime(2026, 3, 23).date()
|
|
|
|
greeks = option_row_greeks(row, underlying_price=460.0, valuation_date=valuation_date)
|
|
expected = black_scholes_price_and_greeks(
|
|
BlackScholesInputs(
|
|
spot=460.0,
|
|
strike=460.0,
|
|
time_to_expiry=(datetime(2026, 6, 19).date() - valuation_date).days / 365.0,
|
|
risk_free_rate=DEFAULT_RISK_FREE_RATE,
|
|
volatility=expected_volatility,
|
|
option_type="put",
|
|
valuation_date=valuation_date,
|
|
)
|
|
)
|
|
|
|
assert greeks["delta"] == pytest.approx(expected.delta, rel=1e-9)
|
|
assert greeks["gamma"] == pytest.approx(expected.gamma, rel=1e-9)
|
|
assert greeks["theta"] == pytest.approx(expected.theta, rel=1e-9)
|
|
assert greeks["vega"] == pytest.approx(expected.vega, rel=1e-9)
|
|
|
|
|
|
def test_option_row_greeks_handles_invalid_input_gracefully() -> None:
|
|
greeks = option_row_greeks(
|
|
{"strike": 0.0, "expiry": "not-a-date", "type": "call", "impliedVolatility": -1.0},
|
|
underlying_price=0.0,
|
|
)
|
|
|
|
assert greeks == {"delta": 0.0, "gamma": 0.0, "theta": 0.0, "vega": 0.0, "rho": 0.0}
|
|
|
|
|
|
def test_normalize_option_rows_populates_greeks() -> None:
|
|
frame = pd.DataFrame(
|
|
[
|
|
{
|
|
"contractSymbol": "GLD260619P00460000",
|
|
"strike": 460.0,
|
|
"bid": 18.5,
|
|
"ask": 19.5,
|
|
"lastPrice": 19.0,
|
|
"impliedVolatility": 0.22,
|
|
"openInterest": 100,
|
|
"volume": 50,
|
|
}
|
|
]
|
|
)
|
|
service = DataService(CacheService(None))
|
|
|
|
rows = service._normalize_option_rows(frame, "GLD", "2026-06-19", "put", 460.0)
|
|
|
|
assert len(rows) == 1
|
|
assert rows[0]["symbol"] == "GLD260619P00460000"
|
|
assert rows[0]["delta"] < 0.0
|
|
assert rows[0]["gamma"] > 0.0
|
|
assert rows[0]["theta"] < 0.0
|
|
assert rows[0]["vega"] > 0.0
|