Video 13: Polymarket Crypto Trading Bot
Latest Nightshark Version Required
This code requires July 27, 2026 or newer version of Nightshark. If you downloaded Nightshark before July 27, 2026, You need to re-download from the website and follow this tutorial.
Download Latest NightsharkThis video covers a Polymarket trading bot that automates crypto up/down markets across the 5-minute, 15-minute, and 1-hour timeframes. The same strategy can run across every supported crypto asset, so the trading rules do not have to be rewritten for each market. These controls work together. A contract entering the entry zone is not enough by itself: the selected session window and delta requirement must also pass before the bot can submit an entry.
YouTube Video
Paper-Trading Code
; ======================================
; PAPER TRADING CONFIGURATION
; =======================================
initialBalance := 1000 ; paper trading starting balance (USDC)
assets := ["BTC"] ; Options ["ETH"], ["SOL"], ["XRP"], ["DOGE"], ["HYPE"], ["BNB"]
TimeFrame := "15M" ; Options: 5M, 15M, 1H
timeDelay := 15 ; activation window in remaining minutes; cannot exceed TimeFrame duration
EntryRange := [0.60, 0.80] ; inclusive entry range: [Min, Max]
Exit := 0.40 ; stop loss when entered side price < this value
TakeProfitPts := 0.10 ; take profit at confirmed fill price + these points; 1 holds winners to resolution
Delta := 0 ; min |market-source open - live market-source price| required alongside EntryRange
orderSize := 5 ; exact number of contracts requested per entry
; =============================================================
; Polymarket Market Data Config (paper trading — no live orders)
; DO NOT TOUCH BELOW VALUES UNLESS YOU KNOW WHAT YOU ARE DOING
; =============================================================
pollIntervalMs := 600
pollJitterMaxMs := 600
marketRefreshMs := 3000 ; refresh market/session metadata less often; midpoints still refresh every loop
polymarketGammaApi := "https://gamma-api.polymarket.com"
polymarketCryptoPriceApi := "https://polymarket.com/api/crypto/crypto-price"
polymarketTimeFrameSeconds := (TimeFrame = "5M") ? 300 : (TimeFrame = "15M" ? 900 : 3600)
polymarketPriceVariant := (TimeFrame = "5M") ? "fiveminute" : (TimeFrame = "15M" ? "fifteen" : "hourly")
if (TimeFrame = "5M")
polymarketAssetSlugPrefix := { "BTC": "btc-updown-5m", "ETH": "eth-updown-5m", "SOL": "sol-updown-5m", "XRP": "xrp-updown-5m", "DOGE": "doge-updown-5m", "HYPE": "hype-updown-5m", "BNB": "bnb-updown-5m" }
else if (TimeFrame = "15M")
polymarketAssetSlugPrefix := { "BTC": "btc-updown-15m", "ETH": "eth-updown-15m", "SOL": "sol-updown-15m", "XRP": "xrp-updown-15m", "DOGE": "doge-updown-15m", "HYPE": "hype-updown-15m", "BNB": "bnb-updown-15m" }
else
polymarketAssetSlugPrefix := { "BTC": "bitcoin-up-or-down", "ETH": "ethereum-up-or-down", "SOL": "solana-up-or-down", "XRP": "xrp-up-or-down", "DOGE": "dogecoin-up-or-down", "HYPE": "hype-up-or-down", "BNB": "bnb-up-or-down" }
polymarketChainlinkSymbolMap := { "BTC": "btc/usd", "ETH": "eth/usd", "SOL": "sol/usd", "XRP": "xrp/usd", "DOGE": "doge/usd", "HYPE": "hype/usd", "BNB": "bnb/usd" }
polymarketBinanceSymbolMap := { "BTC": "btcusdt", "ETH": "ethusdt", "SOL": "solusdt", "XRP": "xrpusdt", "DOGE": "dogeusdt", "HYPE": "hypeusdt", "BNB": "bnbusdt" } ; 1H markets resolve on Binance spot
polymarketFeedLiveMaxAgeMs := 60000
polymarketHttpTimeoutMs := 1000
TimeFrame := Trim(TimeFrame)
StringUpper, TimeFrame, TimeFrame
settingsValid := true
timeFrameValid := (TimeFrame = "5M" || TimeFrame = "15M" || TimeFrame = "1H")
if (!timeFrameValid) {
Log("ERROR: TimeFrame is '" TimeFrame "'. Set it to 5M, 15M, or 1H.")
settingsValid := false
}
initialBalanceValid := IsNumericStr(initialBalance) && (initialBalance + 0) > 0
if (!initialBalanceValid) {
Log("ERROR: initialBalance is '" initialBalance "'. It needs to be a number greater than zero, for example 1000.")
settingsValid := false
}
EntryMin := ""
EntryMax := ""
entryRangeShapeValid := (IsObject(EntryRange) && EntryRange.Length() = 2)
if (!entryRangeShapeValid) {
Log("ERROR: EntryRange must be [Min, Max], for example [0.60, 0.70]. Both values need to be decimals between 0 and 1.")
settingsValid := false
} else {
entryMinRaw := EntryRange[1]
entryMaxRaw := EntryRange[2]
entryMinValid := IsNumericStr(entryMinRaw) && (entryMinRaw + 0) > 0 && (entryMinRaw + 0) < 1
entryMaxValid := IsNumericStr(entryMaxRaw) && (entryMaxRaw + 0) > 0 && (entryMaxRaw + 0) < 1
if (!entryMinValid) {
Log("ERROR: EntryRange Min is '" entryMinRaw "'. It needs to be a decimal between 0 and 1, for example 0.60.")
settingsValid := false
} else {
EntryMin := entryMinRaw + 0
}
if (!entryMaxValid) {
Log("ERROR: EntryRange Max is '" entryMaxRaw "'. It needs to be a decimal between 0 and 1, for example 0.70.")
settingsValid := false
} else {
EntryMax := entryMaxRaw + 0
}
if (entryMinValid && entryMaxValid && EntryMin > EntryMax) {
Log("ERROR: EntryRange Min " EntryMin " is greater than Max " EntryMax ". Put the smaller decimal first, for example [0.60, 0.70].")
settingsValid := false
}
}
exitValid := IsNumericStr(Exit) && (Exit + 0) > 0 && (Exit + 0) < 1
if (!exitValid) {
Log("ERROR: Exit is '" Exit "'. It needs to be a decimal between 0 and 1, for example 0.40.")
settingsValid := false
}
takeProfitPtsValid := IsNumericStr(TakeProfitPts) && (TakeProfitPts + 0) > 0 && (TakeProfitPts + 0) <= 1
if (!takeProfitPtsValid) {
Log("ERROR: TakeProfitPts is '" TakeProfitPts "'. It needs to be greater than 0 and no more than 1, for example 0.10 or 1.")
settingsValid := false
}
timeDelayValid := IsNumericStr(timeDelay) && (timeDelay + 0) > 0
if (!timeDelayValid) {
Log("ERROR: timeDelay is '" timeDelay "'. It needs to be a number greater than zero, measured in minutes.")
settingsValid := false
} else if (timeFrameValid) {
maxTimeDelay := (TimeFrame = "5M") ? 5 : (TimeFrame = "15M" ? 15 : 60)
timeFrameWindowName := (TimeFrame = "5M") ? "5-minute" : (TimeFrame = "15M" ? "15-minute" : "1-hour")
if ((timeDelay + 0) > maxTimeDelay) {
Log("ERROR: timeDelay is " timeDelay ", but a " timeFrameWindowName " window needs timeDelay less than or equal to " maxTimeDelay ". Your settings are not valid.")
settingsValid := false
}
}
orderSizeText := Trim(orderSize)
orderSizeValid := RegExMatch(orderSizeText, "^\d+(\.\d{1,2})?$") && (orderSize + 0) > 0
if (!orderSizeValid) {
Log("ERROR: orderSize is '" orderSize "'. Set it to a positive contract count with no more than two decimal places, for example 5 or 5.50.")
settingsValid := false
} else if ((orderSize + 0) < 5) {
Log("ERROR: Paper trading requires orderSize of at least 5 to match Polymarket's live-trading minimum and follow good trading practice.")
stopCode()
return
}
if (!settingsValid) {
Log("ERROR: Fix the settings listed above and restart the script. Stopping now.")
stopCode()
return
}
if ((TakeProfitPts + 0) = 1)
Log("TakeProfitpts = 1 , winner will be held to resolution")
; Track one open position per asset.
positions := {}
assetPhase := {} ; WAIT_WINDOW | MONITORING | IN_POSITION
assetSessionWindow := {} ; timeframe session currently tracked per asset
assetLastPriceLogTick := {} ; throttled logging control
assetSnapshotCache := {}
assetMarketRefreshTick := {}
assetSessionOpenPrice := {}
assetSessionOpenKey := {}
assetMarketLogStreamVisible := {}
assetFeedLive := {}
; Paper trading state.
paperBalance := initialBalance + 0
paperWins := 0
paperLosses := 0
paperGrossProfit := 0
paperGrossLoss := 0
paperTradeCount := 0
Log("Polymarket PAPER TRADING Started | TimeFrame: " TimeFrame " | EntryRange: [" EntryMin ", " EntryMax "] | Contracts: " orderSize " | Exit: " Exit " | TakeProfitPts: " TakeProfitPts " | Delta: " Delta " | Window: last " timeDelay "m | InitialBalance: " Fmt2(initialBalance))
feedStartupWaitLogged := true
feedStartupTick := A_TickCount
strategyStartupTick := A_TickCount
strategyStartupReadyLogged := false
Log("Starting Polymarket feed in background...")
EnsurePolymarketFeedHelper()
UpdatePaperAccountLogStream()
loop {
for _, asset in assets {
snapshot := GetPolymarketMarketSnapshot(asset)
if !IsObject(snapshot) {
HidePolymarketMarketLogStream(asset)
if (PositionSessionEnded(asset))
MarkHeldToResolution(asset)
noDataKey := asset "|NO_DATA_" CurrentTimeFrameIndex()
if !IsObject(logOnceKeys)
logOnceKeys := {}
if !logOnceKeys.HasKey(noDataKey) {
logOnceKeys[noDataKey] := true
Log(asset " Waiting for Polymarket market data")
}
Sleep 200
continue
}
up := snapshot.up
down := snapshot.down
minsLeft := SnapshotMinutesLeft(snapshot)
if (minsLeft = "")
minsLeft := MinutesRemainingInTimeFrame()
inEntryWindow := (minsLeft > 0 && minsLeft <= timeDelay)
sessionKey := PolymarketSnapshotSessionKey(snapshot)
if (PositionSessionEnded(asset, snapshot))
MarkHeldToResolution(asset)
if (!assetSessionWindow.HasKey(asset)) {
HidePolymarketMarketLogStream(asset)
assetSessionWindow[asset] := sessionKey
assetPhase[asset] := "WAIT_WINDOW"
assetLastPriceLogTick[asset] := 0
LogOnceReset(asset)
openPrice := LoadPolymarketOpeningPrice(asset, snapshot)
if (openPrice != "") {
Log(asset " Polymarket session (" snapshot.marketTicker ") | " asset " OPEN : " FmtOrNA(openPrice))
} else {
Log(asset " Polymarket session (" snapshot.marketTicker ") | waiting for " asset " OPEN")
continue
}
}
if (assetSessionWindow[asset] != sessionKey) {
HidePolymarketMarketLogStream(asset)
assetSessionWindow[asset] := sessionKey
assetPhase[asset] := "WAIT_WINDOW"
assetLastPriceLogTick[asset] := 0
LogOnceReset(asset)
openPrice := LoadPolymarketOpeningPrice(asset, snapshot)
if (openPrice != "")
Log(asset " New Polymarket session (" snapshot.marketTicker ") | " asset " OPEN : " FmtOrNA(openPrice))
else
Log(asset " New Polymarket session (" snapshot.marketTicker ") | waiting for " asset " OPEN")
continue
}
if (!HasPolymarketOpenReference(asset, sessionKey)) {
openPrice := LoadPolymarketOpeningPrice(asset, snapshot)
if (openPrice = "") {
HidePolymarketMarketLogStream(asset)
LogOnce(asset, "WAIT_OPEN_PRICE", asset " Waiting for " asset " OPEN")
continue
}
Log(asset " OPEN : " FmtOrNA(openPrice))
}
if (!strategyStartupReadyLogged) {
strategyStartupReadyLogged := true
startupSeconds := (A_TickCount - strategyStartupTick) / 1000.0
Log("Polymarket strategy ready in " Format("{:.2f}", startupSeconds) "s")
}
if IsObject(positions[asset]) {
pos := positions[asset]
current := (pos.side = "UP") ? up : down
pos.lastMark := current + 0
assetPhase[asset] := "IN_POSITION"
UpdatePolymarketLogStream(asset, snapshot)
exitReason := ""
exitPhase := ""
exitWaitMessage := ""
takeProfitTarget := pos.HasKey("takeProfitTarget") ? pos.takeProfitTarget + 0 : (pos.entry + TakeProfitPts)
if (current < Exit) {
exitReason := "exit_below_" Exit
exitPhase := "STOPPED_OUT"
exitWaitMessage := asset " Stopped out, waiting for next session"
Log(asset " Stop-loss triggered @ " Fmt(current) " < " Fmt(Exit))
} else if (takeProfitTarget <= 1 && current >= takeProfitTarget) {
exitReason := "take_profit_at_" takeProfitTarget
exitPhase := "TOOK_PROFIT"
exitWaitMessage := asset " Take profit completed, waiting for next session"
Log(asset " Take-profit triggered @ " Fmt(current) " >= " Fmt(takeProfitTarget) " (fill " Fmt(pos.entry) " + " Fmt(TakeProfitPts) ")")
}
if (exitReason != "") {
RecordPaperTrade(asset, pos.side, pos.entry, current, pos.size, exitReason)
Log(asset " Position closed")
positions.Delete(asset)
assetPhase[asset] := exitPhase
HidePolymarketMarketLogStream(asset)
LogOnce(asset, exitPhase, exitWaitMessage)
}
continue
}
if (assetPhase[asset] = "STOPPED_OUT") {
HidePolymarketMarketLogStream(asset)
LogOnce(asset, "STOPPED_OUT", asset " Stopped out, waiting for next session")
continue
}
if (assetPhase[asset] = "TOOK_PROFIT") {
HidePolymarketMarketLogStream(asset)
LogOnce(asset, "TOOK_PROFIT", asset " Take profit completed, waiting for next session")
continue
}
if (assetPhase[asset] = "BUY_FAILED") {
HidePolymarketMarketLogStream(asset)
LogOnce(asset, "BUY_FAILED", asset " Buy failed earlier, waiting for next session")
continue
}
; --- Waiting for entry window ---
if (!inEntryWindow) {
HidePolymarketMarketLogStream(asset)
if (minsLeft <= 0)
LogOnce(asset, "SESSION_ENDED", asset " End of session, waiting for new Polymarket 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 Polymarket entry range [" Fmt(EntryMin) ", " Fmt(EntryMax) "] | Delta >= " Delta)
}
LiveDelta := UpdatePolymarketLogStream(asset, snapshot)
side := ""
entryPrice := ""
upInEntryRange := (up >= EntryMin && up <= EntryMax)
downInEntryRange := (down >= EntryMin && down <= EntryMax)
if (upInEntryRange || downInEntryRange) {
if (LiveDelta != "" && LiveDelta >= Delta) {
if (upInEntryRange && (!downInEntryRange || up >= down)) {
side := "UP"
entryPrice := up
} else {
side := "DOWN"
entryPrice := down
}
} else {
LogOnce(asset, "DELTA_LOW", asset " LiveDelta " FmtOrNA(LiveDelta) " < Delta " Delta ", skipping entry")
}
} else {
LogOnce(asset, "PRICE_OUT_OF_RANGE", asset " UP " Fmt(up) " / DOWN " Fmt(down) " outside entry range [" Fmt(EntryMin) ", " Fmt(EntryMax) "]")
}
if (side = "")
continue
Log(asset " " side " triggered @ " Fmt(entryPrice) " — placing paper order")
if (!PaperBuyPosition(asset, side, entryPrice)) {
assetPhase[asset] := "BUY_FAILED"
HidePolymarketMarketLogStream(asset)
LogOnce(asset, "BUY_FAILED", asset " Paper buy failed, waiting for next session")
continue
}
fillPrice := entryPrice + 0
takeProfitTarget := fillPrice + (TakeProfitPts + 0)
tokenId := (side = "UP") ? snapshot.upTokenId : snapshot.downTokenId
positions[asset] := { side: side, entry: fillPrice, takeProfitTarget: takeProfitTarget, ticker: snapshot.marketTicker, tokenId: tokenId, sessionKey: sessionKey, startSec: snapshot.startSec, endSec: snapshot.endSec, size: orderSize + 0, lastMark: fillPrice }
assetPhase[asset] := "IN_POSITION"
assetLastPriceLogTick[asset] := 0
Log(asset " Paper position confirmed | fill: " Fmt(fillPrice) " | size: " PolymarketFormatSize(orderSize))
if (takeProfitTarget > 1)
Log(asset " TP target " Fmt(takeProfitTarget) " > 1; no TP exit—hold for resolution")
else
Log(asset " Monitoring stop-loss < " Fmt(Exit) " | take-profit >= " Fmt(takeProfitTarget) " (fill + " Fmt(TakeProfitPts) ")")
}
UpdatePaperAccountLogStream()
if (pollIntervalMs > 0 || pollJitterMaxMs > 0) {
Random, pollJitterMs, 0, %pollJitterMaxMs%
sleepMs := pollIntervalMs + pollJitterMs
if (sleepMs > 0)
Sleep %sleepMs%
}
}
; =========================
; Paper Trading Engine
; =========================
PaperBuyPosition(asset, side, price) {
global paperBalance, orderSize
cost := (price + 0) * (orderSize + 0)
if (cost > paperBalance) {
Log(asset " Paper buy rejected: cost " Fmt2(cost) " exceeds balance " Fmt2(paperBalance))
Log("Not enough balance left to place trades. Stopping script.")
stopCode()
return false
}
paperBalance -= cost
Log(asset " Paper " side " filled @ " Fmt(price) " | size " PolymarketFormatSize(orderSize) " | cost " Fmt2(cost) " | balance " Fmt2(paperBalance))
return true
}
RecordPaperTrade(asset, side, entryPrice, exitPrice, size, reason) {
global paperBalance, paperWins, paperLosses, paperGrossProfit, paperGrossLoss, paperTradeCount
proceeds := (exitPrice + 0) * (size + 0)
pnl := ((exitPrice + 0) - (entryPrice + 0)) * (size + 0)
paperBalance += proceeds
paperTradeCount++
if (pnl > 0) {
paperWins++
paperGrossProfit += pnl
} else {
paperLosses++
paperGrossLoss += Abs(pnl)
}
Log(asset " Paper " side " closed @ " Fmt(exitPrice) " | entry " Fmt(entryPrice) " | size " PolymarketFormatSize(size) " | PnL " FmtPnl(pnl) " | balance " Fmt2(paperBalance) " | " reason)
}
ComputePaperEquity() {
global paperBalance, positions
equity := paperBalance + 0
for _, pos in positions {
if !IsObject(pos)
continue
mark := pos.HasKey("lastMark") ? pos.lastMark + 0 : pos.entry + 0
equity += mark * (pos.size + 0)
}
return equity
}
ComputePaperLivePnl() {
global initialBalance
return ComputePaperEquity() - (initialBalance + 0)
}
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("endSec") && CurrentUnixSeconds() >= pos.endSec)
return true
if (pos.HasKey("sessionKey") && IsObject(snapshot) && PolymarketSnapshotSessionKey(snapshot) != pos.sessionKey)
return true
return false
}
MarkHeldToResolution(asset) {
global positions, assetPhase
if !IsObject(positions[asset])
return
pos := positions[asset]
lastMark := pos.HasKey("lastMark") ? pos.lastMark + 0 : pos.entry + 0
settlePrice := (lastMark >= 0.5) ? 1 : 0
Log(asset " " pos.side " held to resolution | settled at " settlePrice " (last price " Fmt(lastMark) ")")
RecordPaperTrade(asset, pos.side, pos.entry, settlePrice, pos.size, "held_to_resolution")
positions.Delete(asset)
ClearPolymarketMarketCache(asset)
assetPhase[asset] := "WAIT_WINDOW"
}
PolymarketSnapshotSessionKey(snapshot) {
if !IsObject(snapshot)
return CurrentTimeFrameIndex()
if (snapshot.HasKey("marketTicker") && snapshot.marketTicker != "")
return snapshot.marketTicker
if (snapshot.HasKey("closeTime") && snapshot.closeTime != "")
return snapshot.closeTime
if (snapshot.HasKey("startSec") && snapshot.startSec != "")
return snapshot.startSec
return CurrentTimeFrameIndex()
}
HasPolymarketOpenReference(asset, sessionKey) {
global assetSessionOpenPrice, assetSessionOpenKey
return (assetSessionOpenKey.HasKey(asset) && assetSessionOpenKey[asset] = sessionKey && assetSessionOpenPrice.HasKey(asset) && (assetSessionOpenPrice[asset] + 0) > 0)
}
LoadPolymarketOpeningPrice(asset, snapshot) {
global assetSessionOpenPrice, assetSessionOpenKey
sessionKey := PolymarketSnapshotSessionKey(snapshot)
if (HasPolymarketOpenReference(asset, sessionKey))
return assetSessionOpenPrice[asset] + 0
openPrice := FetchPolymarketSessionOpenPrice(asset, snapshot)
if (openPrice = "")
return ""
assetSessionOpenPrice[asset] := openPrice + 0
assetSessionOpenKey[asset] := sessionKey
return openPrice + 0
}
FetchPolymarketSessionOpenPrice(asset, snapshot) {
body := FetchPolymarketCryptoPriceBody(asset, snapshot)
openPrice := JsonField(body, "openPrice")
if (openPrice != "" && (openPrice + 0) > 0)
return openPrice + 0
return ""
}
FetchPolymarketCryptoPriceBody(asset, snapshot) {
url := PolymarketCryptoPriceUrl(asset, snapshot)
return (url != "") ? PolymarketHttpGet(url) : ""
}
PolymarketCryptoPriceUrl(asset, snapshot) {
global polymarketCryptoPriceApi, polymarketPriceVariant
if (!IsObject(snapshot) || snapshot.startSec = "" || snapshot.endSec = "")
return ""
symbol := PolymarketAssetSymbolParam(asset)
startIso := UnixSecondsToIsoUtc(snapshot.startSec)
endIso := UnixSecondsToIsoUtc(snapshot.endSec)
if (symbol = "" || startIso = "" || endIso = "")
return ""
url := polymarketCryptoPriceApi "?symbol=" PolymarketQueryEncode(symbol)
url .= "&eventStartTime=" PolymarketQueryEncode(startIso)
url .= "&variant=" PolymarketQueryEncode(polymarketPriceVariant)
url .= "&endDate=" PolymarketQueryEncode(endIso)
return url
}
PolymarketAssetSymbolParam(asset) {
StringUpper, symbol, asset
return symbol
}
UnixSecondsToIsoUtc(sec) {
sec := Round(sec + 0)
if (sec <= 0)
return ""
ts := "19700101000000"
EnvAdd, ts, %sec%, Seconds
return SubStr(ts, 1, 4) "-" SubStr(ts, 5, 2) "-" SubStr(ts, 7, 2) "T" SubStr(ts, 9, 2) ":" SubStr(ts, 11, 2) ":" SubStr(ts, 13, 2) "Z"
}
PolymarketQueryEncode(value) {
value := StrReplace(value, "%", "%25")
value := StrReplace(value, ":", "%3A")
value := StrReplace(value, " ", "%20")
return value
}
UpdatePolymarketLogStream(asset, snapshot) {
global assetSessionOpenPrice, assetSessionOpenKey
if !IsObject(snapshot)
return ""
priceObj := GetCachedPolymarketLivePriceObj(asset, snapshot)
livePrice := (IsObject(priceObj) && priceObj.HasKey("value") && priceObj.value > 0) ? priceObj.value : ""
if (livePrice = "" && IsObject(priceObj) && priceObj.HasKey("price") && priceObj.price > 0)
livePrice := priceObj.price
sessionKey := PolymarketSnapshotSessionKey(snapshot)
openPrice := ""
LiveDelta := ""
if (HasPolymarketOpenReference(asset, sessionKey)) {
openPrice := assetSessionOpenPrice[asset] + 0
if (livePrice != "")
LiveDelta := Abs(openPrice - (livePrice + 0))
}
LogMonitoringStream(asset, snapshot, livePrice, openPrice, LiveDelta)
return LiveDelta
}
LogMonitoringStream(asset, snapshot, livePrice := "", openPrice := "", LiveDelta := "") {
global assetMarketLogStreamVisible
if !IsObject(snapshot)
return
; Account stream is refreshed once per loop pass at the bottom of the main
; loop; each LogStream call is a blocking cross-process message.
if (livePrice != "")
LogStream(asset, FmtOrNA(livePrice))
else
LogStream(asset, "n/a")
LogStream("OPEN PRICE", FmtOrNA(openPrice))
LogStream("up", FmtOrNA(snapshot.up))
LogStream("down", FmtOrNA(snapshot.down))
LogStream("LIVE DELTA", FmtLiveDeltaOrNA(LiveDelta))
assetMarketLogStreamVisible[asset] := true
}
UpdatePaperAccountLogStream() {
global paperBalance, paperWins, paperLosses, paperGrossProfit, paperGrossLoss, paperTradeCount
LogStream("LIVE PNL", FmtPnl(ComputePaperLivePnl()))
LogStream("BALANCE", Fmt2(ComputePaperEquity()))
LogStream("WINS", paperWins "/" paperTradeCount)
LogStream("AVG PROFIT", (paperWins > 0) ? Fmt2(paperGrossProfit / paperWins) : "n/a")
LogStream("AVG LOSS", (paperLosses > 0) ? Fmt2(paperGrossLoss / paperLosses) : "n/a")
}
HidePolymarketMarketLogStream(asset) {
global assetMarketLogStreamVisible
if (!assetMarketLogStreamVisible.HasKey(asset) || !assetMarketLogStreamVisible[asset])
return
LogStreamRemove(asset)
LogStreamRemove("OPEN PRICE")
LogStreamRemove("up")
LogStreamRemove("down")
LogStreamRemove("LIVE DELTA")
assetMarketLogStreamVisible[asset] := false
}
GetCachedPolymarketLivePriceObj(asset, snapshot, forceRefresh := false) {
global assetFeedLive, polymarketFeedLiveMaxAgeMs
if (IsObject(assetFeedLive) && assetFeedLive.HasKey(asset)) {
entry := assetFeedLive[asset]
if ((A_TickCount - entry.tick) <= polymarketFeedLiveMaxAgeMs)
return { ok: true, value: entry.value + 0, price: entry.value + 0 }
}
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)
}
Fmt2(value) {
return Format("{:.2f}", value + 0)
}
FmtPnl(value) {
if (value = "")
return "n/a"
value := Round(value + 0, 2)
if (value > 0)
return "+" Format("{:.2f}", value)
return Format("{:.2f}", value)
}
PolymarketFormatSize(size) {
value := Round((size + 0) * 1000000) / 1000000.0
text := Format("{:.6f}", value)
while (InStr(text, ".") && SubStr(text, StrLen(text), 1) = "0")
text := SubStr(text, 1, StrLen(text) - 1)
if (SubStr(text, StrLen(text), 1) = ".")
text := SubStr(text, 1, StrLen(text) - 1)
return text
}
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 ""
}
RunAndCapture(command) {
return RunCMD(command, A_ScriptDir)
}
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
}
MinutesRemainingInTimeFrame() {
global polymarketTimeFrameSeconds
nowSec := CurrentUnixSeconds()
if (nowSec > 0)
return (polymarketTimeFrameSeconds - Mod(nowSec, polymarketTimeFrameSeconds)) / 60.0
currentHour := A_Hour + 0
currentMinute := A_Min + 0
currentSecond := A_Sec + 0
secondsToday := (currentHour * 3600) + (currentMinute * 60) + currentSecond
return (polymarketTimeFrameSeconds - Mod(secondsToday, polymarketTimeFrameSeconds)) / 60.0
}
IsXMinRemaining(x) {
return (MinutesRemainingInTimeFrame() <= x)
}
CurrentTimeFrameIndex() {
global polymarketTimeFrameSeconds
nowSec := CurrentUnixSeconds()
if (nowSec > 0)
return Floor(nowSec / polymarketTimeFrameSeconds)
secondsToday := ((A_Hour + 0) * 3600) + ((A_Min + 0) * 60) + (A_Sec + 0)
return Floor(secondsToday / polymarketTimeFrameSeconds)
}
SnapshotMinutesLeft(snapshot) {
if !IsObject(snapshot)
return ""
if (snapshot.HasKey("endSec") && snapshot.endSec != "") {
nowSec := CurrentUnixSeconds()
if (nowSec > 0)
return ((snapshot.endSec + 0) - nowSec) / 60.0
}
if (snapshot.HasKey("minutesLeft"))
return snapshot.minutesLeft
return ""
}
IsSnapshotExpired(snapshot, cutoffMinutes := 0) {
minsLeft := SnapshotMinutesLeft(snapshot)
return (minsLeft != "" && minsLeft <= cutoffMinutes)
}
ClearPolymarketMarketCache(asset) {
global assetSnapshotCache, assetMarketRefreshTick
if (assetSnapshotCache.HasKey(asset))
assetSnapshotCache.Delete(asset)
if (assetMarketRefreshTick.HasKey(asset))
assetMarketRefreshTick.Delete(asset)
}
GetPolymarketMarketSnapshot(asset, maxRetries := 1) {
global polymarketGammaApi, polymarketAssetSlugPrefix, assetSnapshotCache, assetMarketRefreshTick, marketRefreshMs, polymarketHttpTimeoutMs
if !polymarketAssetSlugPrefix.HasKey(asset)
return false
prefix := polymarketAssetSlugPrefix[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)) {
ClearPolymarketMarketCache(asset)
} else {
if (RefreshPolymarketBestPrices(asset, cached))
return cached
return false
}
}
}
attempt := 1
while (attempt <= maxRetries) {
window := CurrentPolymarketWindow()
slug := BuildPolymarketMarketSlug(prefix, window)
body := PolymarketHttpGet(polymarketGammaApi "/events?slug=" slug "&_=" A_TickCount "-" attempt)
if (body != "") {
upTokenId := ExtractLargeNumberAfter(body, "clobTokenIds", 1)
downTokenId := ExtractLargeNumberAfter(body, "clobTokenIds", 2)
if (upTokenId = "" || downTokenId = "") {
if (attempt < maxRetries) {
Sleep 200
attempt++
continue
}
break
}
FetchPolymarketTokenPricePair(asset, upTokenId, downTokenId, upPrice, downPrice)
minsLeft := (window.endSec - CurrentUnixSeconds()) / 60.0
; Skip markets that already closed or close in < 30 seconds (stale/expired)
if (minsLeft != "" && minsLeft < 0.5) {
if (attempt < maxRetries) {
Sleep 200
attempt++
continue
}
break
}
if (upPrice != "" && downPrice != "") {
obj := {}
obj.up := upPrice + 0
obj.down := downPrice + 0
obj.minutesLeft := minsLeft
obj.startSec := window.startSec
obj.endSec := window.endSec
obj.closeTime := slug
obj.marketTicker := slug
obj.upTokenId := upTokenId
obj.downTokenId := downTokenId
obj.conditionId := JsonField(body, "conditionId")
assetSnapshotCache[asset] := obj
assetMarketRefreshTick[asset] := nowTick
return obj
}
}
if (attempt < maxRetries)
Sleep 200
attempt++
}
if (assetSnapshotCache.HasKey(asset)) {
cached := assetSnapshotCache[asset]
if IsObject(cached) {
if (IsSnapshotExpired(cached, 0.5)) {
ClearPolymarketMarketCache(asset)
return false
}
if (RefreshPolymarketBestPrices(asset, cached))
return cached
}
}
return false
}
RefreshPolymarketBestPrices(asset, snapshot) {
if !IsObject(snapshot)
return false
FetchPolymarketTokenPricePair(asset, snapshot.upTokenId, snapshot.downTokenId, upPrice, downPrice)
snapshot.minutesLeft := SnapshotMinutesLeft(snapshot)
if (upPrice = "" || downPrice = "")
return false
snapshot.up := upPrice + 0
snapshot.down := downPrice + 0
return true
}
CurrentUnixSeconds() {
epoch := CurrentUtcNowAhk()
if (epoch = "")
return 0
EnvSub, epoch, 19700101000000, Seconds
return epoch + 0
}
CurrentPolymarketWindow() {
global polymarketTimeFrameSeconds
nowSec := CurrentUnixSeconds()
startSec := Floor(nowSec / polymarketTimeFrameSeconds) * polymarketTimeFrameSeconds
return { startSec: startSec, endSec: startSec + polymarketTimeFrameSeconds }
}
BuildPolymarketMarketSlug(prefix, window) {
global TimeFrame
if (TimeFrame != "1H")
return prefix "-" window.startSec
eastern := PolymarketUtcToEasternTimestamp(window.startSec)
if (eastern = "")
return ""
monthNames := ["january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"]
year := SubStr(eastern, 1, 4) + 0
month := SubStr(eastern, 5, 2) + 0
day := SubStr(eastern, 7, 2) + 0
hour24 := SubStr(eastern, 9, 2) + 0
period := (hour24 >= 12) ? "pm" : "am"
hour12 := Mod(hour24, 12)
if (hour12 = 0)
hour12 := 12
return prefix "-" monthNames[month] "-" day "-" year "-" hour12 period "-et"
}
PolymarketUtcToEasternTimestamp(sec) {
sec := Round(sec + 0)
if (sec <= 0)
return ""
utc := "19700101000000"
EnvAdd, utc, %sec%, Seconds
year := SubStr(utc, 1, 4) + 0
dstStartDay := PolymarketNthSundayDay(year, 3, 2)
dstEndDay := PolymarketNthSundayDay(year, 11, 1)
dstStartUtc := year . "03" . Format("{:02}", dstStartDay) . "070000"
dstEndUtc := year . "11" . Format("{:02}", dstEndDay) . "060000"
offsetSeconds := (utc >= dstStartUtc && utc < dstEndUtc) ? -14400 : -18000
eastern := utc
EnvAdd, eastern, %offsetSeconds%, Seconds
return eastern
}
PolymarketNthSundayDay(year, month, occurrence) {
monthOffsets := [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]
adjustedYear := year
if (month < 3)
adjustedYear--
firstWeekday := Mod(adjustedYear + Floor(adjustedYear / 4) - Floor(adjustedYear / 100) + Floor(adjustedYear / 400) + monthOffsets[month] + 1, 7)
firstSunday := 1 + Mod(7 - firstWeekday, 7)
return firstSunday + ((occurrence - 1) * 7)
}
FetchPolymarketTokenPricePair(asset, upTokenId, downTokenId, ByRef upPrice, ByRef downPrice) {
upPrice := ""
downPrice := ""
if (upTokenId = "" && downTokenId = "")
return
PolymarketFeedBulkFetch(asset, upTokenId, downTokenId, upPrice, downPrice)
}
PolymarketFeedBulkFetch(asset, upTokenId, downTokenId, ByRef upMid, ByRef downMid) {
global assetFeedLive, feedStartupWaitLogged, feedStartupReadyLogged, feedStartupTick
upMid := ""
downMid := ""
if (upTokenId = "" || downTokenId = "")
return false
if (!GetPolymarketMidpointPair(upTokenId, downTokenId, PolymarketFeedLiveSymbol(asset), upMid, downMid, livePrice)) {
; No answer on first contact means the helper is still cold-starting.
if (!feedStartupWaitLogged && !feedStartupReadyLogged) {
feedStartupWaitLogged := true
Log("Starting up Polymarket dependencies, please wait...")
}
return false
}
if (!feedStartupReadyLogged) {
feedStartupReadyLogged := true
if (feedStartupWaitLogged) {
feedStartupSeconds := (A_TickCount - feedStartupTick) / 1000.0
Log("Polymarket live feed ready in " Format("{:.2f}", feedStartupSeconds) "s")
}
}
if (livePrice != "") {
if !IsObject(assetFeedLive)
assetFeedLive := {}
assetFeedLive[asset] := { value: livePrice + 0, tick: A_TickCount }
}
return true
}
PolymarketFeedLiveSymbol(asset) {
global TimeFrame, polymarketChainlinkSymbolMap, polymarketBinanceSymbolMap
StringUpper, assetUpper, asset
StringLower, symbol, asset
if (TimeFrame = "1H") {
if (polymarketBinanceSymbolMap.HasKey(assetUpper))
return polymarketBinanceSymbolMap[assetUpper]
return symbol "usdt"
}
if (polymarketChainlinkSymbolMap.HasKey(assetUpper)) {
liveSymbol := polymarketChainlinkSymbolMap[assetUpper]
if (polymarketBinanceSymbolMap.HasKey(assetUpper))
liveSymbol .= "," polymarketBinanceSymbolMap[assetUpper]
return liveSymbol
}
return symbol "/usd," symbol "usdt"
}
PolymarketHttpAwaitResponse(http, deadlineTick) {
loop {
try {
status := http.Status
if (status = 200)
return http.ResponseText
if (status > 0) {
http.Abort()
return ""
}
} catch pendingError {
}
remainingMs := deadlineTick - A_TickCount
if (remainingMs <= 0)
break
waitMs := (remainingMs < 10) ? remainingMs : 10
Sleep %waitMs%
}
try http.Abort()
return ""
}
ExtractLargeNumberAfter(text, marker, index) {
pos := InStr(text, """" marker """")
if (!pos)
return ""
segment := SubStr(text, pos, 1500)
found := 0
scanPos := 1
while (scanPos := RegExMatch(segment, "\d{30,}", m, scanPos)) {
found++
if (found = index)
return m
scanPos += StrLen(m)
}
return ""
}
PolymarketHttpGet(url, timeoutMs := 0) {
global httpPublicObj, polymarketHttpTimeoutMs
try {
if (timeoutMs <= 0)
timeoutMs := polymarketHttpTimeoutMs
if !IsObject(httpPublicObj)
httpPublicObj := ComObjCreate("WinHttp.WinHttpRequest.5.1")
http := httpPublicObj
http.Option(9) := 2048 | 8192
phaseTimeoutMs := (timeoutMs < 500) ? timeoutMs : 500
http.SetTimeouts(phaseTimeoutMs, phaseTimeoutMs, phaseTimeoutMs, timeoutMs)
http.Open("GET", url, true)
http.SetRequestHeader("User-Agent", "Mozilla/5.0")
http.SetRequestHeader("Cache-Control", "no-cache, no-store")
http.SetRequestHeader("Pragma", "no-cache")
http.SetRequestHeader("Accept", "application/json")
http.Send()
return PolymarketHttpAwaitResponse(http, A_TickCount + timeoutMs)
} catch e {
try http.Abort()
httpPublicObj := ""
return ""
}
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
}
Live-Trading Mode
Credential Requirements
Credentials Filenames
Private Key
pmkt-privatekeyRelayer Address
pmkt-relayer-addressRelayer Key
pmkt-relayerkeyFunder address
pmkt-walletaddressLive-Trading Code
; =========================
; LIVE TRADING STRATEGY CONFIGURATION
; =========================
assets := ["BTC"] ; Options ["ETH"], ["SOL"], ["XRP"], ["DOGE"], ["HYPE"], ["BNB"]
TimeFrame := "5M" ; Options: 5M, 15M, 1H
timeDelay := 5 ; activation window in remaining minutes; cannot exceed TimeFrame duration
EntryRange := [0.70, 0.80] ; inclusive entry range: [Min, Max]
Exit := 0.40 ; stop loss when entered side price < this value
TakeProfitPts := 0.10 ; take profit at confirmed fill price + these points; 1 holds winners to resolution
Delta := 0 ; min |market-source open - live market-source price| required alongside EntryRange
orderSize := 5 ; exact number of contracts requested per entry
; =============================================================
; Polymarket Order API Config
; DO NOT TOUCH BELOW VALUES UNLESS YOU KNOW WHAT YOU ARE DOING
; =============================================================
TimeFrame := Trim(TimeFrame)
StringUpper, TimeFrame, TimeFrame
settingsValid := true
timeFrameValid := (TimeFrame = "5M" || TimeFrame = "15M" || TimeFrame = "1H")
if (!timeFrameValid) {
Log("ERROR: TimeFrame is '" TimeFrame "'. Set it to 5M, 15M, or 1H.")
settingsValid := false
}
EntryMin := ""
EntryMax := ""
entryRangeShapeValid := (IsObject(EntryRange) && EntryRange.Length() = 2)
if (!entryRangeShapeValid) {
Log("ERROR: EntryRange must be [Min, Max], for example [0.60, 0.70]. Both values need to be decimals between 0 and 1.")
settingsValid := false
} else {
entryMinRaw := EntryRange[1]
entryMaxRaw := EntryRange[2]
entryMinValid := IsNumericStr(entryMinRaw) && (entryMinRaw + 0) > 0 && (entryMinRaw + 0) < 1
entryMaxValid := IsNumericStr(entryMaxRaw) && (entryMaxRaw + 0) > 0 && (entryMaxRaw + 0) < 1
if (!entryMinValid) {
Log("ERROR: EntryRange Min is '" entryMinRaw "'. It needs to be a decimal between 0 and 1, for example 0.60.")
settingsValid := false
} else {
EntryMin := entryMinRaw + 0
}
if (!entryMaxValid) {
Log("ERROR: EntryRange Max is '" entryMaxRaw "'. It needs to be a decimal between 0 and 1, for example 0.70.")
settingsValid := false
} else {
EntryMax := entryMaxRaw + 0
}
if (entryMinValid && entryMaxValid && EntryMin > EntryMax) {
Log("ERROR: EntryRange Min " EntryMin " is greater than Max " EntryMax ". Put the smaller decimal first, for example [0.60, 0.70].")
settingsValid := false
}
}
exitValid := IsNumericStr(Exit) && (Exit + 0) > 0 && (Exit + 0) < 1
if (!exitValid) {
Log("ERROR: Exit is '" Exit "'. It needs to be a decimal between 0 and 1, for example 0.40.")
settingsValid := false
}
takeProfitPtsValid := IsNumericStr(TakeProfitPts) && (TakeProfitPts + 0) > 0 && (TakeProfitPts + 0) <= 1
if (!takeProfitPtsValid) {
Log("ERROR: TakeProfitPts is '" TakeProfitPts "'. It needs to be greater than 0 and no more than 1, for example 0.10 or 1.")
settingsValid := false
}
timeDelayValid := IsNumericStr(timeDelay) && (timeDelay + 0) > 0
if (!timeDelayValid) {
Log("ERROR: timeDelay is '" timeDelay "'. It needs to be a number greater than zero, measured in minutes.")
settingsValid := false
} else if (timeFrameValid) {
maxTimeDelay := (TimeFrame = "5M") ? 5 : (TimeFrame = "15M" ? 15 : 60)
timeFrameWindowName := (TimeFrame = "5M") ? "5-minute" : (TimeFrame = "15M" ? "15-minute" : "1-hour")
if ((timeDelay + 0) > maxTimeDelay) {
Log("ERROR: timeDelay is " timeDelay ", but a " timeFrameWindowName " window needs timeDelay less than or equal to " maxTimeDelay ". Your settings are not valid.")
settingsValid := false
}
}
orderSizeText := Trim(orderSize)
orderSizeValid := RegExMatch(orderSizeText, "^\d+(\.\d{1,2})?$") && (orderSize + 0) > 0
if (!orderSizeValid) {
Log("ERROR: orderSize is '" orderSize "'. Set it to a positive contract count with no more than two decimal places, for example 5 or 5.50.")
settingsValid := false
}
if (!settingsValid) {
Log("ERROR: Fix the settings listed above and restart the script. Stopping now.")
stopCode()
return
}
if ((TakeProfitPts + 0) = 1)
Log("TakeProfitpts = 1 , winner will be held to resolution")
enableLiveOrders := true
pollIntervalMs := 250
pollJitterMaxMs := 50
marketRefreshMs := 5000 ; refresh market/session metadata less often; midpoints still refresh every loop
restDelay := 2000 ; ms to let order rest before checking position
polymarketBuyLimitPrice := 0.99
polymarketSellLimitPrice := 0.03
polymarketEntryOrderType := "GTC" ; BUY size is an exact number of contracts
polymarketExitOrderType := "GTC" ; SELL retries only the remaining contracts
polymarketPositionDustSize := 0.01
minRedeemUsdc := 0.01
polymarketGammaApi := "https://gamma-api.polymarket.com"
polymarketDataApi := "https://data-api.polymarket.com"
polymarketCryptoPriceApi := "https://polymarket.com/api/crypto/crypto-price"
polymarketFeedLiveMaxAgeMs := 60000 ; live price from the pmkt-helper feed is trusted for this long
polymarketHttpTimeoutMs := 1000 ; total wait per public HTTP stage; the outer loop retries failures
polymarketTimeFrameSeconds := (TimeFrame = "5M") ? 300 : (TimeFrame = "15M" ? 900 : 3600)
polymarketPriceVariant := (TimeFrame = "5M") ? "fiveminute" : (TimeFrame = "15M" ? "fifteen" : "hourly")
if (TimeFrame = "5M")
polymarketAssetSlugPrefix := { "BTC": "btc-updown-5m", "ETH": "eth-updown-5m", "SOL": "sol-updown-5m", "XRP": "xrp-updown-5m", "DOGE": "doge-updown-5m", "HYPE": "hype-updown-5m", "BNB": "bnb-updown-5m" }
else if (TimeFrame = "15M")
polymarketAssetSlugPrefix := { "BTC": "btc-updown-15m", "ETH": "eth-updown-15m", "SOL": "sol-updown-15m", "XRP": "xrp-updown-15m", "DOGE": "doge-updown-15m", "HYPE": "hype-updown-15m", "BNB": "bnb-updown-15m" }
else
polymarketAssetSlugPrefix := { "BTC": "bitcoin-up-or-down", "ETH": "ethereum-up-or-down", "SOL": "solana-up-or-down", "XRP": "xrp-up-or-down", "DOGE": "dogecoin-up-or-down", "HYPE": "hype-up-or-down", "BNB": "bnb-up-or-down" }
polymarketChainlinkSymbolMap := { "BTC": "btc/usd", "ETH": "eth/usd", "SOL": "sol/usd", "XRP": "xrp/usd", "DOGE": "doge/usd", "HYPE": "hype/usd", "BNB": "bnb/usd" }
polymarketBinanceSymbolMap := { "BTC": "btcusdt", "ETH": "ethusdt", "SOL": "solusdt", "XRP": "xrpusdt", "DOGE": "dogeusdt", "HYPE": "hypeusdt", "BNB": "bnbusdt" } ; 1H markets resolve on Binance spot
polymarketZeroBytes32 := "0x0000000000000000000000000000000000000000000000000000000000000000"
polymarketSignerPath := ""
polymarketFunderAddress := ""
polymarketCredentialDir := A_ScriptDir "\reactions"
if (!FileExist(polymarketCredentialDir))
polymarketCredentialDir := A_WorkingDir "\reactions"
polymarketPrivateKeyFile := polymarketCredentialDir "\pmkt-privatekey.json"
polymarketPrivateKeyPath := ""
polymarketFunderAddressFile := polymarketCredentialDir "\pmkt-walletaddress.json"
polymarketRelayerKeyFile := polymarketCredentialDir "\pmkt-relayerkey.json"
polymarketRelayerAddressFile := polymarketCredentialDir "\pmkt-relayer-address.json"
polymarketClobCredsFile := polymarketCredentialDir "\pmkt-clob-creds.json"
polymarketPrivateKeyResolvedPath := ""
polymarketFunderAddressResolvedPath := ""
polymarketRelayerKeyResolvedPath := ""
polymarketRelayerAddressResolvedPath := ""
polymarketRelayerKey := ""
polymarketRelayerAddress := ""
polymarketCashBalance := ""
; Track one open position per asset.
positions := {}
assetPhase := {} ; WAIT_WINDOW | MONITORING | IN_POSITION
assetSessionWindow := {} ; timeframe session currently tracked per asset
assetLastPriceLogTick := {} ; throttled logging control
lastRedeemCheckedSession := "" ; wallet-wide reward check guard
assetSnapshotCache := {}
assetMarketRefreshTick := {}
assetFeedLive := {} ; latest live price per asset from the pmkt-helper feed
assetSessionOpenPrice := {}
assetSessionOpenKey := {}
polymarketLastOrderResult := {}
LoadPolymarketCredentialsFromFiles()
if (polymarketSignerPath = "")
polymarketSignerPath := FindNightSharkSigner()
Log("Polymarket Application Started | TimeFrame: " TimeFrame " | EntryRange: [" EntryMin ", " EntryMax "] | Contracts: " orderSize " | EntryOrder: " polymarketEntryOrderType " | ExitOrder: " polymarketExitOrderType " | Exit: " Exit " | TakeProfitPts: " TakeProfitPts " | Delta: " Delta " | Window: last " timeDelay "m")
feedStartupWaitLogged := true
Log("Starting up Polymarket dependencies, please wait...")
EnsurePolymarketFeedHelper()
StartupCredentialCheck()
loop {
for _, asset in assets {
snapshot := GetPolymarketMarketSnapshot(asset)
if !IsObject(snapshot) {
if (PositionSessionEnded(asset))
MarkHeldToResolution(asset)
noDataKey := asset "|NO_DATA_" CurrentTimeFrameIndex()
if !IsObject(logOnceKeys)
logOnceKeys := {}
if !logOnceKeys.HasKey(noDataKey) {
logOnceKeys[noDataKey] := true
Log(asset " Waiting for Polymarket market data")
}
continue
}
up := snapshot.up
down := snapshot.down
minsLeft := SnapshotMinutesLeft(snapshot)
if (minsLeft = "")
minsLeft := MinutesRemainingInTimeFrame()
inEntryWindow := (minsLeft > 0 && minsLeft <= timeDelay)
sessionKey := PolymarketSnapshotSessionKey(snapshot)
if (PositionSessionEnded(asset, snapshot))
MarkHeldToResolution(asset)
if (!assetSessionWindow.HasKey(asset)) {
assetSessionWindow[asset] := sessionKey
assetPhase[asset] := "WAIT_WINDOW"
assetLastPriceLogTick[asset] := 0
LogOnceReset(asset)
openPrice := LoadPolymarketOpeningPrice(asset, snapshot)
if (openPrice != "")
Log(asset " Polymarket session (" snapshot.marketTicker ") | " asset " OPEN : " FmtOrNA(openPrice))
else
Log(asset " Polymarket session (" snapshot.marketTicker ") | waiting for " asset " OPEN")
if (snapshot.marketTicker != "" && HasExistingPosition(snapshot))
assetPhase[asset] := "HAS_POSITION"
redeemSessionKey := (snapshot.startSec != "") ? snapshot.startSec : sessionKey
CheckAndRedeemPolymarketRewards(asset, redeemSessionKey)
continue
}
if (assetSessionWindow[asset] != sessionKey) {
assetSessionWindow[asset] := sessionKey
assetPhase[asset] := "WAIT_WINDOW"
assetLastPriceLogTick[asset] := 0
LogOnceReset(asset)
openPrice := LoadPolymarketOpeningPrice(asset, snapshot)
if (openPrice != "")
Log(asset " New Polymarket session (" snapshot.marketTicker ") | " asset " OPEN : " FmtOrNA(openPrice))
else
Log(asset " New Polymarket session (" snapshot.marketTicker ") | waiting for " asset " OPEN")
if (snapshot.marketTicker != "" && HasExistingPosition(snapshot))
assetPhase[asset] := "HAS_POSITION"
redeemSessionKey := (snapshot.startSec != "") ? snapshot.startSec : sessionKey
CheckAndRedeemPolymarketRewards(asset, redeemSessionKey)
continue
}
if (!HasPolymarketOpenReference(asset, sessionKey)) {
openPrice := LoadPolymarketOpeningPrice(asset, snapshot)
if (openPrice = "") {
LogOnce(asset, "WAIT_OPEN_PRICE", asset " Waiting for " asset " OPEN")
continue
}
Log(asset " OPEN : " FmtOrNA(openPrice))
}
if IsObject(positions[asset]) {
pos := positions[asset]
current := (pos.side = "UP") ? up : down
assetPhase[asset] := "IN_POSITION"
UpdatePolymarketLogStream(asset, snapshot)
exitReason := ""
exitPhase := ""
exitWaitMessage := ""
takeProfitTarget := pos.HasKey("takeProfitTarget") ? pos.takeProfitTarget + 0 : (pos.entry + TakeProfitPts)
if (current < Exit) {
exitReason := "exit_below_" Exit
exitPhase := "STOPPED_OUT"
exitWaitMessage := asset " Stopped out, waiting for next session"
Log(asset " Stop-loss triggered @ " Fmt(current) " < " Fmt(Exit))
} else if (takeProfitTarget <= 1 && current >= takeProfitTarget) {
exitReason := "take_profit_at_" takeProfitTarget
exitPhase := "TOOK_PROFIT"
exitWaitMessage := asset " Take profit completed, waiting for next session"
Log(asset " Take-profit triggered @ " Fmt(current) " >= " Fmt(takeProfitTarget) " (fill " Fmt(pos.entry) " + " Fmt(TakeProfitPts) ")")
}
if (exitReason != "") {
sellConfirmed := false
loop, 4 {
if (A_Index > 1) {
freshSnap := GetPolymarketMarketSnapshot(asset)
if IsObject(freshSnap)
current := (pos.side = "UP") ? freshSnap.up : freshSnap.down
}
if SellPosition(asset, pos.side, current, exitReason) {
Log(asset " Position closed")
sellConfirmed := true
break
}
if (PolymarketResultValue(polymarketLastOrderResult, "fatal", false))
break
if (A_Index < 4) {
Log(asset " Exit retry " A_Index "/3 | " PolymarketLastOrderApiSummary())
Sleep 1000
}
}
if (!sellConfirmed && !PolymarketResultValue(polymarketLastOrderResult, "fatal", false)) {
Log(asset " Exit order failed after 4 attempts | " PolymarketLastOrderApiSummary())
remainingSize := GetPolymarketPositionSizeChecked(pos.tokenId, finalRemainingQueryOk)
if (!finalRemainingQueryOk) {
positions[asset].size := PolymarketResultValue(polymarketLastOrderResult, "remainingSize", pos.size)
assetPhase[asset] := "IN_POSITION"
Log("ERROR: Could not verify the remaining position after sell retries. Keeping the position open and stopping for safety.")
stopCode()
} else if (remainingSize > polymarketPositionDustSize) {
positions[asset].size := remainingSize + 0
assetPhase[asset] := "IN_POSITION"
LogOnce(asset, "EXIT_FAILED_OPEN", asset " Exit failed; position still open at " PolymarketFormatSize(remainingSize) " shares")
} else {
positions.Delete(asset)
assetPhase[asset] := exitPhase
LogOnce(asset, exitPhase, exitWaitMessage)
}
} else if (sellConfirmed) {
positions.Delete(asset)
assetPhase[asset] := exitPhase
LogOnce(asset, exitPhase, exitWaitMessage)
}
}
continue
}
if (assetPhase[asset] = "STOPPED_OUT") {
LogOnce(asset, "STOPPED_OUT", asset " Stopped out, waiting for next session")
continue
}
if (assetPhase[asset] = "TOOK_PROFIT") {
LogOnce(asset, "TOOK_PROFIT", asset " Take profit completed, 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 Polymarket 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 Polymarket entry range [" Fmt(EntryMin) ", " Fmt(EntryMax) "] | Delta >= " Delta)
}
LiveDelta := UpdatePolymarketLogStream(asset, snapshot)
side := ""
entryPrice := ""
upInEntryRange := (up >= EntryMin && up <= EntryMax)
downInEntryRange := (down >= EntryMin && down <= EntryMax)
if (upInEntryRange || downInEntryRange) {
if (LiveDelta != "" && LiveDelta >= Delta) {
if (upInEntryRange && (!downInEntryRange || up >= down)) {
side := "UP"
entryPrice := up
} else {
side := "DOWN"
entryPrice := down
}
} else {
LogOnce(asset, "DELTA_LOW", asset " LiveDelta " FmtOrNA(LiveDelta) " < Delta " Delta ", skipping entry")
}
} else {
LogOnce(asset, "PRICE_OUT_OF_RANGE", asset " UP " Fmt(up) " / DOWN " Fmt(down) " outside entry range [" Fmt(EntryMin) ", " Fmt(EntryMax) "]")
}
if (side = "")
continue
if !CanPlaceOrderNow(asset) {
LogOnce(asset, "NO_CREDS", asset " Missing Polymarket credentials, cannot place orders")
continue
}
if HasExistingPosition(snapshot) {
assetPhase[asset] := "HAS_POSITION"
Log(asset " Existing Polymarket position found, waiting for next session")
continue
}
Log(asset " " side " triggered @ " Fmt(entryPrice) " — placing Polymarket order")
buyConfirmed := false
loop, 4 {
if (A_Index > 1 && HasExistingPosition(snapshot)) {
Log(asset " Position detected before retry " A_Index ", skipping")
buyConfirmed := true
break
}
if (A_Index > 1) {
freshSnap := GetPolymarketMarketSnapshot(asset)
if IsObject(freshSnap)
entryPrice := (side = "UP") ? freshSnap.up : freshSnap.down
}
if (entryPrice < EntryMin || entryPrice > EntryMax) {
Log(asset " Entry retry canceled: " side " price " Fmt(entryPrice) " outside [" Fmt(EntryMin) ", " Fmt(EntryMax) "]")
break
}
if BuyPosition(asset, snapshot, side, entryPrice) {
buyConfirmed := true
break
}
if (PolymarketResultValue(polymarketLastOrderResult, "fatal", false))
break
if (A_Index < 4) {
Log(asset " Entry retry " A_Index "/3 | " PolymarketLastOrderApiSummary())
Sleep 1000
}
}
if (buyConfirmed) {
tokenId := (side = "UP") ? snapshot.upTokenId : snapshot.downTokenId
positionBody := FetchPolymarketPositions(0)
actualSize := ExtractPolymarketPositionSize(positionBody, tokenId)
if (actualSize = "" || (actualSize + 0) <= 0) {
confirmedFilledSize := PolymarketResultValue(polymarketLastOrderResult, "filledSize", "")
if (confirmedFilledSize != "" && (confirmedFilledSize + 0) > 0)
actualSize := confirmedFilledSize + 0
}
fillPrice := ExtractPolymarketPositionAveragePrice(positionBody, tokenId)
fillPriceSource := "Data API avgPrice"
if (fillPrice = "") {
fillPrice := PolymarketLastBuyFillPrice()
fillPriceSource := "CLOB matched amounts"
}
if (fillPrice = "") {
fillPrice := entryPrice + 0
fillPriceSource := "detected price fallback"
Log("WARN: " asset " Confirmed fill price unavailable; using detected entry price " Fmt(fillPrice))
}
posSize := (actualSize != "" && (actualSize + 0) > 0) ? actualSize + 0 : orderSize
takeProfitTarget := (fillPrice + 0) + (TakeProfitPts + 0)
positions[asset] := { side: side, entry: fillPrice + 0, takeProfitTarget: takeProfitTarget, ticker: snapshot.marketTicker, tokenId: tokenId, sessionKey: sessionKey, startSec: snapshot.startSec, endSec: snapshot.endSec, size: posSize }
assetPhase[asset] := "IN_POSITION"
assetLastPriceLogTick[asset] := 0
Log(asset " Position confirmed | fill: " Fmt(fillPrice) " (" fillPriceSource ") | size: " PolymarketFormatSize(posSize))
if (takeProfitTarget > 1)
Log(asset " TP target " Fmt(takeProfitTarget) " > 1; no TP exit—hold for resolution")
else
Log(asset " Monitoring stop-loss < " Fmt(Exit) " | take-profit >= " Fmt(takeProfitTarget) " (fill + " Fmt(TakeProfitPts) ")")
} else if (!PolymarketResultValue(polymarketLastOrderResult, "fatal", false)) {
assetPhase[asset] := "BUY_FAILED"
Log(asset " Entry order not filled after 4 attempts, skipping session | " PolymarketLastOrderApiSummary())
}
}
Random, pollJitterMs, 0, %pollJitterMaxMs%
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("endSec") && CurrentUnixSeconds() >= pos.endSec)
return true
if (pos.HasKey("sessionKey") && IsObject(snapshot) && PolymarketSnapshotSessionKey(snapshot) != pos.sessionKey)
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)
ClearPolymarketMarketCache(asset)
assetPhase[asset] := "WAIT_WINDOW"
}
PolymarketSnapshotSessionKey(snapshot) {
if !IsObject(snapshot)
return CurrentTimeFrameIndex()
if (snapshot.HasKey("marketTicker") && snapshot.marketTicker != "")
return snapshot.marketTicker
if (snapshot.HasKey("closeTime") && snapshot.closeTime != "")
return snapshot.closeTime
if (snapshot.HasKey("startSec") && snapshot.startSec != "")
return snapshot.startSec
return CurrentTimeFrameIndex()
}
HasPolymarketOpenReference(asset, sessionKey) {
global assetSessionOpenPrice, assetSessionOpenKey
return (assetSessionOpenKey.HasKey(asset) && assetSessionOpenKey[asset] = sessionKey && assetSessionOpenPrice.HasKey(asset) && (assetSessionOpenPrice[asset] + 0) > 0)
}
LoadPolymarketOpeningPrice(asset, snapshot) {
global assetSessionOpenPrice, assetSessionOpenKey
sessionKey := PolymarketSnapshotSessionKey(snapshot)
if (HasPolymarketOpenReference(asset, sessionKey))
return assetSessionOpenPrice[asset] + 0
openPrice := FetchPolymarketSessionOpenPrice(asset, snapshot)
if (openPrice = "")
return ""
assetSessionOpenPrice[asset] := openPrice + 0
assetSessionOpenKey[asset] := sessionKey
return openPrice + 0
}
FetchPolymarketSessionOpenPrice(asset, snapshot) {
body := FetchPolymarketCryptoPriceBody(asset, snapshot)
openPrice := JsonField(body, "openPrice")
if (openPrice != "" && (openPrice + 0) > 0)
return openPrice + 0
return ""
}
FetchPolymarketCryptoPriceBody(asset, snapshot) {
global polymarketCryptoPriceApi, polymarketPriceVariant
if (!IsObject(snapshot) || snapshot.startSec = "" || snapshot.endSec = "")
return ""
symbol := PolymarketAssetSymbolParam(asset)
startIso := UnixSecondsToIsoUtc(snapshot.startSec)
endIso := UnixSecondsToIsoUtc(snapshot.endSec)
if (symbol = "" || startIso = "" || endIso = "")
return ""
url := polymarketCryptoPriceApi "?symbol=" PolymarketQueryEncode(symbol)
url .= "&eventStartTime=" PolymarketQueryEncode(startIso)
url .= "&variant=" PolymarketQueryEncode(polymarketPriceVariant)
url .= "&endDate=" PolymarketQueryEncode(endIso)
return PolymarketHttpGet(url)
}
PolymarketAssetSymbolParam(asset) {
StringUpper, symbol, asset
return symbol
}
UnixSecondsToIsoUtc(sec) {
sec := Round(sec + 0)
if (sec <= 0)
return ""
ts := "19700101000000"
EnvAdd, ts, %sec%, Seconds
return SubStr(ts, 1, 4) "-" SubStr(ts, 5, 2) "-" SubStr(ts, 7, 2) "T" SubStr(ts, 9, 2) ":" SubStr(ts, 11, 2) ":" SubStr(ts, 13, 2) "Z"
}
PolymarketQueryEncode(value) {
value := StrReplace(value, "%", "%25")
value := StrReplace(value, ":", "%3A")
value := StrReplace(value, " ", "%20")
return value
}
UpdatePolymarketLogStream(asset, snapshot) {
global assetSessionOpenPrice, assetSessionOpenKey
if !IsObject(snapshot)
return ""
priceObj := GetCachedPolymarketLivePriceObj(asset, snapshot)
livePrice := (IsObject(priceObj) && priceObj.HasKey("value") && priceObj.value > 0) ? priceObj.value : ""
if (livePrice = "" && IsObject(priceObj) && priceObj.HasKey("price") && priceObj.price > 0)
livePrice := priceObj.price
sessionKey := PolymarketSnapshotSessionKey(snapshot)
openPrice := ""
LiveDelta := ""
if (HasPolymarketOpenReference(asset, sessionKey)) {
openPrice := assetSessionOpenPrice[asset] + 0
if (livePrice != "")
LiveDelta := Abs(openPrice - (livePrice + 0))
}
LogMonitoringStream(asset, snapshot, livePrice, openPrice, LiveDelta)
return LiveDelta
}
LogMonitoringStream(asset, snapshot, livePrice := "", openPrice := "", LiveDelta := "") {
if !IsObject(snapshot)
return
if (livePrice != "")
LogStream(asset, FmtOrNA(livePrice))
else
LogStream(asset, "n/a")
LogStream("OPEN PRICE", FmtOrNA(openPrice))
LogStream("up", FmtOrNA(snapshot.up))
LogStream("down", FmtOrNA(snapshot.down))
LogStream("LIVE DELTA", FmtLiveDeltaOrNA(LiveDelta))
}
GetCachedPolymarketLivePriceObj(asset, snapshot, forceRefresh := false) {
global assetFeedLive, polymarketFeedLiveMaxAgeMs
if (IsObject(assetFeedLive) && assetFeedLive.HasKey(asset)) {
entry := assetFeedLive[asset]
if ((A_TickCount - entry.tick) <= polymarketFeedLiveMaxAgeMs)
return { ok: true, value: entry.value + 0, price: entry.value + 0 }
}
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, snapshot, side, price) {
global orderSize
if !IsObject(snapshot)
return false
tokenId := (side = "UP") ? snapshot.upTokenId : snapshot.downTokenId
return PlacePolymarketOrder("BUY", tokenId, side, price, orderSize)
}
SellPosition(asset, side, price, reason, clientOrderId := "") {
global positions, orderSize, polymarketPositionDustSize, polymarketLastOrderResult
if !IsObject(positions[asset]) || !positions[asset].HasKey("tokenId")
return true
tokenId := positions[asset].tokenId
remainingSize := GetPolymarketPositionSize(tokenId)
if (remainingSize != "") {
remainingSize := remainingSize + 0
if (remainingSize <= polymarketPositionDustSize) {
Log(asset " Remaining position " PolymarketFormatSize(remainingSize) " below dust threshold, treating closed")
return true
}
sellSize := remainingSize
} else if (positions[asset].HasKey("size")) {
sellSize := positions[asset].size + 0
} else {
sellSize := orderSize
}
submittedSellSize := PolymarketFixedSellSize(sellSize)
if ((submittedSellSize + 0) <= 0)
return true
if (Abs((submittedSellSize + 0) - (sellSize + 0)) >= 0.000001)
Log(asset " Selling remaining " PolymarketFormatSize(submittedSellSize) " shares (available " PolymarketFormatSize(sellSize) ")")
else if (Abs((sellSize + 0) - (orderSize + 0)) >= 0.01)
Log(asset " Selling remaining " PolymarketFormatSize(sellSize) " shares")
sellComplete := PlacePolymarketOrder("SELL", tokenId, side, price, submittedSellSize, clientOrderId)
if (!sellComplete && IsObject(polymarketLastOrderResult) && polymarketLastOrderResult.HasKey("remainingSize"))
positions[asset].size := polymarketLastOrderResult.remainingSize + 0
return sellComplete
}
HasExistingPosition(snapshotOrToken) {
if !IsObject(snapshotOrToken)
return HasExistingPositionToken(snapshotOrToken)
return (HasExistingPositionToken(snapshotOrToken.upTokenId) || HasExistingPositionToken(snapshotOrToken.downTokenId))
}
HasExistingPositionToken(tokenId) {
global polymarketPositionDustSize
if (tokenId = "")
return false
size := GetPolymarketPositionSize(tokenId)
if (size = "")
return false
return ((size + 0) > polymarketPositionDustSize)
}
GetPolymarketPositionSize(tokenId) {
if (tokenId = "")
return ""
body := FetchPolymarketPositions(0)
if (body = "")
return ""
return ExtractPolymarketPositionSize(body, tokenId)
}
GetPolymarketPositionSizeChecked(tokenId, ByRef queryOk) {
queryOk := false
if (tokenId = "")
return ""
body := FetchPolymarketPositions(0)
if (body = "")
return ""
queryOk := true
size := ExtractPolymarketPositionSize(body, tokenId)
return (size = "") ? 0 : size + 0
}
ExtractPolymarketPositionSize(body, tokenId) {
segment := ExtractPolymarketPositionSegment(body, tokenId)
if (segment = "")
return ""
size := FirstJsonNumber(segment, "size")
if (size = "")
size := FirstJsonNumber(segment, "balance")
if (size = "")
size := FirstJsonNumber(segment, "amount")
return size
}
ExtractPolymarketPositionAveragePrice(body, tokenId) {
segment := ExtractPolymarketPositionSegment(body, tokenId)
if (segment = "")
return ""
avgPrice := FirstJsonNumber(segment, "avgPrice")
if (avgPrice != "" && (avgPrice + 0) > 0 && (avgPrice + 0) <= 1)
return avgPrice + 0
return ""
}
ExtractPolymarketPositionSegment(body, tokenId) {
if (body = "" || tokenId = "")
return ""
pos := InStr(body, tokenId)
if (!pos)
return ""
start := pos
while (start > 1 && SubStr(body, start, 1) != "{")
start--
finish := pos
bodyLen := StrLen(body)
while (finish <= bodyLen && SubStr(body, finish, 1) != "}")
finish++
if (finish <= start)
return ""
return SubStr(body, start, finish - start + 1)
}
PolymarketLastBuyFillPrice() {
global polymarketLastOrderResult
if !IsObject(polymarketLastOrderResult)
return ""
if (ToLower(PolymarketResultValue(polymarketLastOrderResult, "action", "")) != "buy")
return ""
body := PolymarketResultValue(polymarketLastOrderResult, "body", "")
makingAmount := JsonField(body, "makingAmount")
takingAmount := JsonField(body, "takingAmount")
if (makingAmount = "" || takingAmount = "" || (takingAmount + 0) <= 0)
return ""
fillPrice := (makingAmount + 0) / (takingAmount + 0)
if (fillPrice <= 0 || fillPrice > 1)
return ""
return fillPrice
}
FirstJsonNumber(json, field) {
pattern := """" field """\s*:\s*""?(-?\d+(?:\.\d+)?)"
if RegExMatch(json, pattern, m)
return m1
return ""
}
FetchPolymarketPositions(sizeThreshold := 0.01) {
global polymarketDataApi, polymarketFunderAddress
if (polymarketFunderAddress = "")
return ""
url := polymarketDataApi "/positions?user=" polymarketFunderAddress "&limit=100&sizeThreshold=" sizeThreshold "&_=" A_TickCount
return PolymarketHttpGet(url)
}
PlacePolymarketOrder(action, tokenId, side, price, size, clientOrderId := "") {
global enableLiveOrders, polymarketFunderAddress, polymarketPrivateKeyPath, restDelay
global polymarketBuyLimitPrice, polymarketSellLimitPrice, polymarketEntryOrderType, polymarketExitOrderType, polymarketZeroBytes32
global polymarketPositionDustSize
global polymarketLastOrderResult
isBuy := (ToLower(action) = "buy")
orderType := isBuy ? polymarketEntryOrderType : polymarketExitOrderType
polymarketLastOrderResult := { action: action, tokenId: tokenId, side: side, price: "", orderType: orderType, size: size, makerAmount: "", takerAmount: "", status: "", body: "", reason: "not submitted" }
if (!enableLiveOrders) {
polymarketLastOrderResult.reason := "live orders disabled"
return true
}
if (polymarketFunderAddress = "" || polymarketPrivateKeyPath = "") {
polymarketLastOrderResult.reason := "missing Polymarket credentials"
return false
}
if (tokenId = "") {
polymarketLastOrderResult.reason := "missing token id"
return false
}
orderPrice := isBuy ? polymarketBuyLimitPrice : polymarketSellLimitPrice
orderPrice := PolymarketClampPrice(orderPrice)
orderSizeText := PolymarketFixedSize(size)
orderPriceText := PolymarketFixedPrice(orderPrice)
positionBeforeOrder := ""
if (!isBuy) {
positionBeforeOrder := GetPolymarketPositionSize(tokenId)
if (positionBeforeOrder = "")
positionBeforeOrder := orderSizeText + 0
}
PolymarketOrderAmounts(action, orderSizeText, orderPriceText, makerAmount, takerAmount)
submittedSizeText := isBuy ? orderSizeText : PolymarketFixedSellSize(orderSizeText)
polymarketLastOrderResult.price := orderPriceText
polymarketLastOrderResult.size := submittedSizeText
polymarketLastOrderResult.makerAmount := makerAmount
polymarketLastOrderResult.takerAmount := takerAmount
salt := PolymarketOrderSalt()
timestamp := CurrentUnixMilliseconds()
expiration := "0"
metadata := polymarketZeroBytes32
builder := polymarketZeroBytes32
sigOut := PolymarketSignerJson("poly-sign-order-data"
. " --token " QuoteForCmd(tokenId)
. " --action " QuoteForCmd(action)
. " --maker-amount " QuoteForCmd(makerAmount)
. " --taker-amount " QuoteForCmd(takerAmount)
. " --salt " QuoteForCmd(salt)
. " --timestamp " QuoteForCmd(timestamp)
. " --expiration " QuoteForCmd(expiration)
. " --metadata " QuoteForCmd(metadata)
. " --builder " QuoteForCmd(builder))
if (sigOut = "") {
polymarketLastOrderResult.reason := "order signing failed"
return false
}
ok := JsonField(sigOut, "ok")
if (ok != "true") {
err := JsonField(sigOut, "error")
if (err != "")
Log("WARN: Polymarket order failed: " JsonUnescape(err))
polymarketLastOrderResult.reason := "order signing rejected"
polymarketLastOrderResult.body := sigOut
return false
}
orderPayload := BuildPolymarketOrderPayload(sigOut, tokenId, action, makerAmount, takerAmount, salt, timestamp, expiration, metadata, builder, orderType)
if (orderPayload = "") {
polymarketLastOrderResult.reason := "order payload build failed"
return false
}
bodyFile := PolymarketTempFile("order_body_" A_TickCount ".json")
FileDelete, %bodyFile%
FileAppend, %orderPayload%, %bodyFile%
headerOut := PolymarketSignerJson("poly-sign-clob-headers"
. " --method POST"
. " --path /order"
. " --body-file " QuoteForCmd(bodyFile))
FileDelete, %bodyFile%
if (headerOut = "") {
polymarketLastOrderResult.reason := "header signing failed"
return false
}
ok := JsonField(headerOut, "ok")
if (ok != "true") {
err := JsonField(headerOut, "error")
if (err != "")
Log("WARN: Polymarket header signing failed: " JsonUnescape(err))
polymarketLastOrderResult.reason := "header signing rejected"
polymarketLastOrderResult.body := headerOut
return false
}
res := PolymarketSignedPost(JsonField(headerOut, "url"), orderPayload, headerOut)
if !IsObject(res) {
polymarketLastOrderResult.reason := "signed POST failed or no API response"
return false
}
polymarketLastOrderResult.status := res.status
polymarketLastOrderResult.body := res.body
if (res.status != 200 && res.status != 201) {
err := JsonField(res.body, "error")
if (err = "")
err := JsonField(res.body, "errorMsg")
restrictionText := ToLower(err != "" ? JsonUnescape(err) : res.body)
if (res.status = 403 && (InStr(restrictionText, "restricted in your region") || InStr(restrictionText, "geoblock"))) {
polymarketLastOrderResult.reason := "regional restriction"
polymarketLastOrderResult.fatal := true
Log("ERROR: Polymarket rejected the order because you are in a restricted country.")
stopCode()
return false
}
Log("WARN: Polymarket order submit HTTP " res.status (err != "" ? ": " JsonUnescape(err) : ""))
polymarketLastOrderResult.reason := "order rejected"
return false
}
success := JsonField(res.body, "success")
if (success = "false") {
err := JsonField(res.body, "errorMsg")
if (err = "")
err := JsonField(res.body, "error")
Log("WARN: Polymarket order rejected" (err != "" ? ": " JsonUnescape(err) : ""))
polymarketLastOrderResult.reason := "order rejected"
return false
}
polymarketLastOrderResult.reason := "order accepted"
clobStatus := JsonField(res.body, "status")
polymarketLastOrderResult.clobStatus := clobStatus
clobMatched := (ToLower(clobStatus) = "matched")
orderId := JsonField(res.body, "orderID")
polymarketLastOrderResult.orderId := orderId
Sleep %restDelay%
if (isBuy) {
requestedContracts := orderSizeText + 0
filledContracts := GetPolymarketPositionSizeChecked(tokenId, filledQueryOk)
if ((filledQueryOk && filledContracts >= (requestedContracts - 0.000001)) || clobMatched) {
confirmedEntrySize := (filledQueryOk && filledContracts > 0) ? filledContracts + 0 : requestedContracts
if (clobMatched && (!filledQueryOk || filledContracts < (requestedContracts - 0.000001)))
Log("Polymarket CLOB matched all " PolymarketFormatSize(requestedContracts) " entry contracts; Data API position is still updating")
polymarketLastOrderResult.filledSize := confirmedEntrySize
polymarketLastOrderResult.positionOpen := "yes"
polymarketLastOrderResult.reason := "exact-contract entry filled"
return true
}
; The positions Data API can lag behind the CLOB. Check the canonical
; order state before canceling so a just-matched order is never retried.
orderState := PolymarketGetOrderState(orderId)
if (orderState = "matched") {
polymarketLastOrderResult.filledSize := requestedContracts
polymarketLastOrderResult.positionOpen := "yes"
polymarketLastOrderResult.reason := "entry fully matched before cancellation"
Log("Polymarket CLOB confirmed all " PolymarketFormatSize(requestedContracts) " entry contracts matched; Data API position is still updating")
return true
}
cancelOutcome := (orderState = "canceled") ? "canceled" : ""
cancelConfirmed := (orderState = "canceled") ? true : PolymarketCancelOrder(orderId, cancelOutcome)
Sleep 500
filledAfterCancel := GetPolymarketPositionSizeChecked(tokenId, filledAfterCancelQueryOk)
if (!cancelConfirmed) {
cancelConfirmed := PolymarketCancelOrder(orderId, cancelOutcome)
Sleep 500
latestFilledSize := GetPolymarketPositionSizeChecked(tokenId, latestFilledQueryOk)
if (latestFilledQueryOk) {
filledAfterCancel := latestFilledSize
filledAfterCancelQueryOk := true
}
}
if (cancelOutcome = "matched") {
polymarketLastOrderResult.filledSize := requestedContracts
polymarketLastOrderResult.positionOpen := "yes"
polymarketLastOrderResult.reason := "entry fully matched while cancellation was pending"
Log("Polymarket entry fully matched while cancellation was pending; continuing with " PolymarketFormatSize(requestedContracts) " contracts")
return true
}
if (filledAfterCancelQueryOk && filledAfterCancel >= (requestedContracts - 0.000001)) {
polymarketLastOrderResult.filledSize := filledAfterCancel + 0
polymarketLastOrderResult.positionOpen := "yes"
polymarketLastOrderResult.reason := "entry filled while cancellation was pending"
return true
}
if (filledAfterCancelQueryOk && filledAfterCancel > 0.000001) {
if (!cancelConfirmed) {
polymarketLastOrderResult.fatal := true
polymarketLastOrderResult.filledSize := filledAfterCancel + 0
polymarketLastOrderResult.positionOpen := "yes"
polymarketLastOrderResult.reason := "partial entry filled but cancellation was not confirmed"
Log("ERROR: Entry partially filled " PolymarketFormatSize(filledAfterCancel) " contracts, but the pending remainder could not be canceled. Stopping to prevent an oversized position.")
stopCode()
return false
}
polymarketLastOrderResult.filledSize := filledAfterCancel + 0
polymarketLastOrderResult.positionOpen := "yes"
polymarketLastOrderResult.reason := "partial entry filled; remainder canceled"
Log("WARN: Only " PolymarketFormatSize(filledAfterCancel) " of " PolymarketFormatSize(requestedContracts) " entry contracts filled; canceled the remainder and continuing with the filled size")
return true
}
if (!filledAfterCancelQueryOk) {
polymarketLastOrderResult.fatal := true
polymarketLastOrderResult.positionOpen := "unknown"
polymarketLastOrderResult.reason := "entry order canceled but filled size could not be verified"
Log("ERROR: Entry order reconciliation could not read the Polymarket position after cancellation. Stopping to prevent a duplicate order.")
stopCode()
return false
}
if (cancelConfirmed) {
if (cancelOutcome = "terminal") {
polymarketLastOrderResult.fatal := true
polymarketLastOrderResult.positionOpen := "unknown"
polymarketLastOrderResult.reason := "entry order terminal but fill state is ambiguous"
Log("ERROR: Entry order is terminal, but Polymarket did not say whether it was canceled or matched and the position feed has not updated. Stopping to prevent a duplicate buy.")
stopCode()
return false
}
polymarketLastOrderResult.positionOpen := "no"
polymarketLastOrderResult.reason := "entry not filled; order canceled"
return false
}
polymarketLastOrderResult.fatal := true
polymarketLastOrderResult.positionOpen := "unknown"
polymarketLastOrderResult.reason := "entry fill and cancellation could not be confirmed"
Log("ERROR: Could not confirm the GTC entry fill or cancel its remainder. Stopping to prevent a duplicate order.")
stopCode()
return false
}
remainingSize := GetPolymarketPositionSizeChecked(tokenId, remainingQueryOk)
if ((remainingQueryOk && remainingSize <= polymarketPositionDustSize) || clobMatched) {
if (clobMatched && (!remainingQueryOk || remainingSize > polymarketPositionDustSize))
Log("Polymarket CLOB matched the full sell; Data API position is still updating")
polymarketLastOrderResult.filledSize := positionBeforeOrder + 0
polymarketLastOrderResult.remainingSize := 0
polymarketLastOrderResult.positionOpen := "no"
polymarketLastOrderResult.reason := "GTC sell fully filled"
return true
}
orderState := PolymarketGetOrderState(orderId)
if (orderState = "matched") {
polymarketLastOrderResult.filledSize := positionBeforeOrder + 0
polymarketLastOrderResult.remainingSize := 0
polymarketLastOrderResult.positionOpen := "no"
polymarketLastOrderResult.reason := "sell fully matched before cancellation"
Log("Polymarket CLOB confirmed the full sell matched; Data API position is still updating")
return true
}
cancelOutcome := (orderState = "canceled") ? "canceled" : ""
cancelConfirmed := (orderState = "canceled") ? true : PolymarketCancelOrder(orderId, cancelOutcome)
Sleep 500
remainingAfterCancel := GetPolymarketPositionSizeChecked(tokenId, remainingAfterCancelQueryOk)
if (!cancelConfirmed && (!remainingAfterCancelQueryOk || remainingAfterCancel > polymarketPositionDustSize)) {
cancelConfirmed := PolymarketCancelOrder(orderId, cancelOutcome)
Sleep 500
latestRemainingSize := GetPolymarketPositionSizeChecked(tokenId, latestRemainingQueryOk)
if (latestRemainingQueryOk) {
remainingAfterCancel := latestRemainingSize + 0
remainingAfterCancelQueryOk := true
}
}
if (cancelOutcome = "matched") {
polymarketLastOrderResult.filledSize := positionBeforeOrder + 0
polymarketLastOrderResult.remainingSize := 0
polymarketLastOrderResult.positionOpen := "no"
polymarketLastOrderResult.reason := "sell fully matched while cancellation was pending"
Log("Polymarket sell fully matched while cancellation was pending; no retry needed")
return true
}
if (!remainingAfterCancelQueryOk) {
polymarketLastOrderResult.fatal := true
polymarketLastOrderResult.positionOpen := "unknown"
polymarketLastOrderResult.reason := "sell order canceled but remaining position could not be verified"
Log("ERROR: Sell reconciliation could not read the remaining Polymarket position after cancellation. Stopping to prevent a duplicate sell.")
stopCode()
return false
}
if (remainingAfterCancel <= polymarketPositionDustSize) {
polymarketLastOrderResult.filledSize := positionBeforeOrder + 0
polymarketLastOrderResult.remainingSize := remainingAfterCancel
polymarketLastOrderResult.positionOpen := "no"
polymarketLastOrderResult.reason := "GTC sell fully filled during cancellation"
return true
}
soldContracts := (positionBeforeOrder + 0) - (remainingAfterCancel + 0)
if (soldContracts < 0)
soldContracts := 0
polymarketLastOrderResult.filledSize := soldContracts
polymarketLastOrderResult.remainingSize := remainingAfterCancel + 0
polymarketLastOrderResult.positionOpen := "yes"
if (cancelConfirmed) {
if (cancelOutcome = "terminal") {
polymarketLastOrderResult.fatal := true
polymarketLastOrderResult.reason := "sell order terminal but fill state is ambiguous"
Log("ERROR: Sell order is terminal, but Polymarket did not say whether it was canceled or matched and the position feed still shows " PolymarketFormatSize(remainingAfterCancel) " contracts. Stopping to prevent an oversell.")
stopCode()
return false
}
polymarketLastOrderResult.reason := "partial GTC sell; remainder canceled for retry"
Log("WARN: GTC sell filled " PolymarketFormatSize(soldContracts) " contracts; canceled the pending order and will retry the remaining " PolymarketFormatSize(remainingAfterCancel))
return false
}
polymarketLastOrderResult.fatal := true
polymarketLastOrderResult.reason := "partial GTC sell but cancellation was not confirmed"
Log("ERROR: GTC sell still has " PolymarketFormatSize(remainingAfterCancel) " contracts and its pending remainder could not be canceled. Stopping to prevent a duplicate sell.")
stopCode()
return false
}
PolymarketOrderAmounts(action, sizeText, priceText, ByRef makerAmount, ByRef takerAmount) {
isBuy := (ToLower(action) = "buy")
size := sizeText + 0
price := priceText + 0
if (isBuy) {
orderSize := PolymarketFloorDecimals(size, 2)
notional := PolymarketFloorDecimals(orderSize * price, 4)
makerAmount := PolymarketDecimalUnits(notional)
takerAmount := PolymarketDecimalUnits(orderSize)
} else {
orderSize := PolymarketFloorDecimals(size, 2)
notional := PolymarketFloorDecimals(orderSize * price, 4)
makerAmount := PolymarketDecimalUnits(orderSize)
takerAmount := PolymarketDecimalUnits(notional)
}
}
PolymarketFloorDecimals(value, decimals) {
scale := 1
Loop, %decimals%
scale *= 10
return Floor(((value + 0) * scale) + 0.000000001) / scale
}
PolymarketLastOrderApiSummary() {
global polymarketLastOrderResult
if !IsObject(polymarketLastOrderResult)
return "API no order attempt recorded"
action := PolymarketResultValue(polymarketLastOrderResult, "action", "ORDER")
side := PolymarketResultValue(polymarketLastOrderResult, "side", "")
tokenId := PolymarketResultValue(polymarketLastOrderResult, "tokenId", "")
price := PolymarketResultValue(polymarketLastOrderResult, "price", "")
size := PolymarketResultValue(polymarketLastOrderResult, "size", "")
orderType := PolymarketResultValue(polymarketLastOrderResult, "orderType", "")
status := PolymarketResultValue(polymarketLastOrderResult, "status", "")
clobStatus := PolymarketResultValue(polymarketLastOrderResult, "clobStatus", "")
body := PolymarketResultValue(polymarketLastOrderResult, "body", "")
reason := PolymarketResultValue(polymarketLastOrderResult, "reason", "")
orderText := action
if (side != "")
orderText .= " " side
if (tokenId != "")
orderText .= " token=" PolymarketShortLogText(tokenId, 18)
if (price != "")
orderText .= " @" price
if (size != "")
orderText .= " x" size
if (orderType != "")
orderText .= " " orderType
if (status = "")
apiText := "API no response"
else
apiText := "API HTTP " status
if (clobStatus != "")
apiText .= " CLOB=" clobStatus
includeBody := (status != 200 && status != 201)
apiMessage := PolymarketApiMessage(body, includeBody)
if (apiMessage != "")
apiText .= " " apiMessage
if (reason != "")
apiText .= " | " reason
return orderText " | " apiText
}
PolymarketApiMessage(body, includeBodyFallback := true) {
body := Trim(body)
if (body = "")
return ""
errorText := JsonField(body, "error")
errorMsg := JsonField(body, "errorMsg")
message := JsonField(body, "message")
code := JsonField(body, "code")
if (errorMsg != "")
return "errorMsg=" PolymarketCompactLogText(errorMsg, 160)
if (errorText != "")
return "error=" PolymarketCompactLogText(errorText, 160)
if (message != "")
return "msg=" PolymarketCompactLogText(message, 160)
if (code != "")
return "code=" PolymarketCompactLogText(code, 80)
if (includeBodyFallback)
return "body=" PolymarketCompactLogText(body, 180)
return ""
}
PolymarketResultValue(result, key, fallback := "") {
if IsObject(result) && result.HasKey(key)
return result[key]
return fallback
}
PolymarketShortLogText(s, maxLen := 36) {
return PolymarketCompactLogText(s, maxLen)
}
PolymarketCompactLogText(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
}
PolymarketDecimalUnits(value) {
units := Round((value + 0) * 1000000)
if (units < 0)
units := 0
return Format("{:d}", units)
}
PolymarketOrderSalt() {
Random, rand, 100000, 999999
return CurrentUnixSeconds() . rand
}
CurrentUnixMilliseconds() {
sec := CurrentUnixSeconds()
ms := A_MSec + 0
if (ms < 0)
ms := 0
return Format("{:d}", (sec * 1000) + ms)
}
BuildPolymarketOrderPayload(sigOut, tokenId, action, makerAmount, takerAmount, salt, timestamp, expiration, metadata, builder, orderType := "GTC") {
owner := JsonField(sigOut, "owner")
maker := JsonField(sigOut, "maker")
signer := JsonField(sigOut, "orderSigner")
signatureType := JsonField(sigOut, "signatureType")
signature := JsonField(sigOut, "signature")
sideText := (ToLower(action) = "buy") ? "BUY" : "SELL"
if (owner = "" || maker = "" || signer = "" || signatureType = "" || signature = "")
return ""
body := "{""order"":{"
body .= """salt"":" salt
body .= ",""maker"":" PolymarketJsonString(maker)
body .= ",""signer"":" PolymarketJsonString(signer)
body .= ",""tokenId"":" PolymarketJsonString(tokenId)
body .= ",""makerAmount"":" PolymarketJsonString(makerAmount)
body .= ",""takerAmount"":" PolymarketJsonString(takerAmount)
body .= ",""side"":" PolymarketJsonString(sideText)
body .= ",""signatureType"":" signatureType
body .= ",""timestamp"":" PolymarketJsonString(timestamp)
body .= ",""expiration"":" PolymarketJsonString(expiration)
body .= ",""metadata"":" PolymarketJsonString(metadata)
body .= ",""builder"":" PolymarketJsonString(builder)
body .= ",""signature"":" PolymarketJsonString(signature)
body .= "},""owner"":" PolymarketJsonString(owner)
body .= ",""orderType"":" PolymarketJsonString(orderType)
body .= ",""deferExec"":false,""postOnly"":false}"
return body
}
PolymarketJsonString(value) {
value := StrReplace(value, "\", "\\")
value := StrReplace(value, """", "\""")
value := StrReplace(value, "`r", "\r")
value := StrReplace(value, "`n", "\n")
value := StrReplace(value, "`t", "\t")
return """" value """"
}
PolymarketClampPrice(price) {
p := Round((price + 0) * 100) / 100.0
if (p < 0.01)
return 0.01
if (p > 0.99)
return 0.99
return p
}
PolymarketFixedPrice(price) {
p := PolymarketClampPrice(price)
cents := Round(p * 100)
if (cents < 10)
return "0.0" cents
return "0." cents
}
PolymarketFixedSize(size) {
units := Floor(((size + 0) * 1000000) + 0.0001)
if (units < 1)
units := 1
return Format("{:.6f}", units / 1000000.0)
}
PolymarketFixedSellSize(size) {
sellSize := PolymarketFloorDecimals(size, 2)
if (sellSize < 0)
sellSize := 0
return Format("{:.2f}", sellSize)
}
PolymarketFormatSize(size) {
value := Round((size + 0) * 1000000) / 1000000.0
text := Format("{:.6f}", value)
while (InStr(text, ".") && SubStr(text, StrLen(text), 1) = "0")
text := SubStr(text, 1, StrLen(text) - 1)
if (SubStr(text, StrLen(text), 1) = ".")
text := SubStr(text, 1, StrLen(text) - 1)
return text
}
LoadPolymarketCredentialsFromFiles() {
global polymarketFunderAddress, polymarketPrivateKeyFile, polymarketPrivateKeyPath, polymarketFunderAddressFile
global polymarketRelayerKeyFile, polymarketRelayerAddressFile, polymarketPrivateKeyResolvedPath, polymarketFunderAddressResolvedPath
global polymarketRelayerKeyResolvedPath, polymarketRelayerAddressResolvedPath, polymarketRelayerKey, polymarketRelayerAddress
polymarketFunderAddress := ""
polymarketPrivateKeyPath := ""
polymarketRelayerKey := ""
polymarketRelayerAddress := ""
polymarketPrivateKeyResolvedPath := ResolveCredentialPath(polymarketPrivateKeyFile)
if (polymarketPrivateKeyResolvedPath != "")
polymarketPrivateKeyPath := polymarketPrivateKeyResolvedPath
polymarketFunderAddressResolvedPath := ResolveCredentialPath(polymarketFunderAddressFile)
if (polymarketFunderAddressResolvedPath != "")
polymarketFunderAddress := ReadCredentialCode(polymarketFunderAddressResolvedPath)
polymarketRelayerKeyResolvedPath := ResolveCredentialPath(polymarketRelayerKeyFile)
if (polymarketRelayerKeyResolvedPath != "")
polymarketRelayerKey := ReadCredentialCode(polymarketRelayerKeyResolvedPath)
polymarketRelayerAddressResolvedPath := ResolveCredentialPath(polymarketRelayerAddressFile)
if (polymarketRelayerAddressResolvedPath != "")
polymarketRelayerAddress := ReadCredentialCode(polymarketRelayerAddressResolvedPath)
}
ReadCredentialCode(path) {
if (path = "" || !FileExist(path))
return ""
FileRead, raw, %path%
code := JsonField(raw, "code")
if (code = "")
return Trim(raw)
return Trim(JsonUnescape(code))
}
StartupCredentialCheck() {
global enableLiveOrders, polymarketFunderAddress, polymarketPrivateKeyPath, polymarketSignerPath
global polymarketRelayerKey, polymarketRelayerAddress, polymarketCashBalance, orderSize
if (!enableLiveOrders) {
Log("Live orders disabled — credential check skipped")
return
}
funderReady := (polymarketFunderAddress != "")
keyReady := (polymarketPrivateKeyPath != "" && FileExist(polymarketPrivateKeyPath))
signerOut := RunAndCapture(QuoteForCmd(polymarketSignerPath) " version")
signerReady := InStr(signerOut, "NightShark Signer")
if (funderReady && keyReady && signerReady) {
balanceError := ""
if (!FetchPolymarketCashBalance(polymarketCashBalance, balanceError)) {
Log("ERROR: Could not verify Polymarket credentials or fetch cash balance"
. (balanceError != "" ? " — " balanceError : ""))
stopCode()
return
}
Log("Credentials processed successfully | Polymarket cash balance: $" Format("{:.2f}", polymarketCashBalance))
if ((orderSize + 0) < 5) {
Log("ERROR: For Live trading, PolyMarket requires minimum orderSize of 5.")
stopCode()
return
}
if (polymarketRelayerKey = "" || polymarketRelayerAddress = "")
Log("WARNING: Relayer credentials not fully loaded; auto-redeem will be skipped")
return
}
if (!funderReady)
Log("ERROR: Polymarket funder address not loaded")
if (!keyReady)
Log("ERROR: Polymarket private key file not loaded")
if (!signerReady)
Log("ERROR: NightShark signer v2 not found")
Log("Please fix Polymarket credentials. Stopping script.")
stopCode()
}
FetchPolymarketCashBalance(ByRef cashBalance, ByRef errorText) {
cashBalance := ""
errorText := ""
path := "/balance-allowance"
signed := PolymarketSignerJson("poly-sign-clob-headers"
. " --method GET"
. " --path " QuoteForCmd(path))
if (JsonField(signed, "ok") != "true") {
errorText := JsonField(signed, "error")
if (errorText = "")
errorText := "balance request signing rejected"
else
errorText := JsonUnescape(errorText)
return false
}
signatureType := JsonField(signed, "signatureType")
balanceUrl := JsonField(signed, "url")
if (!RegExMatch(signatureType, "^\d+$") || balanceUrl = "") {
errorText := "signer did not return balance request metadata"
return false
}
balanceUrl .= "?asset_type=COLLATERAL&signature_type=" signatureType
res := PolymarketSignedRequest("GET", balanceUrl, "", signed)
if !IsObject(res) {
errorText := "balance endpoint returned no response"
return false
}
if (res.status != 200) {
apiError := JsonField(res.body, "error")
if (apiError = "")
apiError := PolymarketCompactLogText(res.body, 160)
errorText := "balance endpoint HTTP " res.status
. (apiError != "" ? ": " JsonUnescape(apiError) : "")
return false
}
balanceUnits := FirstJsonNumber(res.body, "balance")
if (balanceUnits = "" || (balanceUnits + 0) < 0) {
errorText := "balance endpoint returned an invalid balance"
return false
}
cashBalance := (balanceUnits + 0) / 1000000.0
return true
}
ResolveCredentialPath(pathSpec) {
if (pathSpec = "")
return ""
if FileExist(pathSpec)
return AbsoluteCredentialPath(pathSpec)
normalized := StrReplace(pathSpec, "\", "/")
if FileExist(normalized)
return AbsoluteCredentialPath(normalized)
relative := normalized
while (SubStr(relative, 1, 1) = "/" || SubStr(relative, 1, 1) = "\")
relative := SubStr(relative, 2)
fromScriptDir := A_ScriptDir "/" relative
if FileExist(fromScriptDir)
return AbsoluteCredentialPath(fromScriptDir)
fromScriptDirBackslash := StrReplace(fromScriptDir, "/", "\")
if FileExist(fromScriptDirBackslash)
return AbsoluteCredentialPath(fromScriptDirBackslash)
return ""
}
AbsoluteCredentialPath(path) {
if (path = "" || !FileExist(path))
return ""
Loop, Files, %path%, F
return A_LoopFileFullPath
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
}
PolymarketSignerJson(args, includeRelayer := false) {
global polymarketSignerPath
cmd := QuoteForCmd(polymarketSignerPath) " " args " " PolymarketSignerWalletArgs(includeRelayer)
out := Trim(RunAndCapture(cmd))
if (SubStr(out, 1, 6) = "ERROR:")
return ""
return out
}
PolymarketSignerWalletArgs(includeRelayer := false) {
global polymarketPrivateKeyPath, polymarketFunderAddress, polymarketRelayerKey, polymarketRelayerAddress, polymarketClobCredsFile
args := "--private-key-file " QuoteForCmd(polymarketPrivateKeyPath)
. " --funder " QuoteForCmd(polymarketFunderAddress)
. " --clob-creds-file " QuoteForCmd(polymarketClobCredsFile)
if (includeRelayer) {
args := args
. " --relayer-api-key " QuoteForCmd(polymarketRelayerKey)
. " --relayer-api-key-address " QuoteForCmd(polymarketRelayerAddress)
}
return args
}
PolymarketSignedPost(url, bodyJson, signed) {
return PolymarketSignedRequest("POST", url, bodyJson, signed)
}
PolymarketSignedRequest(method, url, bodyJson, signed) {
if (url = "")
return false
global httpSignedPolyObj
try {
if !IsObject(httpSignedPolyObj)
httpSignedPolyObj := ComObjCreate("WinHttp.WinHttpRequest.5.1")
http := httpSignedPolyObj
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("Accept", "application/json")
http.SetRequestHeader("Content-Type", "application/json")
http.SetRequestHeader("POLY_ADDRESS", JsonField(signed, "POLY_ADDRESS"))
http.SetRequestHeader("POLY_SIGNATURE", JsonField(signed, "POLY_SIGNATURE"))
http.SetRequestHeader("POLY_TIMESTAMP", JsonField(signed, "POLY_TIMESTAMP"))
http.SetRequestHeader("POLY_API_KEY", JsonField(signed, "POLY_API_KEY"))
http.SetRequestHeader("POLY_PASSPHRASE", JsonField(signed, "POLY_PASSPHRASE"))
if (bodyJson = "")
http.Send()
else
http.Send(bodyJson)
} catch e {
httpSignedPolyObj := ""
return false
}
obj := {}
obj.status := http.Status
obj.body := http.ResponseText
return obj
}
PolymarketGetOrderState(orderId) {
if (orderId = "")
return "unknown"
path := "/order/" orderId
signed := PolymarketSignerJson("poly-sign-clob-headers"
. " --method GET"
. " --path " QuoteForCmd(path))
if (JsonField(signed, "ok") != "true")
return "unknown"
res := PolymarketSignedRequest("GET", JsonField(signed, "url"), "", signed)
if !IsObject(res)
return "unknown"
if (res.status != 200)
return "unknown"
status := ToLower(JsonField(res.body, "status"))
if InStr(status, "matched")
return "matched"
if InStr(status, "canceled")
return "canceled"
if InStr(status, "cancelled")
return "canceled"
if InStr(status, "live")
return "live"
return "unknown"
}
PolymarketCancelOrder(orderId, ByRef outcome := "") {
outcome := ""
if (orderId = "")
return false
bodyJson := "{""orderID"":" PolymarketJsonString(orderId) "}"
bodyFile := PolymarketTempFile("cancel_body_" A_TickCount ".json")
FileDelete, %bodyFile%
FileAppend, %bodyJson%, %bodyFile%
signed := PolymarketSignerJson("poly-sign-clob-headers"
. " --method DELETE"
. " --path /order"
. " --body-file " QuoteForCmd(bodyFile))
FileDelete, %bodyFile%
if (JsonField(signed, "ok") != "true") {
Log("WARN: Could not sign cancellation for pending Polymarket GTC order")
return false
}
res := PolymarketSignedRequest("DELETE", JsonField(signed, "url"), bodyJson, signed)
if !IsObject(res) {
Log("WARN: Polymarket GTC order cancellation returned no response")
return false
}
if (res.status != 200) {
Log("WARN: Polymarket GTC order cancellation HTTP " res.status ": " PolymarketCompactLogText(res.body, 160))
return false
}
canceledPattern := """canceled""\s*:\s*\[[^\]]*""" orderId """"
if RegExMatch(res.body, canceledPattern) {
outcome := "canceled"
Log("Canceled pending Polymarket GTC order " PolymarketShortLogText(orderId, 18))
return true
}
responseLower := ToLower(res.body)
if (InStr(responseLower, "matched orders can't be canceled") || InStr(responseLower, "matched orders can’t be canceled")) {
outcome := "matched"
Log("Polymarket GTC order was already fully matched; no cancellation needed")
return true
}
if (InStr(responseLower, "already canceled or matched")) {
orderState := PolymarketGetOrderState(orderId)
if (orderState = "matched") {
outcome := "matched"
Log("Polymarket CLOB confirmed the GTC order was fully matched; no cancellation needed")
return true
}
if (orderState = "canceled") {
outcome := "canceled"
Log("Polymarket CLOB confirmed the GTC order was canceled")
return true
}
outcome := "terminal"
Log("WARN: Polymarket GTC order is terminal, but its final canceled/matched state could not be determined")
return true
}
Log("WARN: Polymarket did not confirm GTC order cancellation: " PolymarketCompactLogText(res.body, 160))
return false
}
PolymarketTempFile(fileName) {
tempBase := A_Temp
if (tempBase = "")
tempBase := A_ScriptDir
tempDir := tempBase "\poly_ahk"
FileCreateDir, %tempDir%
return tempDir "\" fileName
}
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"
, A_ScriptDir "\lib\nightshark-signer.exe"
, A_ScriptDir "/lib/nightshark-signer.exe"
, A_WorkingDir "\lib\nightshark-signer.exe"
, A_WorkingDir "/lib/nightshark-signer.exe" ]
for _, p in candidates {
if FileExist(p)
return p
}
return A_ScriptDir "\nightshark-signer.exe"
}
CanPlaceOrderNow(asset) {
global enableLiveOrders, polymarketFunderAddress, polymarketPrivateKeyPath, polymarketSignerPath
if (!enableLiveOrders)
return true
if (polymarketFunderAddress != "" && polymarketPrivateKeyPath != "" && FileExist(polymarketPrivateKeyPath) && FileExist(polymarketSignerPath))
return true
return false
}
CheckAndRedeemPolymarketRewards(asset, sessionKey) {
global enableLiveOrders, lastRedeemCheckedSession, polymarketRelayerKey, polymarketRelayerAddress
global minRedeemUsdc
if (!enableLiveOrders)
return
if (lastRedeemCheckedSession = sessionKey)
return
lastRedeemCheckedSession := sessionKey
if (polymarketRelayerKey = "" || polymarketRelayerAddress = "") {
LogOnce(asset, "REDEEM_CREDS", asset " Relayer credentials missing, skipping redeem check")
return
}
Log(asset " Checking any pending rewards")
dryRun := PolymarketSignerJson("poly-redeem --dry-run", true)
if (dryRun = "") {
Log(asset " Redeem check failed")
return
}
ok := JsonField(dryRun, "ok")
if (ok != "true") {
err := JsonField(dryRun, "error")
Log(asset " Redeem check failed" (err != "" ? ": " JsonUnescape(err) : ""))
return
}
count := JsonField(dryRun, "count")
if (count = "" || (count + 0) <= 0) {
Log(asset " No pending rewards found")
return
}
usdcReady := JsonField(dryRun, "usdc")
if (usdcReady = "" || (usdcReady + 0) < minRedeemUsdc) {
Log(asset " Pending rewards below redeem minimum")
return
}
Log(asset " Redeemable Polymarket positions found: " count " | usdc: " usdcReady " — redeeming")
result := PolymarketSignerJson("poly-redeem", true)
if (JsonField(result, "ok") = "true") {
redeemed := JsonField(result, "count")
usdc := JsonField(result, "usdc")
Log(asset " Redeem complete | count: " redeemed " | usdc: " usdc)
return
}
err := JsonField(result, "error")
Log(asset " Redeem failed" (err != "" ? ": " JsonUnescape(err) : ""))
}
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
}
MinutesRemainingInTimeFrame() {
global polymarketTimeFrameSeconds
nowSec := CurrentUnixSeconds()
if (nowSec > 0)
return (polymarketTimeFrameSeconds - Mod(nowSec, polymarketTimeFrameSeconds)) / 60.0
currentHour := A_Hour + 0
currentMinute := A_Min + 0
currentSecond := A_Sec + 0
secondsToday := (currentHour * 3600) + (currentMinute * 60) + currentSecond
return (polymarketTimeFrameSeconds - Mod(secondsToday, polymarketTimeFrameSeconds)) / 60.0
}
IsXMinRemaining(x) {
return (MinutesRemainingInTimeFrame() <= x)
}
CurrentTimeFrameIndex() {
global polymarketTimeFrameSeconds
nowSec := CurrentUnixSeconds()
if (nowSec > 0)
return Floor(nowSec / polymarketTimeFrameSeconds)
secondsToday := ((A_Hour + 0) * 3600) + ((A_Min + 0) * 60) + (A_Sec + 0)
return Floor(secondsToday / polymarketTimeFrameSeconds)
}
SnapshotMinutesLeft(snapshot) {
if !IsObject(snapshot)
return ""
if (snapshot.HasKey("endSec") && snapshot.endSec != "") {
nowSec := CurrentUnixSeconds()
if (nowSec > 0)
return ((snapshot.endSec + 0) - nowSec) / 60.0
}
if (snapshot.HasKey("minutesLeft"))
return snapshot.minutesLeft
return ""
}
IsSnapshotExpired(snapshot, cutoffMinutes := 0) {
minsLeft := SnapshotMinutesLeft(snapshot)
return (minsLeft != "" && minsLeft <= cutoffMinutes)
}
ClearPolymarketMarketCache(asset) {
global assetSnapshotCache, assetMarketRefreshTick
if (assetSnapshotCache.HasKey(asset))
assetSnapshotCache.Delete(asset)
if (assetMarketRefreshTick.HasKey(asset))
assetMarketRefreshTick.Delete(asset)
}
GetPolymarketMarketSnapshot(asset, maxRetries := 1) {
global polymarketGammaApi, polymarketAssetSlugPrefix, assetSnapshotCache, assetMarketRefreshTick, marketRefreshMs, polymarketHttpTimeoutMs
if !polymarketAssetSlugPrefix.HasKey(asset)
return false
prefix := polymarketAssetSlugPrefix[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)) {
ClearPolymarketMarketCache(asset)
} else {
if (RefreshPolymarketBestPrices(asset, cached))
return cached
return false
}
}
}
attempt := 1
while (attempt <= maxRetries) {
window := CurrentPolymarketWindow()
slug := BuildPolymarketMarketSlug(prefix, window)
body := PolymarketHttpGet(polymarketGammaApi "/events?slug=" slug "&_=" A_TickCount "-" attempt)
if (body != "") {
upTokenId := ExtractLargeNumberAfter(body, "clobTokenIds", 1)
downTokenId := ExtractLargeNumberAfter(body, "clobTokenIds", 2)
if (upTokenId = "" || downTokenId = "") {
if (attempt < maxRetries) {
Sleep 200
attempt++
continue
}
}
FetchPolymarketTokenPricePair(asset, upTokenId, downTokenId, upPrice, downPrice)
minsLeft := (window.endSec - CurrentUnixSeconds()) / 60.0
if (minsLeft != "" && minsLeft < 0.5) {
if (attempt < maxRetries) {
Sleep 200
attempt++
continue
}
}
if (upPrice != "" && downPrice != "") {
obj := {}
obj.up := upPrice + 0
obj.down := downPrice + 0
obj.minutesLeft := minsLeft
obj.startSec := window.startSec
obj.endSec := window.endSec
obj.closeTime := slug
obj.marketTicker := slug
obj.upTokenId := upTokenId
obj.downTokenId := downTokenId
obj.conditionId := JsonField(body, "conditionId")
assetSnapshotCache[asset] := obj
assetMarketRefreshTick[asset] := nowTick
return obj
}
}
if (attempt < maxRetries)
Sleep 200
attempt++
}
if (assetSnapshotCache.HasKey(asset)) {
cached := assetSnapshotCache[asset]
if IsObject(cached) {
if (IsSnapshotExpired(cached, 0.5)) {
ClearPolymarketMarketCache(asset)
return false
}
if (RefreshPolymarketBestPrices(asset, cached))
return cached
}
}
return false
}
RefreshPolymarketBestPrices(asset, snapshot) {
if !IsObject(snapshot)
return false
FetchPolymarketTokenPricePair(asset, snapshot.upTokenId, snapshot.downTokenId, upPrice, downPrice)
snapshot.minutesLeft := SnapshotMinutesLeft(snapshot)
if (upPrice = "" || downPrice = "")
return false
snapshot.up := upPrice + 0
snapshot.down := downPrice + 0
return true
}
CurrentUnixSeconds() {
epoch := CurrentUtcNowAhk()
if (epoch = "")
return 0
EnvSub, epoch, 19700101000000, Seconds
return epoch + 0
}
CurrentPolymarketWindow() {
global polymarketTimeFrameSeconds
nowSec := CurrentUnixSeconds()
startSec := Floor(nowSec / polymarketTimeFrameSeconds) * polymarketTimeFrameSeconds
return { startSec: startSec, endSec: startSec + polymarketTimeFrameSeconds }
}
BuildPolymarketMarketSlug(prefix, window) {
global TimeFrame
if (TimeFrame != "1H")
return prefix "-" window.startSec
eastern := PolymarketUtcToEasternTimestamp(window.startSec)
if (eastern = "")
return ""
monthNames := ["january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"]
year := SubStr(eastern, 1, 4) + 0
month := SubStr(eastern, 5, 2) + 0
day := SubStr(eastern, 7, 2) + 0
hour24 := SubStr(eastern, 9, 2) + 0
period := (hour24 >= 12) ? "pm" : "am"
hour12 := Mod(hour24, 12)
if (hour12 = 0)
hour12 := 12
return prefix "-" monthNames[month] "-" day "-" year "-" hour12 period "-et"
}
PolymarketUtcToEasternTimestamp(sec) {
sec := Round(sec + 0)
if (sec <= 0)
return ""
utc := "19700101000000"
EnvAdd, utc, %sec%, Seconds
year := SubStr(utc, 1, 4) + 0
dstStartDay := PolymarketNthSundayDay(year, 3, 2)
dstEndDay := PolymarketNthSundayDay(year, 11, 1)
dstStartUtc := year . "03" . Format("{:02}", dstStartDay) . "070000"
dstEndUtc := year . "11" . Format("{:02}", dstEndDay) . "060000"
offsetSeconds := (utc >= dstStartUtc && utc < dstEndUtc) ? -14400 : -18000
eastern := utc
EnvAdd, eastern, %offsetSeconds%, Seconds
return eastern
}
PolymarketNthSundayDay(year, month, occurrence) {
monthOffsets := [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]
adjustedYear := year
if (month < 3)
adjustedYear--
firstWeekday := Mod(adjustedYear + Floor(adjustedYear / 4) - Floor(adjustedYear / 100) + Floor(adjustedYear / 400) + monthOffsets[month] + 1, 7)
firstSunday := 1 + Mod(7 - firstWeekday, 7)
return firstSunday + ((occurrence - 1) * 7)
}
FetchPolymarketTokenPricePair(asset, upTokenId, downTokenId, ByRef upPrice, ByRef downPrice) {
upPrice := ""
downPrice := ""
if (upTokenId = "" && downTokenId = "")
return
PolymarketFeedBulkFetch(asset, upTokenId, downTokenId, upPrice, downPrice)
}
PolymarketFeedBulkFetch(asset, upTokenId, downTokenId, ByRef upMid, ByRef downMid) {
global assetFeedLive, feedStartupWaitLogged, feedStartupReadyLogged
upMid := ""
downMid := ""
if (upTokenId = "" || downTokenId = "")
return false
if (!GetPolymarketMidpointPair(upTokenId, downTokenId, PolymarketFeedLiveSymbol(asset), upMid, downMid, livePrice)) {
if (!feedStartupWaitLogged && !feedStartupReadyLogged) {
feedStartupWaitLogged := true
Log("Starting up Polymarket dependencies, please wait...")
}
return false
}
if (!feedStartupReadyLogged) {
feedStartupReadyLogged := true
if (feedStartupWaitLogged)
Log("Polymarket dependencies ready")
}
if (livePrice != "") {
if !IsObject(assetFeedLive)
assetFeedLive := {}
assetFeedLive[asset] := { value: livePrice + 0, tick: A_TickCount }
}
return true
}
PolymarketFeedLiveSymbol(asset) {
global TimeFrame, polymarketChainlinkSymbolMap, polymarketBinanceSymbolMap
StringUpper, assetUpper, asset
StringLower, symbol, asset
if (TimeFrame = "1H") {
if (polymarketBinanceSymbolMap.HasKey(assetUpper))
return polymarketBinanceSymbolMap[assetUpper]
return symbol "usdt"
}
if (polymarketChainlinkSymbolMap.HasKey(assetUpper)) {
liveSymbol := polymarketChainlinkSymbolMap[assetUpper]
if (polymarketBinanceSymbolMap.HasKey(assetUpper))
liveSymbol .= "," polymarketBinanceSymbolMap[assetUpper]
return liveSymbol
}
return symbol "/usd," symbol "usdt"
}
PolymarketHttpAwaitResponse(http, deadlineTick) {
loop {
try {
status := http.Status
if (status = 200)
return http.ResponseText
if (status > 0) {
http.Abort()
return ""
}
} catch pendingError {
}
remainingMs := deadlineTick - A_TickCount
if (remainingMs <= 0)
break
waitMs := (remainingMs < 10) ? remainingMs : 10
Sleep %waitMs%
}
try http.Abort()
return ""
}
ExtractLargeNumberAfter(text, marker, index) {
pos := InStr(text, """" marker """")
if (!pos)
return ""
segment := SubStr(text, pos, 1500)
found := 0
scanPos := 1
while (scanPos := RegExMatch(segment, "\d{30,}", m, scanPos)) {
found++
if (found = index)
return m
scanPos += StrLen(m)
}
return ""
}
PolymarketHttpGet(url, timeoutMs := 0) {
global httpPublicObj, polymarketHttpTimeoutMs
try {
if (timeoutMs <= 0)
timeoutMs := polymarketHttpTimeoutMs
if !IsObject(httpPublicObj)
httpPublicObj := ComObjCreate("WinHttp.WinHttpRequest.5.1")
http := httpPublicObj
http.Option(9) := 2048 | 8192
phaseTimeoutMs := (timeoutMs < 500) ? timeoutMs : 500
http.SetTimeouts(phaseTimeoutMs, phaseTimeoutMs, phaseTimeoutMs, timeoutMs)
http.Open("GET", url, true)
http.SetRequestHeader("User-Agent", "Mozilla/5.0")
http.SetRequestHeader("Cache-Control", "no-cache, no-store")
http.SetRequestHeader("Pragma", "no-cache")
http.SetRequestHeader("Accept", "application/json")
http.Send()
return PolymarketHttpAwaitResponse(http, A_TickCount + timeoutMs)
} catch e {
try http.Abort()
httpPublicObj := ""
return ""
}
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
}