Skip to content

Commit

Permalink
Improve holdings subcommand output
Browse files Browse the repository at this point in the history
  • Loading branch information
tacgomes committed Dec 4, 2024
1 parent 48eeb0b commit d3ca376
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 37 deletions.
47 changes: 30 additions & 17 deletions src/investir/taxcalculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []

Expand All @@ -237,34 +239,45 @@ 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(
[
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
Expand Down Expand Up @@ -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
16 changes: 7 additions & 9 deletions tests/test_cli/holdings
Original file line number Diff line number Diff line change
@@ -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

8 changes: 3 additions & 5 deletions tests/test_cli/holdings_swks
Original file line number Diff line number Diff line change
@@ -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

12 changes: 6 additions & 6 deletions tests/test_taxcalculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand All @@ -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)


Expand All @@ -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],
Expand All @@ -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):
Expand Down

0 comments on commit d3ca376

Please sign in to comment.