Video 10: Kalshi Trading Bot Version 4
This fourth iteration expands the Version 3 Kalshi backend approach with two new capabilities: multi-agent execution and delta-aware entries. Version 3 moved order routing away from the front end and into Kalshi's backend API. Version 4 keeps that foundation, then extends it so the bot can evaluate several Kalshi 15 minute crypto markets in parallel across different assets while also accounting for how far the underlying market price is from the current target.
YouTube Video
Version 4 Focus
- Multi-agent execution: run the same trading logic across multiple assets in the Kalshi 15 minute crypto market so BTC, ETH, SOL, XRP, DOGE, HYPE, and BNB can each behave like their own bot lane.
- Delta: incorporate the underlying price distance from the current price before taking a trade, so entries can consider both contract price and how far the underlying market still has to move.
- Version 3 foundation: keep the backend API order routing, credential loading, retry handling, and position tracking from the previous version.
Latest Nightshark Build Required
This version requires downloading the latest Nightshark build.
Download Latest NightsharkAPI key Filename
apikey
Private Key Filename
privatekey
Nightshark Code
; =========================
; Strategy Configuration
; =========================
timeDelay := 8 ; activation window in minutes
Entry := 0.85 ; enter when side price >= 85c
Exit := 0.40 ; stop/exit when entered side price < 40c
Delta := 0 ; min |open15m - price| required alongside Entry
assets := ["BTC"] ; Options ["ETH"] ,["SOL"] ,["XRP"] , ["HYPE"] , ["BNB"] , ["DOGE"]
orderSize := 1
; =============================================================
; Kalshi Order API Config
; DO NOT TOUCH BELOW VALUES UNLESS YOU KNOW WHAT YOU ARE DOING
; =============================================================
enableLiveOrders := true
pollIntervalMs := 700
marketRefreshMs := 5000 ; refresh market/session metadata less often; orderbook still refreshes every loop
priceRefreshMs := 1500 ; throttle live asset price calls used only for LogStream
restDelay := 2000 ; ms to let order rest before checking position
entryDiff := 0.02 ; added to price on BUY orders (aggressive entry)
exitDiff := 0.02 ; subtracted from price on SELL orders (aggressive exit)
kalshiApiBase := "https://api.elections.kalshi.com"
assetSeriesMap := { "BTC": "KXBTC15M", "ETH": "KXETH15M", "SOL": "KXSOL15M", "XRP": "KXXRP15M", "DOGE": "KXDOGE15M", "HYPE": "KXHYPE15M", "BNB": "KXBNB15M" }
kalshiApiKeyId := ""
kalshiApiKeyFile := "reactions/apikey.json"
kalshiPrivateKeyFile := "reactions/privatekey.json"
kalshiPrivateKeyPath := ""
kalshiApiKeyResolvedPath := ""
kalshiPrivateKeyResolvedPath := ""
kalshiKeyPassphrase := ""
kalshiSignerPath := ""
; Track one open position per asset.
positions := {}
assetPhase := {} ; WAIT_WINDOW | MONITORING | IN_POSITION
assetSessionQuarter := {} ; quarter index currently tracked per asset
assetLastPriceLogTick := {} ; throttled logging control
assetSnapshotCache := {}
assetMarketRefreshTick := {}
assetPriceCache := {}
assetPriceRefreshTick := {}
LoadKalshiCredentialsFromFiles()
if (kalshiSignerPath = "")
kalshiSignerPath := FindNightSharkSigner()
Log("Application Started | Entry: " Entry " | Exit: " Exit " | Delta: " Delta " | Window: last " timeDelay "m")
StartupCredentialCheck()
loop {
for _, asset in assets {
snapshot := GetKalshiMarketSnapshot(asset)
if !IsObject(snapshot) {
if (PositionSessionEnded(asset))
MarkHeldToResolution(asset)
noDataKey := asset "|NO_DATA_" CurrentQuarterIndex()
if !IsObject(logOnceKeys)
logOnceKeys := {}
if !logOnceKeys.HasKey(noDataKey) {
logOnceKeys[noDataKey] := true
Log(asset " Waiting for market data")
}
continue
}
up := snapshot.up
down := snapshot.down
; Recompute minsLeft live from closeTime so cached snapshots reflect real elapsed time
minsLeft := SnapshotMinutesLeft(snapshot)
if (minsLeft = "")
minsLeft := MinutesRemainingInQuarter()
inEntryWindow := (minsLeft > 0 && minsLeft <= timeDelay)
sessionKey := (snapshot.marketTicker != "") ? snapshot.marketTicker : ((snapshot.closeTime != "") ? snapshot.closeTime : CurrentQuarterIndex())
if (PositionSessionEnded(asset, snapshot)) {
MarkHeldToResolution(asset)
continue
}
if (!assetSessionQuarter.HasKey(asset) || assetSessionQuarter[asset] != sessionKey) {
assetSessionQuarter[asset] := sessionKey
assetPhase[asset] := "WAIT_WINDOW"
assetLastPriceLogTick[asset] := 0
LogOnceReset(asset)
Log(asset " New session (" snapshot.marketTicker ")")
if (snapshot.marketTicker != "" && HasExistingPosition(snapshot.marketTicker))
assetPhase[asset] := "HAS_POSITION"
continue
}
if IsObject(positions[asset]) {
pos := positions[asset]
current := (pos.side = "UP") ? up : down
assetPhase[asset] := "IN_POSITION"
UpdateKalshiLogStream(asset, snapshot)
if (current < Exit) {
Log(asset " Stop-loss triggered @ " Fmt(current) " < " Fmt(Exit))
sellConfirmed := false
loop, 4 {
if (A_Index > 1) {
freshSnap := GetKalshiMarketSnapshot(asset)
if IsObject(freshSnap)
current := (pos.side = "UP") ? freshSnap.up : freshSnap.down
}
if SellPosition(asset, pos.side, current, "exit_below_" Exit) {
Log(asset " Position closed")
sellConfirmed := true
break
}
if (A_Index < 4) {
Log(asset " Exit retry " A_Index "/3 | " KalshiLastOrderApiSummary())
Sleep 1000
}
}
if (!sellConfirmed)
Log(asset " Exit order failed after 4 attempts | " KalshiLastOrderApiSummary())
positions.Delete(asset)
assetPhase[asset] := "STOPPED_OUT"
LogOnce(asset, "STOPPED_OUT", asset " Stopped out, waiting for next session")
}
continue
}
if (assetPhase[asset] = "STOPPED_OUT") {
LogOnce(asset, "STOPPED_OUT", asset " Stopped out, waiting for next session")
continue
}
if (assetPhase[asset] = "BUY_FAILED") {
LogOnce(asset, "BUY_FAILED", asset " Buy failed earlier, waiting for next session")
continue
}
if (assetPhase[asset] = "HAS_POSITION") {
LogOnce(asset, "HAS_POSITION", asset " Existing position, waiting for next session")
continue
}
; --- Waiting for entry window ---
if (!inEntryWindow) {
if (minsLeft <= 0)
LogOnce(asset, "SESSION_ENDED", asset " End of session, waiting for new market")
else
LogOnce(asset, "WAIT_WINDOW", asset " Waiting for last " timeDelay " minutes")
assetPhase[asset] := "WAIT_WINDOW"
continue
}
; --- Entry window open: monitoring prices ---
if (!assetPhase.HasKey(asset) || assetPhase[asset] != "MONITORING") {
assetPhase[asset] := "MONITORING"
Log(asset " Monitoring for entry > " Fmt(Entry) " | Delta >= " Delta)
}
; Live price + LiveDelta (|open15m - price|) is only needed once a current session is ready to monitor.
LiveDelta := UpdateKalshiLogStream(asset, snapshot)
side := ""
entryPrice := ""
if (up >= Entry || down >= Entry) {
if (LiveDelta != "" && LiveDelta >= Delta) {
if (up >= down) {
side := "UP"
entryPrice := up
} else {
side := "DOWN"
entryPrice := down
}
} else {
LogOnce(asset, "DELTA_LOW", asset " LiveDelta " FmtOrNA(LiveDelta) " < Delta " Delta ", skipping entry")
}
}
if (side = "")
continue
if (entryPrice > 0.98) {
LogOnce(asset, "PRICE_HIGH", asset " Price too high (" entryPrice "), waiting for drop")
continue
}
if !CanPlaceOrderNow(asset) {
LogOnce(asset, "NO_CREDS", asset " Missing credentials, cannot place orders")
continue
}
if HasExistingPosition(snapshot.marketTicker) {
assetPhase[asset] := "HAS_POSITION"
Log(asset " Existing position found, waiting for next session")
continue
}
Log(asset " " side " triggered @ " Fmt(entryPrice) " — placing order")
buyConfirmed := false
loop, 4 {
if (A_Index > 1 && HasExistingPosition(snapshot.marketTicker)) {
Log(asset " Position detected before retry " A_Index ", skipping")
buyConfirmed := true
break
}
if (A_Index > 1) {
freshSnap := GetKalshiMarketSnapshot(asset)
if IsObject(freshSnap)
entryPrice := (side = "UP") ? freshSnap.up : freshSnap.down
if (A_Index = 4)
entryPrice := 0.97
}
if BuyPosition(asset, snapshot.marketTicker, side, entryPrice) {
Log(asset " Position confirmed")
buyConfirmed := true
break
}
if (A_Index < 4) {
Log(asset " Entry retry " A_Index "/3 | " KalshiLastOrderApiSummary())
Sleep 1000
}
}
if (buyConfirmed) {
positions[asset] := { side: side, entry: entryPrice, ticker: snapshot.marketTicker, quarterIndex: CurrentQuarterIndex() }
assetPhase[asset] := "IN_POSITION"
assetLastPriceLogTick[asset] := 0
Log(asset " Monitoring stop-loss @ " Fmt(Exit))
} else {
assetPhase[asset] := "BUY_FAILED"
Log(asset " Entry order not filled after 4 attempts, skipping session | " KalshiLastOrderApiSummary())
}
}
Random, pollJitterMs, 0, 250
sleepMs := pollIntervalMs + pollJitterMs
Sleep %sleepMs%
}
LogOnce(asset, reason, msg) {
global logOnceKeys
if !IsObject(logOnceKeys)
logOnceKeys := {}
key := asset "|" reason
if (logOnceKeys.HasKey(key))
return
logOnceKeys[key] := true
Log(msg)
}
LogOnceReset(asset) {
global logOnceKeys
if !IsObject(logOnceKeys)
return
toRemove := []
for k, _ in logOnceKeys {
if (InStr(k, asset "|") = 1)
toRemove.Push(k)
}
for _, k in toRemove
logOnceKeys.Delete(k)
}
PositionSessionEnded(asset, snapshot := "") {
global positions
if !IsObject(positions[asset])
return false
pos := positions[asset]
if (IsObject(snapshot) && snapshot.marketTicker != "" && pos.HasKey("ticker") && pos.ticker != "" && snapshot.marketTicker != pos.ticker)
return true
if (pos.HasKey("quarterIndex") && CurrentQuarterIndex() != pos.quarterIndex)
return true
return false
}
MarkHeldToResolution(asset) {
global positions, assetPhase
if !IsObject(positions[asset])
return
pos := positions[asset]
Log(asset " " pos.side " held to resolution")
positions.Delete(asset)
ClearKalshiMarketCache(asset)
assetPhase[asset] := "WAIT_WINDOW"
}
UpdateKalshiLogStream(asset, snapshot) {
if !IsObject(snapshot)
return ""
priceObj := GetCachedKalshiPriceObj(asset)
livePrice := (IsObject(priceObj) && priceObj.HasKey("price") && priceObj.price > 0) ? priceObj.price : ""
LiveDelta := ""
if (livePrice != "" && IsObject(priceObj) && priceObj.HasKey("open15m") && priceObj.open15m > 0)
LiveDelta := Abs(priceObj.open15m - livePrice)
LogMonitoringStream(asset, snapshot, livePrice, LiveDelta)
return LiveDelta
}
LogMonitoringStream(asset, snapshot, livePrice := "", LiveDelta := "") {
if !IsObject(snapshot)
return
if (livePrice != "")
LogStream(asset, FmtOrNA(livePrice))
else
LogStream(asset, "n/a")
LogStream("up", FmtOrNA(snapshot.up))
LogStream("down", FmtOrNA(snapshot.down))
LogStream("LIVE DELTA", FmtLiveDeltaOrNA(LiveDelta))
}
GetCachedKalshiPriceObj(asset) {
global assetPriceCache, assetPriceRefreshTick, priceRefreshMs
nowTick := A_TickCount
if (assetPriceCache.HasKey(asset) && assetPriceRefreshTick.HasKey(asset)) {
if ((nowTick - assetPriceRefreshTick[asset]) < priceRefreshMs)
return assetPriceCache[asset]
}
priceObj := GetKalshiPrice(asset)
assetPriceRefreshTick[asset] := nowTick
if (IsObject(priceObj) && priceObj.HasKey("price") && priceObj.price > 0) {
assetPriceCache[asset] := priceObj
return priceObj
}
if (assetPriceCache.HasKey(asset))
return assetPriceCache[asset]
return ""
}
GetCachedKalshiAssetPrice(asset) {
obj := GetCachedKalshiPriceObj(asset)
if (IsObject(obj) && obj.HasKey("price"))
return obj.price
return ""
}
FmtOrNA(price) {
if (price = "")
return "n/a"
return Fmt(price)
}
FmtLiveDeltaOrNA(value) {
if (value = "")
return "n/a"
value := value + 0
if (Abs(value) >= 1)
return Round(value, 2)
return Round(value, 4)
}
Fmt(price) {
return Round(price + 0, 2)
}
BuyPosition(asset, marketTicker, side, price) {
global orderSize
return PlaceKalshiOrder("BUY", marketTicker, side, price, orderSize)
}
SellPosition(asset, side, price, reason, clientOrderId := "") {
global positions, orderSize
if !IsObject(positions[asset]) || !positions[asset].HasKey("ticker")
return true
return PlaceKalshiOrder("SELL", positions[asset].ticker, side, price, orderSize, clientOrderId)
}
HasExistingPosition(marketTicker) {
if (marketTicker = "")
return false
res := KalshiSignedRequest("GET", "/trade-api/v2/portfolio/positions?ticker=" marketTicker)
if !IsObject(res) || (res.status != 200)
return false
qty := JsonField(res.body, "position_fp")
if (qty = "")
qty := JsonField(res.body, "market_exposure_dollars")
return (qty != "" && Abs(qty + 0) > 0)
}
PlaceKalshiOrder(action, marketTicker, side, price, size, clientOrderId := "") {
global enableLiveOrders, kalshiApiKeyId, kalshiPrivateKeyPath, restDelay, entryDiff, exitDiff, kalshiLastOrderResult
kalshiLastOrderResult := { action: action, ticker: marketTicker, side: side, apiAction: "", apiSide: "", cents: "", price: "", contractCents: "", timeInForce: "", size: size, status: "", body: "", orderId: "", clientOrderId: clientOrderId, reason: "not submitted" }
if (!enableLiveOrders) {
kalshiLastOrderResult.reason := "live orders disabled"
return true
}
if (kalshiApiKeyId = "" || kalshiPrivateKeyPath = "") {
kalshiLastOrderResult.reason := "missing API credentials"
return false
}
if (marketTicker = "") {
kalshiLastOrderResult.reason := "missing market ticker"
return false
}
isBuy := (ToLower(action) = "buy")
contractPrice := price + 0
if (isBuy)
contractPrice := contractPrice + entryDiff
else
contractPrice := 0.01 ; IoC+reduce_only: sell at any price, exchange fills at best bid
contractCents := KalshiClampCents(Round(contractPrice * 100))
apiAction := ToLower(action)
apiSide := KalshiEventOrderSide(apiAction, side)
apiCents := KalshiEventOrderCents(apiAction, side, contractCents)
priceText := KalshiFixedPriceFromCents(apiCents)
countText := KalshiFixedCount(size)
timeInForce := isBuy ? "good_till_canceled" : "immediate_or_cancel"
reduceOnly := isBuy ? "false" : "true"
if (clientOrderId = "")
clientOrderId := BuildClientOrderId(marketTicker, apiAction, apiSide)
kalshiLastOrderResult.apiAction := apiAction
kalshiLastOrderResult.apiSide := apiSide
kalshiLastOrderResult.cents := apiCents
kalshiLastOrderResult.price := priceText
kalshiLastOrderResult.contractCents := contractCents
kalshiLastOrderResult.timeInForce := timeInForce
kalshiLastOrderResult.clientOrderId := clientOrderId
payload := "{"
. """ticker"":""" marketTicker ""","
. """side"":""" apiSide ""","
. """count"":""" countText ""","
. """price"":""" priceText ""","
. """time_in_force"":""" timeInForce ""","
. """self_trade_prevention_type"":""taker_at_cross"","
. """post_only"":false,"
. """cancel_order_on_pause"":false,"
. """reduce_only"":" reduceOnly ","
. """client_order_id"":""" clientOrderId """"
. "}"
res := KalshiSignedRequest("POST", "/trade-api/v2/portfolio/events/orders", payload)
if !IsObject(res) {
kalshiLastOrderResult.reason := "signed request failed or no API response"
return false
}
kalshiLastOrderResult.status := res.status
kalshiLastOrderResult.body := res.body
orderId := JsonField(res.body, "order_id")
kalshiLastOrderResult.orderId := orderId
if (res.status = 401 || res.status = 403) {
kalshiLastOrderResult.reason := "auth rejected"
return false
}
if (res.status != 201 && res.status != 200) {
kalshiLastOrderResult.reason := "order rejected"
return false
}
kalshiLastOrderResult.reason := "order accepted"
if (isBuy)
Sleep %restDelay%
else
Sleep 150
hasPos := HasExistingPosition(marketTicker)
kalshiLastOrderResult.positionOpen := hasPos ? "yes" : "no"
if (isBuy && !hasPos && orderId != "") {
kalshiLastOrderResult.reason := "not filled; canceling resting order"
CancelKalshiOrder(orderId)
Sleep 500
hasPos := HasExistingPosition(marketTicker)
kalshiLastOrderResult.positionOpen := hasPos ? "yes" : "no"
if (hasPos)
{
kalshiLastOrderResult.reason := "filled during cancel race"
Log(marketTicker " Order filled during cancel race, treating as confirmed")
}
else
kalshiLastOrderResult.reason := "not filled; canceled resting order"
}
if (isBuy) {
if (hasPos && kalshiLastOrderResult.reason = "order accepted")
kalshiLastOrderResult.reason := "position confirmed"
else if (!hasPos && kalshiLastOrderResult.reason = "order accepted")
kalshiLastOrderResult.reason := "position not confirmed"
} else {
if (hasPos)
kalshiLastOrderResult.reason := "position still open after IoC sell"
else
kalshiLastOrderResult.reason := "position closed"
}
return (isBuy ? hasPos : !hasPos)
}
KalshiEventOrderSide(apiAction, contractSide) {
; V2 quotes everything from the YES book: bid=buy YES, ask=sell YES.
if (apiAction = "buy")
return (contractSide = "UP") ? "bid" : "ask"
return (contractSide = "UP") ? "ask" : "bid"
}
KalshiEventOrderCents(apiAction, contractSide, contractCents) {
contractCents := KalshiClampCents(contractCents)
if (contractSide = "UP")
return contractCents
return KalshiClampCents(100 - contractCents)
}
KalshiClampCents(cents) {
cents := Round(cents + 0)
if (cents < 1)
return 1
if (cents > 99)
return 99
return cents
}
KalshiFixedPriceFromCents(cents) {
cents := KalshiClampCents(cents)
return "0." KalshiPad2(cents) "00"
}
KalshiFixedCount(size) {
countCents := Round((size + 0) * 100)
whole := Floor(countCents / 100)
frac := Mod(countCents, 100)
return whole "." KalshiPad2(frac)
}
KalshiPad2(n) {
n := Round(n + 0)
if (n < 10)
return "0" n
return n
}
KalshiLastOrderApiSummary() {
global kalshiLastOrderResult
if !IsObject(kalshiLastOrderResult)
return "API no order attempt recorded"
action := KalshiResultValue(kalshiLastOrderResult, "action", "ORDER")
side := KalshiResultValue(kalshiLastOrderResult, "side", "")
apiSide := KalshiResultValue(kalshiLastOrderResult, "apiSide", "")
ticker := KalshiResultValue(kalshiLastOrderResult, "ticker", "")
cents := KalshiResultValue(kalshiLastOrderResult, "cents", "")
price := KalshiResultValue(kalshiLastOrderResult, "price", "")
contractCents := KalshiResultValue(kalshiLastOrderResult, "contractCents", "")
timeInForce := KalshiResultValue(kalshiLastOrderResult, "timeInForce", "")
size := KalshiResultValue(kalshiLastOrderResult, "size", "")
status := KalshiResultValue(kalshiLastOrderResult, "status", "")
body := KalshiResultValue(kalshiLastOrderResult, "body", "")
orderId := KalshiResultValue(kalshiLastOrderResult, "orderId", "")
clientOrderId := KalshiResultValue(kalshiLastOrderResult, "clientOrderId", "")
reason := KalshiResultValue(kalshiLastOrderResult, "reason", "")
orderText := action
if (side != "")
orderText .= " " side
if (apiSide != "")
orderText .= " -> " apiSide
if (ticker != "")
orderText .= " " ticker
if (price != "")
orderText .= " @" price
else if (cents != "")
orderText .= " @" cents "c"
if (size != "")
orderText .= " x" size
if (timeInForce != "")
orderText .= " " timeInForce
if (contractCents != "" && contractCents != cents)
orderText .= " contract@" contractCents "c"
if (status = "")
apiText := "API no response"
else
apiText := "API HTTP " status
includeBody := (status != 200 && status != 201)
apiMessage := KalshiApiMessage(body, includeBody)
if (apiMessage != "")
apiText .= " " apiMessage
if (orderId != "")
apiText .= " order_id=" KalshiShortLogText(orderId, 28)
if (reason != "")
apiText .= " | " reason
if (clientOrderId != "")
apiText .= " | cid=" KalshiShortLogText(clientOrderId, 36)
return orderText " | " apiText
}
KalshiApiMessage(body, includeBodyFallback := true) {
body := Trim(body)
if (body = "")
return ""
code := JsonField(body, "code")
message := JsonField(body, "message")
detail := JsonField(body, "detail")
errorText := JsonField(body, "error")
if (code != "" && message != "")
return "code=" KalshiCompactLogText(code, 40) " msg=" KalshiCompactLogText(message, 140)
if (message != "")
return "msg=" KalshiCompactLogText(message, 160)
if (detail != "")
return "detail=" KalshiCompactLogText(detail, 160)
if (errorText != "")
return "error=" KalshiCompactLogText(errorText, 160)
if (includeBodyFallback)
return "body=" KalshiCompactLogText(body, 180)
return ""
}
KalshiResultValue(result, key, fallback := "") {
if IsObject(result) && result.HasKey(key)
return result[key]
return fallback
}
KalshiShortLogText(s, maxLen := 36) {
return KalshiCompactLogText(s, maxLen)
}
KalshiCompactLogText(s, maxLen := 160) {
s := Trim(JsonUnescape(s))
s := StrReplace(s, "`r", " ")
s := StrReplace(s, "`n", " ")
s := StrReplace(s, A_Tab, " ")
s := RegExReplace(s, "\s+", " ")
if (StrLen(s) > maxLen)
return SubStr(s, 1, maxLen - 3) "..."
return s
}
CancelKalshiOrder(orderId) {
if (orderId = "")
return
KalshiSignedRequest("DELETE", "/trade-api/v2/portfolio/events/orders/" orderId)
}
LoadKalshiCredentialsFromFiles() {
global kalshiApiKeyId, kalshiApiKeyFile, kalshiPrivateKeyPath, kalshiPrivateKeyFile, kalshiApiKeyResolvedPath, kalshiPrivateKeyResolvedPath
kalshiApiKeyId := ""
kalshiApiKeyResolvedPath := ResolveCredentialPath(kalshiApiKeyFile)
if (kalshiApiKeyResolvedPath != "") {
FileRead, apiRaw, %kalshiApiKeyResolvedPath%
apiCode := JsonField(apiRaw, "code")
if (apiCode != "")
kalshiApiKeyId := Trim(JsonUnescape(apiCode))
}
kalshiPrivateKeyPath := ""
kalshiPrivateKeyResolvedPath := ResolveCredentialPath(kalshiPrivateKeyFile)
if (kalshiPrivateKeyResolvedPath != "") {
FileRead, keyRaw, %kalshiPrivateKeyResolvedPath%
keyCode := JsonField(keyRaw, "code")
if (keyCode != "") {
pemText := JsonUnescape(keyCode)
tempBase := A_Temp
if (tempBase = "")
tempBase := A_ScriptDir
tempPem := tempBase "\kalshi_ahk\privatekey_runtime.pem"
FileCreateDir, % tempBase "\kalshi_ahk"
FileDelete, %tempPem%
FileAppend, %pemText%, %tempPem%
kalshiPrivateKeyPath := tempPem
}
}
}
StartupCredentialCheck() {
global enableLiveOrders, kalshiApiKeyId, kalshiPrivateKeyPath, kalshiSignerPath
apiReady := (kalshiApiKeyId != "")
keyReady := (kalshiPrivateKeyPath != "" && FileExist(kalshiPrivateKeyPath))
signerOut := RunAndCapture(QuoteForCmd(kalshiSignerPath) " version")
signerReady := InStr(signerOut, "NightShark Signer")
if (apiReady && keyReady && signerReady) {
res := KalshiSignedRequest("GET", "/trade-api/v2/portfolio/balance")
if IsObject(res) && (res.status = 200) {
Log("Credentials processed successfully")
return
}
if IsObject(res) && (res.status = 401 || res.status = 403) {
Log("ERROR: API key or private key is incorrect (HTTP " res.status ")")
Log("Please fix your API key / private key and restart.")
stopCode()
}
Log("WARNING: Could not verify credentials (HTTP " (IsObject(res) ? res.status : "no response") ")")
Log("Please paste below link in browser to watch fix video")
Log("https://youtu.be/Es0vvpzyND4")
stopCode()
}
if (!apiReady)
Log("ERROR: API key not loaded")
if (!keyReady)
Log("ERROR: Private key not loaded")
if (!signerReady)
Log("ERROR: NightShark signer not found")
Log("Please fix API credentials. Stopping script.")
stopCode()
}
ResolveCredentialPath(pathSpec) {
if (pathSpec = "")
return ""
if FileExist(pathSpec)
return pathSpec
normalized := StrReplace(pathSpec, "\", "/")
if FileExist(normalized)
return normalized
relative := normalized
while (SubStr(relative, 1, 1) = "/" || SubStr(relative, 1, 1) = "\")
relative := SubStr(relative, 2)
fromScriptDir := A_ScriptDir "/" relative
if FileExist(fromScriptDir)
return fromScriptDir
fromScriptDirBackslash := StrReplace(fromScriptDir, "/", "\")
if FileExist(fromScriptDirBackslash)
return fromScriptDirBackslash
return ""
}
JsonUnescape(s) {
s := StrReplace(s, "\/", "/")
s := StrReplace(s, "\n", "`n")
s := StrReplace(s, "\r", "`r")
s := StrReplace(s, "\t", A_Tab)
s := StrReplace(s, Chr(92) Chr(34), Chr(34))
s := StrReplace(s, Chr(92) Chr(92), Chr(92))
return s
}
KalshiSignedRequest(method, endpointPath, bodyJson := "") {
global kalshiApiBase, kalshiApiKeyId
timestamp := CurrentTimeMillis()
signPath := endpointPath
if InStr(signPath, "?")
signPath := SubStr(signPath, 1, InStr(signPath, "?") - 1)
signature := SignKalshiMessage(timestamp method signPath)
if (signature = "")
return false
url := kalshiApiBase endpointPath
global httpSignedObj
if !IsObject(httpSignedObj)
httpSignedObj := ComObjCreate("WinHttp.WinHttpRequest.5.1")
http := httpSignedObj
http.Option(9) := 2048 | 8192
http.Open(method, url, false)
http.SetTimeouts(1000, 1000, 3000, 5000)
http.SetRequestHeader("User-Agent", "Mozilla/5.0")
http.SetRequestHeader("Cache-Control", "no-cache, no-store")
http.SetRequestHeader("Pragma", "no-cache")
http.SetRequestHeader("KALSHI-ACCESS-KEY", kalshiApiKeyId)
http.SetRequestHeader("KALSHI-ACCESS-TIMESTAMP", timestamp)
http.SetRequestHeader("KALSHI-ACCESS-SIGNATURE", signature)
http.SetRequestHeader("Content-Type", "application/json")
try {
if (bodyJson != "")
http.Send(bodyJson)
else
http.Send()
} catch e {
httpSignedObj := ""
return false
}
obj := {}
obj.status := http.Status
obj.body := http.ResponseText
return obj
}
SignKalshiMessage(message) {
global kalshiPrivateKeyPath, kalshiKeyPassphrase, kalshiSignerPath
if (kalshiPrivateKeyPath = "" || !FileExist(kalshiPrivateKeyPath))
return ""
passArg := ""
if (kalshiKeyPassphrase != "")
passArg := " --passphrase " QuoteForCmd(kalshiKeyPassphrase)
signCmd := QuoteForCmd(kalshiSignerPath)
. " sign --key "
. QuoteForCmd(kalshiPrivateKeyPath)
. " --message "
. QuoteForCmd(message)
. passArg
out := Trim(RunAndCapture(signCmd))
if (SubStr(out, 1, 6) = "ERROR:")
return ""
return out
}
BuildClientOrderId(ticker, action, side) {
nowUtc := CurrentUtcNowAhk()
if (nowUtc = "")
nowUtc := A_Now
Random, r, 100000, 999999
marketToken := RegExReplace(ticker, "^KX([A-Z0-9]+)15M.*$", "$1")
if (marketToken = ticker || marketToken = "")
marketToken := SubStr(RegExReplace(ticker, "[^A-Za-z0-9]", ""), 1, 12)
clientOrderId := marketToken "-" action "-" side "-" nowUtc "-" A_TickCount "-" r
return SubStr(clientOrderId, 1, 64)
}
CurrentTimeMillis() {
epoch := CurrentUtcNowAhk()
if (epoch = "")
return ""
EnvSub, epoch, 19700101000000, Seconds
return epoch * 1000
}
CurrentUtcNowAhk() {
nowUtc := A_NowUTC
if (nowUtc != "")
return nowUtc
out := Trim(RunAndCapture("powershell -NoProfile -Command ""[DateTime]::UtcNow.ToString('yyyyMMddHHmmss')"""))
if RegExMatch(out, "^\d{14}$")
return out
return ""
}
QuoteForCmd(s) {
s := StrReplace(s, """", "\""")
return """" s """"
}
RunAndCapture(command) {
return RunCMD(command, A_ScriptDir)
}
ToLower(s) {
StringLower, out, s
return out
}
FindNightSharkSigner() {
candidates := [ A_ScriptDir "\nightshark-signer.exe"
, A_ScriptDir "/nightshark-signer.exe"
, A_WorkingDir "\nightshark-signer.exe"
, A_WorkingDir "/nightshark-signer.exe" ]
for _, p in candidates {
if FileExist(p)
return p
}
return A_ScriptDir "\nightshark-signer.exe"
}
CanPlaceOrderNow(asset) {
global enableLiveOrders, kalshiApiKeyId, kalshiPrivateKeyPath
if (!enableLiveOrders)
return true
if (kalshiApiKeyId != "" && kalshiPrivateKeyPath != "" && FileExist(kalshiPrivateKeyPath))
return true
return false
}
ShouldLogPrice(asset, minIntervalMs) {
global assetLastPriceLogTick
nowTick := A_TickCount
if !assetLastPriceLogTick.HasKey(asset) {
assetLastPriceLogTick[asset] := nowTick
return true
}
if ((nowTick - assetLastPriceLogTick[asset]) >= minIntervalMs) {
assetLastPriceLogTick[asset] := nowTick
return true
}
return false
}
MinutesRemainingInQuarter() {
utc := CurrentUtcNowAhk()
if (utc = "") {
; Fallback to local clock if UTC unavailable
currentMinute := A_Min + 0
remaining := 15 - Mod(currentMinute, 15)
remaining := remaining - ((A_Sec + 0) / 60.0)
return remaining
}
mm := SubStr(utc, 11, 2) + 0
ss := SubStr(utc, 13, 2) + 0
remaining := 15 - Mod(mm, 15) - (ss / 60.0)
return remaining
}
IsXMinRemaining(x) {
return (MinutesRemainingInQuarter() <= x)
}
CurrentQuarterIndex() {
utc := CurrentUtcNowAhk()
if (utc = "") {
totalMinutes := (A_Hour + 0) * 60 + (A_Min + 0)
return Floor(totalMinutes / 15)
}
hh := SubStr(utc, 9, 2) + 0
mm := SubStr(utc, 11, 2) + 0
totalMinutes := hh * 60 + mm
return Floor(totalMinutes / 15)
}
SnapshotMinutesLeft(snapshot) {
if !IsObject(snapshot)
return ""
if (snapshot.HasKey("closeTime") && snapshot.closeTime != "") {
minsLeft := MinutesUntilIsoUtc(snapshot.closeTime)
if (minsLeft != "")
return minsLeft
}
if (snapshot.HasKey("minutesLeft"))
return snapshot.minutesLeft
return ""
}
IsSnapshotExpired(snapshot, cutoffMinutes := 0) {
minsLeft := SnapshotMinutesLeft(snapshot)
return (minsLeft != "" && minsLeft <= cutoffMinutes)
}
ClearKalshiMarketCache(asset) {
global assetSnapshotCache, assetMarketRefreshTick
if (assetSnapshotCache.HasKey(asset))
assetSnapshotCache.Delete(asset)
if (assetMarketRefreshTick.HasKey(asset))
assetMarketRefreshTick.Delete(asset)
}
GetKalshiMarketSnapshot(asset, maxRetries := 4) {
global assetSeriesMap, assetSnapshotCache, assetMarketRefreshTick, marketRefreshMs
if !assetSeriesMap.HasKey(asset)
return false
series := assetSeriesMap[asset]
nowTick := A_TickCount
if (assetSnapshotCache.HasKey(asset) && assetMarketRefreshTick.HasKey(asset)) {
cached := assetSnapshotCache[asset]
if (IsObject(cached) && (nowTick - assetMarketRefreshTick[asset]) < marketRefreshMs) {
if (IsSnapshotExpired(cached, 0.5)) {
ClearKalshiMarketCache(asset)
} else {
if (RefreshKalshiBestAsks(cached))
return cached
return false
}
}
}
attempt := 1
while (attempt <= maxRetries) {
url := "https://api.elections.kalshi.com/trade-api/v2/markets?series_ticker=" series "&status=open&limit=1&_=" A_TickCount "-" attempt
body := HttpGet(url)
if (body != "") {
marketTicker := GetFirstNonEmptyJsonField(body, ["ticker"])
yesPrice := GetKalshiDollarPrice(body, "yes_ask_dollars", "yes_ask")
noPrice := GetKalshiDollarPrice(body, "no_ask_dollars", "no_ask")
closeIso := GetFirstNonEmptyJsonField(body, ["close_time", "expected_expiration_time", "expiration_time", "settlement_time"])
minsLeft := MinutesUntilIsoUtc(closeIso)
; Skip markets that already closed or close in < 30 seconds (stale/expired)
if (minsLeft != "" && minsLeft < 0.5) {
if (attempt < maxRetries) {
Sleep 200
attempt++
continue
}
}
if (marketTicker != "") {
obj := {}
obj.up := (yesPrice != "") ? yesPrice + 0 : ""
obj.down := (noPrice != "") ? noPrice + 0 : ""
obj.minutesLeft := minsLeft
obj.closeTime := closeIso
obj.marketTicker := marketTicker
assetSnapshotCache[asset] := obj
assetMarketRefreshTick[asset] := nowTick
if (RefreshKalshiBestAsks(obj) || (obj.up != "" && obj.down != ""))
return obj
}
}
if (attempt < maxRetries)
Sleep 200
attempt++
}
if (assetSnapshotCache.HasKey(asset)) {
cached := assetSnapshotCache[asset]
if IsObject(cached) {
if (IsSnapshotExpired(cached, 0.5)) {
ClearKalshiMarketCache(asset)
return false
}
if (RefreshKalshiBestAsks(cached))
return cached
}
}
return false
}
RefreshKalshiBestAsks(snapshot) {
if !IsObject(snapshot) || snapshot.marketTicker = ""
return false
url := "https://api.elections.kalshi.com/trade-api/v2/markets/" snapshot.marketTicker "/orderbook?depth=1&_=" A_TickCount
body := HttpGet(url)
if (body = "")
return false
yesBid := GetOrderbookBestBid(body, "yes_dollars")
noBid := GetOrderbookBestBid(body, "no_dollars")
updated := false
if (noBid != "") {
snapshot.up := Round(1 - (noBid + 0), 4)
updated := true
}
if (yesBid != "") {
snapshot.down := Round(1 - (yesBid + 0), 4)
updated := true
}
return updated
}
GetOrderbookBestBid(json, sideField) {
pattern := """" sideField """\s*:\s*\[\s*\[\s*""?(-?\d+(?:\.\d+)?)"
if RegExMatch(json, pattern, m)
return m1
return ""
}
GetKalshiDollarPrice(json, dollarField, centsField := "") {
val := GetFirstNonEmptyJsonField(json, [dollarField])
if (val != "")
return val
if (centsField != "") {
val := GetFirstNonEmptyJsonField(json, [centsField])
if (val != "" && IsNumericStr(val))
return (val + 0) / 100.0
}
return ""
}
GetFirstNonEmptyJsonField(json, fields, skipZero := false) {
for _, field in fields {
val := JsonField(json, field)
if (val != "") {
if (skipZero && IsNumericStr(val) && (val + 0) = 0)
continue
return val
}
}
return ""
}
IsNumericStr(s) {
s := Trim(s)
return RegExMatch(s, "^-?\d+(\.\d+)?$")
}
JsonField(json, field) {
pattern := """" field """\s*:\s*(""([^""]*)""|[^,}\s][^,}\r\n]*)"
result := ""
pos := 1
while (pos := RegExMatch(json, pattern, m, pos)) {
val := Trim(m1)
val := Trim(val, """")
if (val != "")
result := val
pos += StrLen(m)
}
return result
}
MinutesUntilIsoUtc(iso) {
ts := IsoUtcToAhk(iso)
if (ts = "")
return ""
nowUtc := CurrentUtcNowAhk()
if (nowUtc = "")
return ""
diff := ts
EnvSub, diff, %nowUtc%, Seconds
return diff / 60.0
}
IsoUtcToAhk(iso) {
if (iso = "")
return ""
if !RegExMatch(iso, "O)^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})", m)
return ""
return m1 m2 m3 m4 m5 m6
}