Built-in Strategy Risk, Loop and Other Blocks
This page covers the settings outside params that shape how a built-in strategy trades on Kalshi, Polymarket and Polymarket US: how risk and loop apply, Kalshi's entry and portfolio caps and runtime checks, deployment risk limits, why a Kalshi Bot didn't trade, and which other strategy blocks you can combine with a built-in strategy. For each strategy's own params, and the order sizes they produce, start from Built-in Strategies.
How risk and loop apply
Built-in strategies use the same risk and loop keys as every strategy, but each one reads them in its own way.
| Setting | Type | Allowed values | What it does for built-in strategies |
|---|---|---|---|
risk.max_position | whole number | ≥ 1 (2.9 is rejected; 2.0 is fine) | Sets or caps order size (see Order sizes). spread_capture stops quoting at it. observation_momentum stops buying at it on Polymarket and Polymarket US. It is not a cap on total holdings for panic_fade, or for observation_momentum on Kalshi. |
risk.price_floor | number | 0 to under 1, below price_ceiling | New entries need a price strictly above it. panic_fade also exits when the price falls below it. spread_capture raises its buy quotes to at least the floor. |
risk.price_ceiling | number | above 0 up to 1 | New entries need a price strictly below it. spread_capture lowers its sell quotes to at most the ceiling. |
loop.interval | whole seconds | ≥ 10, whole numbers only | How often the logic runs. It also sets how many samples fit in panic_fade's 5-minute window and how precisely drift detects its blackout. |
risk.max_entries_per_market | whole number | ≥ 0; Kalshi only. Fractions such as 0.5 are rejected. | Caps buy orders per market. Not recommended with spread_capture. |
risk.max_portfolio_positions | whole number | ≥ 0; Kalshi only. Fractions such as 0.9 are rejected. | Refuses a buy in a new market once this many markets are active. 0 or omitted means no limit. |
risk.max_notional, risk.max_loss | — | Rejected | Available only to custom strategies. |
The price bounds gate entries only. They never close a position, except for the panic_fade stop below the floor. On regular Polymarket a buy's price is also clamped between the floor and the ceiling. Sells aren't clamped, so the panic_fade stop sells at the bid, below the floor.
risk.max_entries_per_market on Kalshi
This counts buy orders, not contracts, per market. A buy counts while it rests and once any part of it fills; a buy cancelled with nothing filled frees its slot. Sells never count. The count comes from your Kalshi order history for this deployment, so it survives restarts. A redeploy starts a new count. 0 or omitted means no cap.
When the cap is reached, further buys are skipped rather than refused. The Bot logs "entry skipped: max entries per market reached", and the strategy doesn't advance: mean_reversion's cooldown doesn't start, panic_fade records no fade, observation_momentum's streak doesn't grow, and pre_announcement_drift isn't marked as entered. Exits are never limited, and they only ever sell what the Bot holds.
Warning: Combining
risk.max_entries_per_marketwith spread_capture is not recommended. Once fills have used the allowance it posts only YES sell quotes, and on Kalshi a YES sell while you hold no YES is a NO buy that the cap doesn't limit.
risk.max_portfolio_positions on Kalshi
Before every buy, the Bot counts the active markets: markets where your account holds a position (from any Bot or a manual trade) plus markets where this Bot has resting orders. A buy in a market that isn't already active is refused once that count reaches the cap. The cap never limits sells, or buys in a market that is already active.
The buy is refused, not skipped: the loop logs an error and the strategy doesn't advance its state, so it tries again on a later loop. On spread_capture the ladder stops at its first buy quote in a new market, after that loop's old quotes were cancelled, so no quotes are posted.
Deployment risk limits
Kalshi and Polymarket US Bots also enforce the deployment risk limits you confirm under Risk limits in the deploy dialog. Studio pre-fills them from your strategy:
| Deployment risk limit | Studio pre-fills | If a deployment sets no value |
|---|---|---|
| Max contracts per order | The strategy's largest order: min(max_position, 10) for mean_reversion, max_position for observation_momentum, min(max_position, 5) for pre_announcement_drift, fade_size for panic_fade, 1 for spread_capture | 1 |
| Max open contracts (held plus every resting order in the market) | risk.max_position. The Bot applies the lower of this and max_position. | The smaller of max_position and 3 |
| Max dollars per order | The same number as Max contracts per order, in dollars | $10 |
| Max daily loss (Kalshi only) | risk.max_position, in dollars | Off |
| Max daily notional traded | The largest of the order size, max_position and 10 × the order size, in dollars | $100 |
| API error limit | Not taken from the strategy | 3 errors in 5 minutes |
An order larger than a limit is refused, not shrunk. The Bot logs an error and tries again on a later loop. The exception is a built-in exit on Kalshi or Polymarket US, which is split into orders that fit Max contracts per order. The pre-filled per-order limits fit each strategy's own orders; lower them and larger orders are refused. Buys are also refused once held plus resting contracts in the market would pass the lower of max_position and Max open contracts, which can stop:
- observation_momentum streaks on Kalshi, once their growing orders add up past it,
- panic_fade fades when
fade_sizeis abovemax_position, - spread_capture ladders, because every resting quote counts (see
order_count).
Change the limits in the deploy dialog if your params produce larger sizes. Regular Polymarket built-in Bots don't apply deployment risk limits, so on Polymarket max_position and the strategy's own sizing are your only size controls. See also Kalshi deployment risk limits and the overview.
Note: Max daily loss is enforced on Kalshi only: once the deployment's P&L since 00:00 UTC, realized plus unrealized, reaches it, buys are refused until 00:00 UTC while exits keep running. Polymarket US Bots don't read it, and the deploy dialog doesn't show it there. Once the API error limit trips, the Bot refuses new orders for one error window, then resumes on its own. During it a Polymarket US sell that reduces your position still goes out, while Kalshi built-in exits, which aren't reduce-only, wait out the window.
Kalshi runtime checks
Kalshi Bots run a few fixed checks before every order. You can't change them, and they also apply in paper trading.
- Fresh trades. An order is refused when the market's most recent trade, by anyone, is more than 120 seconds old. A market that has never traded passes. The market must also be open, and a buy needs a two-sided book. On quiet markets spread_capture cancels its quotes and posts none, and the other strategies wait until a fresh trade prints.
- Order budget. One Bot sends at most 20 orders per rolling 60 seconds, shared across all its markets. Cancels don't count. Orders beyond that are skipped, not refused.
- Rate-limit backoff. After Kalshi answers with a rate-limit error, orders are skipped for the wait Kalshi asks for, or 5 minutes if it doesn't say.
A skipped entry leaves the strategy's state unchanged, as described under risk.max_entries_per_market, and a skipped exit is sent again on a later loop while the position is still held. A refused order raises an error instead, and the strategy tries again on a later loop.
Regular Polymarket and Polymarket US don't run the fresh-trade check or the order budget. Polymarket US refuses every order unless the market is active, and a buy also needs both a bid and an ask; an exit only needs a bid. Buys also need enough buying power. A refused exit is retried on a later loop. See Built-in strategies on Polymarket US.
Why a Kalshi Bot didn't trade
Kalshi mean_reversion and panic_fade Bots record a reason on each loop where they don't trade. The other built-in strategies only log their waits and errors. See Monitoring.
| Message | Meaning |
|---|---|
no trades yet, waiting | No price yet: nothing has traded and the book isn't two-sided. |
price within neutral band — waiting | mean_reversion: the price is between entry_low and entry_high, or outside the price bounds. |
holding, waiting for exit_target | mean_reversion: the Bot holds a position and the price isn't within 0.02 of exit_target. |
entry resting, waiting for fill | mean_reversion: the Bot's entry is resting unfilled. It is cancelled after two loops. |
entry cooldown active | mean_reversion: you're flat, but the cooldown hasn't passed or this isn't the series' current market. |
holding fade, waiting for recovery | panic_fade: a fade is open, the price hasn't risen by recovery_exit and is still above price_floor. |
fade buy resting, waiting for fill | panic_fade: the fade's buy hasn't filled yet. It is cancelled after two loops. |
drop below panic_threshold (or fade budget spent) | panic_fade: the drop is too small, max_fades is used up, the price is outside the bounds, or this isn't the series' current market. |
entry skipped: max entries per market reached | Any strategy: risk.max_entries_per_market is used up in this market. |
order attempt budget exhausted or Kalshi rate limit backoff active | A runtime check skipped the order (see Kalshi runtime checks). |
Combining with other blocks
Built-in strategies accept fewer blocks than custom. Some are rejected, and some are accepted but never used.
| Block or key | Kalshi | Polymarket | Polymarket US |
|---|---|---|---|
trading_schedule | Supported | Rejected | Rejected |
active_window | Rejected (custom only) | Rejected | Rejected |
rules | Accepted, ignored | Accepted, ignored | Rejected |
edge | Accepted, ignored | Rejected | Accepted, ignored |
derived | Rejected | Rejected | Rejected |
risk.max_entries_per_market | Supported | Rejected (custom only) | Rejected |
risk.max_portfolio_positions | Supported | Rejected | Rejected |
risk.max_notional, risk.max_loss | Rejected (custom only) | Rejected | Rejected |
market.selection | Supported with series_ticker | Not available | Not available |
market.min_order_size | Rejected | Fallback for markets pinned by token IDs | Rejected |
"Accepted, ignored" means the strategy saves but the Bot never reads that block. Built-in strategies read no external data, so an edge block has no effect on them. Ignored rules are still partly checked: an unknown rule key, a rule-level trading_schedule, or entry_profit_offset, entry_stop_loss_offset and entry_profit_targets are rejected.
On regular Polymarket, market.min_order_size only matters for a market pinned by yes_token_id and no_token_id. It sets the spread_capture quote size and the smallest order the other built-ins send, and defaults to 5. For any other selector the Bot uses the market's own minimum. The value isn't checked when you save, and a non-numeric value stops the Bot from starting. A token-pinned market has no known end date, so pre_announcement_drift never enters on one. See market.min_order_size.
# Rejected: trading_schedule and max_entries_per_market are Kalshi-only for built-in strategies
# error: trading_schedule: supported only for Kalshi event strategies
# error: risk.max_entries_per_market: is only supported for custom strategies on Polymarket
version: 1
platform: polymarket
strategy: panic_fade
market:
slug: "will-example-corp-beat-earnings"
risk:
max_position: 5
price_floor: 0.10
price_ceiling: 0.90
max_entries_per_market: 2
loop:
interval: 30
trading_schedule:
timezone: America/New_York
trading_hours:
- days: [mon, tue, wed, thu, fri]
start: "09:30"
end: "16:00"
params:
panic_threshold: 0.10
fade_size: 5# Rejected: Polymarket US built-in strategies cannot carry a rules block
# error: Polymarket US built-in strategies do not support top-level rules
version: 1
platform: polymarket_us
strategy: mean_reversion
market:
slug: "nba-lal-bos-2026-11-04"
risk:
max_position: 1
price_floor: 0.05
price_ceiling: 0.95
loop:
interval: 30
params:
entry_low: 0.20
entry_high: 0.80
rules:
- name: exit_late
when:
all:
- field: time_to_expiry
op: "<"
value: 600
action: sell_alltrading_schedule on Kalshi
On Kalshi, a trading_schedule works with every built-in strategy. The schedule syntax is under Trading schedule in the Kalshi reference. With a built-in strategy it does this:
- Buy orders outside the allowed hours are refused and logged. The signal is checked again on later loops.
- When the schedule closes, and once at startup, the Bot cancels its own resting entry orders.
- Exits keep running. Every sell is reduce-only and capped at what this Bot owns on the side it sells, so YES and NO positions both exit. A sell with nothing to sell is refused.
Warning: spread_capture with a
trading_scheduleis not recommended. While you hold no YES, each loop posts one buy quote, then stops with an error on its first sell quote.