Commit Graph

27 Commits

Author SHA1 Message Date
jaxperro 57cdbde395 audit fixes: pin-mirror to backtest, unpinned-floor warning, watch_sharps diff, class_pct guard
From the automated-writer clobber audit (3-agent workflow):
- sync_floors now ACTUALLY mirrors floor_pin into backtest.json (the comment
  claimed it; the code never did — so a paper pin diverged from the backtest's
  dynamic p80, defeating calibration). Kruto 80 now consistent across all 3.
- sync_floors warns loudly when about to revert an UNPINNED floor that looks
  hand-edited (>5 and >15%) — floor_pin is the only protected key, so a direct
  edit used to vanish silently at 08:00.
- validate_timing logs the sharps add/drop delta and documents watch_sharps.json
  as generated-only (format kept a plain list — dashboard/discord read it so).
- daily.sh warns if copybot.paper.json vs backtest.json class_pct desync.

Audit verdict otherwise CLEAN: sync_floors is the only pipeline step that
mutates a manual-knob file, and it round-trips the whole JSON touching only
floor — all other knobs (caps, guard, depth_gate, follow set, band) survive.
config.live.example.json is untouched by every automated writer (safe by
neglect). No second floor_pin-class bug exists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 17:11:17 -04:00
jaxperro 7bb165536b sharps: refund band 1e-4 — 0.005 mislabeled ~50c sells as refunds
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 16:04:54 -04:00
jaxperro 5602429144 sharps: split SOLD out of the record — W/L = held to resolution only
rtally now uses the same redeem-vs-sell price discriminator as the
backtest (exit_p ≈ payout ⇒ held; mid print ⇒ SOLD). The sign-only
tally counted a mirror-seller's exits as wins — 0xb0E43B read '94.4%
all-time' on 937 sells with almost no held outcomes. Win% is held-only
now, sold P&L stays in the P&L columns, all_sold/conv_sold/conv30_sold
populate for the dashboard's +NS record chips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 15:52:44 -04:00
jaxperro b5508b521b sharps: _open_split folds by the data-api redeemable flag, not price-pinning
Audit of the two biggest All-Time-vs-PM divergences (oliman2, leegunner)
proved the fold-in RIGHT and PM's leaderboard wrong: live per-position
sums reproduce our numbers to the dollar (oliman2 +181,594 closed
-160,644 abandoned = ~+19k true vs leaderboard 112k; leegunner +323,707
-58,762 = ~274k true vs 347k). redeemable is exact (set on resolution
for winners AND losers); pinning misfolded a few unresolved longshots.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 11:59:36 -04:00
jaxperro c6ca8ad82c P&L: fold abandoned losers into realized (anti-survivorship) — All-Time is the truth, not PM
Resolved-but-unredeemed positions (bets that lost, went to $0, never
redeemed) were sitting in Open P&L, breaking the All-Time+Open=PM reconcile
and — worse — hiding real losses. _open_split now routes decided positions
(curPrice pinned 0/1) into the REALIZED total at their outcome, leaving only
genuinely in-flight positions in Open P&L. This is the project's founding
survivorship insight applied to P&L: PM /profit under-counts abandoned
losers unevenly, so where our All-Time reads lower than PM, PM is biased and
ours is honest. oliman2 $181k->$20k (walked from $161k of losers),
JuiceFarm $380k->$32k; clean wallets unchanged (ewww1 $409k, KBO30 ~PM).
Win% now counts abandoned losers too. Verified: Coteykens/imwalkinghere/KBO30
now ~PM; oliman2/JuiceFarm correctly far below their flattering PM.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 21:19:07 -04:00
jaxperro 6fd2e30752 sharps: add Open P&L (unrealized open-book exposure) column
_open_pnl(w) sums cashPnl over the wallet's current positions (full paging).
Surfaces the sells-winners-holds-losers risk that the realized track record
hides: All-Time (realized) + Open P&L reconciles to PM P&L. A wallet with a
strong realized record but a large negative open book flatters live-follow
experience — now visible instead of inferred from the PM gap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:42:55 -04:00
jaxperro 2395024a8f P&L mirrors PM: All-Time/Conv P&L = realized track record (realizedPnl)
The sharps table's P&L was reconstructed from won×entry×size and diverged
from PM /profit by up to 10x, with sign flips — four root causes found by
decomposing outliers against PM:
  1. bets pull capped at 2000 rows/180d -> high-volume wallets truncated
     (ewww1: 740 of 4088 positions cached -> k shown vs k real)
  2. both-sides (hedged) markets: one-per-market dedup dropped the paired
     side (suraxy: -k of dropped losing legs -> overcount)
  3. initialValue=0 on big longshot winners -> mis-sized reconstruction
  4. corrupt near-epoch res_t rows polluting sums

Fix: All-Time/Conv/30d P&L are now the sum of Polymarket's OWN realizedPnl
per closed position over FULL history (cache.closed_exits, extended to store
realized_pnl; smart_money.closed_exits captures it + relaxes the avg/tb
guard). This is the wallet's realized track record — what a copier mirroring
buy/sell/hold banks — and sums to PM. Immune to all four bugs (each asset is
its own realized row; no size/entry/res_t needed). Win% = share of closed
positions that made money. Verified vs PM: ewww1 1.00x, KBO30 0.0x->0.97x,
suraxy 2.7x->1.03x, sign flip fixed; oliman2 1.63x is correct-by-design
(realized track record vs PM's open-book unrealized marks). README gotcha 11
rewritten; dashboard tooltips reframed off the copy-ceiling language.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:28:09 -04:00
jaxperro fb462b7168 exits: fix the 50-row page cap + incremental exits cache in duckdb
/closed-positions serves 50-row pages regardless of limit; stepping by the
requested size truncated every wallet's exit history to its most recent ~50
closes (1% coverage for hyperactive wallets) — sold-mirroring was silently
falling back to the hold-to-res ceiling almost everywhere. Now: page by
returned size, cache exits incrementally in duckdb (immutable events —
deep backfill once, a page or two per refresh; title/outcome columns for
display). Deep-exit 30d replay: $18,270 (+1727%), 259W/79L/29R/284S,
misses 253 -> 1 (mirrored exits recycle capital as fast as the signal does).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 07:27:30 -04:00
jaxperro 72245e4053 sharps stats mirror exits too (SOLD) — one exit model across all three books
smart_money.closed_exits() is now the shared implementation (backtest +
sharps): close time from /closed-positions, exit price reconstructed from
realized P&L. validate_timing's conv/conv30/all-time/realized tallies count
a bet the wallet sold pre-resolution at its exit price (conv_sold /
conv30_sold / all_sold fields) instead of pretending it rode to resolution.
Beyond the ~4000-row data horizon exits fall back to hold-to-res.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 03:18:41 -04:00
jaxperro 4941818d51 scoring: chain-truth resolutions overlay — refunds no longer count as wins
The cache's won (curPrice>=0.5 at pull) counts 50/50 refunds as wins for
BOTH sides — 521 of 2,128 chain-checked follow-set markets (24%) were
refunds, which is where the whales' 92-100% displayed win rates came from.

- live/payouts.py: resolutions table in cache.duckdb, filled from the CTF
  contract's payout vectors (batched+paced JSON-RPC, ~12 calls/s free tier;
  resolved rows immutable, unresolved recheck 6h, RPC failures never cached).
  truth(cond, asset) -> 1/0/0.5/None; refunds need no asset side.
- validate_timing: every displayed stat (conv/conv30/all-time/realized/copy
  replay) settles at truth; refunds count as neither W nor L, P&L is
  size*(wp-p)/p; new conv_ref/conv30_ref/all_ref feed fields.
- trust.conviction_record: optional truthfn — the selection gates
  (trust_wr/trust_roi) no longer select on refund inflation.
- portfolio.py: replay pays wp (refunds 0.5/share, was 1.0).
- conviction_scan: documented as the (refund-inflated) candidate layer;
  final selection re-judges against truth downstream.

Validation: 0x4bFb-whale conv 174-16 91.6% $1.61M -> 34-16 +140ref 68%
$214k, and truth-adjusted all-time P&L now sits within ~16% of lb-api's
PM P&L (was 7x apart); LSB1 (0 refunds) byte-identical, its P&L matches
PM P&L to 0.07%.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 01:55:50 -04:00
jaxperro 21acc957f9 sharps feed: pm_pnl (Polymarket's own account P&L) + README gotcha 10 — ceiling vs bank statement
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:17:12 -04:00
jaxperro cb4a33279a sharps feed: all-time trusted stats (every bet); bot feed: wallet classes
* validate_timing.display_stats adds all_win/all_won/all_lost/all_pnl —
  the wallet's record and own P&L over EVERY resolved bet ever cached,
  trusted rows only, deduped one-per-market (feeds the dashboard's new
  All-Time columns); consensus temp table now built before the pool
* copybot write_feed adds classes {name: volume|whale} so the dashboard
  can badge whale-class wallets

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 22:43:56 -04:00
jaxperro 6d02cbb40e Revert "sharps: 30-day activity floor — >=50 trusted conviction bets in the last 30d"
This reverts commit d4596c7a28.
2026-07-04 18:57:08 -04:00
jaxperro d4596c7a28 sharps: 30-day activity floor — >=50 trusted conviction bets in the last 30d
The list is for wallets a copier gets real flow from; dormant or
low-frequency edges (however good) don't belong on it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 18:50:04 -04:00
jaxperro 1d754353c2 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>
2026-07-04 09:08:35 -04:00
jaxperro 6e09e7bb71 CRITICAL settle fix: CLOB winner=False means unresolved, not lost
The CLOB reports winner=False on EVERY token of an unresolved market; only a
True winner anywhere means resolved. resolution_price treated False as "lost",
so the 60s poller settled live in-play positions as instant losses 1-3 min
after entry (June's ~2h Actions cadence masked it - markets had genuinely
resolved by the next check). Same semantics fixed in _market's resolved-cache
gate and validate_timing._clob_winner (unresolved held bets were counted as
losses, biasing copy_pnl selection downward).

Also: _record_lag now AGGREGATES fills per token (ADD used to overwrite the
display record, showing one fill's cost against the whole position's P&L -
the Cost $20.84 vs P&L -$57.30 confusion).

State repaired: the four July bets all actually WON on-chain (verified by
token winner flags) - settled correctly, +$225.40 proceeds restored, book
$1,045.19 (+45.19 realized, 4W/0L).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 23:15:27 -04:00
jaxperro 29a4bcca9e harden pipeline against dropped connections; publish fee-aware sharps
- get_json: catch OSError + http.client.HTTPException (RemoteDisconnected/
  IncompleteRead escaped the retry loop and killed last night's validate_timing
  run on a single dropped keep-alive).
- validate_timing: per-wallet retry+exclude guard so one bad wallet can't take
  out the whole selection; regenerated watch_sharps.json — the first fee-aware,
  resolved-only sharp list (10 copy-positive holders of 254).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 09:15:42 -04:00
jaxperro 08f87c589e paper-run realism: taker fees, resolved-only scoring, bounded collect, local runner
- copybot: model Polymarket taker fees (V2, since 2026-03-30: shares*rate*p*(1-p),
  sports 0.03) on every paper/live fill; track fees_paid in state + feed; settle
  P&L nets the entry fee. publish_feed now commits state + fills ledger with the
  feed (autostash rebase) so the local poller is the sole state writer.
- validate_timing: copy_pnl/held_pnl are fee-aware -> sharp selection now requires
  clearing real copy costs; conv stats + lead profile use resolved bets only.
- conviction_scan/skill: exclude res_t>now rows (early-sold positions in
  unresolved markets scored at curPrice - a mark, not an outcome; was ~5% of the
  June test window at a 72% pseudo-win rate).
- portfolio: skip unresolved rows (stake used to vanish from equity); missed bets
  of kind=open no longer KeyError - mark-to-market hypothetical P&L.
- collect: cap stale refreshes at STALE_CAP=2500/run (bulk-aged pool turned the
  daily refresh into a ~40h pull holding the DuckDB lock).
- Actions cron disabled: GitHub ran */5 every ~1.5-2.5h and the 10-min stale
  window skipped the rest -> 1 of ~104 qualifying buys copied. launchd --poll 60
  is now the runner; workflow stays as manual backstop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-01 23:15:14 -06:00
jaxperro 4b9488be9d live: select sharps by copyability, not lead time (surfaces new holders)
Replace the blunt 24h lead gate with a copy-replay selection run on EVERY
conviction wallet: keep only those that are copy-positive AND have a
genuine hold-to-resolution edge (held_pnl>0, held win-rate>=55% over >=8
resolved held bets), active in 30d, with a light >=1h lead floor vs true
snipers. display_stats now splits copy P&L into scalp vs held legs and
uses fresh-positions resolution (clob fallback) so it's cheap enough to
run on all ~229 wallets.

Effect: drops scalper-traps whose win% looked great but lose copied
(ArbTrader, iohihoo, S888, ttj01) and the old lead gate's discards;
surfaces fast-resolving copy-positive holders it used to throw away
(raid3r, 0x6d1A94f4, 0xec1d07e5, mantaray, earthisgood, shisan...).
14 copy-positive holders now in watch_sharps.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 13:31:41 -06:00
jaxperro 66f4229e3d live: add authoritative Copy P&L to the sharp feed
display_stats now computes copy_pnl: what a flat-$50 copier of a wallet's
conviction bets ACTUALLY realizes since Jun 1 — replays their entries,
mirrors their exits, and settles held bets at AUTHORITATIVE clob
resolution (winner by token_id). This is the truth for copyability and
exposes scalpers whose position win% looks great but lose when copied
(ArbTrader 99.5% conv win but -$793 copy; iohihoo 88.7% but -$749).

Position win%/record/P&L stay on the cache (large 180d sample). Added a
clob resolver (_clob_winner) + activity-replay; only Kruto2027 (+1184),
S888, oliman2, fortuneking (+430) etc. are positive to copy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 12:13:42 -06:00
jaxperro 8a07d0d365 live: drop sharps inactive >30d from the feed
validate_timing now filters the sharp list to wallets that have traded in
the last 30 days (last_trade from the /activity pull), so watch_sharps.json
only lists currently-active sharps — not just hidden client-side. Dropped
10 of 41 on this run (-> 31 active). The dashboard keeps its own 30d filter
as a safety net for daily-stale feeds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:18:41 -06:00
jaxperro 8dc00e44b3 live: add 30-day conviction stats to the sharp feed
display_stats now emits conv30_win/won/lost/pnl — conviction (top-20%)
bets resolved in the last 30 days — for a new "30 Day Conv Record" column
on the dashboard alongside the lifetime record.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 10:57:22 -06:00
jaxperro ee3d3d1e9a live: add avg_bet (mean conviction stake) to the sharp feed
display_stats now emits avg_bet = average stake of the wallet's
conviction (top-20%) bets, for a new dashboard column after the P&L
columns.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:19:34 -06:00
jaxperro 37817d380a live: precompute all sharp-table stats into watch_sharps.json
validate_timing.py now writes name, conv_win, conv_won/lost, conv_pnl,
realized_pnl, last_conv_bet and last_trade per sharp, so the dashboard
reads everything from the feed in ONE request instead of 3 data-api
calls per wallet (no more rate-limit storms). Stats come from the cache
(survivorship-correct): conviction win%/record/P&L over ALL of the
wallet's top-20%-stake bets; realized P&L over the last 500 resolved
bets. Resolved P&L per bet = stake*(1-p)/p if won else -stake.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 15:51:18 -06:00
jaxperro 2ee8e79d00 rename timing "insider" verdict -> "last-minute" (honest label)
The lead-time gate in validate_timing.py is a COPYABILITY heuristic, not
proof of inside information: a short entry->resolution lead can be a
genuine insider OR just someone who trades fast-resolving markets (live
sports, hourly) well — indistinguishable, and irrelevant for copy
purposes since either way the window is too tight to mirror. Rename the
verdict accordingly. Gate unchanged (median lead >=24h to count as a
copyable sharp; 50 sharps). Docs updated to current p80 numbers
(218 profile matches, 62/83 forward-profitable, 50 sharps).

The genuine insider-detection scanner (insider.py, z-score / Bubblemaps
fingerprint) keeps its name — that's a different, correctly-named thing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:55:43 -06:00
jaxperro 1cbe1a67b9 conviction = per-wallet top-20% stake (p80), not flat $200
Swap the flat $200 conviction cutoff for a per-wallet percentile (top
20% of each wallet's own stake sizes) everywhere it was used:

- cache.py: canonical CONV_PCTILE=0.80 + conv_cutoff() helper (matches
  the dashboard's pctl: filter >0, sort, linear interp)
- conviction_scan.py: per-wallet quantile_cont(size,0.8) in SQL, was
  `size >= 200`
- validate_timing.py, pnl_focused.py: use cache.conv_cutoff

Rationale + validation: p80 reproduces flat-$200's win-rate lift on the
sharps while adapting to scale (a whale's $200 isn't conviction, a
minnow's is). Re-running the pipeline under p80: scan finds 218 profile
wallets (was 69), forward 62/83 profitable (p~0), +16% pooled ROI — edge
persists out-of-sample. Regenerated conviction_wallets.json /
watch_sharps.json; docs updated. skill.py/strategy.py/insider.py
untouched (score over all bets / size as copyability heuristic only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:47:39 -06:00
jaxperro d116b25d52 live: conviction-profile scan + insider-vs-sharp timing validation
conviction_scan.py finds wallets whose high-conviction (>=$200) bets win on
uncertain (~0.4-0.6) markets — real edge, not favorite-riding — trained pre-June
and validated June (25/37 stayed profitable forward, p=0.024). validate_timing.py
applies the entry->resolution lead-time gate that separates copyable sharps from
uncopyable insiders: of 69 matches, 21 were insiders (lead <6h), leaving 23
validated copyable sharps (watch_sharps.json) now shown on jaxperro.com/trading.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 17:22:50 -06:00