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
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
- `insider.py` — the keeper: z-score/p-value detection, timing/freshness/sizing
signals, and Alchemy funding-cluster ring detection.
- `insider.py` — the detector: z-score/p-value, timing/freshness/sizing signals,
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).
- `archive/` — the strategies that didn't work, kept for reference. See
`archive/README.md`.
+122 -302
View File
@@ -1,328 +1,148 @@
# Polymarket Smart Money
# 🏆 Winning Wallet Finder
Tools to find Polymarket wallets worth following, copy-trade them (paper or
live), and backtest the strategy. Zero dependencies — Python 3 stdlib only
(except live trading, which needs `py-clob-client`).
Find Polymarket wallets with a **real, statistically-verifiable edge**, test
whether copying them actually makes money, and **get pinged the moment they
trade**.
> **Start here — read [What we learned](#what-we-learned-research-log).** The
> project began as "find wallets winning >75% of their bets." That metric turned
> out to be an artifact, and the research below changed what we actually measure.
> Don't fund anything before reading it.
This started as "copy the smart money." Along the way we tested — and ruled out
— six systematic public-data strategies, and found that the *only* signal that
holds up is **statistical improbability**: wallets that win far more than the
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 |
|------|--------------|
| `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). |
## The core idea: z-score, not win rate
## 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.
Adjust the filters (win rate, bets/week, minimum resolved bets, candidate
pool size) and the table updates live while the scan runs. Click any trader
to see their recent resolved bets and a link to their Polymarket profile.
- **z = 0** → you won exactly what your prices implied → no edge.
- **z = 3** → ~1-in-740 by luck. **z = 5** → ~1-in-3.5M. **z = 9** → astronomical.
- **`p(luck)`** is z as a probability: the chance a no-edge bettor does this well by chance.
## 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
python3 smart_money.py --scan # default 150-wallet pool
python3 smart_money.py --scan --pool 300 # broader sweep
Two refinements separate signal from noise:
- **Lifetime trade count** — high z + tens of thousands of trades = a
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
> 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.
## Quickstart
## Copy-trading (`copytrade.py`)
Once you've found wallets worth following, `copytrade.py` watches them and
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.
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
read-only APIs.
```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)
A 7-day backtest of four top wallets returned **48% on deployed capital** —
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
It runs in **PAPER mode by default** and places nothing — it just logs what it
*would* do. Live trading requires **all** of: `"mode": "live"` in the config,
the `--live` flag, typing a confirmation phrase, and `py-clob-client` with
valid credentials. Hard caps (per-trade, daily spend, total exposure, open
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
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
and runtime state never get committed.
## Caveats
- Candidates come from the leaderboards, so this surfaces *profitable* sharps.
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)
The honest story of what the data showed, in order. Each finding killed an
assumption the previous step relied on.
**1. Win rate was an illusion (survivorship bias).**
Polymarket only redeems *winning* shares; losing shares are worth $0 and sit
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.**
`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.**
A 7-day backtest of four "top" wallets returned **48%**. At ~50% entry hit
rates, mirroring entries at flat size just pays the spread on coin flips. A
profitable wallet's edge lives in *sizing and entry prices*, which copying
entries does not reproduce. (The backtest also exposed a missing per-position
cap — proportional adds could balloon one market to the whole exposure limit.)
**5. A reliable edge looks real but rare — and skews young.**
Scanning 1,500 wallets over 120 days: 1,017 had history, 199 passed a
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 --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
```
**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.
### Config (`config.json`, gitignored — holds your secrets)
**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.
```jsonc
{
"discord_webhook": "https://discord.com/api/webhooks/…", // alerts
"alchemy_key": "…", // Polygon RPC for funding-cluster detection
"alchemy_signing_key": "…", // verifies inbound webhook POSTs (live watcher)
"watch": [ {"wallet": "0x…", "name": "Famecesgoal"}, ] // wallets to track
}
```
**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
## Data sources
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.
| Source | Used for |
|--------|----------|
| `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 |
---
## Live watcher — get pinged on every trade
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>`.
1. **Discord webhook** → set `DISCORD_WEBHOOK` env (or `config.json`).
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`.
Keep the two wallet lists in sync: Alchemy's address list (what *triggers*) and
`watch.json` (what *names* the alert).
---
## The honest verdict
- **Detection works.** z-score + timing + funding-cluster reliably surfaces
statistically anomalous wallets (the 60-Minutes use case).
- **Copying them is not proven.** In-sample a weighted, compounding copy
returned +545%; out-of-sample (select pre-May, copy forward) it was +168% —
but driven entirely by *one* longshot lottery wallet, with the strongest
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).
Use this to **find and watch** edge wallets and gather forward data — not as a
green light to bet size on copying them.
+7 -22
View File
@@ -1,24 +1,9 @@
{
"mode": "paper",
"poll_seconds": 12,
"discord_webhook": "",
"watchlist": [],
"bankroll_usd": 1000.0,
"bankroll_pct": 0.02,
"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
}
"discord_webhook": "https://discord.com/api/webhooks/XXXX/YYYY",
"alchemy_key": "your-alchemy-polygon-key",
"alchemy_signing_key": "whsec_from_alchemy_webhook_dashboard",
"watch": [
{"wallet": "0x0b0f92507bbc340762d38eca43eba1e11ee37af1", "name": "Famecesgoal"},
{"wallet": "0xe8c4d68aff65b38cac46987b9b65e01eb47d395d", "name": "JAMJAMJAM4"}
]
}
+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()