diff --git a/src/api/server.ts b/src/api/server.ts index 6ca9d0f..454c7f4 100644 --- a/src/api/server.ts +++ b/src/api/server.ts @@ -78,6 +78,14 @@ export function createServer(): express.Application { }); // ── GET /api/health ─────────────────────────────────────────────────────── + // ── POST /api/refresh ───────────────────────────────────────────────────── + // Force-fetch positions right now (works outside market hours) + app.post('/api/refresh', async (_req, res) => { + const { forcePoll } = await import('../tracker/poll.js'); + await forcePoll(); + res.json({ ok: true, message: 'Refresh complete' }); + }); + app.get('/api/health', (_req, res) => { const lastError = db.prepare( `SELECT error, occurred_at FROM poll_errors ORDER BY occurred_at DESC LIMIT 1` diff --git a/src/index.ts b/src/index.ts index 2d59429..94e15b6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,7 @@ import cron from 'node-cron'; import { initDb } from './db/client.js'; import { login } from './angel/auth.js'; -import { pollTick } from './tracker/poll.js'; +import { pollTick, forcePoll } from './tracker/poll.js'; import { createServer } from './api/server.js'; import { sendServiceNotification } from './notify/telegram.js'; @@ -25,7 +25,7 @@ async function main() { }); // 4. Run first poll immediately so dashboard shows data on startup - await pollTick(); + await forcePoll(); // 5. Schedule recurring poll // node-cron doesn't support sub-minute; for 60s we use a cron expression. diff --git a/src/tracker/poll.ts b/src/tracker/poll.ts index 69ec1a0..b7dacbb 100644 --- a/src/tracker/poll.ts +++ b/src/tracker/poll.ts @@ -169,3 +169,37 @@ async function evaluateAlerts(positions: Position[], today: string): Promise { + const today = todayIST(); + try { + const positions = await fetchAllPositions(); + const upsertPos = db.prepare(` + INSERT INTO positions (key, exchange, tradingsymbol, instrumenttype, producttype, + netqty, ltp, avg_price, unrealised_pnl, realised_pnl, total_pnl, source, updated_at) + VALUES (@key, @exchange, @tradingsymbol, @instrumenttype, @producttype, + @netqty, @ltp, @avg_price, @unrealised_pnl, @realised_pnl, @total_pnl, @source, datetime('now')) + ON CONFLICT(key) DO UPDATE SET + netqty = excluded.netqty, ltp = excluded.ltp, avg_price = excluded.avg_price, + unrealised_pnl = excluded.unrealised_pnl, realised_pnl = excluded.realised_pnl, + total_pnl = excluded.total_pnl, updated_at = excluded.updated_at + `); + for (const pos of positions) { + upsertPos.run({ key: pos.key, exchange: pos.exchange, tradingsymbol: pos.tradingsymbol, + instrumenttype: pos.instrumenttype, producttype: pos.producttype, netqty: pos.netqty, + ltp: pos.ltp, avg_price: pos.avgPrice, unrealised_pnl: pos.unrealisedPnl, + realised_pnl: pos.realisedPnl, total_pnl: pos.totalPnl, source: pos.source }); + } + const activeKeys = positions.map(p => p.key); + if (activeKeys.length > 0) { + db.prepare(`UPDATE positions SET is_closed = 1 WHERE key NOT IN (${activeKeys.map(() => "?").join(",")})`).run(...activeKeys); + } + console.log(`[poll] Force fetch: ${positions.length} positions`); + } catch (err) { + console.error(`[poll] Force fetch error: ${err instanceof Error ? err.message : err}`); + } +}