Skip to content

Repository files navigation

CIPythonLast CommitStargazersIssuesMIT License


StonkSmith

StonkSmith

One sheet to rule them all. “how much money do I actually have?”
Explore the docs »

Report Bug · Request Feature

$ uv run stonksmith --help
usage: stonksmith [-h] [--verbose] [--debug] [--quiet] [--version]
                  {ally,fidelity,schwab529plan,snaptrade,tsp} ...

==================================================
__ _               _     __           _ _   _
/ _\ |_ ___  _ __ | | __/ _\_ __ ___ (_) |_| |__
\ \| __/ _ \| '_ \| |/ /\ \| '_ ` _ \| | __| '__
_\ \ || (_) | | | |   < _\ \ | | | | | | |_| | | |
\__/\__\___/|_| |_|_|\_\___/_| |_| |_|_|\__|_| |_|

==================================================
        Aggregate everything in one dashboard
        Written by: @Gerrrt

Version : 0.1.0
Codename: Forrest Gump

Brokers:
  Available Brokers

  {ally,fidelity,schwab529plan,snaptrade,tsp}
    ally                Brokerage accounts at https://live.invest.ally.com
    fidelity            Brokerage and retirement accounts at https://www.fidelity.com
    schwab529plan       College Savings Account at https://www.schwab529plan.com
    snaptrade           Every brokerage connected through https://snaptrade.com
    tsp                 Thrift Savings Plan, valued from published share prices
Table of Contents
  1. About The Project
  2. Getting Started
  3. Usage
  4. Roadmap
  5. Security
  6. Contributing
  7. License
  8. Contact
  9. Acknowledgments

About The Project

StonkSmith forges your financial data into a single source of truth. It scrapes, aggregates and syncs your investment accounts into one Google Sheets dashboard — holdings, balances, transactions, net worth over time and an asset allocation breakdown, all rendered from databases it owns rather than from whichever app happened to be open.

Absolutely no day-trading.

No more bouncing between a squad of applications each meant for one account, because no current app just works for all the accounts you own. Think of it as a personal financial command center: it scales with your life as you gain wealth, takes new platforms without ceremony, and answers the one question those five apps between them will not.

ONE SHEET TO RULE THEM ALL!

Project structure

StonkSmith/
|--- src/
|    |--- stonksmith/        # the one name this project installs
|         |--- main.py       # CLI entry point
|         |--- etc/          # config, logging, connection, shells, paths,
|         |                  #   records, database, the canonical row shape
|         |                  #   and the one thing that writes it to a sheet
|         |--- brokers/      # one package per broker (broker.py + helpers)
|         |--- modules/      # per-broker scrape/sync modules
|         |--- loaders/      # dynamic broker and module loading
|         |--- helpers/      # db, sheets, logging helpers
|--- docs/                # the reference chapters this file summarises,
|                         #   live verification, what a schedule can carry,
|                         #   and what was decided against
|--- scripts/             # one-off setup and probe scripts
|--- tests/
|--- pyproject.toml

Everything lives under stonksmith/ so that the wheel installs exactly one importable name. It used to install six — main, etc, helpers, modules, loaders, brokers — straight into site-packages, where any of them could collide with an unrelated package in the same environment.

Each broker is one package. brokers/<name>/broker.py holds the login class and publishes it as Broker, optionally alongside broker_args.py and any parser.py. A directory containing broker.py is a broker — that is how BrokerLoader discovers them, scanning src/stonksmith/brokers/ first and then ~/.stonksmith/brokers/.

broker.py is the only file a broker needs. Without a database.py it gets BrokerDatabase, and without a db_navigator.py it gets BrokerNavigator — which is what every bundled broker now takes, SnapTrade's navigator aside. A broker that does ship one and gets it wrong is reported rather than quietly given the default: the file exists because somebody meant something by it.

A broker that raises while loading is reported by name and skipped — it registers no subparser and is simply unavailable for that run, so a half-finished broker under ~/.stonksmith/brokers/ never takes the rest of the tool down with it.

Your own brokers and modules import from stonksmith. — see what to import, which also covers the pre-namespace names, still accepted under deprecation until 1.0.

Brokers come in three shapes. A scraper posts a form and reads the response, and subclasses Connection: Schwab 529. A browser-backed broker has a login guarded by bot detection, a session worth keeping between runs, and a page that only exists after JavaScript has run; it subclasses BrowserConnection, which owns the whole Playwright lifecycle: Fidelity and Ally. An API-backed broker has no login at all — its key lives in config and the OS keyring — and subclasses ApiConnection: SnapTrade, and TSP, which holds no key either because the data it reads is published.

What a module is handed and what it must return is docs/modules.md; src/stonksmith/modules/example.py is the annotated template.

Note

Not every claim here rests on a live run. Green tests say the code does what it was written to do, which is not the same as saying the site still looks the way it did when the parser was written. Four of the five brokers have been run against the real thing, and the sheet has been read back off a real spreadsheet; eleven claims are still open. Five are the fidelity broker, which has never been run — the Fidelity accounts SnapTrade reaches are settled, but that is an API answering, not a browser getting past bot detection to scrape a summary page. Three wait on data rather than on effort — whether the Transactions tab holds every movement or only the newest five hundred, whether the Net Worth series carries across brokers that scraped on different days, and whether a 529 with more than one beneficiary attributes its movements to the right one. Two wait on a condition occurring at all: a SnapTrade connection lapsing, and an account's holdings going stale. One waits only on a run, being whether each allocation block's slices add up on a real tab. docs/live-verification.md is the record of which is which, claim by claim, and this note summarises it rather than being maintained beside it.

Built With

Python Playwright SQLAlchemy Google Sheets SnapTrade Rich

(back to top)

Getting Started

Prerequisites

Python 3.14 and uv. Fidelity and Ally drive a real browser, so those two also need the Playwright runtime; the other three brokers need nothing beyond the install below.

Installation

From PyPI, which gets you the two console scripts and nothing else:

uv tool install stonksmith

Or from a clone, which is what you want to change anything:

git clone https://github.com/Gerrrt/StonkSmith.git
cd StonkSmith
uv sync

Both are supported and both stay current — see Supported versions. Every command below is written for the clone, so drop the uv run if you installed the tool.

Then, if you intend to use Fidelity or Ally:

uv run playwright install firefox

Run that again after any uv sync that moves Playwright. Each release pins a new browser revision, so an already-installed Firefox stops satisfying it and the next run fails with Could not start browser, naming an executable that is not there.

(back to top)

Usage

List the modules available for a broker:

uv run stonksmith schwab529plan -L

Run a module against an account:

uv run stonksmith schwab529plan -M schwab529plan -u <username> -p <password>

Or use a credential stored in the database instead of passing it on the command line:

uv run stonksmith schwab529plan -M schwab529plan -id 1

Brokers

Five brokers, and they do not work alike — one needs you to sign in by hand every time, one needs no credential at all. What each needs and what a run of it does is docs/brokers.md; this table is the index into it.

Broker Shape What it needs from you
Fidelity Browser A manual sign-in once; the session is reused until it expires. Or link it through SnapTrade and skip the browser
Ally Invest Browser A manual sign-in every scrape — Ally honours no restored session. --from-prices revalues between scrapes with no browser at all
SnapTrade API A free Personal API key, and one browser step per brokerage every few weeks. Covers Schwab, Fidelity, Vanguard and the rest through one key
Schwab 529 Scraper A stored credential. A form post and two page reads — no browser, nothing to expire
TSP API Nothing daily. Share prices are published; units come from a quarterly statement or a config line

Adding a brokerage SnapTrade covers is an operator action, not a code change — no new broker, module, database or tab. Vanguard is the standing example.

Output

A run reports what it did. That is the default and it used to not be: every progress line — including the yellow [!] warnings — logs at INFO, and the default level was ERROR, so a sync could read a statement, write the snapshot to the database and update the Google Sheet while printing nothing but a progress bar. Success and doing nothing at all looked identical.

Flag Shows
(none) What the run did, and anything that went wrong.
--quiet Failures only. This is what an unattended run wants.
--verbose Same as the default, but wins over --quiet — useful for seeing inside a wrapper script that hardcodes it.
--debug Everything, including internals.

All four work on either side of the broker name.

Exit codes

The exit status reflects what the run actually did, so a cron entry, systemd timer or CI step can tell whether the sync worked.

Code Meaning
0 The run did its work. A Google Sheets failure after the balances reached the database is still a success — the data is saved, and the log says the dashboard was not updated.
1 The run did not complete: unknown broker or module, could not connect or log in, a module reported it did nothing, only some of the requested modules loaded, or nothing reached the database.
130 Interrupted (128 + SIGINT). Distinct from 1 so a scheduler can page on a real failure and shrug at a human pressing Ctrl-C.

stonksmithdb <command> reports the same way: 0 when the command did its work, 1 when it did not. For stale that means 1 when any account has gone stale or a database would not open — a status a crontab can act on, for the one failure none of the codes above can catch, because nothing broke.

A partial module load still runs the modules that did load — partial data beats none — but reports 1 rather than claiming success.

Where the data goes

Every broker writes to its own SQLite file at ~/.stonksmith/workspaces/<workspace>/<broker>.db, holding four tables: accounts, account_snapshots, holdings and transactions. Money is stored as a number with the source's own text kept beside it, so a site that changes its formatting costs you a parse rather than the record.

Browse and manage it from the shell:

uv run stonksmithdb

The tables, the columns, the shell's commands and what a migration does on open are docs/database.md.

Ask whether anything has quietly stopped updating — no login, no network:

uv run stonksmithdb stale

It exits 1 when any account's as-of date is missing, unreadable or more than a week old. That is the one question a schedule cannot otherwise ask: every other step reports when it breaks, and none reports when it stops happening.

The sheet

StonkSmith owns five tabs and refuses to touch anything else. Accounts, Holdings, Transactions, Net Worth and Dashboard are created on the first sync and rewritten in full every run — so anything of your own goes on a tab of your own, and pulls across with a formula.

That is a refusal rather than a convention. Each tab's first cell carries a banner; before clearing, StonkSmith reads it back. A tab carrying the banner is its own, an empty tab is adopted, and a tab with anything else on it is refused by name with nothing written. The run still exits 0 — the scrape reached the database before the sheet was touched.

Rebuild the tabs from the databases at any time, with no login anywhere:

uv run stonksmithdb sheet

What each tab promises, why the dashboard has to be constructed rather than read, and what verify checks that a successful sync cannot show are docs/sheet.md.

Scheduling

The five brokers do not schedule alike, and two of them do not schedule at all. That is the part a crontab cannot tell you, and getting it wrong is expensive in a specific way: a cron job that errors every night gets muted, and after that the portfolio has stopped updating with nothing to say so.

Broker On a schedule
tsp Yes. No credential in the daily path
snaptrade Yes, until the connection expires — a browser step every few weeks
schwab529plan Yes. A form post with a stored credential
ally --from-prices only, which reprices a stale unit count rather than scraping
fidelity No. Link it through SnapTrade instead

Weekdays after the close, one process per broker — broker is a positional subcommand, so there is no --all — staggered, because two runs inside the same UTC second collapse into one snapshot:

PATH=/usr/local/bin:/usr/bin:/bin

30 18 * * 1-5  cd ~/StonkSmith && uv run stonksmith tsp -M tsp --quiet
35 18 * * 1-5  cd ~/StonkSmith && uv run stonksmith snaptrade -M snaptrade --quiet
40 18 * * 1-5  cd ~/StonkSmith && uv run stonksmith schwab529plan -M schwab529plan -id 1 --quiet
45 18 * * 1-5  cd ~/StonkSmith && uv run stonksmith ally -M ally --from-prices --quiet
50 18 * * 1-5  cd ~/StonkSmith && uv run stonksmithdb sheet

Warning

There is no fidelity line, and the ally line is not a scrape. Ally honours no restored session, so --from-prices values the account from today's published close and the units the last signed-in run recorded — exact arithmetic on a number that goes quietly wrong the moment a deposit lands. It says so on every account it values, and a schedule that mails only on failure will never show anybody that line. Re-run --manual-login after a deposit; the schedule cannot, and will not ask.

The sheet goes last, and it reports: stonksmithdb sheet exits 0 when the tabs were rewritten and 1 when the sheet was unreachable, a tab refused, or a broker database could not be read — a total short by a whole broker being the failure that must not be quiet.

That schedule is committed as scripts/stonksmith.cron, commented and ready to paste into crontab -e. Every line in it is a no-op or a nightly failure until its broker is set up — Ally in particular refuses and exits 1 until a --manual-login run has recorded some units — so read Before the first night before installing it.

docs/scheduling.md is the record: what each broker can do unattended, what --from-prices is and is not, what has to be true before the first night, and which of these claims a live run has actually settled. This section summarises it rather than being maintained beside it.

(back to top)

Roadmap

  • Multi-broker support (Fidelity, Schwab and anything else via SnapTrade, plus Ally Invest and Schwab 529 scrapers, and TSP from public data, for what SnapTrade does not cover)
  • Automatic data scraping (requests + Playwright)
  • Google Sheets sync
  • CLI commands for automation
  • Credentials stored in the OS keyring
  • Account history: numeric balances, holdings and transactions over time
  • Net worth tracking over time
  • Asset allocation breakdown — by account kind and by position, both free from what the databases already hold, plus by asset class from a mapping you keep in ~/.stonksmith/stonksmith.conf. Sector and region are still absent: nothing states them and nothing here guesses
  • Scheduling (cron), for the brokers that run unattended — three of five, plus Ally in a reduced mode. Fidelity is replaced by SnapTrade, not scheduled
  • More brokers. Vanguard needs no code at all; link it through SnapTrade
  • Settle whether the Transactions tab windows at five hundred rows (#141) — blocked on a broker with the movement volume
  • Settle whether the Net Worth series carries across brokers (#149) — blocked on a workspace whose brokers scraped on different days

See the open issues for a full list of proposed features (and known issues).

(back to top)

Security

This project handles sensitive financial data.

Current safeguards:

  • Secrets live in the OS keyring (Keychain on macOS, Secret Service on Linux, Credential Locker on Windows). The SQLite database stores only a reference such as schwab529plan:alice, never the secret itself.

  • show creds masks secrets. Set audit_mode and reveal_chars_of_pwd in ~/.stonksmith/stonksmith.conf to reveal a short prefix when you need to tell two credentials apart.

  • export creds writes the keyring reference, never the secret.

  • Databases created before this change are migrated automatically on first open: each plaintext password moves into the keyring and the column is cleared in place.

  • Everything StonkSmith writes is owner-only0600 for files, 0700 for the directories under ~/.stonksmith. That covers the databases, the config, the run log, page captures, the saved browser session and the Playwright trace.

Important

Passing -p on the command line still exposes the secret to your shell history and process list. Prefer add creds in stonksmithdb plus -id. Never commit credentials to source control.

SECURITY.md is the full account, including the risks this project accepts rather than solves — the CDP debugging port, the width of the Google grant, and what a migrated database still has in its freed pages — and how to report a vulnerability.

(back to top)

Contributing

This is currently a personal project, but contributions may open up in the future.

CONTRIBUTING.md has the gates, the commit and branch conventions, the test house rules, and the handful of settings whose reasons are worth reading before changing them. The four gates are:

uv run ruff check
uv run ruff format --check
uv run ty check
uv run pytest -q --cov --cov-fail-under=87

The version lives in pyproject.toml and nowhere else. --version and the banner read it off the installed distribution, so bumping it is one edit followed by uv sync — and tests/test_version_single_source.py fails if the two ever part company. The codename beside it in src/stonksmith/etc/cli.py is the one piece still written by hand, because nothing can derive one.

(back to top)

License

Distributed under the MIT License. See LICENSE for more information.

(back to top)

Contact

Garrett Allen — @Gerrrt

Project Link: https://github.com/Gerrrt/StonkSmith

(back to top)

Acknowledgments

The three sources StonkSmith reads that it did not have to build:

  • SnapTrade — one key covering the brokerages that would otherwise each need a scraper
  • TSP — publishes its share prices, which is what lets that broker run with no credential at all
  • DFAS — publishes the military pay tables behind the TSP contribution accrual

(back to top)

About

A Python-based tool that scrapes and aggregates investment account data into a centralized Google Sheets dashboard for real-time net worth tracking.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages