Skip to content

Tax Engine

Griffen Fargo edited this page May 3, 2026 · 3 revisions

Tax Engine

The tax engine lives in packages/tax/. It computes cost basis, gain/loss, and produces tax-ready CSV output from classified ledger entries.

Cost Basis Methods

daybook supports three cost basis strategies, all implementing the CostBasisStrategy interface:

FIFO (First In, First Out)

The IRS default. Disposes of the oldest lots first. Often produces long-term gains (taxed at lower rates) but on lots with the lowest basis (larger gains).

daybook export 2024 --method FIFO

HIFO (Highest In, First Out)

Disposes of the highest-cost lots first. Minimizes taxable gain in the current year. Allowed by the IRS as "Specific ID with consistent application."

daybook export 2024 --method HIFO

Specific ID

User hand-picks which lots to dispose. Maximum flexibility for tax-loss harvesting.

# Interactive lot picker
daybook export 2024 --method specific-id

# Replay from saved selections
daybook export 2024 --method specific-id --lot-selections ./selections.json

Interactive Lot Picker

When using --method specific-id without --lot-selections, an Ink-based interactive picker launches. For each disposal:

  1. The disposal details are shown (asset, amount, date)
  2. Available lots are displayed with: lot ID, acquisition date, amount, unit cost, holding period
  3. Select lots using checkbox-style controls
  4. A running total shows progress toward the required amount
  5. When the total meets the disposal amount, the picker advances to the next disposal
  6. Press s to skip a disposal (falls back to FIFO for that one)

After completion, the selections are serialized to JSON and the path is printed for replay.

JSON Replay

The --lot-selections <path> flag loads a JSON file mapping lot IDs to disposal amounts:

{
  "lot-abc123": "1.5",
  "lot-def456": "0.75"
}

If a lot ID in the file no longer exists in the LotBook (e.g., after reclassification), the tool exits with an error listing the missing IDs.

LotBook

The LotBook (packages/tax/src/lot-book.ts) manages per-asset queues of lots. Operations:

  • acquire — add a new lot (from income, trade buy leg, or transfer in)
  • dispose — remove lots according to the selected strategy

Lot splitting is handled automatically — if a disposal partially consumes a lot, the remainder stays in the book.

Universal pooling is used: all lots for an asset are in one pool regardless of which account they came from.

Wash Sale Flagging

File: packages/tax/src/wash-sale.ts

After the main cost-basis computation, a wash sale pass runs over all DisposalResult records:

  • For each disposal with a loss (gainLoss < 0): check if the same asset was acquired within ±30 calendar days (UTC) of the disposal date
  • If a matching acquisition exists: washSaleFlag = true
  • If no match: washSaleFlag = false
  • Disposals with gains (gainLoss >= 0): always washSaleFlag = false (no lookup needed)

Calendar days are computed as Math.floor(date.getTime() / 86_400_000) for consistent UTC-day comparison.

The wash sale flag is informational only. daybook does not compute or apply disallowance amounts. The flag appears in the CSV export as a Wash Sale? column (Y or N) and in the export summary as a count of candidates.

Use --no-wash-sale-flag to suppress both the column and the summary.

Method Comparison

daybook compare 2024

Runs computeTax with both FIFO and HIFO strategies against the same ledger entries and displays a side-by-side Ink table:

FIFO HIFO
Short-term gain $4,820 $1,210
Long-term gain $12,340 $14,890
Total taxable $17,160 $16,100
Income $1,205 $1,205

CSV Export

The CSV exporter (packages/tax/src/csv-export.ts) produces one row per disposal with columns:

  • Date Acquired
  • Date Sold
  • Asset
  • Amount
  • Proceeds (USD)
  • Cost Basis (USD)
  • Gain/Loss (USD)
  • Term (Short/Long)
  • Wash Sale? (Y/N) — omitted with --no-wash-sale-flag

A summary footer includes totals for short-term gain, long-term gain, total income, and wash sale candidate count.

Pricing Integration

The tax engine uses the Pricing module to resolve USD values for each event. Prices are resolved at computation time, not at sync time, so re-running the export after adding manual overrides produces updated results.

Decimal Precision

All amount math uses decimal.js. Amounts are stored as strings in the database and converted to Decimal at math boundaries. JavaScript floating-point is never used for financial calculations.

Clone this wiki locally