mean_reversion
This page covers mean_reversion, one of the five built-in strategies, on Kalshi, Polymarket and Polymarket US: its entry bands, exit target and cooldown, venue differences, backtests and examples. Rules all five share, such as order sizes and how to write params, are on the Built-in Strategies page. Every number here is an illustrative example, not a suggested setting.
mean_reversion buys when the price is at an extreme and exits when it comes back toward a target. Each loop, for each market:
- It reads the YES price.
- If it is flat, no entry is resting, the cooldown has passed and (on Kalshi) this is the series' current market:
- if
price_floor< price ≤entry_low, it buys YES; - if
entry_high≤ price <price_ceiling, it buys NO; - otherwise it waits.
- if
- If it holds a position, it sells that side when the price is within 0.02 of
exit_target.
mean_reversion: what each YES price does
While holding, with exit_target: 0.50: the exit test at whole-cent prices
- 0.47Hold
- 0.48Hold
- 0.49Exit
- 0.50Exit
- 0.51Exit
- 0.52Hold
- 0.53Hold
The exit needs a price strictly within 0.02 of the target, so 0.48 and 0.52 hold. A price that jumps from 0.47 to 0.53 between loops skips the band; the Bot keeps holding until a later loop lands inside it, possibly to settlement.
| Parameter | Type | Default | Allowed values | What it does |
|---|---|---|---|---|
entry_low | number (YES price) | Required | Below entry_high | Buy YES when YES is at or below this price. |
entry_high | number (YES price) | Required | Above entry_low | Buy NO when YES is at or above this price. |
exit_target | number (YES price) | 0.5 | 0 to 1 | Exit when YES comes within 0.02 of this price. |
cooldown | whole seconds | 60 | ≥ 0 | Minimum time between entry orders. |
position_size | — | — | — | Ignored. Entry size comes from risk.max_position (see Order sizes). |
entry_low and entry_high
Validation only checks that entry_low is below entry_high. Two things are worth checking yourself:
entry_lowmust be aboveprice_floor, or YES entries can never happen (the floor test is strict).- Setting
entry_highat or aboveprice_ceilingswitches off NO entries.
The entry is a limit order at the current price. While it rests the Bot places no other entry. If it hasn't filled after two loops (at least 60 seconds), the Bot cancels it and, once the cooldown has passed, enters again at the new price. A part that filled is kept and exits like any position. A trading_schedule that closes on Kalshi also cancels a resting entry. On Polymarket and Polymarket US any order resting in the market, including one you placed, also holds entries back.
Both sides exit the same way: a YES position sells YES and a NO position sells NO.
exit_target
The exit is a narrow band, not a threshold. It fires only when the YES price is strictly within 0.02 of exit_target. At whole-cent prices, a target of 0.50 exits at 0.49, 0.50 or 0.51. If the price jumps past the band between loops, you keep holding, possibly to settlement. There is no stop-loss.
On Kalshi the exit sells everything this Bot holds; on Polymarket US it sells the whole position on the side you hold, as market orders. On both, it is split into orders no larger than Max contracts per order, which Studio pre-fills at the entry size, min(max_position, 10).
cooldown
The cooldown is measured from the last entry the venue accepted, not from a fill or an exit. A skipped entry (a resting order, a Kalshi runtime skip, or not enough buying power on Polymarket US) doesn't start it. On Kalshi each market has its own clock. On Polymarket and Polymarket US there is one clock per Bot, and each new window of a rolling Polymarket series starts without one. The cooldown only spaces entries; it never delays an exit.
Write it as an unquoted whole number of seconds. 2.5, "60" and "5m" fail with params.cooldown: must be an integer, and -1 fails with must be >= 0. Backtests use 0 seconds when cooldown is omitted, so set it explicitly if you want the backtest to match the live Bot.
# Rejected: cooldown must be a whole number of seconds
# error: params.cooldown: must be an integer
version: 1
platform: kalshi
strategy: mean_reversion
market:
series_ticker: "KXBTC15M"
risk:
max_position: 1
price_floor: 0.05
price_ceiling: 0.95
loop:
interval: 30
params:
entry_low: 0.15
entry_high: 0.96
cooldown: 2.5mean_reversion by venue
| Kalshi | Polymarket | Polymarket US | |
|---|---|---|---|
| Entry size | min(max_position, 10) | The market minimum. With max_position below it, entries are skipped. | min(max_position, 10), after a buying-power check |
| Unfilled entry | Cancelled after two loops; a new entry follows after the cooldown | Same | Same |
| Exit | Sells the side this Bot holds, for everything it holds, at that side's best bid | Sells every YES and NO share you hold at the best bid | Market sell of the side and size you hold |
| Cooldown clock | Per market | Per market window | One per Bot |
| Backtest | Yes | Yes | No |
mean_reversion backtests
The backtest runs the same entry and exit tests on each historical bar, then fills the order as a taker at the next bar's ask. The live Bot posts a limit order at the current price instead. Entry size is min(max_position, 10) on every venue, the exit closes both sides, and the default cooldown is 0 seconds (60 live).
mean_reversion examples
Illustrative only: YES-only reversion on the most liquid market of the current Kalshi 15-minute BTC event, with new entries only on weekdays from 09:30 to 16:00 New York time. It buys 2 contracts when YES is above 0.05 and at or below 0.15, and exits when YES trades at 0.49 to 0.51. Studio pre-fills Max contracts per order at 2, min(max_position, 10), so the entry and the one-order exit fit.
version: 1
platform: kalshi
strategy: mean_reversion
market:
series_ticker: "KXBTC15M"
risk:
max_position: 2
price_floor: 0.05
price_ceiling: 0.95
loop:
interval: 15
trading_schedule:
timezone: America/New_York
trading_hours:
- days: [mon, tue, wed, thu, fri]
start: "09:30"
end: "16:00"
params:
entry_low: 0.15
entry_high: 0.96 # at or above price_ceiling, so the NO side never fires
exit_target: 0.50
cooldown: 120Illustrative only: both sides on the rolling Polymarket BTC 15-minute Up/Down series. It buys Up when Up is at or below 0.30, buys Down when Up is at or above 0.70, and sells everything when Up is within 0.02 of 0.50. Entries are the market minimum, so 5 shares on a market whose minimum is 5. Each window starts with a fresh cooldown.
version: 1
platform: polymarket
strategy: mean_reversion
market:
recurring:
kind: updown
asset: btc
interval: 15m
risk:
max_position: 5
price_floor: 0.05
price_ceiling: 0.95
loop:
interval: 15
params:
entry_low: 0.30
entry_high: 0.70
exit_target: 0.50
cooldown: 300