← back to Stock Vshape Viewer
CSV export: download scan matches (ticker/name/score/price/drawdown/RSI/MA-dist/zone) as a dated CSV; button appears after a scan
5ca5574396f3439740e20aef7a7cb31cde5cbe4b · 2026-08-28 00:21:32 -0700 · Steve
Files touched
M public/app.jsM public/index.html
Diff
commit 5ca5574396f3439740e20aef7a7cb31cde5cbe4b
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Aug 28 00:21:32 2026 -0700
CSV export: download scan matches (ticker/name/score/price/drawdown/RSI/MA-dist/zone) as a dated CSV; button appears after a scan
---
public/app.js | 14 ++++++++++++++
public/index.html | 1 +
2 files changed, 15 insertions(+)
diff --git a/public/app.js b/public/app.js
index 5d51940..2bd971f 100644
--- a/public/app.js
+++ b/public/app.js
@@ -588,6 +588,8 @@ function pollScan(job) {
if (j.done) {
const btn = document.getElementById('scan-btn'); btn.disabled = false; btn.textContent = '⚡ Scan NASDAQ + DJIA (≥$8)';
const N = 12;
+ state.lastMatches = j.matches || [];
+ document.getElementById('scan-csv').style.display = state.lastMatches.length ? 'block' : 'none';
await autoLoadMatches(j.matches || [], N); // wait until the board actually has them
document.getElementById('scan-prog').innerHTML += `<div style="margin-top:4px;color:#6f83a8">done in ${(j.elapsedMs/1000).toFixed(0)}s · loaded top ${Math.min(N, j.matchCount)} into the board · ◈ = cheap far-dated options · click any to add more</div>`;
enrichResultsOptions();
@@ -630,6 +632,17 @@ async function enrichResultsOptions() {
}
await Promise.all(Array.from({ length: 6 }, worker));
}
+// export the current scan matches to CSV
+function exportCSV() {
+ const m = state.lastMatches || []; if (!m.length) return;
+ const cols = ['ticker', 'name', 'buyScore', 'price', 'drawdown', 'weeksAgoActual', 'rsi', 'volSurge', 'd50', 'd200', 'zoneLabel'];
+ const esc = v => `"${String(v == null ? '' : v).replace(/"/g, '""')}"`;
+ const csv = [cols.join(',')].concat(m.map(r => cols.map(c => esc(r[c])).join(','))).join('\n');
+ const a = document.createElement('a');
+ a.href = URL.createObjectURL(new Blob([csv], { type: 'text/csv' }));
+ a.download = `vshape-scan-${new Date().toISOString().slice(0, 10)}.csv`;
+ a.click(); URL.revokeObjectURL(a.href);
+}
// push a scan's top-ranked winners straight onto the 3D board
async function autoLoadMatches(matches, N) {
const top = matches.slice(0, N).map(m => m.ticker);
@@ -699,6 +712,7 @@ function renderBacktest(j) {
function wireScanAndOptions() {
document.getElementById('scan-btn').onclick = startScan;
document.getElementById('go-scan').onclick = startScan;
+ document.getElementById('scan-csv').onclick = exportCSV;
const sm = document.getElementById('scan-max');
sm.oninput = () => { scanMax = +sm.value; document.getElementById('v-scanMax').textContent = sm.value; };
// options criteria
diff --git a/public/index.html b/public/index.html
index aef9f70..e5797e6 100644
--- a/public/index.html
+++ b/public/index.html
@@ -181,6 +181,7 @@
<input type="range" id="scan-max" min="100" max="4200" step="100" value="1500"></div>
<div id="scan-prog" class="scanprog"></div>
<div class="results" id="scan-results"></div>
+ <button class="btn" id="scan-csv" style="width:100%;margin-top:6px;display:none">⬇ Export matches to CSV</button>
</div>
<div class="sec">
← ef0e646 Score transparency: expose scoreParts from the engine + rend
·
back to Stock Vshape Viewer
·
Evidence-driven sweet-spot flag: mark scores in the backtest d1e5b8a →