josecarlosrivas

How I lost $2.99 on Kalshi, and what the data said

2026-10-09

I put $100 into a Kalshi account last Tuesday with a few guidelines: binary event contracts only, no leverage, no perpetuals, and told my long-running agent that I was prepared to lose every cent.

Two days later I was down $2.99. That’s the footnote. The real result is in the research—because this is the first time I’ve watched an agent with progressive memory run one end to end: ingest, backtest, paper trade, go live, score against official data, kill, and dig. Here’s what it did and what the data actually said.

What is this progressive memory thing?
Hit me up, I’d love to chat about it!

Day one, morning: read everything, touch nothing

Phase zero was read-only. Kalshi has about twenty “15-minute” series: BTC, ETH, SOL and a dozen other coins, gold, silver, copper, oil, natural gas, a few FX pairs. Each market asks whether the price will be above a strike at the quarter-hour. The agent pulled the last 300 settled markets per series with one-minute order books, then measured three things: calibration (does a 70c contract win 70% of the time?), spreads, and fee drag.

The fee math decides everything here. Kalshi’s taker fee on these series is 0.07 × contracts × P × (1−P), rounded up to the cent. At P=0.50 that’s 1.75c per contract, and these markets are coin flips priced near 50c. The agent tested “buy the favorite at the ask” at T-10, T-6 and T-3 minutes across all twenty series: it loses after fees in 55 of 60 cells. Books are roughly calibrated, thin ones (ZEC, NEAR, the metals, FX) are sloppier by 4 to 9 points, and the spreads eat the slop every time.

One thing survived. BTC’s 15-minute series does about $2.1M of volume per market, twenty times ETH, and a maker-side proxy (rest at the inside, get paid the spread) came out faintly positive at six of seven horizons: +0.1c to +1.5c per market with zero fees. Pennies per hour at a $100 bankroll. The agent said exactly that, and recommended looking at information markets instead.

So it did. Weather highs against the NWS MOS guidance, AAA gas prices against their own momentum, SOFR, TSA checkpoint counts, Truflation. On weather and gas the market’s own prices already beat the free public data, so no edge there; the rest were dead. And one market was different in kind: the daily ERCOT peak-load contract.

Day one, afternoon: paper, three ways

Phase one was paper trading. The maker strategy is simple to describe: between twelve and two minutes before the close, rest one contract at the inside bid and one at the inside ask on the BTC market, re-quote as the book moves, and score fills at settlement.

The hard part is deciding what counts as a fill when you never placed an order. The agent scored every market three ways in parallel:

By the next morning there were 79 settled markets. Queue showed +$19.85, strict +$7.11, touch +$10.94, and the agent’s own review was the first thing that made me trust it: queue’s lead was $15 of directional luck. The strategy caps net inventory at three contracts, and over 62 markets the leftover imbalance happened to land on the right side 44 times. Paired spread income, the only thing a maker is designed to earn, was about 6c per market with 30 of 79 markets negative. “Treat as noise until it persists for a week.”

It also flagged a trap I’d have walked into. Kalshi charges a maker fee on some series, and if BTC 15m carried one it would erase the whole edge. The fee schedule PDF was rate-limited, so the agent found the answer in the API instead: series.fee_type. BTC, ETH and ERCOT are quadratic (takers only); 160 other series are quadratic_with_maker_fees. It then changed the client to refuse quoting on any series that charges makers.

The ERCOT play, or an edge nobody will trade against

ERCOT publishes Texas grid load in real time. Kalshi’s daily peak contract is a ladder of nineteen strikes, 500 MW apart, settling the next morning on ERCOT’s official report. The peak lands between four and six in the afternoon. By seven in the evening you can compute the day’s max hourly average yourself, and the market stays open until midnight.

The agent’s estimate was 22 MW off the official number on the first scored day and 43 MW off on the second. With strikes 500 MW apart, that’s knowing the answer with five hours left to trade.

The first paper run fired at 3 PM, before the peak, and would have lost all six candidates. Lesson learned, gate added: past 19:00 Central, and the last two complete hours both below the day’s max. Then it went live with $10 per market, $20 per day, and a rule to only buy at 90c or better.

It never bought anything. Both evenings, every strike showed only cheap bids on the losing side and zero offers on the winning side. Raw order books, not a parsing bug. Everyone still in that book after seven knows what you know. The information edge was real and completely unmonetizable, which turns out to be a different thing from “no edge”. I shelved it.

Day two: going live, one contract at a time

I’d told the agent to tell me when it had the confidence to trade real money, and it set its own bar first: queue-model paper P&L positive at two standard errors over 200 markets. It didn’t get there. What it had after a day was a fill model it couldn’t validate without real orders, and that’s the argument I bought: one contract per side is the cheapest possible test of whether queue means anything.

The live maker mirrored the paper one. Post-only orders at the inside, one contract each side, expiration set to T-2 so orders die even if the process doesn’t, inventory capped at three, kill switch at -$10 cumulative, dry-run mode, every live action named in chat before it ran. The Kalshi API had its own lessons: orders are placed on the YES leg only (buying NO means asking YES at one minus the NO price), crypto markets live on a separate exchange shard so funds had to be moved there, and the API key scope didn’t allow transfers, so I did that one from the app.

It ran for five hours: twenty markets, 306 fills, about 146 pairs, 75 post-only rejections, zero fees, -$2.99. Twelve of twenty markets lost. The worst single market was -84c. At 6 PM I paused it. Not because of the money. Because the paper model had said +25c per market (6c of it real spread income) and reality said -15c, and I wanted to understand why before deciding anything.

Day three: the dig

The agent and the paper logger had watched the same twenty markets. So the question was answerable: where did paper and reality diverge?

Not on fill rate. Paper’s queue model predicted 17.9 fills per market; live got 15.1. Within fifteen percent, from a model built on the public trade tape. That part worked.

On fill quality, the sign flipped. Take every fill and split it into two pieces: spread capture (the mid price at the moment of the fill, minus what I paid, which is what a maker is supposed to earn) and carry (settlement minus that mid, the ride on inventory).

source fills spread capture / fill carry / fill
live 293 -0.46c -0.61c
paper, queue model 358 +0.71c +0.59c
paper, touch model 400 +0.36c 0.00c

Same markets, same quoting rule, nearly the same number of fills, and the one number the whole strategy exists to earn came out negative. The paper model marked each fill at the quote of the snapshot that produced it. The live order rested at that price for up to ten seconds, one REST poll, while a $2M/market book moved underneath it. By the time the poll saw the fill, the price had gone through it. A slow market maker doesn’t get filled at random. It gets filled when the market is leaving. Then carry finishes the job, because the position is on the side the price just left.

The timing confirms it. Fills in the first five minutes of the window lost $3.97; fills in the last five made $0.85. Earlier fill, more time for the price to travel.

Then the honest part. The agent replayed four rule tweaks (tighter inventory cap, start quoting later, both) on the twenty live markets and on 154 other paper markets. Quoting only the last six minutes turns the live sample into +$0.08. It also looks fine on paper. None of it means anything: the rules were chosen after looking at these twenty markets, and the out-of-sample check runs on the model that gets the sign wrong. The defect is in the execution layer, not the parameters. With a half-spread around a cent and ten seconds between re-quotes, latency is bigger than the edge.

What I actually learned

Negative results with numbers beat positive results with vibes. Every stage here produced a number and a reason. “Coin flips after fees, 55 of 60 cells.” “Six cents of real spread income per market and fifteen dollars of luck.” “Fill rate right, fill quality backwards.” I’d take that over a screenshot of a green P&L any day.

Your paper model has to simulate the thing that kills you. Mine modeled queue position carefully and latency not at all. The fix is one line of intent: mark fills at the post-fill book, not the pre-fill snapshot. We’ll never know how many strategies look good on paper because the paper doesn’t know what a slow fill is.

An information edge is not a monetizable edge. ERCOT was the best signal in the whole program and it paid nothing, because the only people left on the other side at 7 PM also had the signal. “Who’s selling to me, and why?” is the question, every time.

The agent part worked better than I expected. My agent kept the whole program in context across three days and several fresh sessions: every decision, every kill criterion, every number, all with an audit trail. It wrote nearly thirty commits of self-correction, three paper loggers, an API client, and a morning review that caught its own luck before I could get excited about it. Where I earned my keep was in the two calls that mattered: the go-live bar, and the pause. It would have kept running. It wasn’t wrong to; the kill switch was far away from $2.99. But “the paper model disagrees with reality” is a stop, and that judgment is still human.

$2.99 was the price of the dataset. I now have 174 markets of paper fills, 306 real fills, two days of ERCOT estimates scored against official numbers, and a clear statement of why a REST-polling maker can’t earn the BTC spread. That’s cheap.

What’s next

Nothing, for now. The strategy as designed needs a streaming order book and sub-second re-quotes to even be tested, and that’s a different project from “can a careful engineer earn pennies here.” The $97.01 sits in the account. The repo might go public once I’ve scrubbed it. And the next time an agent tells me the paper numbers look good, the first thing I’ll ask is what the paper can’t see.