Ildana

Bank → Actual

A free, fully local converter that turns the statement exports US banks actually produce into files Actual Budget imports cleanly. Nothing is uploaded anywhere — your statements never leave your machine.

Download Bank2Actual.html View source on GitHub

The problem

Actual Budget imports QIF, OFX, and QFX natively. But for many US accounts — Bank of America above all — the only export you can get is CSV, and those CSVs are genuinely broken: a summary preamble before the header row, balance rows mixed into the transactions, split debit/credit columns, inverted sign conventions, and unescaped quotes inside quoted fields that stop most parsers cold. Actual's importer is deliberately generic, so it can't be expected to absorb every bank's quirks.

Bank2Actual knows which bank produced the file — so it knows exactly which malformations to expect. Drop the statement in, get a clean four-column Date, Payee, Notes, Amount CSV that maps straight into Actual's import dialog.

Supported formats

BankExportQuirks handled
Bank of Americachecking / savings CSVsummary preamble, balance rows, unescaped quotes in descriptions
Bank of Americacredit card CSV
Chasechecking / credit CSVcategory + memo merged into Notes
Chasecredit card PDF statementfor accounts with no CSV download at all — text extracted locally, transaction year derived from the statement period
Citicredit card CSVsplit Debit/Credit columns merged; pending rows dropped so they don't duplicate when they settle
American Expresscredit card CSVsign convention flipped

The format is auto-detected from the file itself. If your account offers QFX, use that — Actual imports it natively. This tool exists for everything QFX doesn't cover.

It checks its own math

The design constraint we cared most about: you should never import a plausible-looking CSV that is silently missing a row. Chase PDF output must reconcile against the statement's Previous → New Balance, transaction by transaction; if it doesn't, no file is produced. Bank of America checking output is cross-checked against the statement's own "Total credits / Total debits" summary, and the summary line says MATCH or MISMATCH before you import. Either way, the numbers are checked and you are told.

Fully local, two ways to run it

In your browser: one self-contained HTML file. Open it, drop a statement on it, review the summary, save the converted file. Everything — including the PDF engine — is embedded; the page makes no network requests and your file never leaves your computer.

On the command line: a single Python script, standard library only (Chase PDFs additionally need pypdf).

python3 bank2actual.py statement.csv
python3 bank2actual.py *.csv --outdir converted --merge my-checking

--merge combines overlapping statements into one file, deduplicating the overlap while preserving legitimate same-day duplicate charges — and since 1.2.0 it keys on the bank’s own reference number where the export carries one, so a description rewritten between exports can’t double-count.

--format (new in 1.3.0) chooses the output layout: Actual (default), a spreadsheet-friendly generic CSV that opens directly in Excel or Sheets, or an experimental YNAB layout.

Get it

Download the one file most people need — Bank2Actual.html from the latest release — or clone the repository. MIT licensed. Using a bank we don't support yet? Open an issue with an anonymized sample of the header rows and we'll look at adding it.