Appearance
Kalshi Search & Baskets
Discover, cluster, correlate, and assemble baskets across the live Kalshi prediction-market universe. These endpoints turn the open market set into a queryable database with three lenses — keyword search, embedding similarity, and time-series math — joinable in a single request, plus a composite layer that pushes portfolio construction (correlation-aware basket building, Kelly sizing, backtests) entirely server-side so a client can satisfy each high-level use case with one HTTP call.
Pair this surface with the Prediction Markets Agent for narrative analysis on individual events, and with Events History for per-event time series.
Like the Chat Completions and Responses endpoints, these are direct REST endpoints called relative to the Octagon API base URL:
text
https://api.octagonai.co/v1Authentication
Every request requires a valid Octagon API key passed as a Bearer token. Requests without one return 401.
Authorization: Bearer your-octagon-api-keyConventions
| Convention | Details |
|---|---|
| Path style | Kebab-case (e.g. /behavioral-clusters, /markets-with-edge, /cluster-peers). |
| Field style | snake_case in query strings, request bodies, and response payloads. |
| Timestamps | RFC 3339 UTC (2026-08-19T00:00:00Z). |
| Prices | 0–1 fraction units (Kalshi cents are normalized server-side). |
| Pagination | base64(JSON) cursor tokens. Pass next_cursor from a previous page back as cursor=.... |
| Unknown params | Return 400 with a list of unknown query parameter names. |
| Bad bodies | Pydantic validation errors return 422 for missing or wrongly-typed fields. |
Data model
The endpoints read from a set of tables maintained by the nightly Kalshi sync + clustering pipeline.
| Table | Purpose |
|---|---|
kalshi_markets_active | Live market universe. Includes a 256-d pgvector embedding and a GIN-indexed search_tsv TSVECTOR for full-text search. |
kalshi_events_active | Event metadata, including category / subcategory. |
kalshi_market_candles / kalshi_market_candles_daily | Hourly and daily OHLC bars. |
kalshi_clusters (+ assignments + runs) | Thematic K-means clusters over market embeddings, LLM-labeled, atomically swapped per nightly run. |
kalshi_behavioral_clusters (+ assignments + runs) | Behavioral K-means clusters over 30-day daily return vectors. Covers only markets with ≥14 days of daily candles. |
events_history | Per-run event snapshots — the same table that powers /prediction-markets/events. Drives /kalshi/markets-with-edge. |
Primitives
Single-purpose endpoints that a client can compose freely.
GET /kalshi/markets
Structured + full-text search over the open Kalshi universe.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | No | Full-text query against search_tsv (title + subtitle + ticker). Results are ranked by ts_rank when supplied. Minimum 3 characters. |
category | string | No | Matches event category or subcategory (exact). |
series_ticker | string | No | Filter by Kalshi series (exact match). |
series_prefix | string | No | LIKE '<prefix>%' filter on series_ticker — tree-style browsing (e.g. KXBTC matches KXBTCD, KXBTCY, KXBTCMAX100, …). |
event_ticker | string | No | Filter to markets in a single event. |
close_before | datetime | No | Only markets closing on or before this RFC 3339 timestamp. |
min_volume_24h | float | No | Floor on volume_24h. |
sort_by | string | No | volume_24h, close_time, or last_price. Overrides the default (rank when q, otherwise volume_24h) so you can take a true top-N across the entire universe without client-side reranking. |
limit | integer | No | Page size. Default 50; min 1; max 200. |
cursor | string | No | Pagination cursor returned from a previous response. |
Example
Python
import requests
url = "https://api.octagonai.co/v1/prediction-markets/kalshi/markets"
headers = {"Authorization": "Bearer your-octagon-api-key"}
params = {
"q": "bitcoin",
"category": "crypto",
"min_volume_24h": 10000,
"limit": 20,
}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
print(response.json())JavaScript
const params = new URLSearchParams({
q: "bitcoin",
category: "crypto",
min_volume_24h: "10000",
limit: "20",
});
const response = await fetch(
`https://api.octagonai.co/v1/prediction-markets/kalshi/markets?${params}`,
{ headers: { Authorization: "Bearer your-octagon-api-key" } }
);
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
console.log(await response.json());sh
curl -G "https://api.octagonai.co/v1/prediction-markets/kalshi/markets" \
-H "Authorization: Bearer your-octagon-api-key" \
--data-urlencode "q=bitcoin" \
--data-urlencode "category=crypto" \
--data-urlencode "min_volume_24h=10000" \
--data-urlencode "limit=20"Example response
json
{
"data": [
{
"market_ticker": "KXBTCD-26DEC31-T100000",
"event_ticker": "KXBTCD-26DEC31",
"series_ticker": "KXBTCD",
"title": "Bitcoin above $100k by end of Dec 2026",
"subtitle": null,
"status": "open",
"close_time": "2026-12-31T23:59:59Z",
"last_price": 0.58,
"yes_bid": 0.57,
"yes_ask": 0.59,
"no_bid": 0.41,
"no_ask": 0.43,
"volume": 1234,
"volume_24h": 1234,
"liquidity": 100,
"open_interest": 5,
"category": "crypto",
"event_name": "Bitcoin price ladders"
}
],
"next_cursor": null,
"has_more": false
}GET /kalshi/markets/similar
Cosine-distance similarity over kalshi_markets_active.embedding. Catches matches keyword search misses (e.g. "Will Bitcoin pierce six figures" ↔ "BTC above $100k") and is freely combinable with structured filters.
Query parameters — exactly one of anchor_ticker or q is required.
| Parameter | Type | Required | Description |
|---|---|---|---|
anchor_ticker | string | Cond. | Use the stored embedding of this market as the anchor. Zero added latency. Mutually exclusive with q. |
q | string | Cond. | Anchor by free-text query. Embedded at request time via text-embedding-3-small at dim=256. Adds one OpenAI roundtrip. Mutually exclusive with anchor_ticker. |
top_k | integer | No | Number of nearest neighbors. Default 25; min 1; max 100. |
category | string | No | Restrict to a category. |
min_volume_24h | float | No | Floor on volume_24h. |
close_before | datetime | No | Only markets closing before this RFC 3339 timestamp. |
Supplying neither or both of anchor_ticker / q returns 400.
Example — anchor by ticker (no OpenAI call)
Python
import requests
response = requests.get(
"https://api.octagonai.co/v1/prediction-markets/kalshi/markets/similar",
headers={"Authorization": "Bearer your-octagon-api-key"},
params={"anchor_ticker": "KXBTCD-26DEC31-T100000", "top_k": 10},
)
response.raise_for_status()
print(response.json())JavaScript
const params = new URLSearchParams({
anchor_ticker: "KXBTCD-26DEC31-T100000",
top_k: "10",
});
const response = await fetch(
`https://api.octagonai.co/v1/prediction-markets/kalshi/markets/similar?${params}`,
{ headers: { Authorization: "Bearer your-octagon-api-key" } }
);
console.log(await response.json());sh
curl -G "https://api.octagonai.co/v1/prediction-markets/kalshi/markets/similar" \
-H "Authorization: Bearer your-octagon-api-key" \
--data-urlencode "anchor_ticker=KXBTCD-26DEC31-T100000" \
--data-urlencode "top_k=10"Example — anchor by free-text query (embedded server-side)
sh
curl -G "https://api.octagonai.co/v1/prediction-markets/kalshi/markets/similar" \
-H "Authorization: Bearer your-octagon-api-key" \
--data-urlencode "q=Will Bitcoin pierce six figures" \
--data-urlencode "category=crypto" \
--data-urlencode "min_volume_24h=10000"Example response
json
{
"anchor_ticker": "KXBTCD-26DEC31-T100000",
"anchor_query": null,
"data": [
{
"market_ticker": "KXETHU-26DEC31-T10000",
"event_ticker": "KXETHU-26DEC31",
"title": "ETH above $10k by Dec 2026",
"category": "crypto",
"distance": 0.18
}
]
}Lower distance = closer cosine similarity.
GET /kalshi/series
Per-series rollup over kalshi_markets_active — one row per series with active counts, volume, and dominant category. Replaces "paginate /kalshi/markets and reduce client-side" (10–30 calls) with a single request, and gives you a series-level entry point into the universe tree.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
series_prefix | string | No | LIKE '<prefix>%' filter on series_ticker (e.g. KXBTC matches KXBTCD, KXBTCY, KXBTCMAX100, …). |
category | string | No | Filter against the dominant category for each series. |
min_volume_24h | float | No | Floor on total_volume_24h. |
sort_by | string | No | total_volume_24h (default), market_count, or active_count. |
limit | integer | No | Default 50; min 1; max 200. |
cursor | string | No | Pagination cursor. |
Example
sh
curl -G "https://api.octagonai.co/v1/prediction-markets/kalshi/series" \
-H "Authorization: Bearer your-octagon-api-key" \
--data-urlencode "series_prefix=KXBTC" \
--data-urlencode "sort_by=total_volume_24h"Example response
json
{
"data": [
{
"series_ticker": "KXBTCD",
"series_title": "Bitcoin Daily",
"market_count": 42,
"active_count": 35,
"total_volume_24h": 1234567.89,
"dominant_category": "Crypto",
"categories": ["Crypto"],
"last_seen_at": "2026-05-25T19:39:00Z"
}
],
"next_cursor": null,
"has_more": false
}Performance
This endpoint runs a full-aggregate GROUP BY over the active universe. Expect a 1–3 s first call until the rollup is materialized as part of the nightly sync.
GET /kalshi/series/{series_ticker}/events
List events inside a single series — replaces the manual q="IPO 2026" keyword-search step when you already know the series.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | No | Optional free-text filter within the series (title / subtitle). |
limit | integer | No | Default 50; min 1; max 200. |
cursor | string | No | Pagination cursor. |
Example
sh
curl "https://api.octagonai.co/v1/prediction-markets/kalshi/series/KXIPO/events?limit=20" \
-H "Authorization: Bearer your-octagon-api-key"Example response
json
{
"series_ticker": "KXIPO",
"data": [
{
"event_ticker": "KXIPOSPACEX",
"title": "When will SpaceX IPO?",
"category": "Companies",
"close_time": "2027-01-01T00:00:00Z",
"market_count": 8,
"active_market_count": 6,
"total_volume_24h": 41230.50
}
],
"next_cursor": null,
"has_more": false
}GET /kalshi/events/{event_ticker}/markets
Walk an event's child markets — e.g. every strike in an IPO date-ladder, every state in an election, every band in a recession-by-date event. Removes the manual "search for event then filter" step.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
min_volume_24h | float | No | Floor on volume_24h. |
limit | integer | No | Default 100; min 1; max 500. |
cursor | string | No | Pagination cursor. |
Example
sh
curl "https://api.octagonai.co/v1/prediction-markets/kalshi/events/KXIPOSPACEX/markets?limit=50" \
-H "Authorization: Bearer your-octagon-api-key"Example response
json
{
"event_ticker": "KXIPOSPACEX",
"data": [
{
"market_ticker": "KXIPOSPACEX-26JUN01",
"title": "SpaceX IPO before June 2026?",
"status": "open",
"yes_bid": 0.12,
"no_bid": 0.86,
"last_price": 0.13,
"volume_24h": 2300,
"close_time": "2026-06-01T00:00:00Z",
"category": "Companies"
}
],
"next_cursor": null,
"has_more": false
}GET /kalshi/clusters
Browse the current run of thematic (embedding-based) clusters. Each cluster has an LLM-generated label and description, the cluster size, and a few representative sample_titles.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Default 200; min 1; max 500. |
sample_titles | integer | No | Number of sample titles per cluster. Default 4; min 0; max 20. |
label_contains | string | No | Case-insensitive substring filter on the cluster label. |
Example
sh
curl -G "https://api.octagonai.co/v1/prediction-markets/kalshi/clusters" \
-H "Authorization: Bearer your-octagon-api-key" \
--data-urlencode "label_contains=fed" \
--data-urlencode "sample_titles=4"Example response
json
{
"data": [
{
"cluster_id": 42,
"label": "Fed decisions",
"description": "Markets resolving on FOMC actions and Fed policy outcomes",
"size": 14,
"sample_titles": [
"Will the Fed hike in March?",
"FOMC March: 25bp cut?"
],
"created_at": "2026-05-21T03:00:00Z"
}
]
}GET /kalshi/clusters/{cluster_id}/markets
Markets in one thematic cluster, ranked ascending by distance to the cluster centroid. Same response shape and cursor pagination as GET /kalshi/markets.
Example
sh
curl "https://api.octagonai.co/v1/prediction-markets/kalshi/clusters/42/markets?limit=20" \
-H "Authorization: Bearer your-octagon-api-key"GET /kalshi/behavioral-clusters
Same shape as /kalshi/clusters but over the behavioral cluster run — K-means on 30-day daily return vectors. Each row additionally exposes mean_daily_return and daily_volatility. Covers only the subset of markets with ≥14 days of daily candles (the threshold for behavioral eligibility).
Query parameters — identical to GET /kalshi/clusters.
Example
sh
curl "https://api.octagonai.co/v1/prediction-markets/kalshi/behavioral-clusters?limit=40" \
-H "Authorization: Bearer your-octagon-api-key"GET /kalshi/behavioral-clusters/{cluster_id}/markets
Members of one behavioral cluster, same response shape as the thematic version.
sh
curl "https://api.octagonai.co/v1/prediction-markets/kalshi/behavioral-clusters/4/markets?limit=20" \
-H "Authorization: Bearer your-octagon-api-key"GET /kalshi/markets/{market_ticker}/clusters
Return the thematic and behavioral cluster IDs a single market belongs to under the current runs. Useful for driving "no more than N legs from the same cluster" basket constraints client-side when not using /kalshi/baskets/build.
Example
sh
curl "https://api.octagonai.co/v1/prediction-markets/kalshi/markets/KXBTCD-26DEC31-T100000/clusters" \
-H "Authorization: Bearer your-octagon-api-key"Example response
json
{
"market_ticker": "KXBTCD-26DEC31-T100000",
"thematic": {
"cluster_id": 17,
"label": "Crypto price ladders",
"description": "BTC/ETH/SOL strike-ladder markets",
"size": 122
},
"behavioral": {
"cluster_id": 4,
"label": "Mean-reverting low-vol",
"size": 87,
"mean_daily_return": -0.002,
"daily_volatility": 0.018
}
}Either field can be null if the market isn't in a current-run assignment for that clustering kind.
POST /kalshi/markets/correlations
Pairwise Pearson correlation matrix over close-price series for N markets. Side-aware: pass per-ticker sides to mix YES and NO legs without re-pulling candles (we flip the sign for each NO leg, since corr(YES_A, NO_B) = -corr(YES_A, YES_B)).
Request body
| Field | Type | Required | Description |
|---|---|---|---|
market_tickers | array of strings | Yes | 2–100 distinct market tickers. |
sides | array of strings | No | Per-ticker side — "yes" or "no", same length as market_tickers. Defaults to all yes. Filtered to the present tickers in the response. |
window_days | integer | Yes | Lookback window. Min 1, max 730. |
interval | string | No | "1h" or "1d". Auto-picked when omitted: 1d when window_days ≥ 90, else 1h. 1h reads hourly candles, 1d reads daily. |
include_cell_detail | boolean | No | When true, include a cells_detail array with overlap counts and a reason code per pair. Default false. |
Example
sh
curl -X POST "https://api.octagonai.co/v1/prediction-markets/kalshi/markets/correlations" \
-H "Authorization: Bearer your-octagon-api-key" \
-H "Content-Type: application/json" \
-d '{
"market_tickers": ["KXBTCD-26DEC31-T100000", "KXETHU-26DEC31-T10000", "KXSOL-26DEC31-T200"],
"sides": ["yes", "yes", "no"],
"window_days": 90,
"interval": "1d",
"include_cell_detail": true
}'Example response
json
{
"tickers": ["KXBTCD-26DEC31-T100000", "KXETHU-26DEC31-T10000", "KXSOL-26DEC31-T200"],
"sides": ["yes", "yes", "no"],
"matrix": [
[1.0, 0.92, -0.81],
[0.92, 1.0, -0.74],
[-0.81, -0.74, 1.0]
],
"ranked_pairs": [
{ "ticker_a": "KXBTCD-26DEC31-T100000", "ticker_b": "KXSOL-26DEC31-T200", "correlation": -0.81 },
{ "ticker_a": "KXETHU-26DEC31-T10000", "ticker_b": "KXSOL-26DEC31-T200", "correlation": -0.74 },
{ "ticker_a": "KXBTCD-26DEC31-T100000", "ticker_b": "KXETHU-26DEC31-T10000", "correlation": 0.92 }
],
"cells_detail": [
{ "ticker_a": "KXBTCD-26DEC31-T100000", "ticker_b": "KXETHU-26DEC31-T10000", "correlation": 0.92, "overlap_count": 720, "reason": "ok" },
{ "ticker_a": "KXBTCD-26DEC31-T100000", "ticker_b": "KXSOL-26DEC31-T200", "correlation": -0.81, "overlap_count": 720, "reason": "ok" },
{ "ticker_a": "KXETHU-26DEC31-T10000", "ticker_b": "KXSOL-26DEC31-T200", "correlation": -0.74, "overlap_count": 720, "reason": "ok" }
],
"window_days": 90,
"interval": "1d",
"missing": []
}matrixcells are side-flipped server-side using the suppliedsides(or allyeswhen omitted) — no client-side sign adjustment.ranked_pairsis the upper-triangle of the matrix sorted ascending by correlation — the most-uncorrelated pairs come first.cells_detailisnullunlessinclude_cell_detail: true. Each entry'sreasonis"ok","insufficient_overlap"(fewer than 3 paired observations), or"zero_variance"(constant series).- Markets without candle data in the window appear in
missingand are dropped from the matrix. - Cells with fewer than 3 paired observations or constant series come back as
nullinmatrix(and reflected incells_detailwith the appropriatereason).
POST /kalshi/markets/edge
Per-ticker Octagon model-edge lookup. Accepts a mix of market and event tickers — markets are resolved to their parent event server-side. Pure DB read; the model is never invoked at request time.
Typical use: pull model priors for a known list of tickers, then feed model_probability into POST /kalshi/baskets/size for Kelly sizing.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
tickers | array of strings | Yes | 1–100 tickers. Mix of market_ticker and event_ticker values is allowed. |
run_id | string (UUID) | No | Specific events_history run. Defaults to the most recent run. |
Example
sh
curl -X POST "https://api.octagonai.co/v1/prediction-markets/kalshi/markets/edge" \
-H "Authorization: Bearer your-octagon-api-key" \
-H "Content-Type: application/json" \
-d '{
"tickers": ["KXBTCD-26DEC31-T100000", "KXIPOSPACEX-26JUN01"]
}'Example response
json
{
"run_id": "8c0e1b9a-1f44-4d2a-9b7e-2d6e5f50a912",
"captured_at": "2026-05-25T19:39:00Z",
"data": [
{
"input_ticker": "KXBTCD-26DEC31-T100000",
"market_ticker": "KXBTCD-26DEC31-T100000",
"event_ticker": "KXBTCD-26DEC31",
"title": "Bitcoin above $100k by end of Dec 2026",
"series_category": "Crypto",
"model_probability": 0.62,
"market_probability": 0.55,
"edge_pp": 7.0,
"expected_return": 0.13,
"confidence_score": 6.0,
"total_volume": 12345.67,
"total_open_interest": 890.0,
"status": "scored",
"captured_at": "2026-05-25T19:39:00Z"
}
]
}market_tickerisnullwhen the input was an event ticker (no per-market resolution applies).statusis"scored"when the parent event was scored in the run, or"unscored"when it wasn't —model_probability/edge_pp/expected_returnwill benullin that case.
POST /kalshi/baskets/candles
OHLC bars for a weighted basket NAV.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
market_tickers | array of strings | Yes | Tickers to combine into the basket. |
weights | array of numbers | No | Per-ticker weights. Defaults to equal weight. Must match market_tickers length and sum to a positive value (renormalized server-side). |
timeframe | string | Yes | One of 1w, 1m, 3m, 6m, 1y. Drives both the lookback window and the candle bin size (see below). |
| Timeframe | Lookback | Bin |
|---|---|---|
1w | 7 days | 3h |
1m | 30 days | 12h |
3m | 90 days | 1d |
6m | 180 days | 3d |
1y | 365 days | 7d |
Example
sh
curl -X POST "https://api.octagonai.co/v1/prediction-markets/kalshi/baskets/candles" \
-H "Authorization: Bearer your-octagon-api-key" \
-H "Content-Type: application/json" \
-d '{
"market_tickers": ["KXBTCD-26DEC31-T100000", "KXETHU-26DEC31-T10000"],
"weights": [0.6, 0.4],
"timeframe": "1y"
}'Example response
json
{
"timeframe": "1y",
"interval_source": "1d",
"candles": [
{ "time": 1735776000, "open": 0.41, "high": 0.45, "low": 0.40, "close": 0.44 }
],
"tickers": ["KXBTCD-26DEC31-T100000", "KXETHU-26DEC31-T10000"],
"missing": []
}time values are Unix-seconds boundaries for each bin. Markets without data in the window appear in missing and are excluded from the basket NAV.
Composite endpoints
Higher-level endpoints that combine the primitives plus portfolio math server-side so a client can satisfy a use case in a single request.
GET /kalshi/markets/{market_ticker}/cluster-peers
One-call "show me others in the same theme" — replaces the two-step GET /markets/{ticker}/clusters → GET /clusters/{id}/markets dance.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
kind | string | No | thematic (default) or behavioral. Selects the clustering to use. |
limit | integer | No | Number of peers, excluding the anchor. Default 50; min 1; max 200. |
Example
sh
curl "https://api.octagonai.co/v1/prediction-markets/kalshi/markets/KXBTCD-26DEC31-T100000/cluster-peers?kind=thematic&limit=50" \
-H "Authorization: Bearer your-octagon-api-key"Example response
json
{
"market_ticker": "KXBTCD-26DEC31-T100000",
"kind": "thematic",
"cluster": {
"cluster_id": 17,
"label": "Crypto price ladders",
"description": "BTC/ETH/SOL strike-ladder markets",
"size": 122
},
"data": [
{ "market_ticker": "KXETHU-26DEC31-T10000", "title": "ETH above $10k by Dec 2026", "distance": 0.04 }
]
}POST /kalshi/baskets/size
Apply fractional Kelly to a set of legs the caller has already picked. The server looks up each leg's live yes_bid / no_bid from kalshi_markets_active to determine entry price.
Kelly formula (binary outcome)
For a leg with entry price p (the bid for the chosen side) and model probability q:
edge_pp = (q - p) * 100
payoff b = (1 - p) / p
fraction = (b * q - (1 - q)) / b # classic Kelly
f_capped = min(max(fraction, 0), kelly_multiplier)Sums of f_capped are scaled down so total allocation stays within the multiplier cap. Legs with no edge (q < p) get kelly_fraction = 0.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
bankroll_usd | number | Yes | Must be > 0. |
kelly_multiplier | number | Yes | 0 ≤ x ≤ 1. Cap on total bankroll fraction (e.g. 0.25 for "quarter Kelly"). |
legs | array of objects | Yes | 1–50 entries, each { "market_ticker": string, "side": "yes" | "no", "model_probability": number }. |
Example
sh
curl -X POST "https://api.octagonai.co/v1/prediction-markets/kalshi/baskets/size" \
-H "Authorization: Bearer your-octagon-api-key" \
-H "Content-Type: application/json" \
-d '{
"bankroll_usd": 1000.0,
"kelly_multiplier": 0.25,
"legs": [
{ "market_ticker": "KX-A", "side": "yes", "model_probability": 0.62 },
{ "market_ticker": "KX-B", "side": "no", "model_probability": 0.55 }
]
}'Example response
json
{
"bankroll_usd": 1000.0,
"kelly_multiplier": 0.25,
"total_notional": 168.50,
"legs": [
{
"market_ticker": "KX-A",
"side": "yes",
"model_probability": 0.62,
"price": 0.55,
"edge_pp": 7.0,
"kelly_fraction": 0.127,
"weight": 0.62,
"notional_usd": 104.45
}
]
}POST /kalshi/baskets/build
One-shot diversified basket builder. Pulls a candidate universe, annotates each candidate with its thematic cluster, computes correlations across the pool, greedily selects legs that respect both a per-cluster cap and a pairwise correlation cap, then sizes the legs by the chosen strategy.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
universe | object | Yes | Filters that define the candidate pool. See below. |
n | integer | Yes | Number of legs requested. Min 1; max 20. |
max_per_cluster | integer | Yes | Caps how many legs may share a single thematic cluster. Min 1; max 20. |
max_pairwise_correlation | number | Yes | Range -1.0 to 1.0. Rejects any candidate whose worst pairwise correlation with already-accepted legs exceeds this value. |
candidate_pool_size | integer | Yes | How many markets to consider before greedy selection. Min 2; max 200. |
correlation_window_days | integer | Yes | Lookback for the correlation matrix used during selection. Min 7; max 365. Interval auto-picks 1d for ≥90 days, else 1h. |
sizing | object | Yes | { "strategy": "equal" } or { "strategy": "kelly", "bankroll_usd": ..., "kelly_multiplier": ..., "leg_probabilities": { "<market_ticker>": <probability> } }. Missing probabilities default to 0.5 (no edge → zero allocation). |
universe fields
| Field | Type | Description |
|---|---|---|
market_tickers | array of strings | Explicit candidate pool — 1–200 tickers. Duplicates dropped, caller order preserved, capped to candidate_pool_size. Bypasses the search step entirely. |
q | string | Free-text query — when set, the pool comes from semantic similarity via find_similar_markets. |
anchor_ticker | string | Semantic anchor — same effect as q, but no embedding roundtrip. |
category | string | Restrict to a category. |
series_ticker | string | Restrict to a series. |
min_volume_24h | number | Floor on volume_24h. |
close_before | datetime | Only markets closing before this RFC 3339 timestamp. |
label_contains_any | array | Restrict to markets whose thematic cluster label matches any of these substrings (e.g. ["fed", "cpi"]). |
Universe resolution order
market_tickerssupplied → fetch those tickers directly; skip the search step. The structured filters (category,series_ticker,min_volume_24h,close_before) still apply as post-filters so a noisy list silently narrows rather than failing later in the pipeline.qoranchor_tickersupplied → semantic similarity viafind_similar_markets.- Neither → structured-only via
search_active_markets.
If both market_tickers and q / anchor_ticker are supplied, market_tickers wins. Tickers that don't exist in kalshi_markets_active or that fail the post-filters are silently dropped (no 4xx). Inspect universe_size in the response to confirm how many candidates survived — useful for detecting "all my tickers got filtered out" without comparing array lengths. label_contains_any still post-filters by thematic cluster label, max_per_cluster and max_pairwise_correlation still drive greedy selection, and the response shape is unchanged.
Example
Python
import requests
response = requests.post(
"https://api.octagonai.co/v1/prediction-markets/kalshi/baskets/build",
headers={
"Authorization": "Bearer your-octagon-api-key",
"Content-Type": "application/json",
},
json={
"universe": {"category": "crypto", "min_volume_24h": 10000},
"n": 8,
"max_per_cluster": 2,
"max_pairwise_correlation": 0.6,
"candidate_pool_size": 50,
"correlation_window_days": 60,
"sizing": {
"strategy": "kelly",
"bankroll_usd": 1000,
"kelly_multiplier": 0.25,
"leg_probabilities": {
"KXBTCD-26DEC31-T100000": 0.62,
"KXETHU-26DEC31-T10000": 0.58,
},
},
},
)
response.raise_for_status()
print(response.json())JavaScript
const response = await fetch(
"https://api.octagonai.co/v1/prediction-markets/kalshi/baskets/build",
{
method: "POST",
headers: {
Authorization: "Bearer your-octagon-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
universe: { category: "crypto", min_volume_24h: 10000 },
n: 8,
max_per_cluster: 2,
max_pairwise_correlation: 0.6,
candidate_pool_size: 50,
correlation_window_days: 60,
sizing: {
strategy: "kelly",
bankroll_usd: 1000,
kelly_multiplier: 0.25,
leg_probabilities: {
"KXBTCD-26DEC31-T100000": 0.62,
"KXETHU-26DEC31-T10000": 0.58,
},
},
}),
}
);
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
console.log(await response.json());sh
curl -X POST "https://api.octagonai.co/v1/prediction-markets/kalshi/baskets/build" \
-H "Authorization: Bearer your-octagon-api-key" \
-H "Content-Type: application/json" \
-d '{
"universe": {"category": "crypto", "min_volume_24h": 10000},
"n": 8,
"max_per_cluster": 2,
"max_pairwise_correlation": 0.6,
"candidate_pool_size": 50,
"correlation_window_days": 60,
"sizing": {
"strategy": "kelly",
"bankroll_usd": 1000,
"kelly_multiplier": 0.25,
"leg_probabilities": {
"KXBTCD-26DEC31-T100000": 0.62,
"KXETHU-26DEC31-T10000": 0.58
}
}
}'Example response
json
{
"legs": [
{
"market_ticker": "KXBTCD-26DEC31-T100000",
"title": "Bitcoin above $100k by end of Dec 2026",
"category": "crypto",
"cluster_id": 17,
"cluster_label": "Crypto price ladders",
"volume_24h": 50000,
"price": 0.58,
"side": "yes",
"model_probability": 0.62,
"kelly_fraction": 0.08,
"weight": 0.18,
"notional_usd": 76.40
}
],
"realized_max_pairwise_correlation": 0.54,
"cluster_breakdown": { "17": 2, "22": 2, "31": 1, "44": 1, "58": 2 },
"dropped": [
{ "market_ticker": "KX-X", "reason": "cluster_cap_reached" },
{ "market_ticker": "KX-Y", "reason": "correlation_above_threshold" }
],
"universe_size": 50
}realized_max_pairwise_correlation lets the caller verify the constraint was satisfied; dropped shows which candidates were rejected and why.
POST /kalshi/baskets/backtest
Superset of /kalshi/baskets/candles. Returns the same OHLC bars plus a summary block with backtest statistics computed over the basket NAV series. Request body is identical to /kalshi/baskets/candles.
Annualization details
Kalshi markets trade 24/7, so we annualize by calendar seconds rather than 252 trading days. The basket-candles bin size determines periods_per_year:
| Timeframe | Bin | periods_per_year |
|---|---|---|
1w | 3h | ~2922 |
1m | 12h | ~731 |
3m | 1d | ~365 |
6m | 3d | ~122 |
1y | 7d | ~52 |
sharpe = (mean_period_return / std_period_return) * sqrt(periods_per_year)whenstd > 0, elsenull.annualized_return = (1 + total_return) ** (periods_per_year / n_returns) - 1.max_drawdownis reported as a negative fraction (e.g.-0.092= 9.2% drawdown from peak).win_rateis the share of bin-to-bin returns that were positive.
Example
sh
curl -X POST "https://api.octagonai.co/v1/prediction-markets/kalshi/baskets/backtest" \
-H "Authorization: Bearer your-octagon-api-key" \
-H "Content-Type: application/json" \
-d '{
"market_tickers": ["KX-A","KX-B","KX-C"],
"weights": [0.4, 0.4, 0.2],
"timeframe": "1y"
}'Example response
json
{
"timeframe": "1y",
"interval_source": "1d",
"candles": [],
"tickers": ["KX-A", "KX-B", "KX-C"],
"missing": [],
"summary": {
"total_return": 0.247,
"annualized_return": 0.247,
"sharpe": 1.21,
"max_drawdown": -0.092,
"win_rate": 0.55,
"first_nav": 0.40,
"final_nav": 0.50,
"observation_count": 52
}
}POST /kalshi/baskets/validate
One-call portfolio diagnostics on a hand-built basket. Returns concentration metrics, side-aware pairwise correlations, calendar clashes, duplicate underliers, and a list of human-readable warnings — useful as a final sanity check before placing orders.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
legs | array of objects | Yes | Each leg: { "market_ticker": string, "side": "yes" | "no", "stake_usd": number }. |
bankroll_usd | number | No | When set, max_leg_pct is computed against bankroll_usd instead of total stake. |
correlation_window_days | integer | Yes | Lookback for the pairwise correlation matrix. Min 7; max 730. |
correlation_interval | string | No | "1h" or "1d". Auto-picks 1d when correlation_window_days ≥ 90, else 1h. |
max_pairwise_correlation | number | No | Soft threshold — emits a warning if any pair exceeds it. No legs are dropped. |
calendar_clash_window_days | integer | Yes | Window for grouping near-simultaneous close_time clusters. Min 1; max 90. |
Side-aware: corr(YES_A, NO_B) = -corr(YES_A, YES_B), so YES/NO legs are flipped server-side without re-pulling candles.
Example
sh
curl -X POST "https://api.octagonai.co/v1/prediction-markets/kalshi/baskets/validate" \
-H "Authorization: Bearer your-octagon-api-key" \
-H "Content-Type: application/json" \
-d '{
"legs": [
{"market_ticker": "KXGPT-OPEN-26JUL01", "side": "yes", "stake_usd": 170},
{"market_ticker": "KXAGANNOUNCE-26-SEP01", "side": "no", "stake_usd": 160}
],
"bankroll_usd": 1000,
"correlation_window_days": 30,
"correlation_interval": "1h",
"max_pairwise_correlation": 0.5,
"calendar_clash_window_days": 7
}'Example response
json
{
"total_stake_usd": 330.0,
"bankroll_usd": 1000,
"max_leg_pct": 0.17,
"cluster_breakdown_thematic": { "40": ["KXGPT-OPEN-26JUL01"] },
"cluster_breakdown_behavioral": { "21": ["KXAGANNOUNCE-26-SEP01"] },
"unassigned_market_tickers": [],
"max_pairwise_correlation": -0.42,
"pairwise_correlations": [
{ "ticker_a": "KXGPT-OPEN-26JUL01", "ticker_b": "KXAGANNOUNCE-26-SEP01", "correlation": -0.42 }
],
"calendar_clashes": [
{ "window_start": "2026-07-01T00:00:00Z", "window_end": "2026-07-08T00:00:00Z", "market_tickers": ["KXGPT-OPEN-26JUL01"] }
],
"duplicate_underliers": [
{ "event_ticker": "KXIPO", "market_tickers": ["KX-A", "KX-B"] }
],
"warnings": [
"Single leg is 45% of bankroll",
"Pair KX-A / KX-B exceeds max_pairwise_correlation threshold (0.62 > 0.5)"
]
}max_leg_pctis the largest leg's share of the denominator (bankroll_usdwhen supplied, otherwise the total stake).cluster_breakdown_thematic/cluster_breakdown_behavioralmap cluster IDs to the leg tickers that fall into them under the current runs. Legs without a current-run assignment land inunassigned_market_tickers.pairwise_correlationsalready reflects side flips.max_pairwise_correlationis the largest absolute-value pair in the matrix.calendar_clashesgroups legs whoseclose_timefalls inside a sliding window of sizecalendar_clash_window_days— surfaces "all my legs resolve on the same Fed meeting" risk.duplicate_underlierslists events where the basket holds more than one child market (e.g. two SpaceX-IPO strikes).warningsis a list of human-readable strings. The endpoint never rejects a basket — it surfaces issues for the caller to act on.
GET /kalshi/clusters/ranked-by-return
"Show me thematic baskets that historically returned ≥ X%" in one call. For each current cluster, picks the top-N markets by volume_24h, builds an equal-weight basket, runs the backtest, and returns clusters whose total_return ≥ min_return, sorted descending by return.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
timeframe | string | No | One of 1w, 1m, 3m, 6m, 1y (default 1y). |
min_return | number | No | Minimum total_return to include. Default 0.0. |
top_n_per_cluster | integer | No | Basket size per cluster. Default 5; min 1; max 20. |
kind | string | No | thematic (default) or behavioral. |
max_clusters | integer | No | Cap on how many clusters to evaluate (cost control). Default 50; min 1; max 200. |
Example
sh
curl -G "https://api.octagonai.co/v1/prediction-markets/kalshi/clusters/ranked-by-return" \
-H "Authorization: Bearer your-octagon-api-key" \
--data-urlencode "timeframe=1y" \
--data-urlencode "min_return=0.20" \
--data-urlencode "top_n_per_cluster=5"Example response
json
{
"timeframe": "1y",
"kind": "thematic",
"top_n_per_cluster": 5,
"min_return": 0.20,
"data": [
{
"cluster_id": 22,
"label": "AI infrastructure",
"description": "Markets resolving on AI infra capex, datacenter buildouts, and GPU shipments",
"size": 18,
"basket_tickers": ["KX-AI1", "KX-AI2", "KX-AI3", "KX-AI4", "KX-AI5"],
"summary": {
"total_return": 0.41,
"sharpe": 1.6,
"max_drawdown": -0.11
}
}
]
}GET /kalshi/markets-with-edge
Edge-vs-consensus ranking joined to the latest completed run from events_history (the same table that powers GET /prediction-markets/events). One-call recipe for "10 most-traded politics markets ranked by edge vs consensus".
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
run_id | string (UUID) | No | Defaults to the most recent events_history.run_id. |
category | string | No | Filter on series_category (case-insensitive). |
edge_pp_min | number | No | Lower bound on edge_pp (model probability minus market probability, in percentage points). |
edge_pp_max | number | No | Upper bound on edge_pp. |
expected_return_min | number | No | Floor on expected_return. |
total_volume_min | number | No | Floor on total_volume. |
model_probability_min | number | No | Floor on model_probability. |
sort_by | string | No | One of edge_pp (default), expected_return, total_volume, model_probability. Descending. |
limit | integer | No | Default 50; min 1; max 200. |
cursor | string | No | Pagination cursor. |
Example
Python
import requests
response = requests.get(
"https://api.octagonai.co/v1/prediction-markets/kalshi/markets-with-edge",
headers={"Authorization": "Bearer your-octagon-api-key"},
params={"category": "politics", "sort_by": "edge_pp", "limit": 10},
)
response.raise_for_status()
print(response.json())JavaScript
const params = new URLSearchParams({
category: "politics",
sort_by: "edge_pp",
limit: "10",
});
const response = await fetch(
`https://api.octagonai.co/v1/prediction-markets/kalshi/markets-with-edge?${params}`,
{ headers: { Authorization: "Bearer your-octagon-api-key" } }
);
console.log(await response.json());sh
curl -G "https://api.octagonai.co/v1/prediction-markets/kalshi/markets-with-edge" \
-H "Authorization: Bearer your-octagon-api-key" \
--data-urlencode "category=politics" \
--data-urlencode "sort_by=edge_pp" \
--data-urlencode "limit=10"Example response
json
{
"run_id": "8c0e1b9a-1f44-4d2a-9b7e-2d6e5f50a912",
"captured_at": "2026-05-21T03:00:00Z",
"sort_by": "edge_pp",
"data": [
{
"event_ticker": "KXFEDCUT-26JUN",
"title": "Will the Fed cut rates in June?",
"series_category": "politics",
"model_probability": 0.62,
"market_probability": 0.55,
"edge_pp": 7.0,
"expected_return": 0.12,
"confidence_score": 0.8,
"total_volume": 124300,
"total_open_interest": 5400
}
],
"next_cursor": null,
"has_more": false
}If filters reject every row but the run exists, captured_at still reflects the run's snapshot age.
Use-case recipes
Each recipe is satisfied by a single HTTP call unless explicitly noted. All commands assume BASE=https://api.octagonai.co and a valid OCTAGON_API_KEY.
1. Keyword search — markets matching "BTC 100k"
sh
curl -G "$BASE/v1/prediction-markets/kalshi/markets" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
--data-urlencode "q=BTC 100k"2. Semantic by ticker — markets similar to a known ticker
sh
curl "$BASE/v1/prediction-markets/kalshi/markets/similar?anchor_ticker=KXBTCD-26DEC31-T100000&top_k=25" \
-H "Authorization: Bearer $OCTAGON_API_KEY"3. Semantic by free text — "Will Bitcoin pierce six figures" finds "BTC over $100k"
sh
curl -G "$BASE/v1/prediction-markets/kalshi/markets/similar" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
--data-urlencode "q=Will Bitcoin pierce six figures" \
--data-urlencode "top_k=25"4. Filtered semantic — crypto markets thematically similar to ETH 2.0 staking, closing within 90 days, volume_24h > $10k
sh
curl -G "$BASE/v1/prediction-markets/kalshi/markets/similar" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
--data-urlencode "q=ETH 2.0 staking" \
--data-urlencode "category=crypto" \
--data-urlencode "close_before=2026-08-19T00:00:00Z" \
--data-urlencode "min_volume_24h=10000"5. Browse themes — clustered map of the universe
sh
curl "$BASE/v1/prediction-markets/kalshi/clusters?limit=40&sample_titles=4" \
-H "Authorization: Bearer $OCTAGON_API_KEY"Render label, description, size, sample_titles as a card grid. Drill in with /kalshi/clusters/{id}/markets.
6. Cluster browsing — "all markets in the Fed-decisions theme" (two calls)
sh
# Step 1: find the cluster ID by label
curl -G "$BASE/v1/prediction-markets/kalshi/clusters" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
--data-urlencode "label_contains=fed"
# Step 2: list the markets in it
curl "$BASE/v1/prediction-markets/kalshi/clusters/42/markets?limit=50" \
-H "Authorization: Bearer $OCTAGON_API_KEY"7. Fast first-pass dedup — "show me others in the same theme"
sh
curl "$BASE/v1/prediction-markets/kalshi/markets/KXBTCD-26DEC31-T100000/cluster-peers?kind=thematic&limit=50" \
-H "Authorization: Bearer $OCTAGON_API_KEY"8. Pairwise correlation — find uncorrelated bets
sh
curl -X POST "$BASE/v1/prediction-markets/kalshi/markets/correlations" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"market_tickers":["KXBTCD-26DEC31-T100000","KXETHU-26DEC31-T10000","KXSOL-26DEC31-T200"],"window_days":90,"interval":"1d"}'Read off ranked_pairs[0..k] for the most-uncorrelated pairs — no client-side sorting needed.
9. "Find me 5 uncorrelated bets on macro themes"
sh
curl -X POST "$BASE/v1/prediction-markets/kalshi/baskets/build" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"universe": {"label_contains_any": ["fed", "cpi", "fomc", "gdp", "jobs"]},
"n": 5,
"max_per_cluster": 1,
"max_pairwise_correlation": 0.4,
"candidate_pool_size": 50,
"correlation_window_days": 90,
"sizing": {"strategy": "equal"}
}'10. "Build a $1000 basket on crypto"
sh
curl -X POST "$BASE/v1/prediction-markets/kalshi/baskets/build" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"universe": {"category": "crypto", "min_volume_24h": 10000},
"n": 8,
"max_per_cluster": 2,
"max_pairwise_correlation": 0.6,
"candidate_pool_size": 50,
"correlation_window_days": 60,
"sizing": {
"strategy": "kelly",
"bankroll_usd": 1000,
"kelly_multiplier": 0.25,
"leg_probabilities": {"KXBTCD-26DEC31-T100000": 0.62, "KXETHU-26DEC31-T10000": 0.58}
}
}'leg_probabilities come from your edge engine. If you want Octagon-model probabilities, pull them from /kalshi/markets-with-edge first and feed them in.
11. "10 most-traded politics markets ranked by edge vs consensus"
sh
curl -G "$BASE/v1/prediction-markets/kalshi/markets-with-edge" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
--data-urlencode "category=politics" \
--data-urlencode "sort_by=edge_pp" \
--data-urlencode "limit=10"Swap sort_by=total_volume to literally rank by trading activity first.
12. "Thematic baskets that historically returned 20%+"
sh
curl -G "$BASE/v1/prediction-markets/kalshi/clusters/ranked-by-return" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
--data-urlencode "timeframe=1y" \
--data-urlencode "min_return=0.20" \
--data-urlencode "top_n_per_cluster=5"13. "Did this basket return 20%+?"
sh
curl -X POST "$BASE/v1/prediction-markets/kalshi/baskets/backtest" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"market_tickers":["KX-A","KX-B","KX-C"],"weights":[0.4,0.4,0.2],"timeframe":"1y"}'Read summary.total_return directly.
14. Browse a series tree — every Bitcoin series sorted by volume
sh
curl -G "$BASE/v1/prediction-markets/kalshi/series" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
--data-urlencode "series_prefix=KXBTC" \
--data-urlencode "sort_by=total_volume_24h"15. Walk an event tree — every SpaceX-IPO strike
sh
curl "$BASE/v1/prediction-markets/kalshi/events/KXIPOSPACEX/markets?limit=50" \
-H "Authorization: Bearer $OCTAGON_API_KEY"16. Pull model priors for known tickers, then Kelly-size
sh
# Step 1: get model probabilities for the tickers you care about
curl -X POST "$BASE/v1/prediction-markets/kalshi/markets/edge" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"tickers":["KXBTCD-26DEC31-T100000","KXETHU-26DEC31-T10000"]}'
# Step 2: feed those model_probability values into Kelly sizing
curl -X POST "$BASE/v1/prediction-markets/kalshi/baskets/size" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"bankroll_usd": 1000,
"kelly_multiplier": 0.25,
"legs": [
{"market_ticker":"KXBTCD-26DEC31-T100000","side":"yes","model_probability":0.62},
{"market_ticker":"KXETHU-26DEC31-T10000","side":"yes","model_probability":0.58}
]
}'17. Sanity-check a hand-built basket before placing orders
sh
curl -X POST "$BASE/v1/prediction-markets/kalshi/baskets/validate" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"legs": [
{"market_ticker":"KXGPT-OPEN-26JUL01","side":"yes","stake_usd":170},
{"market_ticker":"KXAGANNOUNCE-26-SEP01","side":"no","stake_usd":160}
],
"bankroll_usd": 1000,
"correlation_window_days": 30,
"max_pairwise_correlation": 0.5,
"calendar_clash_window_days": 7
}'Inspect warnings, max_leg_pct, calendar_clashes, and duplicate_underliers before sending orders.
18. Top-N by volume across the entire universe — no client reranking
sh
curl -G "$BASE/v1/prediction-markets/kalshi/markets" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
--data-urlencode "sort_by=volume_24h" \
--data-urlencode "limit=20"19. Side-aware correlations — mix YES and NO legs in one call
sh
curl -X POST "$BASE/v1/prediction-markets/kalshi/markets/correlations" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"market_tickers":["KX-A","KX-B","KX-C"],
"sides":["yes","yes","no"],
"window_days": 30,
"include_cell_detail": true
}'matrix cells are already sign-flipped; use cells_detail[].reason to debug null cells.
20. Diversify a curated ticker list — CLI workflow
Use this when you already have a curated set of tickers (e.g. a basket build --theme macro command that resolves "macro" against its own registry) and want the server to handle correlation cap, per-cluster cap, and sizing over your list — not over a search result.
sh
curl -X POST "$BASE/v1/prediction-markets/kalshi/baskets/build" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"universe": {
"market_tickers": [
"KXRECSSNBER-26", "KXCPIYOY-26MAY-T4.5", "KXFEDRATE-26JUL",
"KXGDPYOY-26Q2", "KXJOBS-26JUN", "KXM2YOY-26JUN"
],
"min_volume_24h": 1000
},
"n": 4,
"max_per_cluster": 2,
"max_pairwise_correlation": 0.5,
"candidate_pool_size": 50,
"correlation_window_days": 30,
"sizing": {"strategy": "equal"}
}'Tickers that don't match kalshi_markets_active (or that fail the post-filters) are silently dropped — check universe_size to confirm how many made it through. Bump candidate_pool_size if you're sending a registry larger than 50.
What the client owns
The API is intentionally permissive — these are the bits we don't (and can't) absorb server-side:
- Model probabilities for Kelly sizing. The client supplies them (typically pulled from
/kalshi/markets-with-edgeor from its own model). The Kelly endpoint never invents probabilities. - Order placement. Octagon doesn't trade your basket; that's a Kalshi-API concern.
- Presentation. Chart rendering, table layout, and natural-language framing of results.
Errors
| Status | Cause |
|---|---|
400 | Bad query parameters — unknown name, invalid kind / timeframe / sort_by, weights/tickers length mismatch, anchor_ticker + q both supplied, negative kelly_multiplier, and similar. |
401 | Missing or invalid Authorization header. |
422 | Pydantic validation failure on a request body (missing or wrongly-typed fields). |
502 | Upstream failure — typically an OpenAI embedding hiccup on GET /kalshi/markets/similar?q=..., or a candle-pipeline query error. The detail carries only the exception class name; full diagnostics are in server logs. |
503 | Kalshi search is unavailable — either kalshi_markets_active isn't populated yet (nightly sync job hasn't run) or q is shorter than 3 characters. The endpoint deliberately does not fall back to the legacy in-memory Kalshi-API scan, which was both slow and stale; resolving silently hid real problems. Confirm the sync schedule is enabled and the table has rows. |
All errors return a JSON body with a detail field describing the cause.
Related docs
- Prediction Markets Agent — the agent that produces narrative reports on individual events.
- Events History — per-event time-series snapshots from
events_history. - Prediction Markets Events — latest snapshot of every analyzed event. Powers the same underlying table as
/kalshi/markets-with-edge.