From 25ad16dac2749293e3968ae1c01043666f41fdf3 Mon Sep 17 00:00:00 2001 From: mark Date: Sat, 18 Jul 2026 13:34:47 -0400 Subject: [PATCH] Structure into library for pip --- .gitignore | 49 ++++++++++++++++++ LICENSE | 21 ++++++++ README.md | 88 +++++++++++++++++++++++++++++++++ kalshi-parse.py | 99 ------------------------------------- pyproject.toml | 33 +++++++++++++ src/kalshi_csv/__init__.py | 5 ++ src/kalshi_csv/cli.py | 80 ++++++++++++++++++++++++++++++ src/kalshi_csv/formatter.py | 39 +++++++++++++++ src/kalshi_csv/parser.py | 70 ++++++++++++++++++++++++++ tests/conftest.py | 8 +++ tests/test_cli.py | 67 +++++++++++++++++++++++++ tests/test_parser.py | 80 ++++++++++++++++++++++++++++++ 12 files changed, 540 insertions(+), 99 deletions(-) create mode 100644 LICENSE create mode 100644 README.md delete mode 100755 kalshi-parse.py create mode 100644 pyproject.toml create mode 100644 src/kalshi_csv/__init__.py create mode 100644 src/kalshi_csv/cli.py create mode 100644 src/kalshi_csv/formatter.py create mode 100644 src/kalshi_csv/parser.py create mode 100644 tests/conftest.py create mode 100644 tests/test_cli.py create mode 100644 tests/test_parser.py diff --git a/.gitignore b/.gitignore index 949a96c..1b73554 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,51 @@ # Private Kalshi csvs *.csv + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class +*.so + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +.venv/ +venv/ +ENV/ + +# Pytest +.pytest_cache/ +.coverage + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Jupyter Notebook +.ipynb_checkpoints + +# Environment variables +.env diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..12c9bbe --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Mark Robillard Jr + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2a30432 --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# kalshi-csv + +Parse Kalshi transaction CSV files and generate IRS Form 8949 tax summaries for event contract trading. + +## Installation + +```bash +pip install kalshi-csv +``` + +## CLI Usage + +Parse a Kalshi transactions CSV and display the trade matrix with IRS summary: + +```bash +kalshi-csv Kalshi-Transactions-2026.csv +``` + +Export the IRS summary to a file: + +```bash +kalshi-csv Kalshi-Transactions-2026.csv --irs-file irs-summary.txt +``` + +Disable colored output (useful for piping or redirecting): + +```bash +kalshi-csv Kalshi-Transactions-2026.csv --no-color +``` + +### Sample Output + +``` +Ticker | Side | Qty | Entry | Exit | P&L (No Fees) +------------------------------------------------------------------------------- +KXWCADVANCE-26JUL07ARGEGY-ARG | YES | 0.17 | $0.86 | $0.69 | -$0.03 +KXWC1H-26JUL07ARGEGY-TIE | YES | 0.34 | $0.28 | $0.00 | -$0.10 +KXMLBGAME-26JUL081940BOSCWS-BOS | YES | 0.96 | $0.50 | $0.91 | $0.39 +------------------------------------------------------------------------------- +Total Transactions Parsed: 3 +Total Exchange Fees Paid: $0.05 +Internal Tracked Net P&L: $0.26 +------------------------------------------------------------------------------- +=== IRS FORM 8949 / SCHEDULE D AGGREGATE SUMMARY === +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) + * (d) Gross Proceeds: $2.50 + * (e) Cost or Other Basis: $2.24 + * (h) Gain or (Loss): $0.26 +==================================================== +``` + +## Library API + +Use `kalshi-csv` as a Python library in your own scripts: + +```python +from kalshi_csv import KalshiCSV + +kalshi = KalshiCSV("Kalshi-Transactions-2026.csv") +kalshi.parse() + +print(f"Total trades: {kalshi.summary['trade_count']}") +print(f"Total P&L: ${kalshi.summary['total_pnl_with_fees']:.2f}") + +irs = kalshi.irs_summary() +print(f"Gross Proceeds: ${irs['gross_proceeds']:.2f}") +print(f"Cost Basis: ${irs['cost_basis']:.2f}") +print(f"Gain/Loss: ${irs['gain_or_loss']:.2f}") +``` + +## IRS Form 8949 + +Kalshi event contracts are typically reported on **IRS Form 8949, Box C** (short-term transactions not reported on Form 1099-B). The tool calculates: + +- **Gross Proceeds**: Total exit value minus close fees +- **Cost Basis**: Total entry value plus open fees +- **Gain/Loss**: Realized P&L including all fees + +Use the aggregate summary for a single-line entry on Form 8949, or export to a file for your records. + +**Disclaimer**: This tool provides calculations based on Kalshi transaction data. Consult a tax professional for specific tax advice. + +## License + +MIT + diff --git a/kalshi-parse.py b/kalshi-parse.py deleted file mode 100755 index 867a584..0000000 --- a/kalshi-parse.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env python3 -import csv -import sys -import os - -class KalshiCSV: - """Parses Kalshi transaction CSV data and handles terminal color-coding - and tax summary generation. - """ - def __init__(self, file_path): - self.file_path = file_path - self.trade_count = 0 - self.total_tax_basis = 0.0 - self.total_tax_proceeds = 0.0 - self.total_pnl_without_fees = 0.0 - self.total_pnl_with_fees = 0.0 - self.total_fees = 0.0 - - def parse(self): - """Processes the CSV file row-by-row and prints the live trade matrix.""" - if not os.path.exists(self.file_path): - print(f"Error: File '{self.file_path}' not found.") - sys.exit(1) - - print(f"\n{'Ticker':<32} | {'Side':<4} | {'Qty':<6} | {'Entry':<6} | {'Exit':<6} | {'P&L (No Fees)':<14}") - print("-" * 83) - - with open(self.file_path, mode='r', newline='', encoding='utf-8') as f: - reader = csv.DictReader(f) - - for row in reader: - if not row.get('realized_pnl_without_fees_dollars'): - continue - - ticker = row['market_ticker'] - side = row['side'].upper() - qty = float(row['quantity_fp']) - entry = float(row['entry_price_dollars']) - exit_val = float(row['exit_price_dollars']) - - pnl_no_fees = float(row['realized_pnl_without_fees_dollars']) - pnl_with_fees = float(row['realized_pnl_with_fees_dollars']) - - open_fees = float(row['open_fees_dollars']) - close_fees = float(row['close_fees_dollars']) - - # --- Aggregate Calculations --- - self.total_tax_basis += (qty * entry) + open_fees - self.total_tax_proceeds += (qty * exit_val) - close_fees - self.total_pnl_without_fees += pnl_no_fees - self.total_pnl_with_fees += pnl_with_fees - self.total_fees += (open_fees + close_fees) - self.trade_count += 1 - - # Format row P&L using the color methods - pnl_str = self.format_currency_color(pnl_no_fees) - print(f"{ticker:<32} | {side:<4} | {qty:<6.2f} | ${entry:<5.2f} | ${exit_val:<5.2f} | {pnl_str:<14}") - - print("-" * 83) - print(f"Total Transactions Parsed: {self.trade_count}") - print(f"Total Exchange Fees Paid: ${self.total_fees:.2f}") - print(f"Internal Tracked Net P&L: " + self.format_currency_color(self.total_pnl_without_fees)) - print("-" * 83) - - def color_green(self, text): - """Wraps text in ANSI green.""" - return f"\033[1;32m{text}\033[0m" - - def color_red(self, text): - """Wraps text in ANSI red.""" - return f"\033[1;31m{text}\033[0m" - - def format_currency_color(self, value): - """Returns a signed, colorized string based on profit or loss status.""" - val_str = f"${value:+.2f}" - return self.color_green(val_str) if value >= 0 else self.color_red(val_str) - - def summarizeIRS(self): - """Outputs the structured block needed for a single-line entry on IRS Form 8949.""" - print("\033[1;33m=== IRS FORM 8949 / SCHEDULE D AGGREGATE SUMMARY ===\033[0m") - print("Use these exact aggregates for a single-line summary entry:") - print(f" * Box to Check: \033[1;37mBox C\033[0m (Short-term, not reported on Form 1099-B)") - print(f" * (a) Description: Kalshi Event Contracts (Aggregate Summary)") - print(f" * (d) Gross Proceeds: " + self.color_green(f"${self.total_tax_proceeds:.2f}")) - print(f" * (e) Cost or Other Basis: \033[1;36m${self.total_tax_basis:.2f}\033[0m") - print(f" * (h) Gain or (Loss): " + self.format_currency_color(self.total_pnl_with_fees)) - print("\033[1;33m====================================================\033[0m\n") - - -if __name__ == '__main__': - if len(sys.argv) < 2: - print("Usage: python3 kalshi_parser.py ") - sys.exit(1) - - # Instantiate, parse the matrix, and drop the IRS aggregate summary - parser = KalshiCSV(sys.argv[1]) - parser.parse() - parser.summarizeIRS() - diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3632b43 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,33 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "kalshi-csv" +version = "0.1.0" +description = "Parse Kalshi transaction CSV files and generate IRS Form 8949 tax summaries" +readme = "README.md" +license = "MIT" +requires-python = ">=3.8" +authors = [ + { name = "markmental", email = "marky611@gmail.com" } +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: End Users/Desktop", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Office/Business :: Financial", +] +keywords = ["kalshi", "csv", "tax", "irs", "form-8949"] + +[project.scripts] +kalshi-csv = "kalshi_csv.cli:main" + +[tool.hatch.build.targets.wheel] +packages = ["src/kalshi_csv"] diff --git a/src/kalshi_csv/__init__.py b/src/kalshi_csv/__init__.py new file mode 100644 index 0000000..b181324 --- /dev/null +++ b/src/kalshi_csv/__init__.py @@ -0,0 +1,5 @@ +__version__ = "0.1.0" + +from .parser import KalshiCSV + +__all__ = ["KalshiCSV", "__version__"] diff --git a/src/kalshi_csv/cli.py b/src/kalshi_csv/cli.py new file mode 100644 index 0000000..39bf353 --- /dev/null +++ b/src/kalshi_csv/cli.py @@ -0,0 +1,80 @@ +import argparse +import sys + +from .parser import KalshiCSV +from .formatter import ( + color_white, + color_yellow, + color_cyan, + format_currency_color, +) + + +def main(): + parser = argparse.ArgumentParser( + description="Parse Kalshi transaction CSV and generate IRS tax summary." + ) + parser.add_argument("csv_path", help="Path to Kalshi transactions CSV file") + parser.add_argument( + "--irs-file", + help="Write IRS Form 8949 summary to this file", + ) + parser.add_argument( + "--no-color", + action="store_true", + help="Disable ANSI color output", + ) + + args = parser.parse_args() + no_color = args.no_color + + kalshi = KalshiCSV(args.csv_path) + kalshi.parse() + + print() + print( + f"{'Ticker':<32} | {'Side':<4} | {'Qty':<6} | {'Entry':<6} | {'Exit':<6} | {'P&L (No Fees)':<14}" + ) + print("-" * 83) + + for trade in kalshi.trades: + pnl_str = format_currency_color(trade["pnl_no_fees"], no_color) + print( + f"{trade['ticker']:<32} | {trade['side']:<4} | {trade['qty']:<6.2f} | " + f"${trade['entry']:<5.2f} | ${trade['exit']:<5.2f} | {pnl_str:<14}" + ) + + print("-" * 83) + print(f"Total Transactions Parsed: {kalshi.summary['trade_count']}") + print(f"Total Exchange Fees Paid: ${kalshi.summary['total_fees']:.2f}") + print( + f"Internal Tracked Net P&L: " + + format_currency_color(kalshi.summary["total_pnl_without_fees"], no_color) + ) + print("-" * 83) + + irs = kalshi.irs_summary() + print(color_yellow("=== IRS FORM 8949 / SCHEDULE D AGGREGATE SUMMARY ===", no_color)) + 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" * (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)}") + print(color_yellow("====================================================", no_color)) + print() + + if args.irs_file: + with open(args.irs_file, "w", encoding="utf-8") as f: + f.write("IRS FORM 8949 / SCHEDULE D AGGREGATE SUMMARY\n") + 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" * (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") + print(f"IRS summary written to: {args.irs_file}") + + +if __name__ == "__main__": + main() diff --git a/src/kalshi_csv/formatter.py b/src/kalshi_csv/formatter.py new file mode 100644 index 0000000..73769a0 --- /dev/null +++ b/src/kalshi_csv/formatter.py @@ -0,0 +1,39 @@ +def color_green(text, no_color=False): + """Wraps text in ANSI green.""" + if no_color: + return text + return f"\033[1;32m{text}\033[0m" + + +def color_red(text, no_color=False): + """Wraps text in ANSI red.""" + if no_color: + return text + return f"\033[1;31m{text}\033[0m" + + +def color_yellow(text, no_color=False): + """Wraps text in ANSI yellow.""" + if no_color: + return text + return f"\033[1;33m{text}\033[0m" + + +def color_cyan(text, no_color=False): + """Wraps text in ANSI cyan.""" + if no_color: + return text + return f"\033[1;36m{text}\033[0m" + + +def color_white(text, no_color=False): + """Wraps text in ANSI white.""" + if no_color: + return text + return f"\033[1;37m{text}\033[0m" + + +def format_currency_color(value, no_color=False): + """Returns a signed, colorized string based on profit or loss status.""" + val_str = f"${value:+.2f}" + return color_green(val_str, no_color) if value >= 0 else color_red(val_str, no_color) diff --git a/src/kalshi_csv/parser.py b/src/kalshi_csv/parser.py new file mode 100644 index 0000000..46b6470 --- /dev/null +++ b/src/kalshi_csv/parser.py @@ -0,0 +1,70 @@ +import csv +import os + + +class KalshiCSV: + """Parses Kalshi transaction CSV data and calculates tax-relevant aggregates.""" + + def __init__(self, file_path): + self.file_path = file_path + self.trades = [] + self.summary = { + "trade_count": 0, + "total_fees": 0.0, + "total_pnl_without_fees": 0.0, + "total_pnl_with_fees": 0.0, + "total_tax_basis": 0.0, + "total_tax_proceeds": 0.0, + } + + def parse(self): + """Processes the CSV file row-by-row and populates trades and summary.""" + if not os.path.exists(self.file_path): + raise FileNotFoundError(f"File '{self.file_path}' not found.") + + with open(self.file_path, mode="r", newline="", encoding="utf-8") as f: + reader = csv.DictReader(f) + + for row in reader: + if not row.get("realized_pnl_without_fees_dollars"): + continue + + qty = float(row["quantity_fp"]) + entry = float(row["entry_price_dollars"]) + exit_val = float(row["exit_price_dollars"]) + pnl_no_fees = float(row["realized_pnl_without_fees_dollars"]) + pnl_with_fees = float(row["realized_pnl_with_fees_dollars"]) + open_fees = float(row["open_fees_dollars"]) + close_fees = float(row["close_fees_dollars"]) + + trade = { + "ticker": row["market_ticker"], + "side": row["side"].upper(), + "qty": qty, + "entry": entry, + "exit": exit_val, + "pnl_no_fees": pnl_no_fees, + "pnl_with_fees": pnl_with_fees, + "open_fees": open_fees, + "close_fees": close_fees, + } + self.trades.append(trade) + + self.summary["trade_count"] += 1 + self.summary["total_tax_basis"] += (qty * entry) + open_fees + self.summary["total_tax_proceeds"] += (qty * exit_val) - close_fees + self.summary["total_pnl_without_fees"] += pnl_no_fees + self.summary["total_pnl_with_fees"] += pnl_with_fees + self.summary["total_fees"] += open_fees + close_fees + + return self + + def irs_summary(self): + """Returns a dict with IRS Form 8949 aggregate fields.""" + return { + "box": "C", + "description": "Kalshi Event Contracts (Aggregate Summary)", + "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/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..e870412 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,8 @@ +import pytest +from pathlib import Path + + +@pytest.fixture +def sample_csv(): + """Returns the path to the synthetic test CSV fixture.""" + return str(Path(__file__).parent / "fixtures" / "sample.csv") diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..14b7117 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,67 @@ +import subprocess +import sys +from pathlib import Path + + +def test_cli_runs_successfully(sample_csv): + result = subprocess.run( + [sys.executable, "-m", "kalshi_csv.cli", sample_csv], + capture_output=True, + text=True, + ) + assert result.returncode == 0 + assert "TESTMARKET-WIN" in result.stdout + assert "TESTMARKET-LOSS" in result.stdout + assert "Total Transactions Parsed: 3" in result.stdout + + +def test_cli_irs_summary_output(sample_csv): + result = subprocess.run( + [sys.executable, "-m", "kalshi_csv.cli", sample_csv], + capture_output=True, + text=True, + ) + assert "IRS FORM 8949" in result.stdout + assert "Box C" in result.stdout + assert "Gross Proceeds:" in result.stdout + assert "Cost or Other Basis:" in result.stdout + assert "Gain or (Loss):" in result.stdout + + +def test_cli_irs_file_creation(sample_csv, tmp_path): + irs_file = tmp_path / "irs-output.txt" + result = subprocess.run( + [sys.executable, "-m", "kalshi_csv.cli", sample_csv, "--irs-file", str(irs_file)], + capture_output=True, + text=True, + ) + assert result.returncode == 0 + assert irs_file.exists() + + content = irs_file.read_text() + assert "IRS FORM 8949" in content + assert "Box C" in content + assert "Gross Proceeds:" in content + assert "Cost or Other Basis:" in content + assert "Gain or (Loss):" in content + assert "\033[" not in content + + +def test_cli_no_color_flag(sample_csv): + result = subprocess.run( + [sys.executable, "-m", "kalshi_csv.cli", sample_csv, "--no-color"], + capture_output=True, + text=True, + ) + assert result.returncode == 0 + assert "\033[" not in result.stdout + + +def test_cli_missing_file(): + result = subprocess.run( + [sys.executable, "-m", "kalshi_csv.cli", "/nonexistent/file.csv"], + capture_output=True, + text=True, + ) + assert result.returncode != 0 + assert "not found" in result.stderr.lower() or "error" in result.stderr.lower() diff --git a/tests/test_parser.py b/tests/test_parser.py new file mode 100644 index 0000000..22541dc --- /dev/null +++ b/tests/test_parser.py @@ -0,0 +1,80 @@ +import pytest +from kalshi_csv import KalshiCSV + + +def test_parse_returns_self(sample_csv): + kalshi = KalshiCSV(sample_csv) + result = kalshi.parse() + assert result is kalshi + + +def test_trade_count(sample_csv): + kalshi = KalshiCSV(sample_csv) + kalshi.parse() + assert kalshi.summary["trade_count"] == 3 + + +def test_total_fees(sample_csv): + kalshi = KalshiCSV(sample_csv) + kalshi.parse() + assert abs(kalshi.summary["total_fees"] - 0.07) < 1e-6 + + +def test_total_pnl_without_fees(sample_csv): + kalshi = KalshiCSV(sample_csv) + kalshi.parse() + assert abs(kalshi.summary["total_pnl_without_fees"] - (-0.20)) < 1e-6 + + +def test_total_pnl_with_fees(sample_csv): + kalshi = KalshiCSV(sample_csv) + kalshi.parse() + assert abs(kalshi.summary["total_pnl_with_fees"] - (-0.27)) < 1e-6 + + +def test_total_tax_basis(sample_csv): + kalshi = KalshiCSV(sample_csv) + kalshi.parse() + assert abs(kalshi.summary["total_tax_basis"] - 1.64) < 1e-6 + + +def test_total_tax_proceeds(sample_csv): + kalshi = KalshiCSV(sample_csv) + kalshi.parse() + assert abs(kalshi.summary["total_tax_proceeds"] - 1.37) < 1e-6 + + +def test_trades_list_length(sample_csv): + kalshi = KalshiCSV(sample_csv) + kalshi.parse() + assert len(kalshi.trades) == 3 + + +def test_first_trade_data(sample_csv): + kalshi = KalshiCSV(sample_csv) + kalshi.parse() + trade = kalshi.trades[0] + assert trade["ticker"] == "TESTMARKET-WIN" + assert trade["side"] == "YES" + assert trade["qty"] == 1.0 + assert trade["entry"] == 0.50 + assert trade["exit"] == 1.00 + assert abs(trade["pnl_no_fees"] - 0.50) < 1e-6 + assert abs(trade["pnl_with_fees"] - 0.47) < 1e-6 + + +def test_irs_summary(sample_csv): + kalshi = KalshiCSV(sample_csv) + kalshi.parse() + irs = kalshi.irs_summary() + assert irs["box"] == "C" + assert irs["description"] == "Kalshi Event Contracts (Aggregate Summary)" + 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_file_not_found(): + kalshi = KalshiCSV("/nonexistent/path.csv") + with pytest.raises(FileNotFoundError): + kalshi.parse()