0.1.2 - Add support for acquired and sold dates on IRS Form 8949

This commit is contained in:
mark 2026-07-24 20:39:36 -04:00
commit ebeaa8fdc4
7 changed files with 58 additions and 2 deletions

View file

@ -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

View file

@ -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"

View file

@ -1,4 +1,4 @@
__version__ = "0.1.1"
__version__ = "0.1.2"
from .parser import KalshiCSV

View file

@ -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")

View file

@ -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"],

View file

@ -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

View file

@ -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):