Skill: simmer/polymarket-copytrading (version 1.11.1)

--- name: polymarket-copytrading description: Mirror positions from top Polymarket traders. Polling mode (free) for portfolio-style copying, Reactor mode (Pro) for event-driven real-time mirroring via Simmer's on-chain signal infrastructure. metadata: author: Simmer (@simmer_markets) version: "1.11.1" displayName: Polymarket Copytrading difficulty: beginner --- # 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](./DISCLAIMER.md) before connecting to a wallet with real funds. Per-position cap defaults to $50; per-run cap defaults to 10 trades. Use `$SIM` paper-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: 1. **Install the Simmer SDK** (version 0.9.19 or newer — reactor mode requires it) ```bash pip install -U 'simmer-sdk>=0.9.19' ``` 2. **Ask for Simmer API key** - They can get it from simmer.markets/dashboard → SDK tab - Store in environment as `SIMMER_API_KEY` 3. **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 4. **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 ```bash # 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](https://predicting.top)** — Leaderboard of top Polymarket traders with wallet addresses - **[alphawhale.trade](https://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: ```bash 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 | ```bash export COPYTRADING_CADENCE_MODE=balanced ``` Existing installs default to `polling` — **no behavior change** unless you explicitly set this variable. > **Reactor mode note:** `cadence_mode` controls 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 hit `Daily trade limit reached (10/day)` in Reactor mode, raise the server-side limit via: > ```bash > 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-run` to understand how the skill behaves before scaling up. ## How It Works > **By default, only buys execute.** Pass `--rebalance` to also sell positions the whales have exited, or `--whale-exits` to sell only on whale exits. Each cycle the script: 1. Fetches positions from all target wallets via Simmer API 2. Combines using size-weighted aggregation (larger wallets = more influence) 3. Detects conflicts (one wallet long YES, another long NO) and skips those markets 4. Scores by conviction: positions held by 2+ wallets get full sizing, single-wallet positions get 50% sizing 5. Filters out drifted (>30% from entry) and stale (>90% or <10% price) positions 6. Applies Top-N filtering to concentrate on highest-conviction positions 7. Auto-imports missing markets from Polymarket 8. Calculates rebalance trades to match target allocations 9. Executes trades via Simmer SDK (respects spending limits) 10. 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 at `max_size`. No conviction tiering, no rebalance math. - **Server-side watchlist filter.** Your watchlist + `min_size` are 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 `--once` for 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. ```bash # 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: ```bash # 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. ```bash # 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 1. Skill polls `GET /api/sdk/reactor/pending` for pre-resolved whale signals 2. Circuit breaker check: if 5+ consecutive failures in recent reactions, skip this tick 3. For each signal: compute mirror size (`taker_size × mirror_fraction`, capped by `max_size`, floored at 5-share Polymarket minimum) 4. If below minimum → `skipped_filter` reaction, no trade 5. `SimmerClient.trade()` with `skill_slug="polymarket-copytrading"` and `source="sdk:copytrading:reactor"` 6. On success: DELETE the signal from `/api/sdk/reactor/pending/{tx_hash}` and POST a `mirrored` reaction 7. On failure: POST a `failed` reaction, 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. - **[REDACTED_SECRET]**: 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 `sim` venue 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. ```bash # 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):** ```bash python copytrading_trader.py ``` **Execute real trades:** ```bash python copytrading_trader.py --live ``` **Check positions only:** ```bash python copytrading_trader.py --positions ``` **View current config:** ```bash python copytrading_trader.py --config ``` **Override wallets for one run:** ```bash python copytrading_trader.py --wallets 0x123...,0x456... ``` **Full rebalance mode (includes sells):** ```bash python copytrading_trader.py --rebalance ``` **Sell when whales exit positions:** ```bash 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: 1. **Leaderboard tracking**: Check Polymarket leaderboards for consistent performers 2. **Whale watchers**: Follow known profitable traders on social media 3. **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-usd` or reduce `--top-n` to 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 sim` for $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_KEY` is 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](https://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](https://docs.simmer.markets/v2-migration)


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal:  | SkillSpector: suspicious (MEDIUM)