275 lines
9.9 KiB
Python
275 lines
9.9 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import date
|
|
from decimal import Decimal
|
|
|
|
import pytest
|
|
|
|
from app.services.event_comparison_ui import EventComparisonFixtureHistoricalPriceSource, EventComparisonPageService
|
|
|
|
|
|
def test_event_comparison_page_service_accepts_string_and_decimal_boundary_values() -> None:
|
|
service = EventComparisonPageService()
|
|
|
|
preview = service.preview_scenario(
|
|
preset_slug="gld-jan-2024-selloff",
|
|
template_slugs=("protective-put-atm-12m",),
|
|
underlying_units="1000.0",
|
|
loan_amount=Decimal("68000.0"),
|
|
margin_call_ltv="0.75",
|
|
)
|
|
report = service.run_read_only_comparison(
|
|
preset_slug="gld-jan-2024-selloff",
|
|
template_slugs=("protective-put-atm-12m", "protective-put-95pct-12m"),
|
|
underlying_units="1000.0",
|
|
loan_amount=Decimal("68000.0"),
|
|
margin_call_ltv="0.75",
|
|
)
|
|
|
|
assert preview.initial_portfolio.underlying_units == 1000.0
|
|
assert preview.initial_portfolio.loan_amount == 68000.0
|
|
assert preview.initial_portfolio.margin_call_ltv == 0.75
|
|
assert report.scenario.initial_portfolio.underlying_units == 1000.0
|
|
assert report.scenario.initial_portfolio.loan_amount == 68000.0
|
|
assert report.scenario.initial_portfolio.margin_call_ltv == 0.75
|
|
|
|
|
|
def test_event_comparison_page_service_runs_seeded_gld_preset_deterministically() -> None:
|
|
service = EventComparisonPageService()
|
|
|
|
report = service.run_read_only_comparison(
|
|
preset_slug="gld-jan-2024-selloff",
|
|
template_slugs=("protective-put-atm-12m", "protective-put-95pct-12m"),
|
|
underlying_units=1000.0,
|
|
loan_amount=68000.0,
|
|
margin_call_ltv=0.75,
|
|
)
|
|
|
|
assert report.event_preset.slug == "gld-jan-2024-selloff"
|
|
assert report.scenario.start_date.isoformat() == "2024-01-02"
|
|
assert report.scenario.end_date.isoformat() == "2024-01-08"
|
|
assert [item.template_slug for item in report.rankings] == [
|
|
"protective-put-atm-12m",
|
|
"protective-put-95pct-12m",
|
|
]
|
|
assert report.rankings[0].rank == 1
|
|
assert (
|
|
report.rankings[0].result.daily_path[-1].net_portfolio_value
|
|
> report.rankings[-1].result.daily_path[-1].net_portfolio_value
|
|
)
|
|
|
|
|
|
def test_event_comparison_page_service_rejects_empty_template_selection() -> None:
|
|
service = EventComparisonPageService()
|
|
|
|
with pytest.raises(ValueError, match="Select at least one strategy template"):
|
|
service.run_read_only_comparison(
|
|
preset_slug="gld-jan-2024-selloff",
|
|
template_slugs=(),
|
|
underlying_units=1000.0,
|
|
loan_amount=68000.0,
|
|
margin_call_ltv=0.75,
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="Select at least one strategy template"):
|
|
service.preview_scenario(
|
|
preset_slug="gld-jan-2024-selloff",
|
|
template_slugs=(),
|
|
underlying_units=1000.0,
|
|
loan_amount=68000.0,
|
|
margin_call_ltv=0.75,
|
|
)
|
|
|
|
|
|
def test_event_comparison_page_service_exposes_seeded_preset_options() -> None:
|
|
service = EventComparisonPageService()
|
|
|
|
options = service.preset_options("GLD")
|
|
|
|
assert options[0]["slug"] == "gld-jan-2024-selloff"
|
|
assert options[0]["label"] == "GLD January 2024 Selloff"
|
|
assert tuple(options[0]["default_template_slugs"]) == (
|
|
"protective-put-atm-12m",
|
|
"protective-put-95pct-12m",
|
|
"protective-put-90pct-12m",
|
|
"ladder-50-50-atm-95pct-12m",
|
|
)
|
|
|
|
|
|
def test_event_comparison_page_service_keeps_fixture_window_while_using_caller_portfolio_inputs() -> None:
|
|
service = EventComparisonPageService()
|
|
|
|
report = service.run_read_only_comparison(
|
|
preset_slug="gld-jan-2024-selloff",
|
|
template_slugs=("protective-put-atm-12m",),
|
|
underlying_units=9680.0,
|
|
loan_amount=222000.0,
|
|
margin_call_ltv=0.80,
|
|
)
|
|
|
|
assert report.scenario.start_date.isoformat() == "2024-01-02"
|
|
assert report.scenario.end_date.isoformat() == "2024-01-08"
|
|
assert report.scenario.initial_portfolio.entry_spot == 100.0
|
|
assert report.scenario.initial_portfolio.underlying_units == 9680.0
|
|
assert report.scenario.initial_portfolio.loan_amount == 222000.0
|
|
assert report.scenario.initial_portfolio.margin_call_ltv == 0.80
|
|
|
|
|
|
def test_event_comparison_page_service_resets_template_selection_to_preset_defaults() -> None:
|
|
service = EventComparisonPageService()
|
|
|
|
assert service.default_template_selection("gld-jan-2024-selloff") == (
|
|
"protective-put-atm-12m",
|
|
"protective-put-95pct-12m",
|
|
"protective-put-90pct-12m",
|
|
"ladder-50-50-atm-95pct-12m",
|
|
)
|
|
assert service.default_template_selection("gld-jan-2024-drawdown") == (
|
|
"protective-put-atm-12m",
|
|
"ladder-50-50-atm-95pct-12m",
|
|
"ladder-33-33-33-atm-95pct-90pct-12m",
|
|
)
|
|
|
|
|
|
def test_event_comparison_page_service_preview_uses_same_materialization_path() -> None:
|
|
service = EventComparisonPageService()
|
|
|
|
scenario = service.preview_scenario(
|
|
preset_slug="gld-jan-2024-selloff",
|
|
template_slugs=("protective-put-atm-12m",),
|
|
underlying_units=1000.0,
|
|
loan_amount=68000.0,
|
|
margin_call_ltv=0.75,
|
|
)
|
|
|
|
assert scenario.start_date.isoformat() == "2024-01-02"
|
|
assert scenario.end_date.isoformat() == "2024-01-08"
|
|
assert scenario.initial_portfolio.entry_spot == 100.0
|
|
assert [ref.slug for ref in scenario.template_refs] == ["protective-put-atm-12m"]
|
|
|
|
|
|
def test_event_comparison_page_service_derives_entry_spot_without_caller_collateral_validation() -> None:
|
|
service = EventComparisonPageService()
|
|
|
|
entry_spot = service.derive_entry_spot(
|
|
preset_slug="gld-jan-2024-drawdown",
|
|
template_slugs=(
|
|
"protective-put-atm-12m",
|
|
"ladder-50-50-atm-95pct-12m",
|
|
"ladder-33-33-33-atm-95pct-90pct-12m",
|
|
),
|
|
)
|
|
|
|
assert entry_spot == 100.0
|
|
|
|
|
|
def test_event_comparison_page_service_rejects_undercollateralized_historical_start() -> None:
|
|
service = EventComparisonPageService()
|
|
|
|
with pytest.raises(ValueError, match="Historical scenario starts undercollateralized"):
|
|
service.preview_scenario(
|
|
preset_slug="gld-jan-2024-selloff",
|
|
template_slugs=("protective-put-atm-12m",),
|
|
underlying_units=1000.0,
|
|
loan_amount=145000.0,
|
|
margin_call_ltv=0.75,
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="Historical scenario starts undercollateralized"):
|
|
service.run_read_only_comparison(
|
|
preset_slug="gld-jan-2024-selloff",
|
|
template_slugs=("protective-put-atm-12m",),
|
|
underlying_units=1000.0,
|
|
loan_amount=145000.0,
|
|
margin_call_ltv=0.75,
|
|
)
|
|
|
|
|
|
def test_event_comparison_fixture_fails_closed_for_unsupported_range() -> None:
|
|
source = EventComparisonFixtureHistoricalPriceSource()
|
|
|
|
with pytest.raises(ValueError, match="seeded 2024-01-02 through 2024-01-08"):
|
|
source.load_daily_closes("GLD", date(2024, 1, 1), date(2024, 1, 8))
|
|
|
|
|
|
def test_event_comparison_page_service_builds_chart_model_with_unhedged_reference() -> None:
|
|
service = EventComparisonPageService()
|
|
|
|
report = service.run_read_only_comparison(
|
|
preset_slug="gld-jan-2024-selloff",
|
|
template_slugs=("protective-put-atm-12m", "protective-put-95pct-12m"),
|
|
underlying_units=1000.0,
|
|
loan_amount=68000.0,
|
|
margin_call_ltv=0.75,
|
|
)
|
|
chart_model = service.chart_model(report)
|
|
|
|
assert chart_model.dates == (
|
|
"2024-01-02",
|
|
"2024-01-03",
|
|
"2024-01-04",
|
|
"2024-01-05",
|
|
"2024-01-08",
|
|
)
|
|
assert chart_model.series[0].name == "Unhedged collateral baseline"
|
|
assert chart_model.series[1].name == "Protective Put ATM"
|
|
assert len(chart_model.series[0].values) == len(chart_model.dates)
|
|
|
|
|
|
def test_event_comparison_page_service_builds_drilldown_for_selected_ranking() -> None:
|
|
service = EventComparisonPageService()
|
|
|
|
report = service.run_read_only_comparison(
|
|
preset_slug="gld-jan-2024-selloff",
|
|
template_slugs=("protective-put-atm-12m", "protective-put-95pct-12m"),
|
|
underlying_units=1000.0,
|
|
loan_amount=68000.0,
|
|
margin_call_ltv=0.75,
|
|
)
|
|
drilldown = service.drilldown_model(report, template_slug="protective-put-95pct-12m")
|
|
|
|
assert drilldown.rank == 2
|
|
assert drilldown.template_name == "Protective Put 95%"
|
|
assert drilldown.margin_call_days_hedged == report.rankings[1].margin_call_days_hedged
|
|
assert drilldown.hedge_cost == report.rankings[1].hedge_cost
|
|
assert drilldown.final_equity == report.rankings[1].final_equity
|
|
assert (
|
|
drilldown.total_option_payoff_realized == report.rankings[1].result.summary_metrics.total_option_payoff_realized
|
|
)
|
|
assert drilldown.worst_ltv_date == "2024-01-08"
|
|
assert drilldown.rows[0].date == "2024-01-02"
|
|
assert drilldown.rows[-1].date == "2024-01-08"
|
|
|
|
|
|
def test_event_comparison_page_service_defaults_drilldown_to_top_ranked_strategy() -> None:
|
|
service = EventComparisonPageService()
|
|
|
|
report = service.run_read_only_comparison(
|
|
preset_slug="gld-jan-2024-selloff",
|
|
template_slugs=("protective-put-atm-12m", "protective-put-95pct-12m"),
|
|
underlying_units=1000.0,
|
|
loan_amount=68000.0,
|
|
margin_call_ltv=0.75,
|
|
)
|
|
|
|
drilldown = service.drilldown_model(report)
|
|
|
|
assert drilldown.rank == 1
|
|
assert drilldown.template_slug == report.rankings[0].template_slug
|
|
assert drilldown.template_name == report.rankings[0].template_name
|
|
|
|
|
|
def test_event_comparison_page_service_rejects_unknown_drilldown_template_slug() -> None:
|
|
service = EventComparisonPageService()
|
|
|
|
report = service.run_read_only_comparison(
|
|
preset_slug="gld-jan-2024-selloff",
|
|
template_slugs=("protective-put-atm-12m",),
|
|
underlying_units=1000.0,
|
|
loan_amount=68000.0,
|
|
margin_call_ltv=0.75,
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="Unknown ranked template"):
|
|
service.drilldown_model(report, template_slug="missing-template")
|