Polymarket Copytrading
Mirror positions from successful Polymarket traders using the Simmer SDK. Two modes share the same skill, use whichever fits your strategy.
🚨 Framework, not a production trading system. Read DISCLAIMER.md before connecting to a wallet with real funds. Per-position cap defaults to $50; per-run cap defaults to 10 trades. Use
$SIMpaper-mode to validate any new whale selection before scaling to real USDC.
This is a template. The default logic mirrors whale wallets — remix it with your own wallet selection, sizing rules, filters, or cap logic. The skill handles all the plumbing (signal polling, trade execution, dedup, signing). Your agent provides the alpha.
| | Polling mode (free) | Reactor mode (Pro) |
|---|---|---|
| Entrypoint | copytrading_trader.py | copytrading_trader.py --reactor |
| Cadence | Batch scan, runs on cron or manual | Polls every 2s for pre-resolved whale signals |
| Latency | Minutes (Polymarket Data API polling) | Seconds (Simmer detects events in real-time, pre-confirmation) |
| Strategy | Size-weighted aggregation across wallets, conviction tiering, rebalance to target allocations, drift/stale filters | Event-by-event mirror with fixed mirror_fraction sizing, programmatic filters |
| Best for | Portfolio-aware, multi-whale, periodic scans | Real-time reaction to specific whales as they trade |
| Requires | SIMMER_API_KEY | SIMMER_API_KEY + Simmer Pro plan |
Setup Flow
When user asks to install or configure this skill:
-
Install the Simmer SDK (version 0.9.19 or newer — reactor mode requires it)
pip install -U 'simmer-sdk>=0.9.19' -
Ask for Simmer API key
- They can get it from simmer.markets/dashboard → SDK tab
- Store in environment as
SIMMER_API_KEY
-
Ask for wallet private key (required for live trading on Polymarket)
- This is the private key for their Polymarket wallet (the wallet that holds USDC)
- Store in environment as
WALLET_PRIVATE_KEY - The SDK uses this to sign orders client-side automatically — no manual signing needed
- Not needed for $SIM paper trading
-
Ask about settings (or confirm defaults)
- Target wallets: Whale addresses to copy
- Max per position: Amount per trade (default $50)
- Top N positions: How many positions to track (auto-calculated from balance)
- Max trades per run: Safety cap (default 10)
When to Use This Skill
Use this skill when the user wants to:
- Copytrade whale wallets on Polymarket
- Paper trade (copytrade with $SIM) to test strategies without real money
- Check what positions a wallet holds
- Follow specific trader addresses
- Check their copytrading positions
Quick Commands
# Check account balance and positions
python scripts/status.py
# Detailed position list
python scripts/status.py --positions
API Reference:
- Base URL:
https://api.simmer.markets - Auth:
Authorization: Bearer $SIMMER_API_KEY - Portfolio:
GET /api/sdk/portfolio - Positions:
GET /api/sdk/positions
Finding Whale Wallets
- predicting.top — Leaderboard of top Polymarket traders with wallet addresses
- alphawhale.trade — Tools for copying and tracking top performers
- Polymarket Leaderboard — Official rankings (requires account)
Quick Start (Ad-Hoc Usage)
User provides wallet(s) directly in chat:
User: "Copytrade this wallet: 0x1234...abcd"
User: "What positions does 0x5678...efgh have?"
User: "Follow these whales: 0xaaa..., 0xbbb..."
→ Run with --wallets flag:
python copytrading_trader.py --wallets 0x1234...abcd
python copytrading_trader.py --wallets 0xaaa...,0xbbb... --dry-run
This is the simplest way - no setup needed, just pass wallets directly.
Persistent Setup (Optional)
For automated recurring scans, wallets can be saved in environment:
| Setting | Environment Variable | Default |
|---------|---------------------|---------|
| Target wallets | SIMMER_COPYTRADING_WALLETS | (none) |
| Top N positions | SIMMER_COPYTRADING_TOP_N | auto |
| Max per position | SIMMER_COPYTRADING_MAX_USD | 50 |
| Max trades/run | SIMMER_COPYTRADING_MAX_TRADES | 10 |
| Order type | SIMMER_COPYTRADING_ORDER_TYPE | GTC |
| Cadence mode | COPYTRADING_CADENCE_MODE | polling |
| Force Simmer venue | COPYTRADING_FORCE_SIMMER_VENUE | (unset) |
Cadence mode (v1.11+)
COPYTRADING_CADENCE_MODE controls how many trades the skill attempts per polling run. Choose based on how frequently your setup runs:
| Preset | Max trades/run | When to use |
|--------|---------------|-------------|
| polling | 10 | Default. Original polling-mode behavior — unchanged for existing installs |
| balanced | 50 | More active polling, want a higher per-run budget |
| aggressive | 200 | High-frequency polling setups |
export COPYTRADING_CADENCE_MODE=balanced
Existing installs default to polling — no behavior change unless you explicitly set this variable.
Reactor mode note:
cadence_modecontrols polling-mode trade budget only. In Reactor mode, each whale signal is handled individually by_process_reactor_signal— the per-run cap doesn't apply. If you hitDaily trade limit reached (10/day)in Reactor mode, raise the server-side limit via:curl -X PATCH "https://api.simmer.markets/api/sdk/user/settings" \ -H "Authorization: Bearer $SIMMER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"max_trades_per_day": 200}'
Position cap: The per-market position cap is controlled by
SIMMER_COPYTRADING_MAX_USD(default 50). Raise it to allow larger positions per market.
Top N auto-calculation (when not specified):
- Balance < $50: Top 5 positions
- Balance $50-200: Top 10 positions
- Balance $200-500: Top 25 positions
- Balance $500+: Top 50 positions
Polymarket Constraints:
- Minimum 5 shares per order
- SDK enforces $1.00 minimum position value (filters dust positions)
⚠️ Start Conservative: Begin with small amounts (
--max-usd 5-10) and--dry-runto understand how the skill behaves before scaling up.
How It Works
By default, only buys execute. Pass
--rebalanceto also sell positions the whales have exited, or--whale-exitsto sell only on whale exits.
Each cycle the script:
- Fetches positions from all target wallets via Simmer API
- Combines using size-weighted aggregation (larger wallets = more influence)
- Detects conflicts (one wallet long YES, another long NO) and skips those markets
- Scores by conviction: positions held by 2+ wallets get full sizing, single-wallet positions get 50% sizing
- Filters out drifted (>30% from entry) and stale (>90% or <10% price) positions
- Applies Top-N filtering to concentrate on highest-conviction positions
- Auto-imports missing markets from Polymarket
- Calculates rebalance trades to match target allocations
- Executes trades via Simmer SDK (respects spending limits)
- Reports results back to user
Reactor Mode (Pro) — event-driven real-time mirroring
Requires Simmer Pro. The reactor stream is gated by
users.is_pro. Upgrade at simmer.markets/dashboard if you see a 402 error on connect.
Reactor mode polls Simmer for pre-resolved whale trade signals derived from real-time on-chain settlement data. Simmer detects whale trades as they happen — even before on-chain confirmation — and delivers trade-ready signals to your skill. Unlike polling mode (which batches and rebalances), reactor reacts to each whale trade individually.
How it's different from polling mode
- Event-driven, not batched. Each whale settlement is evaluated and acted on independently.
- Fixed per-event sizing.
mirror_fraction× whale size, capped atmax_size. No conviction tiering, no rebalance math. - Server-side watchlist filter. Your watchlist +
min_sizeare stored in Simmer's reactor config and applied on the server before events reach your skill — you only see matches. - Pre-resolved signals. The server resolves Polymarket condition IDs to Simmer market UUIDs before writing the signal — the skill receives trade-ready payloads.
- Two run modes. Loop mode (default, polls every 2s) or
--oncefor cron-style single poll and exit. - Circuit breaker. 5 consecutive trade failures → signals are skipped until the next success. Prevents runaway failures from draining your wallet.
- Server-side dedup. Signals have a 60-second TTL in Redis and are deleted after successful execution. No local state files needed.
- Buys only (MVP). Reactor currently mirrors whale buys only — sell signals are filtered out server-side by the relay. If a whale exits a position, reactor won't mirror the sell. Sell mirroring is planned for a future release.
Configure your reactor watchlist
Reactor uses Simmer-side config (not env vars), so dashboard edits take effect in seconds without restarting the skill.
# Set your watchlist via the Simmer API
curl -X PATCH "https://api.simmer.markets/api/sdk/reactor/config" \
-H "Authorization: Bearer $SIMMER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"wallets": ["0x1234...abcd", "0x5678...efgh"],
"min_size": 1000,
"max_size": 50,
"mirror_fraction": 0.01,
"daily_cap": 100,
"venue": "sim",
"enabled": true
}'
Fields:
| Field | Meaning |
|---|---|
| wallets | Whale addresses to follow (EVM format, lowercased server-side) |
| min_size | Minimum whale trade size to consider (shares) |
| max_size | Cap on your mirror trade size (shares) |
| mirror_fraction | Fraction of whale size to mirror (e.g. 0.01 = 1%) |
| daily_cap | Max total spend per day in venue-native units |
| venue | sim / polymarket / kalshi |
| enabled | Pause reactor by setting false — server will stop delivering events |
| price_buffer | Fraction added above whale's fill price for your buy order. Default 0.02 (2%). Prevents order failures on thin books after whale clears liquidity. Range 0–0.2. |
Run reactor mode
Recommended: cron with --once — polls for pending signals once and exits. Run on a 1-minute cron for reliable, persistent coverage:
# Linux crontab
*/1 * * * * cd /path/to/skill && python copytrading_trader.py --reactor --once --live
# OpenClaw cron
openclaw cron add --name "reactor-poll" --cron "*/1 * * * *" --tz UTC --session isolated \
--message "Run: cd /path/to/skill && python3 copytrading_trader.py --reactor --once"
# One-off check
python copytrading_trader.py --reactor --once
Why cron? Reactor signals expire after a short window. A cron ensures your agent checks for signals reliably, even after reboots or process crashes. If your polling process stops, signals expire silently — cron prevents this.
Advanced: loop mode — polls every 2s continuously. Lower latency but requires a process manager (launchd, systemd, supervisor) to auto-restart on crash. Not recommended for agent runtimes with exec timeouts.
# With a process manager (launchd, systemd, supervisor)
python copytrading_trader.py --reactor
# Plain shell (will not auto-restart)
nohup python copytrading_trader.py --reactor > reactor.log 2>&1 &
Set REACTOR_POLL_INTERVAL_SECONDS to tune the polling cadence (default 2s).
Note: Reactor mode always executes live trades (venue is set in your reactor config). Use
venue: "sim"in your config to paper trade.
What happens per signal
- Skill polls
GET /api/sdk/reactor/pendingfor pre-resolved whale signals - Circuit breaker check: if 5+ consecutive failures in recent reactions, skip this tick
- For each signal: compute mirror size (
taker_size × mirror_fraction, capped bymax_size, floored at 5-share Polymarket minimum) - If below minimum →
skipped_filterreaction, no trade SimmerClient.trade()withskill_slug="polymarket-copytrading"andsource="sdk:copytrading:reactor"- On success: DELETE the signal from
/api/sdk/reactor/pending/{tx_hash}and POST amirroredreaction - On failure: POST a
failedreaction, leave signal (60s TTL clears it)
Example output
[reactor] price_buffer=0.020 (from config)
[reactor] --once: single poll against /api/sdk/reactor/pending
[reactor] 0 pending signals
When a whale trade matches your watchlist:
[reactor] price_buffer=0.020 (from config)
[reactor] loop mode: polling /api/sdk/reactor/pending every 2.0s
[reactor] 1 pending signal(s)
[reactor] 0xbaa2bc... BUY 7067 shares on "Will Iran strike Iraq by April 30, 2026?"
[reactor] mirror: 70.67 shares @ $0.673 (buffer +2.0%) → GTC order placed
[reactor] ✅ mirrored — trade_id=a23dc52a, signal deleted
Reactor venue auto-routing (v1.11+)
When Reactor signals arrive sized above Simmer's 500 $SIM per-trade cap, the skill automatically routes them to Polymarket instead of failing silently. This lets you follow whale sizing without hitting the LMSR hard cap:
[reactor] 0xbaa2bc... amount 800.00 $SIM > 500 $SIM cap → routing to polymarket
[reactor] ✅ 0xbaa2bc... mirrored 800.00 USD trade_id=a23dc52a
Edge cases:
- No Polymarket wallet configured: skill falls back to a 500 $SIM-capped trade and logs the routing decision. You won't miss the signal entirely.
COPYTRADING_FORCE_SIMMER_VENUE=true: disables auto-routing. Signals above 500 $SIM are capped at 500 and traded on Simmer venue.- Signal already targets Polymarket: no change — auto-routing only fires on
simvenue signals.
External wallets just work
Reactor mode runs in your harness, so SimmerClient.trade() signs locally with your existing wallet setup (managed, or external via WALLET_PRIVATE_KEY). No server-side signing, no OWS dependency, no new keys to manage.
When to use polling vs reactor
- Use polling when you want portfolio-style copying: aggregate across multiple whales, rebalance to target allocations, run periodically from cron, filter drifted/stale positions. Doesn't require Pro.
- Use reactor when you want real-time reaction to individual whale trades, fixed per-event sizing, and pre-resolved signals. Requires Pro.
- Use both if you want: polling for your steady-state portfolio alignment + reactor for opportunistic real-time mirroring. Different flags, same skill, same API key.
$SIM Paper Trading
Copytrading supports $SIM mode — mirror whale positions using simulated money on Simmer's LMSR markets. No wallet or USDC required.
# Paper trade with $SIM (explicit)
python copytrading_trader.py --venue sim --wallets 0x123... --live
# Auto-detect: if your account has no linked wallet, $SIM is used automatically
python copytrading_trader.py --wallets 0x123... --live
In $SIM mode:
- Trades execute on Simmer's LMSR at real Polymarket prices
- Each market gets an independent $10K $SIM balance
- Positions tracked in your Simmer portfolio (source:
sdk:copytrading) - Whale signals still come from real Polymarket data
Running the Skill
Run a scan (dry run by default — no trades):
python copytrading_trader.py
Execute real trades:
python copytrading_trader.py --live
Check positions only:
python copytrading_trader.py --positions
View current config:
python copytrading_trader.py --config
Override wallets for one run:
python copytrading_trader.py --wallets 0x123...,0x456...
Full rebalance mode (includes sells):
python copytrading_trader.py --rebalance
Sell when whales exit positions:
python copytrading_trader.py --whale-exits
Reporting Results
After each run, message the user with:
- Current configuration (wallets, Top N, max position)
- Number of wallets fetched and total positions found
- Markets skipped due to conflicts
- Trades executed (or skipped with reason)
- Current portfolio positions
Example output to share:
🐋 Copytrading Scan Complete
Configuration:
• Following 2 wallets
• Top 10 positions, max $50 each
• Balance: $250.00 USDC
Fetched positions:
• 0x1234...abcd: 15 positions
• 0x5678...efgh: 22 positions
• Combined: 28 unique markets
• Conflicts skipped: 2
Top 10 by allocation:
1. "Will BTC hit $100k?" - 18.5% → BUY YES
2. "Trump pardons X?" - 12.3% → BUY NO
3. "Fed rate cut Jan?" - 9.8% → Already held
...
Trades executed: 4 buys ($180 total)
• Bought 45 YES shares on "Will BTC hit $100k?" @ $0.82
• Bought 120 NO shares on "Trump pardons X?" @ $0.15
...
Next scan in 4 hours.
Example Conversations
User: "Copytrade 0x1234...abcd"
→ Run: python copytrading_trader.py --wallets 0x1234...abcd
→ Report what positions that wallet has and what trades would execute
User: "What is 0x5678...efgh holding?"
→ Run: python copytrading_trader.py --wallets 0x5678...efgh --dry-run
→ Show their positions without trading
User: "Follow these wallets: 0xaaa..., 0xbbb..., 0xccc..."
→ Run: python copytrading_trader.py --wallets 0xaaa...,0xbbb...,0xccc...
→ Aggregate positions across all wallets, report results
User: "Copytrade this whale but only top 5 positions"
→ Run: python copytrading_trader.py --wallets 0x... --top-n 5
User: "How are my positions doing?"
→ Run: python copytrading_trader.py --positions
→ Show current Polymarket positions with P&L
User: "Show copytrading config"
→ Run: python copytrading_trader.py --config
→ Display current settings
User: "Sell positions that whales have exited"
→ Run: python copytrading_trader.py --whale-exits
→ Compares your positions to whales, sells any they've closed
User: "Do a full rebalance to match the whales"
→ Run: python copytrading_trader.py --rebalance
→ Includes both buys AND sells to match whale allocations
Finding Good Wallets to Follow
Common approaches:
- Leaderboard tracking: Check Polymarket leaderboards for consistent performers
- Whale watchers: Follow known profitable traders on social media
- Specific strategies: Follow wallets known for weather, politics, or crypto trades
The skill works best when:
- Following 2-5 wallets with overlapping strategies (e.g. all politics-focused, or all crypto-focused)
- Wallets have similar conviction — mixing very different traders means most positions only appear in one wallet and get reduced sizing (50%)
- Wallets trade markets available on Polymarket
Conviction Tiers
When following multiple wallets, positions are scored by conviction:
- High conviction (held by 2+ wallets): full position sizing (
max_usd) - Low conviction (held by 1 wallet): 50% position sizing
High-conviction positions are prioritized in Top N selection. Single-wallet positions still trade, but with reduced size. Using just 1 wallet disables conviction scoring (all positions get full sizing).
Troubleshooting
"Order too small" / "below minimum (5)"
- Polymarket requires minimum 5 shares per order
- Increase
--max-usdor reduce--top-nto concentrate into fewer positions
"No wallets specified"
- Provide wallet addresses in your message, e.g., "copytrade 0x1234..."
- Or set SIMMER_COPYTRADING_WALLETS environment variable for recurring scans
"Agent has no USDC balance"
- Need USDC in your Polymarket wallet, or use
--venue simfor $SIM paper trading - Check wallet is linked at simmer.markets/dashboard
"Conflict skipped"
- Wallets disagree on this market (one long YES, other long NO)
- Markets with net position < 10% are skipped
"All N positions filtered (X conflicts, Y drifted, Z stale)"
- All whale positions were removed by safety filters
- Try different target wallets, or reduce to 1 wallet to disable conviction scoring
- Drift filter skips positions where price moved >30% from whale's entry
- Stale filter skips near-resolved markets (price >90% or <10%)
"Insufficient balance"
- Not enough USDC for all trades
- Reduce SIMMER_COPYTRADING_TOP_N or SIMMER_COPYTRADING_MAX_USD
"Market could not be imported"
- Some markets may not be importable (resolved, private, etc.)
- These are skipped automatically
"External wallet requires a pre-signed order"
WALLET_PRIVATE_KEYis not set in the environment- The SDK signs orders automatically when this env var is present — no manual signing code needed
- Fix:
export WALLET_PRIVATE_KEY=0x<your-polymarket-wallet-private-key> - Do NOT attempt to sign orders manually or modify the skill code — the SDK handles it
"Balance shows $0 but I have funds on Polygon"
- Polymarket V2 (live 2026-04-28) uses pUSD (PolyUSD, 1:1 backed by USDC.e). If your wallet holds USDC.e, migrate at simmer.markets/dashboard with one click (~30s)
- If you bridged native USDC (Circle), swap to USDC.e first, then migrate to pUSD
- Full migration guide: docs.simmer.markets/v2-migration
微信扫一扫