Skip to main content

Cross-account wash sale detection for stocks, options, and ETFs — local-first, IRS Pub 550 rules, with a tax-harvest planner and pre-trade simulator.

Project description

net-alpha

Cross-account wash sale detection for stocks, options, and ETFs — local-first, IRS Pub 550, with a tax-harvest planner and pre-trade simulator.

CI PyPI Downloads Python 3.11+ Coverage License: MIT

Why · Quickstart · Features · Usage · IRS Rules · Service · Architecture · Development


Why net-alpha

Every brokerage tracks wash sales only within its own ecosystem. A loss sale on Schwab can be silently neutralized by a repurchase on Fidelity — and you won't know until well after tax season, long after the 30-day window to act has closed. The problem compounds when you trade options alongside the underlying, or hold ETFs that track the same index in separate accounts.

net-alpha gives you a single, unified view of your wash sale exposure across every account, asset class, and tax year — before it's too late to act.

[!NOTE] The package is published to PyPI as wash-alpha but the CLI command is net-alpha. Both names refer to the same project.


See it in action

All screenshots use the built-in demo dataset: TSLA, NVDA, AAPL, SPY puts, an SPX §1256 call, plus open holdings (MSFT, AMZN, GOOGL, META, AMD, VOO). Pick "Try the demo" on the welcome screen — or run net-alpha ui --demo.

Wash-sale ledger — every match across accounts, with confidence label and rule citation. The NVDA row is the case no single broker would flag: a loss closed in schwab/taxable silently neutralized by a buy in schwab/ira 14 days later, shown as a Cross-account source pill. SPY shows an exact-strike options match with a -8d lag because the replacement leg opened before the loss close.

Wash-sale ledger across accounts

Per-symbol drilldown — NVDA's timeline combines lots from both accounts in one view. KPIs (open lots, realized P/L vs. broker, disallowed loss, last trade) sit above; every row deep-links to the source trade so the chain stays auditable.

NVDA per-symbol drilldown

Pre-trade simulator — propose Sell 20 AAPL @ market and see realized P&L, FIFO lot consumption, and per-account wash-sale verdict before placing the order. The lot-strategy table compares FIFO / LIFO / HIFO / MIN_TAX / MAX_LOSS side-by-side with each strategy's after-tax P&L and wash-sale verdict.

Pre-trade simulator with lot-strategy comparison

Portfolio dashboard — KPIs, equity curve with brush-strip drag-to-reperiod, cash-deployment stacked-area chart, monthly P/L bars, allocation donut, top movers, and wash-sale watch — all accounts in one view. Tiles are drag-to-reorder and hideable via the "Edit layout" toolbar.

Portfolio dashboard


Quickstart

Requires Python 3.11+ and uv.

Web UI (recommended)

# Install with web UI support
uv tool install 'wash-alpha[ui]'

# (Optional) Install the always-on background service
net-alpha service install

# Open the dashboard — browser launches automatically
net-alpha ui

The UI runs at http://127.0.0.1:18765 (auto-picks the next free port in 18765–18775). With the service installed, prices refresh every 4 hours, forward-looking wash-sale checks run daily, and a snapshot backup runs at 03:30 UTC. Without it, net-alpha ui runs ephemerally until Ctrl-C.

[!TIP] First run? Pick "Try the demo" on the welcome screen for a guided tour — no CSV required. Replay any time from /tour.

CLI (no UI extras)

uv tool install wash-alpha
net-alpha schwab.csv --account personal --detail

Features

Detection

Capability Coverage
Cross-account wash sales Match a loss sale on one broker against a repurchase on another in a single pass
§1091 — equities, options, ETFs Exact ticker, exact contract, same-underlying option, or same-index ETF pair
Rev. Rul. 2008-5 IRA traps Taxable-account loss replaced by IRA/Roth/401(k)/HSA buy → permanently disallowed, no basis rollover possible
§1256 contracts SPX, NDX, RUT, VIX, OEX, XSP… exempt from §1091, 60/40 LT/ST split, year-end MTM
§1092 straddles Literal straddles, married puts, non-qualified covered calls, vertical spreads
ETF substantially-identical pairs Bundled index-tracking groups (S&P 500, Nasdaq-100, Russell 2000…); extend at ~/.net_alpha/etf_pairs.yaml

Every match carries a Confirmed / Probable / Unclear confidence label with rule citation and inline explanation.

Planning

  • Pre-trade simulator/sim shows lot consumption, realized P&L, and wash-sale verdict before you execute. Suggestion chips surface the largest unrealized loss, wash-sale risk, and largest gain for one-click sims.
  • Lot-selection strategies — compare FIFO / LIFO / HIFO / MIN_TAX / MAX_LOSS side-by-side on any Sell sim, each with its own independent wash-sale verdict.
  • Tax-harvest planner/tax/harvest/plan builds a ranked, editable harvest plan (greedy by tax saved, capped by §1211's $3,000 ordinary-loss limit). Honors user-declared PositionTargets so it never closes a position you want to keep.
  • Forward-looking watchlist — the always-on service forward-simulates every PositionTarget daily, surfacing deferred vs. permanent IRA-trap risk as colored pills on the Plan view.
  • Action Inbox — urgent items (imminent wash-sale tripwires, §1092 holding-period suspensions, broken reconciliation, missing basis) in one panel.
  • Verify — a global "Data check" badge in the site header that audits live KPIs against pure-function recomputes and one-click jumps to any divergence.

Reporting

  • After-tax performance — realized P&L after estimated taxes, with tax-drag breakdown and ST/LT/§1256 mix bar.
  • Capital-loss carryforward — auto-derived from prior years honoring §1212(b) cross-category netting and §1211 $3K cap, with hand-editable overrides.
  • Auditable explanations — every wash-sale flag includes rule citation, source trades, match reason, math, and confidence reasoning — inline in the UI or via --detail on the CLI.
  • Per-symbol reconciliation — cross-checks computed P&L against your broker's Realized G/L file (Schwab supported).
  • Data hygiene rollup — Imports page groups missing-basis / no-quote / missing-date rows so you can fix them in one pass.

Local & Private

[!IMPORTANT] Your trade data, account information, and P&L never leave your machine. Only ticker symbols are sent to Yahoo Finance for live quotes — and that can be disabled with prices.enable_remote: false in ~/.net_alpha/config.yaml.

  • Zero-knowledge, zero telemetry — everything runs on your local SQLite database at ~/.net_alpha/net_alpha.db.
  • No CDN at runtime, no Node toolchain — htmx, Alpine.js, ApexCharts, Lucide icons, and fonts are fully vendored under web/static/.
  • WAL-safe backups with optional encryptionnet-alpha backup snapshots via SQLite's online .backup() API (safe while the service writes), with optional AES-256-GCM encryption. Daily automated snapshots + inline pre-mutation backups before every import or removal.
  • Manual trade CRUD — add, edit, transfer, or delete trades from the web UI; wash sales recompute over the affected window automatically.

Usage

Web UI

net-alpha ui [--port 18765] [--no-browser] [--reload]
Page Highlights
/ Portfolio KPIs, equity curve (brush-strip, click-to-explain daily drivers, optional SPY benchmark), cash-deployment area chart, monthly P/L bars, allocation donut, top movers, wash-sale watch, drag-to-reorder layout
/positions All / stocks / options / at-loss / closed views; drag-to-reorder Plan view; multi-lot basis editor; per-row side pane with lot ladder, ST→LT clock, and wash-sale outlook
/sim Pre-trade simulator, suggestion chips, FIFO/LIFO/HIFO/MIN_TAX/MAX_LOSS strategy comparison
/tax After-tax performance, harvest queue, plan-builder, projection setup, wash-sale + exempt-match listings
/imports Drop-zone upload (trades or positions CSV), preview/commit, per-import detail, data-hygiene buckets
/ticker/{symbol} Per-symbol timeline, lot ladder, reconciliation vs. broker, lot edit and add-trade forms
/verify Findings table (BasisRecon, RealizedRecon, StaleReference…) with inline "Why?" explainer and one-click suppress
/settings Profile, density, ETF pairs, carryforward, account types, backups, service controls, about

CLI

# Import a CSV → recompute wash sales → print watchlist + YTD impact
net-alpha schwab.csv --account personal [--detail]

# Pre-trade simulation across every account holding the ticker
net-alpha sim TSLA 10 --price 180

# §1092 straddles currently open
net-alpha straddles [--detail]

# Manage past imports (recomputes wash sales on removal)
net-alpha imports
net-alpha imports rm 3 --yes

# Backups — manual snapshot, list all bundles, restore
net-alpha backup [--encrypt] [--note "before tax-day cleanup"]
net-alpha backups
net-alpha restore <bundle-id> --yes

# v1 → v2 schema migration (v2.0.x line only)
net-alpha migrate-from-v1 --yes

Supported Brokers

Broker Trade import Realized G/L reconciliation
Schwab (transactions CSV + Realized G/L CSV)
Additional brokers planned planned

To add a broker: implement the BrokerParser protocol at src/net_alpha/brokers/<name>.py, register it in brokers/registry.py, and optionally add a Realized G/L parser at src/net_alpha/audit/brokers/.


How the Rules Work

net-alpha strictly follows IRS Publication 550. A wash sale is triggered when you sell at a loss and buy a substantially identical security within 30 days before or after the sale.

Confidence labels

Label Meaning
🟥 Confirmed Definite wash sale under IRS Pub 550
🟨 Probable Likely; CPA review recommended before filing
🟦 Unclear Ambiguous; flag for professional review

§1091 — equities, options, ETFs

Scenario Confidence
Sold ticker X at a loss → bought ticker X within 30 days 🟥 Confirmed
Sold option at a loss → bought same option (exact strike + expiry) 🟥 Confirmed
Sold option at a loss → bought option on the same underlying 🟨 Probable
Sold ETF at a loss → bought the same ETF ticker 🟥 Confirmed
Sold ETF at a loss → bought ETF tracking the same index (e.g., SPY → VOO) 🟦 Unclear

§1256 contracts

Broad-based equity index options (SPX, NDX, RUT, VIX, OEX, XSP, …) are exempt from §1091. Their closed P&L is split 60/40 long-term/short-term per §1256(a)(3), regardless of holding period. Open positions at year-end are marked-to-market per §1256(a)(1), with FMV sourced via a cascade: Yahoo option close → Black-Scholes (underlying close + 30-day historical vol) → intrinsic if expired.

IRA-trap violations — Rev. Rul. 2008-5

When a loss in a taxable account is replaced by a substantially-identical buy in a tax-advantaged account (IRA / Roth / 401(k) / HSA) within ±30 days, §1091(a) still disallows the loss, but §1091(d)'s basis rollover cannot apply — the loss is permanently lost. These are classified as permanent_ira violations, distinct from deferred wash sales where the basis rolls into the replacement lot.

§1092 straddles

Same-underlying offsetting positions caught by IRC §1092:

  • Literal straddles — long call + long put on the same underlying
  • Married puts — long stock + long put on the same underlying
  • Non-qualified covered calls — long stock + short call that fails the §1092(c)(4) QCC test
  • Vertical spreads — long + short option of the same series, opposite legs

[!NOTE] The QCC test is a conservative approximation of IRS Notice 2003-31 — it errs on the side of flagging deeper-ITM calls. Loss-deferral computation, the §1092(b) modified wash-sale rule, and identified-straddle elections are deferred to a future release.


Always-on Service

net-alpha service is an opt-in launchd-supervised process that hosts the FastAPI app and runs four background jobs:

Job Schedule What it does
price_refresh Every 4 hours Refreshes quotes for all held and targeted tickers
washsale_watch Daily 04:00 + on every import Forward-simulates PositionTargets and surfaces §1091 risk pills on the Plan view
backup Daily 03:30 UTC WAL-safe snapshot of ~/.net_alpha/; prunes to 14 daily + 10 pre-mutation + 2 GB cap
verify Weekly Sunday 04:30 Audits live KPIs against pure-function recomputes; surfaces divergences in the Verify badge

Lifecycle commands

Command Effect
net-alpha service install Provision runtime venv, write launchd plist + sandbox profile, load the agent. Idempotent.
net-alpha service start / stop / restart Lifecycle control. stop survives reboots until start.
net-alpha service pause / resume Freeze background jobs while keeping the dashboard reachable.
net-alpha service status [--json] Health report.
net-alpha service logs [-f] View or tail the service log.
net-alpha service uninstall Remove plist, sandbox, and runtime venv. Data at ~/.net_alpha/net_alpha.db is preserved.

The /settings/service page exposes the same controls in the UI, plus a recent-runs history and a live status pill in the site header.

[!IMPORTANT] The runtime venv lives at ~/.net_alpha/venv/ — outside ~/Documents/ so launchd's TCC identity can reach it. To make the running service reflect code changes, re-run net-alpha service install. For active development, run net-alpha … directly from your editable .venv.


Architecture

CSV → BrokerParser → Trade (Pydantic) ──► SQLite (~/.net_alpha/net_alpha.db)
                                              │
                                              ├─► Wash-sale engine  (incremental ±30-day window, deferred + permanent_ira)
                                              ├─► §1256 classifier  (60/40 LT/ST split + year-end MTM)
                                              ├─► §1092 straddle detector
                                              ├─► Reconciliation    (vs. broker Realized G/L)
                                              └─► Portfolio / planner / pricing  (pure functions)
                                                                │
                                                  ┌─────────────┴─────────────┐
                                                  ▼                           ▼
                                            Typer CLI                 FastAPI + HTMX UI
                                                                              │
                                                                launchd-supervised service
                                                          (price_refresh · washsale_watch · backup · verify)
Layer Technology
Language Python 3.11+
Package management uv
Data models pydantic v2
ORM / storage sqlmodel over SQLite
CLI typer[all]
Web framework fastapi + Jinja2 (optional)
Frontend HTMX, Alpine.js, ApexCharts, SortableJS (all vendored)
Styling Tailwind CSS + Lucide icons (vendored)
Background jobs APScheduler
Encryption cryptography AES-256-GCM
Linting / formatting ruff
Testing pytest + factory-boy

Key design decisions:

  • Incremental recompute — only the ±30-day window around affected trade dates is recalculated on import or removal; no full-table scans.
  • Single SQLite DB — all trades, all years, all accounts in one file enables cross-year window detection without joins across files.
  • Pure engine functions — wash-sale logic is fully decoupled from storage and UI; every emitted violation is reproducible from source trades alone.
  • No business logic in web/ — the web layer calls existing public seams only (Repository, engine functions, audit/portfolio/planner pure functions).
  • Schema versioning — hand-written ALTER TABLE migrations tracked via a meta table; currently at v26.

Development

# Clone and install everything
git clone https://github.com/chen-star/net_alpha.git
cd net_alpha
uv sync --extra ui --extra dev

# Run tests
uv run pytest                            # full suite
uv run pytest tests/engine/             # by directory
uv run pytest -k "test_wash_sale"       # by pattern

# Lint and format
uv run ruff check .
uv run ruff format .

# Rebuild frontend assets (optional)
make build-css                           # Tailwind bundle
make vendor-lucide                       # Lucide icons
make vendor-apex                         # ApexCharts

See docs/DEVELOPMENT.md for the full setup guide, docs/TESTING.md for coverage thresholds and Playwright snapshot tests, and docs/ARCHITECTURE.md for a deeper dive into the component model.


Disclaimer

[!IMPORTANT] This tool is for informational purposes only and does not constitute tax or legal advice. Wash sale rules — especially around options and ETFs — involve unsettled areas of tax law. Any match labeled Probable or Unclear should be reviewed with a qualified CPA before making filing decisions.


If net-alpha saves you a CPA call this tax season, consider starring the repo — it's the best signal for other traders looking for a tool they can trust with their tax data.

Project details


Release history Release notifications | RSS feed

Download files

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

Source Distribution

wash_alpha-0.80.2.tar.gz (5.6 MB view details)

Uploaded Source

Built Distribution

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

wash_alpha-0.80.2-py3-none-any.whl (1.5 MB view details)

Uploaded Python 3

File details

Details for the file wash_alpha-0.80.2.tar.gz.

File metadata

  • Download URL: wash_alpha-0.80.2.tar.gz
  • Upload date:
  • Size: 5.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for wash_alpha-0.80.2.tar.gz
Algorithm Hash digest
SHA256 2c51bb27442ab746edbc0582dcb30863e5f6543b329443e913a1a95c68d16c9d
MD5 a4bb9b3fc2bd6c172cd24d443eaa393d
BLAKE2b-256 5d9fc31a6544ecdf545ec51be3277a5d1059e9649dcd4ddfd7fb9700b35ca7ad

See more details on using hashes here.

Provenance

The following attestation bundles were made for wash_alpha-0.80.2.tar.gz:

Publisher: release.yml on chen-star/net_alpha

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

File details

Details for the file wash_alpha-0.80.2-py3-none-any.whl.

File metadata

  • Download URL: wash_alpha-0.80.2-py3-none-any.whl
  • Upload date:
  • Size: 1.5 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for wash_alpha-0.80.2-py3-none-any.whl
Algorithm Hash digest
SHA256 358d195634bfde7e2795f4836f5fab55ee31521fefb5e97cce5522655967f86c
MD5 ef838b0746a3f5bcf16bd0a168c1c46c
BLAKE2b-256 0f8d91a0d1a6016a86e0cba502893eda08ef7821f001c7aa2dbe1e2924c8fb5d

See more details on using hashes here.

Provenance

The following attestation bundles were made for wash_alpha-0.80.2-py3-none-any.whl:

Publisher: release.yml on chen-star/net_alpha

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