- name
- monitor-polymarket
- description
- Monitor Polymarket and/or Kalshi prediction markets for 24h price moves, volume changes, fresh comments, and high-conviction alerts
- metadata
- {"title":"Monitor Prediction Markets","mode":"read-only","category":"crypto","var":"","tags":["crypto","research"]}
> **${var}** — Platform selector with an optional single-market override:
> - **empty** (`""`) — run **both** platforms from their watchlists.
> - `polymarket` — run Polymarket's whole watchlist (`skills/monitor-polymarket/watchlist-polymarket.md`).
> - `kalshi` — run Kalshi's whole watchlist (`skills/monitor-polymarket/watchlist-kalshi.md`).
> - `polymarket:<event-slug>` — one ad-hoc Polymarket event (e.g. `polymarket:us-x-iran-ceasefire-by`).
> - `kalshi:<event-ticker>` — one ad-hoc Kalshi event (e.g. `kalshi:KXGDP-26Q2`).
Read `memory/MEMORY.md` for context.
Read the last 2 days of `memory/logs/` to compare against previous readings and flag *new* movers (not repeats of yesterday's news).
## Why this skill exists
A table of prices isn't useful. An operator reading this notification wants to answer: **"is there a market worth forming a view on right now, and why?"** Every rule below exists to push output toward that question — suppress noise, rank by decision value, and demand one line of reasoning per alert. This skill covers two venues — **Polymarket** (crypto-native, CLOB + comments) and **Kalshi** (regulated, liquidity-weighted signals) — and dispatches to the branch(es) the selector asks for.
## Dispatch
Parse `${var}` into a platform choice and an optional single-market override, then run the matching branch(es).
```bash
PLATFORM="both" # both | polymarket | kalshi
SINGLE="" # optional single event slug (Polymarket) or ticker (Kalshi)
case "${var}" in
"") PLATFORM="both" ;;
polymarket) PLATFORM="polymarket" ;;
kalshi) PLATFORM="kalshi" ;;
polymarket:*) PLATFORM="polymarket"; SINGLE="${var#polymarket:}" ;;
kalshi:*) PLATFORM="kalshi"; SINGLE="${var#kalshi:}" ;;
*) # unrecognised prefix — don't guess a venue; fall back to both watchlists
PLATFORM="both"; SINGLE=""
echo "unrecognised selector '${var}' — running both watchlists" ;;
esac
```
- `PLATFORM=both` → run the **Polymarket branch** and the **Kalshi branch**, each from its own watchlist, then emit a combined notification.
- `PLATFORM=polymarket` → run only the Polymarket branch (whole watchlist, or `SINGLE` if set).
- `PLATFORM=kalshi` → run only the Kalshi branch (whole watchlist, or `SINGLE` if set).
Each branch below is independently executable — skip the one(s) the selector didn't ask for.
---
# Polymarket branch
Data source: Polymarket **gamma-api** (events, comments) + **clob** (price history). All endpoints are **public — no auth**.
Watchlist: `skills/monitor-polymarket/watchlist-polymarket.md`. Each line is an event slug; add or remove slugs to change what's monitored.
## P1. Load watchlist
```bash
if [ -n "$SINGLE" ]; then
SLUGS="$SINGLE"
else
# One slug per line, skip comments and blanks
SLUGS=$(grep -v '^#' skills/monitor-polymarket/watchlist-polymarket.md | grep -v '^$')
fi
```
If the watchlist is empty and no single slug was given, there's nothing to do on Polymarket — note it and move on (don't fabricate a report).
## P2. For each event, fetch markets and price history
For each event slug in `$SLUGS`:
**a) Get the event and its markets:**
```bash
curl -s "https://gamma-api.polymarket.com/events?slug=$SLUG&limit=1"
```
The response contains the event `id`, `title`, and a `markets` array. Each market has:
- `id`, `question`, `slug`, `closed`
- `outcomePrices` — JSON array, index 0 = YES price (0.0–1.0)
- `volume24hr`, `volumeNum`, `liquidityNum`
- `clobTokenIds` — JSON array, index 0 = YES token, index 1 = NO token
**Skip closed markets** — they've already resolved.
**b) Get 24h price history for each open market:**
```bash
# YES token is index 0 of clobTokenIds
TOKEN_ID=$(echo "$CLOB_TOKEN_IDS" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())[0])")
curl -s "https://clob.polymarket.com/prices-history?market=$TOKEN_ID&interval=1d&fidelity=60"
```
Response: `{ "history": [{ "t": unix_timestamp, "p": "price_string" }, ...] }`
**c) Calculate 24h stats for each market:**
- **Open / Close** — first and last price in the history
- **Change** — close minus open, in percentage points (e.g. +4.0pp)
- **High / Low** — intraday range
- **Volume** — `volume24hr` from the market data
- **Direction** — classify as: surging (>+5pp), rising (+2 to +5pp), stable (−2 to +2pp), falling (−5 to −2pp), crashing (<−5pp)
## P3. Fetch comments
For each event, get top comments and latest comments:
```bash
EVENT_ID=... # from step P2a
# Top comments by reactions
curl -s "https://gamma-api.polymarket.com/comments?parent_entity_type=Event&parent_entity_id=$EVENT_ID&limit=10&order=reactionCount&ascending=false"
# Latest comments (last 24h chatter)
curl -s "https://gamma-api.polymarket.com/comments?parent_entity_type=Event&parent_entity_id=$EVENT_ID&limit=10&order=createdAt&ascending=false"
```
**Important:** `parent_entity_type` must be `Event` (capital E).
Each comment has: `body`, `profile.username` (often null → use "anon"), `reactionCount`, `createdAt`.
From the combined results, pick the **3 most interesting comments** per event:
- New comments from the last 24h get priority (they react to recent moves)
- High-reaction comments that are still relevant
- Contrarian takes, insider-sounding analysis, whale callouts, humor
## P4. Build the Polymarket report
For each event, produce a summary block:
```
**[Event Title]** (event_id: N)
| Market | YES | 24h Chg | High/Low | 24h Vol |
|--------|-----|---------|----------|---------|
| [question] | XX.X% | +X.Xpp ▲ | XX–XX% | $X.Xm |
| [question] | XX.X% | -X.Xpp ▼ | XX–XX% | $X.Xm |
...
Biggest mover: "[question]" — [direction] from X% to Y%
Comments:
- [user/anon]: "[comment excerpt]" (X upvotes)
- [user/anon]: "[comment excerpt]"
- [user/anon]: "[comment excerpt]"
```
Flag any market that moved more than **5 percentage points** in 24h — these are the ones worth paying attention to.
## P5. Polymarket Network note
`curl` works — there is no network sandbox. Use **WebFetch** as a fallback for a flaky public GET:
- `WebFetch("https://gamma-api.polymarket.com/events?slug=SLUG&limit=1")`
- `WebFetch("https://clob.polymarket.com/prices-history?market=TOKEN_ID&interval=1d&fidelity=60")`
- `WebFetch("https://gamma-api.polymarket.com/comments?parent_entity_type=Event&parent_entity_id=EVENT_ID&limit=10&order=reactionCount&ascending=false")`
- All Polymarket gamma-api / clob endpoints are public and need no auth headers.
---
# Kalshi branch
Data source: Kalshi **trade-api v2** at `https://api.elections.kalshi.com/trade-api/v2`. Despite the "elections" subdomain, this provides access to ALL Kalshi markets (economics, climate, tech, politics, etc.). All endpoints are **public — no auth required**.
Watchlist: `skills/monitor-polymarket/watchlist-kalshi.md`. Each line is an event ticker; add or remove tickers to change what's monitored.
## K1. Load watchlist
```bash
if [ -n "$SINGLE" ]; then
TICKERS="$SINGLE"
else
TICKERS=$(grep -v '^#' skills/monitor-polymarket/watchlist-kalshi.md | grep -v '^$')
fi
```
If the watchlist is empty and no single ticker was given, emit `MONITOR_KALSHI_NO_CONFIG`, notify with a one-line setup hint, and discover trending events for this run only:
```bash
curl -s "https://api.elections.kalshi.com/trade-api/v2/events?status=open&with_nested_markets=true&limit=10"
```
Pick the 5 highest-volume events and monitor those.
## K2. For each event, fetch markets, prices, and liquidity
For each event ticker:
**a) Event + markets:**
```bash
curl -s "https://api.elections.kalshi.com/trade-api/v2/events/$EVENT_TICKER?with_nested_markets=true"
```
Fields used: `event_ticker`, `title`, `category`, `mutually_exclusive`, `markets[]` with `ticker`, `title`, `subtitle`, `status`, `yes_bid`, `yes_ask`, `last_price`, `volume`, `volume_24h`, `open_interest`, `close_time`, `series_ticker`.
**Skip non-open markets** (closed/settled are historical).
**b) 24h candlesticks (batch where possible):**
Prefer the batch endpoint — one call per event, not per market:
```bash
END_TS=$(date -u +%s)
START_TS=$((END_TS - 86400))
# Batch: up to 10,000 candlesticks total across requested tickers
curl -s "https://api.elections.kalshi.com/trade-api/v2/markets/candlesticks?tickers=$COMMA_SEP_MARKET_TICKERS&start_ts=$START_TS&end_ts=$END_TS&period_interval=60"
```
If the batch endpoint errors, fall back to the per-market endpoint:
```bash
curl -s "https://api.elections.kalshi.com/trade-api/v2/series/$SERIES_TICKER/markets/$MARKET_TICKER/candlesticks?start_ts=$START_TS&end_ts=$END_TS&period_interval=60"
```
If both fail for a market, mark its source as `SRC=price_only` and use `last_price` vs yesterday's log entry.
**c) Orderbook depth (liquidity / conviction signal):**
```bash
curl -s "https://api.elections.kalshi.com/trade-api/v2/markets/$MARKET_TICKER/orderbook?depth=10"
```
From the orderbook, compute:
- `spread_pp` = `yes_ask − yes_bid` in percentage points. Wide spread = low conviction, thin book.
- `depth_usd` = sum over top-10 bid levels of `price × size` (approximation, both sides). This scales how much weight to give a price.
If orderbook fails, mark `SRC=no_book` and skip the conviction column for that market.
## K3. Compute per-market signals
For each open market:
- **implied_prob** = `last_price` as a percentage (0.62 → 62%). Report this, not cents.
- **chg_pp** = `close − open` from candlesticks, in percentage points.
- **high / low** = intraday range.
- **vol_24h_usd** ≈ `volume_24h × avg(open, close)` (Kalshi reports contract count — convert so readers can compare across markets).
- **spread_pp** and **depth_usd** from step K2c.
- **move_score** = `|chg_pp| × log10(max(vol_24h_usd, 100))`. This is the key ranking signal — a 3pp move on a $200k market outranks a 5pp move on a $5k market. It prevents thin-book noise from dominating.
**Direction label** (from chg_pp): surging (>+5pp), rising (+2 to +5), stable (−2 to +2), falling (−5 to −2), crashing (<−5).
**Conviction label** (from spread_pp): tight (<2pp), loose (2–5pp), thin (>5pp, treat price skeptically).
## K4. Decide what's worth saying — suppression rules
Before building the report, drop markets that fail ALL of these gates:
- `|chg_pp| >= 2` AND `vol_24h_usd >= $1,000`, OR
- `vol_24h_usd >= $25,000` (large volume alone is signal even if price didn't move much), OR
- `open_interest` grew >30% vs yesterday's log, if yesterday's log has the data.
If a market appeared in yesterday's log with the same direction and a chg_pp within ±1pp of today's, treat it as "continued from yesterday" and demote it — mention once at the event level, don't re-alert.
**Hard alert threshold:** `|chg_pp| >= 5` AND `conviction != thin`. These go to the ALERTS block and require a "why it matters" line.
## K5. Global ranking
Rank events by the max `move_score` of any market within them. Cap the report at the **top 5 events**. Markets within an event are listed in descending `move_score` order, capped at 3 per event (mention "+N more" if truncated).
## K6. Build the Kalshi report
```
*Kalshi monitor — ${today}*
verdict: [1 sentence — dominant theme or "all quiet"]
**[Event Title]** (EVENT_TICKER) — category
| Market | prob | Δ24h | range | vol | spread |
|--------|------|------|-------|-----|--------|
| [title] | 62% | +4.1pp ▲ | 56–65% | $82k | 1pp |
Ver en GitHub