Skip to content

Commit

Permalink
Update trading212 parser (#531)
Browse files Browse the repository at this point in the history
  • Loading branch information
vmartinv authored Jun 8, 2024
1 parent 8bd36e3 commit 83d5ff3
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 22 deletions.
73 changes: 51 additions & 22 deletions cgt_calc/parsers/trading212.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,15 @@
"Currency (Withholding tax)",
"Charge amount (GBP)",
"Transaction fee (GBP)",
"Transaction fee",
"Finra fee (GBP)",
"Stamp duty (GBP)",
"Notes",
"ID",
"Currency conversion fee (GBP)",
"Currency conversion fee",
"Currency (Currency conversion fee)",
"Currency (Transaction fee)",
]


Expand Down Expand Up @@ -75,6 +79,9 @@ def action_from_str(label: str, filename: str) -> ActionType:
if label in ["Interest on cash"]:
return ActionType.INTEREST

if label == "Stock Split":
return ActionType.STOCK_SPLIT

raise ParsingError(filename, f"Unknown action: {label}")


Expand All @@ -98,32 +105,60 @@ def __init__(self, header: list[str], row_raw: list[str], filename: str):
self.price_foreign = decimal_or_none(row["Price / share"])
self.currency_foreign = row["Currency (Price / share)"]
self.exchange_rate = decimal_or_none(row["Exchange rate"])
self.transaction_fee = decimal_or_none(row.get("Transaction fee (GBP)", "0"))
self.finra_fee = decimal_or_none(row.get("Finra fee (GBP)", "0"))
self.stamp_duty = decimal_or_none(row.get("Stamp duty (GBP)", "0"))
self.conversion_fee = decimal_or_none(
row.get("Currency conversion fee (GBP)", "0")
)
fees = (
(self.transaction_fee or Decimal(0))
+ (self.finra_fee or Decimal(0))
+ (self.conversion_fee or Decimal(0))
)
self.transaction_fee = Decimal(row.get("Transaction fee (GBP)") or "0")
transaction_fee_foreign = Decimal(row.get("Transaction fee") or "0")
if transaction_fee_foreign > 0:
if row.get("Currency (Transaction fee)") != "GBP":
raise ParsingError(
filename,
"The transaction fee is not in GBP which is not supported yet",
)
self.transaction_fee += transaction_fee_foreign
self.finra_fee = Decimal(row.get("Finra fee (GBP)") or "0")
self.stamp_duty = Decimal(row.get("Stamp duty (GBP)") or "0")
self.conversion_fee = Decimal(row.get("Currency conversion fee (GBP)") or "0")
conversion_fee_foreign = Decimal(row.get("Currency conversion fee") or "0")
if conversion_fee_foreign > 0:
if row.get("Currency (Currency conversion fee)") != "GBP":
raise ParsingError(
filename,
"The transaction fee is not in GBP which is not supported yet",
)
self.conversion_fee += conversion_fee_foreign
fees = self.transaction_fee + self.finra_fee + self.conversion_fee
if "Total" in row:
amount = decimal_or_none(row["Total"])
currency = row["Currency (Total)"]
else:
amount = decimal_or_none(row["Total (GBP)"])
currency = "GBP"
if (
amount is not None
and (action == ActionType.BUY or self.raw_action == "Withdrawal")
and amount > 0
):
amount *= -1
price = (
abs(amount / quantity)
abs(amount + fees) / quantity
if amount is not None and quantity is not None
else None
)
if amount is not None:
if action == ActionType.BUY or self.raw_action == "Withdrawal":
amount *= -1
amount -= fees
if (
price is not None
and self.price_foreign is not None
and (self.currency_foreign == "GBP" or self.exchange_rate is not None)
):
calculated_price_foreign = price * (self.exchange_rate or Decimal("1"))
discrepancy = self.price_foreign - calculated_price_foreign
if abs(discrepancy) > Decimal("0.015"):
print(
"WARNING: The Price / share for this transaction "
"after converting and adding in the fees "
f"doesn't add up to the total amount: {row}. "
"You may fix the csv by looking at the transaction "
f"in the UI. Discrepancy / share: {discrepancy:.3f}."
)

self.isin = row["ISIN"]
self.transaction_id = row["ID"] if "ID" in row else None
self.notes = row["Notes"] if "Notes" in row else None
Expand All @@ -141,12 +176,6 @@ def __init__(self, header: list[str], row_raw: list[str], filename: str):
broker,
)

def __eq__(self, other: object) -> bool:
"""Compare transactions by ID."""
if not isinstance(other, Trading212Transaction):
raise NotImplementedError()
return self.transaction_id == other.transaction_id

def __hash__(self) -> int:
"""Calculate hash."""
return hash(self.transaction_id)
Expand Down
29 changes: 29 additions & 0 deletions tests/test_data/trading212_2024/expected_output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
INFO: No schwab file provided
INFO: No schwab Equity Award JSON file provided
Parsing tests/test_data/trading212_2024/transactions.csv
INFO: No mssb folder provided
INFO: No sharesight file provided
INFO: No raw file provided
First pass completed
Final portfolio:
Final balance:
Trading212: 1758.05 (GBP)
Dividends: £0.00
Dividend taxes: £0.00
Interest: £0.00
Disposal proceeds: £3138.50


Second pass completed
Portfolio at the end of 2024/2025 tax year:
For tax year 2024/2025:
Number of disposals: 1
Disposal proceeds: £3138.50
Allowable costs: £2381.35
Capital gain: £757.15
Capital loss: £0.00
Total capital gain: £757.15
Taxable capital gain: £0

Generate calculations report
All done!
8 changes: 8 additions & 0 deletions tests/test_data/trading212_2024/transactions.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Action,Time,ISIN,Ticker,Name,No. of shares,Price / share,Currency (Price / share),Exchange rate,Result,Currency (Result),Total,Currency (Total),Withholding tax,Currency (Withholding tax),Transaction fee,Notes,ID,Currency conversion fee,Currency (Currency conversion fee),Currency (Transaction fee)
Deposit,2024-01-01 00:15:20.149,,,,,,,,,,3000.00,"GBP",,,,"Transaction ID: xxxxxxxxxxxx05",xxxxxxxxxxxx05,,,
Market buy,2024-01-01 16:10:05.175,US00000003,FOO,"Foo Company",24.0000000000,124.01,USD,1.25229,,"GBP",2381.35,"GBP",,,,,EOF999999099,4.71,"GBP",
Interest on cash,2024-01-01 11:05:03.126,,,,,,,,,,0.04,"GBP",,,,"Interest on cash",ffffffff-ffff-ffff-ffff-ffffffffffff,,,
Deposit,2024-01-03 10:00:00.100,,,,,,,,,,0.75,"GBP",,,,"Withholding tax refund - interest on cash Q4 2023.",ffffffff-ffff-ffff-ffff-ffffffffff7f,,,
Dividend (Dividend),2024-03-23 18:35:26,US00000003,FOO,"Foo Company",24.0000000000,0.03,USD,Not available,,,0.11,"GBP",0.02,USD,,,,,,
Limit sell,2024-04-29 13:20:03.075,US00000003,FOO,"Foo Company",24.0000000000,164.01,USD,1.25229,-75.67,"GBP",3138.50,"GBP",,,,,EOF999999099,4.71,"GBP",
Withdrawal,2024-05-26 16:18:16.235,,,,,,,,,,-2000.00,"GBP",,,,"Sent to Bank Account 99-99-99 / 9999999",ffffffff-ffff-ffff-ffff-ffffffffff99,,,
31 changes: 31 additions & 0 deletions tests/test_trading212_2024.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Test Trading212 support."""

from pathlib import Path
import subprocess
import sys


def test_run_with_trading212_files() -> None:
"""Runs the script and verifies it doesn't fail."""
cmd = [
sys.executable,
"-m",
"cgt_calc.main",
"--year",
"2024",
"--trading212",
"tests/test_data/trading212_2024/",
"--no-pdflatex",
]
result = subprocess.run(cmd, check=True, capture_output=True)
assert result.stderr == b"", "Run with example files generated errors"
expected_file = (
Path("tests") / "test_data" / "trading212_2024" / "expected_output.txt"
)
expected = expected_file.read_text()
cmd_str = " ".join([param if param else "''" for param in cmd])
assert result.stdout.decode("utf-8") == expected, (
"Run with example files generated unexpected outputs, "
"if you added new features update the test with:\n"
f"{cmd_str} > {expected_file}"
)

0 comments on commit 83d5ff3

Please sign in to comment.