/* Company Stock Price — Stock Screener (live NSE quotes, API fields only) */ const SCREENER_FILTERS = ['All', 'Gainers', 'Losers', 'Unchanged']; const SORT_OPTS = [ { key: 'price', label: 'Price' }, { key: 'chg', label: 'Change %' }, { key: 'vol', label: 'Volume' }, { key: 'hi52', label: '52W High' }, { key: 'lo52', label: '52W Low' }, ]; const SCREENER_PAGE = 25; const COLS = [ { key: 'rank', label: '#', cls: 'col-rank', sortable: false }, { key: 'company', label: 'Company', cls: 'col-company', sortable: false }, { key: 'price', label: 'Price', sortable: true }, { key: 'chg', label: 'Chg %', sortable: true }, { key: 'vol', label: 'Volume', sortable: true }, { key: 'hi52', label: '52W High', sortable: true }, { key: 'lo52', label: '52W Low', sortable: true }, { key: 'action', label: '', sortable: false }, ]; const QB_FIELDS = [ { key: 'price', label: 'Price', unit: '₹', fmt: (v) => '₹' + fmt.price(v) }, { key: 'chg', label: '1-Day Change', unit: '%', fmt: (v) => fmt.pct(v) }, { key: 'vol', label: 'Volume', unit: 'sh', fmt: (v) => (v != null ? fmt.vol(v) : '—') }, { key: 'fromHi', label: '% below 52W High', unit: '%', derive: (s) => s.hi52 ? +((1 - s.price / s.hi52) * 100).toFixed(2) : null, fmt: (v) => v != null ? v.toFixed(1) + '%' : '—' }, { key: 'fromLo', label: '% above 52W Low', unit: '%', derive: (s) => s.lo52 ? +((s.price / s.lo52 - 1) * 100).toFixed(2) : null, fmt: (v) => v != null ? v.toFixed(1) + '%' : '—' }, ]; const QB_OPS = [ { key: 'gt', label: '>', test: (a, b) => a > b }, { key: 'lt', label: '<', test: (a, b) => a < b }, { key: 'gte', label: '≥', test: (a, b) => a >= b }, { key: 'lte', label: '≤', test: (a, b) => a <= b }, ]; const QB_PRESETS = [ { name: 'Momentum movers', conds: [{ field: 'chg', op: 'gt', val: 1 }, { field: 'fromHi', op: 'lt', val: 8 }] }, { name: 'Near 52W low', conds: [{ field: 'fromLo', op: 'lt', val: 15 }] }, { name: 'High volume', conds: [{ field: 'vol', op: 'gt', val: 1000000 }] }, { name: 'Pullback', conds: [{ field: 'fromHi', op: 'gt', val: 10 }, { field: 'chg', op: 'lt', val: 0 }] }, ]; function fieldVal(field, s) { const def = QB_FIELDS.find((f) => f.key === field); return def.derive ? def.derive(s) : s[field]; } function QueryBuilder({ stocks, conds, setConds, onOpenStock }) { function addCond() { setConds([...conds, { field: 'chg', op: 'gt', val: 0.5 }]); } function update(i, patch) { setConds(conds.map((c, j) => (j === i ? { ...c, ...patch } : c))); } function remove(i) { setConds(conds.filter((_, j) => j !== i)); } let results = stocks.filter((s) => conds.every((c) => { const op = QB_OPS.find((o) => o.key === c.op); const val = fieldVal(c.field, s); if (val == null) return false; return op.test(val, Number(c.val)); })); results = [...results].sort((a, b) => b.chg - a.chg); return (

Build your screen

Stack conditions — all must match (AND). Live Yahoo NSE data.
{results.length} match{results.length === 1 ? '' : 'es'}
{QB_PRESETS.map((p) => ( ))}
{conds.map((c, i) => { const f = QB_FIELDS.find((x) => x.key === c.field) || QB_FIELDS[0]; return (
{i === 0 ? 'WHERE' : 'AND'} update(i, { val: e.target.value })} /> {f.unit}
); })}
{results.length ? : }
); } function ScreenerResults({ rows, onOpenStock, sortKey, sortDir, onSort }) { const [page, setPage] = useState(1); const ind = sortDir === 'desc' ? '↓' : '↑'; const totalPages = Math.max(1, Math.ceil(rows.length / SCREENER_PAGE)); const pageRows = rows.slice((page - 1) * SCREENER_PAGE, page * SCREENER_PAGE); const rankBase = (page - 1) * SCREENER_PAGE; useEffect(() => { setPage(1); }, [rows.length, sortKey, sortDir]); return ( <>
{COLS.map((c) => ( ))} {pageRows.map((s, i) => ( onOpenStock(s)} style={{ cursor: 'pointer' }}> ))}
onSort(c.key) : undefined}> {c.label}{c.sortable && onSort && sortKey === c.key && {ind}}
{rankBase + i + 1}
{s.name}
{s.ticker}
₹{fmt.price(s.price)} {s.vol != null ? fmt.vol(s.vol) : '—'} {s.hi52 != null ? fmt.price(s.hi52) : '—'} {s.lo52 != null ? fmt.price(s.lo52) : '—'}
{rows.length > SCREENER_PAGE && (
Showing {rankBase + 1}–{rankBase + pageRows.length} of {rows.length}
Page {page} / {totalPages}
)} ); } function ScreenerScreen({ onOpenStock, mode, onModeChange }) { const MD = useMarketData(); const stocks = MD.stocks; const screenMode = mode || 'browse'; const [filter, setFilter] = useState('All'); const [q, setQ] = useState(''); const [sortKey, setSortKey] = useState('chg'); const [sortDir, setSortDir] = useState('desc'); const [conds, setConds] = useState([{ field: 'chg', op: 'gt', val: 0 }]); if (MD.loading && !MD.ready) return ; if (!stocks.length) return Retry} />; function toggleSort(key) { if (key === sortKey) setSortDir((d) => (d === 'desc' ? 'asc' : 'desc')); else { setSortKey(key); setSortDir('desc'); } } let rows = stocks.filter((s) => (s.name + s.ticker).toLowerCase().includes(q.toLowerCase())); if (filter === 'Gainers') rows = rows.filter((s) => s.chg > 0); else if (filter === 'Losers') rows = rows.filter((s) => s.chg < 0); else if (filter === 'Unchanged') rows = rows.filter((s) => s.chg === 0); rows = [...rows].sort((a, b) => { const av = a[sortKey] == null ? -Infinity : a[sortKey]; const bv = b[sortKey] == null ? -Infinity : b[sortKey]; return sortDir === 'desc' ? bv - av : av - bv; }); return (

Stock Screener

{MD.stocksTotal || stocks.length} NSE stocks · live Yahoo quotes {MD.stocksLoadingMore ? ' · loading more…' : ''}
{screenMode === 'build' ? ( ) : ( <>
{SCREENER_FILTERS.map((f) => ( ))}
setQ(e.target.value)} />
)}
); } Object.assign(window, { ScreenerScreen });