← back to Stock Vshape Viewer
Include ETFs in scan (48 major ETFs: SPY/QQQ/sector SPDRs/ARKK/etc, high scan priority) + Reset watchlist/Reset all buttons + fix cached-universe filter dropping price-0 entries
27672c829fddca7af0b99ab797a9921dd3618ed8 · 2026-08-27 11:31:45 -0700 · Steve
Files touched
M public/app.jsM public/index.htmlM server.js
Diff
commit 27672c829fddca7af0b99ab797a9921dd3618ed8
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Aug 27 11:31:45 2026 -0700
Include ETFs in scan (48 major ETFs: SPY/QQQ/sector SPDRs/ARKK/etc, high scan priority) + Reset watchlist/Reset all buttons + fix cached-universe filter dropping price-0 entries
---
public/app.js | 18 +++++++++++++++++-
public/index.html | 4 ++++
server.js | 6 +++++-
3 files changed, 26 insertions(+), 2 deletions(-)
diff --git a/public/app.js b/public/app.js
index e225fdd..c393a25 100644
--- a/public/app.js
+++ b/public/app.js
@@ -507,7 +507,9 @@ function wireFilters() {
el.onchange = () => { state.filters[key] = el.checked; save(LS.filters, state.filters); applyFilters(); renderWatchlist(); };
}
document.getElementById('reset-filters').onclick = () => {
- state.filters = { ...window.DEFAULT_FILTERS }; save(LS.filters, state.filters); syncFilterUI(); process();
+ state.filters = { ...window.DEFAULT_FILTERS }; save(LS.filters, state.filters);
+ document.querySelectorAll('#presets .p').forEach(el => el.classList.remove('on'));
+ syncFilterUI(); syncOptUI(); updateCriteriaChips(); process();
};
}
function updateCriteriaChips() {
@@ -668,6 +670,20 @@ function wireScanAndOptions() {
});
// lazy-load options when the detail toggle is clicked
document.getElementById('d-opttoggle').onclick = () => { if (state.selected) loadOptions(state.selected); };
+ // reset buttons
+ document.getElementById('reset-watchlist').onclick = () => {
+ state.tickers = window.SEED.map(s => s.ticker); save(LS.tickers, state.tickers);
+ if (state.selected && !state.tickers.includes(state.selected)) clearSelect();
+ loadAll();
+ };
+ document.getElementById('reset-all').onclick = () => {
+ state.filters = { ...window.DEFAULT_FILTERS }; state.tfN = 126; state.tickers = window.SEED.map(s => s.ticker);
+ save(LS.filters, state.filters); save(LS.tf, state.tfN); save(LS.tickers, state.tickers);
+ document.querySelectorAll('#presets .p').forEach(el => el.classList.remove('on'));
+ document.getElementById('scan-results').innerHTML = ''; document.getElementById('scan-prog').innerHTML = '';
+ clearSelect(); syncFilterUI(); syncOptUI(); updateCriteriaChips();
+ loadAll();
+ };
}
function syncOptUI() {
document.getElementById('f-optMin').value = state.filters.optMin;
diff --git a/public/index.html b/public/index.html
index e5e5b06..7d5aa29 100644
--- a/public/index.html
+++ b/public/index.html
@@ -188,6 +188,10 @@
<button class="btn" id="add-btn">+ Add</button>
</div>
<div class="wl" id="wl"></div>
+ <div style="display:flex;gap:6px;margin-top:8px">
+ <button class="btn" id="reset-watchlist" style="flex:1">↺ Watchlist</button>
+ <button class="btn" id="reset-all" style="flex:1">↺ Reset all</button>
+ </div>
</div>
<div class="sec">
diff --git a/server.js b/server.js
index b8c19cf..042ce53 100644
--- a/server.js
+++ b/server.js
@@ -21,6 +21,7 @@ const JOBS = new Map();
const UA = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', 'Accept': 'application/json' };
const DJIA = ['AAPL','AMGN','AXP','BA','CAT','CRM','CSCO','CVX','DIS','DOW','GS','HD','HON','IBM','JNJ','JPM','KO','MCD','MMM','MRK','MSFT','NKE','NVDA','PG','SHW','TRV','UNH','V','VZ','WMT'];
+const MAJOR_ETFS = new Set(['SPY','QQQ','IWM','DIA','VOO','VTI','VEA','VWO','IVV','VUG','VTV','VIG','SCHD','SMH','SOXX','XLK','XLF','XLE','XLV','XLY','XLP','XLI','XLU','XLB','XLRE','XLC','ARKK','ARKG','XBI','IBB','GLD','SLV','TLT','HYG','LQD','GDX','KRE','ITB','XHB','VNQ','EEM','EFA','IEMG','QUAL','MTUM','TQQQ','SQQQ','SOXL']);
const MIME = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8', '.json': 'application/json; charset=utf-8' };
function unauthorized(res) { res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="stock-vshape"' }); res.end('Auth required'); }
@@ -56,7 +57,7 @@ async function getHistory(t) {
// ---------------- universe (NASDAQ screener + DJIA) ----------------
async function getUniverse(minPrice) {
- if (UNIV && Date.now() - UNIV_TS < UTTL) return UNIV.filter(u => u.price >= minPrice);
+ if (UNIV && Date.now() - UNIV_TS < UTTL) return UNIV.filter(u => u.price >= minPrice || u.cap === Infinity);
const r = await fetch('https://api.nasdaq.com/api/screener/stocks?tableonly=true&limit=6000&offset=0&exchange=NASDAQ', { headers: { ...UA, 'Accept-Language': 'en-US,en;q=0.9' } });
let rows = [];
if (r.ok) { const j = await r.json(); rows = j?.data?.table?.rows || j?.data?.rows || []; }
@@ -69,6 +70,9 @@ async function getUniverse(minPrice) {
seen.add(sym); list.push({ ticker: sym, price, cap, name: row.name || sym });
}
for (const d of DJIA) if (!seen.has(d)) { seen.add(d); list.push({ ticker: d, price: 0, cap: Infinity, name: d + ' (DJIA)' }); }
+ // major ETFs — added explicitly (the ETF screener ignores limit + omits SPY/QQQ);
+ // price 0 here, real price comes from history during the scan.
+ for (const e of MAJOR_ETFS) if (!seen.has(e)) { seen.add(e); list.push({ ticker: e, price: 0, cap: Infinity, name: e + ' (ETF)', isEtf: true }); }
list.sort((a, b) => b.cap - a.cap);
UNIV = list; UNIV_TS = Date.now();
return list.filter(u => u.price >= minPrice || u.cap === Infinity);
← d409d10 10 preloaded strategy presets (one-click screens: Deep V-Rec
·
back to Stock Vshape Viewer
·
5x report: six-way core PASS all 3 browsers, 0 console error fdcc20e →