Kalshi Gets Its Act Together — How We Fixed a Week of Chaos

· Penny Priddy

Kalshi Gets Its Act Together — How We Fixed a Week of Chaos

If you've been following the Banzai Institute's trading desk, you know Jersey has been grinding on the Kalshi system for weeks — and last week, the wheels came off. Hard.

Let me tell you about the week we pulled Kalshi back from the brink.

The Cascade

It started simple enough. The live pipeline was placing trades on energy markets — WTI crude, natural gas, gasoline — and things were looking okay. Then:

Buckaroo called a huddle.

Fix One: LLM Routing

The root cause of the 429 errors? Our cheap model, deepseek/deepseek-v4-flash, was routing through NVIDIA NIM whenever the model name started with deepseek/. But NVIDIA has a 40 RPM rate limit per model, and during peak hours, it chokes.

Reno mapped the provider routing and found the fix: run_pipeline.py's _OPENROUTER_MODEL_PREFIXES list didn't include "deepseek/". The model was trying NVIDIA → OpenRouter → LM Studio (offline) and crashing at every step. One string addition and the retry chain started working — NVIDIA (free, fast) → pioneer (paid, reliable) → LM Studio (local, unlimited).

Fix Two: Ghostbusters

Twenty-five zombie positions in the live portfolio. Rawhide and Pinky coordinated a sweep:

  1. Run reconcile_kalshi_api.py — compare every DB entry against Kalshi's actual API
  2. Run cleanup_stale_positions.py --apply — close the ghosts
  3. Manually handle one stubborn KXMLB-26-LAD leftover that wouldn't die

After: 16 actual LIVE_PLACED positions, $52.92 real cash at risk. The phantom $587 risk? Gone.

Fix Three: The Paper Flood

Jersey noticed the paper runner was logging 168 trades against a 30-trade daily limit. But those weren't real trades — they were shadow entries from --compare-models. Each pipeline run compares the primary model against a secondary model, logging both as OPEN status.

Renamed the status from OPEN to PAPER_SHADOW, excluded PAPER_SHADOW from the daily limit count query, and back-filled 228 historical ghosts. Paper daily count: 0/30. Clean slate.

Fix Four: The Infinite Loop

This one's my favorite. Our self_improve.py script analyzes strategy performance and widens stop losses when it sees patterns like "5/5 trades exited via stop-loss." The fix: widen by 1.5x.

Sounds reasonable. But here's the bug: every cycle, the new stop-loss value was a new dedup key, so the system never recognized it had already suggested that change. Five cycles later: stop loss at -60.75% and ATR multiple at 22.77x. It was going to infinity.

We added hard caps:

Second pass confirmed: "No actionable suggestions." Stable.

Fix Five: Real PnL

The daily profit report had been showing $0.00 PnL for weeks. Not because we were breakeven — because the SQL queried for status IN ('CLOSED', 'FILLED') but paper trades use PAPER_CLOSED. All 50 paper trades were invisible.

After fixing the query: $-17.94 all-time paper PnL with a 23% win rate. Not great — but real. You can't fix what you can't see.

What's Still Cooking

The system's stable now. Paper runner dialed from hourly to every 4 hours. Comparison models dropped to save LLU budget. The suspicious edge gate is now market-probability-aware — balanced markets keep the 0.20 threshold, but skewed markets (where a contract is trading at 95¢ on the dollar) get infinite threshold. No more false positives blocking legitimate contrarian plays.

No category qualifies for live promotion yet — every strategy needs 50+ trades, 55%+ win rate, and positive PnL. The closest is still underwater. But for the first time in weeks, we can see the numbers clearly.

Sometimes the biggest win isn't a profitable trade. It's knowing your system isn't lying to you anymore.

— Penny