Custom Rule Entry Checks
When you save a strategy: custom strategy, Studio's behavior checks reject rules that cannot work as written: conditions that contradict each other, entries that match every state, rules that earlier rules make unreachable, and buy sizes that break your risk block. Each section shows a rejected strategy with the error text it produces, followed by a corrected version that saves; the examples use Kalshi and Polymarket. For the three checks that apply only to exits, see Exit Checks.
Contradictory conditions
A rule whose conditions can never all be true, or that compares against a value outside the field's range, can never fire. The classic case is writing cents instead of dollars:
# Rejected: the take-profit uses 60 (cents) instead of 0.60 (dollars)
# error: predicate is contradictory and can never match
version: 1
platform: polymarket
strategy: custom
strategy_name: "Cents instead of dollars (rejected)"
market:
slug: "will-example-index-close-above-6000-on-2026-12-31"
risk:
max_position: 10
price_floor: 0.05
price_ceiling: 0.95
loop:
interval: 30
rules:
- name: take_profit
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: price, op: ">=", value: 60}
action: sell_all
- name: stop_loss
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: price, op: "<=", value: 0.25}
action: sell_all
- name: buy_yes_in_band
when:
all:
- {field: position_size, op: "==", value: 0}
- {field: order_count, op: "==", value: 0}
- {field: price, op: ">=", value: 0.30}
- {field: price, op: "<=", value: 0.40}
action: buy_yes
size: 10Fixed: write the threshold in dollars.
version: 1
platform: polymarket
strategy: custom
strategy_name: "YES band with dollar thresholds"
market:
slug: "will-example-index-close-above-6000-on-2026-12-31"
risk:
max_position: 10
price_floor: 0.05
price_ceiling: 0.95
loop:
interval: 30
rules:
- name: take_profit
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: price, op: ">=", value: 0.60}
action: sell_all
- name: stop_loss
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: price, op: "<=", value: 0.25}
action: sell_all
- name: buy_yes_in_band
when:
all:
- {field: position_size, op: "==", value: 0}
- {field: order_count, op: "==", value: 0}
- {field: price, op: ">=", value: 0.30}
- {field: price, op: "<=", value: 0.40}
action: buy_yes
size: 10On Polymarket, price is the YES midpoint and the buy is a limit order at that midpoint, so order_count == 0 keeps the entry from stacking on an unfilled order. Size 10 clears the common 5-share minimum. The same check rejects bands that do not overlap, such as price >= 0.80 together with price <= 0.20, and impossible gates such as position_size < 0.
Entries that always match
An entry that is true for every possible value of the fields it reads is rejected. The usual cause is any used where all was meant: "at least 0.30 or at most 0.70" covers every price.
# Rejected: an any-group band matches every price, with no position gate
# error: entry predicate is exhaustive
version: 1
platform: kalshi
strategy: custom
strategy_name: "OR instead of AND (rejected)"
market:
series_ticker: KXHIGHNY
risk:
max_position: 2
price_floor: 0.05
price_ceiling: 0.95
loop:
interval: 30
rules:
- name: stop_loss
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: unrealized_pnl, op: "<=", value: -0.10}
action: sell_all
- name: take_profit
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: unrealized_pnl, op: ">=", value: 0.15}
action: sell_all
- name: buy_yes_in_band
when:
any:
- {field: price, op: ">=", value: 0.30}
- {field: price, op: "<=", value: 0.70}
action: buy_yes
size: 1Fixed: use all for the band and add a position gate.
version: 1
platform: kalshi
strategy: custom
strategy_name: "YES band entry with a position gate"
market:
series_ticker: KXHIGHNY
risk:
max_position: 2
price_floor: 0.05
price_ceiling: 0.95
loop:
interval: 30
rules:
- name: stop_loss
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: unrealized_pnl, op: "<=", value: -0.10}
action: sell_all
- name: take_profit
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: unrealized_pnl, op: ">=", value: 0.15}
action: sell_all
- name: buy_yes_in_band
when:
all:
- {field: position_size, op: "==", value: 0}
- {field: price, op: ">=", value: 0.30}
- {field: price, op: "<=", value: 0.70}
action: buy_yes
size: 1Other entries this check rejects: price >= 0, position_size >= 0, and price < 50 (cents again, always true for a 0 to 1 price).
Unreachable rules
If earlier rules already match every state a rule could match, that rule can never fire. Earlier skip and cancel_all rules count. Here the broad dip entry sits above the narrow one:
# Rejected: prices at or below 0.30 are caught by the rule above
# error: earlier rules make this rule unreachable
version: 1
platform: kalshi
strategy: custom
strategy_name: "Broad rule first (rejected)"
market:
series_ticker: KXHIGHNY
risk:
max_position: 2
price_floor: 0.05
price_ceiling: 0.95
loop:
interval: 30
rules:
- name: stop_loss
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: unrealized_pnl, op: "<=", value: -0.15}
action: sell_all
- name: take_profit
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: unrealized_pnl, op: ">=", value: 0.20}
action: sell_all
- name: buy_small_dip
when:
all:
- {field: position_size, op: "==", value: 0}
- {field: price, op: "<=", value: 0.50}
action: buy_yes
size: 1
- name: buy_deep_dip
when:
all:
- {field: position_size, op: "==", value: 0}
- {field: price, op: "<=", value: 0.30}
action: buy_yes
size: 2Fixed: put the narrower rule first.
version: 1
platform: kalshi
strategy: custom
strategy_name: "Two dip sizes, narrow rule first"
market:
series_ticker: KXHIGHNY
risk:
max_position: 2
price_floor: 0.05
price_ceiling: 0.95
loop:
interval: 30
rules:
- name: stop_loss
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: unrealized_pnl, op: "<=", value: -0.15}
action: sell_all
- name: take_profit
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: unrealized_pnl, op: ">=", value: 0.20}
action: sell_all
- name: buy_deep_dip
when:
all:
- {field: position_size, op: "==", value: 0}
- {field: price, op: "<=", value: 0.30}
action: buy_yes
size: 2
- name: buy_small_dip
when:
all:
- {field: position_size, op: "==", value: 0}
- {field: price, op: "<=", value: 0.50}
action: buy_yes
size: 1Studio pre-fills Max contracts per order from the largest buy size, here buy_deep_dip with 2, so both buys fit that deployment limit. If you lower it to 1 when deploying, the 2-contract buy is refused at runtime; see Size rules.
Entry sizes that break your risk block
A buy's size is checked against the strategy's risk block when you save: it must be positive, at most risk.max_position, and size × risk.price_floor must not exceed risk.max_notional when that is set. An omitted size counts as 1.
# Rejected: the entry buys 5 contracts but the strategy may hold at most 2
# error: entry size exceeds risk.max_position from an empty position
version: 1
platform: kalshi
strategy: custom
strategy_name: "Oversized entry (rejected)"
market:
series_ticker: KXHIGHNY
risk:
max_position: 2
price_floor: 0.05
price_ceiling: 0.95
loop:
interval: 30
rules:
- name: stop_loss
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: unrealized_pnl, op: "<=", value: -0.15}
action: sell_all
- name: take_profit
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: unrealized_pnl, op: ">=", value: 0.20}
action: sell_all
- name: buy_yes_cheap
when:
all:
- {field: position_size, op: "==", value: 0}
- {field: yes_best_ask, op: "<=", value: 0.30}
action: buy_yes
size: 5Fixed: size the entry within risk.max_position.
version: 1
platform: kalshi
strategy: custom
strategy_name: "Cheap YES sized within max_position"
market:
series_ticker: KXHIGHNY
risk:
max_position: 2
price_floor: 0.05
price_ceiling: 0.95
loop:
interval: 30
rules:
- name: stop_loss
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: unrealized_pnl, op: "<=", value: -0.15}
action: sell_all
- name: take_profit
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: unrealized_pnl, op: ">=", value: 0.20}
action: sell_all
- name: buy_yes_cheap
when:
all:
- {field: position_size, op: "==", value: 0}
- {field: yes_best_ask, op: "<=", value: 0.30}
action: buy_yes
size: 1The save-time check covers buy_yes and buy_no rules only. Composite orders sizes are not checked against risk.max_position, so keep them within it yourself.