
Trader Analysis
- 297 installs
- 24 repo stars
- Updated July 31, 2026
- agentmc15/polymarket-trader
trader-analysis is a Claude Code skill that scores Polymarket wallet addresses from on-chain trades and API history so developers building copy-trading or discovery features can rank profitable traders and track exposure
About
trader-analysis is a Polymarket-focused agent skill in agentmc15/polymarket-trader that teaches Claude to pull trader activity from Polygon on-chain logs and Polymarket APIs, then compute PnL, win rate, position exposure, and originality heuristics. The bundled Python patterns use web3.py against the CTF Exchange contract on Polygon, async HTTP clients, and a TraderAnalyzer class that filters trades by time window, scores leaderboard candidates, and flags likely copy traders versus original entrants. Developers reach for trader-analysis when wiring trader discovery, copy-trading monitors, or risk dashboards that need repeatable wallet scoring instead of one-off SQL. The skill pairs with the repo's polymarket-api and trading-strategies skills for end-to-end prediction-market tooling.
- Polymarket position tracking
- PnL and win-rate metrics
- market exposure breakdown
- trader behavior patterns
- risk-aware trade insights
Trader Analysis by the numbers
- 297 all-time installs (skills.sh)
- Ranked #334 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Jul 31, 2026 (Skillselion catalog sync)
npx skills add https://github.com/agentmc15/polymarket-trader --skill trader-analysisAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 297 |
|---|---|
| repo stars | ★ 24 |
| Last updated | July 31, 2026 |
| Repository | agentmc15/polymarket-trader ↗ |
How do you score Polymarket traders for copy trading?
Analyze Polymarket trader positions, PnL, and market exposure to inform prediction-market strategy and risk decisions.
Who is it for?
Backend or quant developers building Polymarket copy-trading, leaderboard filters, or wallet risk monitors on Polygon.
Skip if: Developers who only need static market odds without wallet-level performance analytics or on-chain trade history.
When should I use this skill?
A developer asks to analyze Polymarket traders, rank profitable wallets, track PnL, or design copy-trading discovery logic.
What you get
Ranked trader scorecards, originality-filtered follow lists, and exposure summaries keyed to wallet addresses.
- Trader scorecards
- Follow-list rankings
- Exposure summaries
By the numbers
- Targets the Polygon CTF Exchange contract 0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E
- Ships alongside polymarket-api and trading-strategies skills in agentmc15/polymarket-trader
Files
Trader Analysis Skill
Tracking Trader Activity
On-Chain Data
from web3 import Web3
import httpx
from typing import AsyncIterator
CTF_EXCHANGE = "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E"
class TraderTracker:
def __init__(self, polygon_rpc: str):
self.w3 = Web3(Web3.HTTPProvider(polygon_rpc))
self.exchange = self.w3.eth.contract(
address=CTF_EXCHANGE,
abi=CTF_EXCHANGE_ABI
)
async def get_trader_trades(
self,
address: str,
from_block: int = None
) -> list[dict]:
"""Fetch all trades for an address."""
events = self.exchange.events.OrderFilled.get_logs(
fromBlock=from_block or "earliest",
argument_filters={"maker": address}
)
return [self._parse_trade_event(e) for e in events]
def _parse_trade_event(self, event: dict) -> dict:
"""Parse OrderFilled event into trade dict."""
return {
"tx_hash": event.transactionHash.hex(),
"block_number": event.blockNumber,
"maker": event.args.maker,
"taker": event.args.taker,
"token_id": str(event.args.tokenId),
"amount": event.args.amount / 1e6, # Assuming 6 decimals
"price": event.args.price / 1e18,
"side": "BUY" if event.args.side == 0 else "SELL",
"timestamp": self._get_block_timestamp(event.blockNumber)
}Polymarket Data API
class PolymarketDataClient:
BASE_URL = "https://data-api.polymarket.com"
def __init__(self):
self.client = httpx.AsyncClient(
base_url=self.BASE_URL,
timeout=30.0
)
async def get_trader_profile(self, address: str) -> dict:
"""Fetch trader profile and stats."""
response = await self.client.get(f"/users/{address}")
response.raise_for_status()
return response.json()
async def get_trader_positions(self, address: str) -> list[dict]:
"""Get all positions for a trader."""
response = await self.client.get(
"/positions",
params={"user": address}
)
response.raise_for_status()
return response.json()
async def get_trader_activity(
self,
address: str,
limit: int = 100,
offset: int = 0
) -> list[dict]:
"""Get recent trading activity."""
response = await self.client.get(
"/activity",
params={
"user": address,
"limit": limit,
"offset": offset
}
)
response.raise_for_status()
return response.json()
async def get_leaderboard(
self,
period: str = "all",
limit: int = 100
) -> list[dict]:
"""Get top traders by P&L."""
response = await self.client.get(
"/leaderboard",
params={"period": period, "limit": limit}
)
response.raise_for_status()
return response.json()Trader Scoring System
from dataclasses import dataclass
from datetime import datetime, timedelta
import numpy as np
from typing import Optional
@dataclass
class TraderMetrics:
address: str
total_pnl: float
realized_pnl: float
unrealized_pnl: float
win_rate: float
avg_return_per_trade: float
sharpe_ratio: float
total_trades: int
unique_markets: int
avg_position_size: float
avg_hold_time: timedelta
consistency_score: float
recency_score: float
largest_win: float
largest_loss: float
profit_factor: float # gross profit / gross loss
class TraderAnalyzer:
def __init__(self, data_client: PolymarketDataClient):
self.client = data_client
async def analyze_trader(
self,
address: str,
days: int = 90
) -> TraderMetrics:
"""Comprehensive trader analysis."""
activity = await self.client.get_trader_activity(
address, limit=1000
)
positions = await self.client.get_trader_positions(address)
# Filter to time period
cutoff = datetime.utcnow() - timedelta(days=days)
recent_trades = [
t for t in activity
if datetime.fromisoformat(t["timestamp"]) > cutoff
]
return TraderMetrics(
address=address,
total_pnl=self._calculate_total_pnl(positions, recent_trades),
realized_pnl=self._calculate_realized_pnl(recent_trades),
unrealized_pnl=self._calculate_unrealized_pnl(positions),
win_rate=self._calculate_win_rate(recent_trades),
avg_return_per_trade=self._calculate_avg_return(recent_trades),
sharpe_ratio=self._calculate_sharpe(recent_trades),
total_trades=len(recent_trades),
unique_markets=len(set(t["market_id"] for t in recent_trades)),
avg_position_size=self._calculate_avg_size(recent_trades),
avg_hold_time=self._calculate_avg_hold_time(recent_trades),
consistency_score=self._calculate_consistency(recent_trades),
recency_score=self._calculate_recency_score(recent_trades),
largest_win=max((t.get("pnl", 0) for t in recent_trades), default=0),
largest_loss=min((t.get("pnl", 0) for t in recent_trades), default=0),
profit_factor=self._calculate_profit_factor(recent_trades)
)
def _calculate_win_rate(self, trades: list[dict]) -> float:
"""Calculate percentage of profitable trades."""
if not trades:
return 0
winning = sum(1 for t in trades if t.get("pnl", 0) > 0)
return winning / len(trades)
def _calculate_sharpe(self, trades: list[dict]) -> float:
"""Calculate Sharpe ratio of returns."""
returns = [t.get("return_pct", 0) for t in trades if "return_pct" in t]
if len(returns) < 2:
return 0
mean_return = np.mean(returns)
std_return = np.std(returns)
if std_return == 0:
return 0
# Annualize assuming daily trades
return (mean_return * 365**0.5) / std_return
def _calculate_consistency(self, trades: list[dict]) -> float:
"""Score how consistent the trader's performance is."""
if len(trades) < 10:
return 0
# Group by week
weekly_pnl = {}
for trade in trades:
week = datetime.fromisoformat(trade["timestamp"]).isocalendar()[:2]
weekly_pnl[week] = weekly_pnl.get(week, 0) + trade.get("pnl", 0)
if len(weekly_pnl) < 4:
return 0
# Calculate consistency as % of profitable weeks
profitable_weeks = sum(1 for pnl in weekly_pnl.values() if pnl > 0)
return profitable_weeks / len(weekly_pnl)
def _calculate_recency_score(self, trades: list[dict]) -> float:
"""Score based on recent activity (more recent = higher)."""
if not trades:
return 0
latest = max(
datetime.fromisoformat(t["timestamp"]) for t in trades
)
days_since = (datetime.utcnow() - latest).days
# Decay score over 30 days
return max(0, 1 - (days_since / 30))
def _calculate_profit_factor(self, trades: list[dict]) -> float:
"""Gross profit / gross loss."""
gross_profit = sum(t.get("pnl", 0) for t in trades if t.get("pnl", 0) > 0)
gross_loss = abs(sum(t.get("pnl", 0) for t in trades if t.get("pnl", 0) < 0))
if gross_loss == 0:
return float('inf') if gross_profit > 0 else 0
return gross_profit / gross_loss
class TraderScorer:
def __init__(self, weights: dict = None):
self.weights = weights or {
"pnl": 0.20,
"win_rate": 0.15,
"sharpe": 0.15,
"consistency": 0.15,
"recency": 0.10,
"profit_factor": 0.10,
"experience": 0.10,
"diversity": 0.05
}
def calculate_score(self, metrics: TraderMetrics) -> float:
"""Calculate overall trader score (0-100)."""
scores = {
"pnl": self._normalize_pnl(metrics.total_pnl),
"win_rate": metrics.win_rate * 100,
"sharpe": self._normalize_sharpe(metrics.sharpe_ratio),
"consistency": metrics.consistency_score * 100,
"recency": metrics.recency_score * 100,
"profit_factor": self._normalize_profit_factor(metrics.profit_factor),
"experience": self._normalize_trades(metrics.total_trades),
"diversity": self._normalize_markets(metrics.unique_markets)
}
return sum(scores[k] * self.weights[k] for k in self.weights)
def _normalize_pnl(self, pnl: float) -> float:
"""Normalize P&L to 0-100 scale."""
if pnl <= 0:
return max(0, 50 + pnl / 1000)
return min(100, 50 + np.log1p(pnl) * 8)
def _normalize_sharpe(self, sharpe: float) -> float:
"""Normalize Sharpe ratio to 0-100."""
# Sharpe of 2+ is excellent
return min(100, max(0, sharpe * 33))
def _normalize_profit_factor(self, pf: float) -> float:
"""Normalize profit factor to 0-100."""
if pf == float('inf'):
return 100
# PF of 2+ is good
return min(100, pf * 40)
def _normalize_trades(self, trades: int) -> float:
"""Normalize trade count to 0-100."""
# 100+ trades shows experience
return min(100, trades)
def _normalize_markets(self, markets: int) -> float:
"""Normalize unique markets to 0-100."""
# Trading 10+ markets shows diversity
return min(100, markets * 10)Finding Traders to Follow
class TraderDiscovery:
def __init__(
self,
data_client: PolymarketDataClient,
analyzer: TraderAnalyzer
):
self.client = data_client
self.analyzer = analyzer
self.scorer = TraderScorer()
async def find_top_traders(
self,
min_trades: int = 50,
min_pnl: float = 1000,
min_win_rate: float = 0.5,
days: int = 30
) -> list[tuple[str, float, TraderMetrics]]:
"""Discover top performing traders."""
leaderboard = await self.client.get_leaderboard(limit=500)
candidates = []
for trader in leaderboard:
try:
metrics = await self.analyzer.analyze_trader(
trader["address"],
days=days
)
# Apply filters
if (metrics.total_trades >= min_trades and
metrics.total_pnl >= min_pnl and
metrics.win_rate >= min_win_rate):
score = self.scorer.calculate_score(metrics)
candidates.append((trader["address"], score, metrics))
except Exception as e:
# Skip traders with errors
continue
return sorted(candidates, key=lambda x: x[1], reverse=True)
async def find_market_specialists(
self,
market_category: str,
min_trades_in_category: int = 20
) -> list[str]:
"""Find traders who specialize in specific market categories."""
# Implementation would query by category
pass
async def find_original_traders(
self,
min_originality_score: float = 0.7
) -> list[str]:
"""
Find traders who make original trades (not copy trading).
Originality is measured by:
- Trade timing (not consistently after other traders)
- Position uniqueness (not mirroring others)
- Contrarian indicators
"""
leaderboard = await self.client.get_leaderboard(limit=200)
original_traders = []
for trader in leaderboard:
activity = await self.client.get_trader_activity(
trader["address"],
limit=100
)
originality = await self._calculate_originality(activity)
if originality >= min_originality_score:
original_traders.append(trader["address"])
return original_traders
async def _calculate_originality(
self,
trades: list[dict]
) -> float:
"""Calculate how original a trader's trades are."""
# Compare trade timing with market average
# Check for unique position entries
# Measure contrarian behavior
return 0.5 # Placeholder
class CopyTradingManager:
def __init__(
self,
data_client: PolymarketDataClient,
trading_service, # Your trading service
config: dict
):
self.client = data_client
self.trading = trading_service
self.tracked_traders: dict[str, dict] = {}
self.copy_delay = config.get("copy_delay_seconds", 30)
self.size_multiplier = config.get("size_multiplier", 0.25)
self.max_position_pct = config.get("max_position_pct", 0.1)
def add_trader(
self,
address: str,
multiplier: float = None,
markets: list[str] = None
):
"""Add a trader to copy."""
self.tracked_traders[address] = {
"multiplier": multiplier or self.size_multiplier,
"markets": markets, # None = all markets
"last_trade": None
}
def remove_trader(self, address: str):
"""Stop copying a trader."""
self.tracked_traders.pop(address, None)
async def process_trade(self, trade: dict):
"""Process a trade from tracked trader."""
address = trade["trader_address"]
if address not in self.tracked_traders:
return
config = self.tracked_traders[address]
# Check market filter
if config["markets"] and trade["market_id"] not in config["markets"]:
return
# Wait for delay
await asyncio.sleep(self.copy_delay)
# Calculate size
size = trade["size"] * config["multiplier"]
# Apply max position limit
portfolio = await self.trading.get_portfolio()
max_size = portfolio["value"] * self.max_position_pct / trade["price"]
size = min(size, max_size)
# Execute copy trade
await self.trading.place_order(
token_id=trade["token_id"],
side=trade["side"],
price=trade["price"],
size=size,
metadata={"copy_source": address}
)Real-Time Monitoring
import asyncio
from collections import defaultdict
from typing import Callable, Awaitable
class LiveTraderMonitor:
def __init__(self, tracked_addresses: list[str]):
self.tracked = set(tracked_addresses)
self.callbacks: dict[str, list[Callable]] = defaultdict(list)
self._running = False
def on_trade(self, callback: Callable[[dict], Awaitable[None]]):
"""Register callback for trade events."""
self.callbacks["trade"].append(callback)
return callback
def on_position_change(self, callback: Callable[[dict], Awaitable[None]]):
"""Register callback for position changes."""
self.callbacks["position"].append(callback)
return callback
async def start(self):
"""Start monitoring tracked traders."""
self._running = True
async for event in self._watch_events():
if not self._running:
break
if event.get("trader") in self.tracked:
event_type = event.get("type", "trade")
for callback in self.callbacks[event_type]:
try:
await callback(event)
except Exception as e:
print(f"Callback error: {e}")
def stop(self):
"""Stop monitoring."""
self._running = False
def add_trader(self, address: str):
"""Add trader to watch list."""
self.tracked.add(address)
def remove_trader(self, address: str):
"""Remove trader from watch list."""
self.tracked.discard(address)
async def _watch_events(self):
"""Watch for on-chain events."""
# Implementation would use WebSocket or polling
while self._running:
# Poll for new events
await asyncio.sleep(5)
yield {} # PlaceholderRelated skills
How it compares
Pick trader-analysis when you need wallet scoring and copy-trading discovery logic rather than only market-price or order-placement API helpers.
FAQ
What data does trader-analysis use?
trader-analysis combines Polymarket API trader history with Polygon on-chain reads through web3.py, targeting the CTF Exchange contract address for exchange-level trade events and merging them into wallet-level metrics.
Can trader-analysis find traders to copy?
trader-analysis includes discovery flows that pull leaderboard candidates, score performance, and apply originality heuristics so developers can shortlist wallets that are less likely to be mirroring other traders.
What language does trader-analysis assume?
trader-analysis documents async Python patterns with web3.py, httpx, and typed iterators, so implementers should run Polygon RPC access and async HTTP clients in the same stack.