From 75dbaf176f804377b332beb17f2c492bcaf0cd51 Mon Sep 17 00:00:00 2001 From: mark Date: Wed, 22 Jul 2026 12:06:08 -0400 Subject: [PATCH 1/4] Start of work on 0.1.1 version, include fees for each transaction in CLI summary display --- .gitignore | 1 + pyproject.toml | 2 +- src/kalshi_csv/__init__.py | 2 +- src/kalshi_csv/cli.py | 9 +++++---- tests/test_cli.py | 1 + 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 1b73554..dafba28 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ ENV/ # Environment variables .env +u*-p*.sh diff --git a/pyproject.toml b/pyproject.toml index 29e3b29..a64f717 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "kalshi-csv" -version = "0.1.0" +version = "0.1.1" description = "Parse Kalshi transaction CSV files and generate IRS Form 8949 tax summaries" readme = "README.md" license = "MIT" diff --git a/src/kalshi_csv/__init__.py b/src/kalshi_csv/__init__.py index b181324..abf61a0 100644 --- a/src/kalshi_csv/__init__.py +++ b/src/kalshi_csv/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.1.0" +__version__ = "0.1.1" from .parser import KalshiCSV diff --git a/src/kalshi_csv/cli.py b/src/kalshi_csv/cli.py index 39bf353..63ad204 100644 --- a/src/kalshi_csv/cli.py +++ b/src/kalshi_csv/cli.py @@ -33,18 +33,19 @@ def main(): print() print( - f"{'Ticker':<32} | {'Side':<4} | {'Qty':<6} | {'Entry':<6} | {'Exit':<6} | {'P&L (No Fees)':<14}" + f"{'Ticker':<32} | {'Side':<4} | {'Qty':<6} | {'Entry':<6} | {'Exit':<6} | {'P&L (No Fees)':<14} | {'Fees':<6}" ) - print("-" * 83) + print("-" * 90) for trade in kalshi.trades: pnl_str = format_currency_color(trade["pnl_no_fees"], no_color) + fees = trade["open_fees"] + trade["close_fees"] print( f"{trade['ticker']:<32} | {trade['side']:<4} | {trade['qty']:<6.2f} | " - f"${trade['entry']:<5.2f} | ${trade['exit']:<5.2f} | {pnl_str:<14}" + f"${trade['entry']:<5.2f} | ${trade['exit']:<5.2f} | {pnl_str:<14} | ${fees:<5.2f}" ) - print("-" * 83) + print("-" * 90) print(f"Total Transactions Parsed: {kalshi.summary['trade_count']}") print(f"Total Exchange Fees Paid: ${kalshi.summary['total_fees']:.2f}") print( diff --git a/tests/test_cli.py b/tests/test_cli.py index 14b7117..a552c2b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -13,6 +13,7 @@ def test_cli_runs_successfully(sample_csv): assert "TESTMARKET-WIN" in result.stdout assert "TESTMARKET-LOSS" in result.stdout assert "Total Transactions Parsed: 3" in result.stdout + assert "Fees" in result.stdout def test_cli_irs_summary_output(sample_csv): From 4c70fbd27a20f3b17f76240e859ae5faca757477 Mon Sep 17 00:00:00 2001 From: mark Date: Wed, 22 Jul 2026 12:26:37 -0400 Subject: [PATCH 2/4] Fix CLI formatting, standardize ticker names to 32 chars --- src/kalshi_csv/cli.py | 9 ++++++--- src/kalshi_csv/formatter.py | 14 ++++++++++++++ tests/test_cli.py | 19 +++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/kalshi_csv/cli.py b/src/kalshi_csv/cli.py index 63ad204..78b8f8b 100644 --- a/src/kalshi_csv/cli.py +++ b/src/kalshi_csv/cli.py @@ -7,6 +7,8 @@ from .formatter import ( color_yellow, color_cyan, format_currency_color, + format_currency_color_padded, + truncate_ticker, ) @@ -38,11 +40,12 @@ def main(): print("-" * 90) for trade in kalshi.trades: - pnl_str = format_currency_color(trade["pnl_no_fees"], no_color) + pnl_str = format_currency_color_padded(trade["pnl_no_fees"], 14, no_color) fees = trade["open_fees"] + trade["close_fees"] + ticker_display = truncate_ticker(trade["ticker"]) print( - f"{trade['ticker']:<32} | {trade['side']:<4} | {trade['qty']:<6.2f} | " - f"${trade['entry']:<5.2f} | ${trade['exit']:<5.2f} | {pnl_str:<14} | ${fees:<5.2f}" + f"{ticker_display:<32} | {trade['side']:<4} | {trade['qty']:<6.2f} | " + f"${trade['entry']:<5.2f} | ${trade['exit']:<5.2f} | {pnl_str} | ${fees:<5.2f}" ) print("-" * 90) diff --git a/src/kalshi_csv/formatter.py b/src/kalshi_csv/formatter.py index 73769a0..9b6449f 100644 --- a/src/kalshi_csv/formatter.py +++ b/src/kalshi_csv/formatter.py @@ -37,3 +37,17 @@ 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) + + +def format_currency_color_padded(value, width, no_color=False): + """Returns a signed, colorized string padded to specified width before coloring.""" + val_str = f"${value:+.2f}" + padded_str = f"{val_str:<{width}}" + return color_green(padded_str, no_color) if value >= 0 else color_red(padded_str, no_color) + + +def truncate_ticker(ticker, max_len=32): + """Truncates ticker to max_len, using ellipsis if longer than 29 chars.""" + if len(ticker) > 29: + return ticker[:29] + "..." + return ticker diff --git a/tests/test_cli.py b/tests/test_cli.py index a552c2b..7b34915 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -66,3 +66,22 @@ def test_cli_missing_file(): ) assert result.returncode != 0 assert "not found" in result.stderr.lower() or "error" in result.stderr.lower() + + +def test_cli_ticker_truncation(tmp_path): + csv_file = tmp_path / "long_ticker.csv" + csv_file.write_text( + "type,quantity_fp,market_ticker,side,entry_price_dollars,exit_price_dollars," + "open_fees_dollars,close_fees_dollars,realized_pnl_without_fees_dollars," + "realized_pnl_with_fees_dollars,close_timestamp,open_timestamp\n" + "trade,1.00,VERYLONGTICKERNAME-THAT-EXCEEDS-THIRTY-CHARS,yes,0.50,1.00," + "0.01,0.02,0.50,0.47,2026-07-07T12:19:57-04:00,2026-07-07T09:48:19-04:00\n" + ) + result = subprocess.run( + [sys.executable, "-m", "kalshi_csv.cli", str(csv_file), "--no-color"], + capture_output=True, + text=True, + ) + assert result.returncode == 0 + assert "VERYLONGTICKERNAME-THAT-EXCEE..." in result.stdout + assert "VERYLONGTICKERNAME-THAT-EXCEEDS-THIRTY-CHARS" not in result.stdout From ef75e3e5f0de6dfaaa95db70116166a2bd07f2f5 Mon Sep 17 00:00:00 2001 From: mark Date: Wed, 22 Jul 2026 12:48:16 -0400 Subject: [PATCH 3/4] Change table to box-style, supports unicode by default and ASCII as fallback mode --- src/kalshi_csv/cli.py | 36 +++++++++++++++++----- src/kalshi_csv/formatter.py | 61 +++++++++++++++++++++++++++++++++++++ tests/test_cli.py | 14 +++++++++ 3 files changed, 103 insertions(+), 8 deletions(-) diff --git a/src/kalshi_csv/cli.py b/src/kalshi_csv/cli.py index 78b8f8b..fd0efca 100644 --- a/src/kalshi_csv/cli.py +++ b/src/kalshi_csv/cli.py @@ -9,6 +9,10 @@ from .formatter import ( format_currency_color, format_currency_color_padded, truncate_ticker, + format_table_header, + format_table_separator, + format_table_row, + get_box_chars, ) @@ -26,29 +30,45 @@ def main(): action="store_true", help="Disable ANSI color output", ) + parser.add_argument( + "--ascii", + action="store_true", + help="Use ASCII characters instead of Unicode box-drawing", + ) args = parser.parse_args() no_color = args.no_color + ascii_mode = args.ascii kalshi = KalshiCSV(args.csv_path) kalshi.parse() + headers = ["Ticker", "Side", "Qty", "Entry", "Exit", "P&L (No Fees)", "Fees"] + widths = [32, 4, 6, 6, 6, 14, 6] + print() - print( - f"{'Ticker':<32} | {'Side':<4} | {'Qty':<6} | {'Entry':<6} | {'Exit':<6} | {'P&L (No Fees)':<14} | {'Fees':<6}" - ) - print("-" * 90) + print(format_table_separator(widths, ascii_mode, "top")) + print(format_table_header(headers, widths, ascii_mode)) + print(format_table_separator(widths, ascii_mode, "middle")) for trade in kalshi.trades: pnl_str = format_currency_color_padded(trade["pnl_no_fees"], 14, no_color) fees = trade["open_fees"] + trade["close_fees"] ticker_display = truncate_ticker(trade["ticker"]) - print( - f"{ticker_display:<32} | {trade['side']:<4} | {trade['qty']:<6.2f} | " - f"${trade['entry']:<5.2f} | ${trade['exit']:<5.2f} | {pnl_str} | ${fees:<5.2f}" + + box = get_box_chars(ascii_mode) + row = ( + f" {ticker_display:<32} " + f"{box['vertical']} {trade['side']:<4} " + f"{box['vertical']} {trade['qty']:<6.2f} " + f"{box['vertical']} ${trade['entry']:<5.2f} " + f"{box['vertical']} ${trade['exit']:<5.2f} " + f"{box['vertical']} {pnl_str} " + f"{box['vertical']} ${fees:<5.2f} " ) + print(box["vertical"] + row + box["vertical"]) - print("-" * 90) + print(format_table_separator(widths, ascii_mode, "bottom")) print(f"Total Transactions Parsed: {kalshi.summary['trade_count']}") print(f"Total Exchange Fees Paid: ${kalshi.summary['total_fees']:.2f}") print( diff --git a/src/kalshi_csv/formatter.py b/src/kalshi_csv/formatter.py index 9b6449f..7aa8c59 100644 --- a/src/kalshi_csv/formatter.py +++ b/src/kalshi_csv/formatter.py @@ -51,3 +51,64 @@ def truncate_ticker(ticker, max_len=32): if len(ticker) > 29: return ticker[:29] + "..." return ticker + + +UNICODE_BOX = { + "top_left": "┌", + "top_right": "┐", + "bottom_left": "└", + "bottom_right": "┘", + "horizontal": "─", + "vertical": "│", + "top_tee": "┬", + "bottom_tee": "┴", + "left_tee": "├", + "right_tee": "┤", + "cross": "┼", +} + +ASCII_BOX = { + "top_left": "+", + "top_right": "+", + "bottom_left": "+", + "bottom_right": "+", + "horizontal": "-", + "vertical": "|", + "top_tee": "+", + "bottom_tee": "+", + "left_tee": "+", + "right_tee": "+", + "cross": "+", +} + + +def get_box_chars(ascii_mode=False): + """Returns the appropriate box-drawing characters.""" + return ASCII_BOX if ascii_mode else UNICODE_BOX + + +def format_table_header(headers, widths, ascii_mode=False): + """Formats a table header row with box-drawing characters.""" + box = get_box_chars(ascii_mode) + cells = [f" {h:<{w}} " for h, w in zip(headers, widths)] + return box["vertical"] + box["vertical"].join(cells) + box["vertical"] + + +def format_table_separator(widths, ascii_mode=False, position="middle"): + """Formats a table separator line with box-drawing characters.""" + box = get_box_chars(ascii_mode) + segments = [box["horizontal"] * (w + 2) for w in widths] + + if position == "top": + return box["top_left"] + box["top_tee"].join(segments) + box["top_right"] + elif position == "bottom": + return box["bottom_left"] + box["bottom_tee"].join(segments) + box["bottom_right"] + else: + return box["left_tee"] + box["cross"].join(segments) + box["right_tee"] + + +def format_table_row(values, widths, ascii_mode=False): + """Formats a table data row with box-drawing characters.""" + box = get_box_chars(ascii_mode) + cells = [f" {str(v):<{w}} " for v, w in zip(values, widths)] + return box["vertical"] + box["vertical"].join(cells) + box["vertical"] diff --git a/tests/test_cli.py b/tests/test_cli.py index 7b34915..16d5bc4 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -85,3 +85,17 @@ def test_cli_ticker_truncation(tmp_path): assert result.returncode == 0 assert "VERYLONGTICKERNAME-THAT-EXCEE..." in result.stdout assert "VERYLONGTICKERNAME-THAT-EXCEEDS-THIRTY-CHARS" not in result.stdout + + +def test_cli_ascii_flag(sample_csv): + result = subprocess.run( + [sys.executable, "-m", "kalshi_csv.cli", sample_csv, "--no-color", "--ascii"], + capture_output=True, + text=True, + ) + assert result.returncode == 0 + assert "+" in result.stdout + assert "-" in result.stdout + assert "|" in result.stdout + assert "┌" not in result.stdout + assert "│" not in result.stdout From 0c5c18150613ca875fe3cae4f6b8553ddaa26877 Mon Sep 17 00:00:00 2001 From: mark Date: Wed, 22 Jul 2026 12:53:31 -0400 Subject: [PATCH 4/4] README update for 0.1.1 --- README.md | 55 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 68bde08..95d43ff 100644 --- a/README.md +++ b/README.md @@ -50,26 +50,59 @@ Disable colored output (useful for piping or redirecting): kalshi-csv Kalshi-Transactions-2026.csv --no-color ``` +Use ASCII characters instead of Unicode box-drawing (for terminals without UTF-8 support): + +```bash +kalshi-csv Kalshi-Transactions-2026.csv --ascii +``` + ### Sample Output +Default mode (Unicode box-drawing): + ``` -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 -------------------------------------------------------------------------------- +┌──────────────────────────────────┬──────┬────────┬────────┬────────┬────────────────┬────────┐ +│ Ticker │ Side │ Qty │ Entry │ Exit │ P&L (No Fees) │ Fees │ +├──────────────────────────────────┼──────┼────────┼────────┼────────┼────────────────┼────────┤ +│ KXWCADVANCE-26JUL07ARGEGY-ARG │ YES │ 0.17 │ $0.86 │ $0.69 │ $-0.03 │ $0.00 │ +│ KXWC1H-26JUL07ARGEGY-TIE │ YES │ 0.34 │ $0.28 │ $0.00 │ $-0.10 │ $0.00 │ +│ KXMLBGAME-26JUL081940BOSCWS-BOS │ YES │ 0.96 │ $0.50 │ $0.91 │ $+0.39 │ $0.02 │ +└──────────────────────────────────┴──────┴────────┴────────┴────────┴────────────────┴────────┘ Total Transactions Parsed: 3 -Total Exchange Fees Paid: $0.05 -Internal Tracked Net P&L: $0.26 -------------------------------------------------------------------------------- +Total Exchange Fees Paid: $0.02 +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 + * (d) Gross Proceeds: $+2.50 * (e) Cost or Other Basis: $2.24 - * (h) Gain or (Loss): $0.26 + * (h) Gain or (Loss): $+0.26 +==================================================== +``` + +ASCII mode (`--ascii`): + +``` ++----------------------------------+------+--------+--------+--------+----------------+--------+ +| Ticker | Side | Qty | Entry | Exit | P&L (No Fees) | Fees | ++----------------------------------+------+--------+--------+--------+----------------+--------+ +| KXWCADVANCE-26JUL07ARGEGY-ARG | YES | 0.17 | $0.86 | $0.69 | $-0.03 | $0.00 | +| KXWC1H-26JUL07ARGEGY-TIE | YES | 0.34 | $0.28 | $0.00 | $-0.10 | $0.00 | +| KXMLBGAME-26JUL081940BOSCWS-BOS | YES | 0.96 | $0.50 | $0.91 | $+0.39 | $0.02 | ++----------------------------------+------+--------+--------+--------+----------------+--------+ +Total Transactions Parsed: 3 +Total Exchange Fees Paid: $0.02 +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 ==================================================== ```