initial commit

This commit is contained in:
coddard
2026-05-18 00:31:08 +03:00
commit 0096c8819b
26 changed files with 2851 additions and 0 deletions
+215
View File
@@ -0,0 +1,215 @@
{% extends "layout.html" %}
{% block title %}Market Scanner — IBKR Dashboard{% endblock %}
{% block content %}
<div class="scanner-container">
<div class="scanner-header">
<h1 class="scanner-title">Market Scanner</h1>
<div class="scanner-controls">
<div class="control-group">
<label for="scanType">Scan Type</label>
<select id="scanType">
<option value="HOT_BY_VOLUME" selected>Unusual Volume</option>
<option value="MOST_ACTIVE">Most Active</option>
<option value="TOP_PERC_GAIN">Top Gainers</option>
<option value="TOP_PERC_LOSE">Top Losers</option>
<option value="HIGH_OPEN_GAP">High Open Gap</option>
</select>
</div>
<div class="control-group">
<label for="minPrice">Min Price ($)</label>
<input type="number" id="minPrice" placeholder="e.g. 5.00" step="0.01" min="0">
</div>
<div class="control-group">
<label for="minVolume">Min Volume</label>
<input type="number" id="minVolume" placeholder="e.g. 100000" step="1000" min="0">
</div>
<div class="control-group">
<label for="minMarketCap">Min Market Cap ($M)</label>
<input type="number" id="minMarketCap" placeholder="e.g. 100" step="10" min="0">
</div>
<div class="control-group">
<button class="scan-button" id="scanButton" onclick="runScanner()">Run Scan</button>
</div>
<div class="control-group">
<button class="scan-button export-button" id="exportButton" onclick="exportCSV()">Export CSV</button>
</div>
</div>
</div>
<div class="results-container" id="resultsContainer">
<div class="results-header">
<span>Scanner Results</span>
<span class="results-count" id="resultsCount" style="display:none;">0 results</span>
</div>
<div id="scanResults"></div>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script>
window.addEventListener('DOMContentLoaded', () => {
runScanner();
});
async function runScanner() {
const button = document.getElementById('scanButton');
const scanResults = document.getElementById('scanResults');
const resultsCount = document.getElementById('resultsCount');
button.disabled = true;
button.textContent = 'Scanning...';
scanResults.innerHTML = `
<div class="loading-state">
<div class="scanner-spinner"></div>
<p>Running scanner...</p>
</div>
`;
resultsCount.style.display = 'none';
try {
const payload = {
scan_type: document.getElementById('scanType').value,
min_price: parseFloat(document.getElementById('minPrice').value) || 0,
min_volume: parseInt(document.getElementById('minVolume').value) || 0,
min_market_cap: parseFloat(document.getElementById('minMarketCap').value) || 0,
};
const response = await fetch('/scanner', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const data = await response.json();
if (data.status === 'ok') {
displayScanResults(data.results, payload.scan_type);
} else {
showError(data.message || 'Scanner request failed');
}
} catch (err) {
showError('Network error: ' + err.message);
} finally {
button.disabled = false;
button.textContent = 'Run Scan';
}
}
function displayScanResults(results, scanType) {
const scanResults = document.getElementById('scanResults');
const resultsCount = document.getElementById('resultsCount');
resultsCount.textContent = `${results.length} result${results.length !== 1 ? 's' : ''}`;
resultsCount.style.display = 'inline-block';
if (results.length === 0) {
scanResults.innerHTML = '<div class="loading-state"><p>No results found. Try adjusting your filters.</p></div>';
return;
}
const grid = document.createElement('div');
grid.className = 'results-grid';
results.forEach((result) => {
const card = document.createElement('div');
card.className = 'result-card';
card.onclick = () => loadSymbolFromScan(result.symbol);
card.innerHTML = `
<img
class="chart-thumbnail"
src="${result.chart_image}"
alt="${result.symbol} chart"
onerror="handleChartError(this, '${result.symbol}')"
>
<div class="symbol-details">
<div class="symbol-name">${result.symbol}</div>
<div class="symbol-company">${result.company}</div>
<div class="symbol-exchange">${result.exchange || ''}</div>
</div>
`;
grid.appendChild(card);
});
scanResults.innerHTML = '';
scanResults.appendChild(grid);
}
function loadSymbolFromScan(symbol) {
window.location.href = '/?symbol=' + symbol;
}
function formatVolume(volume) {
if (!volume) return '';
if (volume >= 1_000_000) return (volume / 1_000_000).toFixed(1) + 'M';
if (volume >= 1_000) return (volume / 1_000).toFixed(1) + 'K';
return volume.toString();
}
function handleChartError(imgElement, symbol) {
imgElement.style.display = 'none';
const placeholder = document.createElement('div');
placeholder.className = 'chart-placeholder';
placeholder.textContent = symbol;
imgElement.parentNode.insertBefore(placeholder, imgElement);
}
function showError(message) {
const scanResults = document.getElementById('scanResults');
scanResults.innerHTML = `
<div class="loading-state">
<p style="color: #e74c3c;">Error: ${message}</p>
</div>
`;
}
async function exportCSV() {
const button = document.getElementById('exportButton');
button.disabled = true;
button.textContent = 'Exporting...';
try {
const payload = {
scan_type: document.getElementById('scanType').value,
min_price: parseFloat(document.getElementById('minPrice').value) || 0,
min_volume: parseInt(document.getElementById('minVolume').value) || 0,
min_market_cap: parseFloat(document.getElementById('minMarketCap').value) || 0,
};
const response = await fetch('/scanner/export', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!response.ok) {
showError('Export failed');
return;
}
const blob = await response.blob();
const disposition = response.headers.get('Content-Disposition') || '';
const filenameMatch = disposition.match(/filename=([^\s;]+)/);
const filename = filenameMatch ? filenameMatch[1] : 'scanner_export.csv';
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} catch (err) {
showError('Export error: ' + err.message);
} finally {
button.disabled = false;
button.textContent = 'Export CSV';
}
}
</script>
{% endblock %}