Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions data/security_identifiers.csv
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ SCHB,,10712436
SCHE,,10912918
SCHF,US8085248057,20251231
VGT,US92204A7028,1781764
VOO,US9229083632,22423967
VT,US9220427424,4354003
META,US30303M1027,14917609
23 changes: 22 additions & 1 deletion src/opensteuerauszug/importers/schwab/transaction_extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"Cash Merger",
"Cash Merger Adj",
"Div Adjustment",
"Dividend Reinvestment",
"Full Redemption",
"Full Redemption Adj",
"Foreign Tax Paid",
Expand Down Expand Up @@ -409,7 +410,7 @@ def create_cash_stock(
requires_settlement=requires_settlement,
)

if action == "Buy" or action == "Reinvest Shares":
if action in ("Buy", "Reinvest Shares"):
if schwab_qty and schwab_qty > 0 and isinstance(pos_object, SecurityPosition):
calculated_cost = None
cash_flow = None
Expand Down Expand Up @@ -438,6 +439,23 @@ def create_cash_stock(
requires_settlement=True,
)

elif action == "Dividend Reinvestment":
if (
schwab_amount is not None
and schwab_amount < 0
and isinstance(pos_object, SecurityPosition)
):
cash_stock = create_cash_stock(
schwab_amount,
f"Cash out for {action} {pos_object.symbol}",
requires_settlement=True,
)
else:
raise ValueError(
"Dividend Reinvestment requires a negative amount and a valid "
f"SecurityPosition. Amount: {schwab_amount}, Position: {pos_object}"
)

elif action == "Sale" or action == "Sell":
if schwab_qty and isinstance(pos_object, SecurityPosition):
if schwab_qty < 0:
Expand Down Expand Up @@ -471,6 +489,9 @@ def create_cash_stock(
name=action,
)
if cash_flow:
logger.info(
f"Creating cash stock mutation for \"{action}\" transaction for symbol {pos_object.symbol}."
)
cash_stock = create_cash_stock(
cash_flow,
f"Cash in for {action} {pos_object.symbol}",
Expand Down
45 changes: 45 additions & 0 deletions tests/importers/schwab/test_transaction_extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,51 @@ def test_action_reinvest_dividend(self):
assert cash_stock_entry.mutation is True, "Cash stock entry should be a mutation"
assert cash_stock_entry.quantity == Decimal("555.65")

def test_action_dividend_reinvestment_creates_cash_outflow(self):
extractor = create_extractor("EquityAwardsCenter_Transactions_20260916082416.json")
data = {
"FromDate": "01/01/2025",
"ToDate": "12/31/2025",
"Transactions": [
{
"Date": "12/15/2025",
"Action": "Dividend Reinvestment",
"Symbol": "GOOG",
"Description": "Debit",
"Amount": "-$558.74",
},
{
"Date": "12/15/2025",
"Action": "Deposit",
"Symbol": "GOOG",
"Quantity": "1.8079",
"Description": "Div Reinv",
"TransactionDetails": [{"Details": {"PurchasePrice": "$309.0497"}}],
},
],
}

result = run_extraction_test(extractor, data, 2)
assert result is not None

goog_data = find_position(result, SecurityPosition, "GOOG")
cash_data = find_position(result, CashPosition)
assert goog_data is not None
assert cash_data is not None

_, stocks, payments = goog_data
assert len(stocks) == 1
assert stocks[0].quantity == Decimal("1.8079")
assert payments is None

_, cash_stocks, cash_payments = cash_data
assert cash_payments is None
assert len(cash_stocks) == 1
cash_stock = cash_stocks[0]
assert cash_stock.quantity == Decimal("-558.74")
assert cash_stock.balance == Decimal("-558.74")
assert cash_stock.requires_settlement is True

def test_action_qual_div_reinvest(self):
extractor = create_extractor()
data = {
Expand Down
Loading