mirror of
https://github.com/jaxperro/winning-wallet-finder.git
synced 2026-07-27 15:57:47 +00:00
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:
+53
-2
@@ -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 — 45–77% 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 1–3k 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`.
|
||||||
|
|||||||
@@ -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 (~2–5s), 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 ~4–6 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·(1−P)` 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.
|
|
||||||
|
|||||||
+7
-22
@@ -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
@@ -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
@@ -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()
|
||||||
@@ -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 [Feb1–Apr30] 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()
|
||||||
Reference in New Issue
Block a user