
Tax Liability Tracking
- 195 installs
- 257 repo stars
- Updated June 24, 2026
- agiprolabs/claude-trading-skills
tax-liability-tracking is a Claude Code skill for real-time crypto tax liability analysis with proportional cost basis, gain classification, and tax-aware trading signals.
About
tax-liability-tracking is a Claude Code skill for real-time crypto tax analysis aimed at active Solana traders. It tracks proportional cost basis across partial sells, classifies realized versus unrealized and short versus long-term gains, projects quarterly tax obligations, and surfaces tax-aware trading signals. A developer uses it to keep a running tax ledger alongside trading so every decision reflects its tax consequence.
- Real-time tax ledger for active crypto traders
- Proportional cost basis for partial sells, FIFO/LIFO/spec-ID
- Short vs long-term classification and quarterly tax projection
Tax Liability Tracking by the numbers
- 195 all-time installs (skills.sh)
- Ranked #477 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
tax-liability-tracking capabilities & compatibility
- Capabilities
- tax tracking · cost basis · gain classification · tax projection
- Use cases
- trading · data analysis
- Pricing
- Free
What tax-liability-tracking says it does
Real-time tax liability analysis purpose-built for active Solana traders.
When you sell a portion of a position, the cost basis splits proportionally — it does not shift to zero for the remaining tokens.
npx skills add https://github.com/agiprolabs/claude-trading-skills --skill tax-liability-trackingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 195 |
|---|---|
| repo stars | ★ 257 |
| Last updated | June 24, 2026 |
| Repository | agiprolabs/claude-trading-skills ↗ |
What it does
Track real-time crypto tax liability with proportional cost basis and tax-aware trading signals.
Who is it for?
Active Solana traders who need a running tax ledger and tax-aware signals across many taxable events.
Skip if: Definitive tax filing or advice; the skill is informational analysis, not tax advice.
When should I use this skill?
You trade frequently and need real-time after-tax P&L and tax-lot tracking across partial sells.
What you get
A running tax ledger with proportional cost basis, gain classification, quarterly projections, and tax-aware signals.
- A running per-lot tax ledger
- Quarterly tax projections and after-tax P&L view
By the numbers
- Supports FIFO, LIFO, and specific identification
- Short vs long-term split at 1-year holding
- Core tracking uses stdlib only (no external deps)
Files
Tax Liability Tracking
Real-time tax liability analysis purpose-built for active Solana traders. Track proportional cost basis across partial sells, classify realized vs unrealized gains, project quarterly tax obligations, and surface tax-aware signals that feed back into trading decisions.
Why This Matters
Active crypto traders generate hundreds of taxable events per year. Without real-time tracking:
- You do not know your actual after-tax P&L until year-end
- Partial sells (the "house money" play) create cost basis confusion
- Short-term vs long-term classification shifts your effective tax rate by 15-20%
- Tax-loss harvesting windows close without you noticing
- Quarterly estimated payments become guesswork
This skill keeps a running tax ledger alongside your trading activity so every decision incorporates its tax consequence.
Core Concepts
Proportional Cost Basis for Partial Sells
This is the most critical concept. When you sell a portion of a position, the cost basis splits proportionally — it does not shift to zero for the remaining tokens.
Example — The Accumulate / House-Money Play:
1. Buy: 1,000 tokens at $1.00 each. Total cost basis = $1,000. 2. Partial sell: Sell 800 tokens at $1.25 each. Proceeds = $1,000. 3. What happened:
- The 800 sold tokens had proportional cost basis = 800 x $1.00 = $800
- Realized gain on the sale = $1,000 - $800 = $200
- The remaining 200 tokens retain cost basis = 200 x $1.00 = $200
- The remaining tokens are NOT "free" — they carry their original per-unit cost
Common mistake: Recording the partial sell as "capital recovered, remaining basis = $0." This understates the realized gain on the partial sell and overstates the gain when the remaining tokens are eventually sold. The total tax owed is the same either way, but the timing and classification (short-term vs long-term) can differ significantly.
Gain Classification
| Classification | Holding Period | US Federal Rate (2024) |
|---|---|---|
| Short-term capital gain | < 1 year | Ordinary income rate (10-37%) |
| Long-term capital gain | >= 1 year | 0%, 15%, or 20% |
Each tax lot tracks its own acquisition date. Partial sells consume lots in the order specified by your accounting method (FIFO, LIFO, or specific identification).
SOL-Denominated Trading with USD Reporting
Solana traders typically think in SOL but US tax obligations are denominated in USD. Every taxable event requires:
1. SOL price at acquisition — establishes USD cost basis 2. SOL price at disposition — establishes USD proceeds 3. Token price in SOL at both events — converted to USD via SOL/USD rate
The skill tracks SOL/USD rates at each event timestamp to compute USD-denominated gains.
Realized vs Unrealized Gains
- Realized: Position closed (fully or partially). Taxable event occurred.
- Unrealized: Position still open. No tax event yet, but contributes to estimated liability if you plan to close before year-end.
The after-tax P&L view shows both, with unrealized gains marked at current estimated tax rates.
Capabilities
Tax Lot Tracking
- Record buys, sells, and token-to-token swaps as taxable events
- Maintain per-lot cost basis with acquisition date
- Support FIFO, LIFO, and specific identification accounting methods
- Handle partial sells with proportional cost basis allocation
Gain Classification Engine
- Classify each realized gain as short-term or long-term
- Track days remaining until long-term threshold for each open lot
- Flag positions approaching the 1-year boundary
Quarterly Tax Projection
- Estimate federal tax liability using progressive bracket rates
- Include state tax estimate (configurable rate)
- Project Q1-Q4 estimated payment amounts
- Track cumulative realized gains by quarter
Tax-Aware Trading Signals
- Long-term threshold alert: "Position crosses long-term threshold in N days"
- Profit-taking cost: "Taking profit here triggers $X short-term liability"
- Loss harvesting: "You have $X in unrealized losses available to harvest"
- Timing value: "Selling now vs waiting 30 days saves $X in taxes"
- Wash sale warning: "Repurchasing within 30 days may trigger wash sale rules"
After-Tax P&L View
- Standard trading P&L alongside estimated after-tax P&L
- Per-position breakdown of pre-tax and after-tax returns
- Running year-to-date tax liability total
Prerequisites
- Python 3.10+
- No external dependencies for core tracking (stdlib only)
- Historical SOL/USD prices for accurate USD conversion
- Trade history in structured format (timestamp, side, token, amount, price)
Quick Start
from tax_tracker import TaxTracker, Trade
tracker = TaxTracker(
accounting_method="fifo",
federal_bracket=0.32, # Your marginal federal rate
state_rate=0.05, # Your state income tax rate
long_term_rate=0.15, # Your long-term capital gains rate
)
# Record the accumulate/house-money play
tracker.add_trade(Trade(
timestamp="2025-06-15T10:00:00Z",
side="buy",
token="BONK",
amount=1_000_000,
price_usd=0.00002, # Per-token price in USD
total_usd=20.00,
))
tracker.add_trade(Trade(
timestamp="2025-07-20T14:30:00Z",
side="sell",
token="BONK",
amount=800_000, # Sell 80% to recover capital
price_usd=0.000025,
total_usd=20.00,
))
# Check what happened
summary = tracker.position_summary("BONK")
print(f"Realized gain: ${summary.realized_gain:.2f}")
# Realized gain: $4.00 (sold 800k at $0.000025 = $20, basis = $16, gain = $4)
print(f"Remaining basis: ${summary.remaining_cost_basis:.2f}")
# Remaining basis: $4.00 (200k tokens x $0.00002 = $4)
print(f"Classification: {summary.gain_type}")
# Classification: short-term (held ~35 days)
# Tax-aware signals
signals = tracker.get_signals("BONK", current_price_usd=0.00003)
for signal in signals:
print(f" [{signal.type}] {signal.message}")
# [long_term_countdown] Position crosses long-term threshold in 330 days
# [profit_taking_cost] Taking full profit triggers $2.80 short-term liabilityUse Cases
1. Day Trader Tax Tracking
Track hundreds of daily trades, all short-term. Focus on quarterly estimated payment accuracy and cumulative liability.
2. Swing Trader with House-Money Plays
Partial sell to recover capital, let remaining position ride. Proportional cost basis ensures both legs are tracked correctly.
3. Long-Term Holder Monitoring
Track positions approaching the 1-year long-term threshold. Signals alert when selling now vs waiting N days changes the tax classification.
4. Tax-Loss Harvesting
Identify positions with unrealized losses. Harvest losses to offset gains while being mindful of wash sale rules.
5. Year-End Tax Planning
Project remaining quarterly liability, identify optimization opportunities before Dec 31.
6. Multi-Token Portfolio
Track cost basis and gains across dozens of tokens simultaneously, with per-token and aggregate views.
Accounting Methods
FIFO (First In, First Out)
Sells consume the oldest lots first. Default for most traders. Generally results in more long-term gains if you have been accumulating over time.
LIFO (Last In, First Out)
Sells consume the newest lots first. Can minimize short-term gains if recent purchases were at higher prices.
Specific Identification
Choose which lots to sell. Maximum flexibility for tax optimization, but requires careful record-keeping. Must be elected before the trade, not after.
Integration with Trading Workflow
Trade Signal → Tax-Aware Filter → Execute/Defer Decision
│
├─ "Short-term gain: $X liability"
├─ "Long-term in N days — consider waiting"
├─ "Loss harvest opportunity: $X offset"
└─ "Net after-tax profit: $Y"The tax-aware filter does not block trades — it provides information so the trader can make informed decisions about timing.
Files
References
references/planned_features.md— Detailed feature list, proportional cost basis math with worked examples, gain classification rules, quarterly estimation methodology
Scripts
scripts/tax_tracker.py— Demo implementation with proportional cost basis, gain classification, tax-aware signals, and quarterly projection. Run with--demofor an example accumulate/house-money scenario.
Limitations
- US federal and state tax rules only (no international tax support yet)
- Does not handle staking rewards, airdrops, or DeFi yield as income events (planned)
- Wash sale rules are flagged but not automatically enforced
- Token-to-token swaps treated as sell + buy (two taxable events) per IRS guidance
- Does not generate official tax forms (use for tracking and planning only)
Disclaimer
Tax calculations produced by this skill are for informational tracking purposes only. Cryptocurrency tax law is complex and evolving. All tax figures should be verified by a qualified tax professional before filing. This skill does not constitute tax advice. Consult a CPA or tax attorney for guidance specific to your situation.
Tax Liability Tracking — Planned Features & Methodology
Feature Overview
Core Tracking
- Per-trade tax lot creation with acquisition date, amount, per-unit cost basis
- Proportional cost basis allocation on partial sells
- FIFO, LIFO, and specific identification accounting methods
- Realized vs unrealized gain classification
- Short-term vs long-term gain classification with countdown timers
Tax Projection
- Progressive federal bracket estimation
- Configurable state tax rate
- Quarterly estimated payment projection (Q1-Q4)
- Year-to-date cumulative liability tracking
- After-tax P&L view per position and aggregate
Tax-Aware Signals
- Long-term threshold countdown per lot
- Profit-taking tax cost estimation
- Loss harvesting opportunity detection
- Timing value analysis (sell now vs wait N days)
- Wash sale proximity warning (30-day window)
SOL/USD Conversion
- Historical SOL/USD price lookup at each event timestamp
- Token price in SOL converted to USD via SOL/USD rate
- All tax figures reported in USD per IRS requirements
---
Proportional Cost Basis — Detailed Math
The Rule
When selling a fraction of a position, cost basis is allocated proportionally:
sold_basis = (amount_sold / total_amount) * total_cost_basis
remaining_basis = total_cost_basis - sold_basisThis is equivalent to:
per_unit_basis = total_cost_basis / total_amount
sold_basis = amount_sold * per_unit_basis
remaining_basis = amount_remaining * per_unit_basisWorked Example 1 — House-Money Play
Setup: Buy 1,000 tokens at $1.00 each.
| Field | Value |
|---|---|
| Total amount | 1,000 |
| Per-unit basis | $1.00 |
| Total basis | $1,000.00 |
Partial sell: Sell 800 tokens at $1.25 each.
| Calculation | Value |
|---|---|
| Proceeds | 800 x $1.25 = $1,000.00 |
| Sold basis | 800 x $1.00 = $800.00 |
| Realized gain | $1,000.00 - $800.00 = $200.00 |
| Remaining amount | 200 |
| Remaining basis | 200 x $1.00 = $200.00 |
Later sell: Sell remaining 200 tokens at $2.00 each.
| Calculation | Value |
|---|---|
| Proceeds | 200 x $2.00 = $400.00 |
| Sold basis | 200 x $1.00 = $200.00 |
| Realized gain | $400.00 - $200.00 = $200.00 |
Total gain across both sells: $200 + $200 = $400.
Verification: Total proceeds ($1,000 + $400 = $1,400) minus total basis ($1,000) = $400. Correct.
Worked Example 2 — Multiple Accumulations
Buy 1: 500 tokens at $0.50 = $250 basis. Buy 2: 300 tokens at $0.80 = $240 basis. Total: 800 tokens, $490 total basis.
Partial sell (FIFO): Sell 600 tokens at $1.00.
Under FIFO, the 600 sold tokens come from:
- Lot 1: 500 tokens at $0.50 = $250 basis
- Lot 2: 100 tokens at $0.80 = $80 basis
- Total sold basis = $330
| Calculation | Value |
|---|---|
| Proceeds | 600 x $1.00 = $600.00 |
| Sold basis (FIFO) | $250 + $80 = $330.00 |
| Realized gain | $600 - $330 = $270.00 |
| Remaining | 200 tokens from Lot 2 |
| Remaining basis | 200 x $0.80 = $160.00 |
Worked Example 3 — Zero-Basis Misconception
The wrong way (do NOT do this):
Buy 1,000 at $1.00 ($1,000 basis). Sell 800 at $1.25 ($1,000 proceeds). "I recovered my capital, so remaining basis = $0."
This is incorrect because:
- It records $0 realized gain on the partial sell (understated by $200)
- It records $400 gain on the final sell of 200 tokens at $2.00 (overstated by $200)
- If the partial sell is in one tax year and the final sell in another, income shifts between years
- If the holding period crosses the 1-year boundary between sells, the gain classification changes
---
Gain Classification Rules
Holding Period Determination
- Start date: The date the tokens were acquired (trade settlement)
- End date: The date the tokens were disposed of
- Short-term: Holding period < 1 year (365 days)
- Long-term: Holding period >= 1 year
Special Cases
Token-to-token swaps: Treated as two events — a sale of the source token and a purchase of the destination token. Both events use the USD value at the time of the swap.
Wash sales: If you sell at a loss and repurchase the same or "substantially identical" token within 30 days (before or after the sale), the loss may be disallowed. The disallowed loss is added to the basis of the repurchased tokens. Crypto wash sale rules are not yet codified in US tax law but may apply under proposed legislation.
Airdrops and staking rewards: Treated as ordinary income at fair market value when received. The FMV becomes the cost basis for future disposition.
---
Quarterly Estimation Methodology
US Estimated Tax Payments
Self-employed and high-income taxpayers must make quarterly estimated payments:
| Quarter | Period | Due Date |
|---|---|---|
| Q1 | Jan 1 — Mar 31 | April 15 |
| Q2 | Apr 1 — May 31 | June 15 |
| Q3 | Jun 1 — Aug 31 | September 15 |
| Q4 | Sep 1 — Dec 31 | January 15 (next year) |
Estimation Formula
quarterly_liability = (realized_short_term_gains * short_term_rate
+ realized_long_term_gains * long_term_rate
+ state_gains * state_rate)Where:
short_term_rate= marginal federal income tax ratelong_term_rate= 0%, 15%, or 20% based on total incomestate_rate= state income tax rate (varies by state, 0-13.3%)
Safe Harbor Rule
To avoid underpayment penalties, pay the lesser of:
- 90% of current year tax liability, or
- 100% of prior year tax liability (110% if AGI > $150,000)
The tracker projects based on year-to-date realized gains annualized, then compares against safe harbor thresholds.
---
Planned Enhancements
Phase 1 (Current)
- Core proportional cost basis tracking
- FIFO accounting method
- Short-term / long-term classification
- Basic quarterly projection
- Tax-aware trading signals
Phase 2
- LIFO and specific identification methods
- Wash sale detection and basis adjustment
- Multi-year tracking with carryover losses
- CSV/JSON import from exchange exports
Phase 3
- Staking reward and airdrop income tracking
- DeFi yield as ordinary income events
- LP position entry/exit as taxable events
- Integration with Helius transaction history API
Phase 4
- Form 8949 data export
- TurboTax/TaxAct import format
- CoinTracker/Koinly reconciliation
- Multi-jurisdiction support (UK, EU, AU)
#!/usr/bin/env python3
"""Real-time tax liability tracker for active crypto traders.
Demonstrates proportional cost basis tracking, short-term vs long-term gain
classification, tax-aware trading signals, and quarterly tax projection.
Usage:
python scripts/tax_tracker.py # Run demo scenario
python scripts/tax_tracker.py --demo # Same as above
Dependencies:
None — uses Python standard library only.
Environment Variables:
None required for demo mode.
Disclaimer:
Tax calculations are for informational tracking purposes only.
Consult a qualified tax professional for actual tax filing.
"""
import argparse
import json
import sys
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
from typing import Optional
# ── Data Models ─────────────────────────────────────────────────────
class Side(Enum):
BUY = "buy"
SELL = "sell"
class GainType(Enum):
SHORT_TERM = "short-term"
LONG_TERM = "long-term"
class AccountingMethod(Enum):
FIFO = "fifo"
LIFO = "lifo"
@dataclass
class Trade:
"""A single trade event."""
timestamp: str # ISO 8601
side: str # "buy" or "sell"
token: str
amount: float
price_usd: float # Per-unit price in USD
total_usd: float # Total value in USD
@property
def dt(self) -> datetime:
ts = self.timestamp.replace("Z", "+00:00")
return datetime.fromisoformat(ts)
@dataclass
class TaxLot:
"""A single cost basis lot."""
acquisition_date: datetime
amount: float
per_unit_basis: float
token: str
@property
def total_basis(self) -> float:
return self.amount * self.per_unit_basis
def days_held(self, as_of: Optional[datetime] = None) -> int:
ref = as_of or datetime.now()
# Normalize both to naive datetimes for comparison
acq = self.acquisition_date.replace(tzinfo=None)
ref = ref.replace(tzinfo=None)
return (ref - acq).days
def is_long_term(self, as_of: Optional[datetime] = None) -> bool:
return self.days_held(as_of) >= 365
def days_until_long_term(self, as_of: Optional[datetime] = None) -> int:
remaining = 365 - self.days_held(as_of)
return max(0, remaining)
@dataclass
class RealizedGain:
"""A realized gain or loss from a sale."""
token: str
sell_date: datetime
amount_sold: float
proceeds: float
cost_basis: float
gain: float
gain_type: GainType
holding_days: int
@dataclass
class TaxSignal:
"""A tax-aware trading signal."""
signal_type: str
token: str
message: str
value: Optional[float] = None
@dataclass
class PositionSummary:
"""Summary of a token position for display."""
token: str
total_amount: float
total_cost_basis: float
realized_gain: float
unrealized_gain: float
gain_type: str
remaining_cost_basis: float
lots: int
@dataclass
class QuarterlyEstimate:
"""Estimated tax liability for a quarter."""
quarter: str
period_start: str
period_end: str
due_date: str
short_term_gains: float
long_term_gains: float
estimated_federal: float
estimated_state: float
estimated_total: float
# ── Tax Tracker ─────────────────────────────────────────────────────
class TaxTracker:
"""Real-time tax liability tracker with proportional cost basis.
Tracks tax lots, computes proportional cost basis on partial sells,
classifies gains as short-term or long-term, and generates tax-aware
trading signals.
Args:
accounting_method: "fifo" or "lifo" for lot selection order.
federal_bracket: Marginal federal income tax rate (0.0-0.37).
state_rate: State income tax rate (0.0-0.133).
long_term_rate: Long-term capital gains rate (0.0, 0.15, or 0.20).
"""
def __init__(
self,
accounting_method: str = "fifo",
federal_bracket: float = 0.32,
state_rate: float = 0.05,
long_term_rate: float = 0.15,
) -> None:
self.accounting_method = AccountingMethod(accounting_method)
self.federal_bracket = federal_bracket
self.state_rate = state_rate
self.long_term_rate = long_term_rate
self.lots: dict[str, list[TaxLot]] = {}
self.realized_gains: list[RealizedGain] = []
self.trades: list[Trade] = []
def add_trade(self, trade: Trade) -> Optional[list[RealizedGain]]:
"""Record a trade and update tax lots.
For buys, creates a new tax lot. For sells, consumes lots using
the configured accounting method and records realized gains.
Args:
trade: The trade to record.
Returns:
List of realized gains for sell trades, None for buys.
"""
self.trades.append(trade)
if trade.side == "buy":
return self._process_buy(trade)
elif trade.side == "sell":
return self._process_sell(trade)
else:
raise ValueError(f"Unknown trade side: {trade.side}")
def _process_buy(self, trade: Trade) -> None:
"""Create a new tax lot from a buy trade."""
lot = TaxLot(
acquisition_date=trade.dt,
amount=trade.amount,
per_unit_basis=trade.price_usd,
token=trade.token,
)
if trade.token not in self.lots:
self.lots[trade.token] = []
self.lots[trade.token].append(lot)
return None
def _process_sell(self, trade: Trade) -> list[RealizedGain]:
"""Consume tax lots and record realized gains from a sell trade.
Uses proportional cost basis: each lot's basis is allocated
proportionally to the amount sold from that lot.
"""
if trade.token not in self.lots or not self.lots[trade.token]:
raise ValueError(
f"No tax lots for {trade.token} — cannot sell what you don't own"
)
token_lots = self.lots[trade.token]
if self.accounting_method == AccountingMethod.LIFO:
token_lots = list(reversed(token_lots))
remaining_to_sell = trade.amount
sell_price = trade.price_usd
sell_date = trade.dt
gains: list[RealizedGain] = []
lots_to_remove: list[int] = []
for i, lot in enumerate(token_lots):
if remaining_to_sell <= 0:
break
if lot.amount <= remaining_to_sell:
# Consume entire lot
amount_from_lot = lot.amount
basis_from_lot = lot.total_basis
lots_to_remove.append(i)
else:
# Partial consumption — proportional basis
amount_from_lot = remaining_to_sell
basis_from_lot = amount_from_lot * lot.per_unit_basis
lot.amount -= amount_from_lot
proceeds = amount_from_lot * sell_price
gain_amount = proceeds - basis_from_lot
holding_days = (sell_date - lot.acquisition_date).days
gain = RealizedGain(
token=trade.token,
sell_date=sell_date,
amount_sold=amount_from_lot,
proceeds=proceeds,
cost_basis=basis_from_lot,
gain=gain_amount,
gain_type=(
GainType.LONG_TERM
if holding_days >= 365
else GainType.SHORT_TERM
),
holding_days=holding_days,
)
gains.append(gain)
self.realized_gains.append(gain)
remaining_to_sell -= amount_from_lot
# Remove fully consumed lots (reverse order to preserve indices)
original_lots = self.lots[trade.token]
for i in sorted(lots_to_remove, reverse=True):
if self.accounting_method == AccountingMethod.LIFO:
actual_idx = len(original_lots) - 1 - i
else:
actual_idx = i
original_lots.pop(actual_idx)
if remaining_to_sell > 1e-10:
raise ValueError(
f"Tried to sell {trade.amount} {trade.token} but only had "
f"{trade.amount - remaining_to_sell} in tax lots"
)
return gains
def position_summary(self, token: str) -> PositionSummary:
"""Get a summary of current position and realized gains for a token.
Args:
token: The token symbol.
Returns:
PositionSummary with current holdings and realized gain totals.
"""
lots = self.lots.get(token, [])
total_amount = sum(lot.amount for lot in lots)
total_basis = sum(lot.total_basis for lot in lots)
token_gains = [g for g in self.realized_gains if g.token == token]
realized = sum(g.gain for g in token_gains)
gain_types = set(g.gain_type.value for g in token_gains)
if len(gain_types) == 1:
gain_type = gain_types.pop()
elif len(gain_types) > 1:
gain_type = "mixed"
else:
gain_type = "none"
return PositionSummary(
token=token,
total_amount=total_amount,
total_cost_basis=total_basis,
realized_gain=realized,
unrealized_gain=0.0,
gain_type=gain_type,
remaining_cost_basis=total_basis,
lots=len(lots),
)
def get_signals(
self, token: str, current_price_usd: float
) -> list[TaxSignal]:
"""Generate tax-aware trading signals for a token.
Args:
token: The token symbol.
current_price_usd: Current per-unit price in USD.
Returns:
List of tax-aware signals.
"""
signals: list[TaxSignal] = []
lots = self.lots.get(token, [])
if not lots:
return signals
now = datetime.now()
# Long-term threshold countdown
for lot in lots:
days_left = lot.days_until_long_term(now)
if 0 < days_left <= 90:
target_date = lot.acquisition_date + timedelta(days=365)
signals.append(TaxSignal(
signal_type="long_term_countdown",
token=token,
message=(
f"{lot.amount:.0f} tokens cross long-term threshold "
f"in {days_left} days ({target_date.strftime('%Y-%m-%d')})"
),
value=float(days_left),
))
# Profit-taking tax cost
total_amount = sum(lot.amount for lot in lots)
total_basis = sum(lot.total_basis for lot in lots)
market_value = total_amount * current_price_usd
unrealized = market_value - total_basis
if unrealized > 0:
st_lots = [l for l in lots if not l.is_long_term(now)]
st_amount = sum(l.amount for l in st_lots)
st_basis = sum(l.total_basis for l in st_lots)
st_value = st_amount * current_price_usd
st_gain = st_value - st_basis
if st_gain > 0:
st_tax = st_gain * self.federal_bracket
signals.append(TaxSignal(
signal_type="profit_taking_cost",
token=token,
message=(
f"Taking full profit triggers ${st_tax:,.2f} "
f"short-term federal liability on ${st_gain:,.2f} gain"
),
value=st_tax,
))
# Loss harvesting opportunity
if unrealized < 0:
tax_savings = abs(unrealized) * self.federal_bracket
signals.append(TaxSignal(
signal_type="loss_harvest",
token=token,
message=(
f"${abs(unrealized):,.2f} unrealized loss available to harvest "
f"(saves ~${tax_savings:,.2f} in taxes)"
),
value=abs(unrealized),
))
# General unrealized position info
if unrealized != 0:
direction = "gain" if unrealized > 0 else "loss"
signals.append(TaxSignal(
signal_type="unrealized_position",
token=token,
message=(
f"Unrealized {direction}: ${abs(unrealized):,.2f} "
f"on {total_amount:,.0f} tokens "
f"(basis: ${total_basis:,.2f}, market: ${market_value:,.2f})"
),
value=unrealized,
))
return signals
def quarterly_projection(self, tax_year: int = 2025) -> list[QuarterlyEstimate]:
"""Project quarterly estimated tax payments.
Allocates realized gains to quarters based on sell date and
computes estimated federal + state liability per quarter.
Args:
tax_year: The tax year to project for.
Returns:
List of QuarterlyEstimate objects, one per quarter.
"""
quarters = [
("Q1", f"{tax_year}-01-01", f"{tax_year}-03-31", f"{tax_year}-04-15"),
("Q2", f"{tax_year}-04-01", f"{tax_year}-05-31", f"{tax_year}-06-15"),
("Q3", f"{tax_year}-06-01", f"{tax_year}-08-31", f"{tax_year}-09-15"),
("Q4", f"{tax_year}-09-01", f"{tax_year}-12-31", f"{tax_year + 1}-01-15"),
]
estimates: list[QuarterlyEstimate] = []
for label, start_str, end_str, due_str in quarters:
start = datetime.fromisoformat(start_str)
end = datetime.fromisoformat(end_str + "T23:59:59")
q_gains = [
g for g in self.realized_gains
if start <= g.sell_date.replace(tzinfo=None) <= end
]
st_gains = sum(
g.gain for g in q_gains if g.gain_type == GainType.SHORT_TERM
)
lt_gains = sum(
g.gain for g in q_gains if g.gain_type == GainType.LONG_TERM
)
federal = (
max(0, st_gains) * self.federal_bracket
+ max(0, lt_gains) * self.long_term_rate
)
state = max(0, st_gains + lt_gains) * self.state_rate
total = federal + state
estimates.append(QuarterlyEstimate(
quarter=label,
period_start=start_str,
period_end=end_str,
due_date=due_str,
short_term_gains=st_gains,
long_term_gains=lt_gains,
estimated_federal=federal,
estimated_state=state,
estimated_total=total,
))
return estimates
def after_tax_pnl(self) -> dict[str, float]:
"""Compute aggregate after-tax P&L from all realized gains.
Returns:
Dictionary with gross_pnl, estimated_tax, and after_tax_pnl.
"""
st_gains = sum(
g.gain for g in self.realized_gains
if g.gain_type == GainType.SHORT_TERM
)
lt_gains = sum(
g.gain for g in self.realized_gains
if g.gain_type == GainType.LONG_TERM
)
gross = st_gains + lt_gains
federal = (
max(0, st_gains) * self.federal_bracket
+ max(0, lt_gains) * self.long_term_rate
)
state = max(0, gross) * self.state_rate
total_tax = federal + state
return {
"gross_pnl": gross,
"short_term_gains": st_gains,
"long_term_gains": lt_gains,
"estimated_federal_tax": federal,
"estimated_state_tax": state,
"estimated_total_tax": total_tax,
"after_tax_pnl": gross - total_tax,
}
# ── Demo ────────────────────────────────────────────────────────────
def print_header(title: str) -> None:
"""Print a formatted section header."""
print(f"\n{'=' * 60}")
print(f" {title}")
print(f"{'=' * 60}")
def run_demo() -> None:
"""Run the accumulate/house-money demo scenario.
Demonstrates:
1. Buy tokens
2. Partial sell to recover capital (proportional cost basis)
3. Position summary showing correct basis allocation
4. Tax-aware signals on remaining position
5. Quarterly tax projection
6. After-tax P&L
"""
print_header("Tax Liability Tracker — Demo Scenario")
print("\nScenario: Accumulate / House-Money Play on BONK")
print("Accounting method: FIFO")
print("Federal bracket: 32% | State rate: 5% | LT rate: 15%")
tracker = TaxTracker(
accounting_method="fifo",
federal_bracket=0.32,
state_rate=0.05,
long_term_rate=0.15,
)
# ── Step 1: Buy ─────────────────────────────────────────────────
print_header("Step 1: Buy 1,000,000 BONK at $0.00002")
buy_trade = Trade(
timestamp="2025-06-15T10:00:00Z",
side="buy",
token="BONK",
amount=1_000_000,
price_usd=0.00002,
total_usd=20.00,
)
tracker.add_trade(buy_trade)
summary = tracker.position_summary("BONK")
print(f" Amount: {summary.total_amount:,.0f} BONK")
print(f" Cost basis: ${summary.total_cost_basis:.2f}")
print(f" Per-unit basis: ${summary.total_cost_basis / summary.total_amount:.8f}")
print(f" Tax lots: {summary.lots}")
# ── Step 2: Partial sell (recover capital) ──────────────────────
print_header("Step 2: Sell 800,000 BONK at $0.000025 (recover capital)")
sell_trade = Trade(
timestamp="2025-07-20T14:30:00Z",
side="sell",
token="BONK",
amount=800_000,
price_usd=0.000025,
total_usd=20.00,
)
gains = tracker.add_trade(sell_trade)
print("\n Realized gains from partial sell:")
for g in gains:
print(f" Amount sold: {g.amount_sold:,.0f} BONK")
print(f" Proceeds: ${g.proceeds:.2f}")
print(f" Cost basis: ${g.cost_basis:.2f}")
print(f" Gain: ${g.gain:.2f}")
print(f" Type: {g.gain_type.value} ({g.holding_days} days)")
# ── Step 3: Position summary ────────────────────────────────────
print_header("Step 3: Remaining Position")
summary = tracker.position_summary("BONK")
print(f" Remaining amount: {summary.total_amount:,.0f} BONK")
print(f" Remaining cost basis: ${summary.remaining_cost_basis:.2f}")
if summary.total_amount > 0:
print(
f" Per-unit basis: "
f"${summary.remaining_cost_basis / summary.total_amount:.8f}"
)
print(f" Total realized gain: ${summary.realized_gain:.2f}")
print(f" Gain classification: {summary.gain_type}")
print("\n KEY INSIGHT: The remaining 200,000 BONK have basis = $4.00")
print(" (200,000 x $0.00002 = $4.00), NOT $0.00")
print(" The $4.00 realized gain came from the partial sell,")
print(" not deferred to the remaining position.")
# ── Step 4: Tax-aware signals ───────────────────────────────────
print_header("Step 4: Tax-Aware Signals")
print(" (Current BONK price: $0.00005 — 2.5x from entry)")
signals = tracker.get_signals("BONK", current_price_usd=0.00005)
if signals:
for s in signals:
print(f" [{s.signal_type}] {s.message}")
else:
print(" No signals generated.")
# ── Step 5: Sell remaining at a profit ──────────────────────────
print_header("Step 5: Sell remaining 200,000 BONK at $0.00005")
final_sell = Trade(
timestamp="2025-08-10T09:00:00Z",
side="sell",
token="BONK",
amount=200_000,
price_usd=0.00005,
total_usd=10.00,
)
gains2 = tracker.add_trade(final_sell)
print("\n Realized gains from final sell:")
for g in gains2:
print(f" Amount sold: {g.amount_sold:,.0f} BONK")
print(f" Proceeds: ${g.proceeds:.2f}")
print(f" Cost basis: ${g.cost_basis:.2f}")
print(f" Gain: ${g.gain:.2f}")
print(f" Type: {g.gain_type.value} ({g.holding_days} days)")
# ── Step 6: Quarterly projection ────────────────────────────────
print_header("Step 6: Quarterly Tax Projection (2025)")
estimates = tracker.quarterly_projection(tax_year=2025)
for est in estimates:
if est.short_term_gains != 0 or est.long_term_gains != 0:
print(f"\n {est.quarter} ({est.period_start} to {est.period_end})")
print(f" Due: {est.due_date}")
print(f" Short-term gains: ${est.short_term_gains:,.2f}")
print(f" Long-term gains: ${est.long_term_gains:,.2f}")
print(f" Federal estimate: ${est.estimated_federal:,.2f}")
print(f" State estimate: ${est.estimated_state:,.2f}")
print(f" Total estimate: ${est.estimated_total:,.2f}")
# ── Step 7: After-tax P&L ───────────────────────────────────────
print_header("Step 7: After-Tax P&L Summary")
pnl = tracker.after_tax_pnl()
print(f" Gross P&L: ${pnl['gross_pnl']:,.2f}")
print(f" Short-term gains: ${pnl['short_term_gains']:,.2f}")
print(f" Long-term gains: ${pnl['long_term_gains']:,.2f}")
print(f" Federal tax est: ${pnl['estimated_federal_tax']:,.2f}")
print(f" State tax est: ${pnl['estimated_state_tax']:,.2f}")
print(f" Total tax est: ${pnl['estimated_total_tax']:,.2f}")
print(f" After-tax P&L: ${pnl['after_tax_pnl']:,.2f}")
# ── Verification ────────────────────────────────────────────────
print_header("Verification")
total_proceeds = 20.00 + 10.00 # $20 from partial + $10 from final
total_basis = 20.00 # Original purchase
expected_gain = total_proceeds - total_basis
actual_gain = pnl["gross_pnl"]
print(f" Total proceeds: ${total_proceeds:.2f}")
print(f" Total basis: ${total_basis:.2f}")
print(f" Expected gain: ${expected_gain:.2f}")
print(f" Tracked gain: ${actual_gain:.2f}")
print(f" Match: {'YES' if abs(expected_gain - actual_gain) < 0.01 else 'NO'}")
print(f"\n{'=' * 60}")
print(" DISCLAIMER: Tax calculations are for informational")
print(" tracking purposes only. Consult a qualified tax")
print(" professional for actual tax filing.")
print(f"{'=' * 60}\n")
# ── Main ────────────────────────────────────────────────────────────
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Tax liability tracker for active crypto traders"
)
parser.add_argument(
"--demo",
action="store_true",
default=True,
help="Run the demo scenario (default)",
)
args = parser.parse_args()
run_demo()
Related skills
FAQ
How does proportional cost basis work on partial sells?
Selling part of a position splits the cost basis proportionally; the sold tokens carry their share of the original per-unit cost and the remaining tokens keep theirs, so remaining tokens are not treated as free.
What accounting methods are supported?
FIFO, LIFO, and specific identification, with each tax lot tracking its own acquisition date for short versus long-term classification.