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.
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.
eventSymbol, such as KALSHI.KXBTC15M-26OCT161345 for the Bitcoin window that closes at 1:45 PM ET on October 16, 2026.Target Price: $80,811.49. The target is the asset's price at the start of the window.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.
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.
| Category | Markets (series ticker) |
|---|---|
| Crypto | BTC (KXBTC15M), ETH (KXETH15M), SOL (KXSOL15M), XRP (KXXRP15M), DOGE (KXDOGE15M), BNB (KXBNB15M), HYPE (KXHYPE15M), NEAR (KXNEAR15M), ZEC (KXZEC15M) |
| Commodities | Gold (KXGOLD15M), silver (KXSILVER15M), platinum (KXPLATINUM15M), palladium (KXPALLADIUM15M), copper (KXCOPPER15M), WTI oil (KXWTI15M), natural gas (KXNATGAS15M) |
| Financials | EUR/USD (KXEURUSD15M), GBP/USD (KXGBPUSD15M), USD/JPY (KXUSDJPY15M), USD/CAD (KXUSDCAD15M), AUD/USD (KXAUDUSD15M) |
Every symbol for a window is built from the series and the time the window closes, in US Eastern time:
| Identifier | Example |
|---|---|
| Event | KALSHI.KXBTC15M-26OCT161345 (series, then YYMMMDD and the close time HHMM in ET) |
| Outcome | KXBTC15M-26OCT161345-45 (the suffix is the close minute) |
| YES / NO contracts | KALSHI.KXBTC15M-26OCT161345-45.Y / …-45.N |
| Price history IDs | KALSHI.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.
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 field | What happens |
|---|---|---|
| 17:30:00 | openTime | Trading opens. The target price is set from the preceding minute's average. |
| 17:45:00 | closeTime | Trading stops. The next window is already open. |
| 17:50:00 | expectedExpirationTime | The result is determined. |
| 17:50:01 | settlementTime | Winning contracts pay $1.00 (a one-second settlementDelaySeconds). |
| A week later | latestExpirationTime | The 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.
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:
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:
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 summaryget_event_details, wait_for_settlement: Get event detailsget_quotes: Get quotesget_event_contract_bars: GET /userapigateway/historicdata/event-contracts/{eventId}/bars/{period}preflight_buy_event_contract: Preflight single-leg orderbuy_event_contract: Place orderwait_for_fill, wait_for_terminal_status: Get ordercancel: Cancel orderPass 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:
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:00resolution_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.get_event_summary with a FIFTEEN_MINUTES frequency filter.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:
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):
{
"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.outcome.seconds_left) for your order to fill and, if you plan to exit early, to sell.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.
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:
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:
0.026 and an ask of 0.027. Parse prices as decimals, not two-decimal strings.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:
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.0350Bars 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.
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:
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 price | Cost with $0.02 fees | Profit if right | Loss if wrong | Break-even win rate |
|---|---|---|---|---|
| $0.30 | $0.32 | $0.68 | $0.32 | 32% |
| $0.50 | $0.52 | $0.48 | $0.52 | 52% |
| $0.70 | $0.72 | $0.28 | $0.72 | 72% |
| $0.90 | $0.92 | $0.08 | $0.92 | 92% |
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.
Orders use the same endpoints as any event contract. What changes is how you handle time:
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:
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")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:
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):
{
"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"
}
]
}
]
}SETTLED_OUTCOME_OTHERmeans the result wasn't a plain YES or NO. Check your account for the payout.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.
List every open 15-minute window with its target, time left, and prices:
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.Get the rules, the market’s implied odds, and the real cost of a trade before you decide:
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 feesSpell out your entry rules, and have the assistant check every one, preflight the order, and wait for your go-ahead:
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.Check your 15-minute positions and apply your exit rules before the window closes:
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".Score the windows you traded once they settle:
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.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:
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.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:
/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
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:
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:
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.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.
Edit the values under Settings:
| Setting | Default | What it does |
|---|---|---|
DRY_RUN | True | Price each trade with a real preflight and log it, without sending orders. |
MINUTES_LEFT | 5 | Check prices when this many minutes are left in the window. |
MIN_PRICE | 0.80 | Buy YES or NO only when its ask is at least this. |
MAX_PRICE | 0.95 | Never buy above this. Use whole cents, such as 0.95, not 0.955. |
CONTRACTS | 1 | Contracts 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.
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:
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.
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:
API_SECRET_KEY=YOUR_API_SECRET_KEY
DEFAULT_ACCOUNT_NUMBER=YOUR_ACCOUNT_IDIn a terminal opened in that folder, install the SDK and start the script:
pip3 install "publicdotcom-py>=0.1.27"
python3 btc_15m_strategy.pypip install "publicdotcom-py>=0.1.27"
python btc_15m_strategy.pyYou 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):
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 windowTimes 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.
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:
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.
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:
# 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.logPress 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.
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:
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
wait_for_current_event instead of building symbols from the clock.halted, state, and trading before each order. Halted windows reject new orders.