| |
| 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()); |
| } |