diff --git a/public/index.html b/public/index.html index 5813f86..e7ce7a7 100644 --- a/public/index.html +++ b/public/index.html @@ -156,7 +156,7 @@
Unrealised P&L
Open positions MTM
-
Realised P&L
Closed legs today
+
Realised P&L
Booked today (netqty=0)
Total P&L
Unrealised + Realised
@@ -307,7 +307,7 @@ async function loadChart(h=8){ curH=h;['b2','b8','b24','b72'].forEach(id=>document.getElementById(id)?.classList.remove('on')); document.getElementById({2:'b2',8:'b8',24:'b24',72:'b72'}[h])?.classList.add('on'); const {summary,history}=await fetch('/api/pnl-history?hours='+h).then(r=>r.json()); - if(summary){const set=(id,v)=>{const e=document.getElementById(id);e.textContent=fmt(v);e.className='pval '+cls(v)};set('v-u',summary.totalUnrealised);set('v-r',summary.totalRealised);set('v-t',summary.totalPnl);document.getElementById('s-pos').textContent=summary.openPositions;var bEl=document.getElementById('s-booked');if(bEl){bEl.textContent=fmt(summary.bookedPnl||0);bEl.className='sval '+cls(summary.bookedPnl||0);}} + if(summary){const set=(id,v)=>{const e=document.getElementById(id);e.textContent=fmt(v);e.className='pval '+cls(v)};set('v-u',summary.totalUnrealised);set('v-r',summary.bookedPnl||0);set('v-t',summary.totalPnl);document.getElementById('s-pos').textContent=summary.openPositions;var bEl=document.getElementById('s-booked');if(bEl){bEl.textContent=fmt(summary.bookedPnl||0);bEl.className='sval '+cls(summary.bookedPnl||0);}var bEl=document.getElementById('s-booked');if(bEl){bEl.textContent=fmt(summary.bookedPnl||0);bEl.className='sval '+cls(summary.bookedPnl||0);}} const canvas=document.getElementById('pnl-chart'),empty=document.getElementById('chart-empty'); if(!history||history.length<2){canvas.style.display='none';empty.classList.add('show');return;} canvas.style.display='block';empty.classList.remove('show'); @@ -374,7 +374,7 @@ async function loadPositions(){ if(expiries.length>2){ tabsEl.style.display='flex'; tabsEl.innerHTML=expiries.map(function(e){ - return ''; }).join(''); } else { @@ -391,7 +391,7 @@ async function loadPositions(){ // Update collapsed header badge const phEl=document.getElementById('pos-hpnl'); if(phEl){phEl.textContent=fmt(sT);phEl.className='';phEl.style.cssText='font-family:Geist Mono,monospace;font-size:.75rem;font-weight:600;display:inline;color:'+(sT>50?'var(--green)':sT<-50?'var(--red)':'var(--text2)');} - tbody.innerHTML=open.map(p=>{const tte=parseTTE(p.tradingsymbol);return'
'+p.tradingsymbol+'
'+p.exchange+' \u00b7 '+p.producttype+'
'+(tte?'
'+tte+'
':'')+''+(p.netqty>0?'+':'')+p.netqty+'\u20b9'+(+p.ltp).toFixed(2)+'\u20b9'+(+p.avg_price).toFixed(2)+''+fmt(p.unrealised_pnl)+''+fmt(p.realised_pnl)+''+fmt(p.total_pnl)+'';}).join(''); + tbody.innerHTML=filtered.map(p=>{const tte=parseTTE(p.tradingsymbol);return'
'+p.tradingsymbol+'
'+p.exchange+' \u00b7 '+p.producttype+'
'+(tte?'
'+tte+'
':'')+''+(p.netqty>0?'+':'')+p.netqty+'\u20b9'+(+p.ltp).toFixed(2)+'\u20b9'+(+p.avg_price).toFixed(2)+''+fmt(p.unrealised_pnl)+''+fmt(p.realised_pnl)+''+fmt(p.total_pnl)+'';}).join(''); tfoot.style.display=''; const sf=(id,v)=>{const e=document.getElementById(id);e.textContent=fmt(v);e.className='mono '+cls(v)};sf('f-u',sU);sf('f-r',sR);sf('f-t',sT); document.getElementById('override-body').innerHTML=filtered.map(p=>'
'+p.tradingsymbol+'
').join(''); diff --git a/src/api/server.ts b/src/api/server.ts index 17285e8..aad515b 100644 --- a/src/api/server.ts +++ b/src/api/server.ts @@ -105,30 +105,35 @@ export function createServer(): express.Application { FROM positions WHERE is_closed = 0 AND netqty != 0 `).get() as any; - // Realised P&L from positions updated TODAY (IST) only. - // '+5h30m' converts UTC stored time -> IST date for filtering. - // This ensures we never include historical expiry P&L from previous days. - const closedRealised = db.prepare(` + // Booked P&L: fully closed positions TODAY only (netqty=0, updated today IST) + // Separate from open-position realised (partial fills on live legs) + const bookedToday = db.prepare(` SELECT COALESCE(SUM(realised_pnl),0) as r FROM positions - WHERE realised_pnl != 0 + WHERE netqty = 0 + AND realised_pnl != 0 AND date(updated_at, '+5 hours', '+30 minutes') = date('now', '+5 hours', '+30 minutes') `).get() as any; + // Open-position realised = partial fills on still-open legs (shown separately) const totalUnrealised = liveOpen.u; - const totalRealised = liveOpen.r + closedRealised.r; + const openRealised = liveOpen.r; + const bookedPnl = bookedToday.r; + const totalRealised = openRealised + bookedPnl; const totalPnl = totalUnrealised + totalRealised; + const closedRealised = bookedToday; // keep alias for summary below const latest = rows[rows.length - 1] as any; res.json({ ok: true, summary: { totalUnrealised, + openRealised, + bookedPnl, totalRealised, totalPnl, openPositions: liveOpen.cnt, - bookedPnl: closedRealised.r, asOf: latest?.recorded_at ?? new Date().toISOString(), }, history: rows, @@ -143,7 +148,7 @@ export function createServer(): express.Application { const posCount = (db.prepare(`SELECT COUNT(*) as n FROM positions WHERE is_closed = 0 AND netqty != 0`).get() as { n: number }).n; const bookedRealised = (db.prepare(` SELECT COALESCE(SUM(realised_pnl),0) as r FROM positions - WHERE realised_pnl != 0 + WHERE netqty = 0 AND realised_pnl != 0 AND date(updated_at, '+5 hours', '+30 minutes') = date('now', '+5 hours', '+30 minutes') `).get() as { r: number }).r;