feat(CORE-001C): type historical unit materialization
This commit is contained in:
99
tests/test_backtesting_units.py
Normal file
99
tests/test_backtesting_units.py
Normal 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")
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user