Investment Activity Labels#
Investment activity labels are a parallel classification system for investment and crypto account transactions. Instead of using the standard category/merchant taxonomy, transactions and trades on investment accounts carry an investment_activity_label string field that describes the nature of the activity (e.g., "Buy", "Dividend", "Sweep In"). This label takes the place of a category in the transaction list UI for any account where supports_trades? returns true.
The Label Set#
The canonical list of valid labels is defined in Transaction::ACTIVITY_LABELS :
| Label | Color (UI) | Purpose |
|---|---|---|
| Buy | Blue | Security purchase |
| Sell | Red | Security sale |
| Reinvestment | Blue | Dividend reinvested |
| Dividend | Green | Cash dividend income |
| Interest | Green | Interest income |
| Contribution | Violet | Cash deposited into investment account |
| Withdrawal | Amber | Cash withdrawn from investment account |
| Fee | Gray | Brokerage / management fee |
| Transfer | Gray | Internal movement |
| Sweep In | Gray | Auto cash sweep inbound |
| Sweep Out | Gray | Auto cash sweep outbound |
| Exchange | Gray | Crypto swap / currency exchange |
| Other | Gray | Catch-all |
Trade reuses the same values via Trade::ACTIVITY_LABELS = Transaction::ACTIVITY_LABELS.dup.freeze .
Schema#
transactions.investment_activity_label— added by migration 20260110, nullable string with an index.trades.investment_activity_label— added by migration 20260113, with a SQL backfill that assigns"Buy"/"Sell"/"Other"to all existing trades based onqtysign.
Budget / Analytics Impact#
Two sub-groupings drive downstream budget logic:
INTERNAL_MOVEMENT_LABELS — ["Transfer", "Sweep In", "Sweep Out", "Exchange"] — identifies labels that are auto cash management and should be excluded from income-statement totals. IncomeStatement::Totals has a SQL clause that filters out any transaction whose investment_activity_label is in this set .
Contribution / Withdrawal labels are how the InvestmentFlowStatement queries in/out cash flows for investment accounts. It filters directly on these two label values .
Trade records are always fully excluded from expense budgets regardless of label, as excluded_from_budget? returns true. For non-trade Transaction records on investment accounts, the investment_activity_label governs whether the transaction leaks into budget analytics.
UI Entry Points#
In the transaction list, the category column is replaced by an activity label badge for any account where entry.account.supports_trades? && !transaction.transfer? . The badge is rendered from app/views/investment_activity/_quick_edit_badge.html.erb .
The quick-edit badge:
- Shows a color-coded pill when a label is set; falls back to a tag icon + "Activity type" prompt when empty .
- Opens an inline dropdown listing all valid labels for the entryable type on click .
- Income trades (
Dividend,Interest) are intentionally read-only in the badge — the chevron and click handler are suppressed . - For
Transactionentryables it also carries aconvert_urlpointing toconvert_to_trade_transaction_path, enabling in-place conversion to aTrade.
A simpler non-interactive _badge.html.erb partial exists for read-only contexts like the trade detail header .
Which Accounts See Activity Labels#
Account#supports_trades? returns true for all Investment subtypes and for Crypto accounts with subtype == "exchange" . All other account types use standard categories.
Validation#
Trade validates that investment_activity_label must be one of ACTIVITY_LABELS or nil . Transaction has no model-layer validation on this field — enforcement is UI-driven.