fix: tab filtering uses filtered not open; tab class quote; booked=netqty=0 today only

This commit is contained in:
Manohar 2026-05-27 13:32:35 +05:30
parent e8bb8fdd30
commit a1945d06e2
2 changed files with 17 additions and 12 deletions

View file

@ -156,7 +156,7 @@
</nav>
<div class="g3">
<div class="card pcard pu"><div class="plbl">Unrealised P&amp;L</div><div class="pval fl" id="v-u">&#8212;</div><div class="psub">Open positions MTM</div></div>
<div class="card pcard pr"><div class="plbl">Realised P&amp;L</div><div class="pval fl" id="v-r">&#8212;</div><div class="psub">Closed legs today</div></div>
<div class="card pcard pr"><div class="plbl">Realised P&amp;L</div><div class="pval fl" id="v-r">&#8212;</div><div class="psub">Booked today (netqty=0)</div></div>
<div class="card pcard pt"><div class="plbl">Total P&amp;L</div><div class="pval fl" id="v-t">&#8212;</div><div class="psub">Unrealised + Realised</div></div>
</div>
<div class="g6" id="market-grid">
@ -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 '<button class="exp-tab'+(e===_activeExpiry?' active':'"')+'" onclick="event.stopPropagation();setExpiry(this,\''+e+'\')">'+
return '<button class="exp-tab'+(e===_activeExpiry?' active':'')+'" onclick="event.stopPropagation();setExpiry(this,\''+e+'\')">'+
(e==='ALL'?'All Expiries':e)+'</button>';
}).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'<tr><td><div class="sym">'+p.tradingsymbol+'</div><div class="sym-meta">'+p.exchange+' \u00b7 '+p.producttype+'</div>'+(tte?'<div class="tte">'+tte+'</div>':'')+'</td><td class="'+(p.netqty<0?'qs':'ql')+'">'+(p.netqty>0?'+':'')+p.netqty+'</td><td class="mono">\u20b9'+(+p.ltp).toFixed(2)+'</td><td class="mono" style="color:var(--text2)">\u20b9'+(+p.avg_price).toFixed(2)+'</td><td class="mono '+cls(p.unrealised_pnl)+'">'+fmt(p.unrealised_pnl)+'</td><td class="mono '+cls(p.realised_pnl)+'">'+fmt(p.realised_pnl)+'</td><td class="mono '+cls(p.total_pnl)+'" style="font-weight:600">'+fmt(p.total_pnl)+'</td></tr>';}).join('');
tbody.innerHTML=filtered.map(p=>{const tte=parseTTE(p.tradingsymbol);return'<tr><td><div class="sym">'+p.tradingsymbol+'</div><div class="sym-meta">'+p.exchange+' \u00b7 '+p.producttype+'</div>'+(tte?'<div class="tte">'+tte+'</div>':'')+'</td><td class="'+(p.netqty<0?'qs':'ql')+'">'+(p.netqty>0?'+':'')+p.netqty+'</td><td class="mono">\u20b9'+(+p.ltp).toFixed(2)+'</td><td class="mono" style="color:var(--text2)">\u20b9'+(+p.avg_price).toFixed(2)+'</td><td class="mono '+cls(p.unrealised_pnl)+'">'+fmt(p.unrealised_pnl)+'</td><td class="mono '+cls(p.realised_pnl)+'">'+fmt(p.realised_pnl)+'</td><td class="mono '+cls(p.total_pnl)+'" style="font-weight:600">'+fmt(p.total_pnl)+'</td></tr>';}).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=>'<tr><td><div class="sym" style="font-size:.75rem">'+p.tradingsymbol+'</div></td><td><input type="number" min="1" max="50" step="0.5" placeholder="Global default" id="op-'+p.key+'" style="width:130px;padding:4px 8px;border-radius:7px;border:1px solid var(--border2);background:var(--bg3);color:var(--text);font-size:.75rem;font-family:\'Geist Mono\',monospace;outline:none"/></td><td><input type="datetime-local" id="om-'+p.key+'" style="padding:4px 8px;border-radius:7px;border:1px solid var(--border2);background:var(--bg3);color:var(--text);font-size:.72rem;outline:none"/></td><td><button onclick="saveOverride(\''+p.key+'\')" style="padding:3px 10px;border-radius:7px;background:var(--bg3);border:1px solid var(--border2);color:var(--text2);font-size:.72rem;cursor:pointer">Save</button></td></tr>').join('');

View file

@ -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;