Rebrand to Winning Wallet Finder; commit validation scripts; dev-friendly docs

- Add copyback.py (in-sample copy backtest), oos.py (out-of-sample test),
  huntwide.py (wide insider sweep) — completes the detect→hunt→validate→watch
  pipeline.
- Rewrite README around Winning Wallet Finder: the z-score idea explained, how
  the pieces fit, quickstart, data sources, live-watcher setup, honest verdict.
- Extend FINDINGS with the insider-detection results and the in-sample vs
  out-of-sample copy verdict (+545% in-sample collapsed to one-wallet variance
  out-of-sample).
- Refresh config.example.json to the current schema (discord/alchemy/watch).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jaxperro
2026-06-13 16:59:18 -04:00
parent 2a30975325
commit f176ba134a
6 changed files with 632 additions and 319 deletions
+53 -2
View File
@@ -68,10 +68,61 @@ so shared exchanges don't false-link everyone. (See `insider.py`.)
material nonpublic information is illegal, and blindly following a suspected material nonpublic information is illegal, and blindly following a suspected
insider is not a safe strategy. insider is not a safe strategy.
## Insider detection — what the z-score signal actually found
Building `insider.py` and sweeping markets (`hunt.py`, `huntwide.py`) surfaced
genuinely improbable wallets. Out of ~289 scored:
- **DREAMBIG.** (z=8.9, p≈2e-19) and **qcp14** (z=5.3) on the Iran ceasefire
market — 4577% of wins entered <24h before resolution. Textbook insider
fingerprint, on exactly the theme the *60 Minutes* story covered.
- **Famecesgoal** (z=9.6) won only 14.5% of bets — but bet longshots and hit
+98 above what the odds implied. The clearest "beats the prices it pays" case.
Two refinements proved essential:
- **Trade count separates insiders from bots.** `bjprolo` scored z=37 — but on
**306,873** lifetime trades. That's a market-maker grinding a tiny systematic
edge, not information. Real edge wallets show concentrated z over 13k trades.
- **Funding-cluster linking** (Alchemy, the Bubblemaps "who-funded-whom" step)
works *only* with a personal-hub filter: a shared exchange (everyone uses
Coinbase) is not a shared operator. Judge a funder by its own outbound degree.
## The copy-trade verdict — in-sample vs out-of-sample
The decisive test: does copying z-selected wallets make money?
- **In-sample** (`copyback.py`): copy the edge wallets from May 30, z-weighted,
reinvest 100%. Result: **+545%** in 15 days. Looks incredible — and it's
circular (the wallets were *selected* for winning over that very window).
86% of it came from one wallet; the highest-z pick contributed $23.
- **Out-of-sample** (`oos.py`): select wallets using **only data through
Apr 30**, then copy forward May 30→now. Result: **+168%** — but **entirely
from one longshot lottery wallet** (1.5% pre-period win rate hitting again).
The two strongest pre-period signals made **$0** forward. Forward hit rate was
27%. That's variance, not edge that persists.
**Conclusion:** even the one real signal (z-score), when tested for whether you
can *profit by copying it*, fails out-of-sample — joining every other strategy.
The detector is valuable for *finding* anomalous wallets; copying them is not a
proven, fundable edge. The live watcher (`webhook_receiver.py`) exists to gather
real forward (out-of-sample) data on these wallets — observe before you size up.
## Practical conclusion 2
- **Don't** fund a copy strategy — both the +545% and +168% are
variance/concentration, not repeatable edge.
- **Do** use the detector to find statistically anomalous wallets and watch them
live; judge persistence forward with your own eyes.
- A durable trading edge has to come from *you* (a niche you know), with this
tooling built around your judgment.
## Repo layout ## Repo layout
- `insider.py` — the keeper: z-score/p-value detection, timing/freshness/sizing - `insider.py` — the detector: z-score/p-value, timing/freshness/sizing signals,
signals, and Alchemy funding-cluster ring detection. Alchemy funding-cluster ring detection.
- `hunt.py` / `huntwide.py` — market sweeps that surface edge wallets.
- `copyback.py` / `oos.py` — in-sample and out-of-sample copy-trade backtests.
- `webhook_receiver.py` — push-based live trade watcher (Alchemy → Discord).
- `smart_money.py` — data foundation + dashboard (true-win-rate scanner). - `smart_money.py` — data foundation + dashboard (true-win-rate scanner).
- `archive/` — the strategies that didn't work, kept for reference. See - `archive/` — the strategies that didn't work, kept for reference. See
`archive/README.md`. `archive/README.md`.
+114 -294
View File
@@ -1,328 +1,148 @@
# Polymarket Smart Money # 🏆 Winning Wallet Finder
Tools to find Polymarket wallets worth following, copy-trade them (paper or Find Polymarket wallets with a **real, statistically-verifiable edge**, test
live), and backtest the strategy. Zero dependencies — Python 3 stdlib only whether copying them actually makes money, and **get pinged the moment they
(except live trading, which needs `py-clob-client`). trade**.
> **Start here — read [What we learned](#what-we-learned-research-log).** The This started as "copy the smart money." Along the way we tested — and ruled out
> project began as "find wallets winning >75% of their bets." That metric turned — six systematic public-data strategies, and found that the *only* signal that
> out to be an artifact, and the research below changed what we actually measure. holds up is **statistical improbability**: wallets that win far more than the
> Don't fund anything before reading it. prices they paid imply. This repo is the tooling for finding and watching those
wallets, plus an honest record of everything that didn't work.
## Tools > **Read [`FINDINGS.md`](FINDINGS.md) for the full story.** TL;DR: detection of
> edge wallets works; *profitably copying them* is unproven (it survived a naive
> backtest but collapsed to one-wallet variance out-of-sample). Treat this as a
> research + monitoring tool, not a money printer.
**Read [`FINDINGS.md`](FINDINGS.md) first** — it's the honest scorecard of what ---
worked (almost nothing) and what does (`insider.py`).
| File | What it does | ## The core idea: z-score, not win rate
|------|--------------|
| `insider.py` | **The keeper.** Insider/sharp detector — z-score/p-value of wins vs. entry odds, pre-resolution timing, fresh-wallet & sizing signals, and Alchemy funding-cluster ring detection. |
| `smart_money.py` | Data foundation + dashboard. Ranks leaderboard wallets by **true** (survivorship-corrected) win rate. |
| `archive/` | Eight strategy tools that didn't clear (copy-trade, LP farming, cross-venue arb, wallet-consistency research). Kept for reference — see [`archive/README.md`](archive/README.md). |
## Run the dashboard Every Polymarket bet has an entry price that *is* the market's estimate of its
odds (a YES at 30¢ ⇒ market thinks 30%). If you have no edge, over many bets
you win about the **sum of your entry prices** — call it *expected wins*.
```bash ```
python3 smart_money.py z = (actual wins expected wins) / standard deviation
``` ```
Open **http://localhost:8899**, hit **Scan**, and wait a minute or two. - **z = 0** → you won exactly what your prices implied → no edge.
Adjust the filters (win rate, bets/week, minimum resolved bets, candidate - **z = 3** → ~1-in-740 by luck. **z = 5** → ~1-in-3.5M. **z = 9** → astronomical.
pool size) and the table updates live while the scan runs. Click any trader - **`p(luck)`** is z as a probability: the chance a no-edge bettor does this well by chance.
to see their recent resolved bets and a link to their Polymarket profile.
## Run in the terminal Why this beats win rate: a wallet that bets longshots and **wins 14% when the
odds implied 8%** has a huge edge (high z) despite a low win rate. A wallet
buying 90¢ favorites and winning 90% has z≈0 — no edge, just paying for
favorites. **z measures beating the prices you paid.**
```bash Two refinements separate signal from noise:
python3 smart_money.py --scan # default 150-wallet pool - **Lifetime trade count** — high z + tens of thousands of trades = a
python3 smart_money.py --scan --pool 300 # broader sweep market-maker bot, not an insider. Real edge wallets have *concentrated* edge
over a few thousand trades.
- **Pre-resolution timing + fresh wallet** — entering minutes/hours before
resolution on a new account is the insider fingerprint (the Bubblemaps /
*60 Minutes* pattern).
---
## How the pieces fit
```
data layer detection hunting validation live
────────── ───────── ─────── ────────── ────
smart_money.py ──▶ insider.py ──▶ hunt.py / huntwide.py ─▶ copyback.py / oos.py webhook_receiver.py
(Polymarket API, (z-score, timing, (sweep markets, (does copying them (Alchemy webhook →
true win rate) freshness, funding surface edge wallets) actually pay? in- & Discord ping on
clustering) out-of-sample) every trade)
``` ```
## How it works | File | Role |
|------|------|
| `insider.py` | **The detector.** z-score/p-value, pre-resolution timing, fresh-wallet & sizing flags, and Alchemy funding-cluster ring detection. `--scan` / `--market` / `--wallet`. |
| `smart_money.py` | Data foundation + dashboard. Survivorship-corrected **true** win rate. |
| `hunt.py` | Ring-hunt sweep over a fixed list of news-driven event markets. |
| `huntwide.py` | Wide sweep — source wallets from ~100 markets, score each, tier by z. |
| `copyback.py` | Backtest: copy edge wallets' entries from a date, weighted, compounding. |
| `oos.py` | **Out-of-sample test** — select wallets on pre-period data, copy forward. The honesty gate. |
| `webhook_receiver.py` | Push-based live watcher: Alchemy on-chain webhook → enrich → Discord. |
| `archive/` | The six strategies that didn't work, kept for reference ([details](archive/README.md)). |
1. **Candidates** — pulls the 7d, 30d, and all-time leaderboards from ---
`data-api.polymarket.com/v1/leaderboard` and dedupes into a candidate pool
(default 150 wallets).
2. **True win rate** — over resolved bets in the last 90 days. This is the
subtle part: Polymarket only redeems *winning* shares, so `/closed-positions`
(redeemed or sold) is heavily **survivorship-biased toward winners**
losing shares are worth $0 and sit unredeemed in `/positions` at
`curPrice 0` forever. A correct win rate has to **union both** endpoints
over the same window. Counting only `/closed-positions` (the naive approach)
reports ~90% for wallets whose real hit rate is ~50%.
3. **Frequency** — counts trades from `/activity` over the last 4 weeks;
*bets/week* is the number of **distinct markets** traded per week, so 50
fills on one order don't count as 50 bets.
4. **Filter** — keeps wallets with win rate ≥ 75%, ≥ 2 bets/week, and ≥ 10
resolved bets.
> **Reality check:** with the unbiased metric, essentially **no** top wallet ## Quickstart
> wins 75% of its bets — true rates cluster around **49% (a coin flip)**, max
> ~60%. The profitable ones make money through position sizing and entry
> prices, *not* hit rate. Treat a high win rate as a red flag for a bias bug,
> not a green light. See the backtest below.
## Copy-trading (`copytrade.py`) Zero dependencies — Python 3 stdlib only (no `pip install`). macOS python.org
builds lack CA certs, so the code falls back to unverified SSL for these public
Once you've found wallets worth following, `copytrade.py` watches them and read-only APIs.
mirrors their trades onto your own account.
- **Sizing** — each fresh entry stakes a fixed **% of your bankroll** (default 2%).
- **Mirror** — copies **entries and exits**: when they add, it adds
proportionally; when they sell part of a position, it sells the same
fraction of yours.
- **Price guard** — skips a copy if the market has moved **>5%** from their
fill price, so you don't chase.
- **No backfill** — only copies positions they open *after* you start
watching; positions they already held are tracked (so exits still mirror)
but never opened.
- **Per-position cap** — `max_position_usd` caps total cost in any one market.
Without it, proportional adds let a single position balloon toward your whole
exposure limit as the whale piles in (a backtest caught exactly this).
- **Discord alerts** — set `discord_webhook` in the config to get a ping on
every trade it would place (entries green, exits red).
## Backtesting (`backtest.py`)
Replays a watchlist's real trades over a recent window through the same copy
logic, filling at each wallet's actual historical price and marking outcomes
from how markets resolved.
```bash ```bash
python3 backtest.py --days 7 git clone https://github.com/jaxperro/winning-wallet-finder
cd winning-wallet-finder
cp config.example.json config.json # then edit (see Config below)
python3 insider.py --scan 40 # score the top-40 leaderboard wallets
python3 insider.py --market <slug> # score a market's traders + detect rings
python3 insider.py --wallet 0xABC… # deep-profile one wallet
python3 huntwide.py # wide sweep → huntwide.csv (tiered by z)
python3 oos.py # the out-of-sample copy test
python3 smart_money.py # dashboard at http://localhost:8899
``` ```
A 7-day backtest of four top wallets returned **48% on deployed capital** — ### Config (`config.json`, gitignored — holds your secrets)
not because the engine is broken, but because the wallets' true entry hit rate
is ~50% and flat-size copying pays the spread on every coin flip. Copying a
profitable wallet's *entries* does not reproduce its edge, which lives in
sizing and entry prices. Backtest before you fund anything.
### ⚠️ Real money — read this ```jsonc
{
It runs in **PAPER mode by default** and places nothing — it just logs what it "discord_webhook": "https://discord.com/api/webhooks/…", // alerts
*would* do. Live trading requires **all** of: `"mode": "live"` in the config, "alchemy_key": "…", // Polygon RPC for funding-cluster detection
the `--live` flag, typing a confirmation phrase, and `py-clob-client` with "alchemy_signing_key": "…", // verifies inbound webhook POSTs (live watcher)
valid credentials. Hard caps (per-trade, daily spend, total exposure, open "watch": [ {"wallet": "0x…", "name": "Famecesgoal"}, ] // wallets to track
positions, price bounds) apply in both modes. In live mode this places real }
orders with real money on your account — you own the config and the outcomes.
```bash
python3 copytrade.py --init # write config.example.json
cp config.example.json config.json
# ... edit config.json: add wallets to "watchlist", set bankroll & caps ...
python3 copytrade.py # PAPER mode — safe, logs only
python3 copytrade.py --once # single polling pass, then exit
``` ```
Going live (only after you trust the paper output): ---
```bash ## Data sources
pip install py-clob-client
# set "mode": "live" and fill in the "live" block (private_key, funder_address)
python3 copytrade.py --live # prompts for a typed confirmation
```
`config.json` and `copytrade_state.json` are gitignored so your credentials | Source | Used for |
and runtime state never get committed. |--------|----------|
| `data-api.polymarket.com` | positions, trades, leaderboard, activity, true win rate |
| `gamma-api.polymarket.com` | market metadata, resolution times, best bid/ask |
| `clob.polymarket.com` | order books, prices, liquidity-reward configs |
| `api.elections.kalshi.com` | Kalshi prices (cross-venue arb research) |
| Alchemy (Polygon) | on-chain USDC funding traces + the live trade webhook |
## Caveats ---
- Candidates come from the leaderboards, so this surfaces *profitable* sharps. ## Live watcher — get pinged on every trade
A high-win-rate wallet that has never cracked any leaderboard window won't
appear — scanning every wallet on the platform isn't feasible via the
public API.
- Win rate is measured over resolved bets in the last 90 days, not all history.
- **Win rate ≠ EV.** Wallets with positive all-time leaderboard PnL routinely
show ~50% true win rates and even negative 90-day realized PnL. Following a
wallet profitably is about *how* it sizes and prices entries, not how often
it's right. The `realized_pnl` column is position-level over 90 days and is
**not** comparable to the all-time leaderboard figure.
- Very high-volume / market-maker wallets (thousands of fills) can't be cleanly
backtested via the public API — too many fills, no historical position
snapshot.
## What we learned (research log) Push-based (no polling). The instant a tracked wallet's proxy transacts on
Polygon (~25s), Alchemy POSTs `webhook_receiver.py`, which enriches the trade
via the data-API and pings Discord: `🟢 Famecesgoal BUY Yes @ 0.34 ($120) — <market>`.
The honest story of what the data showed, in order. Each finding killed an 1. **Discord webhook** → set `DISCORD_WEBHOOK` env (or `config.json`).
assumption the previous step relied on. 2. **Deploy** the receiver to an always-on host (Railway / Fly / a $5 VPS — *not*
Render free, it sleeps). `Procfile` included; binds to `$PORT`.
3. **Alchemy** → create an *Address Activity* webhook (Polygon mainnet), add the
`watch.json` addresses, point it at `https://your-host/alchemy`, and set the
signing key as `ALCHEMY_SIGNING_KEY`.
**1. Win rate was an illusion (survivorship bias).** Keep the two wallet lists in sync: Alchemy's address list (what *triggers*) and
Polymarket only redeems *winning* shares; losing shares are worth $0 and sit `watch.json` (what *names* the alert).
unredeemed in `/positions` at `curPrice 0` forever, never entering
`/closed-positions`. Measuring win rate over `/closed-positions` alone counts
almost only winners. Examples: NiNo999 read 90.6%, true rate **48.3%**; Boggs
read 73.4%, true **50.3%**. Fixed by unioning both endpoints over a window.
**Takeaway: a high reported win rate is a red flag for a bias bug, not a sharp.**
**2. With the honest metric, nobody wins 75%.** ---
Across 25 top wallets, true win rates clustered at a **median 49%** (coin flip),
max ~60%. Zero passed a 75% bar. Win rate is the wrong thing to rank on.
**3. Win rate ≠ profit; raw PnL ≠ reliability.** ## The honest verdict
`surfandturf` won 54.8% and made millions; `Latina` (leaderboard #1 all-time)
won 43% and was **$3.8M over 90 days**. And wallets with big total PnL often
got there on one or two outlier weeks (38% green weeks) — a lottery, not an
edge. The signal that finds reliable money is **weekly consistency**: % of weeks
green, profit factor, weekly Sharpe — measured per week, with enough weeks.
**4. Flat-size copy-trading is EV.** - **Detection works.** z-score + timing + funding-cluster reliably surfaces
A 7-day backtest of four "top" wallets returned **48%**. At ~50% entry hit statistically anomalous wallets (the 60-Minutes use case).
rates, mirroring entries at flat size just pays the spread on coin flips. A - **Copying them is not proven.** In-sample a weighted, compounding copy
profitable wallet's edge lives in *sizing and entry prices*, which copying returned +545%; out-of-sample (select pre-May, copy forward) it was +168% —
entries does not reproduce. (The backtest also exposed a missing per-position but driven entirely by *one* longshot lottery wallet, with the strongest
cap — proportional adds could balloon one market to the whole exposure limit.) signals contributing nothing. That's variance, not a durable, fundable edge.
- **No turnkey public-data edge survived** — copy-trading, win-rate ranking, LP
reward farming, binary/multi-outcome arb, and cross-venue arb all came back
efficient or illusory. See [`FINDINGS.md`](FINDINGS.md).
**5. A reliable edge looks real but rare — and skews young.** Use this to **find and watch** edge wallets and gather forward data — not as a
Scanning 1,500 wallets over 120 days: 1,017 had history, 199 passed a green light to bet size on copying them.
consistency screen, **77 were copyable** (hold-to-resolution ≥70%). But ~7.5%
of wallets passing by chance is exactly what randomness produces over 1,017
coin-flippers — so some of the 77 are luck. Worse, a 240-day lookback showed
the "best" wallets are **young accounts** (surfandturf's oldest bet: 72 days;
joblessfinalboss: 79). New accounts that get hot rise to the leaderboard and
pass the screen; the ones that flamed out are delisted. **The most impressive
short-term performers are the least trustworthy.**
**6. ROI and size are inversely related.**
Among the 77 copyable wallets, the highest-ROI ones bet small (dnte: 57% ROI on
$109K), while the biggest bettors scalp thin edges (elmcap2: $114M staked,
**0.4%** ROI). `surfandturf` was the lone anomaly — big *and* high-ROI ($27.9M
staked at 16%) — which makes it either the best find or the biggest variance
story. At 72 days old, we can't yet tell.
### Where this leaves the strategy
- **Rank on risk-adjusted consistency** (% green weeks × profit factor ×
Sharpe), never win rate or raw PnL.
- **Require account longevity** — distrust anything under ~46 months.
- **Validate out-of-sample** (walk-forward: select on an early window, measure a
later one) before trusting any wallet list. This is the decisive open step.
- **Copying entries ≠ copying edge.** A working strategy likely needs to model
sizing/pricing, or pivot to a consensus signal (bet where many vetted wallets
agree) rather than blind mirroring.
## Liquidity rewards (the market-making pivot)
After copy-trading proved unreliable, the research pointed to **liquidity
rewards** as the lowest-risk edge. Polymarket pays makers a daily USDC pool for
resting limit orders near a market's midpoint — your share = your score ÷ total
score, where score rewards size and closeness to mid (quadratic:
`((max_spread your_spread) / max_spread)²`). ~$200K/day is distributed across
~8,000 eligible markets (queryable via the CLOB `sampling-markets` endpoint;
each market exposes `rewards.rates[].rewards_daily_rate`, `min_size`,
`max_spread`).
`lp_screener.py` ranks those markets by **risk-adjusted** yield — reward pool ÷
order-book competition near mid (gross APR for a $1000 two-sided position),
penalized by 24h midpoint volatility (the adverse-selection proxy) and by
time-to-resolution (imminent = live = toxic).
```bash
python3 lp_screener.py --min-rate 50 --capital 1000 # one-shot snapshot → lp_markets.csv
```
**It's a one-shot snapshot, not a daemon** — reward pools, books, and the
markets themselves churn daily, so re-run before each session.
**What it found:** the sweet spot is **long-dated, low-volatility prop markets**
(World Cup player props, eliminations) — thin books, decent pools, vol ~0, days
to resolution. Live esports markets show astronomical gross APR but get
correctly de-ranked: that's where you get picked off.
**Caveats that still gate real money:** headline APRs are a *snapshot* — thin
pools attract competitors and yield mean-reverts down; they're *gross*, ignoring
inventory losses when you get filled; and we have not yet confirmed near-empty
books actually pay the full pool.
`lp_paper.py` is the decisive test — no money, no host, no key. It picks the
screener's top low-vol markets, simulates two-sided quotes against the **live**
order book, and tracks **net = rewards accrued adverse-selection bleed**:
```bash
python3 lp_paper.py --capital 1000 --markets 6 --poll 20 # runs until stopped
```
Fills are modeled when the midpoint crosses a resting quote (deliberately a bit
pessimistic on fill rate); rewards accrue by score-share of each pool. Net P&L,
per-market breakdown, and Discord summaries let it run for days to see whether
the edge survives mean-reversion. **Only if net stays clearly positive does a
real, funded, hosted bot make sense.**
## Cross-venue arbitrage: Polymarket ↔ Kalshi (`xarb.py`)
The last relative-value lane: buy YES on one venue + NO on the other for < $1
(net of fees) = locked profit. Kalshi's public API (`api.elections.kalshi.com`)
exposes ~65k markets; `xarb.py` pulls both venues, matches the same event
(token overlap + same resolution month + **exact numeric match** on
thresholds/scores/dates so we compare the same *contract*, not just the same
event), and computes both arb directions with Kalshi's `0.07·P·(1P)` taker fee.
**Verdict: efficient — no retail arb.** On liquid, cleanly-matched, identical
contracts the two venues agree to **~1¢**, and locking both sides costs **>$1
after fees.** Worked example (live): *Brazil vs Morocco — Both Teams To Score*
priced PM 0.46/0.47 vs Kalshi 0.47/0.48; every arb direction nets **negative**.
The large "edges" the scanner surfaces are artifacts: false matches (same event,
different sub-question), illiquid wide-spread markets (exact-score, props), or
stale snapshot timing. Matches the documented reality that real gaps last
~seconds and are taken by bots watching 10k+ markets.
## Insider / sharp detection (`insider.py`) — the one real signal
After the 2026 *60 Minutes* / WSJ coverage of Polymarket insider trading (a firm,
Bubblemaps, found 9 anonymous wallets that won ~$2.4M at a 98% rate on Iran-war
dates), `insider.py` replicates the *per-wallet* detection methodology on the
public data API:
- **Improbability (the core signal):** each bet entered at price `p` has an
odds-implied win prob `p`. Winning far more than `Σp` is a z-score and
one-sided p-value — the rigorous "luck can't explain this." This is the
*correct* version of the edge metric the whole project was chasing: beating
the market's own pricing, not raw win-rate (biased) or PnL (variance).
- **Pre-resolution timing** — median hours before resolution they entered; share
of wins entered <24h out (advance-knowledge tell).
- **Fresh wallet** (`/traded` count) and **sizing** — the insider fingerprint.
- **Scoring is gated by improbability:** a wallet winning at/below its odds
scores 0 no matter how it's timed or sized (kills the sports-bettor confound,
where entering <24h before a game is normal, not suspicious).
```bash
python3 insider.py --scan 40 # score top leaderboard wallets
python3 insider.py --market <conditionId|slug># score everyone who traded a market (Bubblemaps approach)
python3 insider.py --wallet 0xABC… # deep-profile one wallet
```
**Findings:** the all-time leaderboard holds *no* extreme insiders (max z≈2.3) —
those are high-volume sharps, not info-traders. Scanning a *market's* traders
surfaces the real signal: e.g. `arimnestos` at **z=4.0, p≈3e-5** over 2,205 bets
— a demonstrable edge. Distinguishing **sharp** (high z, normal timing) from
**insider** (high z + late entry + fresh wallet) is the timing/freshness combo.
**Funding-cluster linking (the Bubblemaps step) — implemented.** With an
`alchemy_key` in `config.json`, the scanner pulls each wallet's USDC funding
history (`alchemy_getAssetTransfers`, full history, no block-range cap) and
links wallets that share a funder — i.e. likely the same operator. The catch
that makes or breaks this: a *shared exchange* (everyone withdraws from
Coinbase) is not a shared operator. So a candidate funder only counts as a link
if **its own outbound degree is small** (a personal hub sends to ≤15 wallets; an
exchange/bridge fans out to hundreds). Without that filter the method
false-flags everyone — our 10 "independent" watchlist wallets all shared 11
infra funders and looked like one ring until the degree filter correctly
cleared them. Funded-from-a-major-exchange wallets can't be de-anonymized this
way — a known limit the pro firms hit too.
**Why this matters:** the z-score over many bets is the first metric in this
project that identifies a *real, hard-to-fake* edge. A high-z wallet has beaten
the market's own prices repeatedly — a far better "who to study/follow" signal
than the leaderboard. (Caveat: *trading* on material nonpublic info is illegal —
detecting it is fine; blindly following a suspected insider is not a free pass.)
### The bottom line across the whole project
Six systematic, public-data edges tested — copy-trading, win-rate ranking, LP
reward farming, binary arb, multi-outcome logical arb, and cross-venue arb —
**all efficient or illusory.** Polymarket in 2026 does not hand a retail bot a
turnkey edge. Durable edge requires *speed/infra* (competing with pro arb bots),
*genuine private information* (a niche you know better than the market), or
*getting paid to provide a service* (liquidity, at modest adverse-selection-
dominated yields). The most valuable output here is knowing that before funding
any of it.
+8 -23
View File
@@ -1,24 +1,9 @@
{ {
"mode": "paper", "discord_webhook": "https://discord.com/api/webhooks/XXXX/YYYY",
"poll_seconds": 12, "alchemy_key": "your-alchemy-polygon-key",
"discord_webhook": "", "alchemy_signing_key": "whsec_from_alchemy_webhook_dashboard",
"watchlist": [], "watch": [
"bankroll_usd": 1000.0, {"wallet": "0x0b0f92507bbc340762d38eca43eba1e11ee37af1", "name": "Famecesgoal"},
"bankroll_pct": 0.02, {"wallet": "0xe8c4d68aff65b38cac46987b9b65e01eb47d395d", "name": "JAMJAMJAM4"}
"price_guard_pct": 0.05, ]
"risk": { }
"max_trade_usd": 50.0,
"max_position_usd": 40.0,
"daily_spend_cap_usd": 250.0,
"max_total_exposure_usd": 500.0,
"max_open_positions": 20,
"min_price": 0.05,
"max_price": 0.95,
"min_order_usd": 5.0
},
"live": {
"private_key": "",
"funder_address": "",
"signature_type": 1
}
}
+176
View File
@@ -0,0 +1,176 @@
#!/usr/bin/env python3
"""Backtest: copy the z-validated edge wallets' fresh entries from a start date,
weighted by edge strength, reinvesting 100% of profits (compounding).
Honest model:
- We copy each wallet's FIRST buy of a market entered on/after the start date,
filling at THEIR entry price (optimistic — ignores the seconds-to-minutes
lag you'd really face; that's the next thing to test forward).
- Outcome = how that market resolved (curPrice 1 won / 0 lost), or current
price if still open (marked to market).
- Sizing: stake = wallet_weight * BET_K * current_bankroll, capped by cash.
Bankroll = cash + open-position cost, so profits compound into bet size.
"""
import time
from collections import defaultdict
import smart_money as sm
START = "2026-05-30"
BET_K = 0.10 # aggressiveness: top-weight wallet risks ~weight*K of bankroll/bet
START_BANKROLL = 1000.0
# z-validated edge wallets (z-proportional weights — reward edge strength)
EDGE = [
("Famecesgoal", "0x0b0f92507bbc340762d38eca43eba1e11ee37af1", 9.6),
("JAMJAMJAM4", "0xe8c4d68aff65b38cac46987b9b65e01eb47d395d", 5.7),
("Domerina", "0xf4ad6aedf5475f2023912cd191eff5ec90ead00b", 5.3),
("MyLastStand", "0x419f32f30814030554f5df3e9f508ad7394ce853", 4.2),
]
def _parse(d):
return time.mktime(time.strptime(d.replace("Z", "")[:19],
"%Y-%m-%dT%H:%M:%S" if "T" in d else "%Y-%m-%d")) if d else 0
def entries_after(wallet, cutoff):
"""Earliest BUY (price, ts) per asset, for buys on/after cutoff."""
out = {}
off = 0
while off < 4000:
page = sm.get_json("/activity", {"user": wallet, "type": "TRADE",
"limit": 500, "offset": off})
if not page:
break
for t in page:
if t.get("side") == "BUY" and t.get("asset") and t.get("timestamp", 0) >= cutoff:
a = t["asset"]
if a not in out or t["timestamp"] < out[a][1]:
out[a] = (t.get("price", 0), t["timestamp"], t.get("title", "?")[:40])
off += 500
if len(page) < 500 or page[-1].get("timestamp", 0) < cutoff:
break
return out
def outcomes(wallet):
"""asset -> (curPrice, endTs) from open + closed positions."""
o = {}
for ep in ("/positions", "/closed-positions"):
off = 0
while off < 2000:
params = {"user": wallet, "limit": 50, "offset": off}
if "closed" in ep:
params.update(sortBy="TIMESTAMP", sortDirection="DESC")
else:
params["sizeThreshold"] = 0.0
page = sm.get_json(ep, params)
if not page:
break
for p in page:
if p.get("asset"):
o.setdefault(p["asset"], (p.get("curPrice", 0), _parse(p.get("endDate", ""))))
off += 50
if len(page) < 50:
break
return o
def main():
cutoff = _parse(START)
now = time.time()
tot_z = sum(z for _, _, z in EDGE)
weights = {w: z / tot_z for _, w, z in EDGE}
names = {w: n for n, w, z in EDGE}
print(f"Copy-trade backtest from {START} · start ${START_BANKROLL:,.0f} · "
f"reinvest 100% · BET_K={BET_K}")
print("weights (z-proportional):")
for n, w, z in EDGE:
print(f" {n:14} z={z:>4} weight={weights[w]*100:>4.1f}%")
# gather all copied bets
bets = []
for n, w, z in EDGE:
ent = entries_after(w, cutoff)
outc = outcomes(w)
for a, (price, ts, title) in ent.items():
if a not in outc or not (0 < price < 1):
continue
cur, end = outc[a]
resolved = end and end < now
bets.append({"w": w, "name": n, "ts": ts, "price": price,
"mark": cur, "res_ts": end or now, "resolved": bool(resolved),
"title": title})
print(f"\ncopied bets entered since {START}: {len(bets)}")
# discrete-event sim: enter at entry ts, free capital as bets resolve
events = []
for i, b in enumerate(bets):
events.append((b["ts"], 0, i)) # 0=enter
if b["resolved"]:
events.append((b["res_ts"], 1, i)) # 1=resolve
events.sort()
cash = START_BANKROLL
open_cost = 0.0
pos = {} # i -> (shares, stake)
wins = losses = skipped = 0
realized_pnl = 0.0
by_wallet = defaultdict(float)
for ts, kind, i in events:
b = bets[i]
if kind == 0: # ENTER
bankroll = cash + open_cost
stake = min(weights[b["w"]] * BET_K * bankroll, cash)
if stake < 1:
skipped += 1
continue
shares = stake / b["price"]
pos[i] = (shares, stake)
cash -= stake
open_cost += stake
else: # RESOLVE
if i not in pos:
continue
shares, stake = pos.pop(i)
payout = shares * (1 if b["mark"] >= 0.5 else 0)
cash += payout
open_cost -= stake
realized_pnl += payout - stake
by_wallet[b["name"]] += payout - stake
if b["mark"] >= 0.5:
wins += 1
else:
losses += 1
# mark any still-open copied bets to current price
open_val = 0.0
for i, (shares, stake) in pos.items():
mark = bets[i]["mark"]
open_val += shares * mark
by_wallet[bets[i]["name"]] += shares * mark - stake
equity = cash + open_val
unreal = open_val - open_cost
print(f"\n{'='*64}")
print(f" resolved copied bets: {wins+losses} ({wins}W / {losses}L"
f"{f' · {wins/(wins+losses)*100:.0f}% hit' if wins+losses else ''})")
print(f" still open (marked to market): {len(pos)} · skipped (no cash): {skipped}")
print(f"\n REALIZED P&L (locked, resolved bets only): ${realized_pnl:+,.2f}"
f" -> {realized_pnl/START_BANKROLL*100:+.1f}%")
print(f" UNREALIZED (open positions marked to current price): ${unreal:+,.2f}"
f" -> {unreal/START_BANKROLL*100:+.1f}%")
print(f" ending equity: ${equity:,.2f} ({(equity/START_BANKROLL-1)*100:+.1f}% "
f"over {(now-cutoff)/86400:.0f}d) — but {open_val/equity*100:.0f}% of it is UNREALIZED")
print(f"{'='*64}")
print(" P&L by wallet:")
for n, _, _ in EDGE:
print(f" {n:14} {by_wallet[n]:+,.2f}")
if __name__ == "__main__":
main()
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""Wide insider hunt — source candidate wallets from many markets across the
volume spectrum, dedup, score each wallet's full record once, and tally the
insider-grade ones (z-score / p-value) with the markets they showed up in."""
import csv
import json
import ssl
import urllib.request
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor, as_completed
import insider
ctx = ssl._create_unverified_context()
GAMMA = "https://gamma-api.polymarket.com"
N_MARKETS = 100 # markets to source traders from
TOP_TRADERS = 20 # top traders (by notional) per market
MAX_WALLETS = 400 # cap on unique wallets to score (expensive step)
def get(u):
req = urllib.request.Request(u, headers={"User-Agent": "Mozilla/5.0"})
return json.loads(urllib.request.urlopen(req, timeout=30, context=ctx).read())
def source_markets():
"""A spread of active binary markets: high-volume AND mid/niche (where
insiders operate). Walk the volume ranking and sample across it."""
mkts, offset = [], 0
for _ in range(8):
try:
g = get(f"{GAMMA}/markets?limit=100&offset={offset}&active=true"
f"&closed=false&order=volume24hr&ascending=false")
except Exception:
break
if not g:
break
for m in g:
try:
oc = json.loads(m.get("outcomes", "[]"))
except Exception:
oc = []
if [o.lower() for o in oc] == ["yes", "no"] and m.get("conditionId"):
mkts.append((m["conditionId"], m.get("question", "?")[:48]))
if len(g) < 100:
break
offset += 100
# sample evenly across the ranking so we get a volume spread, not just whales
if len(mkts) > N_MARKETS:
step = len(mkts) / N_MARKETS
mkts = [mkts[int(i * step)] for i in range(N_MARKETS)]
return mkts
def main():
key = insider.sm_load_key()
print(f"sourcing traders from up to {N_MARKETS} markets...", flush=True)
markets = source_markets()
print(f" {len(markets)} binary markets", flush=True)
wallet_markets = defaultdict(set)
wallet_name = {}
def grab(cm):
cond, title = cm
try:
cands, _ = insider.market_traders(cond, top=TOP_TRADERS)
return title, cands
except Exception:
return title, []
with ThreadPoolExecutor(max_workers=12) as ex:
for title, cands in ex.map(grab, markets):
for c in cands:
wallet_markets[c["wallet"]].add(title)
wallet_name.setdefault(c["wallet"], c["username"])
print(f" {len(wallet_markets)} unique candidate wallets", flush=True)
# prioritize wallets seen in more markets (more active → enough resolved bets)
wallets = sorted(wallet_markets, key=lambda w: len(wallet_markets[w]), reverse=True)[:MAX_WALLETS]
print(f"scoring {len(wallets)} wallets...", flush=True)
rows, done = [], 0
with ThreadPoolExecutor(max_workers=12) as ex:
futs = {ex.submit(insider.analyze, {"wallet": w, "username": wallet_name[w]}): w
for w in wallets}
for f in as_completed(futs):
done += 1
try:
r = f.result()
except Exception:
r = None
if r:
r["markets"] = sorted(wallet_markets[r["wallet"]])
rows.append(r)
if done % 50 == 0:
print(f" {done}/{len(wallets)}", flush=True)
rows.sort(key=lambda r: r["z"], reverse=True)
with open("huntwide.csv", "w", newline="") as fp:
w = csv.writer(fp)
w.writerow(["z", "pval", "wins", "n", "pre24_pct", "trades", "username", "wallet", "markets"])
for r in rows:
w.writerow([r["z"], r["pval"], r["wins"], r["n"], r["pre24_pct"],
r["trades"], r["username"], r["wallet"], " | ".join(r["markets"][:6])])
extra = [r for r in rows if r["z"] >= 5]
grade = [r for r in rows if 4 <= r["z"] < 5]
sharp = [r for r in rows if 3 <= r["z"] < 4]
print(f"\n{'='*80}")
print(f"SCORED {len(rows)} wallets with >=15 resolved bets")
print(f" extraordinary (z>=5): {len(extra)} insider-grade (z 4-5): {len(grade)} "
f"strong sharp (z 3-4): {len(sharp)}")
print(f"{'='*80}")
for label, group in (("EXTRAORDINARY (z>=5)", extra), ("INSIDER-GRADE (z 4-5)", grade)):
if not group:
continue
print(f"\n{label}:")
for r in group:
print(f" z={r['z']:>4} p={insider.fmt_p(r['pval']):>8} {r['wins']}/{r['n']} "
f"pre24={r['pre24_pct']:>3}% trades={r['trades']:>6} {r['username'][:20]}")
print(f" markets: {', '.join(r['markets'][:4])}")
print(f"\nfull table → huntwide.csv")
if __name__ == "__main__":
main()
+153
View File
@@ -0,0 +1,153 @@
#!/usr/bin/env python3
"""Out-of-sample test of the copy-the-edge-wallets strategy.
SELECT wallets using only resolved bets in [Feb 1 - Apr 30] (z-score), with NO
knowledge of May-June. Then COPY those wallets' entries forward from May 30,
compounding. If the forward return is strong, the edge is real; if it collapses,
the in-sample +545% was selection theater.
"""
import math
import time
from collections import defaultdict
import smart_money as sm
from insider import resolved_bets, norm_sf
from copyback import entries_after, outcomes, _parse, BET_K, START_BANKROLL
SEL_T0 = _parse("2026-02-01") # selection window start (resolution time)
SEL_T1 = _parse("2026-04-30") # selection window end — nothing after this is seen
TEST_START = _parse("2026-05-30") # copy entries on/after this
Z_PICK = 4.0 # insider-grade cut, judged AS OF Apr 30
MIN_BETS = 15
def score_pre(wallet):
"""z-score over resolved bets in the selection window only."""
bets = [b for b in resolved_bets(wallet, SEL_T0 - 10 * 86400)
if SEL_T0 <= b["res_t"] <= SEL_T1]
if len(bets) < MIN_BETS:
return None
wins = sum(1 for b in bets if b["won"])
exp = sum(b["p"] for b in bets)
var = sum(b["p"] * (1 - b["p"]) for b in bets) or 1e-9
z = (wins - exp) / math.sqrt(var)
return {"wallet": wallet, "n": len(bets), "wins": wins, "z": z, "pval": norm_sf(z)}
def candidate_pool():
import csv
seen = {}
try:
for r in csv.DictReader(open("huntwide.csv")):
seen[r["wallet"]] = r["username"]
except FileNotFoundError:
pass
return seen
def main():
from concurrent.futures import ThreadPoolExecutor, as_completed
pool = candidate_pool()
print(f"candidate pool: {len(pool)} wallets · scoring on [Feb1Apr30] only...", flush=True)
selected = []
with ThreadPoolExecutor(max_workers=12) as ex:
futs = {ex.submit(score_pre, w): w for w in pool}
done = 0
for f in as_completed(futs):
done += 1
try:
r = f.result()
except Exception:
r = None
if r and r["z"] >= Z_PICK:
r["name"] = pool[r["wallet"]]
selected.append(r)
if done % 50 == 0:
print(f" {done}/{len(pool)}", flush=True)
selected.sort(key=lambda r: r["z"], reverse=True)
print(f"\nINSIDER-GRADE AS OF APR 30 (z>={Z_PICK}): {len(selected)} wallets")
for r in selected:
print(f" {r['name'][:18]:18} z={r['z']:>4.1f} p={r['pval']:.1e} "
f"{r['wins']}/{r['n']} (pre-period)")
if not selected:
print("\nNo wallets were insider-grade as of Apr 30 — the edge wallets are "
"too new to have a pre-period track record. That itself is the answer.")
return
# forward copy from May 30, z(pre)-weighted, compounding
tot_z = sum(r["z"] for r in selected)
weights = {r["wallet"]: r["z"] / tot_z for r in selected}
names = {r["wallet"]: r["name"] for r in selected}
print(f"\ncopying {len(selected)} wallets forward from 2026-05-30 "
f"(z-pre weighted, compounding)...", flush=True)
bets = []
now = time.time()
for r in selected:
w = r["wallet"]
ent = entries_after(w, TEST_START)
outc = outcomes(w)
for a, (price, ts, title) in ent.items():
if a not in outc or not (0 < price < 1):
continue
cur, end = outc[a]
bets.append({"w": w, "ts": ts, "price": price, "mark": cur,
"res_ts": end or now, "resolved": bool(end and end < now)})
print(f"forward copied bets: {len(bets)}", flush=True)
events = []
for i, b in enumerate(bets):
events.append((b["ts"], 0, i))
if b["resolved"]:
events.append((b["res_ts"], 1, i))
events.sort()
cash = START_BANKROLL
open_cost = 0.0
posn = {}
wins = losses = 0
realized = 0.0
bw = defaultdict(float)
for ts, kind, i in events:
b = bets[i]
if kind == 0:
bankroll = cash + open_cost
stake = min(weights[b["w"]] * BET_K * bankroll, cash)
if stake < 1:
continue
posn[i] = (stake / b["price"], stake)
cash -= stake
open_cost += stake
else:
if i not in posn:
continue
shares, stake = posn.pop(i)
payout = shares * (1 if b["mark"] >= 0.5 else 0)
cash += payout
open_cost -= stake
realized += payout - stake
bw[names[b["w"]]] += payout - stake
wins += b["mark"] >= 0.5
losses += b["mark"] < 0.5
open_val = sum(sh * bets[i]["mark"] for i, (sh, st) in posn.items())
equity = cash + open_val
print(f"\n{'='*64}")
print(f" OUT-OF-SAMPLE forward result (selection knew nothing past Apr 30)")
print(f" resolved: {wins+losses} ({wins}W/{losses}L"
f"{f' · {wins/(wins+losses)*100:.0f}%' if wins+losses else ''}) "
f"· open: {len(posn)}")
print(f" realized P&L: ${realized:+,.2f} ({realized/START_BANKROLL*100:+.1f}%)")
print(f" unrealized: ${open_val-open_cost:+,.2f}")
print(f" ── ending equity ${equity:,.2f} -> {(equity/START_BANKROLL-1)*100:+.1f}% "
f"on $1,000 over {(now-TEST_START)/86400:.0f}d")
print(f"{'='*64}")
for n in sorted(bw, key=lambda k: bw[k], reverse=True):
print(f" {n[:18]:18} {bw[n]:+,.2f}")
if __name__ == "__main__":
main()