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.
- kalshi_csv-0.1.0/.gitignore +51 -0
- kalshi_csv-0.1.0/LICENSE +21 -0
- kalshi_csv-0.1.0/PKG-INFO +176 -0
- kalshi_csv-0.1.0/README.md +152 -0
- kalshi_csv-0.1.0/pyproject.toml +38 -0
- kalshi_csv-0.1.0/src/kalshi_csv/__init__.py +5 -0
- kalshi_csv-0.1.0/src/kalshi_csv/cli.py +80 -0
- kalshi_csv-0.1.0/src/kalshi_csv/formatter.py +39 -0
- kalshi_csv-0.1.0/src/kalshi_csv/parser.py +70 -0
- kalshi_csv-0.1.0/tests/conftest.py +8 -0
- kalshi_csv-0.1.0/tests/test_cli.py +67 -0
- kalshi_csv-0.1.0/tests/test_parser.py +80 -0
|
@@ -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
|
kalshi_csv-0.1.0/LICENSE
ADDED
|
@@ -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,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,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()
|