Skip to main content

💸 Divvy

"What if I'd invested my money into that portfolio instead?"

Replay your real investing history against a different set of holdings — and see the dividends and returns you would have earned.

PyPI CI License: MIT Python 3.12+ Built with uv

🚀 Try the live demo · 🌐 Website · 📦 PyPI

The live demo runs on synthetic data — for your real numbers, run it locally so your financial data never leaves your machine.


Divvy Portfolio Experiment Lab — add portfolios, apply tax, and compare dividends & returns with live charts

The interactive Experiment Lab: add portfolios, tweak the knobs, and compare dividends & returns live. (divvy ui)


Every backtester on the internet (Portfolio Visualizer and friends) simulates a make-believe "$X every month." Divvy is different: it replays the exact money you actually invested — the real dates, the real dollar amounts, straight from your broker — into whatever portfolio you're curious about. So instead of a hypothetical, you get your answer:

💡 "If I'd put the exact money I actually invested into this basket of ETFs/stocks instead, how much more — in dividends specifically, and in total — would I have made?"

Dividends get reinvested (DRIP) into the same holding, so it compounds exactly like a real brokerage account.

Why it exists

No off-the-shelf tool does this. They all assume a clean, synthetic contribution schedule. Real investing is lumpy — you skip months, you add lump sums, you rebalance. Divvy replays your actual cash-flow history against a counterfactual portfolio, and reports on dividend income as a first-class metric (not just total return) — the thing dividend investors actually care about.

✨ Features

  • 🔁 Real-ledger replay — your actual contribution calendar, not a synthetic assumption
  • 🧺 Compare any number of "buckets" (portfolios) side by side from simple YAML
  • 💵 Dividends as a first-class metric — lifetime and trailing-12-month run-rate
  • 📈 DRIP compounding, money-weighted return (XIRR), equity & dividend charts
  • 🧪 Interactive Experiment Lab — a local web app to tweak weights with sliders and compare live
  • 🚀 Zero-data quickstart — try it in 10 seconds with synthetic mode
  • 🔌 Bring your own data — generic CSV, or a Fidelity ledger / 1099 importer
  • 🔒 Private by default — your financial data never leaves your machine

Install

pip install divvy-backtest          # core CLI (import + command are 'divvy')
pip install 'divvy-backtest[ui]'    # + interactive Experiment Lab

Or run from source with uv: uv sync.

⚡ Quickstart (no data, no files)

Backtest a hypothetical "$500/month since 2019" into a couple of dividend baskets — define them inline with --holdings (weights are auto-normalized), so this runs from anywhere:

pip install divvy-backtest

divvy compare --synthetic-monthly 500 --synthetic-start 2019-01-01 \
  --holdings "SCHD=45,DGRO=25,VYM=15,SDY=15" \
  --holdings "SCHD=40,VYM=20,SDY=20,SPYD=20"

Prefer files? Pass --bucket path/to/bucket.yaml instead (see Define a bucket).

…and out comes a side-by-side comparison, plus equity & dividend charts in results/<date>/:

          variant  total_contributed  total_dividends  trailing_12mo_dividends  ending_value  total_return_pct  xirr_pct
dividend_etf_core             7200.0           764.12                   294.02      11641.27             61.68     12.47
  high_yield_tilt             7200.0           958.87                   336.69      11277.28             56.63     11.61

(illustrative output from the bundled example data)


🧪 Interactive Experiment Lab

Prefer sliders to flags? Launch the local web app and tweak portfolios live — add/remove tickers, drag weights, and watch the comparison table and charts update:

pip install 'divvy-backtest[ui]'   # or: uv sync --extra ui
uv run divvy ui

It opens in your browser (running 100% locally — no data leaves your machine): pick a contribution source, edit one or more portfolios in the sidebar, hit Run comparison, and get headline metrics, a sortable table, and interactive value & dividend charts.


Bring your own contributions

Any broker can give you a list of what you invested and when. Put it in a two-column CSV:

date,amount
2021-01-04,200
2021-02-01,200
divvy compare --contributions-csv my_contributions.csv \
  --holdings "SCHD=45,DGRO=25,VYM=15,SDY=15"

See examples/contributions.csv for a full sample.

Define a "bucket" (a candidate portfolio)

A bucket is just a YAML file of tickers and target weights (must sum to 1.0):

name: My dividend basket
weights:
  SCHD: 0.50
  VYM: 0.15
  SDY: 0.20
  ABBV: 0.15

Pass as many --bucket flags as you like to compare them side by side — or skip files entirely with inline --holdings "SCHD=45,DGRO=25,VYM=15,SDY=15" (repeatable, weights auto-normalized).

Compare against your actual Fidelity account

If you export your Fidelity transaction history CSVs, Divvy can auto-derive both your real contribution calendar and the real dividends you received, and add your actual account as a comparison row:

divvy compare --ledger path/to/fidelity_history_csvs/ \
  --holdings "SCHD=45,DGRO=25,VYM=15,SDY=15" \
  --real-value 12345.67 --real-as-of 2026-07-03

Import dividends from a Fidelity 1099 (optional)

To reconstruct the dividend income you actually received from a Consolidated 1099 PDF (as a comparison baseline):

pip install 'divvy-backtest[pdf]'
uv run divvy import-1099 --pdf 2025-Consolidated-1099.pdf --out dividends_2025.csv

Note: a 1099 records dividends received, not what you bought — so it can't drive a backtest on its own (that needs your contribution calendar). It's a baseline helper. The parser targets Fidelity's 1099 layout; other brokers differ.


What the numbers mean

Column Meaning
total_contributed Sum of money you put in
total_dividends Cumulative dividends received over the whole period (reinvested)
trailing_12mo_dividends Dividend income in just the last year — your current annual income run-rate
ending_value Portfolio value today
total_return_pct (ending_value − contributed) / contributed
xirr_pct Money-weighted annualized return (accounts for contribution timing)
max_drawdown_pct Worst peak-to-trough drop of the basket (its own risk, independent of your cash-flow timing)
annual_vol_pct Annualized volatility of the basket — lower is calmer

Every comparison also includes an SPY benchmark row by default (disable with --benchmark none, or pick another ticker with --benchmark VTI), and a dividend-income-by-year chart so you can see income growth, not just a lifetime total.

Realism knobs (optional)

Model a taxable account, periodic rebalancing, and fund fees:

uv run divvy compare --contributions-csv my.csv --bucket buckets/mine.yaml \
  --dividend-tax-rate 0.20 \   # reinvest only after-tax dividends; adds a net-income column
  --rebalance annual \         # annual | quarterly | monthly (default: none, DRIP drifts)
  --expense-ratio 0.0006       # annual fund fee applied to every holding (0.06%)

All three are also available in the Experiment Lab under Realism (optional). Defaults leave behavior unchanged (tax-free DRIP, no rebalancing, no fees). The tax model is a flat-rate estimate — real dividend taxation (qualified vs. ordinary, brackets, state) is more nuanced.

Project a future income goal

How much must you invest monthly to reach a target dividend income? divvy project gives both a deterministic estimate and a Monte Carlo range (returns are random, not a single guess):

uv run divvy project --income 2000 --current-value 7500 --monthly 1000 --years 25

Outputs the required contribution across return assumptions, plus p10/p50/p90 ending-value and after-tax-income scenarios. Like all projections, these are assumption-driven ranges, not predictions.


⚠️ This is a backtester, not a crystal ball


Data sources

  • Prices & dividend history: yfinance (free, no key), cached to data/cache/.
  • Finviz Elite (optional): if you have a key, copy .env.example to .env and add it — used only for ad-hoc yield/screening lookups, not required for the core backtest.

🔒 Privacy

Your financial data never leaves your machine and is never committed: data/, results/, your personal buckets/, and .env are all gitignored. Only code and the fake examples/ data live in the repo.

Install / dev

uv sync --extra dev   # or: pip install -e '.[dev]'
uv run pytest

See CONTRIBUTING.md — new broker adapters and 1099 importers are especially welcome.

License

MIT — see LICENSE.

Found this useful? Give it a ⭐ — it helps other dividend investors find it.

Metadata

Release files for divvy-backtest 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for divvy-backtest 0.2.0
File Size Uploaded
divvy_backtest-0.2.0.tar.gz 24.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for divvy-backtest 0.2.0
File Interpreter ABI Platform
divvy_backtest-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 55.2 kB

Release files / divvy_backtest-0.2.0.tar.gz

Download URL divvy_backtest-0.2.0.tar.gz
Size 24.6 kB
Tags Source
SHA-256 checksum
How to use checksums
e3ff8fc3925f668bb6bcd36c765ab6d794e3d847d7de7ec1fbe226a9af08fea9
BLAKE2b-256 checksum
How to use checksums
2db93a93b55055becd830b55f79cb9e8cecf0e6b39800498d73f4f56c844d6c2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 5, 2026.

Transparency log

Release files / divvy_backtest-0.2.0-py3-none-any.whl

Download URL divvy_backtest-0.2.0-py3-none-any.whl
Size 30.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2e000c5ee54118ef31ff6143116154e0404c6b41576958795ddfeb8d6a9a7dc5
BLAKE2b-256 checksum
How to use checksums
151dc5ebcd7fb65f08444cb55a71e4ae8436b4ac2c3a9f12f76ce7e7becc4899
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 5, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page