Polymarket Bot Tutorial · Chapter 20 of 32
Track Polymarket whale wallets and copy-trade top performers programmatically: identify profitable wallets via leaderboard and on-chain analysis, mirror their trades with size and timing rules.
What this chapter covers
Copying winning Polymarket wallets is a popular idea, but the actual whales on Polymarket are mostly doing late-window arb on resolved markets, not directional bets. This chapter is the honest research from on-chain analysis: which wallets to actually copy, why most aren't worth copying, and the position-sizing math.
- Identifying profitable wallets
- On-chain trade detection
- Position sizing relative to the whale
- Latency: how late is too late
- Filters: only follow wallets with verified edge
- Code: detect whale buy event, place sized copy
Identifying profitable wallets
The premise of whale-copying is that some wallets are consistently profitable and copying their entries captures part of their edge. On-chain analysis of Polymarket's top wallets in 2025-26 produced a sobering result: most of the visible whales are doing late-window arbitrage on resolved markets, not directional trading.
Profile we measured on three candidate whale wallets:
- "hhhhhh6" (98.5% win rate, $n M volume) - 88% of entries at prices ≥0.95, median entry timestamp 226s of a 300s window. Pure tail-yield arb, not directional.
- "anonymous" (20% win rate) - degenerate gambler. Copying loses money.
- "Jkim123" (53.5% win rate) - coin-flip. Not a signal worth copying.
0% of these whales' trades occurred in the first 120s of any 5-minute window. Predictive signal (if any) would come from EARLY-window large entries - but those wallets aren't the ones at the top of the leaderboard, because they're hard.
On-chain trade detection
Detecting a target wallet's trades requires either polling Polymarket's data-api or subscribing to on-chain CTF transfer events. The data-api option is simpler.
def watch_wallet(wallet_addr, last_seen_ts=0):
while True:
url = f"https://data-api.polymarket.com/activity?user={wallet_addr}&limit=100"
events = requests.get(url).json()
for ev in events:
ts = int(ev.get("timestamp", 0))
if ts <= last_seen_ts: continue
if ev["type"] == "TRADE":
process_whale_trade(ev)
last_seen_ts = max(last_seen_ts, ts)
time.sleep(5)
Five-second polling is the practical floor for the data-api. Lower than that you'll hit rate limits. For sub-second detection, subscribe to on-chain ERC-1155 TransferSingle events from the CTF contract filtered by the whale's proxy address.
Position sizing relative to the whale
If you size your copy as a constant fraction of the whale's trade, you inherit their risk profile. Two practical alternatives.
- Cap-based: size each copy to a fixed dollar amount ($10-50) regardless of whale size. Slow to compound but bounded loss per trade.
- Win-rate-weighted: size copy as a function of the whale's recent win rate. 60%+ WR → full-size copy; 40-60% → half-size; below 40% → skip.
The cap-based approach is the safer first deploy. Move to win-rate-weighted only after you've measured the whale's actual win rate on YOUR copies (which is usually worse than their headline number because you arrive late).
Latency: how late is too late
The whale's trade is publicly visible within 1-2 seconds of execution. Copying it requires faster latency than that on the read side, plus your own order placement latency.
End-to-end for a typical copy bot: 5-10 second polling + 200ms order placement = 5-15 seconds total. By the time your copy fires, the whale's signal is in the price.
For 99% of copies, this is too late on Polymarket's narrow markets. The whale's entry moved the mid 1-2 cents; you pay that 1-2 cents premium relative to where they got in. If their edge was 3c, half of it is already gone by the time you arrive.
Copy bots that work either (a) target slow-moving markets where 30s latency doesn't matter, or (b) use on-chain event subscription to react in sub-second timeframes.
Filters: only follow wallets with verified edge
Three filters before adding any wallet to your copy list.
- 30+ closed trades in the wallet's history. Smaller samples are noise.
- Lifetime win rate > 60%, OR positive expected value per trade based on entry prices. Either condition; both is better.
- Pattern is NOT late-window arb. Check the median seconds-to-resolution at entry; if it's near 0, the wallet is doing tail-yield arb that you can't reproduce.
Most candidate whales fail one of these three. The pool of actually-copyable wallets is small. Maintaining the list requires periodic recheck - wallets that were profitable last month may not be this month.
Code: detect whale buy event, place sized copy
Reference: detect a CTF TransferSingle event for a watched whale, fire a sized copy buy.
from web3 import Web3
w3 = Web3(Web3.WebsocketProvider(POLYGON_WSS))
ctf = w3.eth.contract(address=CTF_ADDR, abi=CTF_ABI)
WHALES = {"0xabc...": {"fraction": 0.05, "cap": 50}}
def on_transfer(event):
to = event.args["to"].lower()
if to not in WHALES: return
cfg = WHALES[to]
token_id = event.args.id
whale_size = event.args.value / 1e6
copy_size = min(cfg["cap"], whale_size * cfg["fraction"])
if copy_size < 5: return # not worth fees
book = fetch_book(str(token_id))
if book.best_ask:
place_fok(str(token_id), "BUY", book.best_ask + 0.01, copy_size)
# subscribe to ERC-1155 TransferSingle events from CTF
filt = ctf.events.TransferSingle.create_filter(fromBlock="latest")
while True:
for ev in filt.get_new_entries(): on_transfer(ev)
time.sleep(0.5)
Distinguish buy from sell by checking from (zero address = mint = the whale bought) vs to (zero address = burn = sold).





