selection: trusted-row layer + holder-gate fix — the holder blind spot

Two data bugs were hiding the best copy targets (FINDINGS 'The holder
blind spot'):

* live/trust.py (new): only score rows whose res_t matches the market's
  consensus resolution time across >=2 wallets, pulled after resolution,
  resolved != False. Kills the res_t=ts fallback poison that let scalpers
  masquerade as 99%-win holders (ArbTraderRookie's rows were 100% this).
* conviction_scan.py: trusted rows only; p80 conviction cutoff from the
  train window only (look-ahead leak); new gates z_all>2 (whole-book z,
  ~doubles pooled forward copy-ROI) and median conviction stake >=$50
  (dust filter). 55 wallets selected, forward 30/38 profitable, +21.4%
  pooled (was 284 selected, +16.0%).
* validate_timing.py: the held-edge gates now read the trailing-90d
  trusted conviction record via trust.conviction_record instead of the
  replay's held leg, which is structurally ~all-unresolved for week-lead
  holders (whale 0x73afc816 showed 'held 0-0, 21 unresolved' and was
  rejected at 100% fwd win). 25 copy-positive holders now, including
  Stavenson (51-0), the whale (112-4) and iohihoo (98-10).
* cache.py: query() helper so trust.py shares the single in-process
  connection instead of fighting the single-writer lock.
* README: gotcha 8 (fake res_t/won) + candidate next data sources
  (Goldsky pipelines, PolymarketData order books, Pinnacle CLV,
  Polysights); FINDINGS: dated section correcting the iohihoo/ArbTrader
  scalper-trap verdicts as winner=False-bug artifacts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jaxperro
2026-07-04 09:08:35 -04:00
parent 4b05db4f4c
commit 1d754353c2
8 changed files with 1691 additions and 3574 deletions
+47
View File
@@ -241,6 +241,53 @@ jaxperro.com/trading) surfaced two things:
**fast-resolving** markets (capital velocity > bet size on $1k), and don't
diversify past what you can fund. Two well-chosen holders beat four that overflow.
## The holder blind spot: two data bugs that hid the best copy targets (2026-07-04)
A clean re-run of the May→June train/test on a trusted subset of the cache
overturned two earlier verdicts. Two mechanisms were poisoning the data:
1. **The `res_t = ts` fallback.** When the data-api omits `endDate` on a closed
position, `insider.resolved_bets` stores the wallet's *sell time* as `res_t`
and `won = curPrice >= 0.5` *at pull time* — so a scalper's sold-at-profit
position looks like a resolved win at a fake resolution time.
ArbTraderRookie's 1,997 cached rows were 100% this. Fix: `live/trust.py`
only trust rows whose `res_t` matches the market's modal `res_t` across ≥2
wallets (endDate rows agree; sell-time rows scatter), market over, wallet
pulled after resolution, `resolved` not False. 13.5M of 19.2M rows pass.
2. **The held-leg window bug.** `validate_timing`'s Jun-1→now replay only
counted held bets entered *and* resolved inside the window; a ~7-day-lead
holder always showed `held 0-0, ~20 unresolved` and failed `held_n>=8`. And
before the 2026-07-02 `winner=False` settle fix, those unresolved held bets
were booked as **losses** — which is exactly where the "scalper trap"
numbers for iohihoo ($749) and ArbTrader ($790) came from. **Those two
verdicts were bug artifacts, not scalper traps.** (ArbTrader still deserved
rejection pre-fix — his *cache* stats were res_t=ts poison — but his real
trade record was a ~160h-lead holder.)
**What the clean test found** (select on ≤May trusted rows only, validate on
June, fees+slip): population baseline 1.4%/bet; the existing profile +8.7%
pooled; adding a **whole-book z gate (`z_all > 2`) roughly doubles it** at
every tier; a practical top-basket (also gated on med conviction stake ≥ $50
and holder/borderline lead verdicts) went **+80% pooled, 7/7 wallets
profitable**. A capital-aware $1k replay of the 8-wallet pre-June basket did
**+504% in June** (118 bets, 97W-21L, fees+slip, 53 missed for cash) — with the
three informed holders a combined 62-0 and two basket members *losing* money
(toosmart 4-12), so the selection is good, not magic.
**Where the edge lives:** the top holders (Stavenson, whale `0x73afc816…` with
$20120k clips, iohihoo; ArbTraderRookie until 2026-07-03) bet **low-tier
tennis (ITF/qualifiers/Wimbledon doubles) and tier-3 esports (CCT CS, Dota 2
EPL)** at ~0.5 entries, win 95100%, enter ~160h before resolution, and hold.
That's informed money — plausibly match-fixing-adjacent — which is copyable
precisely because of the long lead. The regime risk is real and demonstrated:
**ArbTraderRookie was wiped from every data-api endpoint mid-analysis** on
2026-07-03. Treat every month of this edge as possibly its last; re-select
weekly; never size as if the 100% win rates are permanent.
*Lesson: selection metrics are only as honest as the rows they read. Gate on
trusted rows, judge held edges on windows longer than the wallet's lead time,
and add `z_all` — skill must show in the whole book, not just the big bets.*
## Repo layout
- `insider.py` — the detector: z-score/p-value, timing/freshness/sizing signals,
+18
View File
@@ -182,6 +182,15 @@ runner is retired (GitHub throttled `*/5` to ~2h in practice — it copied 1 of
| `clob.polymarket.com` | order books, prices, **authoritative resolution** (`winner` flags), market slugs |
| Alchemy (Polygon) | funding-cluster traces + the live trade webhook |
**Candidate next sources** (researched 2026-07, not yet wired in):
| Source | Would unlock |
|--------|--------------|
| [Goldsky Turbo Pipelines](https://docs.goldsky.com/chains/polymarket) | per-fill order events with timestamps for *every* wallet (Polymarket killed subgraphs with the 2026-04-28 v2 migration) — fixes the cache's two blind spots: no entry times, and position-level aggregation hiding scalps. See also [warproxxx/poly_data](https://github.com/warproxxx/poly_data), [Bitquery](https://docs.bitquery.io/docs/examples/polymarket-api/) |
| [PolymarketData.co](https://www.polymarketdata.co/) | historical order-book snapshots (Aug 2025+) → depth-aware fill model, the known step before sizing up |
| Pinnacle closing lines via [SharpAPI](https://sharpapi.io/sportsbooks/pinnacle-odds-api) / [sportsapis.dev](https://sportsapis.dev/historical-odds) / [BettingIsCool](https://api.bettingiscool.com/) (Pinnacle closed its public API 2025-07) | closing-line-value as an independent "was this bet sharp" ground truth; a Pinnacle *suspension* on an ITF/esports match is itself a fixing signal |
| [Polysights Insider Finder](https://gizmodo.com/tracking-insider-trading-on-polymarket-is-turning-into-a-business-of-its-own-2000709286) | cross-check for flagged insider wallets |
## Gotchas a maintainer must know
1. **CLOB `winner` flags: `false` means "not yet", not "lost".** Every token of
@@ -205,6 +214,15 @@ runner is retired (GitHub throttled `*/5` to ~2h in practice — it copied 1 of
for anything latency-sensitive (it copied 1 of ~104 trades in June).
7. **GitHub Pages soft-limits ~10 deploys/hour** on the `jaxperro` repo —
batch dashboard pushes (see that repo's README).
8. **Cached `res_t`/`won` can be fake for high-volume wallets.** When the
data-api omits `endDate`, `res_t` falls back to the wallet's *sell time*
and `won` is the price direction at pull — a scalper's sold-at-profit
position masquerades as a resolved win (ArbTraderRookie's rows were 100%
this). Selection must read **trusted rows only** via `live/trust.py`
(cross-wallet consensus `res_t` + pulled-after-resolution + `resolved` not
False). Also: never judge a held edge on a replay window shorter than the
wallet's entry→resolution lead — that's how the long-lead holders were
being filtered out (see FINDINGS "The holder blind spot").
---
+9
View File
@@ -191,6 +191,15 @@ def invalidate(wallets):
_con.execute("DELETE FROM pulled WHERE wallet=?", [w])
def query(sql, params=None):
"""Serialized raw read access to the cache DB for sibling modules (trust.py's
trusted-row queries). A second duckdb.connect in the same process would fight
this module's read-write connection for the single-writer lock, so everything
in-process must go through this one connection."""
with _lock:
return _con.execute(sql, params or []).fetchall()
def pulled_ages():
"""{wallet: pulled_at} for every wallet ever pulled — lets collect.py bound
how many stale re-pulls one run takes on."""
+46 -25
View File
@@ -5,21 +5,28 @@ favorite-riding — and it persists out-of-sample.
TRAIN = conviction bets resolved before June 1. TEST = June 1+ resolved.
A "conviction" bet = one in the top 20% (p80) of THAT wallet's own stake sizes,
computed per-wallet over its full cached window — replacing the old flat $200.
Validated to reproduce flat-$200's win-rate lift while adapting to each wallet's
scale (kept in sync with cache.CONV_PCTILE and trading/index.html).
A "conviction" bet = one in the top 20% (p80) of THAT wallet's own stake sizes.
2026-07-03: the p80 cutoff is now computed over TRAIN rows only (the full-window
cutoff let test-period stakes leak into the threshold), and everything reads
TRUSTED rows only (see trust.py — the res_t=ts fallback poison made scalpers
look like 99%-win holders and inflated both train and forward stats).
Profile gates (on TRAIN conviction bets):
* >= MIN_N conviction bets
* win rate >= WIN_MIN
* avg entry in [ENTRY_LO, ENTRY_HI] (excludes 0.9 favorite-riders)
* copy-ROI > 0 and z significant (BH-FDR)
* z_all > Z_ALL_MIN over ALL trusted train bets (whole-book skill — the
single strongest add in the 2026-07-03 May->June tournament: it roughly
doubled pooled forward copy-ROI at every tier)
* median conviction stake >= MIN_MED_STAKE (dust wallets betting $2-$6 clips
aren't followable and their fills aren't reproducible)
Then validate forward and count how many keep the profile.
"""
import math, os, time
import duckdb
import trust
HERE = os.path.dirname(__file__)
JUN1 = time.mktime(time.strptime("2026-06-01", "%Y-%m-%d"))
@@ -29,6 +36,8 @@ WIN_MIN = 0.65
ENTRY_LO, ENTRY_HI = 0.30, 0.75
MIN_TEST = 3
FDR_Q = 0.05
Z_ALL_MIN = 2.0 # whole-book z gate (all trusted train bets, any size)
MIN_MED_STAKE = 50.0 # median train conviction stake floor (dust filter)
def r(p, won): return (1 - p) / p if won else -1.0
@@ -44,33 +53,44 @@ def stats(bets):
def main():
con = duckdb.connect(os.path.join(HERE, "cache.duckdb"), read_only=True)
# per-wallet conviction cutoff = p80 of that wallet's own positive stakes, then
# keep only its bets at/above that cutoff (its top ~20% by size).
# res_t <= now: the cache stores early-sold positions in UNRESOLVED markets with
# a future res_t and won = curPrice at pull time — a mark, not an outcome. They
# were ~5% of the June test window with a 72% pseudo-"win" rate, inflating the
# forward validation; only actually-resolved bets may score.
now = int(time.time())
# TRUSTED rows only (trust.py): consensus res_t kills the res_t=ts fallback
# poison; pulled_at >= E and resolved-is-not-False kill stale price marks.
# Conviction cutoff = p80 of the wallet's TRAIN-window positive stakes (the
# old full-window cutoff leaked test-period stake sizes into selection).
rows = con.execute(
"WITH thr AS (SELECT wallet, quantile_cont(size, ?) AS t "
" FROM bets WHERE size > 0 GROUP BY wallet) "
"SELECT b.wallet, b.p, b.won, b.res_t "
"FROM bets b JOIN thr ON b.wallet = thr.wallet "
"WHERE b.size > 0 AND b.size >= thr.t AND b.res_t <= ?",
[CONV_PCTILE, int(time.time())]).fetchall()
f"WITH {trust.cte(now)}, "
"thr AS (SELECT wallet, quantile_cont(size, ?) AS t "
f" FROM trusted WHERE res_t < {JUN1} GROUP BY wallet) "
"SELECT b.wallet, b.p, b.won, b.res_t, b.size "
"FROM trusted b JOIN thr ON b.wallet = thr.wallet "
"WHERE b.size >= thr.t",
[CONV_PCTILE]).fetchall()
# whole-book skill over ALL trusted train bets (any size) — the z_all gate
allz = dict(con.execute(
f"WITH {trust.cte(now)} "
"SELECT wallet, (sum(won::INT) - sum(least(0.999,greatest(0.001,p)))) "
" / sqrt(greatest(sum(least(0.999,greatest(0.001,p)) "
" * (1 - least(0.999,greatest(0.001,p)))), 1e-9)) "
f"FROM trusted WHERE res_t < {JUN1} GROUP BY wallet").fetchall())
byw = {}
for w, p, won, rt in rows:
byw.setdefault(w, []).append((max(0.001, min(0.999, p or 0)), won, rt or 0))
for w, p, won, rt, sz in rows:
byw.setdefault(w, []).append((max(0.001, min(0.999, p or 0)), won, rt or 0, sz or 0))
cand = []
for w, bets in byw.items():
tr = [(p, won) for p, won, rt in bets if rt < JUN1]
tr = [(p, won) for p, won, rt, _ in bets if rt < JUN1]
if len(tr) < MIN_N:
continue
med_stake = sorted(sz for p, won, rt, sz in bets if rt < JUN1)[len(tr) // 2]
z_all = allz.get(w, 0.0)
n, wins, wr, roi, z, ap = stats(tr)
if wr >= WIN_MIN * 100 and ENTRY_LO <= ap <= ENTRY_HI and roi > 0:
te = [(p, won) for p, won, rt in bets if rt >= JUN1]
if (wr >= WIN_MIN * 100 and ENTRY_LO <= ap <= ENTRY_HI and roi > 0
and z_all > Z_ALL_MIN and med_stake >= MIN_MED_STAKE):
te = [(p, won) for p, won, rt, _ in bets if rt >= JUN1]
tm = stats(te) if len(te) >= MIN_TEST else None
cand.append(dict(w=w, n=n, wr=wr, roi=roi, z=z, ap=ap, tm=tm, ntest=len(te)))
cand.append(dict(w=w, n=n, wr=wr, roi=roi, z=z, ap=ap, tm=tm,
ntest=len(te), z_all=z_all, med_stake=med_stake))
# FDR on the edge p-values
ps = sorted(sf(c["z"]) for c in cand)
@@ -81,9 +101,9 @@ def main():
sel = sorted([c for c in cand if sf(c["z"]) <= thr and thr > 0],
key=lambda c: c["roi"], reverse=True)
print(f"wallets with >= {MIN_N} conviction bets (top {1-CONV_PCTILE:.0%} by stake) pre-June: {len(byw):,} scanned")
print(f"matching the profile (win>= {WIN_MIN:.0%}, entry {ENTRY_LO}-{ENTRY_HI}, "
f"+ROI, FDR-significant): {len(sel)}\n")
print(f"wallets with >= {MIN_N} TRUSTED conviction bets (top {1-CONV_PCTILE:.0%} by train stake) pre-June: {len(byw):,} scanned")
print(f"matching the profile (win>= {WIN_MIN:.0%}, entry {ENTRY_LO}-{ENTRY_HI}, +ROI, "
f"z_all>{Z_ALL_MIN:g}, med stake>=${MIN_MED_STAKE:g}, FDR-significant): {len(sel)}\n")
fwd = [c for c in sel if c["tm"]]
if fwd:
@@ -107,6 +127,7 @@ def main():
import json
json.dump([{"wallet": c["w"], "name": c["w"][:10], "train_win": round(c["wr"], 1),
"train_conv_roi": round(c["roi"], 3), "train_z": round(c["z"], 2),
"z_all": round(c["z_all"], 2), "med_stake": round(c["med_stake"]),
"avg_entry": round(c["ap"], 2), "train_n": c["n"],
"fwd_win": round(c["tm"][2], 1) if c["tm"] else None,
"fwd_conv_roi": round(c["tm"][3], 3) if c["tm"] else None,
+583 -3221
View File
File diff suppressed because it is too large Load Diff
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""Trusted-row filtering for cache.duckdb — the 2026-07-03 data-integrity fix.
Two ways a cached bet row can lie (see FINDINGS.md "The holder blind spot"):
1. res_t = ts fallback: when the data-api omits `endDate` on a closed
position, insider.resolved_bets stores the wallet's SELL time as res_t and
`won = curPrice >= 0.5` at pull time. A scalper's sold-at-profit position
then masquerades as a resolved win at a fake resolution time
(ArbTraderRookie's 1,997 legacy rows were 100% this).
2. stale marks: `won` is only authoritative if the wallet was pulled AFTER
the market resolved; rows pulled earlier carry a price mark, and v2 rows
say so via resolved=False, but legacy rows can't.
The fix is cross-wallet consensus: endDate-based rows for one market agree on
the same res_t across every wallet, while ts-fallback rows scatter (each
wallet's sell time is its own). So a row is TRUSTED iff
* its res_t equals the market's modal res_t across >= 2 distinct wallets (E)
* E <= now (the market is actually over)
* the wallet's pulled_at >= E (won observed after resolution -> 0/1)
* resolved IS DISTINCT FROM FALSE (v2 mark rows out)
~13.5M of 19.2M rows pass; what's dropped is exactly the poison that made
scalpers look like 99%-win holders. Selection must only ever score trusted rows.
This module has NO cache.py import (so read-only scripts that open their own
connection can use it without a second in-process connection fighting the
single-writer lock). Callers pass a `runq(sql, params) -> rows` callable.
"""
import time
# CTE fragments: prepend inside `WITH ...` and select from `trusted`.
# {now} must be substituted with an int epoch.
TRUSTED_CTE = """
tr_r AS (SELECT DISTINCT wallet, cond, asset, won, p, res_t, size, src, ts, resolved
FROM bets WHERE res_t > 0 AND size > 0),
tr_cons AS (SELECT cond, res_t AS E FROM (
SELECT cond, res_t, count(DISTINCT wallet) nw,
row_number() OVER (PARTITION BY cond
ORDER BY count(DISTINCT wallet) DESC, count(*) DESC) rn
FROM tr_r GROUP BY cond, res_t) WHERE rn = 1 AND nw >= 2),
trusted AS (
SELECT tr_r.* FROM tr_r
JOIN tr_cons ON tr_r.cond = tr_cons.cond AND tr_r.res_t = tr_cons.E
JOIN pulled pl ON pl.wallet = tr_r.wallet
WHERE tr_cons.E <= {now} AND pl.pulled_at >= tr_cons.E
AND (tr_r.resolved IS DISTINCT FROM FALSE))
"""
def cte(now=None):
"""The trusted-rows CTE body with {now} filled in."""
return TRUSTED_CTE.format(now=int(now or time.time()))
def ensure_cons(runq, now=None):
"""Materialize the consensus map once per connection as TEMP TABLE t_cons
(cond, E) so repeated per-wallet queries don't re-scan 19M rows. Temp
tables are allowed on read-only connections."""
have = runq("SELECT count(*) FROM information_schema.tables "
"WHERE table_name = 't_cons'", [])
if have and have[0][0]:
return
runq(f"""CREATE TEMP TABLE t_cons AS
WITH r AS (SELECT DISTINCT wallet, cond, res_t
FROM bets WHERE res_t > 0 AND size > 0)
SELECT cond, res_t AS E FROM (
SELECT cond, res_t, count(DISTINCT wallet) nw,
row_number() OVER (PARTITION BY cond
ORDER BY count(DISTINCT wallet) DESC, count(*) DESC) rn
FROM r GROUP BY cond, res_t) WHERE rn = 1 AND nw >= 2
AND res_t <= {int(now or time.time())}""", [])
def trusted_wallet_rows(runq, wallet, now=None):
"""This wallet's trusted resolved bets as (cond, won, p, res_t, size),
deduped per token. Requires ensure_cons() first."""
now = int(now or time.time())
return runq("""
SELECT DISTINCT b.cond, b.asset, b.won,
least(0.999, greatest(0.001, b.p)) p, b.res_t, b.size
FROM bets b
JOIN t_cons c ON b.cond = c.cond AND b.res_t = c.E
JOIN pulled pl ON pl.wallet = b.wallet
WHERE b.wallet = ? AND b.size > 0 AND c.E <= ?
AND pl.pulled_at >= c.E AND (b.resolved IS DISTINCT FROM FALSE)""",
[wallet, now])
def conviction_record(runq, wallet, days=90, pctile=0.80, now=None):
"""Trailing trusted CONVICTION record for the held-edge gate:
{n, wr, roi} over the wallet's top-(1-pctile) stake bets resolved in the
last `days`. The conviction cutoff is that wallet's stake p80 over its FULL
trusted history (matching cache.conv_cutoff semantics). roi is the flat-
stake hold-to-resolution copy ROI per bet, fee/slip-free (gates compare it
to 0, and fees are already charged in copy_pnl, the other selection leg)."""
now = int(now or time.time())
rows = trusted_wallet_rows(runq, wallet, now)
if not rows:
return dict(n=0, wr=0.0, roi=0.0)
sizes = sorted(r[5] for r in rows)
k = (len(sizes) - 1) * pctile
f = int(k)
thr = sizes[f] if f + 1 >= len(sizes) else sizes[f] + (sizes[f + 1] - sizes[f]) * (k - f)
cut = now - days * 86400
# one bet per market: keep the largest-stake token row
best = {}
for cond, asset, won, p, res_t, size in rows:
if size >= thr and res_t >= cut:
if cond not in best or size > best[cond][2]:
best[cond] = (won, p, size)
conv = list(best.values())
if not conv:
return dict(n=0, wr=0.0, roi=0.0)
wins = sum(1 for won, _, _ in conv if won)
roi = sum(((1 - p) / p if won else -1.0) for won, p, _ in conv) / len(conv)
return dict(n=len(conv), wr=wins / len(conv), roi=roi)
+39 -12
View File
@@ -13,6 +13,19 @@ conviction wallet and SELECT on copyability directly —
This keeps Kruto (sells often but profitably) and surfaces copy-positive holders
the lead gate used to discard; it drops scalper-traps like a wallet that's only
positive via sells while its held bets lose.
2026-07-03 holder fix: the held-edge gates no longer use the replay's held leg.
That leg only counts bets entered AND resolved inside the Jun-1->now window, so
a ~7-day-lead holder always showed `held 0-0, ~20 unresolved` and failed
held_n>=8 the filter structurally rejected the most copyable wallets (whale
0x73afc816: 100% fwd win in conviction_scan, "held 0-0" here; and pre-Jul-2 the
winner=False bug booked those unresolved bets as LOSSES, which is where the
iohihoo $749 / ArbTrader $790 "scalper trap" numbers came from). The held-edge
gate now reads the wallet's trailing TRUSTED conviction record from the cache
(trust.py: consensus-resolution rows, outcome observed post-resolution), which
includes bets entered before the window that resolved inside it. The replay's
copy_pnl (fees, mirror exits) remains the other selection leg, and held stats
are still computed for display.
"""
import json
@@ -25,6 +38,7 @@ from concurrent.futures import ThreadPoolExecutor
import cache
import smart_money as sm
import trust
HERE = os.path.dirname(__file__)
COPYABLE_MED_LEAD = 24.0 # median lead (h) on winning conviction bets to count as copyable
@@ -190,10 +204,12 @@ def lead_profile(w):
return dict(n=len(leads), med=med, u6=u6, verdict=verdict)
MIN_HELD = 8 # need this many resolved held conviction bets to trust the held edge
MIN_HELD_WR = 0.55 # held bets must WIN a clear majority — excludes longshot-variance
MIN_HELD = 8 # need this many trailing trusted conviction bets to trust the held edge
MIN_HELD_WR = 0.55 # they must WIN a clear majority — excludes longshot-variance
# players (+EV but ~34% win) that don't fit the high-win-rate thesis
MIN_LEAD_H = 1.0 # light sniper guard: drop wallets whose median winning lead < 1h
TRUST_DAYS = 90 # trailing window for the trusted conviction record (long enough
# that week-lead holders have real resolved sample in it)
def main():
@@ -219,6 +235,7 @@ def main():
with ThreadPoolExecutor(max_workers=8) as ex:
stats = list(ex.map(safe_stats, conv))
trust.ensure_cons(cache.query)
cut30 = time.time() - 30 * 86400
sharps = []
for c, ds in zip(conv, stats):
@@ -229,27 +246,37 @@ def main():
c["name"] = ds["name"]
lp = lead_profile(c["wallet"])
c["med_lead_h"] = round(lp["med"], 1) if lp else None
held_n = ds["held_won"] + ds["held_lost"]
held_wr = ds["held_won"] / held_n if held_n else 0
# SELECT a copyable sharp: active, copy-positive, and a genuine hold-to-
# resolution edge — held leg positive AND winning a clear majority on a real
# sample, so the edge survives live latency and isn't longshot variance or
# all sell-timing. A light lead floor drops true sub-hour snipers.
# held-to-resolution edge from the trailing TRUSTED cache record — includes
# bets entered before the replay window that resolved inside it, so long-lead
# holders are judged on their real resolved sample (the replay's own held leg
# is mostly "unresolved" for them and only reported for display).
tr = trust.conviction_record(cache.query, c["wallet"], days=TRUST_DAYS,
pctile=cache.CONV_PCTILE)
c["trust_n"], c["trust_wr"], c["trust_roi"] = tr["n"], round(tr["wr"], 3), round(tr["roi"], 3)
# SELECT a copyable sharp: active, copy-positive (fee-aware replay), and a
# genuine hold-to-resolution edge — trailing trusted conviction record wins a
# clear majority with positive flat-stake ROI on a real sample, so the edge
# survives live latency and isn't longshot variance or all sell-timing. A
# light lead floor drops true sub-hour snipers.
if ((ds["last_trade"] or 0) >= cut30 and ds["copy_pnl"] > 0
and ds["held_pnl"] > 0 and held_n >= MIN_HELD and held_wr >= MIN_HELD_WR
and tr["n"] >= MIN_HELD and tr["wr"] >= MIN_HELD_WR and tr["roi"] > 0
and (c["med_lead_h"] is None or c["med_lead_h"] >= MIN_LEAD_H)):
sharps.append(c)
sharps.sort(key=lambda c: c["copy_pnl"], reverse=True)
print(f"copy-positive holders (copy>0, held>0, held_n>={MIN_HELD}, active, lead>={MIN_LEAD_H}h): "
print(f"copy-positive holders (copy>0, trust_n>={MIN_HELD}, trust_wr>={MIN_HELD_WR:.0%}, "
f"trust_roi>0 over {TRUST_DAYS}d, active, lead>={MIN_LEAD_H}h): "
f"{len(sharps)} of {len(conv)}\n")
h = f"{'copyP&L':>8}{'heldP&L':>8}{'held':>9}{'sold%':>6}{'medLeadH':>9} wallet"
h = (f"{'copyP&L':>8}{'trustRec':>10}{'trustROI':>9}{'heldP&L':>8}{'held':>9}"
f"{'sold%':>6}{'medLeadH':>9} wallet")
print(h); print("-" * len(h))
for c in sharps[:35]:
n = c["held_won"] + c["held_lost"]
sp = 100 * c["sold"] / (c["sold"] + n) if (c["sold"] + n) else 0
ld = f"{c['med_lead_h']:.0f}" if c["med_lead_h"] is not None else ""
print(f"{c['copy_pnl']:>+8}{c['held_pnl']:>+8}{(str(c['held_won'])+'-'+str(c['held_lost'])):>9}"
rec = f"{round(c['trust_wr']*c['trust_n'])}-{round((1-c['trust_wr'])*c['trust_n'])}"
print(f"{c['copy_pnl']:>+8}{rec:>10}{c['trust_roi']:>+9.0%}{c['held_pnl']:>+8}"
f"{(str(c['held_won'])+'-'+str(c['held_lost'])):>9}"
f"{sp:>5.0f}%{ld:>9} {(c.get('name') or c['wallet'][:10])}")
json.dump(sharps, open(os.path.join(HERE, "watch_sharps.json"), "w"), indent=2)
+831 -316
View File
File diff suppressed because it is too large Load Diff