1215 lines
44 KiB
Python
1215 lines
44 KiB
Python
"""Tests for Australian CGT Calculator.
|
|
|
|
Issue #32: [PORT-31] Australian CGT calculator - 50% discount, tax reports
|
|
|
|
Tests cover:
|
|
- CGT enums and dataclasses
|
|
- Acquisition and disposal recording
|
|
- FIFO cost basis matching
|
|
- 50% CGT discount for holdings >12 months
|
|
- Tax year calculations (July-June)
|
|
- Capital loss tracking and carry-forward
|
|
- Tax report generation
|
|
- Foreign currency conversions
|
|
- Edge cases and validation
|
|
"""
|
|
|
|
import pytest
|
|
from datetime import date, timedelta
|
|
from decimal import Decimal
|
|
|
|
from tradingagents.portfolio.tax_calculator import (
|
|
CGTMethod,
|
|
AssetType,
|
|
CGTAssetAcquisition,
|
|
CGTDisposal,
|
|
CGTEvent,
|
|
TaxYearSummary,
|
|
AustralianCGTCalculator,
|
|
)
|
|
|
|
|
|
# ==============================================================================
|
|
# CGTMethod Enum Tests
|
|
# ==============================================================================
|
|
|
|
|
|
class TestCGTMethod:
|
|
"""Tests for CGTMethod enum."""
|
|
|
|
def test_discount_value(self):
|
|
"""Test DISCOUNT method value."""
|
|
assert CGTMethod.DISCOUNT.value == "discount"
|
|
|
|
def test_indexation_value(self):
|
|
"""Test INDEXATION method value."""
|
|
assert CGTMethod.INDEXATION.value == "indexation"
|
|
|
|
def test_other_value(self):
|
|
"""Test OTHER method value."""
|
|
assert CGTMethod.OTHER.value == "other"
|
|
|
|
def test_all_methods_exist(self):
|
|
"""Test all expected methods exist."""
|
|
methods = [m for m in CGTMethod]
|
|
assert len(methods) == 3
|
|
assert CGTMethod.DISCOUNT in methods
|
|
assert CGTMethod.INDEXATION in methods
|
|
assert CGTMethod.OTHER in methods
|
|
|
|
|
|
# ==============================================================================
|
|
# AssetType Enum Tests
|
|
# ==============================================================================
|
|
|
|
|
|
class TestAssetType:
|
|
"""Tests for AssetType enum."""
|
|
|
|
def test_shares_value(self):
|
|
"""Test SHARES type value."""
|
|
assert AssetType.SHARES.value == "shares"
|
|
|
|
def test_foreign_shares_value(self):
|
|
"""Test FOREIGN_SHARES type value."""
|
|
assert AssetType.FOREIGN_SHARES.value == "foreign_shares"
|
|
|
|
def test_etf_value(self):
|
|
"""Test ETF type value."""
|
|
assert AssetType.ETF.value == "etf"
|
|
|
|
def test_cryptocurrency_value(self):
|
|
"""Test CRYPTOCURRENCY type value."""
|
|
assert AssetType.CRYPTOCURRENCY.value == "cryptocurrency"
|
|
|
|
def test_property_value(self):
|
|
"""Test PROPERTY type value."""
|
|
assert AssetType.PROPERTY.value == "property"
|
|
|
|
def test_collectables_value(self):
|
|
"""Test COLLECTABLES type value."""
|
|
assert AssetType.COLLECTABLES.value == "collectables"
|
|
|
|
def test_other_value(self):
|
|
"""Test OTHER type value."""
|
|
assert AssetType.OTHER.value == "other"
|
|
|
|
def test_all_types_exist(self):
|
|
"""Test all expected asset types exist."""
|
|
types = [t for t in AssetType]
|
|
assert len(types) == 7
|
|
|
|
|
|
# ==============================================================================
|
|
# CGTAssetAcquisition Tests
|
|
# ==============================================================================
|
|
|
|
|
|
class TestCGTAssetAcquisition:
|
|
"""Tests for CGTAssetAcquisition dataclass."""
|
|
|
|
def test_create_basic_acquisition(self):
|
|
"""Test creating a basic AUD acquisition."""
|
|
acq = CGTAssetAcquisition(
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("95.50"),
|
|
total_cost_aud=Decimal("9550"),
|
|
)
|
|
assert acq.acquisition_date == date(2023, 1, 15)
|
|
assert acq.quantity == Decimal("100")
|
|
assert acq.cost_per_unit == Decimal("95.50")
|
|
assert acq.total_cost_aud == Decimal("9550")
|
|
assert acq.currency == "AUD"
|
|
assert acq.exchange_rate is None
|
|
assert acq.incidental_costs == Decimal("0")
|
|
|
|
def test_acquisition_with_incidental_costs(self):
|
|
"""Test acquisition with brokerage fees."""
|
|
acq = CGTAssetAcquisition(
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("95.50"),
|
|
total_cost_aud=Decimal("9550"),
|
|
incidental_costs=Decimal("19.95"),
|
|
)
|
|
assert acq.incidental_costs == Decimal("19.95")
|
|
assert acq.total_cost_base == Decimal("9569.95")
|
|
|
|
def test_cost_base_per_unit(self):
|
|
"""Test cost base per unit calculation."""
|
|
acq = CGTAssetAcquisition(
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("95.50"),
|
|
total_cost_aud=Decimal("9550"),
|
|
incidental_costs=Decimal("50"),
|
|
)
|
|
# (9550 + 50) / 100 = 96.00
|
|
assert acq.cost_base_per_unit == Decimal("96")
|
|
|
|
def test_cost_base_per_unit_zero_quantity(self):
|
|
"""Test cost base per unit with zero quantity."""
|
|
acq = CGTAssetAcquisition(
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("0"),
|
|
cost_per_unit=Decimal("95.50"),
|
|
total_cost_aud=Decimal("0"),
|
|
)
|
|
assert acq.cost_base_per_unit == Decimal("0")
|
|
|
|
def test_foreign_acquisition(self):
|
|
"""Test foreign currency acquisition."""
|
|
acq = CGTAssetAcquisition(
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("50"),
|
|
cost_per_unit=Decimal("150"), # USD
|
|
total_cost_aud=Decimal("11250"), # After AUD conversion
|
|
currency="USD",
|
|
exchange_rate=Decimal("1.50"),
|
|
)
|
|
assert acq.currency == "USD"
|
|
assert acq.exchange_rate == Decimal("1.50")
|
|
assert acq.total_cost_aud == Decimal("11250")
|
|
|
|
|
|
# ==============================================================================
|
|
# CGTDisposal Tests
|
|
# ==============================================================================
|
|
|
|
|
|
class TestCGTDisposal:
|
|
"""Tests for CGTDisposal dataclass."""
|
|
|
|
def test_create_basic_disposal(self):
|
|
"""Test creating a basic disposal."""
|
|
disposal = CGTDisposal(
|
|
disposal_date=date(2024, 3, 20),
|
|
symbol="CBA.AX",
|
|
quantity=Decimal("50"),
|
|
proceeds_per_unit=Decimal("110.25"),
|
|
total_proceeds_aud=Decimal("5512.50"),
|
|
)
|
|
assert disposal.disposal_date == date(2024, 3, 20)
|
|
assert disposal.symbol == "CBA.AX"
|
|
assert disposal.quantity == Decimal("50")
|
|
assert disposal.total_proceeds_aud == Decimal("5512.50")
|
|
|
|
def test_net_proceeds_calculation(self):
|
|
"""Test net proceeds after selling costs."""
|
|
disposal = CGTDisposal(
|
|
disposal_date=date(2024, 3, 20),
|
|
symbol="CBA.AX",
|
|
quantity=Decimal("50"),
|
|
proceeds_per_unit=Decimal("110.25"),
|
|
total_proceeds_aud=Decimal("5512.50"),
|
|
incidental_costs=Decimal("19.95"),
|
|
)
|
|
assert disposal.net_proceeds == Decimal("5492.55")
|
|
|
|
|
|
# ==============================================================================
|
|
# CGTEvent Tests
|
|
# ==============================================================================
|
|
|
|
|
|
class TestCGTEvent:
|
|
"""Tests for CGTEvent dataclass."""
|
|
|
|
def test_is_gain(self):
|
|
"""Test capital gain detection."""
|
|
disposal = CGTDisposal(
|
|
disposal_date=date(2024, 3, 20),
|
|
symbol="CBA.AX",
|
|
quantity=Decimal("50"),
|
|
proceeds_per_unit=Decimal("110.25"),
|
|
total_proceeds_aud=Decimal("5512.50"),
|
|
)
|
|
event = CGTEvent(
|
|
event_date=date(2024, 3, 20),
|
|
symbol="CBA.AX",
|
|
asset_type=AssetType.SHARES,
|
|
disposal=disposal,
|
|
gross_gain=Decimal("737.50"),
|
|
discount_eligible=True,
|
|
discount_amount=Decimal("368.75"),
|
|
net_gain=Decimal("368.75"),
|
|
holding_period_days=430,
|
|
cgt_method=CGTMethod.DISCOUNT,
|
|
tax_year=2024,
|
|
)
|
|
assert event.is_gain is True
|
|
assert event.is_loss is False
|
|
|
|
def test_is_loss(self):
|
|
"""Test capital loss detection."""
|
|
disposal = CGTDisposal(
|
|
disposal_date=date(2024, 3, 20),
|
|
symbol="CBA.AX",
|
|
quantity=Decimal("50"),
|
|
proceeds_per_unit=Decimal("80"),
|
|
total_proceeds_aud=Decimal("4000"),
|
|
)
|
|
event = CGTEvent(
|
|
event_date=date(2024, 3, 20),
|
|
symbol="CBA.AX",
|
|
asset_type=AssetType.SHARES,
|
|
disposal=disposal,
|
|
gross_gain=Decimal("-775"),
|
|
discount_eligible=False,
|
|
discount_amount=Decimal("0"),
|
|
net_gain=Decimal("-775"),
|
|
holding_period_days=430,
|
|
cgt_method=CGTMethod.OTHER,
|
|
tax_year=2024,
|
|
)
|
|
assert event.is_gain is False
|
|
assert event.is_loss is True
|
|
|
|
|
|
# ==============================================================================
|
|
# TaxYearSummary Tests
|
|
# ==============================================================================
|
|
|
|
|
|
class TestTaxYearSummary:
|
|
"""Tests for TaxYearSummary dataclass."""
|
|
|
|
def test_num_events(self):
|
|
"""Test event count property."""
|
|
summary = TaxYearSummary(
|
|
tax_year=2024,
|
|
start_date=date(2023, 7, 1),
|
|
end_date=date(2024, 6, 30),
|
|
total_gains=Decimal("1000"),
|
|
total_losses=Decimal("200"),
|
|
losses_applied=Decimal("200"),
|
|
carried_forward_losses=Decimal("0"),
|
|
losses_to_carry=Decimal("0"),
|
|
net_capital_gain=Decimal("800"),
|
|
discounted_gains=Decimal("1000"),
|
|
discount_applied=Decimal("400"),
|
|
taxable_gain=Decimal("400"),
|
|
events=[],
|
|
)
|
|
assert summary.num_events == 0
|
|
|
|
def test_num_gains_and_losses(self):
|
|
"""Test gain and loss count properties."""
|
|
disposal1 = CGTDisposal(
|
|
disposal_date=date(2024, 1, 15),
|
|
symbol="CBA.AX",
|
|
quantity=Decimal("50"),
|
|
proceeds_per_unit=Decimal("110"),
|
|
total_proceeds_aud=Decimal("5500"),
|
|
)
|
|
event1 = CGTEvent(
|
|
event_date=date(2024, 1, 15),
|
|
symbol="CBA.AX",
|
|
asset_type=AssetType.SHARES,
|
|
disposal=disposal1,
|
|
gross_gain=Decimal("500"),
|
|
discount_eligible=True,
|
|
discount_amount=Decimal("250"),
|
|
net_gain=Decimal("250"),
|
|
holding_period_days=400,
|
|
cgt_method=CGTMethod.DISCOUNT,
|
|
tax_year=2024,
|
|
)
|
|
disposal2 = CGTDisposal(
|
|
disposal_date=date(2024, 2, 15),
|
|
symbol="NAB.AX",
|
|
quantity=Decimal("50"),
|
|
proceeds_per_unit=Decimal("25"),
|
|
total_proceeds_aud=Decimal("1250"),
|
|
)
|
|
event2 = CGTEvent(
|
|
event_date=date(2024, 2, 15),
|
|
symbol="NAB.AX",
|
|
asset_type=AssetType.SHARES,
|
|
disposal=disposal2,
|
|
gross_gain=Decimal("-250"),
|
|
discount_eligible=False,
|
|
discount_amount=Decimal("0"),
|
|
net_gain=Decimal("-250"),
|
|
holding_period_days=100,
|
|
cgt_method=CGTMethod.OTHER,
|
|
tax_year=2024,
|
|
)
|
|
summary = TaxYearSummary(
|
|
tax_year=2024,
|
|
start_date=date(2023, 7, 1),
|
|
end_date=date(2024, 6, 30),
|
|
total_gains=Decimal("500"),
|
|
total_losses=Decimal("250"),
|
|
losses_applied=Decimal("250"),
|
|
carried_forward_losses=Decimal("0"),
|
|
losses_to_carry=Decimal("0"),
|
|
net_capital_gain=Decimal("250"),
|
|
discounted_gains=Decimal("500"),
|
|
discount_applied=Decimal("125"),
|
|
taxable_gain=Decimal("125"),
|
|
events=[event1, event2],
|
|
)
|
|
assert summary.num_events == 2
|
|
assert summary.num_gains == 1
|
|
assert summary.num_losses == 1
|
|
|
|
|
|
# ==============================================================================
|
|
# AustralianCGTCalculator - Tax Year Tests
|
|
# ==============================================================================
|
|
|
|
|
|
class TestTaxYearCalculations:
|
|
"""Tests for Australian tax year calculations."""
|
|
|
|
def test_tax_year_july_date(self):
|
|
"""Test tax year for July date (start of new FY)."""
|
|
# July 1, 2023 is in FY2023-24 (tax year 2024)
|
|
assert AustralianCGTCalculator.get_tax_year(date(2023, 7, 1)) == 2024
|
|
|
|
def test_tax_year_june_date(self):
|
|
"""Test tax year for June date (end of FY)."""
|
|
# June 30, 2024 is in FY2023-24 (tax year 2024)
|
|
assert AustralianCGTCalculator.get_tax_year(date(2024, 6, 30)) == 2024
|
|
|
|
def test_tax_year_january_date(self):
|
|
"""Test tax year for January date."""
|
|
# January 15, 2024 is in FY2023-24 (tax year 2024)
|
|
assert AustralianCGTCalculator.get_tax_year(date(2024, 1, 15)) == 2024
|
|
|
|
def test_tax_year_december_date(self):
|
|
"""Test tax year for December date."""
|
|
# December 15, 2023 is in FY2023-24 (tax year 2024)
|
|
assert AustralianCGTCalculator.get_tax_year(date(2023, 12, 15)) == 2024
|
|
|
|
def test_tax_year_dates(self):
|
|
"""Test getting tax year start and end dates."""
|
|
start, end = AustralianCGTCalculator.get_tax_year_dates(2024)
|
|
assert start == date(2023, 7, 1)
|
|
assert end == date(2024, 6, 30)
|
|
|
|
|
|
# ==============================================================================
|
|
# AustralianCGTCalculator - Acquisition Tests
|
|
# ==============================================================================
|
|
|
|
|
|
class TestAcquisitions:
|
|
"""Tests for asset acquisition recording."""
|
|
|
|
def test_add_basic_acquisition(self):
|
|
"""Test adding a basic AUD acquisition."""
|
|
calculator = AustralianCGTCalculator()
|
|
acq = calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("95.50"),
|
|
)
|
|
assert acq.quantity == Decimal("100")
|
|
assert acq.cost_per_unit == Decimal("95.50")
|
|
assert acq.total_cost_aud == Decimal("9550")
|
|
assert calculator.get_holding_quantity("CBA.AX") == Decimal("100")
|
|
|
|
def test_add_acquisition_with_brokerage(self):
|
|
"""Test adding acquisition with incidental costs."""
|
|
calculator = AustralianCGTCalculator()
|
|
acq = calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("95.50"),
|
|
incidental_costs=Decimal("19.95"),
|
|
)
|
|
assert acq.incidental_costs == Decimal("19.95")
|
|
assert acq.total_cost_base == Decimal("9569.95")
|
|
|
|
def test_add_foreign_acquisition(self):
|
|
"""Test adding a foreign currency acquisition."""
|
|
calculator = AustralianCGTCalculator()
|
|
acq = calculator.add_acquisition(
|
|
symbol="AAPL",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("50"),
|
|
cost_per_unit=Decimal("150"), # USD
|
|
currency="USD",
|
|
exchange_rate=Decimal("1.50"), # AUD per USD
|
|
)
|
|
assert acq.currency == "USD"
|
|
assert acq.exchange_rate == Decimal("1.50")
|
|
# 50 * 150 * 1.50 = 11250
|
|
assert acq.total_cost_aud == Decimal("11250")
|
|
|
|
def test_add_multiple_acquisitions_same_symbol(self):
|
|
"""Test adding multiple parcels of same asset."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("95.50"),
|
|
)
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 6, 20),
|
|
quantity=Decimal("50"),
|
|
cost_per_unit=Decimal("100"),
|
|
)
|
|
assert calculator.get_holding_quantity("CBA.AX") == Decimal("150")
|
|
holdings = calculator.get_holdings("CBA.AX")
|
|
assert len(holdings) == 2
|
|
# Should be sorted by acquisition date (FIFO)
|
|
assert holdings[0].acquisition_date == date(2023, 1, 15)
|
|
assert holdings[1].acquisition_date == date(2023, 6, 20)
|
|
|
|
def test_acquisition_with_asset_type(self):
|
|
"""Test setting asset type on acquisition."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="BTC",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("0.5"),
|
|
cost_per_unit=Decimal("30000"),
|
|
asset_type=AssetType.CRYPTOCURRENCY,
|
|
)
|
|
assert calculator.get_asset_type("BTC") == AssetType.CRYPTOCURRENCY
|
|
|
|
def test_acquisition_invalid_quantity(self):
|
|
"""Test acquisition with invalid quantity."""
|
|
calculator = AustralianCGTCalculator()
|
|
with pytest.raises(ValueError, match="Quantity must be positive"):
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("0"),
|
|
cost_per_unit=Decimal("95.50"),
|
|
)
|
|
|
|
def test_acquisition_negative_quantity(self):
|
|
"""Test acquisition with negative quantity."""
|
|
calculator = AustralianCGTCalculator()
|
|
with pytest.raises(ValueError, match="Quantity must be positive"):
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("-10"),
|
|
cost_per_unit=Decimal("95.50"),
|
|
)
|
|
|
|
def test_foreign_acquisition_missing_exchange_rate(self):
|
|
"""Test foreign acquisition without exchange rate."""
|
|
calculator = AustralianCGTCalculator()
|
|
with pytest.raises(ValueError, match="Exchange rate required"):
|
|
calculator.add_acquisition(
|
|
symbol="AAPL",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("50"),
|
|
cost_per_unit=Decimal("150"),
|
|
currency="USD",
|
|
)
|
|
|
|
|
|
# ==============================================================================
|
|
# AustralianCGTCalculator - Disposal Tests
|
|
# ==============================================================================
|
|
|
|
|
|
class TestDisposals:
|
|
"""Tests for asset disposal and CGT calculation."""
|
|
|
|
def test_basic_disposal_with_gain(self):
|
|
"""Test basic disposal with capital gain."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("95.50"),
|
|
)
|
|
event = calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2023, 6, 20), # Less than 12 months
|
|
quantity=Decimal("50"),
|
|
proceeds_per_unit=Decimal("110.25"),
|
|
)
|
|
assert event.symbol == "CBA.AX"
|
|
assert event.disposal.quantity == Decimal("50")
|
|
# Proceeds: 50 * 110.25 = 5512.50
|
|
# Cost: 50 * 95.50 = 4775
|
|
# Gain: 5512.50 - 4775 = 737.50
|
|
assert event.gross_gain == Decimal("737.50")
|
|
assert event.discount_eligible is False # Less than 12 months
|
|
assert event.net_gain == Decimal("737.50")
|
|
assert event.cgt_method == CGTMethod.OTHER
|
|
|
|
def test_disposal_with_discount(self):
|
|
"""Test disposal eligible for 50% CGT discount."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("95.50"),
|
|
)
|
|
event = calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2024, 3, 20), # More than 12 months
|
|
quantity=Decimal("50"),
|
|
proceeds_per_unit=Decimal("110.25"),
|
|
)
|
|
assert event.discount_eligible is True
|
|
assert event.gross_gain == Decimal("737.50")
|
|
assert event.discount_amount == Decimal("368.75")
|
|
assert event.net_gain == Decimal("368.75")
|
|
assert event.cgt_method == CGTMethod.DISCOUNT
|
|
|
|
def test_disposal_with_loss_no_discount(self):
|
|
"""Test disposal with capital loss (no discount on losses)."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("95.50"),
|
|
)
|
|
event = calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2024, 3, 20), # More than 12 months
|
|
quantity=Decimal("50"),
|
|
proceeds_per_unit=Decimal("80"), # Selling at loss
|
|
)
|
|
# Proceeds: 50 * 80 = 4000
|
|
# Cost: 50 * 95.50 = 4775
|
|
# Loss: 4000 - 4775 = -775
|
|
assert event.gross_gain == Decimal("-775.00")
|
|
assert event.discount_eligible is False # No discount on losses
|
|
assert event.discount_amount == Decimal("0.00")
|
|
assert event.net_gain == Decimal("-775.00")
|
|
assert event.is_loss is True
|
|
|
|
def test_disposal_fifo_matching(self):
|
|
"""Test FIFO cost basis matching."""
|
|
calculator = AustralianCGTCalculator()
|
|
# First parcel at $90
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
# Second parcel at $100
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 6, 20),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("100"),
|
|
)
|
|
# Dispose 150 shares - should use first 100 at $90, then 50 at $100
|
|
event = calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2023, 9, 15),
|
|
quantity=Decimal("150"),
|
|
proceeds_per_unit=Decimal("105"),
|
|
)
|
|
# Proceeds: 150 * 105 = 15750
|
|
# Cost: 100 * 90 + 50 * 100 = 9000 + 5000 = 14000
|
|
# Gain: 15750 - 14000 = 1750
|
|
assert event.gross_gain == Decimal("1750.00")
|
|
# Remaining should be 50 shares at $100
|
|
assert calculator.get_holding_quantity("CBA.AX") == Decimal("50")
|
|
|
|
def test_disposal_partial_parcel(self):
|
|
"""Test partial disposal of a parcel."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("95.50"),
|
|
)
|
|
calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2023, 6, 20),
|
|
quantity=Decimal("30"),
|
|
proceeds_per_unit=Decimal("100"),
|
|
)
|
|
# Remaining should be 70 shares
|
|
assert calculator.get_holding_quantity("CBA.AX") == Decimal("70")
|
|
holdings = calculator.get_holdings("CBA.AX")
|
|
assert len(holdings) == 1
|
|
assert holdings[0].quantity == Decimal("70")
|
|
|
|
def test_disposal_insufficient_holdings(self):
|
|
"""Test disposal with insufficient holdings."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("95.50"),
|
|
)
|
|
with pytest.raises(ValueError, match="Insufficient holdings"):
|
|
calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2023, 6, 20),
|
|
quantity=Decimal("150"),
|
|
proceeds_per_unit=Decimal("100"),
|
|
)
|
|
|
|
def test_disposal_no_holdings(self):
|
|
"""Test disposal with no holdings."""
|
|
calculator = AustralianCGTCalculator()
|
|
with pytest.raises(ValueError, match="Insufficient holdings"):
|
|
calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2023, 6, 20),
|
|
quantity=Decimal("50"),
|
|
proceeds_per_unit=Decimal("100"),
|
|
)
|
|
|
|
def test_disposal_with_selling_costs(self):
|
|
"""Test disposal with incidental (selling) costs."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("95.50"),
|
|
)
|
|
event = calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2023, 6, 20),
|
|
quantity=Decimal("50"),
|
|
proceeds_per_unit=Decimal("110"),
|
|
incidental_costs=Decimal("19.95"),
|
|
)
|
|
# Net proceeds: 50 * 110 - 19.95 = 5480.05
|
|
# Cost: 50 * 95.50 = 4775
|
|
# Gain: 5480.05 - 4775 = 705.05
|
|
assert event.gross_gain == Decimal("705.05")
|
|
|
|
|
|
# ==============================================================================
|
|
# AustralianCGTCalculator - Tax Year Summary Tests
|
|
# ==============================================================================
|
|
|
|
|
|
class TestTaxYearSummary:
|
|
"""Tests for tax year summary calculations."""
|
|
|
|
def test_simple_summary_with_gains(self):
|
|
"""Test summary with only gains."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2022, 7, 1),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2023, 10, 15), # FY2024, >12 months
|
|
quantity=Decimal("100"),
|
|
proceeds_per_unit=Decimal("110"),
|
|
)
|
|
summary = calculator.calculate_tax_year_summary(2024)
|
|
# Gross gain: 100 * (110 - 90) = 2000
|
|
assert summary.total_gains == Decimal("2000.00")
|
|
assert summary.total_losses == Decimal("0.00")
|
|
assert summary.discounted_gains == Decimal("2000.00")
|
|
# Discount: 2000 * 0.50 = 1000
|
|
assert summary.discount_applied == Decimal("1000.00")
|
|
assert summary.taxable_gain == Decimal("1000.00")
|
|
|
|
def test_summary_with_losses_applied(self):
|
|
"""Test summary with losses applied to gains."""
|
|
calculator = AustralianCGTCalculator()
|
|
# Gain asset
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2022, 7, 1),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
# Loss asset
|
|
calculator.add_acquisition(
|
|
symbol="NAB.AX",
|
|
acquisition_date=date(2022, 7, 1),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("35"),
|
|
)
|
|
# Dispose with gain
|
|
calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2023, 10, 15),
|
|
quantity=Decimal("100"),
|
|
proceeds_per_unit=Decimal("110"),
|
|
)
|
|
# Dispose with loss
|
|
calculator.dispose(
|
|
symbol="NAB.AX",
|
|
disposal_date=date(2023, 11, 15),
|
|
quantity=Decimal("100"),
|
|
proceeds_per_unit=Decimal("25"),
|
|
)
|
|
summary = calculator.calculate_tax_year_summary(2024)
|
|
assert summary.total_gains == Decimal("2000.00")
|
|
assert summary.total_losses == Decimal("1000.00")
|
|
assert summary.losses_applied == Decimal("1000.00")
|
|
|
|
def test_summary_with_carried_forward_losses(self):
|
|
"""Test summary with carried forward losses."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.set_carried_forward_losses(Decimal("500"))
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2022, 7, 1),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2023, 10, 15),
|
|
quantity=Decimal("100"),
|
|
proceeds_per_unit=Decimal("110"),
|
|
)
|
|
summary = calculator.calculate_tax_year_summary(2024)
|
|
assert summary.carried_forward_losses == Decimal("500.00")
|
|
# Gross gain 2000 - 500 carried loss = 1500
|
|
# Then 50% discount = 750 taxable
|
|
assert summary.taxable_gain == Decimal("750.00")
|
|
|
|
def test_summary_excess_losses_carry_forward(self):
|
|
"""Test excess losses are carried forward."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2022, 7, 1),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
calculator.add_acquisition(
|
|
symbol="NAB.AX",
|
|
acquisition_date=date(2022, 7, 1),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("50"),
|
|
)
|
|
# Small gain
|
|
calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2023, 10, 15),
|
|
quantity=Decimal("100"),
|
|
proceeds_per_unit=Decimal("95"),
|
|
)
|
|
# Large loss
|
|
calculator.dispose(
|
|
symbol="NAB.AX",
|
|
disposal_date=date(2023, 11, 15),
|
|
quantity=Decimal("100"),
|
|
proceeds_per_unit=Decimal("20"),
|
|
)
|
|
summary = calculator.calculate_tax_year_summary(2024)
|
|
assert summary.total_gains == Decimal("500.00") # 100 * (95-90)
|
|
assert summary.total_losses == Decimal("3000.00") # 100 * (50-20)
|
|
assert summary.taxable_gain == Decimal("0.00")
|
|
assert summary.losses_to_carry == Decimal("2500.00") # 3000 - 500
|
|
|
|
def test_summary_no_events(self):
|
|
"""Test summary with no events for tax year."""
|
|
calculator = AustralianCGTCalculator()
|
|
summary = calculator.calculate_tax_year_summary(2024)
|
|
assert summary.num_events == 0
|
|
assert summary.total_gains == Decimal("0.00")
|
|
assert summary.total_losses == Decimal("0.00")
|
|
assert summary.taxable_gain == Decimal("0.00")
|
|
|
|
|
|
# ==============================================================================
|
|
# AustralianCGTCalculator - Tax Report Tests
|
|
# ==============================================================================
|
|
|
|
|
|
class TestTaxReport:
|
|
"""Tests for tax report generation."""
|
|
|
|
def test_generate_basic_report(self):
|
|
"""Test generating a basic tax report."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2022, 7, 1),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2023, 10, 15),
|
|
quantity=Decimal("100"),
|
|
proceeds_per_unit=Decimal("110"),
|
|
)
|
|
report = calculator.generate_tax_report(2024)
|
|
assert report["tax_year"] == "FY2023-24"
|
|
assert report["period"]["start"] == "2023-07-01"
|
|
assert report["period"]["end"] == "2024-06-30"
|
|
assert "summary" in report
|
|
assert "statistics" in report
|
|
assert "transactions" in report
|
|
|
|
def test_report_summary_values(self):
|
|
"""Test report summary values."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2022, 7, 1),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2023, 10, 15),
|
|
quantity=Decimal("100"),
|
|
proceeds_per_unit=Decimal("110"),
|
|
)
|
|
report = calculator.generate_tax_report(2024)
|
|
summary = report["summary"]
|
|
assert summary["total_capital_gains"] == "2000.00"
|
|
assert summary["total_capital_losses"] == "0.00"
|
|
assert summary["discount_method_gains"] == "2000.00"
|
|
assert summary["cgt_discount_applied"] == "1000.00"
|
|
assert summary["taxable_capital_gain"] == "1000.00"
|
|
|
|
def test_report_without_details(self):
|
|
"""Test report without transaction details."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2022, 7, 1),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2023, 10, 15),
|
|
quantity=Decimal("100"),
|
|
proceeds_per_unit=Decimal("110"),
|
|
)
|
|
report = calculator.generate_tax_report(2024, include_details=False)
|
|
assert "transactions" not in report
|
|
|
|
def test_report_statistics(self):
|
|
"""Test report statistics."""
|
|
calculator = AustralianCGTCalculator()
|
|
# Two gains, one loss
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2022, 7, 1),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
calculator.add_acquisition(
|
|
symbol="BHP.AX",
|
|
acquisition_date=date(2022, 7, 1),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("40"),
|
|
)
|
|
calculator.add_acquisition(
|
|
symbol="NAB.AX",
|
|
acquisition_date=date(2022, 7, 1),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("35"),
|
|
)
|
|
calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2023, 10, 15),
|
|
quantity=Decimal("100"),
|
|
proceeds_per_unit=Decimal("110"),
|
|
)
|
|
calculator.dispose(
|
|
symbol="BHP.AX",
|
|
disposal_date=date(2023, 10, 15),
|
|
quantity=Decimal("100"),
|
|
proceeds_per_unit=Decimal("50"),
|
|
)
|
|
calculator.dispose(
|
|
symbol="NAB.AX",
|
|
disposal_date=date(2023, 10, 15),
|
|
quantity=Decimal("100"),
|
|
proceeds_per_unit=Decimal("25"),
|
|
)
|
|
report = calculator.generate_tax_report(2024)
|
|
stats = report["statistics"]
|
|
assert stats["number_of_disposals"] == 3
|
|
assert stats["number_of_gains"] == 2
|
|
assert stats["number_of_losses"] == 1
|
|
|
|
|
|
# ==============================================================================
|
|
# AustralianCGTCalculator - Unrealised Gains Tests
|
|
# ==============================================================================
|
|
|
|
|
|
class TestUnrealisedGains:
|
|
"""Tests for unrealised gains calculation."""
|
|
|
|
def test_unrealised_gains_calculation(self):
|
|
"""Test calculating unrealised gains."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
result = calculator.get_unrealised_gains({"CBA.AX": Decimal("110")})
|
|
assert result["CBA.AX"]["quantity"] == Decimal("100")
|
|
assert result["CBA.AX"]["cost_base"] == Decimal("9000.00")
|
|
assert result["CBA.AX"]["market_value"] == Decimal("11000.00")
|
|
assert result["CBA.AX"]["unrealised_gain"] == Decimal("2000.00")
|
|
|
|
def test_unrealised_loss_calculation(self):
|
|
"""Test calculating unrealised losses."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
result = calculator.get_unrealised_gains({"CBA.AX": Decimal("80")})
|
|
assert result["CBA.AX"]["unrealised_gain"] == Decimal("-1000.00")
|
|
|
|
def test_unrealised_gains_missing_price(self):
|
|
"""Test unrealised gains with missing price."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
result = calculator.get_unrealised_gains({})
|
|
assert result["CBA.AX"]["market_value"] == Decimal("0.00")
|
|
assert result["CBA.AX"]["unrealised_gain"] == Decimal("0.00")
|
|
|
|
def test_unrealised_gains_multiple_parcels(self):
|
|
"""Test unrealised gains with multiple acquisition parcels."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 6, 15),
|
|
quantity=Decimal("50"),
|
|
cost_per_unit=Decimal("100"),
|
|
)
|
|
result = calculator.get_unrealised_gains({"CBA.AX": Decimal("110")})
|
|
assert result["CBA.AX"]["quantity"] == Decimal("150")
|
|
# Cost: 100 * 90 + 50 * 100 = 14000
|
|
assert result["CBA.AX"]["cost_base"] == Decimal("14000.00")
|
|
# Value: 150 * 110 = 16500
|
|
assert result["CBA.AX"]["market_value"] == Decimal("16500.00")
|
|
assert result["CBA.AX"]["unrealised_gain"] == Decimal("2500.00")
|
|
|
|
|
|
# ==============================================================================
|
|
# AustralianCGTCalculator - Edge Cases Tests
|
|
# ==============================================================================
|
|
|
|
|
|
class TestEdgeCases:
|
|
"""Tests for edge cases and special scenarios."""
|
|
|
|
def test_exactly_365_days_no_discount(self):
|
|
"""Test holding exactly 365 days (not eligible for discount)."""
|
|
calculator = AustralianCGTCalculator()
|
|
acq_date = date(2023, 1, 15)
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=acq_date,
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
# Exactly 365 days later
|
|
disposal_date = acq_date + timedelta(days=365)
|
|
event = calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=disposal_date,
|
|
quantity=Decimal("100"),
|
|
proceeds_per_unit=Decimal("110"),
|
|
)
|
|
# Must be MORE than 365 days for discount
|
|
assert event.discount_eligible is False
|
|
|
|
def test_366_days_eligible_for_discount(self):
|
|
"""Test holding 366 days (eligible for discount)."""
|
|
calculator = AustralianCGTCalculator()
|
|
acq_date = date(2023, 1, 15)
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=acq_date,
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
disposal_date = acq_date + timedelta(days=366)
|
|
event = calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=disposal_date,
|
|
quantity=Decimal("100"),
|
|
proceeds_per_unit=Decimal("110"),
|
|
)
|
|
assert event.discount_eligible is True
|
|
|
|
def test_zero_cost_acquisition(self):
|
|
"""Test acquisition with zero cost (e.g., inheritance)."""
|
|
calculator = AustralianCGTCalculator()
|
|
acq = calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("0"),
|
|
)
|
|
assert acq.total_cost_aud == Decimal("0")
|
|
event = calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2024, 3, 20),
|
|
quantity=Decimal("100"),
|
|
proceeds_per_unit=Decimal("100"),
|
|
)
|
|
# All proceeds are gain
|
|
assert event.gross_gain == Decimal("10000.00")
|
|
|
|
def test_very_small_quantities(self):
|
|
"""Test with very small quantities (fractional shares)."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="BRK.A",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("0.001"),
|
|
cost_per_unit=Decimal("500000"),
|
|
)
|
|
event = calculator.dispose(
|
|
symbol="BRK.A",
|
|
disposal_date=date(2023, 6, 20),
|
|
quantity=Decimal("0.001"),
|
|
proceeds_per_unit=Decimal("510000"),
|
|
)
|
|
assert event.gross_gain == Decimal("10.00")
|
|
|
|
def test_clear_calculator(self):
|
|
"""Test clearing all calculator data."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2023, 1, 15),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
calculator.set_carried_forward_losses(Decimal("1000"))
|
|
calculator.clear()
|
|
assert calculator.get_holding_quantity("CBA.AX") == Decimal("0")
|
|
assert calculator.get_carried_forward_losses() == Decimal("0")
|
|
assert len(calculator.get_events()) == 0
|
|
|
|
def test_set_and_get_asset_type(self):
|
|
"""Test setting and getting asset type."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.set_asset_type("BTC", AssetType.CRYPTOCURRENCY)
|
|
assert calculator.get_asset_type("BTC") == AssetType.CRYPTOCURRENCY
|
|
# Default type for unknown asset
|
|
assert calculator.get_asset_type("UNKNOWN") == AssetType.SHARES
|
|
|
|
def test_invalid_carried_forward_losses(self):
|
|
"""Test setting invalid carried forward losses."""
|
|
calculator = AustralianCGTCalculator()
|
|
with pytest.raises(ValueError, match="non-negative"):
|
|
calculator.set_carried_forward_losses(Decimal("-100"))
|
|
|
|
def test_get_events_all(self):
|
|
"""Test getting all events."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2022, 7, 1),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2023, 10, 15),
|
|
quantity=Decimal("50"),
|
|
proceeds_per_unit=Decimal("110"),
|
|
)
|
|
calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2024, 10, 15),
|
|
quantity=Decimal("50"),
|
|
proceeds_per_unit=Decimal("120"),
|
|
)
|
|
all_events = calculator.get_events()
|
|
assert len(all_events) == 2
|
|
|
|
def test_get_events_filtered_by_year(self):
|
|
"""Test getting events filtered by tax year."""
|
|
calculator = AustralianCGTCalculator()
|
|
calculator.add_acquisition(
|
|
symbol="CBA.AX",
|
|
acquisition_date=date(2022, 7, 1),
|
|
quantity=Decimal("100"),
|
|
cost_per_unit=Decimal("90"),
|
|
)
|
|
calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2023, 10, 15), # FY2024
|
|
quantity=Decimal("50"),
|
|
proceeds_per_unit=Decimal("110"),
|
|
)
|
|
calculator.dispose(
|
|
symbol="CBA.AX",
|
|
disposal_date=date(2024, 10, 15), # FY2025
|
|
quantity=Decimal("50"),
|
|
proceeds_per_unit=Decimal("120"),
|
|
)
|
|
fy2024_events = calculator.get_events(tax_year=2024)
|
|
assert len(fy2024_events) == 1
|
|
fy2025_events = calculator.get_events(tax_year=2025)
|
|
assert len(fy2025_events) == 1
|
|
|
|
|
|
# ==============================================================================
|
|
# Module Import Tests
|
|
# ==============================================================================
|
|
|
|
|
|
class TestModuleImports:
|
|
"""Tests for module imports."""
|
|
|
|
def test_import_from_portfolio_module(self):
|
|
"""Test importing from portfolio module."""
|
|
from tradingagents.portfolio import (
|
|
CGTMethod,
|
|
AssetType,
|
|
CGTAssetAcquisition,
|
|
CGTDisposal,
|
|
CGTEvent,
|
|
TaxYearSummary,
|
|
AustralianCGTCalculator,
|
|
)
|
|
assert CGTMethod is not None
|
|
assert AssetType is not None
|
|
assert CGTAssetAcquisition is not None
|
|
assert CGTDisposal is not None
|
|
assert CGTEvent is not None
|
|
assert TaxYearSummary is not None
|
|
assert AustralianCGTCalculator is not None
|
|
|
|
def test_calculator_constants(self):
|
|
"""Test calculator constants."""
|
|
assert AustralianCGTCalculator.DISCOUNT_RATE == Decimal("0.50")
|
|
assert AustralianCGTCalculator.DISCOUNT_HOLDING_DAYS == 365
|