diff --git a/src/angel/auth.ts b/src/angel/auth.ts index f685c30..5212983 100644 --- a/src/angel/auth.ts +++ b/src/angel/auth.ts @@ -6,6 +6,7 @@ import { AngelAuthResponse } from './types.js'; let jwtToken: string | null = null; let refreshToken: string | null = null; let tokenExpiry: Date | null = null; +let loginMutex: Promise | null = null; // prevents concurrent logins const BASE_URL = 'https://apiconnect.angelbroking.com'; @@ -87,9 +88,12 @@ export async function login(): Promise { export async function getToken(): Promise { const now = new Date(); - // No token yet, or past expiry if (!jwtToken || !tokenExpiry || now >= tokenExpiry) { - await login(); + // Mutex: wait for in-progress login instead of starting a second one + if (!loginMutex) { + loginMutex = login().finally(() => { loginMutex = null; }); + } + await loginMutex; } return jwtToken!; diff --git a/src/angel/client.ts b/src/angel/client.ts index 2cbd39a..6003b8d 100644 --- a/src/angel/client.ts +++ b/src/angel/client.ts @@ -54,8 +54,9 @@ export async function fetchHoldings(): Promise { */ function normalisePosition(p: AngelPosition): Position | null { const netqty = parseFloat(p.netqty); - // Skip positions with zero net qty (fully closed intraday) - if (netqty === 0) return null; + const realisedPnl = parseFloat(p.realised) || 0; + // Only skip if qty=0 AND no realised PnL (truly empty row) + if (netqty === 0 && realisedPnl === 0) return null; const ltp = parseFloat(p.ltp) || 0; const unrealised = parseFloat(p.unrealised) || 0; diff --git a/src/angel/market.ts b/src/angel/market.ts index 68882e9..3dc4257 100644 --- a/src/angel/market.ts +++ b/src/angel/market.ts @@ -93,13 +93,14 @@ export async function fetchMarketData(): Promise { try { const live = await fetchFromAngel(); if (live.length > 0) { - saveToCache(live); // update cache whenever we get fresh data + saveToCache(live); return live; } + // Empty from Angel (market closed) — serve cache + return loadFromCache(); } catch (err) { - console.error('[market] Angel fetch error:', err instanceof Error ? err.message : err); + // Auth/network error — always serve last known cache so UI never goes blank + console.error('[market] fetch error, serving cache:', err instanceof Error ? err.message : err); + return loadFromCache(); } - // Market closed or error — serve cached data - const cached = loadFromCache(); - return cached; // empty array if never cached (first run before market open) } diff --git a/src/tracker/poll.ts b/src/tracker/poll.ts index c457e3e..d2bf6c8 100644 --- a/src/tracker/poll.ts +++ b/src/tracker/poll.ts @@ -109,7 +109,9 @@ async function evaluateAlerts(positions: Position[], today: string): Promise p.netqty !== 0); + for (const pos of openPositions) { let state = getBandState.get(pos.key) as BandState | undefined; // First time seeing this position, or new trading day → initialise anchor