/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>
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>
The replay held everything to resolution while the live bot mirrors exits —
two different strategies wearing one dashboard. Now, when a followed wallet
fully closed a position pre-resolution, the replay sells there too: close
time from /closed-positions, exit price reconstructed as
avgPrice + realizedPnl/totalBought, exit taker fee + slippage haircut paid.
Complete in-window round trips on unresolved markets (entered AND exited —
previously invisible to the replay) are included; on-chain-resolved conds
are excluded from round-trip synthesis so redeems can't masquerade as
sells. Sold legs count apart from W/L/R (they're price events, not
outcomes): 30d now 226W/55L/84R/31S, $12,730 (+1173%) — mirrored exits
bank the wallets' early profit-taking that hold-to-resolution left behind.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The honest 30d backtest: $1k -> $2,538 (+154%), 78W/18L/112R — not the
refund-inflated +8,627%. Sharp list: 33 copy-positive holders under truth
gates; 0x4bFb-whale and ArbTrader no longer qualify (their conviction
records were carried by 50/50 refunds counted as wins). portfolio.py now
reports refunds as R, not L (a scratch is not a loss).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The backtest reads positions (state); the bot reads trades (events). A trade
during downtime was silently baselined away at the next boot — not copied,
not missed, invisible — while the backtest still showed it (2026-07-07:
9 ArbTrader positions opened into the Fly trial-expiry crash-loop appeared
only in portfolio.json's current list). Now every boot + backstop poll
records any unseeded, unheld, unrecorded position that clears the follow
filter as a missed bet (reason 'bot offline'), settled hypothetically like
every other miss. Never copied — the entry is stale by definition. Dust
below risk.min_order_usd is ignored; API failure skips (silence is never
data). Bets that open AND close entirely within downtime stay the
backtest's domain.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
Audit 2026-07-06 found the live paper book stuck: 4 of 5 open positions were
50/50 refunds the bot could never settle, and the 5th was a position the
signal had exited a day earlier.
- resolution_price tier 2: CTF payoutNumerators/Denominator via RPC
(ALCHEMY_RPC_URL env or config alchemy_key). CLOB winner flags never
populate for operator-resolved in-play markets and never express 50/50
refunds; the chain records both. Token-id match now precedes outcome-label
match in tier 1. New bet status: 'refund' (settles at $0.50/share,
redeems in live mode).
- reconcile_exits(): at boot + every backstop poll, verify the signal still
holds each copied token (market-filtered data-api queries — no pagination
cap). Exit requires three AFFIRMATIVE facts (open-positions fetched+empty,
closed-positions fetched+contains token, market fetched+still trading) —
an API failure must never read as an exit.
- ledger_drift(): cash-vs-ledger invariant checked every heartbeat, warned
in the log, published in the feed (ledger_drift / adjustments fields).
Accumulators no longer round per fill (that drifted cents per trim);
the feed rounds at render. SELL fills now logged to copybot_fills.jsonl.
- their_positions seed cap 500 -> 2000 (whale books exceed 500).
Dry-run vs a copy of the real stuck state: 4 refunds settled at 0.50,
McCormick mirror-exited at 0.69, drift stable at the known +15.45 residue.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>