- 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":"[Truncated]"}}}
# 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.
```bash
# 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
```bash
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:
```bash
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
```bash
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
```bash
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:
```bash
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 | % |
Ver en GitHub