Execution:
venue.trade() in chat code, strategy.execute() in strategy agents. Data reference: Kalshi Data.Overview
Kalshi is a CFTC-regulated exchange for binary event contracts. Winning contracts settle at $1; prices are probabilities in roughly[0.01, 0.99]. Scalar Field connects to your own Kalshi account — we never create a trader account on your behalf. Cash stays on Kalshi; deposits and withdrawals happen at Kalshi, never through us.
Connecting
- Sign in at kalshi.com/account/profile and click Create API Key. Kalshi shows the private key exactly once — copy both the API Key ID and the PEM private key.
- From Portfolio → Kalshi (or the venue card), open Connect and paste the API Key ID and private key.
- Scalar Field validates the key against Kalshi, then stores it Fernet-encrypted for trading and portfolio reads.
- Disconnect removes our stored copy only; your key stays active on Kalshi.
write::transfer. Perpetual futures (Kalshi margin) are not supported in this integration.
Execution and accounting model
This is the contract for audit-grade use ofvenue.trade, venue.history, venue.account, and venue.cancel on Kalshi. Field meanings come from Kalshi’s Trade API: V2 orders are a single YES book, and fill outcome_side / book_side describe directional exposure, not a second price scale.
Symbol model
Discover markets withscreenKalshiMarkets(). Do not invent tickers.
Pass
symbol to trade YES and symbol_no to trade NO. Bare tickers are treated as the YES leg. Positions, fills, and history use the same handles.
Kalshi stores a signed position_fp per market: positive = YES, negative = NO. Scalar Field splits that into one position row per held outcome, matching Polymarket’s one-row-per-outcome shape.
Book identities
Kalshi’s public order book returns bids only on each leg. Asks are implied by complementarity (Kalshi orderbook docs):bid= buy YES (economically sell NO)ask= sell YES (economically buy NO)
venue.trade converts your (action, outcome, limit_price) into that YES-leg (side, price) before submit. avg_price on the trade response is converted back to the outcome you passed in.
Quantity rules
Kalshi contract counts are fixed-point strings with minimum granularity 0.01 (Fixed-Point Representation). Requests accept 0–2 decimal places; responses emit 2 decimals. This integration truncates toward zero (ROUND_DOWN) to 0.01 before submit. If the result is 0, the order is rejected locally — Kalshi never sees it.
qty = int(budget / price * 100) / 100, then confirm the resting/filled qty — do not assume the exchange kept extra precision.
Order support
Kalshi’s V2 create path (POST /portfolio/events/orders) requires price and time_in_force. There is no native market order type.
Off-grid limit prices are snapped, not rejected at our layer. Kalshi may still reject a price that falls outside the market’s
price_ranges after snapping.
During the Thursday 03:00–05:00 ET pause, placement fails with TRADING_PAUSED; cancels still work. Resting GTC orders are submitted with cancel_order_on_pause=false, so they survive the pause.
Settlement is automatic. Winning contracts pay $1; there is no redeem call (REDEEM_NOT_SUPPORTED). Settled positions appear in venue.history(category="settlement").
Cash vs buying power
Kalshi’sGET /portfolio/balance returns balance_dollars (documented as available USD) and portfolio_value (open-position valuation in cents). It does not return a buying-power field.
venue.account(account="KALSHI") therefore looks like:
buying_power is missing or None, use cash as the available USD balance. Clients that do buying_power or 0 will conclude the account cannot trade.
cash is USD on the Kalshi account. It does not include position mark-to-market; that lives in positions[].market_value / portfolio_value.
Resting-order cash reservation
Kalshi does not publish areserved_cash (or equivalent) on the direct-member balance payload. GET /portfolio/total_resting_order_value is documented as FCM-only and is not used here.
Observed behavior, consistent with that API:
- A resting GTC bid appears in
open_ordersimmediately. cash(balance_dollars) often does not decrease while the order rests.- The matching engine still enforces availability; a bid that cannot be collateralized is rejected at place or fill time.
cash as “unreserved buying power” and stack resting bids that sum to more than cash without checking open_orders. A workable available-cash estimate when buying_power is null:
History semantics and cash formulas
This is the accounting trap. Kalshi fills carry bothyes_price_dollars and no_price_dollars (they sum to 1) plus canonical direction fields (Order direction):
Unified
venue.history(category="trade", account="KALSHI") maps those as:
The general venue invariant
amount == qty * price * multiplier holds. amount is not cash.
Closing a held YES contract at a YES price of $0.43 is a sell-YES, which Kalshi encodes as outcome_side=no. History then looks like:
qty * price (0.57) is the wrong proceeds figure.
Quoted notional vs cash
Reconstruct cash from a history row. V2 cash follows the YES book: bid pays
yes_price, ask receives yes_price.
Worked examples (1 contract, ignore fees first)
Buy-NO opening a short YES credits the YES price. That is the V2 single-book identity: buying NO is selling YES. Terminal PnL still matches “paid 1 complementary payoff is included at settlement.
Net cash is always
gross − fees.
Do not use:
category: "settlement") are different: amount is revenue (payout), closed_pnl is already revenue − cost_basis − fees, and price is payout per contract.
Portfolio venue.history merges Kalshi live fills/settlements with /historical/* so completed activity older than the rolling ~3-month live cutoff is not dropped. Public market-data helpers do not do that merge — see Kalshi Data.
Fees
Kalshi charges exchange fees plus a rounding fee so balances stay on the member’s precision grid (0 (Fee rounding).
Kalshi order responses may omit finalized fees. Use
venue.history(category="trade", account="KALSHI") for authoritative fee and cash accounting. Scalar Field does not add a platform fee on Kalshi; fees on history is Kalshi’s fee_cost.
There is a short delay before fills, balances, and positions show up on REST (Get User Data Timestamp). After a fill, prefer the order response for filled_qty / avg_price, then re-read venue.history / venue.account rather than treating the synchronous fees field as final.
Cancel
venue.cancel(broker_order_id, account="KALSHI") is supported. The primary path is Kalshi V2: DELETE /portfolio/events/orders/{order_id}.
Success:
Pass
symbol when you have it (market_ticker is required for V2 auto-routing). Cancels still work during the weekly trading pause.
Failure is status="FAILED" with reason set to the exception string. Typical cases:
Do not parse 410 / V1 deprecation text as “cancel is unsupported”. Resting-order cancel on V2 works. Treat any
FAILED after a cancel as “confirm via open_orders / get_order” rather than retrying blindly.
Errors
Normalized reasons this integration emits itself:
Everything else (insufficient balance, invalid price after snap, unknown order, already cancelled) is forwarded from Kalshi as
FAILED + the HTTP/error body. Those strings are not a stable enum.
Usage
End-to-end: find a market, buy YES, verify, close the held qty, account for complement-encoded history, confirm flat.strategy.execute(symbol, target_qty) with brokerage_account="KALSHI".
Data Functions
Market data is public (no Kalshi connection required). Full reference, including sparse candles and live-vs-historical coverage: Kalshi Data.Notes
- Weekly maintenance pause: Thursday 03:00–05:00 ET — order placement fails with
TRADING_PAUSED; cancels still work. - The stored API key cannot withdraw or transfer funds (
write::transferis never granted). - Perpetual futures (margin) are not supported in this integration.
- Orders, fills, and settlements older than Kalshi’s ~3-month live cutoff are merged from
/historical/*on portfolio reads (venue.history, positions, resting/completed orders). PublicgetKalshiTrades/getKalshiCandlesalso fail over toGET /historical/tradesandGET /historical/markets/{ticker}/candlesticks.