// Portfolio: the paper wallet, open positions with live PnL, and every paper trade. import { h, age, usd, sol as fsol, compact, signed } from './util.js'; import { icon } from './icons.js'; import { live } from './net.js'; import { paper } from './paper.js'; import { avatar, emptyState } from './parts.js'; import { openSettings } from './settings.js'; export function portfolioView(root) { const wrap = h('div.wrap'); root.append(h('div.page.page-in', null, wrap)); const tiles = h('div.tiles'); const posBody = h('div'); const histBody = h('div'); wrap.append( h('div.ph-h', null, h('div', null, h('h1', null, 'Portfolio'), h('p', null, 'Paper wallet. Fills use the live pool at the moment you click.')), h('button.btn', { onclick: () => openSettings(), html: icon('reset', 14) + 'Reset or change balance' })), tiles, h('div.panel', null, h('div.panel-h', null, h('h3', null, 'Open positions')), posBody), h('div.panel', null, h('div.panel-h', null, h('h3', null, 'Trade history')), histBody)); const coinCell = (m, x) => h('a.coin-cell', { href: `/coin/${m}`, 'data-link': '' }, avatar({ m, s: x.s, img: x.img }, 30), h('div', null, h('b', null, x.s || '–'), h('div.mut', { style: { fontSize: '11.5px' } }, x.n || ''))); function draw() { const sol = live.sol || 0; const t = paper.totals(); const growth = t.total - t.start; const tile = (k, v, s, cls) => h('div.tile', null, h('span.k', null, k), h('span.v' + (cls ? '.' + cls : ''), null, v), h('span.s', null, s)); tiles.replaceChildren( tile('Total value', fsol(t.total) + ' SOL', usd(t.total * sol)), tile('Cash', fsol(t.sol) + ' SOL', usd(t.sol * sol)), tile('Open PnL', signed(t.unreal, x => fsol(x)) + ' SOL', `${t.cost ? signed(t.unreal / t.cost * 100, x => x.toFixed(1) + '%') : '–'} on ${fsol(t.cost)} SOL`, t.unreal >= 0 ? 'up' : 'down'), tile('Since start', signed(growth, x => fsol(x)) + ' SOL', `${signed(growth / t.start * 100, x => x.toFixed(1) + '%')} from ${fsol(t.start)} SOL`, growth >= 0 ? 'up' : 'down')); const open = Object.entries(paper.s.pos).filter(([, p]) => p.tok > 0).sort((a, b) => (b[1].last || 0) - (a[1].last || 0)); if (!open.length) posBody.replaceChildren(emptyState('No open positions', 'Quick buy on Pulse or buy on a coin page.')); else { posBody.replaceChildren(h('table.t', null, h('thead', null, h('tr', null, h('th', null, 'Coin'), h('th.r', null, 'Value'), h('th.r', null, 'Cost'), h('th.r', null, 'PnL'), h('th.r', null, 'MC now'), h('th.r', null, 'Entry MC'), h('th.r', null, ''))), h('tbody', null, open.map(([m, p]) => { const q = paper.px[m]; const value = paper.value(m); const pnl = value - p.cost; const pp = p.cost ? pnl / p.cost * 100 : 0; const entry = p.tok && q && q.price && q.mc ? (p.cost / p.tok) / q.price * q.mc : 0; return h('tr', null, h('td', null, coinCell(m, p)), h('td.r', null, h('b', null, fsol(value) + ' SOL'), h('div.mut', null, usd(value * sol))), h('td.r', null, fsol(p.cost) + ' SOL'), h('td.r.' + (pnl >= 0 ? 'up' : 'down'), null, h('b', null, signed(pnl, x => fsol(x)) + ' SOL'), h('div', null, signed(pp, x => x.toFixed(1) + '%'))), h('td.r', null, q?.mc ? usd(q.mc * sol) : '–'), h('td.r.mut', null, entry ? usd(entry * sol) : '–'), h('td.r', null, h('div', { style: { display: 'inline-flex', gap: '6px' } }, h('button.btn.sm', { onclick: () => paper.sell(m, 0.5) }, 'Sell 50%'), h('button.btn.sm', { onclick: () => paper.sell(m, 1) }, 'Sell all')))); })))); } const hist = paper.s.hist.slice(0, 200); if (!hist.length) histBody.replaceChildren(emptyState('No trades yet')); else histBody.replaceChildren(h('table.t', null, h('thead', null, h('tr', null, h('th', null, 'Age'), h('th', null, 'Coin'), h('th', null, 'Type'), h('th.r', null, 'SOL'), h('th.r', null, 'Amount'), h('th.r', null, 'MC'))), h('tbody', null, hist.map(x => h('tr', null, h('td.mut', null, age(x.ts)), h('td', null, coinCell(x.m, x)), h('td', null, h('b.' + (x.buy ? 'up' : 'down'), null, x.buy ? 'Buy' : 'Sell')), h('td.r', null, fsol(x.sol)), h('td.r', null, compact(x.tok, 2)), h('td.r', null, usd(x.mc * sol))))))); } draw(); let busy = false; wrap.addEventListener('pointerdown', () => { busy = true; }); wrap.addEventListener('pointerup', () => setTimeout(() => { busy = false; }, 300)); const offs = [paper.on('change', draw), paper.on('prices', () => { if (!busy) draw(); })]; return () => offs.forEach(f => f()); }