API

Get Started

WelcomeQuickstartGuidesChangelogSDKs and ToolsAdditional InfoFAQ

Authorization

POSTCreate personal access token

List Accounts

GETGet accounts

Account Details

GETGet account portfolio v2GETGet history

Tax Lot Selling

GETGet unrealized tax lotsGETGet unrealized tax lots for symbolGETGet unrealized tax lots csv

Instrument Details

GETGet all instrumentsGETGet instrumentGETSearch bonds

Market Data

POSTGet quotesGETGet bond detailsPOSTGet option expirationsPOSTGet option chainGETGet bars v2GETGet bars v2 with aggregation

Orders

POSTPreflight single legPOSTPreflight multi legPOSTPlace orderPUTReplace orderPOSTSearch ordersPOSTPlace multileg orderGETGet orderDELETECancel order

Option Details

GETGet option greeksPOSTGet strategy quote

Event Contracts

POSTGet event summaryGETGet event categoriesGETGet event details
HelpFeedback

Get Started

Trading 15-minute prediction markets

Learn how 15-minute markets work, how to find and price the current window, and how to trade it against the clock. Then trade with ready-made prompts in your AI assistant, or set your trade criteria in a Python script that runs on its own and trades every window that meets them.

This guide builds on Trading prediction markets, which covers events, outcomes, YES/NO contracts, and the order flow in detail. Read that first if you haven't traded an event contract through the API before.

⚠️ Before you automate anything

A 15-minute contract can go from $0.60 to $0.04 in five minutes, and it settles at $0.00 if you're wrong. An automated strategy can repeat a losing trade many times a day, and past results don't predict future ones. The script in this guide starts in dry-run mode and buys one contract per window. Keep it in dry-run mode until you trust it, start small, and only trade money you can afford to lose. This guide is educational and isn't investment advice.

How 15-minute markets work

A 15-minute market asks one question about a short window: will this asset's price be at or above where it started when the window ends? Every 15 minutes a new window opens for each market, so each one is a back-to-back series of small events rather than a single long-running one.

  • Each window is its own event, with its own eventSymbol, such as KALSHI.KXBTC15M-26OCT161345 for the Bitcoin window that closes at 1:45 PM ET on October 16, 2026.
  • An "Up or Down" window has a single outcome whose title gives the target price, for example Target Price: $80,811.49. The target is the asset's price at the start of the window.
  • The outcome has a YES contract (the price finishes at or above the target) and a NO contract (it finishes below). Exactly one of them pays $1.00.

The Bitcoin market, for example, resolves YES if the 60-second average of the CF Benchmarks Bitcoin Real Time Index (BRTI) just before the window closes is at least the 60-second average just before it opened. Each market's rules field states its exact resolution source and method, so read it for any market you trade.

Available markets

At the time of writing, these series run 15-minute windows. The series ticker is the part of the event symbol between KALSHI. and the first -. The lineup changes over time, so query the API (shown below) for the current list.

CategoryMarkets (series ticker)
CryptoBTC (KXBTC15M), ETH (KXETH15M), SOL (KXSOL15M), XRP (KXXRP15M), DOGE (KXDOGE15M), BNB (KXBNB15M), HYPE (KXHYPE15M), NEAR (KXNEAR15M), ZEC (KXZEC15M)
CommoditiesGold (KXGOLD15M), silver (KXSILVER15M), platinum (KXPLATINUM15M), palladium (KXPALLADIUM15M), copper (KXCOPPER15M), WTI oil (KXWTI15M), natural gas (KXNATGAS15M)
FinancialsEUR/USD (KXEURUSD15M), GBP/USD (KXGBPUSD15M), USD/JPY (KXUSDJPY15M), USD/CAD (KXUSDCAD15M), AUD/USD (KXAUDUSD15M)

Reading the symbols

Every symbol for a window is built from the series and the time the window closes, in US Eastern time:

IdentifierExample
EventKALSHI.KXBTC15M-26OCT161345 (series, then YYMMMDD and the close time HHMM in ET)
OutcomeKXBTC15M-26OCT161345-45 (the suffix is the close minute)
YES / NO contractsKALSHI.KXBTC15M-26OCT161345-45.Y / …-45.N
Price history IDsKALSHI.KXBTC15M-26OCT161345-EVENT and KALSHI.KXBTC15M-26OCT161345-45.Y-EVENTCONTRACT

You could build these from the clock, but don't: look each window up with the API so your code follows any change to the naming and never trades a window that doesn't exist.

The life of one window

Every window follows the same timeline. Times below are for the window that closes at 1:45 PM ET (17:45 UTC):

Time (UTC)Timeline fieldWhat happens
17:30:00openTimeTrading opens. The target price is set from the preceding minute's average.
17:45:00closeTimeTrading stops. The next window is already open.
17:50:00expectedExpirationTimeThe result is determined.
17:50:01settlementTimeWinning contracts pay $1.00 (a one-second settlementDelaySeconds).
A week laterlatestExpirationTimeThe latest the result can be determined if expiration is delayed.

Because one window opens the moment the last one closes, the market is always trading. Your code needs to treat each window as a short-lived event: find it, trade it, and move on to the next one.

Set up

The examples in this guide use version 0.1.27 or later of the Public Python SDK. You also need an API secret key and your brokerage account ID (from Get accounts). Install the SDK:

Install the Python SDKbash
pip install "publicdotcom-py>=0.1.27"

The Python examples share this client. The SDK generates and refreshes access tokens from your API secret key:

Python SDKpython
from public_api_sdk import (
    ApiKeyAuthConfig,
    PublicApiClient,
    PublicApiClientConfiguration,
)

client = PublicApiClient(
    ApiKeyAuthConfig(api_secret_key="YOUR_API_SECRET_KEY"),
    config=PublicApiClientConfiguration(
        default_account_number="YOUR_ACCOUNT_ID"
    ),
)

Prefer REST?

Every SDK call in this guide wraps a REST endpoint, so you can make the same requests from any language. Authenticate with an access token and use these endpoints. Field names are camelCase in REST and snake_case in Python, so closeTime is close_time. Trading prediction markets shows the event contract requests in both REST and Python.

  • wait_for_current_event: Get event summary
  • get_event_details, wait_for_settlement: Get event details
  • get_quotes: Get quotes
  • get_event_contract_bars: GET /userapigateway/historicdata/event-contracts/{eventId}/bars/{period}
  • preflight_buy_event_contract: Preflight single-leg order
  • buy_event_contract: Place order
  • wait_for_fill, wait_for_terminal_status: Get order
  • cancel: Cancel order

Find the current window

Pass a series ticker, with the market's category and subcategory, to wait_for_current_event. It returns the window that's trading now, and if none is open yet, it waits for the next one:

Python SDKpython
event = client.wait_for_current_event(
    "KXBTC15M", category="Crypto", subcategory="BTC"
)
print(event.event_symbol)     # KALSHI.KXBTC15M-26OCT161345
print(event.title)            # BTC Up or Down - 15 minutes
print(event.resolution_time)  # 2026-10-16 17:45:00+00:00
  • resolution_timeis the window's close time. Once it passes, call wait_for_current_event again to pick up the next window.
  • symbols on the event lists related instruments (for Bitcoin, BTC-CRYPTO and Bitcoin ETFs), not the window's contract symbols. Get those from event details.
  • To browse every 15-minute window that's open across all markets, call get_event_summary with a FIFTEEN_MINUTES frequency filter.

Read the window's rules and timeline

Pass the window's event symbol to get_event_details for its target price, rules, exact timeline, and contract symbols. A 15-minute window has a single outcome, which details.outcome returns with its YES and NO contracts, the seconds left until close, and an is_tradable check:

Python SDKpython
details = client.get_event_details(event.event_symbol)
outcome = details.outcome
yes, no = outcome.yes, outcome.no

print(outcome.title)
print(f"YES {yes.symbol}: bid {yes.bid} / ask {yes.ask}")
print(f"NO  {no.symbol}: bid {no.bid} / ask {no.ask}")
print(f"{outcome.seconds_left}s left, tradable={outcome.is_tradable}")

This is the full set of fields the API returns for the window, in the API's camelCase (rules trimmed):

Event detailsjson
{
  "eventSymbol": "KALSHI.KXBTC15M-26OCT161345",
  "exchange": "KALSHI",
  "resolved": false,
  "halted": false,
  "title": "BTC Up or Down - 15 minutes",
  "category": "Crypto",
  "volume": "1040894.68",
  "subcategories": ["BTC"],
  "cftcContract": {
    "contractTermsUrl": "https://assets.kalshi.com/contract_terms/CRYPTO.pdf",
    "prohibitions": [
      "Persons who are employed by any of the Source Agencies are not permitted to trade on the Contract.",
      "Persons who hold any material, non-public information on the Underlying are not permitted to trade on the Contract."
    ],
    "resolutionSources": [
      { "name": "CF Benchmarks", "url": "https://www.cfbenchmarks.com/" }
    ]
  },
  "outcomeCount": 1,
  "outcomes": [
    {
      "outcomeId": "KXBTC15M-26OCT161345-45",
      "title": "Target Price: $80,811.49",
      "rules": "If the simple average of the sixty seconds of CF Benchmarks' BRTI before 1:45 PM EDT on Oct 16, 2026 is at least the simple average of the sixty seconds of CF Benchmarks' BRTI before 1:30 PM EDT on October 16, 2026, then the market resolves to Yes. ...",
      "volume": "1040894.68",
      "state": "STATE_OPEN",
      "timeline": {
        "openTime": "2026-10-16T17:30:00+00:00",
        "closeTime": "2026-10-16T17:45:00+00:00",
        "expectedExpirationTime": "2026-10-16T17:50:00+00:00",
        "latestExpirationTime": "2026-10-23T17:45:00+00:00",
        "settlementTime": "2026-10-16T17:50:01+00:00",
        "settlementDelaySeconds": 1
      },
      "settledOutcome": "SETTLED_OUTCOME_UNSPECIFIED",
      "trading": "BUY_AND_SELL",
      "contracts": [
        {
          "symbol": "KALSHI.KXBTC15M-26OCT161345-45.Y",
          "predictedOutcome": "YES",
          "bid": "0.67",
          "ask": "0.68",
          "last": "0.68",
          "openInterest": "275290.67",
          "probability": "0.68"
        },
        {
          "symbol": "KALSHI.KXBTC15M-26OCT161345-45.N",
          "predictedOutcome": "NO",
          "bid": "0.32",
          "ask": "0.33",
          "last": "0.32",
          "openInterest": "275290.67",
          "probability": "0.32"
        }
      ]
    }
  ]
}

Before trading a window, check that:

  • outcome.is_tradable is True: the outcome's state is STATE_OPEN and its trading mode is BUY_AND_SELL. Also check that details.halted is False.
  • Enough time is left (outcome.seconds_left) for your order to fill and, if you plan to exit early, to sell.
  • You've read the rules and the contract terms, and none of the prohibitions apply to you.

Daily-change fields such as dailyGainValue are left out for 15-minute contracts, because a window never lasts a day.

Watch prices during the window

Event details carry a pricing snapshot, but for trading decisions use live quotes, which add the size available at the bid and ask. Quote both contracts in one call:

Python SDKpython
from public_api_sdk import InstrumentType, OrderInstrument

quotes = client.get_quotes(
    [
        OrderInstrument(symbol=yes.symbol, type=InstrumentType.EVENTCONTRACT),
        OrderInstrument(symbol=no.symbol, type=InstrumentType.EVENTCONTRACT),
    ]
)
for quote in quotes:
    spread = quote.ask - quote.bid
    print(
        f"{quote.instrument.symbol}: bid {quote.bid} x {quote.bid_size}, "
        f"ask {quote.ask} x {quote.ask_size}, spread {spread}"
    )

Things to expect from 15-minute prices:

  • They move fast. In one observed Bitcoin window, the YES contract opened at $0.57 and traded at $0.04 five minutes later. Poll every few seconds while you hold a position.
  • Busy markets quote in tenths of a cent, such as a bid of 0.026 and an ask of 0.027. Parse prices as decimals, not two-decimal strings.
  • Quiet markets have wide spreads. A spread of $0.10 or more is common in thinly traded series. Check the spread and ask size before you buy.
  • The two sides mirror each other. The YES ask and the NO bid add up to about $1.00, so buying NO at $0.33 is roughly the same trade as selling YES at $0.67.

Price history inside a window

get_event_contract_bars returns 5-minute bars for the DAYperiod, so a window has at most three. Pass the window's -EVENTID and the contract's -EVENTCONTRACT symbol:

Python SDKpython
from public_api_sdk import EventContractBarPeriod

charts = client.get_event_contract_bars(
    "KALSHI.KXBTC15M-26OCT161330-EVENT",
    EventContractBarPeriod.DAY,
    ["KALSHI.KXBTC15M-26OCT161330-30.Y-EVENTCONTRACT"],
)
for bar in charts.charts[0].bars:
    print(bar.timestamp, bar.open, bar.high, bar.low, bar.close)

# 2026-10-16T13:15:00-04:00 0.5700 0.5700 0.0400 0.0440
# 2026-10-16T13:20:00-04:00 0.0430 0.2900 0.0210 0.0350

Bars are too coarse to time a trade inside a window. They're useful after the fact, for reviewing how windows typically move when you tune your criteria.

Know your costs and break-even

Run a preflight before each order. Preflight doesn't place anything; it returns the order value plus fees. preflight_buy_event_contractprices a buy at the contract's ask, rounded up to a whole cent:

Python SDKpython
from decimal import Decimal

preview = client.preflight_buy_event_contract(yes, Decimal("5"))
print(f"Order value {preview.order_value}, estimated cost {preview.estimated_cost}")

At the time of writing, preflight shows a $0.01 commission and a $0.01 exchange fee per contract, so 5 contracts at a $0.69 ask come to an estimated $3.55. Fees can change, so always read them from preflight rather than hard-coding them.

Fees matter more here than on longer markets, because each trade is small and you may make dozens a day. If you buy at price p and hold to settlement, your cost per contract is p + fees, and you break even only if you're right more often than that number:

Entry priceCost with $0.02 feesProfit if rightLoss if wrongBreak-even win rate
$0.30$0.32$0.68$0.3232%
$0.50$0.52$0.48$0.5252%
$0.70$0.72$0.28$0.7272%
$0.90$0.92$0.08$0.9292%

Buying the favorite at $0.90 wins often but makes $0.08 when it does and loses $0.92 when it doesn't, so a single upset wipes out eleven wins. Selling before settlement pays the fees a second time. Build these numbers into your criteria.

Place orders against the clock

Orders use the same endpoints as any event contract. What changes is how you handle time:

  • Use limit orders.Set the limit at or slightly above the ask when buying and at the bid when selling, so a fast market can't fill you far from the quote.
  • Round limit prices to whole centsunless you've confirmed a market accepts finer increments, even when quotes show tenths of a cent.
  • Cancel unfilled orders yourself.Don't rely on an order expiring on its own before the window closes. Give each order a short timeout, then cancel what's left and confirm the cancel.
  • Stop entering well before the close. Leave time for the fill, and for an exit if you plan one.
  • Check before you resend.If a request fails and you're not sure it went through, look the order up by its ID before sending another.

buy_event_contractplaces a limit buy at the contract's ask, rounded up to a whole cent. Wait a set time for the fill, then cancel whatever is left. The filled quantity may be a partial fill:

Python SDKpython
from public_api_sdk import WaitTimeoutError

order = client.buy_event_contract(yes, Decimal("5"))
try:
    filled = order.wait_for_fill(timeout=20)
except WaitTimeoutError:
    order.cancel()
    filled = order.wait_for_terminal_status(timeout=15)

print(f"Order {order.order_id}: {filled.status}, {filled.filled_quantity} filled")

After the window closes

About five minutes after the close, the outcome settles. wait_for_settlementwaits until the outcome's state is STATE_SETTLED, and won tells you whether a contract paid out. It returns Nonewhen the window didn't settle as a plain YES or NO:

Python SDKpython
result = client.wait_for_settlement(event.event_symbol, timeout=600)
won = result.won(yes)
if won is None:
    print(f"Settled {result.settled_outcome}; check your account for the payout")
else:
    print("YES won" if won else "YES lost")

A settled window's event details look like this (trimmed):

Settled event detailsjson
{
  "eventSymbol": "KALSHI.KXBTC15M-26OCT161315",
  "resolved": true,
  "title": "BTC 15 min · $81,322.44 target",
  "outcomes": [
    {
      "outcomeId": "KXBTC15M-26OCT161315-15",
      "title": "Target Price: $81,322.44",
      "state": "STATE_SETTLED",
      "settledOutcome": "SETTLED_OUTCOME_NO",
      "trading": "DISABLED",
      "contracts": [
        {
          "symbol": "KALSHI.KXBTC15M-26OCT161315-15.Y",
          "predictedOutcome": "YES",
          "bid": "0.00",
          "ask": "1.00",
          "last": "0.001"
        },
        {
          "symbol": "KALSHI.KXBTC15M-26OCT161315-15.N",
          "predictedOutcome": "NO",
          "bid": "0.00",
          "ask": "1.00",
          "last": "0.999"
        }
      ]
    }
  ]
}
  • Here Bitcoin finished below its target, so NO holders received $1.00 per contract and YES holders received nothing.
  • The bid and ask of a settled contract read $0.00 and $1.00. They're placeholders, not prices you can trade at.
  • SETTLED_OUTCOME_OTHERmeans the result wasn't a plain YES or NO. Check your account for the payout.

Trade from your AI assistant with the Public MCP

You don't need to write code to trade 15-minute markets. Connect your Public account to Claude, ChatGPT, Perplexity, or another assistant through the Public hosted MCP server (https://mcp.public.com/mcp), and the assistant can make every call in this guide for you: finding the current window, reading its rules, quoting it, running preflight, and placing and cancelling orders.

An assistant only acts when you send it a message. It can't watch a window on its own, so each prompt is one pass: it checks the market, reports back, and trades if your rules say to. That suits trading a few windows with you approving each order. To trade every window without you, use the auto-trader script in the next section.

Each prompt below works on its own. Replace the market, prices, and sizes with your own. If you have more than one brokerage account, name the one to use, because the assistant will otherwise ask.

See what’s trading now

List every open 15-minute window with its target, time left, and prices:

Prompttext
Using my Public account, list every 15-minute prediction market
trading right now. For each one, show a table with: the market, its
event symbol, the target price, minutes until the window closes, the
YES bid and ask, and the NO bid and ask. Sort by minutes left.

Size up one window

Get the rules, the market’s implied odds, and the real cost of a trade before you decide:

Prompttext
Using my Public account, look up the 15-minute Bitcoin window that is
trading now. Tell me:
- The target price, and how the result is decided (from the rules)
- How many minutes are left until it closes
- The YES and NO bid, ask, spread, and the size available at the ask
- The implied probability of each side
- What 5 contracts of the favored side would cost, from a preflight
  (don't place an order), and the win rate I'd need to break even
  after fees

Trade one window with your rules

Spell out your entry rules, and have the assistant check every one, preflight the order, and wait for your go-ahead:

Prompttext
Using my Public account, check the 15-minute Bitcoin window that is
trading now and trade it only if every rule below passes:
- At least 3 and at most 10 minutes are left before it closes
- The event isn't halted and the outcome is open for buying and selling
- Buy whichever side is priced higher, and only if its ask is
  between $0.55 and $0.85
- The spread on that side is $0.03 or less
- Size: 5 contracts, and no more than $5.00 total including fees

If any rule fails, don't trade; tell me which rule failed. If they all
pass, run a preflight for a LIMIT buy at the ask rounded up to the
cent, show me the contract symbol, side, price, quantity, and estimated
cost, and wait for me to reply "confirm" before placing the order.
After placing it, check the order status. If it hasn't filled within
30 seconds, cancel it and confirm the cancellation.

Manage an open position

Check your 15-minute positions and apply your exit rules before the window closes:

Prompttext
Using my Public account, show my open 15-minute event contract
positions. For each one, show the current bid, minutes left in its
window, and my gain or loss. Then apply these exit rules:
- If the bid is $0.95 or higher, sell at the bid (take profit)
- If the bid is $0.30 or lower, sell at the bid (stop loss)
- Otherwise, hold to settlement

Use LIMIT orders, show me each sell before placing it, and wait for
me to reply "confirm".

Review your results

Score the windows you traded once they settle:

Prompttext
Using my Public account, find my 15-minute event contract orders from
today. For each window I traded, show: the market, the window's close
time, the side I bought, my average price and quantity, how the window
settled (or whether I sold early), and my profit or loss including
fees. Finish with my total P&L, my win rate, and the average price I
paid on winners versus losers.

Set rules once for a trading session

Send your rules at the start of a conversation, then type “next” each time a new window opens. The assistant applies the same rules every time and keeps a running tally:

Prompttext
For the rest of this conversation, trade 15-minute Bitcoin windows on
my Public account with these rules. Each time I say "next", check the
window that is trading now and apply them:

Entry
- Only enter between 5 and 10 minutes into the window
- Buy the side priced higher if its ask is between $0.60 and $0.80
  and the spread is $0.02 or less
- Never trade the same window twice

Size and limits
- 3 contracts per trade, never more than $3.00 including fees
- Keep a running total of my realized P&L in this conversation;
  stop trading if it reaches -$10.00

Orders
- LIMIT orders only, at the ask rounded up to the cent
- Preflight first, show me the order, and place it only after I
  reply "confirm"
- Cancel anything not filled within 30 seconds

After each check, reply with one line: the window, what you did, and
the running P&L.

Repeat a prompt every window

Some AI tools can re-run a prompt on a schedule. Before relying on one, check that it can run as often as every 15 minutes, that it can use your Public connection while you're away, and how it handles approving each trade. Many scheduled-task features run hourly or less often. In Claude Code with the Public MCP server connected, for example, /loop re-runs a prompt on an interval:

Claude Codetext
/loop 15m Using my Public account, check the open BTC 15-minute
window and follow my trading session rules from earlier in this
conversation. If nothing qualifies, reply "no trade" and the reason.

A scheduled prompt that places orders is an unattended trading strategy, so give it firm limits: a small size, a per-trade cost cap, and a daily loss limit it checks before every trade. If you want your rules enforced in code rather than in an instruction to the assistant, use the script.

Using an assistant safely

  • Every order an assistant places is a real order. Read the preflight it shows you before you confirm.
  • Assistants can misread a number or a rule. Ask it to quote the contract symbol, side, price, and quantity back to you before it places the order.
  • Prices move between the assistant's quote and your reply. Ask for a fresh quote if more than a minute has passed.
  • Keep your API secret key out of chats. The hosted MCP server connects through your platform's connector settings.

Build an auto-trader

The steps above are everything a trading loop needs, and the auto-trader script puts them together. You set your trade criteria at the top, start it, and it trades every Bitcoin window until you stop it. For each window, it:

  1. Waits for the open window.
  2. Sleeps until a set number of minutes before the window closes.
  3. Checks the YES and NO prices. If either side's ask is inside your price range, it buys that side with a limit order.
  4. Cancels whatever hasn't filled just before the close.
  5. Waits for the window to settle and records the result and profit or loss in a CSV file.

Download the script

Download btc_15m_strategy.py and save it in a folder of its own. It's a single file that needs only Python 3.10 or later and the Public SDK. Open it in any text editor and you'll find three parts, from top to bottom:

  1. Settings: your trade criteria, one setting per line with a comment explaining it. This is the only part most people change.
  2. pick_side: a short function that decides which contract to buy. By default it applies your price range; replace it to trade your own idea.
  3. The rest: waiting for windows, placing and cancelling orders, settlement, and logging. You shouldn't need to change it.

When it runs, it writes two files next to the script: a log of everything it does (btc_15m_strategy.log), and btc_15m_trades.csv, with one row per window: the prices it saw, what it did and why, and the result and P&L of any trade.

Set your trade criteria

Edit the values under Settings:

SettingDefaultWhat it does
DRY_RUNTruePrice each trade with a real preflight and log it, without sending orders.
MINUTES_LEFT5Check prices when this many minutes are left in the window.
MIN_PRICE0.80Buy YES or NO only when its ask is at least this.
MAX_PRICE0.95Never buy above this. Use whole cents, such as 0.95, not 0.955.
CONTRACTS1Contracts to buy per window.

With the defaults, the script checks each Bitcoin window five minutes before it closes, buys one contract of whichever side is priced between $0.80 and $0.95, and holds it to settlement. As the break-even table above shows, a $0.90 entry has to win about 92% of the time to break even. These defaults show how the settings fit together. They aren't a recommended strategy.

The script waits for each window to settle before moving to the next one, about five minutes after the close. With MINUTES_LEFT above 10, it reaches the next window after your entry time and checks it late.

To trade a different market, change SERIES, CATEGORY, and SUBCATEGORYat the top of the internals to the market's series ticker and the category and a subcategory shown for it in the event summary response.

Write your own entry rule

The pick_sidefunction receives the window's outcome, whose yes and no contracts carry their bid and ask, and returns the contract to buy or None to skip the window. Replace its body to trade your own idea. For example, to also skip any side whose spread is wider than two cents:

Custom pick_sidepython
def pick_side(outcome: EventOutcome) -> Optional[EventContract]:
    """The side priced MIN_PRICE to MAX_PRICE with a spread of $0.02 or less."""
    sides = [
        c
        for c in (outcome.yes, outcome.no)
        if c is not None
        and c.bid is not None
        and c.ask is not None
        and THRESHOLD <= c.ask <= PRICE_CAP
        and c.ask - c.bid <= Decimal("0.02")
    ]
    return max(sides, key=lambda c: c.ask, default=None)

THRESHOLD and PRICE_CAP are MIN_PRICE and MAX_PRICEas exact decimals. The script buys at the ask rounded up to a whole cent, so return a contract only if you're happy to pay that.

Run it in dry-run mode

Generate an API secret key on your Public API settings page. Then create a file named .env in the same folder as the script, with your key and brokerage account ID:

.envbash
API_SECRET_KEY=YOUR_API_SECRET_KEY
DEFAULT_ACCOUNT_NUMBER=YOUR_ACCOUNT_ID

In a terminal opened in that folder, install the SDK and start the script:

Mac or Linux (Terminal)bash
pip3 install "publicdotcom-py>=0.1.27"
python3 btc_15m_strategy.py
Windows (PowerShell)bash
pip install "publicdotcom-py>=0.1.27"
python btc_15m_strategy.py

You can set API_SECRET_KEY and DEFAULT_ACCOUNT_NUMBER as environment variables instead of using a .env file. Either way, keep the key out of the script itself.

In dry-run mode it reads live windows and prices and runs a real preflight for each trade, but sends no orders. It records each trade as if it had filled at the preflight price, and scores it against how the window actually settles. The log looks like this (example output):

btc_15m_strategy.logbash
2026-10-16 13:30:04,112 INFO Starting KXBTC15M strategy (DRY_RUN=True, min 0.8, max 0.95, qty 1)
2026-10-16 13:30:04,113 INFO Waiting for the next KXBTC15M window
2026-10-16 13:30:04,871 INFO KALSHI.KXBTC15M-26OCT161345 (Target Price: $80,811.49) closes 2026-10-16 17:45:00+00:00; entry check at 2026-10-16 17:40:00+00:00
2026-10-16 13:40:00,402 INFO Entry check: tradable=True, YES bid/ask 0.86/0.87, NO bid/ask 0.13/0.14
2026-10-16 13:40:00,977 INFO DRY RUN: would buy 1 x KALSHI.KXBTC15M-26OCT161345-45.Y @ 0.87 (est. cost 0.89)
2026-10-16 13:50:31,540 INFO Settled KALSHI.KXBTC15M-26OCT161345 → SETTLED_OUTCOME_YES, P&L 0.11
2026-10-16 13:50:31,541 INFO Window done: buy
2026-10-16 13:50:31,542 INFO Waiting for the next KXBTC15M window

Times at the start of each line are your computer's local time; window times are UTC. Let it run for at least a few hours, across different market conditions, before you consider going live. Dry-run results are optimistic, because a real order can miss or fill partially.

Go live

When the dry-run results show the behavior you want, set DRY_RUN = False, keep CONTRACTSat an amount you're comfortable losing every window, and start it again. In live mode it also:

  • Places a real limit order at the ask, rounded up to a whole cent, and cancels whatever hasn't filled five seconds before the close. Partial fills are recorded at the quantity that filled.
  • Skips a window if your account already holds either of its contracts, for example after a restart.

The script has no daily loss limit. It trades every window that meets your criteria, so check btc_15m_trades.csvregularly and stop it if the results aren't what you expect.

Keep it running

The script runs until you stop it, but only while the terminal that started it stays open and the computer stays awake. To run it in the background on macOS or Linux, use tmux or nohup, or move it to an always-on cloud server:

Terminalbash
# Option 1: tmux. Detach with Ctrl+B then D; reattach with "tmux attach -t trader"
tmux new -s trader
python3 btc_15m_strategy.py

# Option 2: nohup. Follow the log with tail
nohup python3 btc_15m_strategy.py > /dev/null 2>&1 &
tail -f btc_15m_strategy.log

Press Ctrl+C to stop it (or kill the process if you used nohup). A contract you hold stays open and settles on its own. If a window fails with an error, the script logs it and tries again after 15 seconds, so check the log if it stops trading.

Customize it with an AI assistant

You can paste the script into an AI assistant such as Claude or ChatGPT and describe your criteria in plain English. Ask it to change only the settings and pick_side, so the order handling stays intact. A prompt like this works well:

Prompttext
Below is a Python script that trades 15-minute Bitcoin prediction
markets through the Public API. Change only the Settings section and
the pick_side function to match these criteria, and keep DRY_RUN set
to True:

- Check prices with 3 minutes left in each window
- Buy whichever side is priced between $0.70 and $0.85
- Skip the window if that side's spread is wider than $0.02
- Buy 2 contracts per window

Explain each change you made, then return the complete script.

[paste btc_15m_strategy.py here]

Read the changes before you run them, and test the edited script in dry-run mode first. Don't paste your API secret key into a chat; keep it in the .env file on the machine that runs the script.

📝 Important Notes

  • Each window is a separate event with its own symbols. Look up the current window with wait_for_current_event instead of building symbols from the clock.
  • A contract settles at $1.00 or $0.00 about five minutes after the window closes. Your maximum loss on a purchase is what you paid, fees included.
  • Use limit orders, cancel unfilled orders before the window closes, and stop entering with enough time left to exit.
  • Read fees from preflight on every trade, and account for them in your break-even win rate.
  • Check halted, state, and trading before each order. Halted windows reject new orders.
  • The script's P&L is an estimate built from the preflight's estimated cost. Your account's positions and history are the record of what actually happened.
  • Keep the API secret key out of the script and out of chats. Anyone with it can trade your account.