The Kalshi Automation Stack: From Signal to Settlement
We pulled Kalshi's full series catalog this morning. There were 13,394 series in it, across eighteen categories. Most of them never repeat — 5,585 are custom-scheduled and 5,398 are one-offs, which is what a catalog dominated by sports and entertainment looks like. The 2,408 that do run on a fixed clock span six different cadences, from every fifteen minutes to once a year.
Then we checked 290 open markets across crypto, weather, and economic series. Every single one reported an expected_expiration_time that differed from its latest_expiration_time. On Bitcoin hourly contracts, the gap was seven days.
A catalog that size is not a venue you trade by hand. Yet most Kalshi bot guides still walk you through a single box — pick a signal, or call the orders endpoint, or size with Kelly.
Almost none of them describe the arrows between the boxes. That's where the seven-day gap lives, and it's where working bots actually break.
**Key Takeaways** - Kalshi's catalog holds **13,394 series**, but only **2,408 repeat on a fixed clock** — and those span six cadences from 15-minute crypto to annual, so one stack runs six timers (our pull of Kalshi's public `/series` API, Aug 24, 2026) - Only **31% of open markets** in our 291-market sample carried two-sided quotes. Your signal stage will happily generate trades on markets nobody is quoting - The taker fee eats **7 × P percent of your maximum profit** and **7 × (1−P) percent of your capital at risk** — the same curve is worst at opposite ends depending on the denominator - Median spreads ran **1¢ on BTC 15-minute contracts and 18¢ on CPI markets**. Crossing that CPI spread costs about five times the worst-case taker fee - **All 290 markets** we checked for expiry fields had `expected_expiration_time` ≠ `latest_expiration_time`. A bot scheduling exits off the wrong field waits a week for an hourly contract

The Stack Is Four Stages and Three Seams
Every working Kalshi automation stack has the same four stages. Signal decides what's mispriced. Sizing decides how much. Execution turns that into resting orders. Monitoring watches the position until settlement pays it out.
The stages are the easy part. Each one is a solved problem with good documentation and, in most cases, a post on this blog explaining it.
The seams are the hard part. A seam is where one stage hands state to the next, and every seam on Kalshi has a venue-specific failure mode. A probability estimate has to become a market ticker that still exists. A dollar allocation has to become an integer contract count. A submitted order has to become a confirmed position.
**The framing that matters:** stage bugs are loud and seam bugs are quiet. If your signal model is broken, your backtest tells you. If your seam between sizing and execution silently rounds a $47 allocation down to zero contracts, nothing tells you — your bot just stops trading and reports no errors.
Stage 1: Kalshi's Catalog Runs Six Clocks at Once
Kalshi isn't one market type with one rhythm. Our catalog pull returned 3,483 Sports series, 2,510 Entertainment, 2,224 Politics, 914 Financials, 730 Economics, 354 Climate and Weather, and 272 Crypto, among others.
What matters operationally isn't the category. It's the frequency field, because that determines how often your signal stage has to wake up — and the first thing it tells you is that most of Kalshi doesn't tick at all. Roughly 82% of series are marked custom or one_off: a specific match, a specific award, a specific event that happens once. Those need an event-driven scheduler keyed off open and close times, not a polling interval.
The remaining 2,408 series are the ones with a clock, and their clocks are nothing alike.
Nineteen series settle every fifteen minutes, and fourteen of those are crypto. Fifty-seven settle hourly. Two hundred fifty settle daily — 113 of them weather. But 1,455 settle annually and 341 monthly, which is where most economic contracts live.
A stack that polls every series on the same timer is either hammering the API for annual contracts or sleeping through 15-minute crypto expiries. Cadence has to be a property of the series, read at runtime from frequency, not a constant in your config.
frequency | Series | Sensible poll interval | Typical content |
|---|---|---|---|
fifteen_min | 19 | WebSocket only | Crypto and index ranges |
hourly | 57 | WebSocket only | Crypto, FX, hourly weather |
daily | 250 | Minutes | Daily high/low temperature |
weekly | 286 | Hours | Jobless claims, weekly financials |
monthly | 341 | Hours | CPI, payrolls, Fed |
annual | 1,455 | Daily | Long-horizon economics, awards |
custom / one_off | 10,983 | Event-driven, not polled | Sports, entertainment, elections |
The signal itself is category-specific and well covered elsewhere — the quant playbook lays out the platform-agnostic version of signal design. Our guide on Kalshi economic event contracts covers CPI, Fed, and jobs releases; automating strike ladders covers reading a crypto ladder as a probability distribution.
Seam 1: A Signal Has to Name a Market That Still Exists
Your model outputs a probability. To act on it, that probability needs to attach to a specific market ticker with a live order book and a known expiry. Three things go wrong here.
Most markets aren't quoted. Across 291 open markets in five series, only 91 carried two-sided quotes — 31%. The other 69% are open, tradeable in principle, and completely untouched. A signal stage that filters on status: "active" will generate a healthy stream of trades that execution cannot fill. Thin books are the norm rather than the exception here — the UCD study puts the median total money staked on a Kalshi contract at $8,982, counting both sides.
The expiry field you want isn't the obvious one. Kalshi markets expose expected_expiration_time, latest_expiration_time, and close_time. In our sample, all 290 markets had expected ≠ latest. A KXBTCD hourly contract closing at 15:00 UTC showed expected_expiration_time of 15:05 the same day and latest_expiration_time of 15:00 seven days later. The second is a backstop for disputed settlement, not a schedule. Key your exit logic off it and your hourly bot holds for a week.
Field names drift. Kalshi's market objects now return prices as decimal strings under _dollars suffixes — yes_bid_dollars, yes_ask_dollars, notional_value_dollars — alongside the older integer-cent fields. Parsing the wrong one gets you a silent zero, not an exception.
**The fix is a contract-resolution layer.** Between signal and sizing, insert one step that takes a proposed trade and either returns a fully-resolved market — ticker, live two-sided quote, correct expiry, tick structure — or rejects the signal. This layer is the single highest-leverage thing in a Kalshi stack, and almost nobody writes it because it doesn't feel like strategy work.
This is unglamorous work, and that's exactly why it goes missing. Nobody writes a blog post about a function that takes a probability and a category and returns either a market or a rejection reason, because there's no cleverness in it — it's plumbing that mostly says no. But it's the only place in the stack where the three failure modes above can be caught in one pass, and every hour you don't spend building it, you spend instead debugging orders that were rejected for reasons your logs describe badly. Kalshi gives you the feed to keep that layer current. The market_lifecycle_v2 and event_lifecycle WebSocket channels push market open, close, and settlement transitions as they happen (Kalshi Docs, 2026), so your resolver learns that a market died from a push rather than from a failed order.
Stage 2: The Kalshi Fee Formula Has Two Denominators
Kalshi's taker fee is closed-form and published, which is genuinely unusual. The general formula is:
fee = roundup(0.07 × C × P × (1 − P))where C is contract count and P is price in dollars, rounded up to the next cent. Kalshi's fee schedule as filed with the CFTC states plainly that there is no settlement fee, no processing fee, and no membership fee, and that trading fees apply only to orders immediately matched against the book — resting orders that later fill aren't charged (Kalshi Fee Schedule, as filed with the CFTC). Holding to settlement costs nothing. That filing is the 2022 iteration and per-series terms have diverged since, so treat it as the shape of the schedule and read the live values off the API.
Two caveats before you hardcode 0.07.
First, series carry their own fee_multiplier. In our catalog pull, 19 series ran a 0.5 multiplier — every one of them an MLB series — and 14 ran a multiplier of 0, meaning no trading fee at all. Read the multiplier off the series object rather than trusting the general formula.
Second, the round-up applies to the order total, not to each contract. A University College Dublin study that reconstructed Kalshi fees from transaction-level data puts the effective fee on a 50¢ contract at 1.77% of notional once rounding is counted, against 1.75% without it (Bürgi, Deng & Whelan, 2026). The drift is trivial on a 500-lot and brutal on a 1-lot, where rounding a sub-penny fee up to a full cent can multiply it several times over. Most fee calculators compute per contract and then multiply, which gets this backwards.
Everyone plots that curve in dollars and observes that it peaks at 50¢. That's true and not very useful, because you don't size in absolute fees. You size against what's at stake. And the moment you normalize, the symmetric curve splits into two straight lines pointing opposite directions.
Divide the fee by your maximum profit (1−P) and it simplifies to exactly 0.07 × P. Divide it by your capital at risk P and you get 0.07 × (1−P).
**So "cheap contracts" and "expensive contracts" swap places depending on which question you're asking.** A 95¢ contract is nearly free in dollar terms — a third of a cent — but that fee consumes **6.65% of the five cents you stand to win**. A 5¢ longshot costs the same third of a cent, which is only **0.35% of your upside** but **6.65% of the nickel you're risking**. Sizing rules that quote "the fee is small at the tails" without saying *which tail and which denominator* are quoting a coin flip.
This isn't an edge case, because Kalshi's prices cluster at exactly the two ends where the denominators diverge most. In the UCD sample of 313,972 contract prices, roughly two-thirds sat below 10¢ or above 90¢, and only 2.7% fell between 50¢ and 59¢. The tails aren't where the odd trade happens — the tails are where Kalshi lives.
For a strategy holding to settlement, the max-profit denominator is the one that binds. Your required edge to break even is 0.07 × P × (1−P) in absolute probability terms — 1.75 points at 50¢, 0.63 points at 90¢.
Worth sizing against a sobering baseline: the same study found the average pre-fee return on a Kalshi contract is −20%, and investors buying contracts priced under 10¢ lost over 60% of their money. Cheap longshots are exactly what a naive scanner surfaces most of, and they're the worst trades on the venue before fees even apply.
That number goes into the Kelly fraction, not next to it. Our post on position sizing and risk management covers why fractional Kelly on a noisy edge estimate beats full Kelly, and why backtests lie covers how badly an unfee'd backtest overstates that edge.
Seam 2: Sizing Outputs Dollars, Kalshi Wants Integers
Your sizing stage produces a dollar allocation. Kalshi trades whole contracts at integer cent prices — 1¢ through 99¢, with no 32.5¢ (Bürgi, Deng & Whelan, 2026). That conversion is lossy, and the loss is not symmetric.
At 3¢ a contract, a $50 allocation buys 1,666 contracts and rounding is irrelevant. At 94¢, it buys 53, and the rounding error is under 1%. But a small account sizing 0.5% of a $400 balance into a 94¢ contract gets $2, which is two contracts — and the fee, rounded up to the next cent, is now a meaningful fraction of the position.
Three rules make this seam safe:
- Round contract counts down, never up. Rounding up silently exceeds your risk limit.
- Reject rather than round to zero. A sizing result below one contract is a signal that the position is too small to express, not a trade to skip quietly. Log it.
- Recompute the fee after rounding, not before. The roundup in Kalshi's formula applies at the order level, so fee-per-contract rises as order size falls.
Stage 3: Kalshi API Execution — REST for State, WebSocket for Change
Kalshi exposes both a REST API and a WebSocket feed, and the split between them is not a preference. It's a rate-limit constraint.
REST reads draw from a token bucket. The Basic tier refills at 200 read tokens per second, and most requests cost the default of 10 tokens (Kalshi Docs, 2026). That's twenty reads per second, total. Polling twenty markets once a second consumes your entire read budget. Polling a full 188-rung Bitcoin ladder that way isn't a tuning problem — it's architecturally wrong.
The rule that works: REST for state you need once, WebSocket for state that changes. Fetch the market catalog, contract terms, and your starting position over REST. Subscribe to orderbook_delta and fill channels for everything that moves after that. Your steady-state REST budget should be close to zero.
Two details in that documentation deserve more attention than they get:
- Batching doesn't save tokens. A batch create of 25 orders costs 25 × 10 = 250 tokens, exactly what 25 individual calls cost. Batch for atomicity and latency, never for rate-limit relief.
- Basic-tier write buckets don't burst. Above Basic, write buckets hold two seconds of budget, so an idle bot can fire a double-rate block of orders when the market moves. At Basic, the bucket holds one second and idle time banks nothing. The event-driven repricing pattern that works at Advanced silently throttles at Basic.
Which order type to use is a property of the contract, not a global setting. Here's why:
Median spreads in our Aug 24 sample ran 1¢ on BTC 15-minute contracts, 2¢ on BTC hourly ranges and NYC daily high temperature, 4¢ on hourly NYC temperature, and 18¢ on CPI year-over-year markets.
Crossing an 18¢ spread costs roughly 9¢ per contract at mid. The worst-case taker fee, at 50¢, is 1.75¢. The spread is five times the fee. On CPI markets, arguing about fee optimization while crossing the spread is rearranging deck chairs — you need resting limit orders or you have no strategy.
| Contract type | Median spread | Cost to cross | Execution style |
|---|---|---|---|
| BTC 15-minute | 1¢ | ~0.5¢ | Taker viable |
| BTC hourly range | 2¢ | ~1¢ | Taker viable, maker better |
| NYC daily high temp | 2¢ | ~1¢ | Taker viable, maker better |
| NYC hourly temp | 4¢ | ~2¢ | Maker preferred |
| CPI year-over-year | 18¢ | ~9¢ | Resting orders only |
That's also where maker economics matter. Of the 13,394 series in our catalog pull, only 130 carried a maker-fee type — about 1%. On the other 99%, resting orders that fill pay no trading fee at all. The series object exposes this directly in its fee_type field, so it's one call to check before you design your execution style.
The UCD study reaches the same conclusion from the returns side. Using Kalshi's own trade-initiator flag rather than inferring it, the authors find makers earn higher returns than takers. Their sample runs through April 2025, before maker fees existed anywhere on the venue, so read it as a floor on the maker advantage rather than a current measurement. Fee structure and realized returns both point at resting orders.
One more budget trap worth knowing. In June 2026 Kalshi set the token cost of the legacy /portfolio/orders mutation and batch endpoints to ten times the equivalent v2 /portfolio/events/orders costs (Kalshi changelog, 2026). A bot still calling the old create-order path burns ten times the write budget for identical work. It keeps working, just at a tenth of the throughput — the most expensive kind of silent failure.
Seam 3: The Order You Sent Is Not the Position You Hold

This is the seam that quietly destroys accounts. Your bot submits an order. The order rests. It fills partially. It fills again. It gets cancelled at close. Somewhere in there, your internal notion of "what I own" drifts from Kalshi's.
The failure mode isn't dramatic, which is precisely the problem. There's no exception, no alert, no red line in a dashboard. It's a bot that thinks it holds 40 contracts, actually holds 55, and sizes its next trade against the wrong denominator — then does that again, and again, each time from a slightly worse starting point. By the time anything looks visibly wrong, the position is several multiples of the limit you carefully wrote down, and the limit was never wrong. The arithmetic feeding it was. Reconstructing what happened after the fact is miserable, because every individual decision in the chain looks defensible in isolation.
Three practices close it:
- Treat fills as the source of truth, not orders. Subscribe to the
fillchannel and build position from fills. An acknowledged order is an intention. - Reconcile against the exchange's own view on a timer. Kalshi exposes a
market_positionsWebSocket channel alongside the REST positions endpoint (Kalshi Docs, 2026). Every few minutes, compare your computed position to theirs and alert on divergence rather than auto-correcting silently. - Make reconciliation a blocking check before sizing. If local and remote disagree, stop trading that market. Don't let a known-stale position feed the next sizing decision.
Two mechanics make this tractable. Kalshi's WebSocket returns error code 25, "subscription buffer overflow," when your client falls behind during a message burst — that's your signal that local state is now untrustworthy, not a transient warning to log and ignore. And the orderbook_delta channel accepts a get_snapshot action, so a client that has fallen behind can resynchronize in place instead of tearing down and rebuilding the subscription.
Stage 4: Monitoring Through Settlement
Settlement is the stage most stacks forget, because it's the one where nothing is requested. Positions resolve, cash appears, and no order was ever sent.
Three Kalshi-specific things matter here.
Settlement costs nothing and needs no action. There's no settlement fee in the fee schedule. A bot that reflexively closes positions before expiry to "avoid fees" is paying a spread and a taker fee to dodge a cost that doesn't exist. When to exit early is a real question — our post on timing trades around Kalshi resolution windows covers it — but fees aren't the reason.
Settlement timing is per-category, and weather is two categories. Kalshi's daily high and low temperature markets settle on the National Weather Service Daily Climate Report, which publishes the following morning. Its hourly temperature markets settle on The Weather Company data for a named station and process roughly 25 to 35 minutes after market close (Kalshi Help Center, 2026). Same category on the marketing page, two different oracles and two very different settlement lags. A monitoring loop that expects cash within minutes will flag the daily markets as broken every single night.
Kalshi is also explicit that only the named settlement source governs. Checking AccuWeather to predict a settlement your contract doesn't reference is a nice way to build a confidently wrong bot.
Exchange health isn't one flag. Kalshi's /exchange/status endpoint returns per-segment statuses, not just a global boolean. Our call this morning returned four separate indexes: Default, Combos, Crypto, and Tennis & Baseball, each with its own trading_active flag.
**That means a crypto bot reading only the top-level `trading_active` can see a green light while its own segment is halted.** Your health check has to read the `exchange_index` that matches the series you trade. This is a two-line fix that essentially nobody makes, because the top-level flag looks like the answer.
The Demo-to-Live Promotion Gate
Before anything else, check the host you're pointed at. Kalshi documented new production and demo endpoints in May 2026 (Kalshi changelog, 2026):
production REST https://external-api.kalshi.com/trade-api/v2
production WS wss://external-api-ws.kalshi.com/trade-api/ws/v2
demo REST https://external-api.demo.kalshi.co/trade-api/v2
demo WS wss://external-api-ws.demo.kalshi.co/trade-api/ws/v2The older api.elections.kalshi.com and demo-api.kalshi.co hosts remain supported for compatibility, which is precisely why almost every third-party Kalshi tutorial still prints them and nobody notices. Both responded when we probed them this morning. Compatibility is not a commitment.
That the demo environment is a plumbing test rather than a strategy test is settled ground — Kalshi's own docs say demo prices may not reflect real markets (Kalshi Docs, 2026), and we've made that case at length in why Kalshi is built for automated trading. The question worth asking here is narrower: which seam does demo actually catch?
Mostly this one.
**The one signing detail that eats a day:** Kalshi's RSA-PSS auth signs `timestamp + METHOD + path`, and in the exchange's own words you sign the full request path from the API root, without the hostname or query string. Sign `/trade-api/v2/portfolio/balance`, never `/trade-api/v2/portfolio/balance?limit=10`. Get it wrong and you get a 401 that says nothing about why — exactly the class of bug the demo environment exists to absorb.
That gives a clean three-gate promotion path, each gate testing a different thing:
- Backtest. Does the edge survive fees and realistic fills? This is the only gate that can answer that, and how to backtest prediction market strategies covers doing it honestly.
- Demo. Run every seam above against the real API shape — resolution, rounding, reconciliation, backoff. Nothing about your edge is being tested here, and that's fine, because nothing about your edge is what breaks first.
- Live at minimum size. Real fills, real latency, real money — and what paper trading can't prove becomes obvious fast.
The mistake is running these in parallel or skipping the middle one. Demo is where seam bugs surface cheaply, and seam bugs are the ones that don't show up in a backtest by construction.
Wiring It Without Writing the Plumbing
Read back through this post and count how much of it is strategy. Almost none. It's contract resolution, integer rounding, fill reconciliation, per-segment health checks, and cadence scheduling — the arrows, not the boxes.
That's the actual cost of a Kalshi stack. The signal is the fun part and usually the smallest file in the repo.
Turbine Studio exists because that plumbing is identical for everyone. Contract resolution, fee-aware sizing, WebSocket order management, fill reconciliation, and demo-to-live promotion are built once and shared, so what you write is the part that's actually yours — the signal. If you'd rather build it all yourself, our Kalshi bot tutorial walks the code path, and why Kalshi is built for automated trading covers the venue rules in depth.
See what Turbine Studio costs →
Frequently Asked Questions
What does a complete Kalshi automation stack actually include?
Four stages and three seams. Signal generation, position sizing, execution, and settlement monitoring — connected by contract resolution, dollar-to-integer conversion, and fill reconciliation. The stages are well-documented; the seams cause most live failures because they fail silently rather than throwing errors.
How do I calculate Kalshi fees before placing a trade?
Use roundup(0.07 × C × P × (1−P)), where C is contracts and P is price in dollars. There's no settlement fee. Normalize it: the fee equals 7 × P percent of your maximum profit, and 7 × (1−P) percent of your capital at risk.
Should I use Kalshi's REST API or WebSocket for my bot?
Both, split by purpose. REST for state you fetch once — catalog, contract terms, starting positions. WebSocket for anything that changes, via the orderbook_delta and fill channels. At the Basic tier's 200 read tokens per second, polling twenty markets once a second consumes your entire read budget.
Which part of a Kalshi bot fails most often in production?
The seams, not the stages. Position drift from unreconciled fills is the most common, because it fails silently instead of throwing: a bot that believes it holds 40 contracts while actually holding 55 keeps sizing against the wrong number until a risk limit breaks.
What is the correct Kalshi API base URL in 2026?
Production is https://external-api.kalshi.com/trade-api/v2, with WebSocket at wss://external-api-ws.kalshi.com/trade-api/ws/v2. Demo uses external-api.demo.kalshi.co. The older api.elections.kalshi.com host still works for compatibility, which is why most tutorials still print it.
Why do most of Kalshi's open markets show no quotes?
Liquidity concentrates in a handful of active series. In our 291-market sample across crypto, weather, and CPI series, only 31% carried two-sided quotes. Open status means tradeable in principle, not quoted — your signal stage needs an explicit liquidity filter.
The Takeaways
- Cadence is data, not config. Only 2,408 of Kalshi's 13,394 series repeat on a fixed clock, and those six clocks range from fifteen minutes to a year. The other 82% are one-off or custom and want an event-driven scheduler instead. Read
frequencyat runtime, or your timer is wrong for most of the catalog and silently idle for the rest. - Build a contract-resolution layer. Just 31% of the open markets we sampled carried two-sided quotes, and all 290 we checked for expiry fields exposed a misleading
latest_expiration_time. One rejection gate between signal and sizing kills both bugs at once. - Normalize the fee before you size. 7 × P percent of upside, 7 × (1−P) percent of risk.
- Let the contract pick the order type. A 1¢ BTC spread and an 18¢ CPI spread are not the same execution problem — and since only about 1% of series charge makers anything, resting is close to free almost everywhere.
- Reconcile from fills. Position drift compounds in silence. While you're there, read the
exchange_indexthat matches your series: a green top-leveltrading_activecan sit directly above a halted Crypto segment.
The stages were never the hard part. Build the arrows.
This article is for informational and educational purposes only. It is not investment advice, and nothing here is a recommendation to trade any specific contract. Prediction market trading involves risk of loss. Market data cited was captured from Kalshi's public API on August 24, 2026, and reflects that moment only — series counts, spreads, quote coverage, and expiration fields change continuously. Fee schedules, rate limits, and exchange rules change; verify current terms with Kalshi directly before trading.