From 3190b1b9ff21e446bc776b8800b018c40a6efc1e Mon Sep 17 00:00:00 2001 From: zlei9 Date: Sun, 29 Mar 2026 09:38:03 +0800 Subject: [PATCH] Initial commit with translated description --- SKILL.md | 103 ++++++++++++++++++ _meta.json | 6 ++ polymarket.mjs | 277 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 386 insertions(+) create mode 100644 SKILL.md create mode 100644 _meta.json create mode 100644 polymarket.mjs diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..0e3e89b --- /dev/null +++ b/SKILL.md @@ -0,0 +1,103 @@ +--- +name: Polymarket +description: "通过CLI查询Polymarket预测市场赔率和事件。搜索市场、获取当前价格、按类别列出事件。支持体育博彩(NFL、NBA、足球/英超、欧冠)、政治、加密货币、选举、地缘政治。真钱市场比民意调查更准确。无需API密钥。当被问及赔率、概率、预测或"X的可能性有多大"时使用。" +--- + +# Polymarket Prediction Markets + +Query real-time odds from Polymarket, the world's largest prediction market. + +## Quick Start + +```bash +# Search for markets (instant via /public-search API) +polymarket search "Arsenal FC" +polymarket search "Super Bowl" +polymarket search "Bitcoin" +polymarket search "Trump" + +# Browse by category +polymarket events --tag=sports +polymarket events --tag=crypto +polymarket events --tag=politics + +# Get specific market details +polymarket market will-bitcoin-reach-100k +``` + +The CLI is at `polymarket.mjs` in this skill folder. Run with: +```bash +node /path/to/skill/polymarket.mjs +``` + +## Commands + +| Command | Description | +|---------|-------------| +| `search ` | Search markets by keyword (recommended) | +| `events [options]` | List active events | +| `market ` | Get market details by slug | +| `tags` | List available categories | +| `price ` | Get current price for a token | +| `book ` | Get orderbook depth | + +## Event Options + +- `--tag=` - Filter by category (crypto, politics, sports, etc.) +- `--limit=` - Maximum results (default: 20) + +## Understanding Odds + +Prices = Probabilities: +- 0.65 (65¢) = 65% chance of "Yes" +- Volume = total $ traded +- Liquidity = available $ in orderbook + +## Individual Match Betting + +Polymarket has individual match markets for soccer, NFL, NBA, and more. + +```bash +# Soccer - use "FC" suffix for team names +polymarket search "Arsenal FC" +polymarket search "Manchester United FC" +polymarket search "Liverpool FC" + +# NFL/NBA - team name works +polymarket search "Patriots" +polymarket search "Chiefs" +polymarket search "Lakers" +``` + +**Market types available:** +- **Moneyline**: Win/Draw/Lose percentages +- **Spreads**: e.g., Arsenal -1.5 +- **Totals**: Over/Under 2.5 goals +- **BTTS**: Both Teams to Score + +## Common Categories + +| Tag | Markets | +|-----|---------| +| `sports` | NFL, NBA, soccer, tennis, etc. | +| `politics` | Elections, legislation, appointments | +| `crypto` | Price targets, ETFs, regulations | +| `business` | IPOs, acquisitions, earnings | +| `tech` | Product launches, AI developments | + +## API Reference + +The CLI uses these public endpoints (no auth required): + +- **Search**: `GET /public-search?q=` - keyword search +- **Events**: `GET /events?active=true&closed=false` - list events +- **Markets**: `GET /markets?slug=` - market details +- **Tags**: `GET /tags` - available categories + +Base URL: `https://gamma-api.polymarket.com` + +## Notes + +- Real money markets tend to be more accurate than polls/pundits +- Odds update in real-time as people trade +- Markets resolve to $1.00 (correct) or $0.00 (incorrect) diff --git a/_meta.json b/_meta.json new file mode 100644 index 0000000..519e3c2 --- /dev/null +++ b/_meta.json @@ -0,0 +1,6 @@ +{ + "ownerId": "kn73vt6y69bfa21fmmwc7qrrrn7zwjxw", + "slug": "polymarket-odds", + "version": "1.0.0", + "publishedAt": 1769364207598 +} \ No newline at end of file diff --git a/polymarket.mjs b/polymarket.mjs new file mode 100644 index 0000000..a7369c4 --- /dev/null +++ b/polymarket.mjs @@ -0,0 +1,277 @@ +#!/usr/bin/env node +/** + * Polymarket CLI - Query prediction market odds + * + * Usage: + * polymarket search - Search for markets by keyword + * polymarket events [options] - List events with filters + * polymarket market - Get market details and current odds + * polymarket sports - List sports leagues/series + * polymarket tags - List available categories + * polymarket price - Get current price for a token + */ + +const GAMMA_API = 'https://gamma-api.polymarket.com'; +const CLOB_API = 'https://clob.polymarket.com'; + +async function fetchJSON(url) { + const res = await fetch(url); + if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`); + return res.json(); +} + +function parsePrice(pricesStr) { + try { + const prices = JSON.parse(pricesStr); + return prices.map(p => (parseFloat(p) * 100).toFixed(1) + '%'); + } catch { + return []; + } +} + +function parseOutcomes(outcomesStr) { + try { + return JSON.parse(outcomesStr); + } catch { + return []; + } +} + +function formatMarket(market, verbose = false) { + const outcomes = parseOutcomes(market.outcomes); + const prices = parsePrice(market.outcomePrices); + const odds = outcomes.map((o, i) => `${o}: ${prices[i] || '?'}`).join(' | '); + + let out = `📊 ${market.question}\n`; + out += ` ${odds}\n`; + out += ` Volume: $${(market.volumeNum || 0).toLocaleString()}`; + if (market.liquidity) out += ` | Liquidity: $${parseFloat(market.liquidity).toLocaleString()}`; + if (verbose) { + out += `\n Slug: ${market.slug}`; + if (market.endDate) out += `\n Ends: ${market.endDate.split('T')[0]}`; + } + return out; +} + +function formatEvent(event, verbose = false) { + let out = `\n🎯 ${event.title}\n`; + if (event.description && verbose) { + out += ` ${event.description.slice(0, 200)}${event.description.length > 200 ? '...' : ''}\n`; + } + out += ` Volume: $${(event.volume || 0).toLocaleString()}`; + if (event.liquidity) out += ` | Liquidity: $${parseFloat(event.liquidity).toLocaleString()}`; + out += '\n'; + + if (event.markets && event.markets.length > 0) { + for (const m of event.markets.slice(0, 5)) { + if (m.active && !m.closed) { + out += formatMarket(m, verbose) + '\n'; + } + } + if (event.markets.length > 5) { + out += ` ... and ${event.markets.length - 5} more markets\n`; + } + } + return out; +} + +async function search(query, limit = 10) { + // Use the public-search endpoint for efficient keyword search + const url = `${GAMMA_API}/public-search?q=${encodeURIComponent(query)}&limit=50`; + const data = await fetchJSON(url); + + if (!data.events || data.events.length === 0) { + console.log(`No markets found for "${query}"`); + return; + } + + // Filter to only active, non-closed events + const matches = data.events.filter(e => e.active && !e.closed).slice(0, limit); + + if (matches.length === 0) { + console.log(`No active markets found for "${query}"`); + return; + } + + console.log(`Found ${matches.length} active events matching "${query}":\n`); + for (const event of matches) { + console.log(formatEvent(event, true)); + } +} + +async function listEvents(options = {}) { + const params = new URLSearchParams(); + params.set('active', 'true'); + params.set('closed', 'false'); + params.set('limit', options.limit || '20'); + + if (options.tag) params.set('tag_slug', options.tag); + if (options.series) params.set('series_id', options.series); + if (options.order) params.set('order', options.order); + + const url = `${GAMMA_API}/events?${params}`; + const events = await fetchJSON(url); + + console.log(`Active events (${events.length}):\n`); + for (const event of events) { + console.log(formatEvent(event)); + } +} + +async function getMarket(slugOrId) { + const url = `${GAMMA_API}/markets?slug=${encodeURIComponent(slugOrId)}`; + const markets = await fetchJSON(url); + + if (!markets || markets.length === 0) { + // Try by ID + const byIdUrl = `${GAMMA_API}/markets/${slugOrId}`; + try { + const market = await fetchJSON(byIdUrl); + console.log(formatMarket(market, true)); + return; + } catch { + console.log(`Market not found: ${slugOrId}`); + return; + } + } + + for (const market of markets) { + console.log(formatMarket(market, true)); + console.log(); + } +} + +async function listSports() { + const url = `${GAMMA_API}/sports`; + try { + const sports = await fetchJSON(url); + console.log('Sports leagues:\n'); + for (const sport of sports.slice(0, 30)) { + console.log(` ${sport.label || sport.title} (series_id: ${sport.id})`); + } + } catch (e) { + console.log('Sports endpoint not available or empty'); + } +} + +async function listTags(limit = 50) { + const url = `${GAMMA_API}/tags?limit=${limit}`; + const tags = await fetchJSON(url); + + console.log('Available categories:\n'); + for (const tag of tags) { + console.log(` ${tag.label} (slug: ${tag.slug})`); + } +} + +async function getPrice(tokenId, side = 'buy') { + const url = `${CLOB_API}/price?token_id=${tokenId}&side=${side}`; + try { + const data = await fetchJSON(url); + console.log(`Price (${side}): ${(parseFloat(data.price) * 100).toFixed(1)}%`); + } catch (e) { + console.log(`Error fetching price: ${e.message}`); + } +} + +async function getOrderbook(tokenId) { + const url = `${CLOB_API}/book?token_id=${tokenId}`; + try { + const data = await fetchJSON(url); + console.log('Orderbook:'); + console.log(' Bids:', data.bids?.slice(0, 5).map(b => `${(parseFloat(b.price)*100).toFixed(1)}% x $${b.size}`).join(', ') || 'none'); + console.log(' Asks:', data.asks?.slice(0, 5).map(a => `${(parseFloat(a.price)*100).toFixed(1)}% x $${a.size}`).join(', ') || 'none'); + } catch (e) { + console.log(`Error fetching orderbook: ${e.message}`); + } +} + +// Main +const [,, cmd, ...args] = process.argv; + +(async () => { + try { + switch (cmd) { + case 'search': + case 's': + if (!args[0]) { + console.log('Usage: polymarket search '); + process.exit(1); + } + await search(args.join(' '), parseInt(args.find(a => a.startsWith('--limit='))?.split('=')[1]) || 10); + break; + + case 'events': + case 'e': + const evtOpts = {}; + for (const arg of args) { + if (arg.startsWith('--tag=')) evtOpts.tag = arg.split('=')[1]; + if (arg.startsWith('--series=')) evtOpts.series = arg.split('=')[1]; + if (arg.startsWith('--limit=')) evtOpts.limit = arg.split('=')[1]; + if (arg.startsWith('--order=')) evtOpts.order = arg.split('=')[1]; + } + await listEvents(evtOpts); + break; + + case 'market': + case 'm': + if (!args[0]) { + console.log('Usage: polymarket market '); + process.exit(1); + } + await getMarket(args[0]); + break; + + case 'sports': + await listSports(); + break; + + case 'tags': + case 't': + await listTags(parseInt(args[0]) || 50); + break; + + case 'price': + case 'p': + if (!args[0]) { + console.log('Usage: polymarket price [buy|sell]'); + process.exit(1); + } + await getPrice(args[0], args[1] || 'buy'); + break; + + case 'book': + case 'b': + if (!args[0]) { + console.log('Usage: polymarket book '); + process.exit(1); + } + await getOrderbook(args[0]); + break; + + default: + console.log(`Polymarket CLI - Query prediction market odds + +Commands: + search Search markets by keyword + events [options] List active events + --tag= Filter by category (crypto, politics, sports, etc.) + --limit= Max results (default: 20) + market Get market details and current odds + sports List sports leagues + tags List available categories + price Get current price for a token + book Get orderbook for a token + +Examples: + polymarket search "super bowl" + polymarket search "bitcoin 100k" + polymarket events --tag=crypto --limit=10 + polymarket market will-bitcoin-reach-100k +`); + } + } catch (e) { + console.error('Error:', e.message); + process.exit(1); + } +})();