From 52189a67ada926ba4533f27e025c7c819551cedc Mon Sep 17 00:00:00 2001 From: Jakub Sygnowski Date: Tue, 2 Jul 2024 11:20:30 +0100 Subject: [PATCH 1/2] test for splits --- tests/test_data/calc_test_data.py | 117 ++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/tests/test_data/calc_test_data.py b/tests/test_data/calc_test_data.py index f112b548..3d9b4a2c 100644 --- a/tests/test_data/calc_test_data.py +++ b/tests/test_data/calc_test_data.py @@ -89,6 +89,26 @@ def transaction( ) +def split_transaction( + date: datetime.date, + symbol: str, + quantity: float, +) -> BrokerTransaction: + """Create sell transaction.""" + return BrokerTransaction( + date, + ActionType.STOCK_SPLIT, + symbol, + f"Split of {symbol}", + round_decimal(Decimal(quantity), 6), + price=Decimal(0), + fees=Decimal(0), + amount=Decimal(0), + currency="USD", + broker="Testing", + ) + + calc_basic_data = [ pytest.param( 2020, # tax year @@ -992,4 +1012,101 @@ def transaction( }, id="sell_on_30/6_is_split_into_104+same_day+b&d", ), + pytest.param( + 2023, # tax year + [ + transfer_transaction(datetime.date(day=1, month=5, year=2020), 100), + buy_transaction( + date=datetime.date(day=2, month=5, year=2023), + symbol="FOO", + quantity=12, + price=5, + amount=-60, + fees=0, + ), + sell_transaction( + date=datetime.date(day=10, month=5, year=2023), + symbol="FOO", + quantity=2, + price=6, + amount=12, # 2.00 gain + fees=0, + ), + split_transaction( + date=datetime.date(day=15, month=5, year=2023), + symbol="FOO", + quantity=10, # 2x split + ), + sell_transaction( + date=datetime.date(day=10, month=6, year=2023), + symbol="FOO", + quantity=2, + price=5, + amount=10, # 5.00 gain + fees=0, + ), + ], + 7.00, # Expected capital gain/loss + None, # Expected unrealized gains + None, # GBP/USD prices + None, # Current prices + { + datetime.date(day=2, month=5, year=2023): { + "buy$FOO": [ + CalculationEntry( + RuleType.SECTION_104, + quantity=Decimal(12), + amount=Decimal(-60), + allowable_cost=Decimal(60), + new_quantity=Decimal(12), + fees=Decimal(0), + new_pool_cost=Decimal(60), + ), + ], + }, + datetime.date(day=10, month=5, year=2023): { + "sell$FOO": [ + CalculationEntry( + RuleType.SECTION_104, + quantity=Decimal(2), + amount=Decimal(12), + gain=Decimal(2), + allowable_cost=Decimal(10), + fees=Decimal(0), + new_quantity=Decimal(10), + new_pool_cost=Decimal(50), + ), + ], + }, + datetime.date(day=15, month=5, year=2023): { + "split$FOO": [ + CalculationEntry( + RuleType.SECTION_104, + quantity=Decimal(10), + amount=Decimal(0), + gain=Decimal(0), + fees=Decimal(0), + allowable_cost=Decimal(0), + new_quantity=Decimal(20), + new_pool_cost=Decimal(50), + ), + ], + }, + datetime.date(day=10, month=6, year=2023): { + "sell$FOO": [ + CalculationEntry( + RuleType.SECTION_104, + quantity=Decimal(2), + amount=Decimal(10), + gain=Decimal(5), + fees=Decimal(0), + allowable_cost=Decimal(5), + new_quantity=Decimal(18), + new_pool_cost=Decimal(45), + ), + ], + }, + }, + id="split", + ), ] From 1ec704c34ecb1039ad76fbc4e69bf353208595df Mon Sep 17 00:00:00 2001 From: Jakub Sygnowski Date: Tue, 2 Jul 2024 11:55:41 +0100 Subject: [PATCH 2/2] attempt to resolve the split issue --- cgt_calc/main.py | 13 +++++++++++-- cgt_calc/transaction_log.py | 12 ++++++++++++ tests/test_data/calc_test_data.py | 20 +++----------------- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/cgt_calc/main.py b/cgt_calc/main.py index de74a67c..53c7357a 100755 --- a/cgt_calc/main.py +++ b/cgt_calc/main.py @@ -42,7 +42,7 @@ ) from .parsers import read_broker_transactions, read_initial_prices from .spin_off_handler import SpinOffHandler -from .transaction_log import add_to_list, has_key +from .transaction_log import add_to_list, has_key, multiply_entries from .util import round_decimal LOGGER = logging.getLogger(__name__) @@ -123,7 +123,16 @@ def add_acquisition( price, amount = self.handle_spin_off(transaction) elif transaction.action is ActionType.STOCK_SPLIT: price = Decimal(0) - amount = Decimal(0) + original = self.portfolio[symbol].quantity + multiple = Decimal((original + quantity) / original) + # Retroactively alter the acquisition / disposal list, instead of + # adding the split as a separate acquisition event. + multiply_entries( + self.acquisition_list, symbol, multiple, transaction.date) + multiply_entries( + self.disposal_list, symbol, multiple, transaction.date) + self.portfolio[symbol] += Position(quantity, Decimal(0)) + return else: if price is None: raise PriceMissingError(transaction) diff --git a/cgt_calc/transaction_log.py b/cgt_calc/transaction_log.py index a2e07903..e5a5ecfd 100644 --- a/cgt_calc/transaction_log.py +++ b/cgt_calc/transaction_log.py @@ -31,3 +31,15 @@ def add_to_list( amount=amount, fees=fees, ) + +def multiply_entries( + current_list: HmrcTransactionLog, + symbol: str, + multiple: Decimal, + multiply_date: datetime.date | None = None +) -> None: + for date, entries in current_list.items(): + if multiply_date is not None: + assert multiply_date >= date + entries[symbol].quantity *= multiple + diff --git a/tests/test_data/calc_test_data.py b/tests/test_data/calc_test_data.py index 3d9b4a2c..ff9f5acb 100644 --- a/tests/test_data/calc_test_data.py +++ b/tests/test_data/calc_test_data.py @@ -1055,10 +1055,10 @@ def split_transaction( "buy$FOO": [ CalculationEntry( RuleType.SECTION_104, - quantity=Decimal(12), + quantity=Decimal(24), amount=Decimal(-60), allowable_cost=Decimal(60), - new_quantity=Decimal(12), + new_quantity=Decimal(24), fees=Decimal(0), new_pool_cost=Decimal(60), ), @@ -1068,25 +1068,11 @@ def split_transaction( "sell$FOO": [ CalculationEntry( RuleType.SECTION_104, - quantity=Decimal(2), + quantity=Decimal(4), amount=Decimal(12), gain=Decimal(2), allowable_cost=Decimal(10), fees=Decimal(0), - new_quantity=Decimal(10), - new_pool_cost=Decimal(50), - ), - ], - }, - datetime.date(day=15, month=5, year=2023): { - "split$FOO": [ - CalculationEntry( - RuleType.SECTION_104, - quantity=Decimal(10), - amount=Decimal(0), - gain=Decimal(0), - fees=Decimal(0), - allowable_cost=Decimal(0), new_quantity=Decimal(20), new_pool_cost=Decimal(50), ),