Strategy Specs
A strategy spec is the structured document Studio saves for every strategy. It is the reviewable output Studio uses to turn a natural-language trading idea into something that can be backtested, reviewed, and deployed. Studio's AI writes it in YAML, you can read it on the strategy's Config tab, and you change it by asking the AI in chat.
A strategy spec is intentionally narrower than a general programming language. It expresses the strategy surface: what market to trade, what data to read, when to enter, when to exit, how much risk to take, and when to stop.
Note: This page explains what a strategy spec is and how to review one. For the full list of parameters, with every key's type, default, allowed values and venue support, see the Strategy Reference. Jump straight to your venue: Kalshi, Polymarket, Polymarket US, Kalshi Perps or Arbitrage.
Why strategy specs exist
AI is good at drafting strategy logic, but arbitrary generated code is hard to audit. Strategy specs give Studio a safer target. A spec is:
- readable enough for you to inspect,
- structured enough for Studio to validate on every save,
- constrained enough to backtest,
- portable enough to deploy,
- explicit enough to revise without losing the original thesis.
The spec is the source of truth. Studio checks every save in three steps: it parses the YAML, validates every value, and compiles the spec into the program your Bot runs. If any step fails, the change isn't saved, and the AI gets the list of problems to fix. When you deploy, Studio compiles the saved spec again, so a Bot always runs a version that passed. The Bot runs the spec, not the chat conversation.
Shape of a strategy
Kalshi, Polymarket and Polymarket US strategies share one shape. Each part of the spec answers one question:
| Question | Spec area |
|---|---|
| Which venue is this for? | platform: kalshi, polymarket or polymarket_us |
| Which market or markets? | market |
| What kind of strategy is this? | strategy: one of five built-in types, or custom |
| How often should it check? | loop.interval, in seconds (10 or more) |
| When should it act? | params for a built-in type, rules for custom |
| What outside data does it read? | edge and derived (custom strategies, on some venues) |
| How much can it risk? | risk, plus the deployment risk limits you confirm when you deploy |
| When may it trade? | trading_schedule or active_window (optional). See When a strategy may trade. |
version: 1, platform, strategy, market, risk (with max_position and price_ceiling) and loop.interval are required. A custom strategy also needs at least one rule that sells or cancels. A built-in type needs its required params keys, for example spread_floor and order_count for spread_capture. See Built-in Strategies. Studio rejects any top-level key it doesn't know.
strategy_name is the label Studio shows. strategy_name_origin records whether you, the AI or a template named the strategy. name is an old key that Studio still accepts but ignores, so use strategy_name for the label. None of these three changes how the Bot trades.
Kalshi Perps and Arbitrage strategies use their own shapes. See Kalshi Perps and Arbitrage.
Example
This Kalshi strategy follows Kalshi's Bitcoin above/below series, KXBTCD. It buys YES when Coinbase BTC has risen more than 1.2% over 15 minutes and the market's spread is 6 cents or less. It exits if that move reverses or when the market is within 30 minutes of closing. The market and every value are illustrative, not recommendations.
Each KXBTCD event holds several markets with different strike prices, and YES on the market the Bot picks pays only if BTC settles above that market's strike. These rules never compare BTC with the strike (derived metrics can). The example shows how the parts of a spec fit together. It isn't a tested way to trade BTC moves.
version: 1
platform: kalshi
strategy: custom
strategy_name: Illustrative BTC move follower
market:
series_ticker: KXBTCD
risk:
max_position: 2
price_floor: 0.08
price_ceiling: 0.92
max_entries_per_market: 1
loop:
interval: 60
edge:
btc:
provider: coinbase
symbol: BTC-USD
fields: [change_15m]
refresh: 30s
rules:
- name: flatten_before_close
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: time_to_expiry, op: "<=", value: "30m"}
action: sell_all
- name: exit_when_move_reverses
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: edge.btc.change_15m, op: "<", value: 0}
action: sell_all
- name: enter_yes_on_btc_move
when:
all:
- {field: position_size, op: "==", value: 0}
- {field: edge.btc.change_15m, op: ">", value: 0.012}
- {field: spread, op: "<=", value: 0.06}
- {field: yes_best_ask, op: "<=", value: 0.60}
- {field: time_to_expiry, op: ">", value: "30m"}
action: buy_yes
size: 1What each part does:
market.series_ticker: on every cycle, the Bot finds the event in the series that closes soonest. By default it trades that event's most liquid market.risk:max_position: 2is the most the strategy should hold in one market. Each buy's quantity comes from the rule'ssize. On Kalshi custom strategiesmax_positionpre-fills the deploy dialog's Max open contracts, which is the live cap.price_floorandprice_ceilingkeep each buy's limit price between 8 and 92 cents, but they don't skip buys outside that range (see Risk fields).max_entries_per_market: 1allows one entry order in each market, and each new market in the series starts a fresh count.loop.interval: after each cycle finishes, the Bot waits 60 seconds before it starts the next one.edge.btc: reads Coinbase BTC-USD.change_15mis a fraction, so0.012means +1.2%. Withrefresh: 30sand a 60-second loop, the Bot fetches fresh data every cycle and can ride out one failed fetch before it starts skipping cycles. See Edge data references.rules: checked top to bottom on every cycle, and the first rule whosewhenblock matches acts. On Kalshi, an entry held back by a trading schedule, or in an older series market the Bot keeps only to manage its position, lets later rules run (see Custom Rules). The exits come first and only fire while you hold a position. The entry only fires while you're flat, and its ownyes_best_askcondition is what keeps it from buying above 60 cents.
The first time you deploy, Studio pre-fills the deploy dialog from this spec: 1 contract and $1 per order, and 2 open contracts per market. When you update a Bot that's already deployed, the dialog starts from that Bot's current limits instead. Every order has to pass both the spec's checks and those limits. See Strategy risk fields vs deployment risk limits.
This example is based on the first prompt under Good strategy prompts. Two parts of that prompt don't translate directly:
- "While the market mid has moved less than 4 cents." Custom rules see only the current market snapshot and have no memory of earlier prices, so this can't be written as a rule. A good spec leaves out what it can't express and says so, instead of inventing a key.
- "Cap exposure at $300." The closest key is
risk.max_notional, a cap on the dollars spent on entries in each market during one deployment run. It isn't a cap across markets, and selling doesn't free budget. With one 1-contract entry per market, this example spends less than $1 per market anyway.
How a rule reads
Each rule has a name, a when block and an action. when holds either all (every condition must hold) or any (at least one must hold). A condition compares a field with a number in value, or with another field in value_field, using <, >, <=, >=, == or !=. Always quote the operator (op: ">="), because an unquoted >, >= or != breaks the YAML. time_to_expiry takes a duration string such as "30m". Every other field takes a plain number. If either side of a comparison is unavailable on a cycle, such as yes_best_ask on a book with no YES asks, the condition is false whatever the operator, != included.
The actions are buy_yes, buy_no, sell_yes and sell_no (Kalshi and regular Polymarket only), sell_all, cancel_all and skip. skip places no order, but it still counts as the match for that cycle. A buy's size is a number of contracts (shares on Polymarket), defaults to 1, and can't be larger than risk.max_position. See Custom Rules for every field and action.
Market selectors
Strategies should identify their market scope as precisely as possible. Each venue has its own selectors, and using another venue's selector is a validation error.
| Venue | Selector | What the Bot trades |
|---|---|---|
| Kalshi | ticker | One market. It never moves to another. |
| Kalshi | event_ticker | The first open market Kalshi lists for that event, checked again every cycle. Best for single-market events. |
| Kalshi | series_ticker | The soonest-closing event of a recurring series. selection: most_liquid (the default) trades one bracket; selection: all trades every bracket. |
| Kalshi | query | A market from the first open event whose title contains the text, searched live every cycle. It can't be backtested. |
| Polymarket | slug, condition_id, or yes_token_id with no_token_id | One market. |
| Polymarket | series_slug or recurring | A rolling series. The Bot moves to the next market when one ends. |
| Polymarket US | slug | One market. event_slug and query also work, but they pick a market once when the Bot starts. |
Prefer an exact selector when you already know the market. Search-style selectors are for exploring: a Kalshi query match can change between cycles. On regular Polymarket, event_slug and query are rejected on their own, and a Bot that still has one next to a concrete selector stops at startup. Ask Studio to replace them with a concrete market before you backtest or deploy.
Caps such as max_entries_per_market and max_notional apply to each market separately. On a Kalshi series each new market starts fresh, and with selection: all every bracket gets its own caps, so your exposure grows with the number of brackets. See Kalshi market selection.
Strategy types
Studio can draft five built-in strategy types, plus custom:
| Strategy type | What it does |
|---|---|
spread_capture | Quotes a YES bid and a YES ask around the mid when the spread is wide enough. |
mean_reversion | Buys YES when YES is cheap, or NO when YES is expensive, then exits near a target price. |
panic_fade | Buys YES after a sharp drop from the recent 5-minute high, then exits on a small recovery. |
observation_momentum | Buys in the direction of a recent price move, with a size that grows while the move continues. It has no exit of its own. |
pre_announcement_drift | Enters once in a window before the market closes, and can exit just before close. |
custom | Runs your own list of rules instead of a fixed template. |
A built-in type runs fixed logic that you tune with a flat params map. It never runs rules or reads edge data. Kalshi and regular Polymarket accept a rules block on a built-in type but ignore it, and Polymarket US rejects it. An edge block is ignored on Kalshi and Polymarket US, and regular Polymarket rejects edge on every strategy type.
Warning: On Kalshi, some built-in exits sell YES whatever you hold. The exits of
mean_reversionandpre_announcement_driftdon't close a NO position, and themean_reversionexit adds to it. Apanic_fadeexit, or apre_announcement_driftexit after a partial fill, can sell YES the Bot never bought, which opens a NO position. Read the Built-in Strategies caveats for your venue before you deploy one.
Use custom when the behavior needs explicit conditions. Some features work only with custom: edge data, derived metrics, max_notional, max_loss (Kalshi only), and active_window. See Custom Rules.
Risk fields
Every spec has a risk block, and it should be one of the first things you review. These are the keys:
| Field | What it limits | Where it works |
|---|---|---|
max_position | The most the strategy should hold in one market, in contracts (shares on Polymarket). A whole number above 0. Required. On Kalshi custom strategies the live cap is the deploy dialog's Max open contracts, which starts from this value. | All three venues |
price_floor, price_ceiling | The price band for buys, in dollars from 0 to 1. price_ceiling is required. | All three venues |
max_entries_per_market | Entry orders in each market | Kalshi (any strategy type); Polymarket (custom only) |
max_notional | Dollars spent on entries in each market | Kalshi and Polymarket, custom only |
max_portfolio_positions | Blocks buys into a new market once this many markets are active: every market your Kalshi account holds a position in (manual trades and other Bots included), plus markets where this strategy has a resting order. | Kalshi |
max_loss | Losses across the whole deployment run. At the limit the Bot cancels its orders, sells its own inventory and stops. Manual trades or another Bot's orders in the same market also trigger this halt. | Kalshi, custom only |
On Kalshi custom strategies the price band clamps a buy's limit price instead of skipping the buy: an ask above the ceiling becomes a resting bid at the ceiling. The floor doesn't block cheap buys either: an ask below price_floor is sent at the floor price and can still fill at the lower ask. To skip entries outside a price range, put the range in the entry rule, as the example does with yes_best_ask. For a lower bound, add a condition such as yes_best_ask >= 0.08.
Other protections are written as rule conditions, or come from the deployment:
- Spread limits: a
spreadcondition on the entry, such asspread <= 0.06. A one-sided book reads as a wide spread, so the gate fails closed. - Thin markets: a
spreadlimit already fails closed on a one-sided book. Add avolumecondition if you also want a minimum traded volume. Seevolumefor how live and backtest counts differ. - Stop opening near close: a
time_to_expirycondition on the entry, plus an exit rule for positions you still hold near close. On Kalshi, atrading_schedulecan also block new entries during set hours. See When a strategy may trade. - Pulling resting orders: a rule with
action: cancel_allcancels resting orders when its conditions hold. On Kalshi that means this Bot's orders in that market. On Polymarket and Polymarket US it means all your open orders in that market, manual ones included. - Stale data: edge data fails closed on its own. See Edge data references.
- Order size and daily volume: on Kalshi and Polymarket US, the deployment risk limits in the deploy dialog cap contracts per order, open contracts, dollars per order and daily notional (on Kalshi, counted separately for each market). A buy larger than a limit is refused, not shrunk. Regular Polymarket Bots don't read these limits, so the spec's
riskblock and your wallet balance are the only size controls there.
Warning: The deploy dialog also shows a Max daily loss field, but no strategy Bot enforces it today. Don't rely on it as a loss stop. On Kalshi custom strategies, use
risk.max_loss.
AI agents should not remove risk fields to make a backtest look better. If a backtest improves only after risk limits are loosened, that is a result to explain, not a result to hide.
max_entries_per_market
max_entries_per_market works on Kalshi for every strategy type, and on regular Polymarket for custom strategies. Polymarket US doesn't support it. On Kalshi it isn't recommended with panic_fade, pre_announcement_drift or spread_capture: those strategies carry on as if a skipped entry had been placed, so a later exit or sell quote can sell YES you never bought and open a NO position. See risk.max_entries_per_market on Kalshi.
On Kalshi, an entry order uses one allowance once it has any fill, while it's still resting, or if its final state is unknown. An order that ends cancelled or rejected with no fill gives its allowance back. Closing the position doesn't restore the allowance; a new market does, such as the next market in a series. The count belongs to one deployment run: deploying or updating the Bot starts it again from zero, but a restart of the same run doesn't.
On Polymarket the count comes from your account's trade history in that market, so earlier runs and manual trades count too. It isn't recommended with a rolling Polymarket series: once one market reaches the cap, later markets in the series stay blocked until you redeploy. See Kalshi and Polymarket for the details.
When a strategy may trade
Two optional blocks limit when a strategy acts. Use one or the other: Studio rejects a spec that has both.
trading_scheduleworks on Kalshi, with any strategy type. It sets weeklytrading_hoursandblackoutsin a namedtimezone, such asAmerica/New_York. It blocks only new entries. Exits,cancel_allandmax_losskeep running. While a schedule is present, a sell only sells what this deployment run bought, or adopted when you deployed. It can be backtested.active_windowworks in custom strategies on Kalshi and Polymarket. It is one period with astart, anendor both, written as timestamps with an offset, such as2026-10-15T18:30:00-04:00. Beforestartthe Bot does nothing. Atendit cancels resting orders once and stops evaluating, exit rules included. On Polymarket that cancel covers all of your open orders in that market, including manual ones. It never sells your positions. It can't be backtested.
See Trading schedule and Active window for the full format.
Edge data references
A custom strategy can read supported outside data next to the venue's order book. You declare each source as a named alias under edge, list the fields your rules may read, and read them in conditions as edge.<alias>.<field>.
| Provider | Selector | Example | What it provides | Refresh default (minimum) |
|---|---|---|---|---|
coinbase | symbol | BTC-USD | Crypto prices, returns, moving averages and MACD | 5s (1s, or 5s if you list a candle field such as change_15m) |
nws | station, a 4-letter weather station code | KMDW (Chicago Midway) | Station observations, forecasts and active alerts | 5m (60s) |
economic_calendar | event | FOMC, CPI or FOMC,CPI | Time until the next and since the previous announcement | 15m (5m) |
You can declare up to 8 aliases. An alias name is up to 32 lowercase letters, digits and underscores, starting with a letter.
Edge data works in custom strategies on Kalshi and Polymarket US. Regular Polymarket rejects the edge block. derived metrics, such as a forecast minus the market's strike, work only in Kalshi custom strategies.
A strategy should define exactly how edge data affects behavior. This fragment reads an NWS forecast and gates an entry on it. The station and thresholds are illustrative, not recommendations:
edge:
chi:
provider: nws
station: KMDW # Chicago Midway
fields: [forecast_high_f, precip_prob]
refresh: 5m # NWS allows 60s or slower
rules:
- name: enter_yes_on_warm_forecast
when:
all:
- {field: position_size, op: "==", value: 0}
- {field: edge.chi.forecast_high_f, op: ">=", value: 80}
- {field: edge.chi.precip_prob, op: "<", value: 0.40} # a fraction: 0.40 = 40%
- {field: spread, op: "<=", value: 0.05}
action: buy_yes
size: 1A complete strategy also needs an exit rule and the other required blocks. forecast_high_f is the next daytime forecast high in °F, so in the evening it refers to tomorrow.
If edge data is unavailable or stale, the safe behavior is to avoid opening new risk. The Bot fails closed:
- If an alias can't be refreshed, the Bot reuses its last good values for up to three refresh periods. After that it skips the whole cycle for that market, including exit rules and the
max_losscheck. On Kalshi it also cancels its resting orders there. If the first fetch after the Bot starts fails, there is nothing to reuse, so the cycle is skipped right away. - A single unavailable value, such as a late Coinbase candle, doesn't skip the cycle. Conditions that compare it are false, whatever the operator.
- A few NWS fields fail open instead. For example,
alert_activereads 0 if the alerts lookup fails, so don't use it as your only safety exit. - Every declared alias is kept fresh even if no rule reads it, and a failing one still skips cycles. Declare only what your rules use.
See Edge Data for every provider field, refresh minimums and backtest support.
Good strategy spec habits
- Keep strategy names descriptive.
strategy_namecan be up to 80 characters. - Review the
riskblock first, then the exits. - Write prices in dollars from 0 to 1:
0.60, not60. Studio rejectsprice_ceiling: 60. - Write fractions as fractions. A Coinbase
change_15mthreshold of0.012means +1.2%.1.2saves without an error, but it means +120%. - Write numbers as plain numbers, with no quotes or units:
interval: 60, notinterval: "60s", which Studio rejects. Edgerefreshandtime_to_expiryvalues are the exceptions, because they take units. Use whole numbers formax_position,max_entries_per_market,max_portfolio_positionsandloop.interval. Studio drops decimals there without an error, somax_entries_per_market: 0.5turns the cap off. - Put exits above entries. Gate exits on holding a position (
position_size > 0) and entries on being flat (position_size == 0). - Prefer simple thresholds before complex formulas.
- Check spelling inside blocks. Studio rejects unknown top-level keys, but a misspelled key inside
market,risk,loop,active_window,paramsor an edge alias is ignored without an error. - Use one market family per strategy. Each Studio chat is tied to one venue; cross-venue strategies use Arbitrage.
- Backtest every material change, and know what can't be backtested: Polymarket US strategies, a Kalshi
market.query,active_window, some edge data (Coinbasebid,ask,spreadandspread_bps, and NWS stations outside the supported list), and some Polymarket features. See Backtest and Edge Data. - Keep live deployment aligned with the backtested spec. Set the deploy dialog's limits so the spec's order sizes fit.
Here is what a spelling mistake looks like. These keys aren't in the spec format, so Studio ignores them, and the required values they were meant to set are missing:
# Rejected: max_position_usd and interval_seconds are not keys, so the required max_position and interval are missing
# error: risk.max_position: must be > 0
# error: loop.interval: must be > 0
version: 1
platform: kalshi
strategy: custom
strategy_name: Misspelled keys (rejected)
market:
series_ticker: KXBTCD
risk:
max_position_usd: 300 # not a key: ignored
price_ceiling: 0.60
loop:
interval_seconds: 60 # not a key: ignored
rules:
- name: flatten_before_close
when:
all:
- {field: position_size, op: ">", value: 0}
- {field: time_to_expiry, op: "<=", value: "30m"}
action: sell_all
- name: enter_yes
when:
all:
- {field: position_size, op: "==", value: 0}
- {field: yes_best_ask, op: "<=", value: 0.40}
action: buy_yes
size: 1These typos hit required keys, so the save failed. A misspelled optional key, such as max_notinal: 5, saves without an error, and the Bot runs with no spend cap.
What not to put in strategy specs
Do not use strategy specs as a place to encode private implementation details. They should not need internal service names, database structure, cloud topology, or proprietary deployment plumbing.
Also keep these out:
- Credentials. Never put API keys, secrets or private keys in a spec or in chat. Venue credentials go through Studio's credential flow when you deploy. See Credential boundary.
- Keys the format doesn't have. Fields such as
risk.max_position_usdorloop.interval_secondsaren't settings, and Studio ignores them. If part of an idea can't be expressed, the spec should leave it out, and the AI should tell you. - Notes as extra keys. A top-level key like
description:is rejected. Keep your thesis in the strategy name and the chat.
The public contract is strategy behavior. You and your AI agent should be able to understand the strategy without reading Turbine's internals.
Where to find every parameter
| Page | What's there |
|---|---|
| Strategy Reference | The shape of a spec, feature support by venue, units, and how validation errors look. |
| Kalshi | Selectors, series and brackets, risk keys, loop, active_window and trading_schedule. |
| Polymarket | Selectors, rolling series, shares and ticks, and backtest limits. |
| Polymarket US | Slug selectors and the supported subset of risk keys and rules. |
| Built-in Strategies | Every params key for the five built-in types. |
| Custom Rules | Conditions, fields, operators, actions and the logic checks Studio runs. |
| Advanced Kalshi Rules | Composite maker orders, per-fill profit targets and stops, and rule-level schedules. |
| Edge Data | Coinbase, NWS and economic calendar fields, and strike-distance metrics. |
| Kalshi Perps | Perp markets, sizing and leverage, take-profit and stop-loss, and rules. |
| Arbitrage | Venue legs, outcome mapping, net-edge limits and fees. |
Before you deploy, read Risk & Limits. For how to ask Studio for a strategy, see Prompting.