
Regulatory Reporting
- 194 installs
- 257 repo stars
- Updated June 24, 2026
- agiprolabs/claude-trading-skills
regulatory-reporting is a Claude Code skill that organizes crypto trade data into US tax report formats like IRS Form 8949 and Schedule D.
About
regulatory-reporting is a Claude Code skill for organizing crypto trading activity into US regulatory report formats. It generates basic IRS Form 8949 line items and covers Schedule D, FinCEN FBAR, and large-transaction rules. It is marked a STUB, is not validated against current IRS guidance, and repeatedly warns that a qualified tax professional must review any output before filing.
- Generates basic IRS Form 8949 line items, splitting short-term and long-term dispositions
- Documents Schedule D, FinCEN FBAR ($10,000 threshold), and large-transaction reporting
- Marked STUB with strong disclaimers: output is not tax advice and must be reviewed by a professional
Regulatory Reporting by the numbers
- 194 all-time installs (skills.sh)
- Ranked #481 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
regulatory-reporting capabilities & compatibility
Free; the demo script has no external dependencies, and pandas/reportlab are only needed for a production PDF implementation.
- Capabilities
- tax reporting · capital gains calculation · form generation · compliance tracking
- Use cases
- data analysis
- Pricing
- Free
What regulatory-reporting says it does
Track and generate required regulatory reports for cryptocurrency trading activity.
You MUST consult a qualified tax professional (CPA, EA, or tax attorney with crypto expertise) before relying on any output from this skill for actual tax filings.
npx skills add https://github.com/agiprolabs/claude-trading-skills --skill regulatory-reportingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 194 |
|---|---|
| repo stars | ★ 257 |
| Last updated | June 24, 2026 |
| Repository | agiprolabs/claude-trading-skills ↗ |
What it does
Organize crypto trade data into US tax report formats such as IRS Form 8949 and Schedule D.
Who is it for?
Structuring crypto trade records into tax-form line items as a starting point for professional review.
Skip if: Filing taxes directly; it is a stub, unvalidated against IRS guidance, and not tax advice.
When should I use this skill?
You need to turn crypto trade history into Form 8949 / Schedule D style line items to hand to a tax professional.
What you get
Draft tax-form line items and a requirements overview to be validated by a qualified tax professional.
- Draft IRS Form 8949 line items
- Short-term/long-term gain classification
By the numbers
- IRS Form 8949 8 columns documented
- $10,000 FBAR aggregate threshold
Files
Regulatory Reporting
STATUS: STUB — This skill outlines planned capabilities for crypto tax and regulatory reporting. All generated output must be reviewed by a qualified tax professional before filing. Nothing in this skill constitutes tax or legal advice.
Overview
Track and generate required regulatory reports for cryptocurrency trading activity. Covers U.S. federal forms (IRS Form 8949, Schedule D), FinCEN foreign account reporting (FBAR), state-specific crypto obligations, large-transaction flagging, and deadline tracking.
Crypto tax reporting is complex and evolving. Rules change frequently, cost-basis methods vary by jurisdiction, and DeFi activities (swaps, LP positions, airdrops, staking rewards) have ambiguous treatment under current guidance. This skill provides a structured framework for organizing trade data into the formats regulators expect — but a tax professional must validate every filing.
Disclaimer
WARNING: This skill is for informational and educational purposes only. It does NOT constitute tax, legal, or financial advice. Cryptocurrency tax law is complex, jurisdiction-specific, and rapidly evolving. You MUST consult a qualified tax professional (CPA, EA, or tax attorney with crypto expertise) before relying on any output from this skill for actual tax filings. Errors in tax reporting can result in penalties, interest, and legal consequences. The authors accept no liability for any use of this material.
Current Status
This is a STUB skill. The code and references provided are starting points that demonstrate data structures and basic calculations. They have NOT been validated against current IRS guidance or any state tax authority requirements.
What Exists Now
- Basic Form 8949 line-item generation from trade data (see
scripts/form_8949_generator.py) - Regulatory requirements overview (see
references/planned_features.md) - Demo mode with sample trade data
What Is Planned
- Complete IRS Form 8949 and Schedule D PDF generation
- FinCEN FBAR generation when foreign exchange accounts exceed $10,000
- State-specific reporting requirement detection and templates
- Reporting threshold tracking and deadline calendar
- Large transaction flagging ($10,000+ cash-equivalent transactions)
- Foreign account reporting requirements (FATCA Form 8938)
- Wash sale detection and adjustment (where applicable)
- Cost-basis method selection (FIFO, LIFO, Specific ID, HIFO)
- DeFi-specific event classification (swaps, LP entry/exit, staking, airdrops)
- Actual IRS-compatible CSV/PDF form data output
Prerequisites
# No external dependencies for the demo script
python scripts/form_8949_generator.py --demoFor a production implementation, the following would be needed:
uv pip install pandas reportlab # PDF generationKey Concepts
IRS Form 8949 — Sales and Dispositions of Capital Assets
Form 8949 reports each individual sale or disposition of a capital asset. For crypto, every trade, swap, or spending event is a taxable disposition. Key fields:
| Column | Description |
|---|---|
| (a) | Description of property (e.g., "2.5 BTC") |
| (b) | Date acquired |
| (c) | Date sold or disposed of |
| (d) | Proceeds (sale price in USD) |
| (e) | Cost or other basis |
| (f) | Adjustment code (e.g., W for wash sale) |
| (g) | Adjustment amount |
| (h) | Gain or loss (d minus e, adjusted by g) |
Transactions are split into Part I (short-term, held one year or less) and Part II (long-term, held more than one year).
Schedule D — Capital Gains and Losses
Schedule D aggregates the totals from Form 8949:
- Line 1b/8b: Totals from Form 8949 Part I / Part II (basis reported to IRS)
- Line 1c/8c: Totals where basis was NOT reported to IRS
- Line 7/15: Net short-term / long-term capital gain or loss
- Line 16: Combined net gain or loss
FinCEN FBAR (FinCEN Form 114)
Required when the aggregate value of foreign financial accounts exceeds $10,000 at any point during the calendar year. Crypto held on foreign exchanges (e.g., Binance for non-U.S. entities) may trigger this requirement — though IRS guidance on whether crypto accounts are "foreign financial accounts" remains evolving.
- Filing deadline: April 15 (automatic extension to October 15)
- Penalty for non-filing: Up to $12,500 per non-willful violation; up to $100,000 or 50% of account balance for willful violations
Large Transaction Reporting
Businesses receiving $10,000+ in cash (which may include crypto under recent guidance) must file IRS Form 8300 within 15 days. Individual traders generally do not file Form 8300, but exchanges may report large transactions.
Quick Start
"""Generate Form 8949 line items from trade history."""
from dataclasses import dataclass
from datetime import date
from decimal import Decimal
@dataclass
class Trade:
asset: str
quantity: Decimal
date_acquired: date
date_sold: date
proceeds_usd: Decimal
cost_basis_usd: Decimal
def classify_holding_period(trade: Trade) -> str:
"""Return 'short-term' or 'long-term' based on holding period."""
holding_days = (trade.date_sold - trade.date_acquired).days
return "short-term" if holding_days <= 365 else "long-term"
def compute_gain_loss(trade: Trade) -> Decimal:
"""Compute capital gain or loss for a single trade."""
return trade.proceeds_usd - trade.cost_basis_usd
# Example
trade = Trade(
asset="SOL",
quantity=Decimal("10"),
date_acquired=date(2025, 1, 15),
date_sold=date(2025, 8, 20),
proceeds_usd=Decimal("2500.00"),
cost_basis_usd=Decimal("1800.00"),
)
gain = compute_gain_loss(trade)
period = classify_holding_period(trade)
print(f"{trade.asset}: {period} gain of ${gain}")
# SOL: short-term gain of $700.00Use Cases
1. End-of-year tax preparation — Export trade history from exchanges, generate Form 8949 line items, calculate Schedule D totals, and hand the organized data to your tax professional.
2. Quarterly estimated tax tracking — Monitor cumulative realized gains throughout the year to estimate quarterly tax payments (IRS Form 1040-ES).
3. FBAR threshold monitoring — Track aggregate balances across foreign exchange accounts to determine whether FBAR filing is required.
4. Audit preparation — Maintain organized records of all dispositions with cost-basis documentation in case of an IRS inquiry.
5. Multi-state filing — Identify state-specific crypto reporting obligations when trading from states with additional requirements.
Files
References
references/planned_features.md— Regulatory requirements overview, Form 8949/Schedule D field descriptions, FBAR thresholds, state requirements summary, and reporting deadlines
Scripts
scripts/form_8949_generator.py— Basic Form 8949 line-item generator from trade data with--demomode; no external dependencies
Limitations
- Not legal or tax advice. This is a software tool, not a tax professional.
- U.S.-focused. International tax obligations are not covered.
- Evolving rules. Crypto tax guidance changes frequently; this skill may not reflect the latest IRS notices or rulings.
- No DeFi classification yet. Complex DeFi events (LP positions, yield farming, rebasing tokens) are not yet handled.
- No wash sale certainty. Whether wash sale rules apply to crypto is unsettled; the skill flags potential wash sales but cannot make definitive determinations.
- Cost-basis methods limited. Only FIFO is implemented in the demo; LIFO, HIFO, and Specific ID are planned.
Reporting Deadlines (U.S. Federal)
| Form | Deadline | Extension |
|---|---|---|
| Form 8949 / Schedule D | April 15 | October 15 (with Form 4868) |
| FinCEN FBAR (Form 114) | April 15 | October 15 (automatic) |
| Form 8300 | 15 days after transaction | None |
| FATCA Form 8938 | With tax return | With tax return extension |
| 1040-ES (quarterly) | Apr 15, Jun 15, Sep 15, Jan 15 | None |
Contributing
This is a stub skill with significant room for expansion. Contributions welcome in these areas:
- Additional cost-basis methods (LIFO, HIFO, Specific ID)
- DeFi event classification (LP entry/exit, staking, airdrops, bridging)
- State-specific requirement databases
- Wash sale detection algorithms
- PDF form generation (IRS-compatible output)
- International tax jurisdiction support
- Integration with exchange API export formats
All contributions must include appropriate disclaimers and must not provide specific tax advice. See the project CLAUDE.md for contribution guidelines.
Related Skills
portfolio-analytics— Portfolio performance tracking that feeds into tax reportingtrade-journal— Trade logging that can serve as source data for Form 8949
Regulatory Reporting — Planned Features & Requirements Overview
DISCLAIMER: This document is for informational purposes only and does not constitute tax or legal advice. Consult a qualified tax professional before making any filing decisions.
IRS Form 8949 — Sales and Dispositions of Capital Assets
Purpose
Report each sale, exchange, or disposition of a capital asset. For cryptocurrency, every taxable event (trade, swap, spend, gift) generates a line item on Form 8949.
Parts
- Part I: Short-term transactions (held one year or less). Check Box A, B, or C.
- Part II: Long-term transactions (held more than one year). Check Box D, E, or F.
Box Codes (Basis Reporting)
| Box | Meaning |
|---|---|
| A/D | Basis reported to IRS (1099-B received with basis) |
| B/E | Basis NOT reported to IRS (1099-B received, no basis) |
| C/F | No 1099-B received at all |
Most crypto transactions through 2025 fall under Box C/F. Starting in 2026, centralized exchanges must issue 1099-DA forms, shifting many transactions to Box A/D.
Column Definitions
| Column | Field | Description | Example |
|---|---|---|---|
| (a) | Description | Asset name and quantity | "10.5 SOL" |
| (b) | Date Acquired | Purchase or receipt date | "01/15/2025" |
| (c) | Date Sold | Sale or disposition date | "08/20/2025" |
| (d) | Proceeds | Amount received in USD | "$2,500.00" |
| (e) | Cost Basis | Original cost in USD + fees | "$1,800.00" |
| (f) | Adjustment Code | W (wash sale), B (basis incorrect), etc. | "W" |
| (g) | Adjustment Amount | Dollar amount of adjustment | "$200.00" |
| (h) | Gain or Loss | Column (d) minus (e), adjusted by (g) | "$700.00" |
Taxable Events in Crypto
| Event | Taxable? | Notes |
|---|---|---|
| Crypto-to-fiat sale | Yes | Gain/loss = proceeds minus basis |
| Crypto-to-crypto swap | Yes | Fair market value at time of swap |
| Spending crypto | Yes | Treated as a sale at FMV |
| Receiving as payment | Yes (income) | Ordinary income at FMV when received |
| Mining/staking rewards | Yes (income) | Ordinary income at FMV when received |
| Airdrops | Yes (income) | Ordinary income at FMV when received |
| Gifts received | No (until sold) | Basis carries over from donor |
| Transfers between wallets | No | Same owner, no disposition |
| Buying crypto with fiat | No | Establishes cost basis |
Schedule D — Capital Gains and Losses
Key Lines
| Line | Description |
|---|---|
| 1b | Short-term totals from Form 8949, Box A |
| 1c | Short-term totals from Form 8949, Box B |
| 2 | Short-term totals from Form 8949, Box C |
| 7 | Net short-term capital gain or (loss) |
| 8b | Long-term totals from Form 8949, Box D |
| 8c | Long-term totals from Form 8949, Box E |
| 9 | Long-term totals from Form 8949, Box F |
| 15 | Net long-term capital gain or (loss) |
| 16 | Combine lines 7 and 15 |
| 21 | Net capital loss deduction (max $3,000/year) |
Capital Loss Carryforward
If net capital losses exceed $3,000 in a tax year, the excess carries forward to future years indefinitely. Track cumulative carryforward balances across tax years.
FinCEN FBAR (Form 114)
Filing Requirement
A U.S. person must file FinCEN Form 114 if they have a financial interest in, or signature authority over, foreign financial accounts with an aggregate value exceeding $10,000 at any time during the calendar year.
Key Thresholds
| Threshold | Amount | Notes |
|---|---|---|
| Filing trigger | $10,000 aggregate | Sum of ALL foreign accounts on any single day |
| Non-willful penalty | Up to $12,500 per violation | Per account, per year |
| Willful penalty | Up to $100,000 or 50% of balance | Whichever is greater |
Crypto and FBAR
The applicability of FBAR to crypto accounts on foreign exchanges is an evolving area. FinCEN proposed rules in 2020 to include virtual currency, but final rules have not been issued as of this writing. Conservative guidance: if you hold crypto on a foreign exchange, consult a tax professional about FBAR obligations.
Required Information per Account
- Name and address of the foreign financial institution
- Account number
- Type of account
- Maximum value during the calendar year
- Currency type
Filing Details
- Where: Filed electronically via BSA E-Filing System (not with your tax return)
- Deadline: April 15, with automatic extension to October 15
- No tax due: FBAR is an information return, not a tax form
FATCA — Form 8938
Filing Thresholds
| Filing Status | Living in U.S. | Living Abroad |
|---|---|---|
| Single | $50,000 (year-end) / $75,000 (any time) | $200,000 (year-end) / $300,000 (any time) |
| Married filing jointly | $100,000 (year-end) / $150,000 (any time) | $400,000 (year-end) / $600,000 (any time) |
Form 8938 is filed with your tax return, unlike FBAR. Both may be required for the same accounts.
State-Specific Requirements
States with Notable Crypto Provisions (as of 2025)
| State | Requirement | Notes |
|---|---|---|
| California | Conforms to federal treatment | No special crypto rules; high marginal rates |
| New York | BitLicense for businesses | Individual reporting follows federal |
| Wyoming | No state income tax | Favorable crypto legislation |
| Texas | No state income tax | No additional reporting |
| Florida | No state income tax | No additional reporting |
| Colorado | Accepts crypto for tax payments | Standard capital gains treatment |
| Illinois | Follows federal treatment | Additional state capital gains |
General State Guidance
- Most states conform to federal capital gains treatment
- Some states tax capital gains as ordinary income (no preferential long-term rate)
- State-level wash sale rules may differ from federal
- Check state Department of Revenue for current guidance
Reporting Deadlines Calendar
| Date | Form | Description |
|---|---|---|
| January 15 | 1040-ES | Q4 estimated tax payment |
| January 31 | 1099 forms | Exchanges issue 1099s to taxpayers |
| April 15 | 1040 + Schedules | Federal tax return (with 8949, Schedule D) |
| April 15 | FBAR (Form 114) | Foreign account reporting |
| April 15 | 1040-ES | Q1 estimated tax payment |
| June 15 | 1040-ES | Q2 estimated tax payment |
| September 15 | 1040-ES | Q3 estimated tax payment |
| October 15 | Extended 1040 | Extended federal return deadline |
| October 15 | Extended FBAR | Automatic FBAR extension deadline |
Large Transaction Reporting (Form 8300)
Businesses receiving more than $10,000 in cash (which may include digital assets under the Infrastructure Investment and Jobs Act) must file Form 8300 within 15 days. As of 2024, the IRS has indicated that digital assets will be treated as cash for Form 8300 purposes for transactions occurring after January 1, 2024.
Key Points
- Applies to businesses, not individual traders
- Structuring transactions to avoid the $10,000 threshold is illegal
- Must report the identity of the person from whom cash was received
- Filed with FinCEN, not the IRS
Planned Implementation Features
Phase 1 — Core Reporting (Current Focus)
- Form 8949 line-item generation from trade CSV data
- FIFO cost-basis calculation
- Short-term vs. long-term classification
- Schedule D summary totals
Phase 2 — Enhanced Calculations
- Multiple cost-basis methods (LIFO, HIFO, Specific ID)
- Wash sale detection and flagging
- Like-kind exchange analysis (pre-2018 trades only)
- Capital loss carryforward tracking
Phase 3 — Additional Forms
- FinCEN FBAR data compilation
- FATCA Form 8938 threshold checking
- Form 8300 large-transaction flagging
- 1040-ES quarterly estimated tax worksheet
Phase 4 — Output & Integration
- IRS-compatible CSV export
- PDF form generation
- Exchange API import (Coinbase, Kraken, Binance)
- Multi-year reporting with carryforward
#!/usr/bin/env python3
"""Basic IRS Form 8949 line-item generator from trade data.
Generates Form 8949-compatible line items from a list of trades,
classifying each as short-term (Part I) or long-term (Part II) and
computing gain/loss per disposition.
WARNING: This is a STUB implementation for informational purposes only.
All output must be reviewed by a qualified tax professional before use
in any actual tax filing. This tool does NOT constitute tax advice.
Usage:
python scripts/form_8949_generator.py --demo
python scripts/form_8949_generator.py --csv trades.csv
python scripts/form_8949_generator.py --help
Dependencies:
None (standard library only)
Environment Variables:
None required.
"""
import argparse
import csv
import io
import json
import sys
from dataclasses import asdict, dataclass, field
from datetime import date, datetime
from decimal import Decimal, InvalidOperation
from typing import Optional
# ── Data Models ─────────────────────────────────────────────────────
@dataclass
class Trade:
"""A single buy or sell event."""
asset: str
action: str # "buy" or "sell"
quantity: Decimal
price_usd: Decimal
fee_usd: Decimal
trade_date: date
exchange: str = ""
@property
def total_usd(self) -> Decimal:
"""Total cost (buy) or proceeds (sell) including fees."""
if self.action == "buy":
return (self.quantity * self.price_usd) + self.fee_usd
return (self.quantity * self.price_usd) - self.fee_usd
@dataclass
class LotAssignment:
"""Maps a sell to a specific buy lot (FIFO)."""
asset: str
quantity: Decimal
date_acquired: date
date_sold: date
proceeds_usd: Decimal
cost_basis_usd: Decimal
adjustment_code: str = ""
adjustment_amount: Decimal = field(default_factory=lambda: Decimal("0"))
@property
def gain_loss(self) -> Decimal:
"""Net gain or loss after adjustments."""
return self.proceeds_usd - self.cost_basis_usd - self.adjustment_amount
@property
def holding_period(self) -> str:
"""'short-term' if held 365 days or fewer, else 'long-term'."""
days_held = (self.date_sold - self.date_acquired).days
return "short-term" if days_held <= 365 else "long-term"
@property
def form_8949_part(self) -> str:
"""Part I (short-term) or Part II (long-term)."""
return "I" if self.holding_period == "short-term" else "II"
def to_form_row(self) -> dict:
"""Return a dict matching Form 8949 columns."""
return {
"(a) Description": f"{self.quantity} {self.asset}",
"(b) Date Acquired": self.date_acquired.strftime("%m/%d/%Y"),
"(c) Date Sold": self.date_sold.strftime("%m/%d/%Y"),
"(d) Proceeds": f"{self.proceeds_usd:.2f}",
"(e) Cost Basis": f"{self.cost_basis_usd:.2f}",
"(f) Adjustment Code": self.adjustment_code,
"(g) Adjustment Amount": f"{self.adjustment_amount:.2f}",
"(h) Gain or Loss": f"{self.gain_loss:.2f}",
"Part": self.form_8949_part,
"Holding Period": self.holding_period,
}
@dataclass
class ScheduleDSummary:
"""Aggregated totals for Schedule D."""
short_term_proceeds: Decimal = field(default_factory=lambda: Decimal("0"))
short_term_basis: Decimal = field(default_factory=lambda: Decimal("0"))
short_term_adjustments: Decimal = field(default_factory=lambda: Decimal("0"))
short_term_gain_loss: Decimal = field(default_factory=lambda: Decimal("0"))
long_term_proceeds: Decimal = field(default_factory=lambda: Decimal("0"))
long_term_basis: Decimal = field(default_factory=lambda: Decimal("0"))
long_term_adjustments: Decimal = field(default_factory=lambda: Decimal("0"))
long_term_gain_loss: Decimal = field(default_factory=lambda: Decimal("0"))
@property
def net_gain_loss(self) -> Decimal:
return self.short_term_gain_loss + self.long_term_gain_loss
def add_lot(self, lot: LotAssignment) -> None:
"""Add a lot assignment to the running totals."""
if lot.holding_period == "short-term":
self.short_term_proceeds += lot.proceeds_usd
self.short_term_basis += lot.cost_basis_usd
self.short_term_adjustments += lot.adjustment_amount
self.short_term_gain_loss += lot.gain_loss
else:
self.long_term_proceeds += lot.proceeds_usd
self.long_term_basis += lot.cost_basis_usd
self.long_term_adjustments += lot.adjustment_amount
self.long_term_gain_loss += lot.gain_loss
# ── FIFO Lot Matching ───────────────────────────────────────────────
def match_lots_fifo(trades: list[Trade]) -> list[LotAssignment]:
"""Match sells to buys using FIFO (First In, First Out).
Args:
trades: List of Trade objects sorted by date.
Returns:
List of LotAssignment objects, one per disposition.
Raises:
ValueError: If a sell has insufficient buy lots to cover it.
"""
# Separate and sort buys by date (FIFO order)
buy_lots: dict[str, list[tuple[date, Decimal, Decimal]]] = {}
assignments: list[LotAssignment] = []
# Sort all trades by date
sorted_trades = sorted(trades, key=lambda t: t.trade_date)
for trade in sorted_trades:
asset = trade.asset.upper()
if trade.action == "buy":
if asset not in buy_lots:
buy_lots[asset] = []
cost_per_unit = trade.total_usd / trade.quantity
buy_lots[asset].append([
trade.trade_date,
trade.quantity,
cost_per_unit,
])
elif trade.action == "sell":
if asset not in buy_lots or not buy_lots[asset]:
raise ValueError(
f"No buy lots available for {trade.quantity} {asset} "
f"sold on {trade.trade_date}"
)
sell_remaining = trade.quantity
sell_price_per_unit = trade.total_usd / trade.quantity
while sell_remaining > Decimal("0"):
if not buy_lots[asset]:
raise ValueError(
f"Insufficient buy lots for {asset}: "
f"{sell_remaining} units unmatched on {trade.trade_date}"
)
lot = buy_lots[asset][0]
lot_date, lot_qty, lot_cost = lot[0], lot[1], lot[2]
if lot_qty <= sell_remaining:
# Consume entire lot
matched_qty = lot_qty
buy_lots[asset].pop(0)
else:
# Partial lot consumption
matched_qty = sell_remaining
lot[1] = lot_qty - sell_remaining
proceeds = matched_qty * sell_price_per_unit
basis = matched_qty * lot_cost
assignments.append(LotAssignment(
asset=asset,
quantity=matched_qty,
date_acquired=lot_date,
date_sold=trade.trade_date,
proceeds_usd=proceeds.quantize(Decimal("0.01")),
cost_basis_usd=basis.quantize(Decimal("0.01")),
))
sell_remaining -= matched_qty
return assignments
# ── CSV Parsing ─────────────────────────────────────────────────────
def parse_csv(csv_path: str) -> list[Trade]:
"""Parse trades from a CSV file.
Expected columns: asset, action, quantity, price_usd, fee_usd,
trade_date, exchange (optional).
Args:
csv_path: Path to the CSV file.
Returns:
List of Trade objects.
"""
trades: list[Trade] = []
with open(csv_path, "r", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
try:
trade = Trade(
asset=row["asset"].strip().upper(),
action=row["action"].strip().lower(),
quantity=Decimal(row["quantity"].strip()),
price_usd=Decimal(row["price_usd"].strip()),
fee_usd=Decimal(row.get("fee_usd", "0").strip() or "0"),
trade_date=datetime.strptime(
row["trade_date"].strip(), "%Y-%m-%d"
).date(),
exchange=row.get("exchange", "").strip(),
)
if trade.action not in ("buy", "sell"):
print(f"Warning: skipping row with action '{trade.action}'")
continue
trades.append(trade)
except (KeyError, InvalidOperation, ValueError) as e:
print(f"Warning: skipping malformed row: {e}")
continue
return trades
# ── Demo Data ───────────────────────────────────────────────────────
def generate_demo_trades() -> list[Trade]:
"""Generate sample trades for demonstration purposes.
Returns:
List of demo Trade objects covering various scenarios.
"""
return [
# Buy SOL in January 2025
Trade("SOL", "buy", Decimal("50"), Decimal("95.00"),
Decimal("2.50"), date(2025, 1, 10), "Coinbase"),
# Buy more SOL in March 2025
Trade("SOL", "buy", Decimal("30"), Decimal("140.00"),
Decimal("1.80"), date(2025, 3, 15), "Coinbase"),
# Sell some SOL in June 2025 (short-term, matches Jan lot via FIFO)
Trade("SOL", "sell", Decimal("40"), Decimal("180.00"),
Decimal("3.60"), date(2025, 6, 20), "Coinbase"),
# Buy BTC in February 2025
Trade("BTC", "buy", Decimal("0.5"), Decimal("42000.00"),
Decimal("10.00"), date(2025, 2, 1), "Kraken"),
# Sell BTC in September 2025 (short-term)
Trade("BTC", "sell", Decimal("0.3"), Decimal("58000.00"),
Decimal("8.70"), date(2025, 9, 10), "Kraken"),
# Buy ETH in January 2024 (for long-term example)
Trade("ETH", "buy", Decimal("5"), Decimal("2200.00"),
Decimal("5.50"), date(2024, 1, 5), "Coinbase"),
# Sell ETH in March 2025 (long-term, held > 1 year)
Trade("ETH", "sell", Decimal("3"), Decimal("3500.00"),
Decimal("5.25"), date(2025, 3, 20), "Coinbase"),
]
# ── Report Formatting ──────────────────────────────────────────────
def print_form_8949(assignments: list[LotAssignment]) -> None:
"""Print Form 8949 line items to stdout.
Args:
assignments: List of LotAssignment objects from FIFO matching.
"""
part_i = [a for a in assignments if a.form_8949_part == "I"]
part_ii = [a for a in assignments if a.form_8949_part == "II"]
header = (
f"{'Description':<20} {'Acquired':<12} {'Sold':<12} "
f"{'Proceeds':>12} {'Basis':>12} {'Adj Code':>9} "
f"{'Adj Amt':>10} {'Gain/Loss':>12}"
)
separator = "-" * len(header)
if part_i:
print("\n" + "=" * len(header))
print("FORM 8949 — PART I: Short-Term (held one year or less)")
print("Box C: Basis NOT reported to IRS; no Form 1099-B received")
print("=" * len(header))
print(header)
print(separator)
for lot in part_i:
row = lot.to_form_row()
print(
f"{row['(a) Description']:<20} "
f"{row['(b) Date Acquired']:<12} "
f"{row['(c) Date Sold']:<12} "
f"${row['(d) Proceeds']:>11} "
f"${row['(e) Cost Basis']:>11} "
f"{row['(f) Adjustment Code']:>9} "
f"${row['(g) Adjustment Amount']:>9} "
f"${row['(h) Gain or Loss']:>11}"
)
if part_ii:
print("\n" + "=" * len(header))
print("FORM 8949 — PART II: Long-Term (held more than one year)")
print("Box F: Basis NOT reported to IRS; no Form 1099-B received")
print("=" * len(header))
print(header)
print(separator)
for lot in part_ii:
row = lot.to_form_row()
print(
f"{row['(a) Description']:<20} "
f"{row['(b) Date Acquired']:<12} "
f"{row['(c) Date Sold']:<12} "
f"${row['(d) Proceeds']:>11} "
f"${row['(e) Cost Basis']:>11} "
f"{row['(f) Adjustment Code']:>9} "
f"${row['(g) Adjustment Amount']:>9} "
f"${row['(h) Gain or Loss']:>11}"
)
def print_schedule_d(summary: ScheduleDSummary) -> None:
"""Print Schedule D summary totals.
Args:
summary: Aggregated ScheduleDSummary object.
"""
print("\n" + "=" * 60)
print("SCHEDULE D SUMMARY — Capital Gains and Losses")
print("=" * 60)
print(f"\nShort-Term Capital Gains/Losses (Part I):")
print(f" Total Proceeds: ${summary.short_term_proceeds:>12,.2f}")
print(f" Total Cost Basis: ${summary.short_term_basis:>12,.2f}")
print(f" Total Adjustments: ${summary.short_term_adjustments:>12,.2f}")
print(f" Net Short-Term: ${summary.short_term_gain_loss:>12,.2f}")
print(f"\nLong-Term Capital Gains/Losses (Part II):")
print(f" Total Proceeds: ${summary.long_term_proceeds:>12,.2f}")
print(f" Total Cost Basis: ${summary.long_term_basis:>12,.2f}")
print(f" Total Adjustments: ${summary.long_term_adjustments:>12,.2f}")
print(f" Net Long-Term: ${summary.long_term_gain_loss:>12,.2f}")
print(f"\nCombined Net Gain/(Loss): ${summary.net_gain_loss:>12,.2f}")
if summary.net_gain_loss < 0:
deductible = max(summary.net_gain_loss, Decimal("-3000"))
carryforward = summary.net_gain_loss - deductible
print(f" Deductible this year: ${deductible:>12,.2f}")
if carryforward < 0:
print(f" Carryforward to next: ${carryforward:>12,.2f}")
def export_json(assignments: list[LotAssignment]) -> str:
"""Export lot assignments as JSON.
Args:
assignments: List of LotAssignment objects.
Returns:
JSON string of form 8949 rows.
"""
rows = [a.to_form_row() for a in assignments]
return json.dumps(rows, indent=2)
# ── Main ────────────────────────────────────────────────────────────
def main() -> None:
"""Entry point: parse arguments and generate Form 8949 report."""
parser = argparse.ArgumentParser(
description="Generate IRS Form 8949 line items from trade data. "
"WARNING: Output must be reviewed by a tax professional.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=(
"DISCLAIMER: This tool is for informational purposes only and "
"does NOT constitute tax advice. Consult a qualified tax "
"professional before using any output for tax filings."
),
)
parser.add_argument(
"--demo", action="store_true",
help="Run with built-in sample trade data",
)
parser.add_argument(
"--csv", type=str, default="",
help="Path to CSV file with columns: asset, action, quantity, "
"price_usd, fee_usd, trade_date, exchange",
)
parser.add_argument(
"--json", action="store_true",
help="Output as JSON instead of formatted text",
)
args = parser.parse_args()
if not args.demo and not args.csv:
parser.print_help()
print("\nError: specify --demo or --csv <file>")
sys.exit(1)
# Load trades
if args.demo:
print("=" * 60)
print("DEMO MODE — Using sample trade data")
print("This is NOT real financial data")
print("=" * 60)
trades = generate_demo_trades()
else:
trades = parse_csv(args.csv)
if not trades:
print("No valid trades found in CSV file.")
sys.exit(1)
# Match lots via FIFO
try:
assignments = match_lots_fifo(trades)
except ValueError as e:
print(f"Error matching lots: {e}")
sys.exit(1)
if not assignments:
print("No dispositions found (no sells matched to buys).")
sys.exit(0)
# Output
if args.json:
print(export_json(assignments))
else:
print_form_8949(assignments)
# Schedule D summary
summary = ScheduleDSummary()
for lot in assignments:
summary.add_lot(lot)
print_schedule_d(summary)
print("\n" + "-" * 60)
print("WARNING: This output is for informational purposes only.")
print("Consult a qualified tax professional before filing.")
print("-" * 60)
if __name__ == "__main__":
main()
Related skills
FAQ
Can I file with this skill's output?
No; it is a STUB, has not been validated against IRS guidance, and its output must be reviewed by a qualified tax professional before filing.
What is implemented today?
Basic Form 8949 line-item generation from trade data (scripts/form_8949_generator.py) with short-term/long-term classification and a demo mode.