Tax-loss harvesting opportunity identification, scoring, and planning with wash sale compliance and annual carryforward tracking
npx skills add https://github.com/agiprolabs/claude-trading-skills --skill tax-loss-harvesting
Identify, score, and plan tax-loss harvesting (TLH) opportunities across a crypto portfolio. This skill covers unrealized-loss ranking, net-benefit calculation, wash sale compliance, annual loss carryforward tracking, and year-end "use it or lose it" strategies.
> Disclaimer: This skill provides informational analysis only. It is NOT tax advice. Tax rules vary by jurisdiction and change frequently. Consult a qualified tax professional before making any tax-related trading decisions.
Tax-loss harvesting is the practice of intentionally realizing investment losses to offset realized capital gains, thereby reducing your current-year tax liability.
| Holding Period | Classification | Typical Tax Rate |
|---------------|---------------|-----------------|
| < 1 year | Short-term capital gain/loss | Ordinary income rate |
| >= 1 year | Long-term capital gain/loss | Preferential rate (0-20%) |
Short-term losses first offset short-term gains; long-term losses first offset long-term gains. Remaining net losses cross over to offset the other category.
If total net losses exceed total gains, the excess is deductible against ordinary income up to $3,000 per year ($1,500 if married filing separately). Any remaining loss carries forward indefinitely to future tax years.
Not all unrealized losses are equally valuable to harvest. This skill scores each opportunity on four dimensions:
Larger dollar losses provide more tax savings. The raw loss is the difference between current market value and cost basis.
unrealized_loss = current_value - cost_basis # negative when loss
tax_savings = abs(unrealized_loss) * marginal_tax_rate
A position approaching the 1-year holding mark deserves special consideration:
days_held = (today - acquisition_date).days
days_to_long_term = max(0, 365 - days_held)
Positions with fewer days remaining until long-term are more urgent to evaluate because once they cross 365 days, a short-term loss becomes a less-valuable long-term loss.
The IRS wash sale rule prohibits claiming a loss if you buy a "substantially identical" security within 30 days before or after the sale. In crypto, the exact application is evolving, but prudent planning avoids re-entering the same token within the 61-day wash sale window (30 days before + sale day + 30 days after).
Correlation scoring: If you hold (or plan to re-enter) a position that is highly correlated with the harvested asset, wash sale risk increases. Score this as:
wash_sale_risk = 1.0 # if same token re-entry planned within 30 days
wash_sale_risk = correlation_coefficient # if correlated substitute held
wash_sale_risk = 0.0 # if no re-entry or uncorrelated substitute
Higher wash sale risk reduces the effective score of the opportunity.
A harvested loss is only immediately useful if there are realized gains to offset. Score opportunities higher when:
offset_efficiency = min(1.0, available_matching_gains / abs(unrealized_loss))
def tlh_score(
unrealized_loss: float,
days_to_long_term: int,
wash_sale_risk: float,
offset_efficiency: float,
weights: dict | None = None,
) -> float:
w = weights or {
"magnitude": 0.35,
"urgency": 0.25,
"wash_safety": 0.20,
"offset_match": 0.20,
}
magnitude_score = min(abs(unrealized_loss) / 10_000, 1.0)
urgency_score = max(0, 1.0 - days_to_long_term / 365)
wash_safety_score = 1.0 - wash_sale_risk
return (
w["magnitude"] * magnitude_score
+ w["urgency"] * urgency_score
+ w["wash_safety"] * wash_safety_score
+ w["offset_match"] * offset_efficiency
)
Harvesting a loss is not free. Transaction costs (swap fees, slippage, gas) reduce the benefit.
def net_benefit(
unrealized_loss: float,
marginal_tax_rate: float,
transaction_cost: float,
re_entry_cost: float = 0.0,
) -> float:
"""Compute net dollar benefit of harvesting a loss.
Args:
unrealized_loss: Negative number representing the loss.
marginal_tax_rate: Applicable tax rate (0.0 to 1.0).
transaction_cost: Cost to execute the sell (fees + slippage).
re_entry_cost: Cost to re-enter a substitute position.
Returns:
Net benefit in dollars. Positive means harvesting is worthwhile.
"""
tax_savings = abs(unrealized_loss) * marginal_tax_rate
total_costs = transaction_cost + re_entry_cost
return tax_savings - total_costs
Rule of thumb: Only harvest when net_benefit > 0 by a meaningful margin. Very small losses are not worth the transaction costs and operational complexity.
Near December 31, evaluate whether to accelerate harvesting:
The wash sale rule applies to purchases of substantially identical securities within:
If triggered, the disallowed loss is added to the cost basis of the replacement shares, deferring (not eliminating) the tax benefit.
| Strategy | Description | Trade-off |
|----------|------------|-----------|
| Wait 31 days | Sell, wait 31 days, re-buy | Market exposure gap |
| Substitute asset | Sell, immediately buy a non-identical but correlated asset | Tracking error |
| No re-entry | Sell and stay out | Lost upside |
| Double-up | Buy additional shares, wait 31 days, sell original lot | Capital intensive |
def compute_carryforward(
realized_gains_st: float,
realized_gains_lt: float,
realized_losses_st: float,
realized_losses_lt: float,
prior_carryforward: float = 0.0,
annual_deduction_limit: float = 3_000.0,
) -> dict:
"""Compute net tax position and carryforward.
Returns dict with keys:
net_st, net_lt, total_net,
deduction_used, carryforward
"""
net_st = realized_gains_st + realized_losses_st # losses are negative
net_lt = realized_gains_lt + realized_losses_lt
total_net = net_st + net_lt - prior_carryforward
if total_net >= 0:
return {
"net_st": net_st, "net_lt": net_lt,
"total_net": total_net, "deduction_used": 0.0,
"carryforward": 0.0,
}
excess_loss = abs(total_net)
deduction_used = min(excess_loss, annual_deduction_limit)
carryforward = max(0, excess_loss - annual_deduction_limit)
return {
"net_st": net_st, "net_lt": net_lt,
"total_net": total_net,
"deduction_used": deduction_used,
"carryforward": carryforward,
}
| Capability | Description |
|-----------|-------------|
| Opportunity scanning | Identify all positions with unrealized losses |
| Multi-factor scoring | Rank by magnitude, urgency, wash safety, offset match |
| Net benefit analysis | Compare tax savings against transaction costs |
| Wash sale tracking | Flag positions within the 61-day window |
| Carryforward calculator | Track annual $3K limit and loss carryforward |
| Year-end planning | Prioritize harvesting before December 31 |
| Harvesting plan output | Generate actionable plan with sell orders and re-entry dates |
from datetime import date
# Define a portfolio position
position = {
"symbol": "TOKEN-A",
"cost_basis": 10_000.0,
"current_value": 6_500.0,
"acquisition_date": date(2025, 8, 15),
"quantity": 500.0,
}
unrealized_loss = position["current_value"] - position["cost_basis"] # -3500
days_held = (date.today() - position["acquisition_date"]).days
days_to_lt = max(0, 365 - days_held)
# Score the opportunity
score = tlh_score(
unrealized_loss=unrealized_loss,
days_to_long_term=days_to_lt,
wash_sale_risk=0.0,
offset_efficiency=0.8,
)
print(f"TLH score: {score:.3f}")
# Calculate net benefit
benefit = net_benefit(
unrealized_loss=unrealized_loss,
marginal_tax_rate=0.35,
transaction_cost=15.0,
re_entry_cost=15.0,
)
print(f"Net benefit: ${benefit:.2f}")
| File | Description |
|------|-------------|
| references/planned_features.md | TLH mechanics, scoring formula, wash sale interaction, carryforward rules, year-end strategies |
| scripts/harvest_scanner.py | Demo scanner: score opportunities, generate harvesting plan, compute net benefit |
> Important: This skill provides analytical tools for informational purposes only. All tax-related decisions should be reviewed by a qualified tax professional. Tax laws vary by jurisdiction and are subject to change.
Guide users through a structured workflow for co-authoring documentation. Use when user wants to write documentation, proposals, technical specs, decision docs, or similar structured content. This workflow helps users efficiently transfer context, refine content through iteration, and verify the doc works for readers. Trigger when user mentions writing docs, creating proposals, drafting specs, or similar documentation tasks.
Intelligently organizes your files and folders across your computer by understanding context, finding duplicates, suggesting better structures, and automating cleanup tasks. Reduces cognitive load and keeps your digital workspace tidy without manual effort.
Generates creative domain name ideas for your project and checks availability across multiple TLDs (.com, .io, .dev, .ai, etc.). Saves hours of brainstorming and manual checking.
You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation.
Implements Manus-style file-based planning for complex tasks. Creates task_plan.md, findings.md, and progress.md. Use when starting complex multi-step tasks, research projects, or any task requiring >5 tool calls.
Creative research ideation and exploration. Use for open-ended brainstorming sessions, exploring interdisciplinary connections, challenging assumptions, or identifying research gaps. Best for early-stage research planning when you do not have specific observations yet. For formulating testable hypotheses from data use hypothesis-generation.
Comprehensive GitHub project management with swarm-coordinated issue tracking, project board automation, and sprint planning
Interview the user relentlessly about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions "grill me".
Take agiprolabs/tax-loss-harvesting from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.