fix fetch data in signal page and the backtest export excel issue

This commit is contained in:
2026-02-02 21:30:19 +08:00
parent 50f9734dcd
commit 891f0ea2b0
21 changed files with 8328 additions and 57803 deletions
+4 -2
View File
@@ -255,7 +255,9 @@
// --- 3. Construct Row ---
htmlContent += `
<tr>
<td>${item.symbol}</td>
<td>
<div class="fw-bold">${item.name || item.symbol}</div>
</td>
<td><span class="${dateStyle}" style="font-size: 0.75rem; padding: 2px 5px; border-radius: 4px;">${displayDate}</span></td>
<td class="fw-bold">${item.last_close}</td>
<td class="${item.change_pct >= 0 ? 'text-up' : 'text-down'}">
@@ -330,7 +332,7 @@
}
}
setInterval(checkStatus, 30000);
setInterval(checkStatus, 300000);
checkStatus(); // Initial check
// --- 5. Initial Load ---
+161
View File
@@ -0,0 +1,161 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Settings - CSV Master Editor</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<style>
body { background-color: #f8f9fa; font-family: 'Segoe UI', Tahoma, sans-serif; overflow-x: hidden; }
/* Forces the table to stay within the browser width */
.table-container { width: 100%; overflow: hidden; }
.table {
table-layout: fixed; /* Crucial for 60/40 ratio */
width: 100%;
}
/* Column Widths */
.col-name { width: 50%; }
.col-cusip { width: 33%; }
.col-provider { width: 10%; }
.col-manage { width: 7%; }
/* Compact Input styling (2/3 height) */
.form-control, .form-select {
width: 100%;
font-size: 0.9rem;
border: 1px solid #dee2e6;
padding: 2px 8px !important; /* Reduced vertical padding */
height: auto !important;
min-height: 30px;
}
.form-control:focus {
border-color: #0d6efd;
box-shadow: none;
background-color: #fffdf7;
}
/* Compact Table Cells */
#settingsTable td {
padding: 3px 4px !important;
}
#settingsTable th {
padding: 6px 8px !important;
font-size: 0.85rem;
}
</style>
</head>
<body>
<div class="container-fluid px-3 mt-4">
<div class="card shadow border-0">
<div class="card-header bg-dark text-white d-flex justify-content-between align-items-center py-3">
<div>
<h5 class="mb-0"><i class="bi bi-gear-fill me-2"></i>CSV Master Editor</h5>
<small class="text-secondary">instruments.csv</small>
</div>
<div class="d-flex gap-2">
<input type="text" id="searchInput" class="form-control form-control-sm" placeholder="Search..." onkeyup="filterTable()" style="width: 200px;">
<button type="button" class="btn btn-primary btn-sm px-3" onclick="addRow()">
<i class="bi bi-plus-lg"></i> Add Ticker
</button>
</div>
</div>
<div class="card-body p-0">
<form action="/settings/save" method="POST">
<div class="table-responsive" style="max-height: 75vh; overflow-y: auto; overflow-x: hidden;">
<table class="table table-hover align-middle mb-0" id="settingsTable">
<thead class="table-light sticky-top">
<tr>
<th class="col-name">Name</th>
<th class="col-cusip">cusips / symbols</th>
<th class="col-provider">Provider</th>
<th class="col-manage text-center">Manage</th>
</tr>
</thead>
<tbody>
{% for item in instruments %}
<tr>
<td><input type="text" name="name[]" class="form-control fw-bold" value="{{ item.name }}"></td>
<td><input type="text" name="cusip[]" class="form-control" value="{{ item.cusip }}"></td>
<td>
<select name="provider[]" class="form-select">
<option value="yahoo" {% if item.provider == 'yahoo' %}selected{% endif %}>Yahoo</option>
<option value="agi" {% if item.provider == 'agi' %}selected{% endif %}>Allianz</option>
<option value="jpm" {% if item.provider == 'jpm' %}selected{% endif %}>JPM</option>
<option value="ft" {% if item.provider == 'ft' %}selected{% endif %}>FT</option>
</select>
</td>
<td class="text-center">
<button type="button" class="btn btn-outline-danger btn-sm border-0 py-0" onclick="confirmDelete(this)">
<i class="bi bi-trash3"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="card-footer bg-light p-3 d-flex justify-content-between align-items-center">
<div class="text-muted small">
<i class="bi bi-hdd-fill me-1"></i> Disk Updated: <span class="text-primary">{{ last_updated }}</span>
</div>
<div>
<a href="/" class="btn btn-outline-secondary btn-sm me-2">Cancel</a>
<button type="submit" class="btn btn-success btn-sm px-4 shadow-sm">
<i class="bi bi-cloud-arrow-up-fill me-1"></i> Save Changes
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<script>
function confirmDelete(btn) {
if (confirm("Permanently remove this entry?")) {
btn.closest('tr').remove();
}
}
function addRow() {
const tbody = document.querySelector('#settingsTable tbody');
// Updated names to name[] and cusip[] to match CSV mapping
const row = `<tr>
<td><input type="text" name="name[]" class="form-control fw-bold" placeholder="New Name"></td>
<td><input type="text" name="cusip[]" class="form-control" placeholder="CUSIP/Ticker"></td>
<td>
<select name="provider[]" class="form-select">
<option value="yahoo">Yahoo</option>
<option value="agi">Allianz</option>
<option value="jpm">JPM</option>
<option value="ft">FT</option>
</select>
</td>
<td class="text-center">
<button type="button" class="btn btn-outline-danger btn-sm border-0 py-0" onclick="this.closest('tr').remove()">
<i class="bi bi-trash3"></i>
</button>
</td>
</tr>`;
tbody.insertAdjacentHTML('afterbegin', row);
}
function filterTable() {
let input = document.getElementById("searchInput").value.toUpperCase();
let rows = document.querySelector("#settingsTable tbody").rows;
for (let i = 0; i < rows.length; i++) {
let n = rows[i].cells[0].querySelector('input').value.toUpperCase();
let c = rows[i].cells[1].querySelector('input').value.toUpperCase();
rows[i].style.display = (n.includes(input) || c.includes(input)) ? "" : "none";
}
}
</script>
</body>
</html>
+44 -3
View File
@@ -244,6 +244,8 @@
if (!res.ok) {
throw new Error(data.error || `Server returned ${res.status}`);
}
// SAVE DATA GLOBALLY FOR EXCEL EXPORTER
window.currentBacktestData = data;
// Update UI
document.getElementById('kpiArea')?.classList.remove('d-none');
@@ -454,10 +456,49 @@ function updateSyncBadge(dateString) {
}
function exportToExcel() {
const table = document.getElementById("ledgerTable");
const wb = XLSX.utils.table_to_book(table);
XLSX.writeFile(wb, "Investment_Backtest_Results.xlsx");
// 1. Verify the global data exists
if (!window.currentBacktestData || window.currentBacktestData.length === 0) {
alert("No data available to export. Please run a backtest first.");
return;
}
const excelRows = window.currentBacktestData.map(row => {
// Calculate returns using the same logic as your renderTable
const vaAnnRet = row.va_invested > 0
? ((row.va_value - row.va_invested) / row.va_invested)
: 0;
const dcaAnnRet = row.dca_invested > 0
? ((row.dca_value - row.dca_invested) / row.dca_invested)
: 0;
return {
"Date": row.date,
"Price": row.price,
// DVA / Value Averaging Columns (Separated)
"DVA Investment ($)": row.va_diff,
"DVA Shares Change": row.va_shares_trans,
"DVA Total Shares": row.va_shares_total,
"DVA Target Value": row.va_target_value,
"DVA Portfolio Value": row.va_value,
// DCA Columns (Separated)
"DCA Investment ($)": row.dca_invested,
"DCA Shares Change": row.dca_shares_trans,
"DCA Total Shares": row.dca_shares_total,
"DCA Portfolio Value": row.dca_value,
// Performance
"DVA Return (%)": (vaAnnRet * 100).toFixed(2) + "%",
"DCA Return (%)": (dcaAnnRet * 100).toFixed(2) + "%"
}
});
// 3. Convert JSON to Worksheet
const worksheet = XLSX.utils.json_to_sheet(excelRows);
// 4. Create Workbook and Download
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "Backtest Results");
XLSX.writeFile(workbook, "Investment_Backtest_Results.xlsx");
}
// This checks if the Flask server is responding every 30 seconds
async function checkStatus() {
const indicator = document.getElementById('statusIndicator');