fix(pricing): correct relative hedge payoff calculations
This commit is contained in:
@@ -4,6 +4,12 @@ from typing import Any
|
||||
|
||||
from nicegui import ui
|
||||
|
||||
from app.domain.portfolio_math import (
|
||||
strategy_benefit_per_unit,
|
||||
strategy_protection_floor_bounds,
|
||||
strategy_upside_cap_price,
|
||||
)
|
||||
|
||||
|
||||
class StrategyComparisonPanel:
|
||||
"""Interactive strategy comparison with scenario slider and cost-benefit table."""
|
||||
@@ -107,8 +113,6 @@ class StrategyComparisonPanel:
|
||||
for strategy in self.strategies:
|
||||
name = str(strategy.get("name", "strategy")).replace("_", " ").title()
|
||||
cost = float(strategy.get("estimated_cost", 0.0))
|
||||
floor = strategy.get("max_drawdown_floor", "—")
|
||||
cap = strategy.get("upside_cap", "—")
|
||||
scenario = self._scenario_benefit(strategy)
|
||||
scenario_class = (
|
||||
"text-emerald-600 dark:text-emerald-400" if scenario >= 0 else "text-rose-600 dark:text-rose-400"
|
||||
@@ -117,8 +121,8 @@ class StrategyComparisonPanel:
|
||||
<tr class=\"border-b border-slate-200 dark:border-slate-800\">
|
||||
<td class=\"px-4 py-3 font-medium text-slate-900 dark:text-slate-100\">{name}</td>
|
||||
<td class=\"px-4 py-3 text-slate-600 dark:text-slate-300\">${cost:,.2f}</td>
|
||||
<td class=\"px-4 py-3 text-slate-600 dark:text-slate-300\">{self._fmt_optional_money(floor)}</td>
|
||||
<td class=\"px-4 py-3 text-slate-600 dark:text-slate-300\">{self._fmt_optional_money(cap)}</td>
|
||||
<td class=\"px-4 py-3 text-slate-600 dark:text-slate-300\">{self._format_floor(strategy)}</td>
|
||||
<td class=\"px-4 py-3 text-slate-600 dark:text-slate-300\">{self._format_cap(strategy)}</td>
|
||||
<td class=\"px-4 py-3 font-semibold {scenario_class}\">${scenario:,.2f}</td>
|
||||
</tr>
|
||||
""")
|
||||
@@ -140,17 +144,26 @@ class StrategyComparisonPanel:
|
||||
"""
|
||||
|
||||
def _scenario_benefit(self, strategy: dict[str, Any]) -> float:
|
||||
scenario_spot = self._scenario_spot()
|
||||
cost = float(strategy.get("estimated_cost", 0.0))
|
||||
floor = strategy.get("max_drawdown_floor")
|
||||
cap = strategy.get("upside_cap")
|
||||
benefit = -cost
|
||||
return strategy_benefit_per_unit(
|
||||
strategy,
|
||||
current_spot=self.current_spot,
|
||||
scenario_spot=self._scenario_spot(),
|
||||
)
|
||||
|
||||
if isinstance(floor, (int, float)) and scenario_spot < float(floor):
|
||||
benefit += float(floor) - scenario_spot
|
||||
if isinstance(cap, (int, float)) and scenario_spot > float(cap):
|
||||
benefit -= scenario_spot - float(cap)
|
||||
return benefit
|
||||
def _format_floor(self, strategy: dict[str, Any]) -> str:
|
||||
bounds = strategy_protection_floor_bounds(strategy, current_spot=self.current_spot)
|
||||
if bounds is None:
|
||||
return self._fmt_optional_money(strategy.get("max_drawdown_floor"))
|
||||
low, high = bounds
|
||||
if abs(high - low) < 1e-9:
|
||||
return f"${high:,.2f}"
|
||||
return f"${low:,.2f}–${high:,.2f}"
|
||||
|
||||
def _format_cap(self, strategy: dict[str, Any]) -> str:
|
||||
cap = strategy_upside_cap_price(strategy, current_spot=self.current_spot)
|
||||
if cap is None:
|
||||
return self._fmt_optional_money(strategy.get("upside_cap"))
|
||||
return f"${cap:,.2f}"
|
||||
|
||||
@staticmethod
|
||||
def _fmt_optional_money(value: Any) -> str:
|
||||
|
||||
Reference in New Issue
Block a user