Rule-Level Trading Schedules
This page covers rule-level schedules in Kalshi custom strategies (platform: kalshi, strategy: custom): the keys, how they combine with a top-level schedule, what happens to orders at window edges, overnight and overlapping windows, and the format rules. A rule-level schedule can't share a strategy with composite orders or a top-level active_window; see What can be combined. For a complete strategy, see Time-varying EMA-distance entry thresholds.
A trading_schedule inside a buy_yes or buy_no rule limits when that rule may buy. It has the same shape as the top-level trading_schedule (see Trading schedule). Its main use is time-varying entry thresholds: write one entry rule per threshold, each with its own hours.
| Parameter | Type | Default | Allowed values | What it does |
|---|---|---|---|---|
trading_schedule (on a rule) | mapping | none | Only on buy_yes/buy_no rules | Weekly hours when this rule may enter |
trading_schedule.timezone | string | required | UTC, or an IANA name such as America/New_York | The timezone for this rule's windows. It is not inherited from the top level. |
trading_schedule.trading_hours | list of windows | omitted: all week | 1 to 32 windows in total, counting blackouts | The only windows when this rule may enter |
trading_schedule.blackouts | list of windows | none | Counts toward the 32 | Windows when it may not enter. Blackouts win over trading hours. |
trading_hours[].days | list of strings | required | Unique mon tue wed thu fri sat sun, lowercase | The days each window starts on (blackouts too) |
trading_hours[].start | string | required | "00:00" to "23:59", zero-padded, quoted | Window start, included |
trading_hours[].end | string | required | "00:00" to "24:00", different from start | Window end, excluded. Earlier than start wraps past midnight. |
How the two schedules combine
- A scheduled entry rule may buy only when both the top-level schedule (if you have one) and its own schedule allow the current minute.
- If its conditions match while either schedule is closed, the Bot logs a blocked decision (
outside_trading_hoursorschedule_blackout) and evaluation continues to the next rule. So you can stack entry rules with different thresholds and windows. Where windows overlap, the higher rule wins, as usual. - Two more reasons can block a scheduled entry:
schedule_unavailablewhen the schedule's timezone data can't be loaded (entries stay blocked, exits still run), andschedule_cancellation_pendingwhile the Bot is still cancelling pending entries at a window edge or after a restart. - A rule window that never overlaps the top-level schedule's open hours is accepted, but that rule can never enter.
- Exits are never blocked by time. Stops and profit rules can't carry a rule-level schedule, so they run at all hours.
Entry windows for time-varying thresholds
The week
- MonEntries 09:30–16:00
- TueEntries 09:30–16:00
- WedEntries 09:30–16:00
- ThuEntries 09:30–16:00
- FriEntries 09:30–16:00
- SatExits only
- SunExits only
Stops and take-profits run every day, at all hours.
One weekday, Monday to Friday
- open_strict_threshold: distance ≥ 0.004
- midday_looser_threshold: distance ≥ 0.0025
- Per-fill stop and take-profit
Orders at window edges
- Pending entries are cancelled at every edge. When any rule's window opens or closes, and when the Bot starts, it cancels all of its own pending entry orders. That includes orders from rules whose windows are still open. Exits and other Bots' orders stay. New entries wait until the cancel succeeds, logged as
schedule_cancellation_pending. - Resting entries also expire on the exchange. A resting entry gets an expiry at the next window edge, so an order placed at 10:59 expires at 11:00 even if the Bot is offline.
- Sells become reduce-only. With any schedule in the strategy, top-level or rule-level, every sell is reduce-only and limited to contracts this Bot bought that aren't already reserved by a resting exit. If none are left, the sell fails with "no unreserved Bot-owned inventory for this exit". Rules in a scheduled strategy can't sell contracts you bought by hand.
For how window edges behave in paper trading and backtests, see Backtests and paper trading.
Overnight and overlapping windows
When end is earlier than start, the window runs past midnight and belongs to the day it starts on. A sun window from "22:00" to "02:00" covers Sunday 22:00 to Monday 02:00. Two rules can overlap:
rules:
- name: window_a_primary
trading_schedule:
timezone: America/New_York
trading_hours:
- days: [mon, tue, wed, thu, fri, sat, sun]
start: "22:00"
end: "02:00"
when:
all:
- {field: yes_best_ask, op: ">=", value: 0.55}
- {field: yes_best_ask, op: "<=", value: 0.75}
- {field: yes_position_size, op: "==", value: 0}
action: buy_yes
size: 1
- name: window_b_fallback
trading_schedule:
timezone: America/New_York
trading_hours:
- days: [mon, tue, wed, thu, fri, sat, sun]
start: "01:00"
end: "03:00"
when:
all:
- {field: yes_best_ask, op: ">=", value: 0.50}
- {field: yes_best_ask, op: "<=", value: 0.65}
- {field: yes_position_size, op: "==", value: 0}
action: buy_yes
size: 1From 22:00 to 01:00 only window_a_primary can enter. From 01:00 to 02:00 both windows are open: an ask from 0.55 to 0.75 fires window_a_primary, and an ask from 0.50 up to 0.55 falls through to window_b_fallback. From 02:00 to 03:00 only window_b_fallback can enter.
A scheduled rule never makes the rules below it unreachable, so overlapping windows are fine. An unscheduled rule with the same conditions placed above a scheduled one does, and fails with "earlier rules make this rule unreachable". See Save-time behavior checks.
Schedule format rules
Common validator messages lists the message most of these mistakes get when you save.
timezoneisUTCor an IANA name with a slash, such asAmerica/Chicago.utc,GMT,ESTandEtc/GMT+5are rejected. Daylight saving time is handled for you.- Quote times and zero-pad them:
"09:00", not9:00."24:00"is allowed only as anend. - Omit a list you don't use.
trading_hours: []is rejected, and so istrading_schedule: {}. - The windows must leave some time open: "trading hours and blackouts leave no time for entries".
- Only
buy_yes/buy_norules take a schedule. Anything else is rejected:
# Rejected: a rule-level schedule on a stop rule (stops always run at all hours)
# error: supported only on Kalshi custom buy_yes/buy_no rules
version: 1
platform: kalshi
strategy: custom
strategy_name: Scheduled stop (rejected)
strategy_name_origin: generated
market:
series_ticker: KXBTCD
risk:
max_position: 1
price_floor: 0.20
price_ceiling: 0.80
loop:
interval: 15
rules:
- name: stop_each_fill
trading_schedule:
timezone: UTC
trading_hours:
- days: [mon, tue, wed, thu, fri]
start: "13:00"
end: "21:00"
when:
all:
- {field: yes_position_size, op: ">", value: 0}
action: sell_yes
entry_stop_loss_offset: 0.10
- name: enter_yes
when:
all:
- {field: yes_best_ask, op: ">=", value: 0.30}
- {field: yes_best_ask, op: "<=", value: 0.45}
- {field: yes_position_size, op: "==", value: 0}
action: buy_yes
size: 1