fix: three issues — symbol parser, booked PnL window, token reauth

1. parseOptionSymbol (greeks.ts)
   Added Format 3 for SENSEX weekly DD+MON+STRIKE symbols (no year).
   SENSEX26JUN77300PE was returning null — parser read year=26, day=77
   giving invalid date 2026-06-77. Now correctly parses as day=26,
   month=JUN, year=2026 (inferred), strike=77300.

2. Booked PnL window (server.ts)
   Changed all three booked-PnL queries from 'today IST only' to
   'last 7 days'. With a stale Angel One token, positions are not
   updated today so date(updated_at)=today returns 0 even when
   real closed-position PnL exists from yesterday.

3. Angel One token expiry UX (server.ts + index.html)
   - Added POST /api/reauth endpoint — forces a fresh Angel One
     login without restarting the container
   - Nav now shows red error text +  Reauth button whenever
     lastError is set; clicking reauth calls /api/reauth then
     re-runs refresh + analysis
   - Fixed loadHealth() to show/hide the error span (was always
     hidden due to missing display toggle)
   - Label changed from 'Booked P&L Today' → 'Booked PnL (7d)'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Manohar 2026-06-24 10:07:20 +05:30
parent 1fbc9279a4
commit 8cd09d8a0e
2 changed files with 43 additions and 15 deletions

View file

@ -157,7 +157,8 @@
</div>
<div class="nav-r">
<span class="ts" id="ts">&#8212;</span>
<span id="s-err" style="display:none;font-size:.65rem;font-family:'Geist Mono',monospace"></span>
<span id="s-err" style="display:none;font-size:.65rem;font-family:'Geist Mono',monospace;color:var(--red)"></span>
<button class="ibtn" id="reauth-btn" onclick="forceReauth()" style="display:none;font-size:.65rem;color:var(--red);border-color:var(--red);padding:3px 8px">&#9889; Reauth</button>
<div class="pill" id="mkt-pill"><span class="dot"></span><span id="mkt-lbl">Checking</span></div>
<button class="ibtn" id="theme-btn" onclick="toggleTheme()">&#9728;&#65039;</button>
<button class="ibtn" onclick="refresh()">&#8635;</button>
@ -179,7 +180,7 @@
</div>
<div class="g3" style="margin-bottom:16px">
<div class="card scard"><div class="slbl">Open Positions</div><div class="sval" id="s-pos">&#8212;</div></div>
<div class="card scard"><div class="slbl">Booked P&amp;L Today</div><div class="sval" id="s-booked">&#8212;</div><div style="font-size:.6rem;color:var(--text3);margin-top:2px">Closed legs realised</div></div>
<div class="card scard"><div class="slbl">Booked PnL (7d)</div><div class="sval" id="s-booked">&#8212;</div><div style="font-size:.6rem;color:var(--text3);margin-top:2px">Closed legs realised</div></div>
<div class="card scard"><div class="slbl">Alerts Today</div><div class="sval amber" id="s-alrt">&#8212;</div></div>
</div>
<div class="card collapsible" id="sec-chart" style="margin-bottom:16px">
@ -469,11 +470,30 @@ async function loadHealth(){
const pill=document.getElementById('mkt-pill');
document.getElementById('mkt-lbl').textContent=d.marketOpen?'Market Open':'Market Closed';
pill.className='pill'+(d.marketOpen?' open':'');
const e=document.getElementById('s-err');
if(d.lastError){e.textContent=d.lastError.error.slice(0,30)+'\u2026';e.style.color='var(--red)';}
else{e.textContent='Healthy';e.style.color='var(--green)';}
const errEl=document.getElementById('s-err');
const reauthBtn=document.getElementById('reauth-btn');
if(d.lastError){
errEl.textContent=d.lastError.error.slice(0,40)+'\u2026';
errEl.style.display='inline';
if(reauthBtn)reauthBtn.style.display='inline-block';
} else {
errEl.textContent='';errEl.style.display='none';
if(reauthBtn)reauthBtn.style.display='none';
}
document.getElementById('ts').textContent=new Date().toLocaleTimeString('en-IN',{timeZone:'Asia/Kolkata',hour12:false})+' IST';
}
async function forceReauth(){
const btn=document.getElementById('reauth-btn');
if(btn){btn.textContent='Authing\u2026';btn.disabled=true;}
try{
const r=await fetch('/api/reauth',{method:'POST'}).then(function(x){return x.json();});
if(r.ok){await refresh();await loadAnalysis();}
else{alert('Reauth failed: '+r.error);}
}catch(e){alert('Reauth error: '+String(e));}
if(btn){btn.textContent='\u26a1 Reauth';btn.disabled=false;}
loadHealth();
}
async function loadConfig(){
const {global:g}=await fetch('/api/config').then(r=>r.json()).catch(()=>({}));
if(!g)return;

View file

@ -86,6 +86,18 @@ export function createServer(): express.Application {
res.json({ ok: true, message: 'Refresh complete' });
});
// ── POST /api/reauth ──────────────────────────────────────────────────────
// Force a fresh Angel One login (use when token has expired)
app.post('/api/reauth', async (_req, res) => {
try {
const { login } = await import('../angel/auth.js');
await login();
res.json({ ok: true, message: 'Re-authenticated with Angel One' });
} catch (err) {
res.status(500).json({ ok: false, error: String(err) });
}
});
// ── GET /api/pnl-history ──────────────────────────────────────────────────
// Returns P&L snapshots for charting. ?hours=N (default 8, max 72)
app.get('/api/pnl-history', (_req, res) => {
@ -105,15 +117,14 @@ export function createServer(): express.Application {
FROM positions WHERE is_closed = 0 AND netqty != 0
`).get() as any;
// Booked P&L: fully closed positions TODAY only (netqty=0, updated today IST)
// Booked P&L: fully closed positions in the last 7 days (covers a full trading week)
// 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 netqty = 0
AND realised_pnl != 0
AND date(updated_at, '+5 hours', '+30 minutes')
= date('now', '+5 hours', '+30 minutes')
AND updated_at >= datetime('now', '-7 days')
`).get() as any;
// Open-position realised = partial fills on still-open legs (shown separately)
@ -149,8 +160,7 @@ export function createServer(): express.Application {
const bookedRealised = (db.prepare(`
SELECT COALESCE(SUM(realised_pnl),0) as r FROM positions
WHERE netqty = 0 AND realised_pnl != 0
AND date(updated_at, '+5 hours', '+30 minutes')
= date('now', '+5 hours', '+30 minutes')
AND updated_at >= datetime('now', '-7 days')
`).get() as { r: number }).r;
res.json({
@ -166,18 +176,16 @@ export function createServer(): express.Application {
// ── GET /api/closed-positions ─────────────────────────────────────────────
// Positions closed TODAY (IST). Groups by expiry so UI can show per-expiry view.
// Positions closed in last 7 days with realised P&L.
app.get('/api/closed-positions', (_req, res) => {
const todayIST = "date('now', '+5 hours', '+30 minutes')";
// Today's closed positions only — filtered by IST date
// Last 7 days of closed positions (covers a full trading week + weekend buffer)
const closed = db.prepare(`
SELECT tradingsymbol, exchange, producttype, instrumenttype,
netqty, avg_price, ltp, realised_pnl, updated_at
FROM positions
WHERE realised_pnl != 0
AND netqty = 0
AND date(updated_at, '+5 hours', '+30 minutes') = date('now', '+5 hours', '+30 minutes')
AND updated_at >= datetime('now', '-7 days')
ORDER BY ABS(realised_pnl) DESC
`).all();