From d3ca3763dfc3a80e5bc89113cef1afef2cb1730e Mon Sep 17 00:00:00 2001 From: Tiago Gomes Date: Sun, 1 Dec 2024 18:59:04 +0000 Subject: [PATCH] Improve holdings subcommand output --- src/investir/taxcalculator.py | 47 ++++++++++++++++++++++------------- tests/test_cli/holdings | 16 ++++++------ tests/test_cli/holdings_swks | 8 +++--- tests/test_taxcalculator.py | 12 ++++----- 4 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/investir/taxcalculator.py b/src/investir/taxcalculator.py index 45bafe2..2fc7e37 100644 --- a/src/investir/taxcalculator.py +++ b/src/investir/taxcalculator.py @@ -212,15 +212,17 @@ def get_holdings_table( "Security Name", "ISIN", "Cost (£)", - "Allocation (%)", "Quantity", - "Average Cost (£)", - "Unrealised Gain/Loss (£)", + "Current Value (£)", + "Gain/Loss (£)", + "Weight (%)", ] ) if not show_gain_loss: - table.hide_field("Unrealised Gain/Loss (£)") + table.hide_field("Current Value (£)") + table.hide_field("Gain/Loss (£)") + table.hide_field("Weight (%)") holdings = [] @@ -237,14 +239,27 @@ def get_holdings_table( if isin in self._holdings: holdings = [(isin, self._holdings[isin])] - total_cost = sum(round(holding.cost, 2) for _, holding in holdings) + holding2value = ( + { + isin: value + for isin, holding in holdings + if (value := self._get_holding_value(isin, holding)) is not None + } + if show_gain_loss + else {} + ) + + portfolio_value = sum(val for val in holding2value.values()) total_gain_loss = Decimal("0.0") last_idx = len(holdings) - 1 for idx, (isin, holding) in enumerate(holdings): - if show_gain_loss and ( - gain_loss := self._calculate_unrealised_gain_loss(isin, holding) - ): + gain_loss: Decimal | None = None + weight: Decimal | None = None + + if holding_value := holding2value.get(isin): + gain_loss = holding.cost - holding_value + weight = holding_value / portfolio_value * 100 total_gain_loss += gain_loss table.add_row( @@ -252,19 +267,17 @@ def get_holdings_table( self._tr_hist.get_security_name(isin), isin, holding.cost, - holding.cost / total_cost * 100, holding.quantity, - holding.cost / holding.quantity, - gain_loss - if show_gain_loss and gain_loss is not None - else "Not available", + holding_value, + gain_loss or "n/a", + weight or "n/a", ], divider=idx == last_idx, ) - if table.rows: + if table.rows and show_gain_loss: table.add_row( - ["", "", total_cost, Decimal("100.0"), "", "", total_gain_loss] + ["", "", "", "", portfolio_value, total_gain_loss, Decimal("100.0")] ) return table @@ -438,12 +451,12 @@ def _process_section104_disposals(self, isin: ISIN) -> None: logger.warning("Not calculating holding for %s", isin) break - def _calculate_unrealised_gain_loss( + def _get_holding_value( self, isin: ISIN, holding: Section104Holding ) -> Decimal | None: if (price := self._findata.get_security_price(isin)) and ( price_gbp := self._findata.convert_currency(price.amount, price.currency) ): - return holding.quantity * price_gbp - holding.cost + return holding.quantity * price_gbp return None diff --git a/tests/test_cli/holdings b/tests/test_cli/holdings index 45d78a1..a271a0a 100644 --- a/tests/test_cli/holdings +++ b/tests/test_cli/holdings @@ -1,10 +1,8 @@ - Security Name ISIN Cost (£) Allocation (%) Quantity Average Cost (£) ----------------------------------------------------------------------------------------------- - SMT GB00BLDYK618 2196.81 55.88 162.00000000 13.56 - Microsoft US5949181045 882.35 22.44 3.62439184 243.45 - Apple US0378331005 301.70 7.67 3.00000000 100.57 - Skyworks US83088M1027 301.08 7.66 2.29594360 131.13 - PayPal US70450Y1038 249.59 6.35 4.13171759 60.41 ----------------------------------------------------------------------------------------------- - 3931.53 100.00 + Security Name ISIN Cost (£) Quantity +---------------------------------------------------------- + SMT GB00BLDYK618 2196.81 162.00000000 + Microsoft US5949181045 882.35 3.62439184 + Apple US0378331005 301.70 3.00000000 + Skyworks US83088M1027 301.08 2.29594360 + PayPal US70450Y1038 249.59 4.13171759 diff --git a/tests/test_cli/holdings_swks b/tests/test_cli/holdings_swks index 920e41e..d7043a0 100644 --- a/tests/test_cli/holdings_swks +++ b/tests/test_cli/holdings_swks @@ -1,6 +1,4 @@ - Security Name ISIN Cost (£) Allocation (%) Quantity Average Cost (£) --------------------------------------------------------------------------------------------- - Skyworks US83088M1027 301.08 100.00 2.29594360 131.13 --------------------------------------------------------------------------------------------- - 301.08 100.00 + Security Name ISIN Cost (£) Quantity +-------------------------------------------------------- + Skyworks US83088M1027 301.08 2.29594360 diff --git a/tests/test_taxcalculator.py b/tests/test_taxcalculator.py index 8fa2fcc..de627fd 100644 --- a/tests/test_taxcalculator.py +++ b/tests/test_taxcalculator.py @@ -1307,7 +1307,7 @@ def test_get_holdings_table_with_gain_loss(make_tax_calculator): ) table = tax_calculator.get_holdings_table(show_gain_loss=True) - assert "Unrealised Gain/Loss (£)" in str(table) + assert "Gain/Loss (£)" in str(table) assert "50.00" in str(table) @@ -1327,7 +1327,7 @@ def test_get_holdings_table_with_gain_loss_and_currency_conversion(make_tax_calc ) table = tax_calculator.get_holdings_table(show_gain_loss=True) - assert "Unrealised Gain/Loss (£)" in str(table) + assert "Gain/Loss (£)" in str(table) assert "12.50" in str(table) @@ -1345,8 +1345,8 @@ def test_get_holdings_table_with_gain_loss_when_price_or_fx_rate_not_available( tax_calculator = make_tax_calculator([order], price=DataProviderError) table = tax_calculator.get_holdings_table(show_gain_loss=True) - assert "Unrealised Gain/Loss (£)" in str(table) - assert "Not available" in str(table) + assert "Gain/Loss (£)" in str(table) + assert "n/a" in str(table) tax_calculator = make_tax_calculator( [order], @@ -1355,8 +1355,8 @@ def test_get_holdings_table_with_gain_loss_when_price_or_fx_rate_not_available( ) table = tax_calculator.get_holdings_table(show_gain_loss=True) - assert "Unrealised Gain/Loss (£)" in str(table) - assert "Not available" in str(table) + assert "Gain/Loss (£)" in str(table) + assert "n/a" in str(table) def test_get_holdings_table_ambiguous_ticker(make_tax_calculator, capsys):