Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 26 additions & 19 deletions harnesses/pm-resolution-delay/cmd/script/kalshi_resolution.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@ package main
//
// Unlike Polymarket (UMA on-chain events, 2h floor), Kalshi resolves via its
// own internal process. Delay is measured as:
// observed_resolution_time - market.close_time
//
// where observed_resolution_time = first poll at which result != null.
// Accuracy is bounded by KALSHI_POLL_SECONDS (default 300 = 5 min), which
// is negligible for delays measured in hours.
// settlement_ts - market.close_time
//
// settlement_ts is returned directly by the Kalshi API (exact, no polling error).
// No auth required — uses the public /trade-api/v2/markets endpoint.

import (
Expand All @@ -23,17 +20,18 @@ import (
)

const (
kalshiMarketsURL = "https://api.elections.kalshi.com/trade-api/v2/markets"
kalshiMarketsURL = "https://api.elections.kalshi.com/trade-api/v2/markets"
kalshiLookbackDays = 30
)

type kalshiMarketRecord struct {
Ticker string `json:"ticker"`
Title string `json:"title"`
CloseTime string `json:"close_time"` // RFC3339
Status string `json:"status"` // "open", "closed", "finalized"
Result string `json:"result"` // "yes", "no", "" when unresolved
Category string `json:"category"` // "Sports", "Politics", "Crypto", etc.
Ticker string `json:"ticker"`
Title string `json:"title"`
CloseTime string `json:"close_time"` // RFC3339
SettlementTs string `json:"settlement_ts"` // RFC3339Nano, present when settled
Status string `json:"status"` // "open", "closed", "finalized"
Result string `json:"result"` // "yes", "no", "" when unresolved
Category string `json:"category"` // "Sports", "Politics", "Crypto", etc.
}

type kalshiMarketsResponse struct {
Expand Down Expand Up @@ -129,13 +127,14 @@ func (k *kalshiTracker) pollClosed() {
}
}

// pollFinalized fetches recently finalized markets and emits histogram
// observations for any we haven't recorded yet.
// pollFinalized fetches recently settled markets and emits histogram
// observations for any we haven't recorded yet. The API filter param is
// "settled"; the status field in the response body is "finalized".
// Delay is settlement_ts - close_time (exact, from API, no polling error).
func (k *kalshiTracker) pollFinalized() {
cutoff := time.Now().Add(-kalshiLookbackDays * 24 * time.Hour)
now := time.Now()

markets, _, err := k.fetchPage("finalized", "")
markets, _, err := k.fetchPage("settled", "")
if err != nil {
log.Printf("[kalshi-res] pollFinalized error: %v", err)
return
Expand All @@ -160,15 +159,23 @@ func (k *kalshiTracker) pollFinalized() {
k.recorded[m.Ticker] = struct{}{}
delete(k.watched, m.Ticker)

delay := now.Sub(ct).Seconds()
// Prefer settlement_ts (exact); fall back to now (first-poll proxy, ±5 min).
var settleAt time.Time
if m.SettlementTs != "" {
settleAt, _ = time.Parse(time.RFC3339Nano, m.SettlementTs)
}
if settleAt.IsZero() {
settleAt = time.Now()
}
delay := settleAt.Sub(ct).Seconds()
if delay < 0 || delay > float64(kalshiLookbackDays*24*3600) {
continue
}
cat := normalizeKalshiCategory(m.Category)
resolutionDelay.WithLabelValues("kalshi", cat, "false").Observe(delay)
resolutionsTotal.WithLabelValues("kalshi", cat, "false").Inc()
log.Printf("[kalshi-res] resolved ticker=%s category=%s delay=%.0fs close_time=%s result=%s",
m.Ticker, cat, delay, m.CloseTime, m.Result)
log.Printf("[kalshi-res] resolved ticker=%s category=%s delay=%.0fs close_time=%s settled=%s result=%s",
m.Ticker, cat, delay, m.CloseTime, settleAt.UTC().Format(time.RFC3339), m.Result)
}
}

Expand Down
Loading