HANDOFF.md: Phase 1 complete — orphan seam closed (Option A), 1.3 + 1.8 done; next is Phase 2 funding (user)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+51
-86
@@ -1,4 +1,4 @@
|
||||
# Session handoff — 2026-07-08
|
||||
# Session handoff — 2026-07-08 (rev 2: Phase 1 COMPLETE)
|
||||
|
||||
Self-contained pickup for a fresh session. Read [README.md](README.md) gotchas
|
||||
1–11, [FINDINGS.md](FINDINGS.md), and [LIVE_ROLLOUT.md](LIVE_ROLLOUT.md) first.
|
||||
@@ -11,97 +11,60 @@ deeper history; the repo is authoritative.
|
||||
push mode. Following **Set D** (2026-07-08): LSB1, imwalkinghere, Kruto2027,
|
||||
42021, 0xbadaf319, BikesAreTheBikes — 6 moderate-bet volume wallets chosen by
|
||||
simulation ($27.8k / +2680% 30d backtest). `flyctl logs --app wwf-copybot`.
|
||||
- **P&L is honest end-to-end.** The sharps table's All-Time P&L is now the
|
||||
realized track record (sum of Polymarket `realizedPnl` per closed position)
|
||||
PLUS abandoned-loser fold-in (`_open_split` in validate_timing) — matches PM
|
||||
where PM is honest, reads lower where PM hides abandoned losers (oliman2 $181k
|
||||
→$20k). 27/31 sharps match PM within a few %, 4 honestly-below, **0
|
||||
over-count**. New **Open P&L** column = genuine in-flight exposure.
|
||||
- **Ledger reconciles** (`ledger_drift` 0.00, self-checked every heartbeat).
|
||||
- **Daily pipeline** (launchd 08:00) runs the corrected code; exits cache is
|
||||
backfilled (86/96 wallets `complete`, incremental from here). Verified wired.
|
||||
- **Data caches**: `live/cache.duckdb` — `bets` (still windowed/capped, used for
|
||||
z-score selection only), `exits` (FULL history, incremental, `realized_pnl`),
|
||||
`resolutions` (chain payouts). `closed_exits` is the shared exit/realized
|
||||
source for both the backtest and the sharps table.
|
||||
- **P&L is honest end-to-end** (sharps All-Time = realized track record +
|
||||
abandoned-loser fold-in; Open P&L = in-flight exposure — see rev 1 in git
|
||||
history for the full derivation).
|
||||
- **Ledger reconciles** (`ledger_drift` 0.00, self-checked every heartbeat AND
|
||||
after every trade — see below).
|
||||
- **Daily pipeline** (launchd 08:00) runs the corrected code; exits cache
|
||||
incremental (86/96 wallets `complete`).
|
||||
|
||||
## THE PROPER FIX (do before real money) — the orphan / cash-debit gap
|
||||
## DONE 2026-07-08 (this session) — the proper fix + Phase 1 closeout
|
||||
|
||||
**Symptom seen 2026-07-08:** after the Set D deploy the ledger self-check flagged
|
||||
`⚠ LEDGER DRIFT $+36.35`. Root: a position ("Hanyi Liu vs Aoran Wang: Total
|
||||
Sets", ArbTrader — a now-dropped wallet) sat in `state["my_pos"]` but its cost
|
||||
was **never debited from `state["cash"]`** and it had **no bet record and no
|
||||
`conds` entry**. A second market on the same event, sold, was left `status:open`
|
||||
(a "ghost"). Both were cleaned by hand (`copybot_state.json` surgery, committed);
|
||||
drift is 0 now. But the *mechanism that creates orphans is guarded, not fixed.*
|
||||
**Orphan/cash-debit seam CLOSED (Option A, commit 167a0b9).** The mechanism
|
||||
that created the $+36.35 drift orphan is fixed, not just guarded:
|
||||
|
||||
**Why it happens (as far as traced):** `on_wallet_activity` books cash + creates
|
||||
the bet record via:
|
||||
```python
|
||||
for f in self._drain_fills(): # _drain_fills debits cash for EVERY fill
|
||||
if f["token"] == tok: # but _record_lag (the bet record) fires
|
||||
self._record_lag(wallet, t, f) # ONLY for the fill matching THIS trade's token
|
||||
```
|
||||
When two markets of one event are processed in a batch (or a fill is left in
|
||||
`ex.fills` across iterations, or an exception fires between `handle_trade` and
|
||||
`_drain_fills`), a buy fill can end up **drained-but-not-recorded** (no bet
|
||||
record), or **added-to-my_pos-but-never-drained** (cost not debited). The
|
||||
`_record_lag` `if f["token"] == tok` filter is the fragile seam.
|
||||
- `on_wallet_activity` now records EVERY drained buy fill: the tok-match keeps
|
||||
`_record_lag` (real lag/slippage); leftovers get `_record_untracked_buy`
|
||||
(bet record synthesized from the fill, conds from the position, audit line
|
||||
with `"untracked":true`). `reconcile_exits`' drain does the same instead of
|
||||
discarding returned buys.
|
||||
- `check_book()` asserts the invariant **after every trade and at boot**:
|
||||
every `my_pos` token ⇒ bet record + conds entry + cash debited. Records and
|
||||
conds self-correct in place. The cash leg heals ONLY at boot
|
||||
(`check_book(heal_cash=True)`, called after `bot.seed()`) and ONLY when
|
||||
`ledger_drift` matches ONE un-vouched position's cost+fee within
|
||||
max($0.10, 1%) — vouched = fills-ledger BUY line for the token, or a real
|
||||
`_record_lag` record (`their_price` set). A drift that matches nothing stays
|
||||
a loud `⚠ LEDGER DRIFT`, never papered over.
|
||||
- Supporting plumbing: `my_pos` entries carry `cond` (engine stamps it on
|
||||
OPEN/ADD); fills-ledger BUY lines carry `token`; `_synth_bet` (shared with
|
||||
`write_feed`) estimates the fee with the same `taker_fee` formula
|
||||
`_drain_fills` charges, so a synthesized record over correctly-debited cash
|
||||
closes to drift 0 — and a never-debited orphan shows drift = cost+fee
|
||||
exactly, which is what the heal keys on.
|
||||
- Tested: 6-case harness (untracked booking, synth+conds, matching heal,
|
||||
non-matching refusal, vouched exclusion, multi-orphan refusal) all pass;
|
||||
dry-run against the real state was a clean no-op (drift $0.0055, nothing
|
||||
touched). Deployed; heartbeats clean.
|
||||
|
||||
**Guards already in place (make recurrence VISIBLE, not silent):**
|
||||
- `Copybot.ledger_drift()` — checked every heartbeat, logged `⚠ LEDGER DRIFT`,
|
||||
published in the feed (`ledger_drift` field). Catches the cash gap in ≤60s.
|
||||
- `write_feed` synthesizes a bet record for any open `my_pos` position missing
|
||||
one (so `open_count` always matches the visible table).
|
||||
- `my_pos` entries now carry `wallet` (copytrade.py) for attribution.
|
||||
**Phase 1 is 8/8 DONE** (LIVE_ROLLOUT.md): 1.1 feed separation, 1.2 env
|
||||
secrets, 1.3 live deps in the worker image (py-clob-client==0.34.6 +
|
||||
web3==7.16.0 in fly.Dockerfile, import-verified via `flyctl ssh console`),
|
||||
1.4 on-chain cash anchor, 1.5 fatal geocheck, 1.6 exit retries, 1.7 config
|
||||
sync, 1.8 dashboard REAL MONEY section (jaxperro `trading/index.html` commit
|
||||
871e56e — fourth board chip + section reading `live/copybot_live_real.json`,
|
||||
shared `copybotSection()` renderer, ledger drift/adjustments surfaced on both
|
||||
books, verified against a mode:"live" feed copy and the 404 NOT STARTED
|
||||
empty-state; `?rmFeed=`/`?botFeed=` query params override feeds for testing).
|
||||
|
||||
**The proper fix — two options, recommend A for Phase 1:**
|
||||
## Next: Phase 2 — funding (USER ONLY)
|
||||
|
||||
**Option A (surgical, lower risk):** in `on_wallet_activity`, record EVERY
|
||||
drained buy fill, not just the `tok`-matching one. The tok-match gets
|
||||
`_record_lag` (real lag/slippage); others get a bet record synthesized from the
|
||||
fill (lag unknown). AND assert the invariant after each trade: every `my_pos`
|
||||
token has (cost in cash via a drained fill) + (bet record) + (`conds` entry);
|
||||
if not, log and self-correct. This closes the "drained-but-not-recorded" and
|
||||
"added-but-not-drained" seams without touching the cash model.
|
||||
|
||||
**Option B (architectural, eliminates the class):** stop *accumulating*
|
||||
`state["cash"]` per fill; **derive** it each cycle from the book:
|
||||
`cash = bankroll + Σ(settled bet pnl) − Σ(open my_pos cost+fee) + adjustments`.
|
||||
Then `ledger_drift` is 0 by construction and an orphan simply (correctly)
|
||||
reduces available cash. Higher effort — the derivation must reproduce every flow
|
||||
the accumulation handles (entry fee, sold-leg proceeds, refund/settlement
|
||||
payoffs, redeems). Worth it long-term; risky to rush before real money.
|
||||
|
||||
**To reproduce/investigate:** add a log line in `_handle_their_buy` on every
|
||||
`my_pos` add with `len(executor.fills)` before/after, and in `_drain_fills` log
|
||||
each fill booked. Watch a batch where one wallet trades ≥2 markets of the same
|
||||
event in <10 min (ITF tennis sub-markets, in-play soccer). The orphan appears
|
||||
when the second market's fill misses `_record_lag`.
|
||||
|
||||
## Phase 1 remaining (LIVE_ROLLOUT.md) — 6 of 8 done
|
||||
|
||||
Done + verified: 1.1 feed separation, 1.2 env secrets, 1.4 on-chain cash anchor
|
||||
(`chain_cash_gap`), 1.5 fatal geocheck (refuses from a blocked box), 1.6 exit
|
||||
retries (`⚠ EXIT STUCK`), 1.7 config sync (`config.live.example.json`).
|
||||
|
||||
**1.3 — live deps in the worker image (NOT done).** `fly.Dockerfile` is
|
||||
stdlib-only. Add `pip install py-clob-client web3` (pin versions) — needed by
|
||||
`LedgerLiveExecutor` + `redeem.py`. Keep the paper path import-lazy (already is).
|
||||
Verify: `flyctl deploy --remote-only`, then
|
||||
`flyctl ssh console -C "python3 -c 'import py_clob_client, web3'"` exits 0, and
|
||||
the paper bot still boots clean (banner + heartbeat, drift 0.00).
|
||||
|
||||
**1.8 — dashboard live-money section (NOT done).** `trading/index.html` (in the
|
||||
`jaxperro` repo) should read `live/copybot_live_real.json` (the live feed path
|
||||
from 1.1, set in `config.live.example.json`) into a fourth board chip + tables,
|
||||
clearly labeled REAL MONEY, reusing the standardized columns (Their/Our Entry,
|
||||
Stake, Result W/L/R/S, Placed/Settled) and the `ledger_drift`/`adjustments`
|
||||
fields. Verify against a copy of the paper feed with `"mode":"live"`; then
|
||||
confirm graceful empty-state when the file 404s (test not started).
|
||||
|
||||
After Phase 1: Phase 2 is funding (USER ONLY), then preflight → supervised
|
||||
first fill → the edge-case matrix (LIVE_ROLLOUT.md Phase 5).
|
||||
Everything before real money is done. Per LIVE_ROLLOUT.md: fund the live
|
||||
wallet (test size, rule 0.6 caps stay at $5/trade · $25/day · $30 exposure),
|
||||
then Phase 3 preflight (`preflight_live.py`), Phase 4 supervised first fill,
|
||||
Phase 5 edge-case matrix. The typed-phrase interlock (rule 0.7) is the human
|
||||
checkpoint — never automated.
|
||||
|
||||
## Standing to-dos (user-only or external)
|
||||
|
||||
@@ -125,3 +88,5 @@ first fill → the edge-case matrix (LIVE_ROLLOUT.md Phase 5).
|
||||
pipeline or a regen is running.
|
||||
- Bot commits its own state/feed every few minutes — always
|
||||
`git pull --rebase --autostash` before pushing.
|
||||
- Image changes (fly.Dockerfile) need `flyctl deploy --remote-only`; code-only
|
||||
changes just need a push + `flyctl apps restart` (start.sh clones main).
|
||||
|
||||
Reference in New Issue
Block a user