core.test_portfolio

Tests for Hedge Portfolio Display Functions

Tests the pure functions in f.domains.hedge.core.portfolio that build displayable portfolio entries for the frontend hedge analysis UI.

MATHEMATICAL BACKGROUND

The hedge portfolio display shows option positions scaled relative to the LP position size, with allocation percentages for risk management.

KEY FORMULAS

  1. Quantity per ETH (Hedge Ratio):

    quantity_per_eth = total_quantity / lp_size
    

    Where: total_quantity = Number of put contracts held lp_size = Size of LP position in ETH

    Interpretation: "For each 1 ETH of LP exposure, we hold X puts"

    Example: total_quantity = 11, lp_size = 32.787 quantity_per_eth = 11 / 32.787 = 0.3355

    Meaning: ~0.34 puts per ETH of LP position
    
  2. Allocation Percentage:

    allocation_pct = (position_premium / total_cost) × 100
    

    Where: position_premium = Premium paid for this strike position total_cost = Total premium across all positions

    Example: position_premium = 1133.49, total_cost = 8889 allocation_pct = (1133.49 / 8889) × 100 = 12.75%

  3. Scaled Quantity: The raw quantity from the optimizer, preserved for display. This is what gets sent to the exchange for order execution.

  4. Premium Calculation:

    premium = quantity × put_price × spot_price
    

    Where: put_price = Black-Scholes price as fraction of spot

EXCEL VERIFICATION GUIDE

For a mathematician to verify these metrics in Excel:

  1. Hedge Ratio: =Quantity / LP_Size_ETH
  2. Allocation %: =Premium / Total_Premium
  3. Zero Quantity Pricing: Use Black-Scholes Put formula (see test_black_scholes.py).

PORTFOLIO ENTRY STRUCTURE

Each HedgePortfolioEntry contains:

Field Type Description
strike Decimal Strike price (e.g., 2200)
instrument_name str Deribit format (ETH-28MAR26-2200-P)
quantity_per_eth Decimal Contracts per unit of LP size
scaled_quantity Decimal Total contracts to purchase
put_price Decimal Option price as ratio of spot
premium Decimal Total cost for this position
allocation_pct Decimal Percentage of total hedge cost

ZERO QUANTITY ENTRIES

The portfolio display includes "zero quantity" entries for strikes that are available but not selected by the optimizer. These entries:

  • Show theoretical put prices (for user reference)
  • Have quantity_per_eth = 0, scaled_quantity = 0, premium = 0
  • Allow users to see what protection would cost at unused strikes

This is computed using Black-Scholes with the current market parameters (spot, volatility, time to expiry).

DISPLAY ORDERING

Entries are sorted by strike price descending (highest strike first). This matches the convention of showing ATM options at the top.

Example: [Strike 2800, Strike 2600, Strike 2400, Strike 2200]

FUNCTIONS TESTED

  1. build_hedge_portfolio_entry()

    • Builds entry from HedgePosition with scaling calculations
    • Calculates quantity_per_eth and allocation_pct
  2. build_zero_quantity_entry()

    • Builds entry for unused strike with theoretical price
    • Uses Black-Scholes for put_price calculation
  3. build_hedge_portfolio_display()

    • Combines positions with selected strikes
    • Fills gaps with zero-quantity entries
    • Returns sorted list
@pytest.fixture
def sample_position():

Sample hedge position for testing.

@pytest.fixture
def sample_positions():

Multiple hedge positions for testing.

@pytest.fixture
def expiry_date():

Sample expiry date.

def test_build_portfolio_entry_standard(sample_position, expiry_date):

Test: Build portfolio entry from standard position.

NUMERICAL EXAMPLE

Input: position: Put 2200, amount=11, price=0.0338, premium=1133.49 size = 32.787 p0 = 3050 total_cost = 8889

Calculation: quantity_per_eth = 11 / 32.787 = 0.3355 allocation_pct = (1133.49 / 8889) × 100 = 12.75%

Expected: Valid HedgePortfolioEntry with calculated values

EXCEL REPRODUCTION

1. Inputs (Cells A1:B4)

Cell Parameter Value
B1 Quantity 11
B2 Premium 1133.49
B3 LP_Size 32.787
B4 TotalCost 8889

2. Calculation (Cells B5:B6)

Cell Name Formula Expectation
B5 Qty/ETH =B1/B3 0.3355
B6 Alloc% =B2/B4 12.75%
def test_build_portfolio_entry_large_quantity(expiry_date):

Test portfolio entry with large quantity.

def test_build_portfolio_entry_small_quantity(expiry_date):

Test portfolio entry with small quantity.

def test_build_zero_quantity_entry_otm(expiry_date):

Test: Build zero quantity entry for OTM strike.

NUMERICAL EXAMPLE

Input: strike = 2200 (OTM, below current price) p0 = 3050 volatility = 0.70 time_to_expiry = 0.29 years

Expected: Entry with quantity=0 but valid put price > 0

def test_build_zero_quantity_entry_atm(expiry_date):

Test zero quantity entry for ATM strike.

def test_build_zero_quantity_entry_itm(expiry_date):

Test zero quantity entry for ITM strike.

def test_build_portfolio_display_with_selected(sample_positions, expiry_date):

Test: Build portfolio display with selected strikes.

NUMERICAL EXAMPLE

Input: positions: [Put 2600 × 5, Put 2200 × 10] selected_strikes: [2800, 2600, 2200] # Includes 2800 not in positions

Expected: 3 entries: 2800 (zero qty), 2600 (has position), 2200 (has position)

def test_build_portfolio_display_without_selected(sample_positions, expiry_date):

Test portfolio display without selected strikes (auto from positions).

def test_build_portfolio_display_mixed(expiry_date):

Test portfolio display with mix of positions and selected strikes.