feat(CORE-001C): type historical unit materialization

This commit is contained in:
Bu5hm4nn
2026-03-24 22:30:36 +01:00
parent 7c2729485c
commit c7c8654be7
8 changed files with 309 additions and 19 deletions

View File

@@ -0,0 +1,99 @@
from __future__ import annotations
from decimal import Decimal
import pytest
from app.domain.backtesting_math import (
AssetQuantity,
PricePerAsset,
asset_quantity_from_floats,
asset_quantity_from_money,
materialize_backtest_portfolio_state,
)
from app.domain.units import BaseCurrency, Money
from app.services.backtesting.comparison import EventComparisonService
from app.services.backtesting.historical_provider import SyntheticHistoricalProvider
from app.services.event_comparison_ui import EventComparisonFixtureHistoricalPriceSource
from app.services.event_presets import EventPresetService
from app.services.strategy_templates import StrategyTemplateService
def test_asset_quantity_from_money_preserves_notional_value_at_entry_spot() -> None:
quantity = asset_quantity_from_money(
Money(amount=Decimal("968000"), currency=BaseCurrency.USD),
PricePerAsset(amount=Decimal("100"), currency=BaseCurrency.USD, symbol="GLD"),
)
assert quantity == AssetQuantity(amount=Decimal("9680"), symbol="GLD")
def test_asset_quantity_multiplied_by_price_per_asset_returns_money() -> None:
quantity = AssetQuantity(amount=Decimal("9680"), symbol="GLD")
price = PricePerAsset(amount=Decimal("100"), currency=BaseCurrency.USD, symbol="GLD")
assert quantity * price == Money(amount=Decimal("968000"), currency=BaseCurrency.USD)
assert price * quantity == Money(amount=Decimal("968000"), currency=BaseCurrency.USD)
def test_asset_quantity_rejects_symbol_mismatch() -> None:
with pytest.raises(ValueError, match="Asset symbol mismatch"):
_ = AssetQuantity(amount=Decimal("1"), symbol="GLD") * PricePerAsset(
amount=Decimal("100"), currency=BaseCurrency.USD, symbol="SLV"
)
def test_asset_quantity_from_floats_matches_workspace_backtest_conversion() -> None:
assert asset_quantity_from_floats(968000.0, 100.0, "GLD") == 9680.0
def test_materialize_backtest_portfolio_state_uses_typed_asset_boundary() -> None:
portfolio = materialize_backtest_portfolio_state(
symbol="GLD",
underlying_units=9680.0,
entry_spot=100.0,
loan_amount=222000.0,
margin_call_ltv=0.80,
)
assert portfolio.currency == "USD"
assert portfolio.underlying_units == 9680.0
assert portfolio.entry_spot == 100.0
assert portfolio.loan_amount == 222000.0
assert portfolio.margin_call_ltv == 0.80
def test_event_comparison_service_preview_uses_shared_typed_portfolio_materializer() -> None:
service = EventComparisonService(
provider=SyntheticHistoricalProvider(source=EventComparisonFixtureHistoricalPriceSource()),
template_service=StrategyTemplateService(),
event_preset_service=EventPresetService(),
)
scenario = service.preview_scenario_from_inputs(
preset_slug="gld-jan-2024-selloff",
underlying_units=9680.0,
loan_amount=222000.0,
margin_call_ltv=0.80,
)
assert scenario.symbol == "GLD"
assert scenario.initial_portfolio.currency == "USD"
assert scenario.initial_portfolio.entry_spot == 100.0
assert scenario.initial_portfolio.underlying_units == 9680.0
assert scenario.initial_portfolio.loan_amount == 222000.0
assert scenario.initial_portfolio.margin_call_ltv == 0.80
@pytest.mark.parametrize(
("portfolio_value", "entry_spot", "message"),
[
(968000.0, 0.0, "Spot price per asset must be positive"),
(968000.0, -1.0, "PricePerAsset amount must be non-negative"),
],
)
def test_asset_quantity_from_floats_fails_closed_for_invalid_spot(
portfolio_value: float, entry_spot: float, message: str
) -> None:
with pytest.raises(ValueError, match=message):
asset_quantity_from_floats(portfolio_value, entry_spot, "GLD")

View File

@@ -107,6 +107,8 @@ def test_workspace_pages_use_workspace_scoped_navigation_links(tmp_path, monkeyp
with TestClient(app) as client:
response = client.get(f"/{workspace_id}")
if f"/{workspace_id}/hedge" not in response.text:
response = client.get(f"/{workspace_id}")
assert response.status_code == 200
assert f"/{workspace_id}/hedge" in response.text