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
Cluster a conference's event records into a small set of coarse themes with finer sub-clusters, an explicit outlier bucket, and soft (multi-membership) affinities — using the hybrid embed-then-label pipeline (embed abstracts, reduce, density-cluster, then LLM-label the clusters) when embedding libraries are available, and an LLM-reasoned hierarchical fallback when they are not. Embeddings do the grouping; the LLM only names the groups. Conference-agnostic. Use when turning structured event records into a navigable theme map for preference elicitation and scheduling, when you need 6-8 reasonable themes rather than 20 muddy ones, or when overlapping talks must belong to more than one theme. Trigger keywords - theme clustering, cluster talks, embed then label, soft membership, outlier talks, conference themes, topic map.
development
Build a personal conference schedule as a constraint-optimization problem — hard constraints (no time overlap, room-to-room travel time, capacity/registration, the attendee's own must-attends and blackouts) plus a user-owned weighted objective trading interest against breadth, pacing (maximize contiguous free time), and serendipity. Surfaces unbreakable conflicts (two high-value overlapping talks the model cannot rank) as decisions for the human rather than silently picking, and reports what each choice traded away. Conference-agnostic. Use to turn a preference profile plus a theme map into a day-by-day plan, to resolve overlapping sessions, or to balance a packed vs paced schedule. Trigger keywords - schedule optimization, conference schedule, constraint optimization, overlapping talks, contiguous free time, conflict surfacing, packed vs paced.
development
Parse a heterogeneous conference program (markdown, HTML, PDF-derived text, or JSON) into normalized event records with per-field confidence scores and independent classification axes (topic, depth, format, prerequisites, recorded, capacity). Detects the program's format before extracting, treats every inferred field as uncertain (present vs inferred vs missing), and flags thin or missing abstracts so downstream enrichment can target them. Conference-agnostic. Use when ingesting a conference or event schedule into a structured store, normalizing a talk/session list, or extracting per-session metadata with calibrated confidence. Trigger keywords - program ingestion, parse schedule, session extraction, event records, conference program, talk metadata, per-field confidence.
development
Build a personalized preference profile from a small number of well-chosen, cluster-grounded questions instead of a long survey. Represents the person's interests as an uncertainty region over the theme map, picks the single highest-information-gain choice-based question (contrasting real talks from different clusters), balances exploiting known interests against exploring uncertain ones, deliberately injects outlier probes to fight selection bias, and stops as soon as the schedule would be stable. Also elicits the user-owned objective weights and hard constraints. Interactive — runs where it can actually ask the person. Conference-agnostic. Use to turn a theme map into a preference profile, to decide what to ask a conference attendee, or to elicit scheduling priorities. Trigger keywords - preference elicitation, ask few questions, information gain, choice-based questions, selection bias probe, objective weights, attendee preferences.