- bets table gains asset (token id = position identity), src/ts (endpoint
provenance + close time), resolved (False = early-sold in an unended market;
won is a curPrice mark). Auto-migrates v1 in place (~8s, 3,697 exact dupes
merged); legacy rows carry NULLs until their wallet refreshes.
- refresh is now an upsert by token instead of a wallet wipe: rows sliding out
of the rolling pull window survive, so per-wallet history accumulates into a
permanent archive. Same-asset rows from both endpoints (partially-closed
positions) dedupe to the larger-stake row - kills the two-endpoint
double-count class (~35k suspect pairs found in the audit).
- p stored raw (0 = avgPrice missing), clamped on read by get_bets, so missing
prices stay distinguishable from real 0.1c longshots; insider CLI + oos clamp
their own direct use.
- resolved_bets(strict=True): a failed page raises instead of returning a
silently truncated history; get_bets no longer caches or marks failed pulls
(pre-v2 an API error cached the wallet as empty-and-fresh for 14 days -
Kruto2027 was a live victim of this last night).
Verified: migration 18,289,320 -> 18,285,623 rows; forced refreshes of two
sharps show 0 same-asset dups, 0 legacy/new mixing, clamped reads, conviction
stats intact (Kruto conv win 73%).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 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>
- 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>