From b59347ae5c2018d8255bb96481477c1e5c79e093 Mon Sep 17 00:00:00 2001 From: Theodore Song Date: Sun, 12 Jul 2026 16:35:24 -0400 Subject: [PATCH] Fix paper market browser and accounts --- index.html | 91 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 8 deletions(-) diff --git a/index.html b/index.html index d23cfcb..81f24ca 100644 --- a/index.html +++ b/index.html @@ -480,6 +480,8 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color + +
@@ -504,6 +506,10 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color

Paper account leaderboard

best returns
+
+

Portfolio value history

paper equity over time
+ +

Paper positions

@@ -515,6 +521,8 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
@@ -546,6 +554,16 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color

Device sync

Portfolios, returns, reports, suggestions, and chart history now sync through the site automatically. Copy Sync and Import Sync remain as a fallback for moving the exact state between devices.

+
+

Real-money trading requirements

before enabling deposits
+
    +
  1. Eligibility — identity checks, location restrictions, age checks, terms acceptance, and sanctions screening.
  2. +
  3. Wallets — non-custodial wallet connection or Polymarket deposit-wallet flow so users sign orders themselves.
  4. +
  5. Funds — compliant deposit/withdraw rails, reconciled balances, statements, tax/accounting exports, and customer support.
  6. +
  7. Execution — order router using Polymarket CLOB authentication, signed orders, cancellations, fill tracking, and audit logs.
  8. +
  9. Risk controls — user permissions for each agent, exposure limits, stop rules, emergency pause, and withdrawal access at all times.
  10. +
+
⚠️ Paper trading only — not financial advice. Nothing here places real orders or moves money. The analysis is a transparent heuristic. Portfolios sync through the site when cloud state is available, with this browser keeping a local backup.
@@ -593,6 +611,8 @@ const DATA_API = "https://data-api.polymarket.com"; const LB_API = "https://lb-api.polymarket.com"; const CLOB = "https://clob.polymarket.com"; let PAPER_SEARCH_RESULTS = null; +let PAPER_MARKET_OFFSET = 0; +let PAPER_MARKET_QUERY = ""; const catColor = (c) => CAT_COLORS[c] || CAT_COLORS.Other; const getFocus = () => localStorage.getItem(FOCUS_KEY) || "All"; const setFocus = (c) => localStorage.setItem(FOCUS_KEY, c); @@ -1818,7 +1838,7 @@ function isPaperUnlocked(acct){return !acct.passwordHash||sessionStorage.getItem function defaultPaperAccount(name="Paper Trader",password=""){ const id="acct_"+Date.now().toString(36)+Math.random().toString(36).slice(2,6); if(password)sessionStorage.setItem(unlockKey(id),"1"); - return {id,name,cash:STARTING_BALANCE,starting_balance:STARTING_BALANCE,positions:[],history:[],passwordHash:password?hashPassword(password):null}; + return {id,name,cash:STARTING_BALANCE,starting_balance:STARTING_BALANCE,positions:[],history:[],snapshots:[],passwordHash:password?hashPassword(password):null}; } function loadPaperStore(){ let store;try{store=JSON.parse(localStorage.getItem(PAPER_KEY));}catch(e){store=null;} @@ -1828,7 +1848,7 @@ function loadPaperStore(){ localStorage.setItem(PAPER_KEY,JSON.stringify(store)); } if(!store.accounts[store.activeId])store.activeId=Object.keys(store.accounts)[0]; - Object.values(store.accounts).forEach(a=>{if(!a.history)a.history=[];if(!a.positions)a.positions=[];if(a.passwordHash===undefined)a.passwordHash=null;}); + Object.values(store.accounts).forEach(a=>{if(!a.history)a.history=[];if(!a.positions)a.positions=[];if(!a.snapshots)a.snapshots=[];if(a.passwordHash===undefined)a.passwordHash=null;}); return store; } function savePaperStore(store,sync=true){ @@ -1841,6 +1861,13 @@ function activePaperAccount(store=loadPaperStore()){ function paperEquity(acct){ return acct.cash+(acct.positions||[]).reduce((s,p)=>s+(p.value||p.shares*p.current_price||0),0); } +function recordPaperSnapshot(acct){ + const eq=paperEquity(acct),ts=nowIso(),last=acct.snapshots&&acct.snapshots[acct.snapshots.length-1]; + if(last&&last.timestamp&&Math.abs(new Date(ts)-new Date(last.timestamp))<30000)return; + acct.snapshots=acct.snapshots||[]; + acct.snapshots.push({timestamp:ts,date:todayStr(),cash:+acct.cash.toFixed(2),equity:+eq.toFixed(2),return_pct:+((eq/acct.starting_balance-1)*100).toFixed(2),open_positions:(acct.positions||[]).length}); + acct.snapshots=acct.snapshots.slice(-500); +} function markPaperToMarket(acct){ const sugs=(loadSuggestions().suggestions)||[]; const byId={};sugs.forEach(s=>byId[s.market_id]=s); @@ -1885,6 +1912,7 @@ function paperBuy(marketId,side,amount){ acct.positions.push({market_id:s.market_id,question:s.question,side,shares,entry_price:+entry.toFixed(4),current_price:+entry.toFixed(4), cost,value:cost,unrealized_pnl:0,category:s.category,opened_at:cycleIso(),url:s.url||"",paper:true}); acct.history.push({date:logDay(),action:"OPEN",detail:`Paper bought ${shares} ${side} '${s.question.slice(0,40)}' @ ${pct(entry)} for ${fmtUSD(cost)}`}); + recordPaperSnapshot(acct); savePaperStore(store);renderPaperTab();toast("Paper bet placed."); } function paperBuyAgent(agentId,amount){ @@ -1897,6 +1925,7 @@ function paperBuyAgent(agentId,amount){ acct.positions.push({kind:"AGENT",agentId,question:`${cfg.name} agent shares`,side:"AGENT",shares:+units.toFixed(6),units, entry_price:+eq.toFixed(2),current_price:+eq.toFixed(2),cost:+amount.toFixed(2),value:+amount.toFixed(2),unrealized_pnl:0,category:"Agent",opened_at:cycleIso(),paper:true}); acct.history.push({date:logDay(),action:"OPEN",detail:`Bought ${fmtUSD(amount)} of ${cfg.name}'s return stream.`}); + recordPaperSnapshot(acct); savePaperStore(store);renderPaperTab();toast(`Bought ${cfg.name} shares.`); } function paperTradeFromMarket(m){ @@ -1915,6 +1944,7 @@ function paperClose(index){ acct.cash=+(acct.cash+proceeds).toFixed(2); acct.history.push({date:logDay(),action:"CLOSE",detail:`Paper sold '${pos.question.slice(0,40)}' for ${fmtUSD(proceeds)} (${fmtUSD(realized)})`}); acct.positions.splice(index,1); + recordPaperSnapshot(acct); savePaperStore(store);renderPaperTab(); } function paperMoveCash(kind,amount,password){ @@ -1925,6 +1955,7 @@ function paperMoveCash(kind,amount,password){ acct.cash=+(acct.cash+(kind==="deposit"?amount:-amount)).toFixed(2); if(kind==="deposit")acct.starting_balance=+(acct.starting_balance+amount).toFixed(2); acct.history.push({date:logDay(),action:kind==="deposit"?"DEPOSIT":"WITHDRAW",detail:`${kind==="deposit"?"Deposited":"Withdrew"} ${fmtUSD(amount)} paper cash.`}); + recordPaperSnapshot(acct); savePaperStore(store);renderPaperTab();toast(kind==="deposit"?"Paper deposit added.":"Paper withdrawal complete."); } function renderExactTrades(){ @@ -2013,7 +2044,16 @@ function renderPaperTab(){ : acct.passwordHash?`
Account unlocked for this browser session.
`:`
No password set for this paper account.
`; const unlockBtn=$("paperUnlockBtn"); if(unlockBtn)unlockBtn.onclick=()=>{if(requirePaperUnlock(acct,$("paperUnlockInput").value)){renderPaperTab();toast("Paper account unlocked.");}}; + const locked=acct.passwordHash&&!isPaperUnlocked(acct); + if(locked){ + $("paperStats").innerHTML=`
Log in to view this paper portfolio.
`; + $("paperPositions").innerHTML=`
Locked.
`; + $("paperHistory").innerHTML=`
Locked.
`; + const chart=$("paperPortfolioChart");if(chart)chart.innerHTML=""; + renderPaperMarkets();renderPaperLeaderboard(store);return; + } const eq=paperEquity(acct),pnl=eq-acct.starting_balance; + recordPaperSnapshot(acct); $("paperStats").innerHTML=[ {ic:"💰",label:"Equity",value:fmtUSD(eq)}, {ic:"💵",label:"Cash",value:fmtUSD(acct.cash)}, @@ -2024,6 +2064,7 @@ function renderPaperTab(){ renderPaperMarkets(); renderPaperHistory(acct); renderPaperLeaderboard(store); + renderPaperPortfolioChart(acct); } function renderPaperPositions(acct){ const root=$("paperPositions");if(!root)return; @@ -2048,7 +2089,7 @@ function renderPaperMarkets(){ filter.innerHTML=CATEGORIES.map(c=>`${c}${counts[c]||0}`).join(""); document.querySelectorAll("[data-paper-cat]").forEach(el=>el.onclick=()=>{setFocus(el.dataset.paperCat);renderPaperTab();}); } - const shown=all.filter(s=>focus==="All"||!focus||s.category===focus).slice(0,24); + const shown=all.filter(s=>focus==="All"||!focus||s.category===focus); if(!shown.length){root.innerHTML=`
Run a cycle or search all Polymarket markets to load paper-tradable ideas.
`;return;} root.innerHTML=shown.map(s=>`
@@ -2075,18 +2116,41 @@ function renderPaperLeaderboard(store){ const rows=Object.values(store.accounts).map(a=>{markPaperToMarket(a);const eq=paperEquity(a),ret=(eq/a.starting_balance-1)*100;return {a,eq,ret};}).sort((x,y)=>y.ret-x.ret); root.innerHTML=rows.map((r,i)=>`
#${i+1} ${esc(r.a.name)}
${r.a.positions.length} open · ${fmtUSD(r.eq)} equity
${fmtPct(r.ret)}
`).join(""); } +function renderPaperPortfolioChart(acct){ + const svg=$("paperPortfolioChart");if(!svg)return; + const snaps=(acct.snapshots||[]).filter(s=>Number.isFinite(snapTime(s))).sort((a,b)=>snapTime(a)-snapTime(b)); + if(snaps.length<2){svg.innerHTML=`Portfolio value history will appear after a few account changes.`;return;} + const W=720,H=260,padL=62,padR=24,padT=26,padB=38,vals=snaps.map(s=>s.equity),mn=Math.min(...vals),mx=Math.max(...vals),span=(mx-mn)||1; + const x=i=>padL+(i/(snaps.length-1))*(W-padL-padR),y=v=>padT+(mx-v)/span*(H-padT-padB); + const d=vals.map((v,i)=>`${i?"L":"M"}${x(i).toFixed(1)},${y(v).toFixed(1)}`).join(" "); + let inner=``; + for(let i=0;i<4;i++){const v=mn+(span*i/3),yy=y(v);inner+=`${fmtUSD(v)}`;} + inner+=``; + for(const tick of chartTicks(snaps.map(snapTime),4)){inner+=`${chartDateLabel(tick.t,true)}`;} + svg.innerHTML=inner; +} async function searchPaperMarkets(){ const q=($("paperSearchInput").value||"").trim(); if(!q)return toast("Enter a market search."); + PAPER_MARKET_OFFSET=0;PAPER_MARKET_QUERY=q; + await loadPaperMarketPage(true); +} +async function loadPaperMarketPage(reset=false){ + const q=PAPER_MARKET_QUERY||(($("paperSearchInput")&&$("paperSearchInput").value)||"").trim(); setStatus("searching Polymarket...",true); try{ - const params=new URLSearchParams({closed:"false",active:"true",archived:"false",include_tag:"true",limit:"80",order:"volume24hr",ascending:"false",search:q}); + if(reset){PAPER_SEARCH_RESULTS=[];PAPER_MARKET_OFFSET=0;} + const params=new URLSearchParams({closed:"false",active:"true",archived:"false",include_tag:"true",limit:"100",offset:String(PAPER_MARKET_OFFSET),order:"volume24hr",ascending:"false"}); + if(q)params.set("search",q); const r=await fetch(`${GAMMA}/markets?${params}`); - if(!r.ok)throw new Error("search failed"); + if(!r.ok)throw new Error("market load failed"); const raw=await r.json(); - PAPER_SEARCH_RESULTS=raw.map(normalizeMarket).filter(Boolean).map(paperTradeFromMarket); + const next=raw.map(normalizeMarket).filter(Boolean).map(paperTradeFromMarket); + PAPER_SEARCH_RESULTS=(PAPER_SEARCH_RESULTS||[]).concat(next); + PAPER_MARKET_OFFSET+=raw.length; renderPaperTab();setStatus("up to date",false); - }catch(e){setStatus("search error",false);toast("Polymarket search failed.");} + toast(next.length?`Loaded ${PAPER_SEARCH_RESULTS.length} active markets.`:"No more active markets found."); + }catch(e){setStatus("market load error",false);toast("Polymarket market load failed.");} } function renderAbout(){ const root=$("aboutAgents");if(!root)return; @@ -2169,7 +2233,18 @@ $("paperDepositBtn").addEventListener("click",()=>paperMoveCash("deposit",$("pap $("paperWithdrawBtn").addEventListener("click",()=>paperMoveCash("withdraw",$("paperMoneyAmount").value,$("paperPasswordInput").value)); $("buyAgentBtn").addEventListener("click",()=>paperBuyAgent($("buyAgentSelect").value,$("buyAgentAmount").value)); $("paperSearchBtn").addEventListener("click",()=>searchPaperMarkets()); -$("paperClearSearchBtn").addEventListener("click",()=>{PAPER_SEARCH_RESULTS=null;renderPaperTab();}); +$("paperAllBtn").addEventListener("click",()=>{PAPER_MARKET_QUERY="";PAPER_SEARCH_RESULTS=[];loadPaperMarketPage(true);}); +$("paperMoreBtn").addEventListener("click",()=>loadPaperMarketPage(false)); +$("paperClearSearchBtn").addEventListener("click",()=>{PAPER_SEARCH_RESULTS=null;PAPER_MARKET_OFFSET=0;PAPER_MARKET_QUERY="";renderPaperTab();}); +$("paperLoginBtn").addEventListener("click",()=>{ + const acct=activePaperAccount(); + if(requirePaperUnlock(acct,$("paperPasswordInput").value)){renderPaperTab();toast("Logged in.");} +}); +$("paperSignOutBtn").addEventListener("click",()=>{ + const acct=activePaperAccount(); + sessionStorage.removeItem(unlockKey(acct.id)); + renderPaperTab();toast("Signed out of paper account."); +}); window.addEventListener("focus",()=>refreshFromCloudAndRender(true)); document.addEventListener("visibilitychange",()=>{ if(!document.hidden)refreshFromCloudAndRender(true);