Skip to content

Commit

Permalink
Add parsing support for gift transactions (Schwab Equity Awards JSON).
Browse files Browse the repository at this point in the history
This adds a new action type, as TRANSFER seems to deal with money transfers
rather than share transfers, and SALEs also involve money whereas gifted
shares do not.

The calculation code will raise a NotImplementedError if it encounters
one of these transactions.

This may help #510. This contribution has been developed in my spare time.
  • Loading branch information
m01 committed Jun 30, 2024
1 parent d9b69fd commit fa8855c
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 1 deletion.
4 changes: 4 additions & 0 deletions cgt_calc/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ def convert_to_hmrc_transactions(
self.add_disposal(transaction)
if self.date_in_tax_year(transaction.date):
total_sells += self.converter.to_gbp_for(amount, transaction)
elif transaction.action is ActionType.GIFT:
raise NotImplementedError(
f"{transaction.action} is not currently supported ({transaction})"
)
elif transaction.action is ActionType.FEE:
amount = get_amount_or_fail(transaction)
new_balance += amount
Expand Down
1 change: 1 addition & 0 deletions cgt_calc/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class ActionType(Enum):
WIRE_FUNDS_RECEIVED = 14
STOCK_SPLIT = 15
CASH_MERGER = 16
GIFT = 17


@dataclass
Expand Down
16 changes: 15 additions & 1 deletion cgt_calc/parsers/schwab_equity_award_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ def action_from_str(label: str) -> ActionType:
if label == "Wire Funds Received":
return ActionType.WIRE_FUNDS_RECEIVED

if label == "Gift":
return ActionType.GIFT

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


Expand Down Expand Up @@ -199,6 +202,7 @@ def __init__(self, row: JsonRowType, file: str, field_names: FieldNames) -> None
quantity = _decimal_from_number_or_str(row, names.quantity)
amount = _decimal_from_number_or_str(row, names.amount)
fees = _decimal_from_number_or_str(row, names.fees)
currency = "USD"
if row[names.action] == "Deposit":
if len(row[names.transac_details]) != 1:
raise ParsingError(
Expand All @@ -222,6 +226,17 @@ def __init__(self, row: JsonRowType, file: str, field_names: FieldNames) -> None
f"{details[names.award_date]} "
f"(ID {details[names.award_id]})"
)
elif row[names.action] == "Gift":
if OPTIONAL_DETAILS_NAME in row[names.transac_details][0]:
details = row[names.transac_details][0]["Details"]
else:
details = row[names.transac_details][0]
date = datetime.datetime.strptime(row[names.date], "%m/%d/%Y").date()
if any((row[names.amount], row[names.fees])):
raise ParsingError(file, "Unexpected fees or amount for gifted shares.")
price = None
# Note that currently the default currency is used, because the model does
# not support a None currency.
elif row[names.action] == "Sale":
# Schwab's data export shows the settlement date,
# whereas HMRC wants the trade date:
Expand Down Expand Up @@ -292,7 +307,6 @@ def __init__(self, row: JsonRowType, file: str, field_names: FieldNames) -> None
file, f"Parsing for action {row[names.action]} is not implemented!"
)

currency = "USD"
broker = "Charles Schwab"
super().__init__(
date,
Expand Down
38 changes: 38 additions & 0 deletions tests/test_data/schwab_equity_award_v1.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
{
"transactions": [
{
"typeName": "DisbursementViewModel",
"eventDateSortValue": "2022-11-20T00:00:00",
"eventDate": "11/20/2022",
"action": "Gift",
"symbol": "GOOG",
"quantitySortValue": 2.0,
"quantity": "2",
"description": "Share Transfer",
"totalCommissionsAndFeesSortValue": null,
"totalCommissionsAndFees": null,
"disbursementElection": null,
"amountSortValue": null,
"amount": null,
"transactionDetails": [
{
"shareLotType": "RS",
"quantity": "2",
"vestDate": "03/25/2021",
"vestFMV": "$2,045.06",
"grantId": "C111111",
"typeName": "DisbursementDetail",
"fieldNameToDisplayValueLookup": {
"Type": "RS",
"Shares": "53",
"PurchaseDate": null,
"PurchasePrice": null,
"PurchaseFairMarketValue": null,
"SubscriptionDate": null,
"SubscriptionFairMarketValue": null,
"DispositionType": null,
"VestDate": "03/25/2021",
"VestFairMarketValue": "$2,045.06",
"GrantId": "C111111"
}
}
]
},
{
"typeName": "ShareSaleViewModel",
"eventDateSortValue": "2022-11-16:00:00",
Expand Down
27 changes: 27 additions & 0 deletions tests/test_data/schwab_equity_award_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,33 @@
"FromDate": "01/01/2020",
"ToDate": "12/23/2023",
"Transactions": [
{
"Date": "09/29/2023",
"Action": "Gift",
"Symbol": "GOOG",
"Quantity": "2",
"Description": "Share Transfer",
"FeesAndCommissions": null,
"DisbursementElection": null,
"Amount": null,
"TransactionDetails": [
{
"Details": {
"Type": "RS",
"Shares": "2",
"PurchaseDate": null,
"PurchasePrice": null,
"PurchaseFairMarketValue": null,
"SubscriptionDate": null,
"SubscriptionFairMarketValue": null,
"DispositionType": null,
"VestDate": "09/25/2023",
"VestFairMarketValue": "$131.25",
"GrantId": "C987654"
}
}
]
},
{
"Date": "09/27/2023",
"Action": "Deposit",
Expand Down
18 changes: 18 additions & 0 deletions tests/test_schwab_equity_award_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ def test_schwab_transaction_v1() -> None:
assert transactions[3].amount == Decimal("25745")
assert transactions[3].fees == Decimal("0.50")

assert transactions[4].date == datetime.date(2022, 11, 20)
assert transactions[4].action == ActionType.GIFT
assert transactions[4].symbol == "GOOG"
assert transactions[4].quantity == Decimal("2")
assert transactions[4].price is None
assert transactions[4].fees == Decimal("0")
assert transactions[4].currency == "USD"
assert transactions[4].broker == "Charles Schwab"


def test_schwab_transaction_v2() -> None:
"""Test read_schwab_equity_award_json_transactions() on v2 data."""
Expand Down Expand Up @@ -137,3 +146,12 @@ def test_schwab_transaction_v2() -> None:
assert transactions[4].fees == Decimal("0")
assert transactions[4].currency == "USD"
assert transactions[4].broker == "Charles Schwab"

assert transactions[5].date == datetime.date(2023, 9, 29)
assert transactions[5].action == ActionType.GIFT
assert transactions[5].symbol == "GOOG"
assert transactions[5].quantity == Decimal("2")
assert transactions[5].price is None
assert transactions[5].fees == Decimal("0")
assert transactions[5].currency == "USD"
assert transactions[5].broker == "Charles Schwab"

0 comments on commit fa8855c

Please sign in to comment.