diff --git a/index.html b/index.html index cf27843..157aa80 100644 --- a/index.html +++ b/index.html @@ -230,7 +230,8 @@ a.market-title:hover{color:#fff;text-decoration:underline;text-decoration-color: .agent-brief{grid-template-columns:1fr} .invest-grid{grid-template-columns:1fr} .invest-agent{grid-template-columns:auto 1fr} - .invest-agent input{grid-column:1 / -1;width:100%} +.invest-agent input{grid-column:1 / -1;width:100%} + .paper-grid{grid-template-columns:1fr} .pos{align-items:flex-start;gap:12px} .pos .pq{max-width:none} .right{min-width:92px} @@ -263,6 +264,18 @@ a.market-title:hover{color:#fff;text-decoration:underline;text-decoration-color: .exposure-row{display:flex;justify-content:space-between;gap:12px;padding:10px 0;border-bottom:1px solid var(--border);font-size:13px} .exposure-row:last-child{border-bottom:none} .risk-note{border:1px solid rgba(251,113,133,.28);background:rgba(251,113,133,.08);border-radius:14px;padding:14px 16px;font-size:13px;color:#f4c6cf} +.paper-grid{display:grid;grid-template-columns:.8fr 1.2fr;gap:18px} +.account-row{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px} +.input{border:1px solid var(--border);border-radius:9px;background:rgba(255,255,255,.05);color:var(--text);font:600 13px 'Inter',sans-serif;padding:9px 10px;min-width:0} +.input:focus{outline:1px solid var(--accent)} +.trade-list{display:grid;gap:12px} +.trade-card{border:1px solid var(--border);border-radius:14px;background:rgba(255,255,255,.035);padding:14px} +.trade-head{display:flex;justify-content:space-between;gap:12px;align-items:flex-start;margin-bottom:8px} +.trade-title{font-weight:700;font-size:13.5px;line-height:1.35} +.trade-actions{display:flex;gap:7px;flex-wrap:wrap;align-items:center;margin-top:10px} +.trade-actions .input{width:98px} +.trade-meta{display:flex;gap:10px;flex-wrap:wrap;color:var(--muted);font-size:11.5px} +.exact-table{display:grid;gap:10px} /* ---------- misc ---------- */ .toast{position:fixed;bottom:24px;right:24px;z-index:100;background:rgba(18,24,40,.92);border:1px solid var(--accent);padding:13px 19px;border-radius:13px;font-size:14px;opacity:0;transform:translateY(12px);transition:.28s;pointer-events:none;max-width:360px;backdrop-filter:blur(12px);box-shadow:var(--shadow)} @@ -298,6 +311,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color + +
+

Exact trades agents are holding

side, size, price, link
+
+
+ + + +
+
๐Ÿงพ Paper Trading Accounts
+
+
+
+

Account

browser paper money
+ +
+
+ + +
+

Copying an agent replaces this paper account with that agent's current cash and open positions. No real orders are placed.

+
+
+

Paper positions

+
+
+
+
+
+

Browse Polymarket ideas

place paper bets
+
+
+
+
+

Paper activity

+
+
+
+
@@ -491,8 +547,9 @@ const VIEW_KEY = "pma_view_v1"; const PF_SORT_KEY = "pma_portfolio_sort_v1"; const CHART_RANGE_KEY = "pma_chart_range_v1"; const INVEST_KEY = "pma_invest_allocations_v1"; +const PAPER_KEY = "pma_paper_accounts_v1"; const CATEGORIES = ["All","Politics","Sports","Crypto","Economy","Pop Culture","Other"]; -const SYNC_KEYS=[AGENTS_KEY,SUG_KEY,FOCUS_KEY,VIEW_KEY,PF_SORT_KEY,CHART_RANGE_KEY,INVEST_KEY]; +const SYNC_KEYS=[AGENTS_KEY,SUG_KEY,FOCUS_KEY,VIEW_KEY,PF_SORT_KEY,CHART_RANGE_KEY,INVEST_KEY,PAPER_KEY]; const CAT_COLORS = {Politics:"#fb7185",Sports:"#34d399",Crypto:"#fbbf24",Economy:"#7c8cff", "Pop Culture":"#c77dff",Other:"#94a1bb",All:"#7c8cff",Copy:"#22d3ee"}; const DATA_API = "https://data-api.polymarket.com"; @@ -543,6 +600,8 @@ const $ = (id) => document.getElementById(id); const fmtUSD = (n) => "$" + Number(n).toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2}); const fmtPct = (n) => (n>=0?"+":"") + Number(n).toFixed(2) + "%"; const signClass = (n) => (n>0?"pos-val":n<0?"neg-val":""); +const marketSearchUrl=(q)=>`https://polymarket.com/search?query=${encodeURIComponent(q||"")}`; +const positionUrl=(pos)=>pos&&pos.url?pos.url:marketSearchUrl(pos&&pos.question); function partsInLocalTime(d=new Date()){ const parts=new Intl.DateTimeFormat("en-CA",{timeZone:"America/Toronto",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",hourCycle:"h23"}).formatToParts(d); return Object.fromEntries(parts.map(p=>[p.type,p.value])); @@ -1001,13 +1060,17 @@ async function fetchTraderPositions(wallet){ const d=await r.json(); return d.filter(p=>(p.outcome==="Yes"||p.outcome==="No")&&p.curPrice>0.02&&p.curPrice<0.98&&!p.redeemable&&p.size*p.curPrice>=1) .map(p=>({asset:String(p.asset),conditionId:p.conditionId,outcome:p.outcome,side:String(p.outcome).toUpperCase(), - curPrice:+p.curPrice,title:p.title||"market",value:p.size*p.curPrice})); + curPrice:+p.curPrice,title:p.title||"market",value:p.size*p.curPrice,url:rawPositionUrl(p)})); }catch(e){return [];} } function prettyName(n,w){ if(n&&!/^0x/i.test(n))return n; // real display name / pseudonym return "0x"+String(w).slice(2,6)+"โ€ฆ"+String(w).slice(-4); // anonymous โ†’ short wallet } +function rawPositionUrl(p){ + const slug=p.eventSlug||p.event_slug||p.slug||p.marketSlug||p.market_slug; + return slug?`https://polymarket.com/event/${slug}`:marketSearchUrl(p.title||p.question||""); +} async function discoverWhales(){ const picked=[]; try{ @@ -1058,7 +1121,8 @@ async function runWhaleAgent(p,wallet,decision){ 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, - original_shares:shares,original_cost:cost,unrealized_pnl:0,conviction:null,opened_at:cycleIso(),gain_stops:{},stop_losses:{}}); + original_shares:shares,original_cost:cost,unrealized_pnl:0,conviction:null,opened_at:cycleIso(),url:t.url||marketSearchUrl(t.title), + gain_stops:{},stop_losses:{}}); 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)}`}); } @@ -1090,7 +1154,8 @@ 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, - original_shares:shares,original_cost:cost,unrealized_pnl:0,conviction:lp.conviction,opened_at:cycleIso(),gain_stops:{},stop_losses:{}}); + original_shares:shares,original_cost:cost,unrealized_pnl:0,conviction:lp.conviction,opened_at:cycleIso(),url:lp.url||"", + gain_stops:{},stop_losses:{}}); 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)}`}); } @@ -1297,7 +1362,7 @@ function board(){ const MEDALS=["๐Ÿฅ‡","๐Ÿฅˆ","๐Ÿฅ‰"]; /* ---------- Rendering ---------- */ -function renderAll(){renderOverview();renderLeaderboard();renderSuggestions();renderPortfolioTab();renderInvestTab();renderAbout();} +function renderAll(){renderOverview();renderLeaderboard();renderSuggestions();renderPortfolioTab();renderInvestTab();renderPaperTab();renderAbout();} function renderOverview(){ const b=board(); const lead=b[0]; @@ -1643,11 +1708,13 @@ function renderPositions(positions){ return (b.unrealized_pnl||0)-(a.unrealized_pnl||0); }); root.innerHTML=ordered.map(p=>{const pnl=p.unrealized_pnl||0,col=catColor(p.category); + const title=`${esc(p.question)}`; return `
-
${esc(p.question)}
+
${title}
${p.category?`${esc(p.category)} `:""}${p.shares} ${p.side} @ ${Math.round(p.entry_price*100)}ยข โ†’ ${Math.round(p.current_price*100)}ยข
${esc(stopLossLabel(p))}
${esc(gainStopLabel(p))}
+
${fmtUSD(p.value)}
${pnl>=0?"+":""}${fmtUSD(pnl)}
`;}).join(""); } function renderHistory(history){ @@ -1655,6 +1722,83 @@ function renderHistory(history){ if(!history.length){root.innerHTML=`
No activity yet.
`;return;} root.innerHTML=history.map(h=>`
${h.action}${h.date} โ€” ${esc(h.detail)}
`).join(""); } +function defaultPaperAccount(name="Paper Trader"){ + return {id:"acct_"+Date.now().toString(36)+Math.random().toString(36).slice(2,6),name,cash:STARTING_BALANCE,starting_balance:STARTING_BALANCE,positions:[],history:[]}; +} +function loadPaperStore(){ + let store;try{store=JSON.parse(localStorage.getItem(PAPER_KEY));}catch(e){store=null;} + if(!store||!store.accounts){ + const acct=defaultPaperAccount("My Paper Account"); + store={activeId:acct.id,accounts:{[acct.id]:acct}}; + localStorage.setItem(PAPER_KEY,JSON.stringify(store)); + } + if(!store.accounts[store.activeId])store.activeId=Object.keys(store.accounts)[0]; + return store; +} +function savePaperStore(store,sync=true){ + localStorage.setItem(PAPER_KEY,JSON.stringify(store)); + if(sync)pushCloudState(); +} +function activePaperAccount(store=loadPaperStore()){ + return store.accounts[store.activeId]||Object.values(store.accounts)[0]; +} +function paperEquity(acct){ + return acct.cash+(acct.positions||[]).reduce((s,p)=>s+(p.value||p.shares*p.current_price||0),0); +} +function markPaperToMarket(acct){ + const sugs=(loadSuggestions().suggestions)||[]; + const byId={};sugs.forEach(s=>byId[s.market_id]=s); + (acct.positions||[]).forEach(pos=>{ + const s=byId[pos.market_id]; + if(s){ + const price=pos.side==="YES"?s.yes_price:s.no_price; + pos.current_price=+price.toFixed(4); + pos.url=s.url||pos.url||""; + } + pos.value=+(pos.shares*pos.current_price).toFixed(2); + pos.unrealized_pnl=+(pos.value-pos.cost).toFixed(2); + }); +} +function cloneAgentBook(agentId){ + const st=loadState(),src=st.agents[agentId],cfg=agentById(agentId); + if(!src)return null; + return {cash:+src.cash.toFixed(2),positions:JSON.parse(JSON.stringify(src.positions||[])), + history:[{date:logDay(),action:"COPY",detail:`Copied ${cfg.name}'s current paper book exactly.`}]}; +} +function paperBuy(marketId,side,amount){ + const store=loadPaperStore(),acct=activePaperAccount(store),s=((loadSuggestions().suggestions)||[]).find(x=>x.market_id===marketId); + if(!s)return toast("Market not found in current ideas."); + amount=Math.max(0,Math.min(Number(amount||0),acct.cash)); + if(amount<1)return toast("Enter a paper amount first."); + const entry=side==="YES"?s.yes_price:s.no_price; + if(entry<=0||entry>=1)return toast("That market price is unavailable."); + const shares=+(amount/entry).toFixed(2),cost=+(shares*entry).toFixed(2); + acct.cash=+(acct.cash-cost).toFixed(2); + 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)}`}); + savePaperStore(store);renderPaperTab();toast("Paper bet placed."); +} +function paperClose(index){ + const store=loadPaperStore(),acct=activePaperAccount(store),pos=acct.positions[index]; + if(!pos)return; + const proceeds=+(pos.shares*pos.current_price).toFixed(2),realized=+(proceeds-pos.cost).toFixed(2); + 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); + savePaperStore(store);renderPaperTab(); +} +function renderExactTrades(){ + const root=$("exactTrades");if(!root)return; + const rows=board().flatMap(row=>(row.p.positions||[]).map(pos=>({agent:row.c,pos}))) + .sort((a,b)=>(b.pos.value||0)-(a.pos.value||0)); + if(!rows.length){root.innerHTML=`
No open agent trades yet.
`;return;} + root.innerHTML=rows.slice(0,80).map(({agent,pos})=>`
+
${agent.emoji} ${esc(agent.name)} ยท ${esc(pos.question)}
+
${esc(pos.side)}${Number(pos.shares||0).toFixed(2)} sharesentry ${pct(pos.entry_price)}now ${pct(pos.current_price)}${fmtUSD(pos.value||0)}
+ Open market
+
`).join(""); +} function loadInvestAllocations(){ try{return JSON.parse(localStorage.getItem(INVEST_KEY))||{};}catch(e){return {};} } @@ -1668,7 +1812,7 @@ function agentExposureSummary(cfg,p,amount){ if(!amount||!open.length)return `
Add an amount to preview this agent's largest mapped exposures.
`; return open.map(pos=>{ const mapped=amount*((pos.value||0)/eq); - const q=pos.url?`${esc(pos.question.slice(0,52))}${pos.question.length>52?"...":""}`:`${esc(pos.question.slice(0,52))}${pos.question.length>52?"...":""}`; + const q=`${esc(pos.question.slice(0,52))}${pos.question.length>52?"...":""}`; return `
${q}
${esc(pos.side)} ยท ${esc(pos.category||"Other")}
${fmtUSD(mapped)}
`; }).join(""); } @@ -1711,6 +1855,69 @@ function renderInvestTab(){ input.oninput=saveDraft; input.onchange=()=>{saveDraft();pushCloudState();renderInvestTab();}; }); + renderExactTrades(); +} +function renderPaperTab(){ + const acctSelect=$("paperAccountSelect"),root=$("paperMarketList"); + if(!acctSelect||!root)return; + const store=loadPaperStore(),acct=activePaperAccount(store); + markPaperToMarket(acct);savePaperStore(store,false); + acctSelect.innerHTML=Object.values(store.accounts).map(a=>``).join(""); + acctSelect.onchange=()=>{store.activeId=acctSelect.value;savePaperStore(store);renderPaperTab();}; + const agentSel=$("copyAgentSelect"); + if(agentSel)agentSel.innerHTML=AGENTS.map(a=>``).join(""); + const eq=paperEquity(acct),pnl=eq-acct.starting_balance; + $("paperStats").innerHTML=[ + {ic:"๐Ÿ’ฐ",label:"Equity",value:fmtUSD(eq)}, + {ic:"๐Ÿ’ต",label:"Cash",value:fmtUSD(acct.cash)}, + {ic:"๐Ÿ“ˆ",label:"P&L",value:fmtUSD(pnl),cls:signClass(pnl)}, + {ic:"๐ŸŽฏ",label:"Return",value:fmtPct((eq/acct.starting_balance-1)*100),cls:signClass(pnl)}, + ].map(s=>`
${s.ic}
${s.label}
${s.value}
`).join(""); + renderPaperPositions(acct); + renderPaperMarkets(); + renderPaperHistory(acct); +} +function renderPaperPositions(acct){ + const root=$("paperPositions");if(!root)return; + if(!acct.positions.length){root.innerHTML=`
No paper positions yet. Copy an agent or place a paper bet from the market browser.
`;return;} + root.innerHTML=acct.positions.map((p,i)=>{const pnl=p.unrealized_pnl||0,col=catColor(p.category); + return `
+ +
${p.category?`${esc(p.category)} `:""}${p.shares} ${p.side} @ ${Math.round(p.entry_price*100)}ยข โ†’ ${Math.round(p.current_price*100)}ยข
+ +
${fmtUSD(p.value||0)}
${pnl>=0?"+":""}${fmtUSD(pnl)}
`; + }).join(""); + document.querySelectorAll("[data-paper-close]").forEach(btn=>btn.onclick=()=>paperClose(Number(btn.dataset.paperClose))); +} +function renderPaperMarkets(){ + const root=$("paperMarketList"),filter=$("paperFilterbar");if(!root)return; + const all=(loadSuggestions().suggestions)||[],focus=getFocus(); + if(filter){ + const counts={};CATEGORIES.forEach(c=>counts[c]=0);all.forEach(s=>{counts[counts[s.category]!==undefined?s.category:"Other"]++;});counts.All=all.length; + 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); + if(!shown.length){root.innerHTML=`
Run a cycle to load paper-tradable Polymarket ideas.
`;return;} + root.innerHTML=shown.map(s=>`
+
+
${esc(s.category)}YES ${pct(s.yes_price)}NO ${pct(s.no_price)}${Math.round(s.conviction)} conviction
+ Open market
+
+ + + +
+
`).join(""); + document.querySelectorAll("[data-paper-buy]").forEach(btn=>btn.onclick=()=>{ + const input=[...document.querySelectorAll("[data-paper-amount]")].find(el=>el.dataset.paperAmount===btn.dataset.paperBuy); + paperBuy(btn.dataset.paperBuy,btn.dataset.side,input&&input.value); + }); +} +function renderPaperHistory(acct){ + const root=$("paperHistory");if(!root)return; + const hist=(acct.history||[]).slice(-40).reverse(); + root.innerHTML=hist.length?hist.map(h=>`
${h.action}${h.date} โ€” ${esc(h.detail)}
`).join(""):`
No paper activity yet.
`; } function renderAbout(){ const root=$("aboutAgents");if(!root)return; @@ -1721,7 +1928,7 @@ function renderAbout(){ function esc(s){return String(s).replace(/[&<>"]/g,c=>({"&":"&","<":"<",">":">",'"':"""}[c]));} /* ---------- Tab routing ---------- */ -const TABS=["overview","leaderboard","suggestions","portfolio","invest","about"]; +const TABS=["overview","leaderboard","suggestions","portfolio","invest","paper","about"]; function showTab(name){ if(!TABS.includes(name))name="overview"; document.querySelectorAll(".tabpanel").forEach(p=>p.classList.toggle("active",p.dataset.tab===name)); @@ -1772,6 +1979,21 @@ $("resetBtn").addEventListener("click",()=>{ if(!confirm("Reset all ten agents to their $10,000 starting balance?"))return; saveState(defaultState());localStorage.removeItem(SUG_KEY);pushCloudState(true);toast("All agents reset.");renderAll(); }); +$("createPaperAccountBtn").addEventListener("click",()=>{ + const name=($("paperAccountName").value||"Paper Trader").trim().slice(0,40); + const store=loadPaperStore(),acct=defaultPaperAccount(name||"Paper Trader"); + store.accounts[acct.id]=acct;store.activeId=acct.id; + $("paperAccountName").value=""; + savePaperStore(store);renderPaperTab();toast("Paper account created."); +}); +$("copyAgentBtn").addEventListener("click",()=>{ + const agentId=$("copyAgentSelect").value,book=cloneAgentBook(agentId),cfg=agentById(agentId); + if(!book)return toast("Agent book unavailable."); + if(!confirm(`Replace this paper account with ${cfg.name}'s current paper positions?`))return; + const store=loadPaperStore(),acct=activePaperAccount(store); + acct.cash=book.cash;acct.positions=book.positions;acct.history=(acct.history||[]).concat(book.history); + savePaperStore(store);renderPaperTab();showTab("paper");toast(`Copied ${cfg.name} into paper account.`); +}); window.addEventListener("focus",()=>refreshFromCloudAndRender(true)); document.addEventListener("visibilitychange",()=>{ if(!document.hidden)refreshFromCloudAndRender(true);