core.test_lifetime

Tests for Lifetime Option Valuation

This module tests src/core/lifetime.py which provides option valuation and portfolio analysis during the lifetime of the hedge (before expiry), accounting for time value in addition to intrinsic value.

Key Concept: At Expiry vs During Lifetime

Aspect At Expiry During Lifetime
Option Value Intrinsic only: max(K-S, 0) Black-Scholes with time val
Fees Full period fees Pro-rated based on elapsed
Time Value Zero Positive (decays over time)
APR Calculation Based on total days Based on days elapsed

EXCEL VERIFICATION GUIDE

For a mathematician to verify these tests in Excel:

  1. Option Valuation (Lifetime):

    • Use the Black-Scholes Put formula (see test_black_scholes.py).
    • Multiply the result (P/S ratio) by the Spot Price to get dollar value.
    • Value = BS_Put_Ratio(K, T_remaining, S, σ, r) * S
  2. Fee Proration:

    • Formula: Fees = Total_Projected_Fees * (Days_Elapsed / Total_Duration)
    • Where Total_Projected_Fees = Investment * Yield * (Total_Duration / 365)
  3. Position Value:

    • PositionValue = OptionValue * Quantity

Test Categories

  1. Option Valuation

    • Tests for calc_option_value_during_lifetime()
    • Verifies time value for OTM options, ITM > intrinsic, expiry = intrinsic
  2. Position Value

    • Tests for calc_position_value_during_lifetime()
    • Verifies quantity scaling and directional behavior
  3. Pro-Rated Fees

    • Tests for calc_prorated_fees()
    • Verifies linear proration based on days elapsed
  4. Lifetime Analysis

    • Tests for generate_lifetime_analysis()
    • Verifies complete analysis generation with proper structure
  5. Comparison Tests

    • Tests for compare_expiry_vs_lifetime()
    • Verifies time value difference between lifetime and expiry
  6. Edge Cases

    • Tests for boundary conditions (0 days, negative days, empty positions)

Standard Test Parameters

SPOT = 3050        (Current ETH price in USDT)
P_MIN = 2200       (Lower price bound for LP position)
P_MAX = 3650       (Upper price bound for LP position)
VOLATILITY = 0.70  (70% implied volatility)
INVESTMENT = 100000 (Initial investment in USDT)
FEE_YIELD = 0.63   (63% APR fee yield from LP)

Run with: uv run pytest tests/test_lifetime.py -v

SPOT = Decimal('3050')
P_MIN = Decimal('2200')
P_MAX = Decimal('3650')
VOLATILITY = Decimal('0.70')
INVESTMENT = Decimal('100000')
FEE_YIELD = Decimal('0.63')
@pytest.fixture
def sample_inputs():

Create sample HedgeInputs for testing.

Returns HedgeInputs with standard parameters:

  • P0 = 3050 USDT/ETH
  • Price range: [2200, 3650]
  • Investment: $100,000
  • Expiry: 106 days from now
  • Volatility: 70%
  • Fee yield: 63% APR
@pytest.fixture
def sample_position():

Create a sample hedge position.

Returns a long put position:

  • Strike: 2500 (OTM by ~18%)
  • Quantity: 10 contracts
  • Premium: $1,525 total paid
@pytest.fixture
def sample_scenarios():

Create sample scenario points.

Returns 3 scenarios:

  1. -20% price drop (2440 USDT/ETH)
  2. 0% change (3050 USDT/ETH)
  3. +20% price rise (3660 USDT/ETH)

Each scenario includes LP values and IL calculations.

def test_otm_option_has_time_value():

OTM option should have time value when days remaining > 0.

EXCEL REPRODUCTION

1. Inputs (Cells A1:B5)

Cell Parameter Value
B1 Spot 3050
B2 Strike 2500
B3 Days 50
B4 Vol 0.70
B5 Rate 0

2. Time in Years (Cell B6)

Cell Name Formula Value
B6 T =B3/365 ~0.137

3. Black-Scholes Value (Cell B7) Use the d1/d2 and Put Price formulas from test_black_scholes.py.

  • Result should be > 0 even though Strike (2500) < Spot (3050).
  • Intrinsic Value = MAX(2500-3050, 0) = 0.
  • Option Value > Intrinsic Value.
def test_itm_option_greater_than_intrinsic():

ITM option should be worth more than intrinsic value.

def test_expiry_returns_intrinsic():

At expiry (days=0), should return only intrinsic value.

def test_more_time_more_value():

More days remaining should mean more option value (all else equal).

def test_higher_vol_more_value():

Higher volatility should increase option value.

def test_position_value_scales_with_quantity(sample_position):

Position value should scale linearly with quantity.

def test_position_value_increases_on_downside(sample_position):

Put position value should increase when price drops.

def test_full_period_fees():

Full period should give full fees.

EXCEL REPRODUCTION

1. Inputs (Cells A1:B4)

Cell Parameter Value
B1 Invest 100000
B2 Yield 0.63
B3 DaysTot 106
B4 DaysElap 106

2. Calculation (Cell B5)

Cell Name Formula Expectation
B5 Fees =(B4/365) * B1 * B2 ~18295.89

Note: The fee calculation assumes the yield is annualized (APR) and prorates it based on the number of days elapsed out of a 365-day year.

def test_half_period_half_fees():

Half period should give approximately half fees.

def test_zero_days_zero_fees():

Zero days elapsed should give zero fees.

def test_returns_lifetime_analysis(sample_inputs, sample_scenarios, sample_position):

Should return LifetimeAnalysis object.

def test_days_remaining_correct(sample_inputs, sample_scenarios, sample_position):

days_remaining should match input.

def test_scenarios_count_matches(sample_inputs, sample_scenarios, sample_position):

Number of result scenarios should match input scenarios.

def test_fees_prorated(sample_inputs, sample_scenarios, sample_position):

Fees should be pro-rated based on days elapsed.

def test_returns_both_analyses_cmp(sample_inputs, sample_scenarios, sample_position):

Should return both expiry and lifetime analyses.

def test_expiry_has_zero_days_remaining_cmp(sample_inputs, sample_scenarios, sample_position):

Expiry analysis should have days_remaining=0.

def test_lifetime_options_worth_more_than_expiry_cmp(sample_inputs, sample_scenarios, sample_position):

During lifetime, OTM options have time value. At expiry, OTM options are worthless. So lifetime capital should often be higher when options are OTM.

def test_zero_days_remaining_equals_intrinsic_edge(sample_inputs, sample_scenarios):

With 0 days remaining, options should be at intrinsic value.

NUMERICAL EXAMPLE

Position: Long 10 Put 3200 (ITM) spot = 3050, days = 0

Intrinsic = max(3200 - 3050, 0) = 150 per contract Position value = 10 × 150 = 1500 USDT

def test_negative_days_treated_as_zero_edge():

Negative days remaining should be treated as 0 (expiry).

def test_empty_positions_list_edge(sample_inputs, sample_scenarios):

Should handle empty positions list gracefully.