skills/tax-loss-harvest-scanner/SKILL.md
Scans a taxable brokerage account for individual lots with unrealized losses above a configurable threshold, identifies wash-sale risks by checking recent buys and forward planned buys of substantially identical securities (across all household accounts including spousal), and proposes harvest pairs (sell-for-loss + immediate buy of a similar-but-not-identical replacement). Use for year-end tax planning, monthly TLH scans, after market drawdowns, or when user mentions tax-loss harvesting, TLH, wash sale, or harvest candidates.
npx skillsauth add lyndonkl/claude tax-loss-harvest-scannerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Tax-loss harvesting realizes losses in a taxable brokerage to offset gains (and up to $3,000 of ordinary income per year) without changing the household's economic position — the proceeds buy a similar-but-not-identical security, keeping market exposure intact. The hardest part is the wash-sale rule: a sale at a loss is disallowed if a substantially identical security is bought within 30 days in any household account, including a spouse's IRA.
This skill scans the taxable brokerage for harvest candidates and validates each against the wash-sale window across all household accounts.
The caller provides:
taxable_holdings — lots in the taxable brokerage, ideally per-lot: {symbol, shares, cost_basis_cents, value_cents, acquisition_date}. If only aggregate position-level data is available, mark lot_resolution: aggregate and document the limitation.all_account_transactions_60d — every buy/sell/dividend reinvest across every household account (taxable, 401k, IRA, spouse's IRA, HSA) in the last 60 days. Used for wash-sale detection.planned_buys_30d (optional) — dividend reinvest schedules, 401k contributions, automatic deposits — anything that will buy in the next 30 days.loss_threshold_cents — minimum unrealized loss to consider; default 50000 ($500).today — ISO date.Per IRS, a wash sale is triggered when a security sold at a loss is replaced by a "substantially identical" security purchased within the 61-day window centered on the sale date (30 days before, 30 days after).
For this skill:
TLH Scan Progress:
- [ ] Step 1: Filter taxable lots with unrealized loss >= loss_threshold
- [ ] Step 2: For each candidate, check 30-day backward wash-sale window
- [ ] Step 3: For each candidate, check 30-day forward planned-buys window
- [ ] Step 4: Identify substitute security (similar exposure, not identical)
- [ ] Step 5: Compute realized loss and tax savings estimate
- [ ] Step 6: Emit harvest proposals with explicit wash-sale guard
- [ ] Step 7: Surface candidates blocked by wash sale with the unblock date
unrealized_loss_cents = value_cents − cost_basis_cents (negative). Keep lots where |unrealized_loss_cents| ≥ loss_threshold_cents AND unrealized_loss_cents < 0.
If lot-level resolution is unavailable, fall back to position-level using weighted_avg_cost_basis. Document lot_resolution: aggregate and warn that some lots may have gains while others have losses; only the aggregate is loss-positive.
For each candidate symbol, look back 30 days in all_account_transactions_60d for any buy of the same or substantially-identical security. If found, the sale would be partially or fully washed — disallow until 30 days after the most recent buy. Surface as blocked_until: YYYY-MM-DD.
Look at planned_buys_30d for the same or substantially-identical security. Common cases:
Resolutions:
Pick a substitute that maintains market exposure. The skill carries a default mapping (see Substitute pairs). The substitute must:
tax_savings_cents ≈ |realized_loss_cents| × marginal_rate, where marginal rate is provided or defaulted to 24% for offsetting ordinary income up to $3,000 and 15% for offsetting long-term gains. Surface both.
Each proposal includes:
For losses that are blocked, emit a blocked[] list with unblock_date so the user knows when the candidate becomes harvestable.
Reasonable starting pairs (skill emits these as proposed, not authoritative):
| Sell | Buy substitute | Rationale | |---|---|---| | VTI (Total US Market) | ITOT (Total US Market, iShares) | Different family, similar exposure | | VOO (S&P 500) | VTI (Total US Market) | Different index | | VXUS (Total Intl) | IXUS (Total Intl, iShares) | Different family | | BND (US Aggregate Bond) | AGG (US Aggregate Bond, iShares) | Different family, same exposure — borderline; prefer a slightly different index like SCHZ | | IEFA (Developed Intl) | VEA (Developed Intl) | Different family | | FXAIX (Fidelity S&P 500) | FSKAX (Fidelity Total Market) | Different index |
Always document the substitute as a suggestion — the user verifies appropriateness for their plan.
{
"as_of": "2026-04-25",
"candidates_total": 5,
"harvest_proposals": [
{
"id": "tlh_20260425_001",
"sell": {
"account_id": "acc_inv_fid_001",
"symbol": "VTI",
"lots": [
{ "acquisition_date": "2024-09-12", "shares": 35, "cost_basis_cents": 985250, "value_cents": 935500 }
],
"realized_loss_cents": -49750
},
"buy": {
"account_id": "acc_inv_fid_001",
"symbol": "ITOT",
"shares": 38,
"value_cents": 935500,
"rationale": "Substantially similar exposure to VTI, different index family — not substantially identical per common interpretation"
},
"wash_sale_check": {
"backward_30d_clear": true,
"forward_30d_clear": false,
"blockers": [
{ "type": "401k_payroll_buy", "symbol_substantially_identical": "FSKAX", "buy_date": "2026-05-02" }
],
"remediation": "Change 401k allocation away from FSKAX for the 2026-05-02 cycle, or harvest a different security"
},
"tax_savings_estimate_cents": 11940,
"drip_disable_required": true,
"status": "remediation_required"
}
],
"blocked": [
{
"symbol": "VXUS",
"reason": "spouse_ira_buy_within_30d",
"unblock_date": "2026-05-12"
}
],
"warnings": []
}
testing
--- name: advisory-edit description: A strict advisory-only editing discipline for a writer who dictates ("speaks out") essays and wants help WITHOUT having their voice changed. The editor directs structure, flags grammar, and suggests strategic language — but never modifies the writer's text unless the writer explicitly says "apply" / "make that change" / "rewrite this." Produces a line-referenced, suggestion-only critique where every item is marked the writer's call. Four passes: structural, l
testing
Provides the house style for analyst-grade strategist writing — third-person register with sparing first-person, no em dashes, no "not X, not Y, not Z" negation cascades, numbered footnote citations rather than inline source parentheticals, specific opinion-signaling phrases, and topic-forward paragraph structure modeled on voice patterns observed in Damodaran's Musings on Markets and Thompson's Stratechery. Use when consolidating working notes into a finished long-form strategist or analyst report that must read as written by a senior human analyst rather than an AI assistant.
testing
Renders a markdown report to a PDF using pandoc with xelatex (11pt serif body, 1-inch margins, numbered footnotes, formal heading hierarchy). Requires a one-time install of pandoc and a LaTeX engine on the user's machine — basictex on macOS or texlive-xetex on Linux. Does not attempt automatic install. Fails loudly with the exact install commands if pandoc or xelatex is missing on the user's PATH. Use when producing a finished strategist or analyst report PDF from a polished markdown source.
testing
Produces step-by-step computational walkthroughs of vector and matrix operations as a sequence of numbered "frames", showing the explicit state at each step. The text-equivalent of a 3Blue1Brown animation — each frame shows what changed and why, so the learner can re-trace the operation by hand. Use when the learner needs to *see* a computation unfold (eigenvalue computation, attention with 3 tokens, gradient descent step, SVD on a 2×2, layer norm on a 3-vector, softmax of a small input), when an explanation has been given but the learner needs to ground it in a worked example, or when introducing an operation that's intimidating in symbol form but trivial in pencil-and-paper form.