From d16e8527efb85e592297023faade7f688d401e22 Mon Sep 17 00:00:00 2001 From: theodore-song Date: Sun, 5 Jul 2026 15:23:22 -0400 Subject: [PATCH] Real day-by-day backtest: replay strategy decisions on historical prices + whale on-chain trade history --- index.html | 160 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 117 insertions(+), 43 deletions(-) diff --git a/index.html b/index.html index e2c27d6..09c8fed 100644 --- a/index.html +++ b/index.html @@ -257,7 +257,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color

The Equity Race

-
Past week is a backtest — current holdings marked against real Polymarket price history. Live tracking takes over from today.
+
Past week is a day-by-day backtest: agents re-ran their strategy on each day's real historical prices; whales are replayed from their on-chain trade history. (Volume/liquidity signals use current values — Polymarket doesn't expose historical volume.) Live tracking begins today.

Top Picks Today

highest conviction
@@ -389,6 +389,8 @@ const fmtPct = (n) => (n>=0?"+":"") + Number(n).toFixed(2) + "%"; const signClass = (n) => (n>0?"pos-val":n<0?"neg-val":""); const todayStr = () => new Date().toISOString().slice(0,10); const nowIso = () => new Date().toISOString(); +let SIM_DAY = null; // set during backtest so logs/snapshots use the simulated date +const logDay = () => SIM_DAY || todayStr(); function toast(msg){ let t=document.querySelector(".toast"); if(!t){t=document.createElement("div");t.className="toast";document.body.appendChild(t);} @@ -513,7 +515,7 @@ function markToMarket(p,priceMap){ pos.exit_price=pos.current_price;pos.closed_at=nowIso(); pos.realized_pnl=+(proceeds-pos.cost).toFixed(2); p.closed.push(pos); - p.history.push({date:todayStr(),action:"CLOSE",question:pos.question,side:pos.side, + p.history.push({date:logDay(),action:"CLOSE",question:pos.question,side:pos.side, detail:`Settled '${pos.question.slice(0,40)}' for ${fmtUSD(proceeds)} (P&L ${fmtUSD(pos.realized_pnl)})`}); continue; } @@ -544,14 +546,14 @@ function openPositions(p,cfg,rankedSugs,focus){ token_id:(s.side==="YES"?s.clob_yes:s.clob_no)||null, entry_price:+entry.toFixed(4),current_price:+entry.toFixed(4),cost,value:cost, unrealized_pnl:0,conviction:s.conviction,category:s.category,opened_at:nowIso(),url:s.url||""}); - p.history.push({date:todayStr(),action:"OPEN",question:s.question,side:s.side, + p.history.push({date:logDay(),action:"OPEN",question:s.question,side:s.side, detail:`Bought ${shares} ${s.side} '${s.question.slice(0,40)}' @ ${pct(entry)} for ${fmtUSD(cost)}`}); opened++; } } function recordSnapshot(p){ const eq=equity(p); - const snap={date:todayStr(),timestamp:nowIso(),cash:+p.cash.toFixed(2), + const snap={date:logDay(),timestamp:(SIM_DAY?new Date(SIM_DAY+"T12:00:00Z").toISOString():nowIso()),cash:+p.cash.toFixed(2), equity:+eq.toFixed(2),pnl:+(eq-p.starting_balance).toFixed(2), return_pct:+((eq/p.starting_balance-1)*100).toFixed(2),open_positions:p.positions.length}; p.snapshots=p.snapshots.filter(s=>s.date!==snap.date); @@ -604,7 +606,7 @@ async function runWhaleAgent(p,wallet){ if(!held.has(pos.asset)){ const proceeds=pos.shares*pos.current_price;p.cash+=proceeds; pos.exit_price=pos.current_price;pos.realized_pnl=+(proceeds-pos.cost).toFixed(2);p.closed.push(pos); - p.history.push({date:todayStr(),action:"CLOSE",question:pos.question,side:pos.side, + p.history.push({date:logDay(),action:"CLOSE",question:pos.question,side:pos.side, detail:`Trader exited '${pos.question.slice(0,36)}' — settled ${fmtUSD(proceeds)} (P&L ${fmtUSD(pos.realized_pnl)})`}); }else keep.push(pos); } @@ -622,7 +624,7 @@ async function runWhaleAgent(p,wallet){ p.cash=+(p.cash-cost).toFixed(2); p.positions.push({asset:t.asset,token_id:t.asset,market_id:t.conditionId,question:t.title,side:t.side,category:"Copy", shares,entry_price:+entry.toFixed(4),current_price:+entry.toFixed(4),cost,value:cost,unrealized_pnl:0,conviction:null}); - p.history.push({date:todayStr(),action:"OPEN",question:t.title,side:t.side, + p.history.push({date:logDay(),action:"OPEN",question:t.title,side:t.side, detail:`Copied trade — ${shares} ${t.side} '${t.title.slice(0,36)}' @ ${pct(entry)} for ${fmtUSD(cost)}`}); } recordSnapshot(p); @@ -637,7 +639,7 @@ function runCopycat(p,leader,priceMap){ if(!held.has(pos.market_id)){ const proceeds=pos.shares*pos.current_price;p.cash+=proceeds; pos.exit_price=pos.current_price;pos.realized_pnl=+(proceeds-pos.cost).toFixed(2);p.closed.push(pos); - p.history.push({date:todayStr(),action:"CLOSE",question:pos.question,side:pos.side, + p.history.push({date:logDay(),action:"CLOSE",question:pos.question,side:pos.side, detail:`Leader exited '${pos.question.slice(0,36)}' — settled ${fmtUSD(proceeds)}`}); }else keep.push(pos); } @@ -655,7 +657,7 @@ function runCopycat(p,leader,priceMap){ p.cash=+(p.cash-cost).toFixed(2); p.positions.push({market_id:lp.market_id,token_id:lp.token_id||null,question:lp.question,side:lp.side,category:lp.category, shares,entry_price:+entry.toFixed(4),current_price:+entry.toFixed(4),cost,value:cost,unrealized_pnl:0,conviction:lp.conviction}); - p.history.push({date:todayStr(),action:"OPEN",question:lp.question,side:lp.side, + p.history.push({date:logDay(),action:"OPEN",question:lp.question,side:lp.side, detail:`Copied leader — ${shares} ${lp.side} '${lp.question.slice(0,36)}' @ ${pct(entry)}`}); } recordSnapshot(p); @@ -681,31 +683,104 @@ function priceOnDay(map,day,fallback){ if(best!=null)return best; const ks=Object.keys(map).sort();return ks.length?map[ks[0]]:fallback; } -async function seedWeekInto(st){ - // 1) make sure every open position knows its CLOB token id - for(const a of AGENTS)for(const pos of st.agents[a.id].positions){ - if(pos.token_id)continue; - if(pos.asset){pos.token_id=pos.asset;continue;} - if(pos.market_id){const t=await fetchClobTokens(pos.market_id);if(t)pos.token_id=(pos.side==="YES")?t[0]:t[1];} - } - // 2) pull 7-day price history for each unique token once - const ids=new Set(); - for(const a of AGENTS)for(const pos of st.agents[a.id].positions)if(pos.token_id)ids.add(pos.token_id); - const hist={}; - for(const id of ids)hist[id]=await fetchPriceHistory(id); - // 3) rebuild each agent's snapshots across the past week - const dates=lastNDates(7); - for(const a of AGENTS){ - const p=st.agents[a.id]; +async function fetchTraderActivity(wallet){ + try{const r=await fetch(`${DATA_API}/activity?user=${wallet}&limit=500&type=TRADE`); + if(!r.ok)return [];const d=await r.json(); + return d.filter(x=>x.asset&&(x.side==="BUY"||x.side==="SELL")) + .map(x=>({asset:String(x.asset),side:x.side,size:+x.size||0,ts:x.timestamp}));}catch(e){return [];} +} +async function fetchTraderPositionsRaw(wallet){ + try{const r=await fetch(`${DATA_API}/positions?user=${wallet}&sizeThreshold=1&limit=100`); + if(!r.ok)return [];const d=await r.json(); + return d.filter(p=>(p.outcome==="Yes"||p.outcome==="No")&&!p.redeemable&&p.curPrice>0.02&&p.curPrice<0.98) + .map(p=>({asset:String(p.asset),size:+p.size||0,curPrice:+p.curPrice,title:p.title||""}));}catch(e){return [];} +} +/* Replay each whale by reconstructing its past holdings from on-chain trade + activity (current size minus net trades after each day), valued at real + historical token prices — then scaled to the $10k paper bankroll. */ +async function backtestWhales(st,dates){ + for(const a of AGENTS.filter(x=>x.kind==="whale")){ + const p=st.agents[a.id], wh=st.whales[a.id]; + const flat=()=>{for(const d of dates){SIM_DAY=d;recordSnapshot(p);}SIM_DAY=null;}; + if(!wh){flat();continue;} + setStatus(`backtest: replaying ${wh.name}…`,true); + let cur=[],acts=[]; + try{cur=await fetchTraderPositionsRaw(wh.wallet);}catch(e){} + try{acts=await fetchTraderActivity(wh.wallet);}catch(e){} + cur.sort((x,y)=>y.size*y.curPrice-x.size*x.curPrice); + const top=cur.slice(0,12); + if(!top.length){flat();continue;} + const histA={}; + for(const t of top)histA[t.asset]=await fetchPriceHistory(t.asset); + const dayVal={}; + for(const day of dates){ + const endTs=Math.floor(new Date(day+"T23:59:59Z").getTime()/1000); + let v=0; + for(const t of top){ + let netAfter=0; + for(const ac of acts)if(ac.asset===t.asset&&ac.ts>endTs)netAfter+=(ac.side==="BUY"?ac.size:-ac.size); + const holdings=Math.max(0,t.size-netAfter); + v+=holdings*priceOnDay(histA[t.asset],day,t.curPrice); + } + dayVal[day]=v; + } + const anchor=dayVal[dates[dates.length-1]]||1; p.snapshots=dates.map(day=>{ - let pv=0; - for(const pos of p.positions){pv+=pos.shares*priceOnDay(hist[pos.token_id],day,pos.entry_price);} - const eq=p.cash+pv; - return {date:day,timestamp:new Date(day+"T12:00:00Z").toISOString(),cash:+p.cash.toFixed(2), - equity:+eq.toFixed(2),pnl:+(eq-p.starting_balance).toFixed(2), - return_pct:+((eq/p.starting_balance-1)*100).toFixed(2),open_positions:p.positions.length,seeded:true}; + const eq=10000*(dayVal[day]/(anchor||1)); + return {date:day,timestamp:new Date(day+"T12:00:00Z").toISOString(),cash:0, + equity:+eq.toFixed(2),pnl:+(eq-10000).toFixed(2),return_pct:+((eq/10000-1)*100).toFixed(2), + open_positions:top.length,seeded:true}; }); } + SIM_DAY=null; +} +/* Full day-by-day backtest: for each of the past 7 days, rebuild every market's + state from that day's real price (+ correct time-to-resolution), re-run the + scoring engine, and step every strategy agent through a day of trading. + NOTE: volume/liquidity signals use current values as a proxy — Polymarket + does not expose historical volume. Price & timing are truly historical. */ +async function backtestWeek(st){ + const dates=lastNDates(7), start=dates[0]; + AGENTS.forEach(a=>{st.agents[a.id]=defaultPortfolio();}); + if(AGENTS.some(a=>a.kind==="whale"&&!st.whales[a.id])){ + setStatus("backtest: finding top Polymarket traders…",true); + const picked=await discoverWhales(); + AGENTS.filter(a=>a.kind==="whale").forEach(a=>{if(!st.whales[a.id]&&picked[a.slot])st.whales[a.id]=picked[a.slot];}); + } + setStatus("backtest: loading markets…",true); + const markets=await fetchMarkets(2,100); + const uni=markets.filter(m=>m.clob_token_ids&&m.clob_token_ids.length===2&&(!m.end_date||m.end_date>=start)).slice(0,100); + const hist={}; let n=0; + for(const m of uni){setStatus(`backtest: price history ${++n}/${uni.length}…`,true);hist[m.id]=await fetchPriceHistory(m.clob_token_ids[0]);} + const focus=getFocus(); + for(const day of dates){ + SIM_DAY=day; + setStatus(`backtest: simulating ${day}…`,true); + const snaps=[]; + for(const m of uni){ + const yp=priceOnDay(hist[m.id],day,null); + if(yp==null||yp<=0.02||yp>=0.98)continue; + const dtr=m.end_date?(new Date(m.end_date)-new Date(day+"T12:00:00Z"))/86400000:null; + if(dtr!=null&&dtr<-0.5)continue; + snaps.push(Object.assign({},m,{yes_price:+yp.toFixed(4),no_price:+(1-yp).toFixed(4),days_to_resolution:dtr})); + } + const sugs=generateSuggestions(snaps,12); + const priceMap={}; snaps.forEach(s=>priceMap[s.id]={yes_price:s.yes_price,no_price:s.no_price}); + for(const cfg of AGENTS.filter(a=>a.kind==="strategy")){ + const p=st.agents[cfg.id]; + markToMarket(p,priceMap); + openPositions(p,cfg,cfg.rank(sugs),focus); + recordSnapshot(p); + } + const leaderId=AGENTS.filter(a=>a.kind==="strategy").map(a=>({id:a.id,eq:equity(st.agents[a.id])})).sort((x,y)=>y.eq-x.eq)[0].id; + st.copycatLeader=leaderId; + runCopycat(st.agents.copycat,st.agents[leaderId],priceMap); + if(day===dates[dates.length-1])saveSuggestions(sugs); // today's live-ish board for the Suggestions tab + } + SIM_DAY=null; + await backtestWhales(st,dates); + // establish today's actual whale paper positions so live tracking continues + for(const a of AGENTS.filter(x=>x.kind==="whale")){const wh=st.whales[a.id];if(wh){try{await runWhaleAgent(st.agents[a.id],wh.wallet);}catch(e){}}} st.seeded=true; } @@ -750,7 +825,6 @@ async function runDailyCycle(){ try{await runWhaleAgent(st.agents[a.id],wh.wallet);} catch(e){recordSnapshot(st.agents[a.id]);} } - if(!st.seeded){setStatus("reconstructing past week…",true);await seedWeekInto(st);} st.date=todayStr();st.last_run=nowIso(); saveState(st); setStatus("up to date",false); @@ -955,24 +1029,24 @@ $("resetBtn").addEventListener("click",()=>{ saveState(defaultState());localStorage.removeItem(SUG_KEY);toast("All agents reset.");renderAll(); }); -/* On load: route, render, auto-run once per day. */ +/* On load: first visit runs a 7-day backtest; afterwards a live daily update. */ (async function init(){ showTab(location.hash.slice(1)||"overview"); renderAll(); - const sug=loadSuggestions(); - if(sug.date!==todayStr()){ - const btn=$("runBtn");btn.disabled=true;btn.textContent="Running…"; - try{await runDailyCycle();renderAll();toast("Fresh daily competition ready.");} + const st=loadState(); + const btn=$("runBtn"); + if(!st.seeded){ + btn.disabled=true;btn.textContent="Backtesting…"; + try{await backtestWeek(st);st.date=todayStr();st.last_run=nowIso();saveState(st);renderAll(); + toast("7-day backtest complete — live tracking begins today.");} + catch(e){setStatus("backtest error — click Run",false);} + btn.disabled=false;btn.textContent="Run cycle"; + }else if(st.date!==todayStr()){ + btn.disabled=true;btn.textContent="Running…"; + try{await runDailyCycle();renderAll();toast("Daily update ready.");} catch(e){setStatus("error — click Run to retry",false);} btn.disabled=false;btn.textContent="Run cycle"; }else{setStatus("up to date",false);} - // If a cycle already ran today but we haven't seeded the past week yet, do it now. - const stx=loadState(); - if(!stx.seeded && AGENTS.some(a=>stx.agents[a.id].positions.length)){ - setStatus("reconstructing past week…",true); - try{await seedWeekInto(stx);saveState(stx);renderAll();toast("Backfilled the past week of history.");}catch(e){} - setStatus("up to date",false); - } })();