OpenParable
Skip to content
← Back to skills

polymarket-profile

Apps & Automation

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.

View on GitHub ↗
QUICK START

How to use this skill

Bring this guide into your coding agent with a prompt tailored to the tool you use.

  1. Open your project in Codex.
  2. Copy and paste the prompt below.
  3. Review files and risks before approving any action.
Prompt to paste
Inspect the Agent Skill at https://github.com/runesleo/polymarket-toolkit/blob/HEAD/skills/polymarket-profile/SKILL.md and all supporting files. Report provenance, permissions, scripts, dependencies, and security risks. Propose an exact installation into .agents/skills/polymarket-profile, but wait for my approval before writing files or running scripts.

Copying this prompt does not install or run the skill. Review third-party files before use.

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 TypeExampleHow to handle
0x address0x63ce3421...Use directly
Profile URLpolymarket.com/profile/Theo4Extract username from URL, then resolve via leaderboard lookup (see below)
UsernameTheo4Resolve 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.

EndpointBase URLPurpose
LB APIhttps://lb-api.polymarket.comPnL, volume, rankings
Data APIhttps://data-api.polymarket.comPositions, activity, trades
CLOB APIhttps://clob.polymarket.comOrderbook, market prices
Gamma APIhttps://gamma-api.polymarket.comMarket 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:

FieldDescription
titleMarket question
outcome"Yes" or "No"
sizeNumber of shares held
avgPriceAverage entry price
curPriceCurrent market price
initialValueTotal cost (size × avgPrice)
currentValueCurrent value (size × curPrice)
cashPnlRealized + unrealized PnL
percentPnlPnL as percentage
totalBoughtTotal shares ever bought
realizedPnlPnL from closed portions
redeemableCan claim settlement winnings
endDateMarket expiry date
eventSlugEvent 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:

BucketConditionMeaning
Wonredeemable == true AND currentValue > 0Market settled in user's favor, awaiting redemption
Lostredeemable == true AND currentValue == 0Market settled against user
Openredeemable == false AND currentValue > 0Market 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:

TypeWhat it means
TRADEBought or sold shares (check side field: "BUY" or "SELL")
SPLITCreated YES+NO pairs from USDC (market-neutral entry)
MERGECombined YES+NO back to USDC (exit / arbitrage capture)
REDEEMClaimed winnings after market settlement
CONVERSIONConverted between YES/NO (special market operation)
REBATEFee 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 labelDisplay Category
Sports, NBA Playoffs, Chess, Esports, any sports team nameSports
Crypto, NFTs, Bitcoin, EthereumCrypto
US-current-affairs, Elections, any president/congress/party keywordPolitics
Ukraine & Russia, Iran, any war/military/invasion keywordGeopolitics
Business, any GDP/oil/fed/rate keywordFinance
Pop-Culture, Art, CoronavirusCulture
temperature/weather/celsius keyword in titleWeather
AI/tech/SpaceX keyword in titleTech
Musk/tweet keyword in titleMusk/Tweets
No matchOther

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

PatternConditionsDescription
SPLIT Arbitragesplit_volume > trade_volume × 0.2 AND merge_volume > 0Enters via SPLIT (creates YES+NO pairs), sells one side or MERGEs back. Capital-efficient, market-neutral entry.
Market Makertrade_count > 500 AND unique_markets < 15 AND avg_trade_size < $20High-frequency small trades concentrated in few markets. Provides liquidity, earns spread.
Whale / Concentratedconcentration > 60% AND unique_markets < 10Heavy capital in a few markets. High-conviction directional bets.
Diversifiedunique_markets > 50Spread across many markets. Portfolio approach, lower per-market risk.
Small Tradertrade_count < 50 AND total_invested < $500Limited activity. New or casual user.
MixedNone of the aboveCombination 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.