Skip to content

Commit

Permalink
Update Morgan Stanley parser (#399)
Browse files Browse the repository at this point in the history
- Handle GOOG stock split correctly
- Treat "Staged" releases similarly to "Completed"
  • Loading branch information
goofy57 committed Jun 26, 2023
1 parent c675799 commit 8b4a40d
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 15 deletions.
60 changes: 53 additions & 7 deletions cgt_calc/parsers/mssb.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from __future__ import annotations

import csv
from datetime import datetime
from dataclasses import dataclass
import datetime
from decimal import Decimal
from pathlib import Path
from typing import Final
Expand Down Expand Up @@ -48,6 +49,20 @@
}


@dataclass
class StockSplit:
"""Info about stock split."""

symbol: str
date: datetime.date
factor: int


STOCK_SPLIT_INFO = [
StockSplit(symbol="GOOG", date=datetime.datetime(2022, 6, 15).date(), factor=20),
]


def _hacky_parse_decimal(decimal: str) -> Decimal:
return Decimal(decimal.replace(",", ""))

Expand All @@ -60,7 +75,7 @@ def _init_from_release_report(row_raw: list[str], filename: str) -> BrokerTransa
if row["Type"] != "Release":
raise ParsingError(filename, f"Unknown type: {row_raw[3]}")

if row["Status"] != "Complete":
if row["Status"] != "Complete" and row["Status"] != "Staged":
raise ParsingError(filename, f"Unknown status: {row_raw[5]}")

if row["Price"][0] != "$":
Expand All @@ -79,7 +94,7 @@ def _init_from_release_report(row_raw: list[str], filename: str) -> BrokerTransa
symbol = TICKER_RENAMES.get(symbol, symbol)

return BrokerTransaction(
date=datetime.strptime(row["Vest Date"], "%d-%b-%Y").date(),
date=datetime.datetime.strptime(row["Vest Date"], "%d-%b-%Y").date(),
action=ActionType.STOCK_ACTIVITY,
symbol=symbol,
description=row["Plan"],
Expand All @@ -92,9 +107,38 @@ def _init_from_release_report(row_raw: list[str], filename: str) -> BrokerTransa
)


# Morgan Stanley decided to put a notice in the end of the withdrawal report that looks
# like that:
# "Please note that any Alphabet share sales, transfers, or deposits that occurred on
# or prior to the July 15, 2022 stock split are reflected in pre-split. Any sales,
# transfers, or deposits that occurred after July 15, 2022 are in post-split values.
# For GSU vests, your activity is displayed in post-split values."
# It makes sense, but it totally breaks the CSV parsing
def _is_notice(row: list[str]) -> bool:
return row[0][:11] == "Please note"


def _handle_stock_split(transaction: BrokerTransaction) -> BrokerTransaction:
for split in STOCK_SPLIT_INFO:
if (
transaction.symbol == split.symbol
and transaction.action == ActionType.SELL
and transaction.date < split.date
):
if transaction.quantity:
transaction.quantity *= split.factor
if transaction.price:
transaction.price /= split.factor

return transaction


def _init_from_withdrawal_report(
row_raw: list[str], filename: str
) -> BrokerTransaction:
) -> BrokerTransaction | None:
if _is_notice(row_raw):
return None

if len(COLUMNS_WITHDRAWAL) != len(row_raw):
raise UnexpectedColumnCountError(row_raw, len(COLUMNS_WITHDRAWAL), filename)
row = {col: row_raw[i] for i, col in enumerate(COLUMNS_WITHDRAWAL)}
Expand Down Expand Up @@ -122,8 +166,8 @@ def _init_from_withdrawal_report(
else:
action = ActionType.SELL

return BrokerTransaction(
date=datetime.strptime(row["Date"], "%d-%b-%Y").date(),
transaction = BrokerTransaction(
date=datetime.datetime.strptime(row["Date"], "%d-%b-%Y").date(),
action=action,
symbol=KNOWN_SYMBOL_DICT[row["Plan"]],
description=row["Plan"],
Expand All @@ -135,6 +179,8 @@ def _init_from_withdrawal_report(
broker="Morgan Stanley",
)

return _handle_stock_split(transaction)


def _validate_header(
header: list[str], golden_header: list[str], filename: str
Expand Down Expand Up @@ -172,4 +218,4 @@ def read_mssb_transactions(transactions_folder: str) -> list[BrokerTransaction]:
_init_from_release_report(row, str(file)) for row in lines
]

return transactions
return [transaction for transaction in transactions if transaction]
3 changes: 2 additions & 1 deletion tests/test_data/mssb/Releases Report.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Vest Date,Order Number,Plan,Type,Status,Price,Quantity,Net Cash Proceeds,Net Share Proceeds,Tax Payment Method
25-Mar-2021,ORDER_NUMBER_HIDDEN,GSU Class C,Release,Complete,"$2,045.06",20.000,$0.00,10.60,Fractional Shares
25-Mar-2021,ORDER_NUMBER_HIDDEN,GSU Class C,Release,Complete,$102.25,400.000,$0.00,212.0,Fractional Shares
25-Mar-2023,ORDER_NUMBER_HIDDEN,GSU Class C,Release,Complete,$121.64,40.000,$0.00,21.201,Fractional Shares
5 changes: 4 additions & 1 deletion tests/test_data/mssb/Withdrawals Report.csv
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
Date,Order Number,Plan,Type,Order Status,Price,Quantity,Net Amount,Net Share Proceeds,Tax Payment Method
01-Apr-2021,ORDER_NUMBER_HIDDEN,GSU Class C,Sale,Complete,"$2,110.00",-2,"$4,219.95",0,N/A
02-Apr-2023,ORDER_NUMBER_HIDDEN,Cash,Sale,Complete,$1.00,"-4,218.95","$4,218.95",0,N/A
02-Apr-2021,ORDER_NUMBER_HIDDEN,Cash,Sale,Complete,$1.00,"-4,218.95","$4,218.95",0,N/A
09-Feb-2023,ORDER_NUMBER_HIDDEN,Cash,Sale,Complete,$1.00,"-3,170.93","$3,170.93",0,N/A
07-Feb-2023,ORDER_NUMBER_HIDDEN,GSU Class C,Sale,Complete,$105.70,-30.00,"$3,170.93",0,N/A
Please note that any Alphabet share sales, transfers, or deposits that occurred on or prior to the July 15, 2022 stock split are reflected in pre-split. Any sales, transfers, or deposits that occurred after July 15, 2022 are in post-split values. For GSU vests, your activity is displayed in post-split values.
12 changes: 6 additions & 6 deletions tests/test_data/test_run_with_example_files_output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ First pass completed
Final portfolio:
GE: 1.00
NVDA: 1.00
GOOG: 8.60
GOOG: 163.20
FOO: 30.50
Final balance:
Charles Schwab: 9477.90 (USD)
Expand All @@ -24,15 +24,15 @@ Portfolio at the end of 2020/2021 tax year:
GE: 1.00, £8.07
NVDA: 1.00, £401.01
FOO: 30.50, £543.97
GOOG: 8.60, £12516.92
GOOG: 172.00, £12516.55
For tax year 2020/2021:
Number of disposals: 4
Disposal proceeds: £42769.56
Allowable costs: £17656.17
Capital gain: £25113.39
Allowable costs: £17656.08
Capital gain: £25113.48
Capital loss: £0.00
Total capital gain: £25113.39
Taxable capital gain: £12813.39
Total capital gain: £25113.48
Taxable capital gain: £12813.48

Generate calculations report
All done!

0 comments on commit 8b4a40d

Please sign in to comment.