Dividend and DRIP Modeling#
Overview#
Cash dividends and dividend reinvestments (DRIPs) are modeled differently from one another — and both differ from ordinary buy/sell trades — using a combination of record type (Trade vs Transaction) and the investment_activity_label field.
| Activity | Record Type | Label | qty / price |
|---|---|---|---|
| Cash dividend (manual) | Trade | "Dividend" | 0 / 0 |
| DRIP / Reinvestment (SnapTrade) | Trade | "Reinvestment" | non-zero |
| Cash dividend (SnapTrade) | Transaction | "Dividend" | n/a |
Cash Dividends#
Manual Entry#
Dividends entered manually via the trade form are always stored as Trade records, never as Transaction records. The "dividend" form type routes to create_dividend_income in Trade::CreateForm, which calls create_income_trade with:
qty: 0,price: 0,fee: 0— no position changeinvestment_activity_label: "Dividend"amountnegated (amount * -1), representing cash inflow- Security is required — the form enforces this and returns a validation error if no ticker is present
The security association links the dividend income to a specific holding without changing its quantity.
Via SnapTrade Sync#
When synced from a brokerage via SnapTrade, DIVIDEND and DIV activity types map to the "Dividend" label . Crucially, both are listed in CASH_TYPES , so they are processed by process_cash_activity and stored as Transaction records, not Trade records .
The amount sign is normalized in normalize_cash_amount: DIVIDEND / DIV amounts are negated (-amount.abs), consistent with the manual-entry convention where cash inflows are negative .
Key distinction: A manually-entered cash dividend is a
Trade(withqty=0); a SnapTrade-synced cash dividend is aTransaction. Both share the"Dividend"label.
Dividend Reinvestment (DRIP)#
DRIPs come exclusively from brokerage sync via SnapTrade. The REI and REINVEST activity types are listed in TRADE_TYPES , so they route through process_trade and create Trade records with a non-zero quantity, representing actual share acquisition .
Both map to the "Reinvestment" label via SNAPTRADE_TYPE_TO_LABEL .
There is no manual DRIP/reinvestment entry type in the current UI — the trade form only supports: buy, sell, dividend, interest, deposit, withdrawal .
Label System and Budget Impact#
"Dividend" and "Reinvestment" are members of the shared ACTIVITY_LABELS constant, defined on Transaction and mirrored on Trade via Trade::ACTIVITY_LABELS = Transaction::ACTIVITY_LABELS.dup.freeze . See Investment Activity Labels for the full label set.
Budget exclusion: All Trade records (including qty=0 dividend trades) are unconditionally excluded from expense budgets via excluded_from_budget?. For SnapTrade-synced cash dividends stored as Transaction records, "Dividend" is not in INTERNAL_MOVEMENT_LABELS, so those transactions do flow through to income statement totals . This means SnapTrade-synced dividends and manually-entered dividends have different analytics behavior despite sharing the same label.
The income-badge UI marks "Dividend" and "Interest" trades as read-only — the label chevron and click handler are suppressed for these activity types .
Key Source Files#
| File | Purpose |
|---|---|
app/models/trade/create_form.rb | Manual dividend/DRIP creation logic |
app/models/trade.rb | Trade model, ACTIVITY_LABELS, budget exclusion |
app/models/snaptrade_account/activities_processor.rb | SnapTrade type→label mapping, TRADE_TYPES vs CASH_TYPES |
| Investment Activity Labels | Full label reference, UI badge behavior, budget/analytics impact |