polymarket-profile

star 169

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.

runesleo By runesleo schedule Updated 3/31/2026

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"]}}}

Polymarket Address Profile

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.

Trigger

User provides a Polymarket identity and wants analysis, profile, or trading overview.

Input Resolution

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)

When user provides a username or URL (not 0x address)

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.

  • Found → use the proxyWallet as the 0x address, continue to Step 1 of execution
  • Not found after 3 pages (1500 users) → the account is likely unranked, fall back to Step 2

Step 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:

  1. Open the profile page on Polymarket
  2. Click the address/wallet icon near the username — it copies the 0x address
  3. 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.

API Endpoints

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

Execution Steps

Run steps 1-4 in parallel where possible to minimize latency.

Step 1: PnL Snapshot

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}"

Step 2: Current Positions

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:

  • Number of open positions: redeemable == false AND currentValue > 0
  • Largest position by currentValue
  • Total portfolio value: sum of currentValue for open positions

Win Rate Calculation

Classify 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.

Fallback: When Positions Are Empty or Incomplete

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:

  1. From activity, group all REDEEM records by slug (market)
  2. From activity, group all TRADE records by slug
  3. Markets with REDEEM volume > 0 → Won
  4. Markets with TRADE volume > 0 but no REDEEM → Lost (invested but no payout)
  5. Label Win Rate as "estimated from activity" when using this fallback

Step 3: Activity History

curl -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.

Activity Pagination (REQUIRED)

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.

Step 4: Market Categories

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).

Category Mapping

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.

Step 5: Strategy Pattern Detection

Analyze data from Steps 1-4 to classify the address into a trading pattern. Use multiple dimensions — no single metric is sufficient.

Dimensions to compute

From activity data:

  • trade_count: total TRADE records
  • split_count / split_volume: SPLIT records and USDC volume
  • merge_count / merge_volume: MERGE records and USDC volume
  • redeem_volume: total REDEEM volume
  • unique_markets: number of distinct slug values
  • avg_trade_size: total trade volume / trade count
  • trade_frequency: trade count / active days (first to last timestamp)

From positions data:

  • open_count: open positions (not redeemable)
  • settled_count: won + lost
  • concentration: top 3 positions as % of total invested

Pattern Classification

Evaluate 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.

Output format

🎯 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.

Step 6: Top Trades

From positions data (not activity), use cashPnl field for settled positions:

  • Top 5 Wins: Settled positions with highest positive cashPnl
  • Top 5 Losses: Settled positions with most negative cashPnl

For 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.

Fallback: When Positions Data Is Sparse

If positions API returns very few settled records, estimate Top Trades from activity:

  1. Group TRADE records by slug, sum usdcSize per market (= total invested)
  2. Group REDEEM records by slug, sum usdcSize per market (= total payout)
  3. PnL per market ≈ REDEEM volume - TRADE BUY volume
  4. Sort by PnL for top wins/losses. Label as "estimated from activity".

Step 7: Assemble Profile

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
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Important Notes

  • PnL from lb-api is the ground truth. Use it as the primary source. Position-level cashPnl may differ due to SPLIT/MERGE accounting.
  • Activity pagination: use end timestamp, never offset. Do NOT deduplicate — same txHash with multiple records means multiple fills in one transaction.
  • Always paginate ALL activity records. Partial data produces inaccurate profiles. Show pagination progress to the user (e.g. "Fetching activity... page 5, 2500 records so far").
  • Category mapping: use 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).
  • All monetary values in USD, round to 2 decimal places.
  • Address format: APIs accept both checksummed and lowercase. Normalize to lowercase.
  • lb-api may require proxy in some regions. data-api and gamma-api work globally.
  • Rate limits: No documented limits, but keep requests reasonable (<10 concurrent). Add 500ms delay between sequential pagination calls. If a request returns empty or non-JSON, retry once after 2 seconds.
  • lb-api 7d/30d: May return empty array [] for inactive accounts. Display as "N/A" instead of $0.
Install via CLI
npx skills add https://github.com/runesleo/polymarket-toolkit --skill polymarket-profile
Repository Details
star Stars 169
call_split Forks 24
navigation Branch main
article Path SKILL.md
More from Creator