بنقرة واحدة
polymarket-profile
// Polymarket address profiler — input any 0x address, get a complete trading profile with PnL, win rate, positions, category breakdown, and top trades. All data from public APIs, no local database needed.
// Polymarket address profiler — input any 0x address, get a complete trading profile with PnL, win rate, positions, category breakdown, and top trades. All data from public APIs, no local database needed.
Precise PnL calculator for any Polymarket address — reconstructs profit/loss from Data API cashflow (BUY/SELL/REDEEM/MERGE/SPLIT/REBATE) plus unrealized position value. Matches official /profit within ~0.2% MAPE on leaderboard validation. Bring-your-own Python (httpx).
Brier Score calculator for Polymarket addresses — measures prediction accuracy independent of PnL. Separates skilled predictors from market makers and arbitrageurs.
| name | polymarket-profile |
| description | Polymarket address profiler — input any 0x address, get a complete trading profile with PnL, win rate, positions, category breakdown, and top trades. All data from public APIs, no local database needed. |
| allowed-tools | Bash(curl:*) Bash(node:*) Read Write Edit |
| metadata | {"version":"0.2.0","openclaw":{"skillKey":"polymarket-profile","homepage":"https://leolabs.me","requires":{"anyBins":["curl","node"]}}} |
Generate a complete trading profile for any Polymarket address. All data comes from public APIs in real-time — no local database or API key required.
User provides a Polymarket identity and wants analysis, profile, or trading overview.
The skill requires a 0x proxy wallet address. Users may provide:
| Input Type | Example | How to handle |
|---|---|---|
| 0x address | 0x63ce3421... | Use directly |
| Profile URL | polymarket.com/profile/Theo4 | Extract username from URL, then resolve via leaderboard lookup (see below) |
| Username | Theo4 | Resolve via leaderboard lookup (see below) |
Step 1: Try leaderboard lookup — Search lb-api.polymarket.com/profit to resolve username → address.
# Paginate through the leaderboard searching for the username
# Start with offset=0, increment by 500 until found or no more results
curl -s "https://lb-api.polymarket.com/profit?window=all&limit=500&offset=0"
Response is an array of objects with name, pseudonym, and proxyWallet fields. Search for a case-insensitive match on name or pseudonym.
proxyWallet as the 0x address, continue to Step 1 of executionStep 2: Manual fallback (only if leaderboard lookup fails)
"This username isn't in the Polymarket leaderboard (only ranked users can be auto-resolved). You can find the 0x address by:
- Open the profile page on Polymarket
- Click the address/wallet icon near the username — it copies the 0x address
- Or check the browser URL — some profile pages show the address"
NOTE: Leaderboard lookup covers all users with a PnL ranking (tens of thousands). Only very new or inactive accounts with zero trading history won't be found.
All endpoints are public, no authentication needed.
| Endpoint | Base URL | Purpose |
|---|---|---|
| LB API | https://lb-api.polymarket.com | PnL, volume, rankings |
| Data API | https://data-api.polymarket.com | Positions, activity, trades |
| CLOB API | https://clob.polymarket.com | Orderbook, market prices |
| Gamma API | https://gamma-api.polymarket.com | Market metadata, events, categories |
Run steps 1-4 in parallel where possible to minimize latency.
curl -s "https://lb-api.polymarket.com/profit?window=all&address={ADDRESS}"
Response is an array. Extract [0].amount for total PnL, [0].name for username.
NOTE: lb-api only returns amount (total PnL). It does NOT return invested/numTrades/numWins. Those must be computed from positions (Step 2) and activity (Step 3).
Also fetch time-windowed PnL for trend:
curl -s "https://lb-api.polymarket.com/profit?window=7d&address={ADDRESS}"
curl -s "https://lb-api.polymarket.com/profit?window=30d&address={ADDRESS}"
curl -s "https://data-api.polymarket.com/positions?user={ADDRESS}&sizeThreshold=0&limit=100&offset=0"
Paginate with offset parameter: if response returns exactly 100 records, fetch next page with offset=100, offset=200, etc. until fewer than 100 returned.
Each position object contains:
| Field | Description |
|---|---|
title | Market question |
outcome | "Yes" or "No" |
size | Number of shares held |
avgPrice | Average entry price |
curPrice | Current market price |
initialValue | Total cost (size × avgPrice) |
currentValue | Current value (size × curPrice) |
cashPnl | Realized + unrealized PnL |
percentPnl | PnL as percentage |
totalBought | Total shares ever bought |
realizedPnl | PnL from closed portions |
redeemable | Can claim settlement winnings |
endDate | Market expiry date |
eventSlug | Event identifier for Gamma API |
Extract:
redeemable == false AND currentValue > 0currentValuecurrentValue for open positionsClassify positions into three buckets:
| Bucket | Condition | Meaning |
|---|---|---|
| Won | redeemable == true AND currentValue > 0 | Market settled in user's favor, awaiting redemption |
| Lost | redeemable == true AND currentValue == 0 | Market settled against user |
| Open | redeemable == false AND currentValue > 0 | Market not yet settled |
Win Rate = Won / (Won + Lost)
IMPORTANT: Do NOT use cashPnl > 0 to determine wins. Winning positions have currentValue > 0 (shares worth $1) even if cashPnl appears negative due to partial sells. The redeemable flag is the definitive settlement indicator.
For inactive accounts, the positions API may return very few records (settled positions get cleaned up). If Won + Lost < 3, fall back to activity-based estimation:
slug (market)slugcurl -s "https://data-api.polymarket.com/activity?user={ADDRESS}&limit=500"
IMPORTANT: You MUST paginate to get complete data. Large accounts have 10,000+ records. 500 records is NOT enough for an accurate profile.
Loop:
1. First request: /activity?user={ADDRESS}&limit=500
2. Get `timestamp` of the LAST record in the response
3. Next request: /activity?user={ADDRESS}&limit=500&end={last_timestamp}
4. Repeat until response returns fewer than 500 records (= last page)
5. Merge all results
Inform user of progress for large accounts: "Fetching activity... page X (Y records so far)"
Each activity object contains: type, size, usdcSize, price, side, title, slug, timestamp, outcome.
Classify by type field:
| Type | What it means |
|---|---|
| TRADE | Bought or sold shares (check side field: "BUY" or "SELL") |
| SPLIT | Created YES+NO pairs from USDC (market-neutral entry) |
| MERGE | Combined YES+NO back to USDC (exit / arbitrage capture) |
| REDEEM | Claimed winnings after market settlement |
| CONVERSION | Converted between YES/NO (special market operation) |
| REBATE | Fee rebate from maker orders |
Count and sum usdcSize for each type. Also track unique markets (slug) for diversity analysis.
For each position, fetch market metadata via Gamma API using the eventSlug from positions:
curl -s "https://gamma-api.polymarket.com/events?slug={eventSlug}"
Or batch multiple slugs. Each event has a category field and a tags array (each tag has a label field).
Gamma API categories are legacy naming. Map to Polymarket's frontend categories:
| Gamma category / tag label | Display Category |
|---|---|
Sports, NBA Playoffs, Chess, Esports, any sports team name | Sports |
Crypto, NFTs, Bitcoin, Ethereum | Crypto |
US-current-affairs, Elections, any president/congress/party keyword | Politics |
Ukraine & Russia, Iran, any war/military/invasion keyword | Geopolitics |
Business, any GDP/oil/fed/rate keyword | Finance |
Pop-Culture, Art, Coronavirus | Culture |
| temperature/weather/celsius keyword in title | Weather |
| AI/tech/SpaceX keyword in title | Tech |
| Musk/tweet keyword in title | Musk/Tweets |
| No match | Other |
Priority: Use Gamma category field first. If it's missing or too generic (All), fall back to tag labels. If still unclear, infer from market title keywords.
NOTE: Gamma API may return empty [] for old/settled markets. This is expected — fall back to keyword inference from market titles in activity data.
Optimization: Collect all unique eventSlug values from positions first, then batch fetch from Gamma (avoid one API call per position). Group by slug to avoid duplicates.
Compute the percentage distribution by volume invested.
Analyze data from Steps 1-4 to classify the address into a trading pattern. Use multiple dimensions — no single metric is sufficient.
From activity data:
trade_count: total TRADE recordssplit_count / split_volume: SPLIT records and USDC volumemerge_count / merge_volume: MERGE records and USDC volumeredeem_volume: total REDEEM volumeunique_markets: number of distinct slug valuesavg_trade_size: total trade volume / trade counttrade_frequency: trade count / active days (first to last timestamp)From positions data:
open_count: open positions (not redeemable)settled_count: won + lostconcentration: top 3 positions as % of total investedEvaluate in this order (first match wins):
| Pattern | Conditions | Description |
|---|---|---|
| SPLIT Arbitrage | split_volume > trade_volume × 0.2 AND merge_volume > 0 | Enters via SPLIT (creates YES+NO pairs), sells one side or MERGEs back. Capital-efficient, market-neutral entry. |
| Market Maker | trade_count > 500 AND unique_markets < 15 AND avg_trade_size < $20 | High-frequency small trades concentrated in few markets. Provides liquidity, earns spread. |
| Whale / Concentrated | concentration > 60% AND unique_markets < 10 | Heavy capital in a few markets. High-conviction directional bets. |
| Diversified | unique_markets > 50 | Spread across many markets. Portfolio approach, lower per-market risk. |
| Small Trader | trade_count < 50 AND total_invested < $500 | Limited activity. New or casual user. |
| Mixed | None of the above | Combination of strategies, no dominant pattern. |
🎯 Strategy Pattern: {pattern_name}
{1-2 sentence explanation based on actual numbers}
Key metrics: {trade_count} trades across {unique_markets} markets, avg ${avg_trade_size}/trade
Do NOT reveal specific thresholds used for classification. Just state the pattern name and explain it in plain language using the trader's actual data.
From positions data (not activity), use cashPnl field for settled positions:
cashPnlcashPnlFor each: market title, outcome (Yes/No), entry price (avgPrice), PnL amount.
NOTE: cashPnl from positions is approximate (affected by partial sells). This is acceptable for v0.1. Do NOT attempt to match individual BUY→SELL/REDEEM from activity — that requires complex logic and is not worth the accuracy gain for a profile overview.
If positions API returns very few settled records, estimate Top Trades from activity:
slug, sum usdcSize per market (= total invested)slug, sum usdcSize per market (= total payout)Output the complete profile in this format:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Polymarket Profile: {ADDRESS_SHORT}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 Overview
Address: {ADDRESS}
Total PnL: ${totalProfit} (invested ${invested})
Win Rate: {wins}/{total} ({winRate})
7d PnL: ${pnl_7d}
30d PnL: ${pnl_30d}
📈 Current Positions ({count})
Largest: {market_name} — {direction} ${size} @ ${avg_price}
Portfolio: ${total_value}
| # | Market | Direction | Size | Avg Price | Current | PnL |
|---|--------|-----------|------|-----------|---------|-----|
| 1 | ... | YES/NO | $xx | $0.xx | $0.xx | +$x |
...
📂 Category Distribution
| Category | Positions | Volume | % |
|----------|-----------|--------|---|
| Crypto | 12 | $5,000 | 45% |
| Politics | 5 | $2,000 | 22% |
...
📋 Activity Summary
| Type | Count | Volume |
|------------|-------|--------|
| TRADE BUY | xxx | $xxx |
| TRADE SELL | xxx | $xxx |
| SPLIT | xxx | $xxx |
| MERGE | xxx | $xxx |
| REDEEM | xxx | $xxx |
🏆 Top Wins
1. {market} — {direction} +${pnl} (entry ${price} → ${exit})
2. ...
3. ...
💀 Top Losses
1. {market} — {direction} -${pnl} (entry ${price} → ${exit})
2. ...
3. ...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Generated: {timestamp} | Data: Polymarket Public APIs
Powered by Leo Labs — leolabs.me
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
cashPnl may differ due to SPLIT/MERGE accounting.end timestamp, never offset. Do NOT deduplicate — same txHash with multiple records means multiple fills in one transaction.eventSlug from positions to query Gamma API. Category comes from the event's tags or category field. If Gamma is slow, infer from market title keywords (BTC/ETH/crypto → Crypto, Trump/election → Politics, NBA/FIFA → Sports, temperature/weather → Weather).[] for inactive accounts. Display as "N/A" instead of $0.