Skip to main content

Replay your actual brokerage transaction history against a counterfactual portfolio bucket and compare dividend income + returns.

Project description

💸 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

[!WARNING] Divvy is an analysis tool, not investment advice — and backtest results are not gospel.

  • A backtest only tells you what already happened over one specific window. It says nothing about the future. A basket that crushed it over the last 3 years can easily lag over the next 3.
  • Past performance does not predict future results. Dividends can be cut, and any single stock can fall hard (the high-yield names that look best in a backtest often carry the most risk).
  • The projection helper (divvy.project) is built entirely on assumptions you choose (future return, yield, tax). Treat its output as a range of scenarios, not a promise.
  • Divvy does not know your taxes, fees, goals, or risk tolerance. Nothing here is personalized financial advice. Do your own research and, for real money decisions, talk to a licensed advisor.

Use Divvy to ask better questions about your portfolio — not to get a "winner" to blindly follow.


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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

divvy_backtest-0.2.0.tar.gz (24.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

divvy_backtest-0.2.0-py3-none-any.whl (30.6 kB view details)

Uploaded Python 3

File details

Details for the file divvy_backtest-0.2.0.tar.gz.

File metadata

  • Download URL: divvy_backtest-0.2.0.tar.gz
  • Upload date:
  • Size: 24.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for divvy_backtest-0.2.0.tar.gz
Algorithm Hash digest
SHA256 e3ff8fc3925f668bb6bcd36c765ab6d794e3d847d7de7ec1fbe226a9af08fea9
MD5 7ceb77e7bcd34bd8b71728c47d5ff2b7
BLAKE2b-256 2db93a93b55055becd830b55f79cb9e8cecf0e6b39800498d73f4f56c844d6c2

See more details on using hashes here.

Provenance

The following attestation bundles were made for divvy_backtest-0.2.0.tar.gz:

Publisher: publish.yml on DanMat/Divvy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file divvy_backtest-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: divvy_backtest-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 30.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for divvy_backtest-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2e000c5ee54118ef31ff6143116154e0404c6b41576958795ddfeb8d6a9a7dc5
MD5 798c72b8be8b610697c3ded621a9571c
BLAKE2b-256 151dc5ebcd7fb65f08444cb55a71e4ae8436b4ac2c3a9f12f76ce7e7becc4899

See more details on using hashes here.

Provenance

The following attestation bundles were made for divvy_backtest-0.2.0-py3-none-any.whl:

Publisher: publish.yml on DanMat/Divvy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page