Election bots for Kalshi are automated trading agents that buy and sell political event contracts on Kalshi’s CFTC-regulated exchange using its REST API, WebSocket feeds, or FIX protocol. These bots ingest polling data, election models, news sentiment, and cross-platform price feeds to trade binary contracts on outcomes like Senate control, House races, gubernatorial elections, and ballot measures — all settled in USD with no cryptocurrency required.

The 2024 presidential election cycle proved that political prediction markets are a multi-billion-dollar category. Polymarket alone saw over $3 billion in election volume. Kalshi, after winning its legal battle with the CFTC to offer election contracts, is now a primary venue for regulated political trading in the United States. The 2026 midterm elections — 35 Senate seats, 435 House seats, 36 governorships — represent the next major opportunity for election trading bots.

This guide covers everything you need to build, deploy, and operate an election bot on Kalshi for the 2026 midterms: market types, bot categories, data sources, integration code, strategies, architecture, and the realistic constraints that separate profitable election trading from political speculation.

For background on Kalshi’s API architecture, see the Kalshi API Guide. For cross-platform strategies, see Cross-Platform Arbitrage. For Polymarket’s side of the equation, see the Polymarket API Guide.


Why Kalshi for Election Trading

Political prediction markets existed before Kalshi, but they existed in a legal gray area for US participants. Polymarket is offshore and crypto-native. PredictIt operated under a CFTC no-action letter with severe position limits. Kalshi changed this by becoming a CFTC-regulated Designated Contract Market and then winning the legal right to list election contracts.

For election bot builders, this has specific consequences.

CFTC-Regulated Designated Contract Market

Kalshi operates under the same federal regulatory framework as the CME or CBOE. This means legal clarity for US-based traders, institutional participants, and funds. If you are building an election bot for a firm, a fund, or even a serious personal trading operation, Kalshi is the only platform where you are unambiguously operating within US regulatory boundaries. No OFAC risk. No question about whether political contracts are legal. The CFTC oversees it.

USD Settlement — No Crypto Required

Kalshi deposits and withdrawals use traditional banking rails. You fund your account with USD via bank transfer. When contracts settle, you receive USD. This eliminates the entire crypto onboarding stack — no wallets, no gas fees, no USDC bridging, no stablecoin depegging risk. For election bots, this simplifies architecture because your bot’s capital is denominated in the same currency as the contracts.

Full API Infrastructure

Kalshi provides three access methods, each suited to different bot architectures:

  • REST API — Market data retrieval, order placement and management, account operations. Standard HTTPS with RSA-PSS signed requests. This is where most election bots start.
  • WebSocket — Real-time streaming of orderbook updates, trades, and ticker changes. Essential for bots that need to react to price movements in seconds rather than polling on an interval.
  • FIX 4.4 Protocol — Institutional-grade, low-latency trading interface. The same protocol used by equity and futures exchanges. Use this if you are building a market-making bot or need sub-second execution.

This is the best developer experience of any prediction market. Polymarket’s CLOB API is powerful but requires blockchain wallet management and on-chain order signing. Kalshi’s API patterns are familiar to anyone who has built against a traditional exchange API.

Binary Contract Structure

Every Kalshi election market is a binary contract priced between $0.01 and $0.99 (expressed as cents internally). If the event occurs, the contract pays $1.00. If not, $0.00. The price is the market’s implied probability.

A Senate control contract trading at $0.58 implies a 58% probability that the specified party controls the Senate. You buy YES contracts at 58 cents and receive $1.00 each if correct — a profit of 42 cents per contract. Or you buy NO at 42 cents and profit 58 cents if the other party controls the Senate.

Election Market Depth

The 2024 cycle demonstrated that political prediction markets attract deep liquidity from a motivated, opinionated participant base. Unlike niche markets (weather, entertainment), election contracts draw traders from political campaigns, media organizations, hedge funds, academic researchers, and politically engaged individuals. This means:

  • Tighter bid-ask spreads on headline markets (Senate/House control)
  • Meaningful volume for position entry and exit
  • Active two-sided markets even months before election day
  • Price discovery that reflects real information, not just noise

2026 Midterm Election Markets on Kalshi

The 2026 US midterm elections are the dominant political event of the year. Understanding what markets Kalshi will list — and when — determines which bot strategies are viable.

Senate Control

The most liquid political prediction market. A single binary contract: “Which party controls the US Senate after the 2026 election?” This is typically the first election market to appear on Kalshi and the one with the deepest order book.

The 2026 Senate map features 35 seats (including two special elections). The current balance and which seats are up create a structural landscape that models can quantify. Senate control is where the most capital flows because it is the simplest to model and trade.

House Control

A binary contract on which party holds a majority in the House of Representatives after November 2026. All 435 seats are contested. House control is harder to model (more seats, more local factors) but equally liquid as a top-level contract.

Individual Senate Races

Kalshi is expected to list contracts on competitive individual Senate races — particularly swing states where the outcome is genuinely uncertain. These are the building blocks for correlated market strategies: if your model shifts its view on Senate control, individual race contracts should move in sympathy, and the ones that lag create trading opportunities.

Key states to watch for 2026 markets: any seats rated as “Toss Up” or “Lean” by Cook Political Report or Sabato’s Crystal Ball will likely have Kalshi contracts.

Individual House Races

More granular than Senate contracts, but potentially available for the most competitive districts. House race contracts have thinner liquidity individually but create a large opportunity set for model-driven bots that can process many races simultaneously.

Gubernatorial Races

With 36 governorships on the ballot in 2026, Kalshi may list contracts on competitive gubernatorial races. These are lower-liquidity markets but offer diversification for election bots that want exposure beyond Congressional races.

Ballot Measures

State ballot initiatives on high-profile topics (cannabis legalization, abortion access, tax policy) may get Kalshi contracts. These are typically binary and can be modeled from polling data, though the polling infrastructure for ballot measures is thinner than for candidate races.

Market Creation Timing

Kalshi does not list all midterm markets on a fixed schedule. The pattern from previous cycles:

  • Top-level control markets (Senate control, House control) appear 6-12 months before election day
  • Individual race markets appear as the cycle heats up — typically 3-6 months before election day for competitive races
  • Ballot measure markets may appear 2-4 months before election day
  • Liquidity deepens significantly in the final 2-3 months before election day

For bot builders, this means your system needs to discover new markets as they appear, not just trade a static set.


Bot Categories for Election Trading

Election markets support several distinct bot architectures, each with different data requirements, latency needs, and risk profiles.

Polling Model Bots

The most straightforward election bot. Ingest polling data from aggregators and individual pollsters, convert polls into probability estimates using a statistical model, and trade when Kalshi’s price diverges from your model’s estimate.

How it works:

  1. Aggregate polls for each race (or for control of a chamber)
  2. Apply a model that converts polling averages to win probabilities (accounting for poll quality, sample size, historical accuracy, and time-to-election)
  3. Compare your model’s probability to Kalshi’s current price
  4. When the divergence exceeds a threshold, place orders on the underpriced side
  5. Continuously update as new polls are released

Edge source: The market cannot instantly reprice when a new poll is released, especially for individual races with thinner liquidity. If your model updates faster than the market, you can trade the brief window of mispricing.

Risk: Polling errors are systematic and correlated. If polls are systematically biased in one direction (as in 2016 and 2020), your model inherits that bias.

Fundamentals Model Bots

Instead of (or in addition to) polls, these bots use macroeconomic and political fundamentals to model election outcomes. Academic political science has decades of research on what predicts midterm election results.

Key fundamentals for midterms:

  • Presidential approval rating — the single strongest predictor of midterm outcomes. The president’s party almost always loses seats, and the magnitude correlates with approval.
  • Generic congressional ballot — “If the election were held today, would you vote for the Democratic or Republican candidate for Congress?” This is a direct signal for House outcomes.
  • Economic indicators — unemployment rate, real GDP growth, inflation (CPI), consumer sentiment. Voters reward or punish the incumbent party based on economic conditions.
  • Historical midterm penalty — the president’s party has lost House seats in 38 of the last 40 midterm elections. This is a strong prior.

Edge source: Fundamentals models often disagree with polling models early in the cycle, when polls are sparse but economic conditions are well-measured. A bot that trades on fundamentals early and transitions to a polling-weighted model as election day approaches can capture alpha in both regimes.

Arbitrage Bots

Both Kalshi and Polymarket list contracts on the same political outcomes. Different user bases, different market microstructures, and different information flows mean their prices diverge — often by 2-5 percentage points.

How it works:

  1. Monitor the same race on both Kalshi and Polymarket
  2. When Kalshi’s YES price plus Polymarket’s NO price (or vice versa) sums to less than $1.00, an arbitrage exists
  3. Buy the cheap side on each platform simultaneously
  4. Wait for resolution — you are guaranteed profit regardless of outcome

Complications: Kalshi is USD-based. Polymarket is USDC-based. Resolution criteria must match exactly. Execution timing matters — prices can move between your two trades. Capital is locked on both platforms until settlement.

For the full cross-platform arbitrage framework, see the Cross-Platform Arbitrage guide.

News and Sentiment Bots

Political markets reprice on news events: debate performances, scandal reporting, endorsements, campaign staff changes, judicial decisions, and policy announcements. A bot that parses news feeds faster than manual Kalshi traders can trade before the market adjusts.

Data sources:

  • News APIs (Associated Press, Reuters, major newspaper RSS feeds)
  • Social media sentiment (Twitter/X political accounts, campaign accounts)
  • LLM-based summarization and sentiment scoring of real-time news
  • Debate transcripts and real-time debate coverage

Edge source: Most Kalshi election traders are not running real-time news parsers. During high-profile events like debates, the market may take minutes to fully reprice. A bot that scores debate performance in real time and trades immediately captures the repricing window.

Risk: Sentiment is noisy. LLMs can misinterpret political news. False signals from social media are common. This strategy requires high confidence thresholds to avoid trading on noise.

Market-Making Bots

Provide liquidity on election contracts by posting bid and ask orders on both sides. Earn the spread between your buy and sell prices.

How it works:

  1. Post a bid (buy) order at a price below your estimate of fair value
  2. Post an ask (sell) order above fair value
  3. When both sides fill, you earn the spread
  4. Continuously adjust quotes as the market moves and as your model updates

Edge source: Election markets need liquidity providers, especially for individual race contracts. If you can quote a tight spread informed by a good model, you earn the spread while providing a service the market needs.

Risk: Adverse selection. When a major poll drops or a news event occurs, informed traders will hit your stale quotes before you can update them. Market-making on election contracts requires fast model updates and rapid quote adjustment.

Correlated Market Bots

This is where election trading gets genuinely sophisticated. Political outcomes are correlated: if one party is having a good election night, it affects Senate control, House control, and dozens of individual races simultaneously. Kalshi’s individual race contracts may not reprice as quickly as the top-level control contracts.

How it works:

  1. Build a model of how individual race outcomes correlate with overall control outcomes
  2. Monitor the Senate control contract as the “fast” signal
  3. When Senate control moves (say, toward Democrats), check whether the individual Senate race contracts in competitive states have moved proportionally
  4. Trade the laggards — buy the underpriced individual race contracts that should have moved but haven’t yet

Edge source: Traders watching Senate control actively reprice it on new information. But the same information implies specific changes to individual race probabilities, and those individual markets are thinner and slower to adjust. The bot captures the propagation lag.


Data Sources for Election Modeling

The quality of your election bot depends directly on the quality of your data. Here are the essential sources.

Polling Aggregators

  • FiveThirtyEight / 538 — The gold standard for polling aggregation. Provides polling averages, pollster ratings, and (in election years) model-based forecasts. Their data is available via public pages and can be scraped or accessed through their data files.
  • RealClearPolitics (RCP) — Maintains simple polling averages for major races. Less model-driven than 538 but widely referenced. RCP averages are easy to scrape and compare against Kalshi prices.
  • The Economist — Publishes election forecast models with downloadable data for competitive races.

Race Ratings

  • Cook Political Report — Rates races as Solid, Likely, Lean, or Toss Up for each party. These ratings serve as a prior for your model and are updated frequently during the cycle.
  • Sabato’s Crystal Ball (University of Virginia Center for Politics) — Similar rating system with independent analysis.
  • Inside Elections (formerly Rothenberg & Gonzales) — Another independent race rating source.

Individual Pollsters

For the most responsive model, ingest individual polls as they are released rather than waiting for aggregator updates:

  • Quinnipiac University — high-quality swing state and national polls
  • Marist / NPR / PBS — frequent national and state polls
  • Siena College / New York Times — gold-standard state-level polls
  • Monmouth University — high-quality state polls
  • Emerson College — frequent state-level releases
  • SurveyUSA — automated polls with broad geographic coverage

Economic and Political Fundamentals

  • Bureau of Labor Statistics (BLS) — employment data, CPI, unemployment rate
  • Bureau of Economic Analysis (BEA) — GDP data
  • University of Michigan Consumer Sentiment Index
  • Gallup / FiveThirtyEight presidential approval trackers
  • Generic congressional ballot polls (aggregated by 538 and RCP)

Simple Polling-Based Senate Model

Here’s a minimal example that converts a polling average into a win probability estimate using the historical relationship between polling leads and election outcomes:

import math
from dataclasses import dataclass


@dataclass
class RaceEstimate:
    state: str
    dem_polling_avg: float
    rep_polling_avg: float
    days_until_election: int

    @property
    def polling_margin(self) -> float:
        return self.dem_polling_avg - self.rep_polling_avg

    def win_probability(self, party: str = "dem") -> float:
        """Convert polling margin to win probability using logistic model.

        The scaling factor adjusts for uncertainty: farther from election day
        means more uncertainty, so the same polling margin implies a probability
        closer to 50%. Historical midterm polling error is ~4-5 points (SD).
        """
        polling_error_sd = 4.5
        uncertainty_factor = 1 + (self.days_until_election / 200)
        effective_sd = polling_error_sd * uncertainty_factor
        z = self.polling_margin / effective_sd
        prob = 1 / (1 + math.exp(-1.7 * z))
        if party == "rep":
            return 1 - prob
        return prob


races_2026 = [
    RaceEstimate("Michigan", 47.2, 44.8, 245),
    RaceEstimate("Pennsylvania", 46.5, 46.1, 245),
    RaceEstimate("Wisconsin", 47.0, 45.5, 245),
    RaceEstimate("Nevada", 45.8, 46.2, 245),
    RaceEstimate("Arizona", 45.1, 46.9, 245),
    RaceEstimate("Georgia", 44.5, 47.3, 245),
    RaceEstimate("North Carolina", 44.0, 47.8, 245),
]

for race in races_2026:
    prob = race.win_probability("dem")
    print(f"{race.state}: D+{race.polling_margin:.1f} -> "
          f"Dem win prob: {prob:.1%}, Kalshi fair price: ${prob:.2f}")

This is a starting point. A production model would incorporate poll quality weighting, likely voter screens, trend adjustments, fundamentals priors, and correlation between races.


Strategies for 2026 Midterm Elections

The 2026 midterms present specific strategic opportunities that differ from presidential election trading.

Polling Model vs. Kalshi Spread

The core strategy: maintain a polling model for each competitive race and compare its output to Kalshi prices.

import requests
from dataclasses import dataclass


KALSHI_BASE = "https://trading-api.kalshi.com/trade-api/v2"

@dataclass
class TradingSignal:
    ticker: str
    model_prob: float
    kalshi_price: float
    edge: float
    side: str
    confidence: str


def get_kalshi_price(ticker: str, headers: dict) -> float | None:
    resp = requests.get(
        f"{KALSHI_BASE}/markets/{ticker}",
        headers=headers,
    )
    if resp.status_code != 200:
        return None
    market = resp.json().get("market", {})
    yes_price = market.get("yes_ask", 0)
    return yes_price / 100  # cents to probability


def generate_signals(
    model_estimates: dict[str, float],
    headers: dict,
    min_edge: float = 0.03,
) -> list[TradingSignal]:
    """Compare model probabilities to Kalshi prices and generate signals.

    model_estimates: {ticker: model_probability}
    min_edge: minimum divergence to generate a signal (default 3 points)
    """
    signals = []
    for ticker, model_prob in model_estimates.items():
        kalshi_price = get_kalshi_price(ticker, headers)
        if kalshi_price is None:
            continue

        edge = model_prob - kalshi_price

        if abs(edge) < min_edge:
            continue

        side = "yes" if edge > 0 else "no"
        confidence = "high" if abs(edge) > 0.07 else "medium"

        signals.append(TradingSignal(
            ticker=ticker,
            model_prob=model_prob,
            kalshi_price=kalshi_price,
            edge=edge,
            side=side,
            confidence=confidence,
        ))

    signals.sort(key=lambda s: abs(s.edge), reverse=True)
    return signals


# Example: your model's probability estimates for midterm races
model_estimates = {
    "SENATE-CONTROL-DEM-2026": 0.52,
    "HOUSE-CONTROL-DEM-2026": 0.41,
    "SENATE-MI-DEM-2026": 0.61,
    "SENATE-PA-DEM-2026": 0.53,
    "SENATE-WI-DEM-2026": 0.57,
}

# headers = build_kalshi_auth_headers(...)  # See Kalshi API Guide
# signals = generate_signals(model_estimates, headers, min_edge=0.03)
# for s in signals:
#     print(f"{s.ticker}: Model={s.model_prob:.0%}, Kalshi={s.kalshi_price:.0%}, "
#           f"Edge={s.edge:+.1%}, Side={s.side}, Confidence={s.confidence}")

The key parameter is min_edge — the minimum divergence between your model and Kalshi’s price before you trade. Set this too low and you trade on noise. Set too high and you miss opportunities. For midterm elections, 3-5 percentage points is a reasonable starting threshold, tightening to 2-3 points as election day approaches and uncertainty decreases.

Historical Fundamentals as a Prior

Midterm elections have a strong historical pattern: the president’s party loses seats. Since 1946, the president’s party has lost an average of 26 House seats and 4 Senate seats in midterm elections. This effect is stronger when the president’s approval rating is low.

Use this as a Bayesian prior in your model:

  1. Start with the historical base rate for the president’s party losing seats
  2. Adjust based on current presidential approval rating
  3. Adjust based on generic congressional ballot
  4. Adjust based on economic conditions
  5. As polls for individual races come in, transition to a poll-weighted model

The value of fundamentals is highest early in the cycle (8-12 months out) when individual race polls are sparse. A bot that trades on fundamentals in the spring and transitions to a polling model in the fall captures two distinct sources of edge.

Correlated Market Trading

This is the highest-alpha strategy for election bots and the hardest to implement well.

The logic: Senate control is a function of individual Senate race outcomes. When the Senate control price moves on new information (say, a major poll shows the minority party gaining ground), the implied probability shift should propagate to every individual race contract. But individual race contracts are thinner and reprice slower.

Implementation:

  1. Maintain a model that decomposes Senate control probability into individual race probabilities
  2. When Senate control moves by more than X points, calculate the implied shift for each individual race
  3. Compare the implied individual race prices to actual individual race prices
  4. Trade the races where the gap is largest

This requires a correlation model — an understanding of how much each individual race contributes to the overall control outcome and how races co-move. Election models like FiveThirtyEight’s simulate thousands of election outcomes to capture these correlations. You need something similar.

Debate and Event Reaction

Political debates, major endorsements, Supreme Court decisions, and October surprises create rapid repricing events in election markets. Historically, the market’s reaction is fastest on the top-level contracts and slower on individual races — the same propagation lag that correlated market bots exploit.

A debate reaction bot:

  1. Monitors real-time debate coverage (TV transcript feeds, social media sentiment, pundit reactions)
  2. Uses an LLM or sentiment model to score debate performance
  3. Translates the score into an expected price move for relevant contracts
  4. Places trades before the market fully adjusts

The window is narrow — minutes, not hours — and the signal is noisy. But during the 2024 presidential debates, prediction market prices moved 5-15 points in the hour following key moments. Even capturing a fraction of that move on midterm debate nights is significant.

Cross-Platform Arbitrage: Kalshi vs. Polymarket

Both Kalshi and Polymarket list election markets. Their prices frequently diverge because:

  • Different user bases (Kalshi: US-regulated, institutional; Polymarket: crypto-native, global)
  • Different fee structures (Kalshi: 0% trading fee; Polymarket: 2% on net winnings)
  • Different liquidity dynamics and market makers
  • Different information propagation speeds

A simple cross-platform scanner:

from dataclasses import dataclass


@dataclass
class ArbOpportunity:
    race: str
    kalshi_yes: float
    poly_no: float
    arb_margin: float
    direction: str


def scan_election_arbs(
    races: dict[str, dict],
    min_margin: float = 0.01,
) -> list[ArbOpportunity]:
    """Find arbitrage between Kalshi and Polymarket election markets.

    races: {race_name: {"kalshi_yes": float, "kalshi_no": float,
                        "poly_yes": float, "poly_no": float}}
    """
    opportunities = []

    for race, prices in races.items():
        # Direction 1: Buy YES on Kalshi + Buy NO on Polymarket
        cost_1 = prices["kalshi_yes"] + prices["poly_no"]
        if cost_1 < 1.0:
            opportunities.append(ArbOpportunity(
                race=race,
                kalshi_yes=prices["kalshi_yes"],
                poly_no=prices["poly_no"],
                arb_margin=1.0 - cost_1,
                direction="Kalshi YES + Polymarket NO",
            ))

        # Direction 2: Buy NO on Kalshi + Buy YES on Polymarket
        cost_2 = prices["kalshi_no"] + prices["poly_yes"]
        if cost_2 < 1.0:
            opportunities.append(ArbOpportunity(
                race=race,
                kalshi_yes=prices["kalshi_no"],
                poly_no=prices["poly_yes"],
                arb_margin=1.0 - cost_2,
                direction="Kalshi NO + Polymarket YES",
            ))

    opportunities.sort(key=lambda o: o.arb_margin, reverse=True)
    return [o for o in opportunities if o.arb_margin >= min_margin]


# Example: prices from both platforms for the same races
race_prices = {
    "Senate Control - Dem": {
        "kalshi_yes": 0.52, "kalshi_no": 0.49,
        "poly_yes": 0.55, "poly_no": 0.46,
    },
    "Senate MI - Dem": {
        "kalshi_yes": 0.60, "kalshi_no": 0.41,
        "poly_yes": 0.63, "poly_no": 0.38,
    },
}

arbs = scan_election_arbs(race_prices, min_margin=0.01)
for a in arbs:
    print(f"{a.race}: {a.direction}, margin={a.arb_margin:.1%}")

The practical challenge is execution timing: you need to place trades on two different platforms before prices move. Kalshi uses standard REST with RSA-PSS authentication; Polymarket uses the py-clob-client with EIP-712 wallet signing. A production arbitrage bot runs both execution paths in parallel. See the Cross-Platform Arbitrage guide for the full execution framework.


Kalshi API Integration for Election Markets

All Kalshi election trading runs through the same API infrastructure described in the Kalshi API Guide. Here’s what’s specific to election markets.

Authentication

Kalshi uses RSA-PSS signed requests. Generate an API key pair in your Kalshi account settings, download the private key PEM file, and sign each request:

import time
import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding


def build_kalshi_headers(
    method: str,
    path: str,
    api_key: str,
    private_key_path: str,
) -> dict:
    timestamp = str(int(time.time() * 1000))
    message = f"{timestamp}{method.upper()}{path}"

    with open(private_key_path, "rb") as f:
        private_key = serialization.load_pem_private_key(f.read(), password=None)

    signature = private_key.sign(
        message.encode(),
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH,
        ),
        hashes.SHA256(),
    )

    return {
        "KALSHI-ACCESS-KEY": api_key,
        "KALSHI-ACCESS-SIGNATURE": base64.b64encode(signature).decode(),
        "KALSHI-ACCESS-TIMESTAMP": timestamp,
        "Content-Type": "application/json",
    }

Finding Election Market Tickers

Kalshi organizes markets into events and series. Election markets use structured ticker formats. To discover available election markets:

import requests


KALSHI_BASE = "https://trading-api.kalshi.com/trade-api/v2"

def find_election_markets(headers: dict) -> list[dict]:
    """Retrieve all election-related markets from Kalshi."""
    markets = []
    cursor = None

    while True:
        params = {"limit": 200, "status": "open"}
        if cursor:
            params["cursor"] = cursor

        resp = requests.get(
            f"{KALSHI_BASE}/markets",
            headers=headers,
            params=params,
        )
        data = resp.json()

        for market in data.get("markets", []):
            title = market.get("title", "").lower()
            category = market.get("category", "").lower()
            tags = [t.lower() for t in market.get("tags", [])]

            is_election = (
                "election" in title
                or "senate" in title
                or "house" in title
                or "governor" in title
                or "congress" in title
                or "politics" in category
                or "election" in tags
            )

            if is_election:
                markets.append({
                    "ticker": market["ticker"],
                    "title": market["title"],
                    "yes_ask": market.get("yes_ask"),
                    "yes_bid": market.get("yes_bid"),
                    "volume": market.get("volume"),
                    "open_interest": market.get("open_interest"),
                    "close_time": market.get("close_time"),
                })

        cursor = data.get("cursor")
        if not cursor:
            break

    return sorted(markets, key=lambda m: m.get("volume", 0), reverse=True)

Placing an Election Trade

Once you have a signal and a ticker:

import requests
import json


KALSHI_BASE = "https://trading-api.kalshi.com/trade-api/v2"

def place_election_trade(
    ticker: str,
    side: str,
    contracts: int,
    price_cents: int,
    headers: dict,
) -> dict:
    """Place a limit order on a Kalshi election market.

    side: 'yes' or 'no'
    price_cents: price in cents (1-99)
    """
    order_path = "/trade-api/v2/portfolio/orders"
    order_headers = build_kalshi_headers(
        "POST", order_path,
        api_key=headers["KALSHI-ACCESS-KEY"],
        private_key_path="kalshi_private_key.pem",
    )

    payload = {
        "ticker": ticker,
        "action": "buy",
        "side": side,
        "count": contracts,
        "type": "limit",
        "yes_price": price_cents if side == "yes" else None,
        "no_price": price_cents if side == "no" else None,
    }
    payload = {k: v for k, v in payload.items() if v is not None}

    resp = requests.post(
        f"{KALSHI_BASE}/portfolio/orders",
        headers=order_headers,
        json=payload,
    )

    return resp.json()

WebSocket for Real-Time Election Prices

For bots that need to react to price movements rather than polling on an interval:

import json
import asyncio
import websockets


KALSHI_WS = "wss://trading-api.kalshi.com/trade-api/ws/v2"

async def stream_election_prices(
    tickers: list[str],
    api_key: str,
    private_key_path: str,
    callback,
):
    """Stream real-time price updates for election markets."""
    async with websockets.connect(KALSHI_WS) as ws:
        auth_msg = {
            "id": 1,
            "cmd": "login",
            "params": {"api_key": api_key},
        }
        await ws.send(json.dumps(auth_msg))
        await ws.recv()

        for ticker in tickers:
            sub_msg = {
                "id": 2,
                "cmd": "subscribe",
                "params": {
                    "channels": ["ticker"],
                    "market_tickers": [ticker],
                },
            }
            await ws.send(json.dumps(sub_msg))

        async for message in ws:
            data = json.loads(message)
            if data.get("type") == "ticker":
                await callback(data)

For full authentication details including WebSocket auth and FIX protocol setup, see the Kalshi API Guide.


Architecture

A production election bot has several components. Here’s the high-level pipeline:

┌─────────────────────────────────────────────────────────────┐
│                  DATA INGESTION LAYER                       │
│                                                             │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐   │
│  │ Polling  │ │ Economic │ │  News /  │ │  Polymarket  │   │
│  │  Feeds   │ │   Data   │ │Sentiment │ │    Prices    │   │
│  │(538,RCP) │ │(BLS,BEA) │ │(AP,LLM)  │ │  (CLOB API)  │   │
│  └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬───────┘   │
│       └──────────┬──┴───────────┬┘              │           │
│                  ▼              ▼                │           │
│  ┌───────────────────────────────────┐          │           │
│  │       ELECTION MODEL ENGINE       │          │           │
│  │                                   │          │           │
│  │  Polls → Polling Model ─────┐    │          │           │
│  │  Econ → Fundamentals Model ─┤    │          │           │
│  │  News → Sentiment Adj. ─────┤    │          │           │
│  │                             ▼    │          │           │
│  │              Ensemble Probability │          │           │
│  │              (per race + control) │          │           │
│  └──────────────┬────────────────────┘          │           │
│                 │                                │           │
│                 ▼                                ▼           │
│  ┌──────────────────────────────────────────────────────┐   │
│  │              SIGNAL GENERATION                       │   │
│  │                                                      │   │
│  │  Model Prob vs Kalshi Price → Directional Signal     │   │
│  │  Kalshi Price vs Poly Price → Arbitrage Signal       │   │
│  │  Control Price vs Race Prices → Correlation Signal   │   │
│  └──────────────────────┬───────────────────────────────┘   │
│                         ▼                                   │
│  ┌──────────────────────────────────────────────────────┐   │
│  │              RISK & POSITION MANAGEMENT              │   │
│  │                                                      │   │
│  │  Kelly Criterion Sizing │ Max Position Limits        │   │
│  │  Correlation-Adj. Exposure │ Drawdown Circuit Breaker│   │
│  └──────────────────────┬───────────────────────────────┘   │
│                         ▼                                   │
│  ┌──────────────────────────────────────────────────────┐   │
│  │              EXECUTION LAYER                         │   │
│  │                                                      │   │
│  │  Kalshi REST/FIX ──── Order Placement                │   │
│  │  Polymarket CLOB ──── Cross-Platform Arb Execution   │   │
│  │  WebSocket ────────── Real-Time Fill Monitoring       │   │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

Python Class Skeleton

import asyncio
from dataclasses import dataclass, field
from abc import ABC, abstractmethod


@dataclass
class ElectionMarket:
    ticker: str
    race: str
    party: str
    kalshi_yes: float = 0.0
    kalshi_no: float = 0.0
    poly_yes: float = 0.0
    poly_no: float = 0.0
    model_prob: float = 0.5
    last_updated: float = 0.0


@dataclass
class Signal:
    ticker: str
    side: str
    edge: float
    strategy: str
    contracts: int = 0
    price_cents: int = 0


class ElectionModel(ABC):
    @abstractmethod
    def update(self, race: str, new_data: dict) -> float:
        """Return updated probability for the given race."""
        ...


class PollingModel(ElectionModel):
    def __init__(self):
        self.polls: dict[str, list[dict]] = {}
        self.pollster_weights: dict[str, float] = {}

    def update(self, race: str, new_data: dict) -> float:
        if race not in self.polls:
            self.polls[race] = []
        self.polls[race].append(new_data)
        return self._weighted_average(race)

    def _weighted_average(self, race: str) -> float:
        polls = self.polls[race][-20:]  # recent polls only
        total_weight = 0.0
        weighted_sum = 0.0
        for poll in polls:
            weight = self.pollster_weights.get(poll["pollster"], 1.0)
            weight *= poll.get("sample_size", 500) / 500
            weighted_sum += poll["dem_pct"] * weight
            total_weight += weight
        avg_dem = weighted_sum / total_weight if total_weight else 50.0
        return avg_dem / 100


class FundamentalsModel(ElectionModel):
    def __init__(self, pres_approval: float, generic_ballot: float):
        self.pres_approval = pres_approval
        self.generic_ballot = generic_ballot

    def update(self, race: str, new_data: dict) -> float:
        if "pres_approval" in new_data:
            self.pres_approval = new_data["pres_approval"]
        if "generic_ballot" in new_data:
            self.generic_ballot = new_data["generic_ballot"]
        return self._fundamentals_estimate(race)

    def _fundamentals_estimate(self, race: str) -> float:
        midterm_penalty = -0.03
        approval_effect = (self.pres_approval - 50) * 0.005
        ballot_effect = (self.generic_ballot - 50) * 0.01
        return 0.50 + midterm_penalty + approval_effect + ballot_effect


class ElectionBot:
    def __init__(self, kalshi_api_key: str, kalshi_private_key: str):
        self.markets: dict[str, ElectionMarket] = {}
        self.models: list[ElectionModel] = []
        self.signals: list[Signal] = []
        self.max_position_per_market: int = 100
        self.min_edge: float = 0.03
        self.kalshi_api_key = kalshi_api_key
        self.kalshi_private_key = kalshi_private_key

    def add_model(self, model: ElectionModel):
        self.models.append(model)

    def update_model_estimates(self, race: str, data: dict):
        probs = [m.update(race, data) for m in self.models]
        ensemble_prob = sum(probs) / len(probs) if probs else 0.5
        if race in self.markets:
            self.markets[race].model_prob = ensemble_prob

    def generate_signals(self) -> list[Signal]:
        signals = []
        for ticker, market in self.markets.items():
            edge = market.model_prob - market.kalshi_yes
            if abs(edge) >= self.min_edge:
                side = "yes" if edge > 0 else "no"
                price = market.kalshi_yes if side == "yes" else market.kalshi_no
                contracts = self._size_position(abs(edge), price)
                signals.append(Signal(
                    ticker=ticker,
                    side=side,
                    edge=edge,
                    strategy="model_divergence",
                    contracts=contracts,
                    price_cents=int(price * 100),
                ))
        return signals

    def _size_position(self, edge: float, price: float) -> int:
        """Kelly criterion sizing capped at max position."""
        if price <= 0 or price >= 1:
            return 0
        b = (1 / price) - 1
        p = price + edge
        kelly_fraction = (b * p - (1 - p)) / b
        kelly_fraction = max(0, min(kelly_fraction, 0.10))
        contracts = int(kelly_fraction * self.max_position_per_market)
        return min(contracts, self.max_position_per_market)

    async def run(self):
        """Main loop: update data, generate signals, execute trades."""
        while True:
            # 1. Fetch latest polling data and update models
            # 2. Fetch latest Kalshi and Polymarket prices
            # 3. Generate signals
            self.signals = self.generate_signals()
            # 4. Execute trades for signals that pass risk checks
            for signal in self.signals:
                if self._passes_risk_checks(signal):
                    await self._execute(signal)
            await asyncio.sleep(60)

    def _passes_risk_checks(self, signal: Signal) -> bool:
        return signal.contracts > 0 and abs(signal.edge) >= self.min_edge

    async def _execute(self, signal: Signal):
        """Place order on Kalshi. Implementation uses REST or FIX."""
        pass

This skeleton separates concerns: data ingestion, model estimation, signal generation, risk management, and execution. Each component can be developed and tested independently. The model layer uses an abstract base class so you can plug in polling models, fundamentals models, and sentiment models without changing the bot logic.

For position sizing details, see the Kelly Criterion Bot guide.


Realistic Expectations

Election trading bots face constraints that are fundamentally different from sports betting or crypto market bots.

Elections Are Rare Events

The 2026 midterms happen once. You cannot backtest on a large sample of identical events. Historical midterm data gives you maybe 20 data points (40 years of midterms), each with different political contexts. This is not like backtesting a sports model on thousands of games. Overfitting to historical election data is extremely easy and extremely dangerous.

Polling Errors Are Systematic and Correlated

When polls miss, they miss in the same direction across multiple races. The 2016 and 2020 elections both saw systematic polling errors — polls underestimated one party’s support across many states simultaneously. This means a polling model bot is exposed to correlated risk: if polls are wrong, your entire portfolio of election positions can move against you at once.

The 2016 miss: national polls were close but state-level polls in the Midwest (Michigan, Wisconsin, Pennsylvania) underestimated Trump support by 4-8 points. A bot trading individual race contracts based on state polls would have been caught on the wrong side of multiple positions simultaneously.

The 2020 miss: polls overestimated Biden’s lead in most swing states by 3-5 points. Biden still won, but the margin was much tighter than polls suggested. A bot that aggressively bought Democratic race contracts at poll-implied prices would have faced significant mark-to-market losses even though many positions eventually resolved profitably.

The lesson: build polling error correlation into your model. When sizing positions, treat your portfolio of election bets as a single correlated exposure, not as independent bets.

Market Efficiency Increases Near Election Day

Early in the cycle (12+ months out), election markets are thin and potentially inefficient. Your model may identify significant mispricings. As election day approaches, more capital flows in, more models are running, and prices converge toward consensus. The easy mispricings disappear.

This has implications for bot strategy:

  • Early cycle: Wider mispricings but less certainty in your own model. Trade smaller.
  • Mid cycle: Model confidence increases as polls accumulate. Trade larger positions but expect smaller edges.
  • Late cycle: Prices are mostly efficient. Focus on event-driven trades (debates, October surprises) and arbitrage rather than model-vs-market divergence.

What Works vs. What Doesn’t

Works:

  • Cross-platform arbitrage (structural pricing differences persist because different user bases)
  • Fundamentals-based trading early in the cycle (when polls are sparse but economic data is strong)
  • Correlated market trading (propagation lag between control and individual race contracts)
  • Event reaction trading during debates and major news

Marginal:

  • Pure polling model vs. market (the market already incorporates the same polls)
  • Market-making on thin individual race contracts (adverse selection risk is high around news events)

Doesn’t work:

  • High-frequency trading on election markets (not enough continuous volume)
  • Pure sentiment trading without a probability model (noise overwhelms signal)
  • Assuming polls are perfectly accurate (they aren’t, and the errors are correlated)

Frequently Asked Questions

Can I legally use a bot to trade election markets on Kalshi?

Yes. Kalshi is a CFTC-regulated Designated Contract Market, and programmatic trading via their REST API, WebSocket, and FIX protocol is explicitly supported. Unlike offshore platforms, Kalshi operates under federal oversight with full legal clarity for US-based traders. There is no Terms of Service prohibition on automated trading — Kalshi’s API infrastructure was built for it.

How is trading elections on Kalshi different from Polymarket?

Kalshi is CFTC-regulated, uses USD (not crypto), and requires standard KYC. Polymarket runs on Polygon, uses USDC, and relies on blockchain wallet signatures. Kalshi uses RSA-PSS authentication for its REST API, while Polymarket uses EIP-712 signing. Kalshi contracts are priced in cents (1-99), Polymarket in decimal dollars (0.01-0.99). Both are binary outcome markets, but regulatory status, settlement currency, and API patterns differ significantly. For a full comparison, see Polymarket vs. Kalshi vs. DraftKings.

What 2026 midterm election markets will Kalshi offer?

Kalshi is expected to list markets on Senate control, House control, individual Senate and House races (particularly competitive seats), gubernatorial races, and potentially ballot measures. The 2026 midterms feature 35 Senate seats, all 435 House seats, and 36 governorships. Market creation timing varies — expect control markets well before election day, with individual race markets appearing as the cycle intensifies.

What data do I need to build an election model for Kalshi trading?

At minimum: polling averages from aggregators like FiveThirtyEight or RealClearPolitics, race ratings from Cook Political Report or Sabato’s Crystal Ball, and generic congressional ballot polls. For fundamentals models, add presidential approval ratings, economic indicators (employment, CPI, consumer sentiment), and historical midterm patterns. The more data sources you integrate, the more robust your probability estimates. See the data sources section above for specific feeds and a Python model example.

How liquid are Kalshi election markets?

Election markets are among the most liquid on Kalshi, particularly Senate and House control contracts. The 2024 presidential cycle proved that prediction market election trading attracts significant volume. Midterm markets are typically less liquid than presidential cycle markets, but top-of-ticket races should support meaningful position sizes. Liquidity increases substantially in the final 2-3 months before election day.

Can I arbitrage between Kalshi and Polymarket on election markets?

Yes, and this is one of the most practical election bot strategies. Both platforms list contracts on the same political outcomes, but they serve different user bases and reprice at different speeds. Structural pricing divergences of 2-5 percentage points are common. You need funded accounts on both platforms and must verify that resolution criteria match exactly before trading. See Cross-Platform Arbitrage for the full execution framework.

What returns should I expect from an election bot on Kalshi?

Returns vary by strategy. Polling model bots that correctly identify 3-5 point mispricings and trade with disciplined sizing might generate 10-25% returns over a cycle, but election cycles are small samples with high variance. Arbitrage strategies between Kalshi and Polymarket produce more consistent but smaller returns (1-3% per opportunity). Market-making on liquid election contracts can generate 5-15% annualized. All strategies face the fundamental challenge that elections are rare events with systematic, correlated errors in the underlying data.


See Also


Guide updated March 2026. Not financial advice. Built for builders.