0.1.2 - Add support for acquired and sold dates on IRS Form 8949
This commit is contained in:
parent
0c5c181506
commit
ebeaa8fdc4
7 changed files with 58 additions and 2 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
__version__ = "0.1.1"
|
||||
__version__ = "0.1.2"
|
||||
|
||||
from .parser import KalshiCSV
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue