feat(PORT-001A): add collateral entry basis settings
This commit is contained in:
@@ -29,4 +29,13 @@ def test_homepage_and_options_page_render() -> None:
|
||||
assert "RuntimeError" not in body_text
|
||||
page.screenshot(path=str(ARTIFACTS / "options.png"), full_page=True)
|
||||
|
||||
page.goto(f"{BASE_URL}/settings", wait_until="domcontentloaded", timeout=30000)
|
||||
expect(page.locator("text=Settings").first).to_be_visible(timeout=15000)
|
||||
expect(page.locator("text=Collateral entry basis").first).to_be_visible(timeout=15000)
|
||||
expect(page.locator("text=Entry price ($/oz)").first).to_be_visible(timeout=15000)
|
||||
settings_text = page.locator("body").inner_text(timeout=15000)
|
||||
assert "RuntimeError" not in settings_text
|
||||
assert "Server error" not in settings_text
|
||||
page.screenshot(path=str(ARTIFACTS / "settings.png"), full_page=True)
|
||||
|
||||
browser.close()
|
||||
|
||||
@@ -2,6 +2,8 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from app.models.portfolio import PortfolioConfig
|
||||
|
||||
|
||||
def test_ltv_calculation(sample_portfolio) -> None:
|
||||
assert sample_portfolio.current_ltv == pytest.approx(0.60, rel=1e-12)
|
||||
@@ -20,3 +22,57 @@ def test_net_equity_calculation(sample_portfolio) -> None:
|
||||
def test_margin_call_threshold(sample_portfolio) -> None:
|
||||
assert sample_portfolio.margin_call_price() == pytest.approx(368.0, rel=1e-12)
|
||||
assert sample_portfolio.ltv_at_price(sample_portfolio.margin_call_price()) == pytest.approx(0.75, rel=1e-12)
|
||||
|
||||
|
||||
def test_portfolio_config_derives_gold_weight_from_start_value_and_entry_price() -> None:
|
||||
config = PortfolioConfig(
|
||||
gold_value=215_000.0,
|
||||
entry_price=215.0,
|
||||
entry_basis_mode="value_price",
|
||||
)
|
||||
|
||||
assert config.gold_ounces == pytest.approx(1_000.0, rel=1e-12)
|
||||
assert config.gold_value == pytest.approx(215_000.0, rel=1e-12)
|
||||
assert config.entry_basis_mode == "value_price"
|
||||
|
||||
|
||||
def test_portfolio_config_derives_start_value_from_gold_weight_and_entry_price() -> None:
|
||||
config = PortfolioConfig(
|
||||
gold_ounces=1_000.0,
|
||||
entry_price=215.0,
|
||||
entry_basis_mode="weight",
|
||||
)
|
||||
|
||||
assert config.gold_value == pytest.approx(215_000.0, rel=1e-12)
|
||||
assert config.gold_ounces == pytest.approx(1_000.0, rel=1e-12)
|
||||
assert config.entry_basis_mode == "weight"
|
||||
|
||||
|
||||
def test_portfolio_config_serializes_canonical_entry_basis_fields() -> None:
|
||||
config = PortfolioConfig(gold_value=215_000.0, entry_price=215.0)
|
||||
|
||||
data = config.to_dict()
|
||||
|
||||
assert data["gold_value"] == pytest.approx(215_000.0, rel=1e-12)
|
||||
assert data["gold_ounces"] == pytest.approx(1_000.0, rel=1e-12)
|
||||
assert data["entry_price"] == pytest.approx(215.0, rel=1e-12)
|
||||
assert PortfolioConfig.from_dict(data).gold_ounces == pytest.approx(1_000.0, rel=1e-12)
|
||||
|
||||
|
||||
def test_portfolio_config_keeps_legacy_gold_value_payloads_compatible() -> None:
|
||||
config = PortfolioConfig.from_dict({"gold_value": 215_000.0, "loan_amount": 145_000.0})
|
||||
|
||||
assert config.gold_value == pytest.approx(215_000.0, rel=1e-12)
|
||||
assert config.entry_price == pytest.approx(215.0, rel=1e-12)
|
||||
assert config.gold_ounces == pytest.approx(1_000.0, rel=1e-12)
|
||||
|
||||
|
||||
def test_portfolio_config_rejects_invalid_entry_basis_values() -> None:
|
||||
with pytest.raises(ValueError, match="Entry price must be positive"):
|
||||
PortfolioConfig(entry_price=0.0)
|
||||
|
||||
with pytest.raises(ValueError, match="Gold weight must be positive"):
|
||||
PortfolioConfig(gold_ounces=-1.0, entry_price=215.0, entry_basis_mode="weight")
|
||||
|
||||
with pytest.raises(ValueError, match="Gold value and weight contradict each other"):
|
||||
PortfolioConfig(gold_value=215_000.0, gold_ounces=900.0, entry_price=215.0)
|
||||
|
||||
Reference in New Issue
Block a user