feat: 30s polling, market open/close Telegram alerts, mobile responsive UI
This commit is contained in:
parent
ea6af0ea82
commit
ed84985237
3 changed files with 87 additions and 1 deletions
|
|
@ -95,6 +95,43 @@
|
|||
.collapse-body { overflow:hidden; transition:max-height .35s cubic-bezier(.4,0,.2,1); max-height:5000px; }
|
||||
.collapsible.collapsed .collapse-body { max-height:0 !important; }
|
||||
|
||||
|
||||
/* ── Mobile responsive ── */
|
||||
@media (max-width: 768px) {
|
||||
.wrap { padding: 12px; }
|
||||
nav { padding: 10px 14px; flex-wrap: wrap; gap: 8px; }
|
||||
.nav-sub { display: none; }
|
||||
.ts { display: none; }
|
||||
.g3, .g6 { grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 10px; }
|
||||
.g6 { grid-template-columns: repeat(3, 1fr); }
|
||||
.pcard { padding: 16px 14px 12px; }
|
||||
.pval { font-size: 1.6rem; }
|
||||
.pcard.pt .pval { font-size: 2rem; }
|
||||
.mcard { padding: 10px 12px; }
|
||||
.mprice { font-size: 1.1rem; }
|
||||
.scard { padding: 10px 12px; }
|
||||
.card-head { padding: 12px 14px; flex-wrap: wrap; gap: 6px; }
|
||||
.chart-wrap { height: 160px; padding: 8px 10px 12px; }
|
||||
.range-btns { gap: 2px; }
|
||||
.rbtn { padding: 2px 7px; font-size: .65rem; }
|
||||
/* Tables: horizontal scroll */
|
||||
.card > .collapse-body { overflow-x: auto; }
|
||||
table { min-width: 520px; }
|
||||
td, thead th { padding: 9px 10px; font-size: .75rem; }
|
||||
.sym { font-size: .72rem; }
|
||||
.sym-meta, .tte { font-size: .6rem; }
|
||||
.mono { font-size: .72rem; }
|
||||
/* Settings */
|
||||
.sg { grid-template-columns: 1fr; gap: 10px; }
|
||||
tfoot td { padding: 8px 10px; font-size: .72rem; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.g3 { grid-template-columns: 1fr; }
|
||||
.g6 { grid-template-columns: repeat(2, 1fr); }
|
||||
.pval { font-size: 1.4rem; }
|
||||
.pcard.pt .pval { font-size: 1.7rem; }
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import cron from 'node-cron';
|
||||
import { initDb } from './db/client.js';
|
||||
import { login } from './angel/auth.js';
|
||||
import { pollTick, forcePoll } from './tracker/poll.js';
|
||||
import { pollTick, forcePoll, sendMarketOpenAlert, sendPreCloseAlert } from './tracker/poll.js';
|
||||
import { createServer } from './api/server.js';
|
||||
import { sendServiceNotification } from './notify/telegram.js';
|
||||
|
||||
|
|
@ -39,6 +39,12 @@ async function main() {
|
|||
console.log(`[main] Polling every ${POLL_SECONDS}s`);
|
||||
}
|
||||
|
||||
|
||||
// Market open alert — 9:15 IST = 3:45 UTC
|
||||
cron.schedule("45 3 * * 1-5", sendMarketOpenAlert, { timezone: "UTC" });
|
||||
// Pre-close alert — 3:25 IST = 9:55 UTC
|
||||
cron.schedule("55 9 * * 1-5", sendPreCloseAlert, { timezone: "UTC" });
|
||||
console.log("[main] Market open/close alerts scheduled");
|
||||
// 6. Notify Telegram that service started
|
||||
await sendServiceNotification('start');
|
||||
|
||||
|
|
|
|||
|
|
@ -218,3 +218,46 @@ function recordSnapshot(positions: Position[]): void {
|
|||
VALUES (?, ?, ?, ?, datetime('now'))
|
||||
`).run(totalUnrealised, totalRealised, totalPnl, positions.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Market open alert (9:15 IST) — summary of all positions at open
|
||||
*/
|
||||
export async function sendMarketOpenAlert(): Promise<void> {
|
||||
try {
|
||||
const positions = await fetchAllPositions();
|
||||
if (!positions.length) {
|
||||
await sendTelegram("🔔 *Market Open* — No open positions");
|
||||
return;
|
||||
}
|
||||
const totalPnl = positions.reduce((s, p) => s + p.totalPnl, 0);
|
||||
const lines = positions.map(p =>
|
||||
` • ${p.tradingsymbol}: ₹${p.totalPnl >= 0 ? "+" : ""}${p.totalPnl.toFixed(0)} (qty ${p.netqty})`
|
||||
).join("\n");
|
||||
await sendTelegram(
|
||||
`🔔 *Market Open — Position Summary*\n\n${lines}\n\n*Total P&L: ₹${totalPnl >= 0 ? "+" : ""}${totalPnl.toFixed(0)}*`
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("[poll] Market open alert error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Market close warning (3:25 IST) — 5 min before close
|
||||
*/
|
||||
export async function sendPreCloseAlert(): Promise<void> {
|
||||
try {
|
||||
const positions = await fetchAllPositions();
|
||||
const totalUnrealised = positions.reduce((s, p) => s + p.unrealisedPnl, 0);
|
||||
const totalRealised = positions.reduce((s, p) => s + p.realisedPnl, 0);
|
||||
const totalPnl = positions.reduce((s, p) => s + p.totalPnl, 0);
|
||||
await sendTelegram(
|
||||
`⚠️ *5 Min to Close — Review Positions*\n\n` +
|
||||
`Unrealised: ₹${totalUnrealised >= 0 ? "+" : ""}${totalUnrealised.toFixed(0)}\n` +
|
||||
`Realised: ₹${totalRealised >= 0 ? "+" : ""}${totalRealised.toFixed(0)}\n` +
|
||||
`*Total: ₹${totalPnl >= 0 ? "+" : ""}${totalPnl.toFixed(0)}*\n\n` +
|
||||
`${positions.length} open positions — consider closing before 3:30`
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("[poll] Pre-close alert error:", e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue