core.test_analysis
Tests for Hedge Analysis Functions
This module tests the pure functions in f.domains.hedge.core.analysis that analyze hedge portfolio performance and extract key metrics.
MATHEMATICAL BACKGROUND
The analysis functions evaluate hedged LP positions by computing:
Scenario Analysis: Finding worst/best/base case outcomes
- Worst case: argmin(scenarios, key=ROI)
- Best case: argmax(scenarios, key=ROI)
- Base case: argmin(|variation - target|)
Break-even Analysis: Finding where ROI approaches zero
- Break-even: scenario where |ROI| <= tolerance
Capital Breakdown: Decomposing final capital into components
- net_capital = LP_value + fees - hedge_cost + hedge_payoff
Unhedged Loss: Maximum IL without hedge protection
- worst_loss = max(|IL_vs_deposit|) for all scenarios
Strike Profit Matrix: Payoff of each strike at each scenario
- payoff[i,j] = max(K_i - S_j, 0) * quantity_i
KEY FORMULAS
Put Option Payoff: payoff = max(K - S, 0) * quantity
Where: K = strike price S = spot price at expiry quantity = number of contracts
ROI (Return on Investment): ROI = (capital / total_investment) - 1
APR (Annualized Percentage Rate): APR = (1 + ROI)^(365/days) - 1
Impermanent Loss vs Deposit: IL_deposit = (V_LP / I_initial) - 1
STANDARD TEST PARAMETERS
- P0 (spot price): 3050 USD
- P_min: 2200 USD
- Investment: 100,000 USD
- Fee yield: 63% APR
- Volatility: 70%
- Precision: 50 decimal places
Functions Tested (10 functions, 30 tests total)
- find_worst_case_scenario - 3 tests
- find_best_case_scenario - 3 tests
- find_base_case_scenario - 3 tests
- find_break_even_scenario - 3 tests
- calculate_unhedged_worst_loss - 3 tests
- build_key_takeaways - 3 tests
- calculate_capital_breakdown_entry - 3 tests
- build_capital_breakdown - 3 tests
- calculate_strike_payoff_at_price - 3 tests
- build_strike_profit_matrix - 3 tests
Sample scenario results for testing.
Four scenarios representing price movements from -30% to +20%:
| Variation | Price | LP Value | Capital | ROI | APR |
|---|---|---|---|---|---|
| -30% | 2135 | 75,000 | 95,000 | -5% | -15% |
| -10% | 2745 | 92,000 | 105,000 | +5% | +18% |
| 0% | 3050 | 100,000 | 110,000 | +10% | +35% |
| +20% | 3660 | 110,000 | 125,000 | +25% | +75% |
Sample IL scenario points for testing.
Three scenarios showing impermanent loss at different price levels:
| Variation | Price | LP Value | HODL Value | IL vs HODL | IL vs Deposit |
|---|---|---|---|---|---|
| -30% | 2135 | 75,000 | 80,000 | -6.25% | -25% |
| 0% | 3050 | 100,000 | 100,000 | 0% | 0% |
| +20% | 3660 | 105,000 | 110,000 | -4.5% | +5% |
Note: IL vs HODL = (V_LP / V_HODL) - 1 IL vs Deposit = (V_LP / I_initial) - 1 where I_initial = 100,000
Sample hedge positions for testing.
Two put options for downside protection:
| Strike | Quantity | Price (ratio) | Premium |
|---|---|---|---|
| 2600 | 5 | 0.05 (5%) | $762.50 |
| 2200 | 10 | 0.03 (3%) | $915.00 |
Total premium = $1,677.50
Test: Find scenario with minimum ROI from multiple scenarios.
MATHEMATICAL BACKGROUND
Worst case is defined as the scenario with the lowest ROI:
worst = argmin(scenarios, key=lambda s: s.roi)
This identifies the scenario where the hedged position performs worst, which typically occurs during significant downside moves where hedge protection may be insufficient.
NUMERICAL EXAMPLE
Input scenarios: [-30%: ROI=-5%, -10%: ROI=+5%, 0%: ROI=+10%, +20%: ROI=+25%]
Calculation: min(ROI) = min(-0.05, 0.05, 0.10, 0.25) = -0.05 Corresponding variation = -30%
Expected: worst.roi = -5% worst.variation = -30%
Test: Find worst case when only one scenario exists.
MATHEMATICAL BACKGROUND
With a single scenario, that scenario is by definition both the worst and best case.
NUMERICAL EXAMPLE
Input: Single scenario: variation=0%, ROI=+10%
Expected: worst.roi = +10% (only option)
Test: Handle empty scenario list gracefully.
EDGE CASE
When no scenarios are provided, return None rather than raising an exception. This allows callers to handle the case explicitly.
Expected: worst = None
Test: Find scenario with maximum ROI from multiple scenarios.
MATHEMATICAL BACKGROUND
Best case is defined as the scenario with the highest ROI:
best = argmax(scenarios, key=lambda s: s.roi)
This identifies the scenario where the hedged position performs best, typically when price moves favorably and hedge costs are minimized.
NUMERICAL EXAMPLE
Input scenarios: [-30%: ROI=-5%, -10%: ROI=+5%, 0%: ROI=+10%, +20%: ROI=+25%]
Calculation: max(ROI) = max(-0.05, 0.05, 0.10, 0.25) = 0.25 Corresponding variation = +20%
Expected: best.roi = +25% best.variation = +20%
Test: Find best case when only one scenario exists.
NUMERICAL EXAMPLE
Input: Single scenario: variation=-10%, ROI=0%
Expected: best.roi = 0% (only option)
Test: Handle empty scenario list gracefully.
Expected: best = None
Test: Find base case when exact variation=0 exists.
MATHEMATICAL BACKGROUND
Base case is the scenario closest to a target variation (default 0%):
base = argmin(scenarios, key=lambda s: |s.variation - target|)
When target=0%, this represents the "no price change" scenario, useful as a reference point for comparing other outcomes.
NUMERICAL EXAMPLE
Input scenarios: variations = [-30%, -10%, 0%, +20%] target = 0%
Calculation: distances = [|-0.30 - 0| = 0.30, |-0.10 - 0| = 0.10, |0 - 0| = 0, |0.20 - 0| = 0.20] min_distance = 0 at variation = 0%
Expected: base.variation = 0%
Test: Find base case when exact match doesn't exist.
NUMERICAL EXAMPLE
Input scenarios: variations = [-15%, +5%] target = 0%
Calculation: distances = [|-0.15 - 0| = 0.15, |0.05 - 0| = 0.05] min_distance = 0.05 at variation = +5%
Note: +5% is closer to 0% than -15%
Expected: base.variation = +5%
Test: Handle empty scenario list gracefully.
Expected: base = None
Test: Find break-even when ROI is exactly zero.
MATHEMATICAL BACKGROUND
Break-even is the scenario where ROI is within tolerance of zero:
break_even = scenario where |ROI| <= epsilon
This represents the price point where the strategy neither makes nor loses money, useful for risk assessment.
NUMERICAL EXAMPLE
Input: scenario: variation=-8%, ROI=0% tolerance = 1%
Calculation: |0| = 0 <= 0.01 (tolerance) Found break-even
Expected: break_even.roi = 0%
Test: Find break-even when ROI is near zero (within tolerance).
NUMERICAL EXAMPLE
Input scenarios: 1. variation=-10%, ROI=+0.5% 2. variation=-20%, ROI=-5% tolerance = 1%
Calculation: |0.005| = 0.005 <= 0.01 (within tolerance) - MATCH |-0.05| = 0.05 > 0.01 (outside tolerance)
Expected: break_even.variation = -10%
Test: Return None when no break-even point within tolerance.
NUMERICAL EXAMPLE
Input scenarios: 1. variation=-30%, ROI=-5% 2. variation=+20%, ROI=+25% tolerance = 1%
Calculation: |-0.05| = 0.05 > 0.01 (outside tolerance) |0.25| = 0.25 > 0.01 (outside tolerance)
Expected: break_even = None (no scenario within tolerance)
Test: Calculate maximum IL from scenarios with negative IL.
MATHEMATICAL BACKGROUND
Unhedged worst loss is the maximum absolute impermanent loss:
worst_loss = max(|IL_vs_deposit|) for all scenarios
IL is typically negative (a loss), so we take absolute value. This represents the worst-case loss without any hedge protection.
NUMERICAL EXAMPLE
Input scenario points: IL_vs_deposit = [-25%, 0%, +5%]
Calculation: absolute_values = [|-0.25|, |0|, |0.05|] = [0.25, 0, 0.05] max_loss = max(0.25, 0, 0.05) = 0.25
Expected: worst_loss = 25%
Test: Calculate worst loss when IL is zero (no price change).
NUMERICAL EXAMPLE
Input: Single point: IL_vs_deposit = 0%
Calculation: |0| = 0
Expected: worst_loss = 0%
Test: Handle empty scenario point list gracefully.
EDGE CASE
With no scenarios, return 0 as there's no loss to measure.
Expected: worst_loss = 0
Test: Build comprehensive key takeaways from scenarios.
MATHEMATICAL BACKGROUND
Key takeaways aggregate critical metrics from the analysis:
KeyTakeaways = {
worst_case: scenario with min(ROI),
best_case: scenario with max(ROI),
base_case: scenario closest to variation=0,
break_even_price: price where ROI ~ 0,
break_even_percent: variation at break-even,
max_protected_loss: worst_case.ROI,
unhedged_loss: max(|IL_vs_deposit|)
}
NUMERICAL EXAMPLE
Input: scenarios: 4 scenarios from -30% to +20% ROIs: [-5%, +5%, +10%, +25%] IL_vs_deposit: [-25%, 0%, +5%]
Calculation: worst_case.roi = min(-0.05, 0.05, 0.10, 0.25) = -0.05 best_case.roi = max(-0.05, 0.05, 0.10, 0.25) = 0.25 base_case.variation = 0 (exact match) unhedged_loss = max(|-0.25|, |0|, |0.05|) = 0.25
Expected: worst_case.roi = -5% best_case.roi = +25% base_case.variation = 0% unhedged_loss = 25%
Test: Build key takeaways when all values are zero.
NUMERICAL EXAMPLE
Input: Single scenario: variation=0%, ROI=0% Single point: IL=0%
Expected: All metrics at zero (no gain, no loss)
Test: Handle empty input gracefully.
EDGE CASE
With no scenarios, return None rather than incomplete data.
Expected: takeaways = None
Test: Calculate capital breakdown when hedge has positive payoff (ITM).
MATHEMATICAL BACKGROUND
Capital breakdown decomposes the final capital into components:
net_capital = LP_value + fees - hedge_cost + hedge_payoff
Where: LP_value: Value of LP position at scenario price fees: Earned fees over the period hedge_cost: Premium paid for hedge (negative) hedge_payoff: max(K - S, 0) * quantity for each position
NUMERICAL EXAMPLE
Input: scenario: variation=-30%, price=2135 positions: Put 2600 x 5, Put 2200 x 10 fees = $18,295 total_cost = $1,677.50 spot_price = 3050
Calculation: future_spot = 3050 * (1 + (-0.30)) = 3050 * 0.70 = 2135 Put 2600 payoff = max(2600 - 2135, 0) * 5 = 465 * 5 = 2325 Put 2200 payoff = max(2200 - 2135, 0) * 10 = 65 * 10 = 650 Total hedge_payoff = 2325 + 650 = 2975
Expected: entry.variation = -30% entry.lp_position = 75,000 entry.fees_earned = 18,295 entry.hedge_cost = -1,677.50 (negative) entry.hedge_payoff > 0 (ITM puts)
Test: Calculate capital breakdown when hedge has zero payoff (OTM).
NUMERICAL EXAMPLE
Input: scenario: variation=+20%, price=3660 positions: Put 2600 x 5, Put 2200 x 10 spot_price = 3050
Calculation: future_spot = 3050 * (1 + 0.20) = 3050 * 1.20 = 3660 Put 2600 payoff = max(2600 - 3660, 0) * 5 = 0 (OTM) Put 2200 payoff = max(2200 - 3660, 0) * 10 = 0 (OTM) Total hedge_payoff = 0
Note: Both puts are OTM when price rises to 3660
Expected: entry.hedge_payoff = 0
Test: Capital breakdown with moderate downside (partial OTM).
NUMERICAL EXAMPLE
Input: scenario: variation=-10%, price=2745 positions: Put 2600 x 5, Put 2200 x 10 spot_price = 3050
Calculation: future_spot = 3050 * (1 + (-0.10)) = 3050 * 0.90 = 2745 Put 2600 payoff = max(2600 - 2745, 0) * 5 = 0 (OTM, 2745 > 2600) Put 2200 payoff = max(2200 - 2745, 0) * 10 = 0 (OTM, 2745 > 2200) Total hedge_payoff = 0
Note: Even at -10%, price (2745) is still above both strikes
Expected: entry.hedge_payoff = 0
Test: Build capital breakdown for multiple scenarios.
MATHEMATICAL BACKGROUND
Builds a list of CapitalBreakdownEntry, one for each scenario:
breakdowns = [
calculate_capital_breakdown_entry(s, positions, fees, cost, spot)
for s in scenarios
]
NUMERICAL EXAMPLE
Input: 4 scenarios: variations = [-30%, -10%, 0%, +20%]
Expected: 4 breakdown entries, one per scenario
Test: Build capital breakdown for single scenario.
NUMERICAL EXAMPLE
Input: 1 scenario: variation=0% No hedge positions
Expected: 1 breakdown entry
Test: Build capital breakdown for empty scenarios.
Expected: Empty list
Test: Calculate put payoff when option is ITM (In-The-Money).
MATHEMATICAL BACKGROUND
Put option payoff at expiry:
payoff = max(K - S, 0) * quantity
Where: K = strike price S = spot price at expiry quantity = number of contracts
ITM condition: S < K (spot below strike)
NUMERICAL EXAMPLE
Input: strike = 2500 quantity = 10 spot_price = 2200
Calculation: intrinsic = max(2500 - 2200, 0) = max(300, 0) = 300 payoff = 300 * 10 = 3000
Expected: payoff = $3,000
Test: Calculate put payoff when option is OTM (Out-of-The-Money).
MATHEMATICAL BACKGROUND
OTM condition: S > K (spot above strike) When OTM, intrinsic value is zero.
NUMERICAL EXAMPLE
Input: strike = 2500 quantity = 10 spot_price = 3000
Calculation: intrinsic = max(2500 - 3000, 0) = max(-500, 0) = 0 payoff = 0 * 10 = 0
Expected: payoff = $0
Test: Calculate put payoff when option is ATM (At-The-Money).
MATHEMATICAL BACKGROUND
ATM condition: S = K (spot equals strike) At expiry, ATM options have zero intrinsic value.
NUMERICAL EXAMPLE
Input: strike = 2500 quantity = 10 spot_price = 2500
Calculation: intrinsic = max(2500 - 2500, 0) = max(0, 0) = 0 payoff = 0 * 10 = 0
Expected: payoff = $0
Test: Build strike profit matrix with explicitly selected strikes.
MATHEMATICAL BACKGROUND
The strike profit matrix shows each strike's payoff at each scenario:
Matrix structure:
- Rows: strikes (sorted descending)
- Columns: scenarios (variations)
- Values: payoff = max(K - S, 0) * quantity
StrikeProfitEntry = { strike: K, quantity: q, payoffs: [ {variation: v, spot_price: S, payoff: max(K-S,0)*q} for each scenario ] }
NUMERICAL EXAMPLE
Input: positions: Put 2600 x 5, Put 2200 x 10 scenarios: 4 scenarios selected_strikes: [2600, 2200]
Expected: 2 entries (one per strike) 4 payoffs per entry (one per scenario) Strikes sorted descending: [2600, 2200]
Test: Build strike profit matrix auto-extracting strikes from positions.
NUMERICAL EXAMPLE
Input: positions: Put 2600 x 5, Put 2200 x 10 selected_strikes: None (auto-extract)
Expected: 2 entries (one per position with non-zero quantity)
Test: Build strike profit matrix with no positions.
EDGE CASE
With no positions, return empty matrix.
Expected: matrix = []