kalshi-csv 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,51 @@
1
+ # Private Kalshi csvs
2
+ *.csv
3
+
4
+ # Byte-compiled / optimized / DLL files
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+ *.so
9
+
10
+ # C extensions
11
+ *.so
12
+
13
+ # Distribution / packaging
14
+ .Python
15
+ build/
16
+ develop-eggs/
17
+ dist/
18
+ downloads/
19
+ eggs/
20
+ .eggs/
21
+ lib/
22
+ lib64/
23
+ parts/
24
+ sdist/
25
+ var/
26
+ wheels/
27
+ *.egg-info/
28
+ .installed.cfg
29
+ *.egg
30
+
31
+ # Virtual environments
32
+ .venv/
33
+ venv/
34
+ ENV/
35
+
36
+ # Pytest
37
+ .pytest_cache/
38
+ .coverage
39
+
40
+ # IDE
41
+ .vscode/
42
+ .idea/
43
+ *.swp
44
+ *.swo
45
+ *~
46
+
47
+ # Jupyter Notebook
48
+ .ipynb_checkpoints
49
+
50
+ # Environment variables
51
+ .env
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mark Robillard Jr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.4
2
+ Name: kalshi-csv
3
+ Version: 0.1.0
4
+ Summary: Parse Kalshi transaction CSV files and generate IRS Form 8949 tax summaries
5
+ Project-URL: Homepage, https://github.com/MARKMENTAL/kalshi-csv
6
+ Project-URL: Source, https://codeberg.org/markmental/kalshi-csv
7
+ Project-URL: Issues, https://github.com/MARKMENTAL/kalshi-csv/issues
8
+ Author-email: markmental <marky611@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: csv,form-8949,irs,kalshi,tax
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Office/Business :: Financial
22
+ Requires-Python: >=3.8
23
+ Description-Content-Type: text/markdown
24
+
25
+ # kalshi-csv
26
+
27
+ Parse Kalshi transaction CSV files and generate IRS Form 8949 tax summaries for event contract trading.
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install kalshi-csv
33
+ ```
34
+
35
+ ## Getting Your Transactions CSV
36
+
37
+ Download your transaction history from Kalshi:
38
+
39
+ 1. Go to [https://kalshi.com/account/taxes](https://kalshi.com/account/taxes)
40
+ 2. Download the transaction CSV for the tax year you want to analyze
41
+ 3. Pass it to `kalshi-csv` as shown below
42
+
43
+ Each tax year produces a separate CSV file.
44
+
45
+ ## CSV Format
46
+
47
+ The tool expects the standard Kalshi transaction export with these columns:
48
+
49
+ ```
50
+ type, quantity_fp, market_ticker, side, entry_price_dollars, exit_price_dollars,
51
+ open_fees_dollars, close_fees_dollars, realized_pnl_without_fees_dollars,
52
+ realized_pnl_with_fees_dollars, close_timestamp, open_timestamp
53
+ ```
54
+
55
+ Rows without `realized_pnl_without_fees_dollars` are automatically skipped.
56
+
57
+ ## CLI Usage
58
+
59
+ Parse a Kalshi transactions CSV and display the trade matrix with IRS summary:
60
+
61
+ ```bash
62
+ kalshi-csv Kalshi-Transactions-2026.csv
63
+ ```
64
+
65
+ Export the IRS summary to a file:
66
+
67
+ ```bash
68
+ kalshi-csv Kalshi-Transactions-2026.csv --irs-file irs-summary.txt
69
+ ```
70
+
71
+ Disable colored output (useful for piping or redirecting):
72
+
73
+ ```bash
74
+ kalshi-csv Kalshi-Transactions-2026.csv --no-color
75
+ ```
76
+
77
+ ### Sample Output
78
+
79
+ ```
80
+ Ticker | Side | Qty | Entry | Exit | P&L (No Fees)
81
+ -------------------------------------------------------------------------------
82
+ KXWCADVANCE-26JUL07ARGEGY-ARG | YES | 0.17 | $0.86 | $0.69 | -$0.03
83
+ KXWC1H-26JUL07ARGEGY-TIE | YES | 0.34 | $0.28 | $0.00 | -$0.10
84
+ KXMLBGAME-26JUL081940BOSCWS-BOS | YES | 0.96 | $0.50 | $0.91 | $0.39
85
+ -------------------------------------------------------------------------------
86
+ Total Transactions Parsed: 3
87
+ Total Exchange Fees Paid: $0.05
88
+ Internal Tracked Net P&L: $0.26
89
+ -------------------------------------------------------------------------------
90
+ === IRS FORM 8949 / SCHEDULE D AGGREGATE SUMMARY ===
91
+ Use these exact aggregates for a single-line summary entry:
92
+ * Box to Check: Box C (Short-term, not reported on Form 1099-B)
93
+ * (a) Description: Kalshi Event Contracts (Aggregate Summary)
94
+ * (d) Gross Proceeds: $2.50
95
+ * (e) Cost or Other Basis: $2.24
96
+ * (h) Gain or (Loss): $0.26
97
+ ====================================================
98
+ ```
99
+
100
+ ## Library API
101
+
102
+ Use `kalshi-csv` as a Python library in your own scripts:
103
+
104
+ ```python
105
+ from kalshi_csv import KalshiCSV
106
+
107
+ kalshi = KalshiCSV("Kalshi-Transactions-2026.csv")
108
+ kalshi.parse()
109
+
110
+ # Access individual trades
111
+ for trade in kalshi.trades:
112
+ print(f"{trade['ticker']}: {trade['side']} {trade['qty']} @ ${trade['entry']}")
113
+ print(f" P&L: ${trade['pnl_with_fees']:.2f}")
114
+
115
+ # Access aggregate summary
116
+ print(f"Total trades: {kalshi.summary['trade_count']}")
117
+ print(f"Total fees: ${kalshi.summary['total_fees']:.2f}")
118
+ print(f"Total P&L: ${kalshi.summary['total_pnl_with_fees']:.2f}")
119
+
120
+ # Get IRS Form 8949 data
121
+ irs = kalshi.irs_summary()
122
+ print(f"Gross Proceeds: ${irs['gross_proceeds']:.2f}")
123
+ print(f"Cost Basis: ${irs['cost_basis']:.2f}")
124
+ print(f"Gain/Loss: ${irs['gain_or_loss']:.2f}")
125
+ ```
126
+
127
+ ### Data Structures
128
+
129
+ **Trade dict** (`kalshi.trades`):
130
+ - `ticker`: Market ticker symbol
131
+ - `side`: "YES" or "NO"
132
+ - `qty`: Quantity of contracts
133
+ - `entry`: Entry price in dollars
134
+ - `exit`: Exit price in dollars
135
+ - `pnl_no_fees`: P&L without fees
136
+ - `pnl_with_fees`: P&L including fees
137
+ - `open_fees`: Opening fees
138
+ - `close_fees`: Closing fees
139
+
140
+ **Summary dict** (`kalshi.summary`):
141
+ - `trade_count`: Number of trades parsed
142
+ - `total_fees`: Sum of all fees
143
+ - `total_pnl_without_fees`: Total P&L excluding fees
144
+ - `total_pnl_with_fees`: Total P&L including fees
145
+ - `total_tax_basis`: Total cost basis for IRS reporting
146
+ - `total_tax_proceeds`: Total proceeds for IRS reporting
147
+
148
+ **IRS summary dict** (`kalshi.irs_summary()`):
149
+ - `box`: "C" (for Form 8949 Box C)
150
+ - `description`: "Kalshi Event Contracts (Aggregate Summary)"
151
+ - `gross_proceeds`: Total proceeds
152
+ - `cost_basis`: Total cost basis
153
+ - `gain_or_loss`: Net gain or loss
154
+
155
+ ## IRS Form 8949
156
+
157
+ Kalshi event contracts are typically reported on **IRS Form 8949, Box C** (short-term transactions not reported on Form 1099-B). The tool calculates:
158
+
159
+ - **Gross Proceeds**: Total exit value minus close fees
160
+ - **Cost Basis**: Total entry value plus open fees
161
+ - **Gain/Loss**: Realized P&L including all fees
162
+
163
+ Use the aggregate summary for a single-line entry on Form 8949, or export to a file for your records.
164
+
165
+ **Disclaimer**: This tool provides calculations based on Kalshi transaction data. Consult a tax professional for specific tax advice.
166
+
167
+ ## Source Code
168
+
169
+ This project is hosted in two locations:
170
+ - **GitHub**: [https://github.com/MARKMENTAL/kalshi-csv](https://github.com/MARKMENTAL/kalshi-csv)
171
+ - **Codeberg**: [https://codeberg.org/markmental/kalshi-csv](https://codeberg.org/markmental/kalshi-csv)
172
+
173
+ ## License
174
+
175
+ [MIT](LICENSE)
176
+
@@ -0,0 +1,152 @@
1
+ # kalshi-csv
2
+
3
+ Parse Kalshi transaction CSV files and generate IRS Form 8949 tax summaries for event contract trading.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install kalshi-csv
9
+ ```
10
+
11
+ ## Getting Your Transactions CSV
12
+
13
+ Download your transaction history from Kalshi:
14
+
15
+ 1. Go to [https://kalshi.com/account/taxes](https://kalshi.com/account/taxes)
16
+ 2. Download the transaction CSV for the tax year you want to analyze
17
+ 3. Pass it to `kalshi-csv` as shown below
18
+
19
+ Each tax year produces a separate CSV file.
20
+
21
+ ## CSV Format
22
+
23
+ The tool expects the standard Kalshi transaction export with these columns:
24
+
25
+ ```
26
+ type, quantity_fp, market_ticker, side, entry_price_dollars, exit_price_dollars,
27
+ open_fees_dollars, close_fees_dollars, realized_pnl_without_fees_dollars,
28
+ realized_pnl_with_fees_dollars, close_timestamp, open_timestamp
29
+ ```
30
+
31
+ Rows without `realized_pnl_without_fees_dollars` are automatically skipped.
32
+
33
+ ## CLI Usage
34
+
35
+ Parse a Kalshi transactions CSV and display the trade matrix with IRS summary:
36
+
37
+ ```bash
38
+ kalshi-csv Kalshi-Transactions-2026.csv
39
+ ```
40
+
41
+ Export the IRS summary to a file:
42
+
43
+ ```bash
44
+ kalshi-csv Kalshi-Transactions-2026.csv --irs-file irs-summary.txt
45
+ ```
46
+
47
+ Disable colored output (useful for piping or redirecting):
48
+
49
+ ```bash
50
+ kalshi-csv Kalshi-Transactions-2026.csv --no-color
51
+ ```
52
+
53
+ ### Sample Output
54
+
55
+ ```
56
+ Ticker | Side | Qty | Entry | Exit | P&L (No Fees)
57
+ -------------------------------------------------------------------------------
58
+ KXWCADVANCE-26JUL07ARGEGY-ARG | YES | 0.17 | $0.86 | $0.69 | -$0.03
59
+ KXWC1H-26JUL07ARGEGY-TIE | YES | 0.34 | $0.28 | $0.00 | -$0.10
60
+ KXMLBGAME-26JUL081940BOSCWS-BOS | YES | 0.96 | $0.50 | $0.91 | $0.39
61
+ -------------------------------------------------------------------------------
62
+ Total Transactions Parsed: 3
63
+ Total Exchange Fees Paid: $0.05
64
+ Internal Tracked Net P&L: $0.26
65
+ -------------------------------------------------------------------------------
66
+ === IRS FORM 8949 / SCHEDULE D AGGREGATE SUMMARY ===
67
+ Use these exact aggregates for a single-line summary entry:
68
+ * Box to Check: Box C (Short-term, not reported on Form 1099-B)
69
+ * (a) Description: Kalshi Event Contracts (Aggregate Summary)
70
+ * (d) Gross Proceeds: $2.50
71
+ * (e) Cost or Other Basis: $2.24
72
+ * (h) Gain or (Loss): $0.26
73
+ ====================================================
74
+ ```
75
+
76
+ ## Library API
77
+
78
+ Use `kalshi-csv` as a Python library in your own scripts:
79
+
80
+ ```python
81
+ from kalshi_csv import KalshiCSV
82
+
83
+ kalshi = KalshiCSV("Kalshi-Transactions-2026.csv")
84
+ kalshi.parse()
85
+
86
+ # Access individual trades
87
+ for trade in kalshi.trades:
88
+ print(f"{trade['ticker']}: {trade['side']} {trade['qty']} @ ${trade['entry']}")
89
+ print(f" P&L: ${trade['pnl_with_fees']:.2f}")
90
+
91
+ # Access aggregate summary
92
+ print(f"Total trades: {kalshi.summary['trade_count']}")
93
+ print(f"Total fees: ${kalshi.summary['total_fees']:.2f}")
94
+ print(f"Total P&L: ${kalshi.summary['total_pnl_with_fees']:.2f}")
95
+
96
+ # Get IRS Form 8949 data
97
+ irs = kalshi.irs_summary()
98
+ print(f"Gross Proceeds: ${irs['gross_proceeds']:.2f}")
99
+ print(f"Cost Basis: ${irs['cost_basis']:.2f}")
100
+ print(f"Gain/Loss: ${irs['gain_or_loss']:.2f}")
101
+ ```
102
+
103
+ ### Data Structures
104
+
105
+ **Trade dict** (`kalshi.trades`):
106
+ - `ticker`: Market ticker symbol
107
+ - `side`: "YES" or "NO"
108
+ - `qty`: Quantity of contracts
109
+ - `entry`: Entry price in dollars
110
+ - `exit`: Exit price in dollars
111
+ - `pnl_no_fees`: P&L without fees
112
+ - `pnl_with_fees`: P&L including fees
113
+ - `open_fees`: Opening fees
114
+ - `close_fees`: Closing fees
115
+
116
+ **Summary dict** (`kalshi.summary`):
117
+ - `trade_count`: Number of trades parsed
118
+ - `total_fees`: Sum of all fees
119
+ - `total_pnl_without_fees`: Total P&L excluding fees
120
+ - `total_pnl_with_fees`: Total P&L including fees
121
+ - `total_tax_basis`: Total cost basis for IRS reporting
122
+ - `total_tax_proceeds`: Total proceeds for IRS reporting
123
+
124
+ **IRS summary dict** (`kalshi.irs_summary()`):
125
+ - `box`: "C" (for Form 8949 Box C)
126
+ - `description`: "Kalshi Event Contracts (Aggregate Summary)"
127
+ - `gross_proceeds`: Total proceeds
128
+ - `cost_basis`: Total cost basis
129
+ - `gain_or_loss`: Net gain or loss
130
+
131
+ ## IRS Form 8949
132
+
133
+ Kalshi event contracts are typically reported on **IRS Form 8949, Box C** (short-term transactions not reported on Form 1099-B). The tool calculates:
134
+
135
+ - **Gross Proceeds**: Total exit value minus close fees
136
+ - **Cost Basis**: Total entry value plus open fees
137
+ - **Gain/Loss**: Realized P&L including all fees
138
+
139
+ Use the aggregate summary for a single-line entry on Form 8949, or export to a file for your records.
140
+
141
+ **Disclaimer**: This tool provides calculations based on Kalshi transaction data. Consult a tax professional for specific tax advice.
142
+
143
+ ## Source Code
144
+
145
+ This project is hosted in two locations:
146
+ - **GitHub**: [https://github.com/MARKMENTAL/kalshi-csv](https://github.com/MARKMENTAL/kalshi-csv)
147
+ - **Codeberg**: [https://codeberg.org/markmental/kalshi-csv](https://codeberg.org/markmental/kalshi-csv)
148
+
149
+ ## License
150
+
151
+ [MIT](LICENSE)
152
+
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "kalshi-csv"
7
+ version = "0.1.0"
8
+ description = "Parse Kalshi transaction CSV files and generate IRS Form 8949 tax summaries"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.8"
12
+ authors = [
13
+ { name = "markmental", email = "marky611@gmail.com" }
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: End Users/Desktop",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.8",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Office/Business :: Financial",
26
+ ]
27
+ keywords = ["kalshi", "csv", "tax", "irs", "form-8949"]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/MARKMENTAL/kalshi-csv"
31
+ Source = "https://codeberg.org/markmental/kalshi-csv"
32
+ Issues = "https://github.com/MARKMENTAL/kalshi-csv/issues"
33
+
34
+ [project.scripts]
35
+ kalshi-csv = "kalshi_csv.cli:main"
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/kalshi_csv"]
@@ -0,0 +1,5 @@
1
+ __version__ = "0.1.0"
2
+
3
+ from .parser import KalshiCSV
4
+
5
+ __all__ = ["KalshiCSV", "__version__"]
@@ -0,0 +1,80 @@
1
+ import argparse
2
+ import sys
3
+
4
+ from .parser import KalshiCSV
5
+ from .formatter import (
6
+ color_white,
7
+ color_yellow,
8
+ color_cyan,
9
+ format_currency_color,
10
+ )
11
+
12
+
13
+ def main():
14
+ parser = argparse.ArgumentParser(
15
+ description="Parse Kalshi transaction CSV and generate IRS tax summary."
16
+ )
17
+ parser.add_argument("csv_path", help="Path to Kalshi transactions CSV file")
18
+ parser.add_argument(
19
+ "--irs-file",
20
+ help="Write IRS Form 8949 summary to this file",
21
+ )
22
+ parser.add_argument(
23
+ "--no-color",
24
+ action="store_true",
25
+ help="Disable ANSI color output",
26
+ )
27
+
28
+ args = parser.parse_args()
29
+ no_color = args.no_color
30
+
31
+ kalshi = KalshiCSV(args.csv_path)
32
+ kalshi.parse()
33
+
34
+ print()
35
+ print(
36
+ f"{'Ticker':<32} | {'Side':<4} | {'Qty':<6} | {'Entry':<6} | {'Exit':<6} | {'P&L (No Fees)':<14}"
37
+ )
38
+ print("-" * 83)
39
+
40
+ for trade in kalshi.trades:
41
+ pnl_str = format_currency_color(trade["pnl_no_fees"], no_color)
42
+ print(
43
+ f"{trade['ticker']:<32} | {trade['side']:<4} | {trade['qty']:<6.2f} | "
44
+ f"${trade['entry']:<5.2f} | ${trade['exit']:<5.2f} | {pnl_str:<14}"
45
+ )
46
+
47
+ print("-" * 83)
48
+ print(f"Total Transactions Parsed: {kalshi.summary['trade_count']}")
49
+ print(f"Total Exchange Fees Paid: ${kalshi.summary['total_fees']:.2f}")
50
+ print(
51
+ f"Internal Tracked Net P&L: "
52
+ + format_currency_color(kalshi.summary["total_pnl_without_fees"], no_color)
53
+ )
54
+ print("-" * 83)
55
+
56
+ irs = kalshi.irs_summary()
57
+ print(color_yellow("=== IRS FORM 8949 / SCHEDULE D AGGREGATE SUMMARY ===", no_color))
58
+ print("Use these exact aggregates for a single-line summary entry:")
59
+ print(f" * Box to Check: {color_white('Box C', no_color)} (Short-term, not reported on Form 1099-B)")
60
+ print(f" * (a) Description: {irs['description']}")
61
+ print(f" * (d) Gross Proceeds: {format_currency_color(irs['gross_proceeds'], no_color)}")
62
+ print(f" * (e) Cost or Other Basis: {color_cyan(f'${irs['cost_basis']:.2f}', no_color)}")
63
+ print(f" * (h) Gain or (Loss): {format_currency_color(irs['gain_or_loss'], no_color)}")
64
+ print(color_yellow("====================================================", no_color))
65
+ print()
66
+
67
+ if args.irs_file:
68
+ with open(args.irs_file, "w", encoding="utf-8") as f:
69
+ f.write("IRS FORM 8949 / SCHEDULE D AGGREGATE SUMMARY\n")
70
+ f.write("Use these exact aggregates for a single-line summary entry:\n")
71
+ f.write(f" * Box to Check: Box C (Short-term, not reported on Form 1099-B)\n")
72
+ f.write(f" * (a) Description: {irs['description']}\n")
73
+ f.write(f" * (d) Gross Proceeds: ${irs['gross_proceeds']:.2f}\n")
74
+ f.write(f" * (e) Cost or Other Basis: ${irs['cost_basis']:.2f}\n")
75
+ f.write(f" * (h) Gain or (Loss): ${irs['gain_or_loss']:.2f}\n")
76
+ print(f"IRS summary written to: {args.irs_file}")
77
+
78
+
79
+ if __name__ == "__main__":
80
+ main()
@@ -0,0 +1,39 @@
1
+ def color_green(text, no_color=False):
2
+ """Wraps text in ANSI green."""
3
+ if no_color:
4
+ return text
5
+ return f"\033[1;32m{text}\033[0m"
6
+
7
+
8
+ def color_red(text, no_color=False):
9
+ """Wraps text in ANSI red."""
10
+ if no_color:
11
+ return text
12
+ return f"\033[1;31m{text}\033[0m"
13
+
14
+
15
+ def color_yellow(text, no_color=False):
16
+ """Wraps text in ANSI yellow."""
17
+ if no_color:
18
+ return text
19
+ return f"\033[1;33m{text}\033[0m"
20
+
21
+
22
+ def color_cyan(text, no_color=False):
23
+ """Wraps text in ANSI cyan."""
24
+ if no_color:
25
+ return text
26
+ return f"\033[1;36m{text}\033[0m"
27
+
28
+
29
+ def color_white(text, no_color=False):
30
+ """Wraps text in ANSI white."""
31
+ if no_color:
32
+ return text
33
+ return f"\033[1;37m{text}\033[0m"
34
+
35
+
36
+ def format_currency_color(value, no_color=False):
37
+ """Returns a signed, colorized string based on profit or loss status."""
38
+ val_str = f"${value:+.2f}"
39
+ return color_green(val_str, no_color) if value >= 0 else color_red(val_str, no_color)
@@ -0,0 +1,70 @@
1
+ import csv
2
+ import os
3
+
4
+
5
+ class KalshiCSV:
6
+ """Parses Kalshi transaction CSV data and calculates tax-relevant aggregates."""
7
+
8
+ def __init__(self, file_path):
9
+ self.file_path = file_path
10
+ self.trades = []
11
+ self.summary = {
12
+ "trade_count": 0,
13
+ "total_fees": 0.0,
14
+ "total_pnl_without_fees": 0.0,
15
+ "total_pnl_with_fees": 0.0,
16
+ "total_tax_basis": 0.0,
17
+ "total_tax_proceeds": 0.0,
18
+ }
19
+
20
+ def parse(self):
21
+ """Processes the CSV file row-by-row and populates trades and summary."""
22
+ if not os.path.exists(self.file_path):
23
+ raise FileNotFoundError(f"File '{self.file_path}' not found.")
24
+
25
+ with open(self.file_path, mode="r", newline="", encoding="utf-8") as f:
26
+ reader = csv.DictReader(f)
27
+
28
+ for row in reader:
29
+ if not row.get("realized_pnl_without_fees_dollars"):
30
+ continue
31
+
32
+ qty = float(row["quantity_fp"])
33
+ entry = float(row["entry_price_dollars"])
34
+ exit_val = float(row["exit_price_dollars"])
35
+ pnl_no_fees = float(row["realized_pnl_without_fees_dollars"])
36
+ pnl_with_fees = float(row["realized_pnl_with_fees_dollars"])
37
+ open_fees = float(row["open_fees_dollars"])
38
+ close_fees = float(row["close_fees_dollars"])
39
+
40
+ trade = {
41
+ "ticker": row["market_ticker"],
42
+ "side": row["side"].upper(),
43
+ "qty": qty,
44
+ "entry": entry,
45
+ "exit": exit_val,
46
+ "pnl_no_fees": pnl_no_fees,
47
+ "pnl_with_fees": pnl_with_fees,
48
+ "open_fees": open_fees,
49
+ "close_fees": close_fees,
50
+ }
51
+ self.trades.append(trade)
52
+
53
+ self.summary["trade_count"] += 1
54
+ self.summary["total_tax_basis"] += (qty * entry) + open_fees
55
+ self.summary["total_tax_proceeds"] += (qty * exit_val) - close_fees
56
+ self.summary["total_pnl_without_fees"] += pnl_no_fees
57
+ self.summary["total_pnl_with_fees"] += pnl_with_fees
58
+ self.summary["total_fees"] += open_fees + close_fees
59
+
60
+ return self
61
+
62
+ def irs_summary(self):
63
+ """Returns a dict with IRS Form 8949 aggregate fields."""
64
+ return {
65
+ "box": "C",
66
+ "description": "Kalshi Event Contracts (Aggregate Summary)",
67
+ "gross_proceeds": self.summary["total_tax_proceeds"],
68
+ "cost_basis": self.summary["total_tax_basis"],
69
+ "gain_or_loss": self.summary["total_pnl_with_fees"],
70
+ }
@@ -0,0 +1,8 @@
1
+ import pytest
2
+ from pathlib import Path
3
+
4
+
5
+ @pytest.fixture
6
+ def sample_csv():
7
+ """Returns the path to the synthetic test CSV fixture."""
8
+ return str(Path(__file__).parent / "fixtures" / "sample.csv")
@@ -0,0 +1,67 @@
1
+ import subprocess
2
+ import sys
3
+ from pathlib import Path
4
+
5
+
6
+ def test_cli_runs_successfully(sample_csv):
7
+ result = subprocess.run(
8
+ [sys.executable, "-m", "kalshi_csv.cli", sample_csv],
9
+ capture_output=True,
10
+ text=True,
11
+ )
12
+ assert result.returncode == 0
13
+ assert "TESTMARKET-WIN" in result.stdout
14
+ assert "TESTMARKET-LOSS" in result.stdout
15
+ assert "Total Transactions Parsed: 3" in result.stdout
16
+
17
+
18
+ def test_cli_irs_summary_output(sample_csv):
19
+ result = subprocess.run(
20
+ [sys.executable, "-m", "kalshi_csv.cli", sample_csv],
21
+ capture_output=True,
22
+ text=True,
23
+ )
24
+ assert "IRS FORM 8949" in result.stdout
25
+ assert "Box C" in result.stdout
26
+ assert "Gross Proceeds:" in result.stdout
27
+ assert "Cost or Other Basis:" in result.stdout
28
+ assert "Gain or (Loss):" in result.stdout
29
+
30
+
31
+ def test_cli_irs_file_creation(sample_csv, tmp_path):
32
+ irs_file = tmp_path / "irs-output.txt"
33
+ result = subprocess.run(
34
+ [sys.executable, "-m", "kalshi_csv.cli", sample_csv, "--irs-file", str(irs_file)],
35
+ capture_output=True,
36
+ text=True,
37
+ )
38
+ assert result.returncode == 0
39
+ assert irs_file.exists()
40
+
41
+ content = irs_file.read_text()
42
+ assert "IRS FORM 8949" in content
43
+ assert "Box C" in content
44
+ assert "Gross Proceeds:" in content
45
+ assert "Cost or Other Basis:" in content
46
+ assert "Gain or (Loss):" in content
47
+ assert "\033[" not in content
48
+
49
+
50
+ def test_cli_no_color_flag(sample_csv):
51
+ result = subprocess.run(
52
+ [sys.executable, "-m", "kalshi_csv.cli", sample_csv, "--no-color"],
53
+ capture_output=True,
54
+ text=True,
55
+ )
56
+ assert result.returncode == 0
57
+ assert "\033[" not in result.stdout
58
+
59
+
60
+ def test_cli_missing_file():
61
+ result = subprocess.run(
62
+ [sys.executable, "-m", "kalshi_csv.cli", "/nonexistent/file.csv"],
63
+ capture_output=True,
64
+ text=True,
65
+ )
66
+ assert result.returncode != 0
67
+ assert "not found" in result.stderr.lower() or "error" in result.stderr.lower()
@@ -0,0 +1,80 @@
1
+ import pytest
2
+ from kalshi_csv import KalshiCSV
3
+
4
+
5
+ def test_parse_returns_self(sample_csv):
6
+ kalshi = KalshiCSV(sample_csv)
7
+ result = kalshi.parse()
8
+ assert result is kalshi
9
+
10
+
11
+ def test_trade_count(sample_csv):
12
+ kalshi = KalshiCSV(sample_csv)
13
+ kalshi.parse()
14
+ assert kalshi.summary["trade_count"] == 3
15
+
16
+
17
+ def test_total_fees(sample_csv):
18
+ kalshi = KalshiCSV(sample_csv)
19
+ kalshi.parse()
20
+ assert abs(kalshi.summary["total_fees"] - 0.07) < 1e-6
21
+
22
+
23
+ def test_total_pnl_without_fees(sample_csv):
24
+ kalshi = KalshiCSV(sample_csv)
25
+ kalshi.parse()
26
+ assert abs(kalshi.summary["total_pnl_without_fees"] - (-0.20)) < 1e-6
27
+
28
+
29
+ def test_total_pnl_with_fees(sample_csv):
30
+ kalshi = KalshiCSV(sample_csv)
31
+ kalshi.parse()
32
+ assert abs(kalshi.summary["total_pnl_with_fees"] - (-0.27)) < 1e-6
33
+
34
+
35
+ def test_total_tax_basis(sample_csv):
36
+ kalshi = KalshiCSV(sample_csv)
37
+ kalshi.parse()
38
+ assert abs(kalshi.summary["total_tax_basis"] - 1.64) < 1e-6
39
+
40
+
41
+ def test_total_tax_proceeds(sample_csv):
42
+ kalshi = KalshiCSV(sample_csv)
43
+ kalshi.parse()
44
+ assert abs(kalshi.summary["total_tax_proceeds"] - 1.37) < 1e-6
45
+
46
+
47
+ def test_trades_list_length(sample_csv):
48
+ kalshi = KalshiCSV(sample_csv)
49
+ kalshi.parse()
50
+ assert len(kalshi.trades) == 3
51
+
52
+
53
+ def test_first_trade_data(sample_csv):
54
+ kalshi = KalshiCSV(sample_csv)
55
+ kalshi.parse()
56
+ trade = kalshi.trades[0]
57
+ assert trade["ticker"] == "TESTMARKET-WIN"
58
+ assert trade["side"] == "YES"
59
+ assert trade["qty"] == 1.0
60
+ assert trade["entry"] == 0.50
61
+ assert trade["exit"] == 1.00
62
+ assert abs(trade["pnl_no_fees"] - 0.50) < 1e-6
63
+ assert abs(trade["pnl_with_fees"] - 0.47) < 1e-6
64
+
65
+
66
+ def test_irs_summary(sample_csv):
67
+ kalshi = KalshiCSV(sample_csv)
68
+ kalshi.parse()
69
+ irs = kalshi.irs_summary()
70
+ assert irs["box"] == "C"
71
+ assert irs["description"] == "Kalshi Event Contracts (Aggregate Summary)"
72
+ assert abs(irs["gross_proceeds"] - 1.37) < 1e-6
73
+ assert abs(irs["cost_basis"] - 1.64) < 1e-6
74
+ assert abs(irs["gain_or_loss"] - (-0.27)) < 1e-6
75
+
76
+
77
+ def test_file_not_found():
78
+ kalshi = KalshiCSV("/nonexistent/path.csv")
79
+ with pytest.raises(FileNotFoundError):
80
+ kalshi.parse()