Files
fxhnt/tests/unit/test_vrp_pricing.py

44 lines
1.5 KiB
Python

import math
import pytest
from fxhnt.application.vrp_pricing import bs_put, implied_vol_put, put_delta, parity_forward
def test_bs_put_atm_positive_and_bounded():
# ATM put (F=K) has value between 0 and K; ~0.4*F*sigma*sqrt(tau) for small tau
p = bs_put(F=100.0, K=100.0, sigma=0.20, tau=30 / 365)
assert 0.0 < p < 100.0
assert p == pytest.approx(0.4 * 100.0 * 0.20 * math.sqrt(30 / 365), rel=0.05)
def test_bs_put_monotone_in_strike():
lo = bs_put(F=100.0, K=90.0, sigma=0.2, tau=0.1)
hi = bs_put(F=100.0, K=110.0, sigma=0.2, tau=0.1)
assert hi > lo # higher strike put is worth more
def test_implied_vol_round_trip():
price = bs_put(F=100.0, K=95.0, sigma=0.27, tau=45 / 365)
iv = implied_vol_put(price=price, F=100.0, K=95.0, tau=45 / 365)
assert iv == pytest.approx(0.27, abs=1e-4)
def test_implied_vol_returns_none_on_arbitrage():
# a price below intrinsic (K-F, discounted) has no arb-free IV
assert implied_vol_put(price=0.0001, F=100.0, K=130.0, tau=0.1) is None
def test_put_delta_otm_near_target():
# ~20-delta OTM put sits below spot; delta magnitude in (0,0.5)
d = put_delta(F=100.0, K=94.0, sigma=0.20, tau=30 / 365)
assert 0.0 < d < 0.5
def test_parity_forward_recovers_forward():
F = 100.0
tau = 0.1
sigma = 0.2
# build synthetic ATM call/put at K=100 consistent with forward F via parity: C - P = F - K
p = bs_put(F=F, K=100.0, sigma=sigma, tau=tau)
c = p + (F - 100.0)
assert parity_forward(call_atm=c, put_atm=p, k_atm=100.0) == pytest.approx(F, abs=1e-6)