75 lines
2.4 KiB
Python
75 lines
2.4 KiB
Python
|
|
from datetime import UTC, date, datetime
|
||
|
|
from decimal import Decimal
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from broker_sync.fx import FxCache
|
||
|
|
from broker_sync.models import AccountType, Activity, ActivityType, FxRateSource
|
||
|
|
from broker_sync.normaliser import normalise_to_gbp
|
||
|
|
|
||
|
|
|
||
|
|
def _buy_usd(amount_usd: Decimal = Decimal("100")) -> Activity:
|
||
|
|
return Activity(
|
||
|
|
external_id="schwab:1",
|
||
|
|
account_id="schwab-rsu",
|
||
|
|
account_type=AccountType.GIA,
|
||
|
|
date=datetime(2026, 4, 1, tzinfo=UTC),
|
||
|
|
activity_type=ActivityType.BUY,
|
||
|
|
symbol="META",
|
||
|
|
quantity=Decimal("1"),
|
||
|
|
unit_price=amount_usd,
|
||
|
|
currency="USD",
|
||
|
|
fee=Decimal("0"),
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_gbp_activity_unchanged(tmp_path: Path) -> None:
|
||
|
|
cache = FxCache(tmp_path / "fx.db")
|
||
|
|
a = Activity(
|
||
|
|
external_id="t212:1",
|
||
|
|
account_id="t212-isa",
|
||
|
|
account_type=AccountType.ISA,
|
||
|
|
date=datetime(2026, 4, 1, tzinfo=UTC),
|
||
|
|
activity_type=ActivityType.BUY,
|
||
|
|
symbol="VUAG",
|
||
|
|
quantity=Decimal("1"),
|
||
|
|
unit_price=Decimal("100"),
|
||
|
|
currency="GBP",
|
||
|
|
)
|
||
|
|
out = normalise_to_gbp(a, cache=cache)
|
||
|
|
assert out.amount_gbp == Decimal("100")
|
||
|
|
assert out.fx_rate_gbp == Decimal("1")
|
||
|
|
assert out.fx_rate_source is FxRateSource.ECB_LIVE
|
||
|
|
|
||
|
|
|
||
|
|
def test_usd_buy_converts_using_cache(tmp_path: Path) -> None:
|
||
|
|
cache = FxCache(tmp_path / "fx.db")
|
||
|
|
cache.put("USD", date(2026, 4, 1), Decimal("0.80"), FxRateSource.ECB_LIVE)
|
||
|
|
out = normalise_to_gbp(_buy_usd(Decimal("100")), cache=cache)
|
||
|
|
assert out.amount_gbp == Decimal("80.00")
|
||
|
|
assert out.fx_rate_gbp == Decimal("0.80")
|
||
|
|
assert out.fx_rate_source is FxRateSource.ECB_LIVE
|
||
|
|
|
||
|
|
|
||
|
|
def test_dividend_amount_drives_gbp(tmp_path: Path) -> None:
|
||
|
|
cache = FxCache(tmp_path / "fx.db")
|
||
|
|
cache.put("USD", date(2026, 4, 1), Decimal("0.80"), FxRateSource.ECB_LIVE)
|
||
|
|
div = Activity(
|
||
|
|
external_id="schwab:div:1",
|
||
|
|
account_id="schwab-rsu",
|
||
|
|
account_type=AccountType.GIA,
|
||
|
|
date=datetime(2026, 4, 1, tzinfo=UTC),
|
||
|
|
activity_type=ActivityType.DIVIDEND,
|
||
|
|
currency="USD",
|
||
|
|
amount=Decimal("50"),
|
||
|
|
)
|
||
|
|
out = normalise_to_gbp(div, cache=cache)
|
||
|
|
assert out.amount_gbp == Decimal("40.00")
|
||
|
|
|
||
|
|
|
||
|
|
def test_missing_rate_raises(tmp_path: Path) -> None:
|
||
|
|
cache = FxCache(tmp_path / "fx.db")
|
||
|
|
with pytest.raises(LookupError):
|
||
|
|
normalise_to_gbp(_buy_usd(), cache=cache)
|