From ebeaa8fdc41a4e064dd5f2ea0c2aa198742720d3 Mon Sep 17 00:00:00 2001 From: mark Date: Fri, 24 Jul 2026 20:39:36 -0400 Subject: [PATCH] 0.1.2 - Add support for acquired and sold dates on IRS Form 8949 --- README.md | 4 ++++ pyproject.toml | 2 +- src/kalshi_csv/__init__.py | 2 +- src/kalshi_csv/cli.py | 4 ++++ src/kalshi_csv/parser.py | 33 +++++++++++++++++++++++++++++++++ tests/test_cli.py | 4 ++++ tests/test_parser.py | 11 +++++++++++ 7 files changed, 58 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 95d43ff..c31fdb5 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,8 @@ Internal Tracked Net P&L: $+0.26 Use these exact aggregates for a single-line summary entry: * Box to Check: Box C (Short-term, not reported on Form 1099-B) * (a) Description: Kalshi Event Contracts (Aggregate Summary) + * (b) Date Acquired: 07/07/2026 + * (c) Date Sold: 07/08/2026 * (d) Gross Proceeds: $+2.50 * (e) Cost or Other Basis: $2.24 * (h) Gain or (Loss): $+0.26 @@ -100,6 +102,8 @@ Internal Tracked Net P&L: $+0.26 Use these exact aggregates for a single-line summary entry: * Box to Check: Box C (Short-term, not reported on Form 1099-B) * (a) Description: Kalshi Event Contracts (Aggregate Summary) + * (b) Date Acquired: 07/07/2026 + * (c) Date Sold: 07/08/2026 * (d) Gross Proceeds: $+2.50 * (e) Cost or Other Basis: $2.24 * (h) Gain or (Loss): $+0.26 diff --git a/pyproject.toml b/pyproject.toml index a64f717..c1de816 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "kalshi-csv" -version = "0.1.1" +version = "0.1.2" description = "Parse Kalshi transaction CSV files and generate IRS Form 8949 tax summaries" readme = "README.md" license = "MIT" diff --git a/src/kalshi_csv/__init__.py b/src/kalshi_csv/__init__.py index abf61a0..3f9929b 100644 --- a/src/kalshi_csv/__init__.py +++ b/src/kalshi_csv/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.1.1" +__version__ = "0.1.2" from .parser import KalshiCSV diff --git a/src/kalshi_csv/cli.py b/src/kalshi_csv/cli.py index fd0efca..276ae3d 100644 --- a/src/kalshi_csv/cli.py +++ b/src/kalshi_csv/cli.py @@ -82,6 +82,8 @@ def main(): print("Use these exact aggregates for a single-line summary entry:") print(f" * Box to Check: {color_white('Box C', no_color)} (Short-term, not reported on Form 1099-B)") print(f" * (a) Description: {irs['description']}") + print(f" * (b) Date Acquired: {irs['date_acquired']}") + print(f" * (c) Date Sold: {irs['date_sold']}") print(f" * (d) Gross Proceeds: {format_currency_color(irs['gross_proceeds'], no_color)}") print(f" * (e) Cost or Other Basis: {color_cyan(f'${irs['cost_basis']:.2f}', no_color)}") print(f" * (h) Gain or (Loss): {format_currency_color(irs['gain_or_loss'], no_color)}") @@ -94,6 +96,8 @@ def main(): f.write("Use these exact aggregates for a single-line summary entry:\n") f.write(f" * Box to Check: Box C (Short-term, not reported on Form 1099-B)\n") f.write(f" * (a) Description: {irs['description']}\n") + f.write(f" * (b) Date Acquired: {irs['date_acquired']}\n") + f.write(f" * (c) Date Sold: {irs['date_sold']}\n") f.write(f" * (d) Gross Proceeds: ${irs['gross_proceeds']:.2f}\n") f.write(f" * (e) Cost or Other Basis: ${irs['cost_basis']:.2f}\n") f.write(f" * (h) Gain or (Loss): ${irs['gain_or_loss']:.2f}\n") diff --git a/src/kalshi_csv/parser.py b/src/kalshi_csv/parser.py index 46b6470..d072147 100644 --- a/src/kalshi_csv/parser.py +++ b/src/kalshi_csv/parser.py @@ -1,5 +1,6 @@ import csv import os +from datetime import datetime class KalshiCSV: @@ -15,6 +16,8 @@ class KalshiCSV: "total_pnl_with_fees": 0.0, "total_tax_basis": 0.0, "total_tax_proceeds": 0.0, + "earliest_open_date": None, + "latest_close_date": None, } def parse(self): @@ -57,13 +60,43 @@ class KalshiCSV: self.summary["total_pnl_with_fees"] += pnl_with_fees self.summary["total_fees"] += open_fees + close_fees + if row.get("open_timestamp"): + try: + open_dt = datetime.fromisoformat(row["open_timestamp"]) + if ( + self.summary["earliest_open_date"] is None + or open_dt < self.summary["earliest_open_date"] + ): + self.summary["earliest_open_date"] = open_dt + except ValueError: + pass + + if row.get("close_timestamp"): + try: + close_dt = datetime.fromisoformat(row["close_timestamp"]) + if ( + self.summary["latest_close_date"] is None + or close_dt > self.summary["latest_close_date"] + ): + self.summary["latest_close_date"] = close_dt + except ValueError: + pass + return self + def _format_date(self, dt): + """Formats a datetime object as MM/DD/YYYY for IRS Form 8949.""" + if dt is None: + return "" + return dt.strftime("%m/%d/%Y") + def irs_summary(self): """Returns a dict with IRS Form 8949 aggregate fields.""" return { "box": "C", "description": "Kalshi Event Contracts (Aggregate Summary)", + "date_acquired": self._format_date(self.summary["earliest_open_date"]), + "date_sold": self._format_date(self.summary["latest_close_date"]), "gross_proceeds": self.summary["total_tax_proceeds"], "cost_basis": self.summary["total_tax_basis"], "gain_or_loss": self.summary["total_pnl_with_fees"], diff --git a/tests/test_cli.py b/tests/test_cli.py index 16d5bc4..13ac9ac 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -24,6 +24,8 @@ def test_cli_irs_summary_output(sample_csv): ) assert "IRS FORM 8949" in result.stdout assert "Box C" in result.stdout + assert "Date Acquired:" in result.stdout + assert "Date Sold:" in result.stdout assert "Gross Proceeds:" in result.stdout assert "Cost or Other Basis:" in result.stdout assert "Gain or (Loss):" in result.stdout @@ -42,6 +44,8 @@ def test_cli_irs_file_creation(sample_csv, tmp_path): content = irs_file.read_text() assert "IRS FORM 8949" in content assert "Box C" in content + assert "Date Acquired:" in content + assert "Date Sold:" in content assert "Gross Proceeds:" in content assert "Cost or Other Basis:" in content assert "Gain or (Loss):" in content diff --git a/tests/test_parser.py b/tests/test_parser.py index 22541dc..83a77a6 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -69,11 +69,22 @@ def test_irs_summary(sample_csv): irs = kalshi.irs_summary() assert irs["box"] == "C" assert irs["description"] == "Kalshi Event Contracts (Aggregate Summary)" + assert irs["date_acquired"] == "07/07/2026" + assert irs["date_sold"] == "07/07/2026" assert abs(irs["gross_proceeds"] - 1.37) < 1e-6 assert abs(irs["cost_basis"] - 1.64) < 1e-6 assert abs(irs["gain_or_loss"] - (-0.27)) < 1e-6 +def test_date_tracking(sample_csv): + kalshi = KalshiCSV(sample_csv) + kalshi.parse() + assert kalshi.summary["earliest_open_date"] is not None + assert kalshi.summary["latest_close_date"] is not None + assert kalshi.summary["earliest_open_date"].strftime("%m/%d/%Y") == "07/07/2026" + assert kalshi.summary["latest_close_date"].strftime("%m/%d/%Y") == "07/07/2026" + + def test_file_not_found(): kalshi = KalshiCSV("/nonexistent/path.csv") with pytest.raises(FileNotFoundError):