add last sync time in index, fix csv file caching time in fetching new data

This commit is contained in:
2026-02-06 03:56:55 +08:00
parent 2d9fa9a47b
commit c0f158684f
29 changed files with 53379 additions and 704 deletions
Binary file not shown.
+1259
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4
View File
@@ -5293,3 +5293,7 @@ date,close
2026-01-27,37.18 2026-01-27,37.18
2026-01-28,37.23 2026-01-28,37.23
2026-01-29,37.2 2026-01-29,37.2
2026-01-30,37.11
2026-02-02,36.99
2026-02-03,37.17
2026-02-04,37.12
1 date close
5293 2026-01-27 37.18
5294 2026-01-28 37.23
5295 2026-01-29 37.2
5296 2026-01-30 37.11
5297 2026-02-02 36.99
5298 2026-02-03 37.17
5299 2026-02-04 37.12
+1259
View File
File diff suppressed because it is too large Load Diff
+24
View File
@@ -0,0 +1,24 @@
date,close
2026-01-05,22.87
2026-01-06,22.77
2026-01-07,22.61
2026-01-08,22.56
2026-01-09,22.49
2026-01-12,22.54
2026-01-13,22.66
2026-01-14,22.66
2026-01-15,22.93
2026-01-16,22.9
2026-01-19,23.01
2026-01-20,23.0
2026-01-21,22.66
2026-01-22,23.02
2026-01-23,23.25
2026-01-26,23.34
2026-01-27,23.04
2026-01-28,21.99
2026-01-29,21.97
2026-01-30,22.46
2026-02-02,22.51
2026-02-03,22.78
2026-02-04,22.85
1 date close
2 2026-01-05 22.87
3 2026-01-06 22.77
4 2026-01-07 22.61
5 2026-01-08 22.56
6 2026-01-09 22.49
7 2026-01-12 22.54
8 2026-01-13 22.66
9 2026-01-14 22.66
10 2026-01-15 22.93
11 2026-01-16 22.9
12 2026-01-19 23.01
13 2026-01-20 23.0
14 2026-01-21 22.66
15 2026-01-22 23.02
16 2026-01-23 23.25
17 2026-01-26 23.34
18 2026-01-27 23.04
19 2026-01-28 21.99
20 2026-01-29 21.97
21 2026-01-30 22.46
22 2026-02-02 22.51
23 2026-02-03 22.78
24 2026-02-04 22.85
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3
View File
@@ -4365,3 +4365,6 @@ date,close
2026-01-28,28.72 2026-01-28,28.72
2026-01-29,29.1 2026-01-29,29.1
2026-01-30,28.99 2026-01-30,28.99
2026-02-02,27.73
2026-02-03,28.9
2026-02-04,29.0
1 date close
4365 2026-01-28 28.72
4366 2026-01-29 29.1
4367 2026-01-30 28.99
4368 2026-02-02 27.73
4369 2026-02-03 28.9
4370 2026-02-04 29.0
@@ -4343,3 +4343,11 @@ date,close
2026-01-22,279.27 2026-01-22,279.27
2026-01-23,279.99 2026-01-23,279.99
2026-01-26,283.53 2026-01-26,283.53
2026-01-27,285.51
2026-01-28,290.19
2026-01-29,290.78
2026-01-30,288.15
2026-02-02,281.4
2026-02-03,290.03
2026-02-04,289.6
2026-02-05,283.7
1 date close
4343 2026-01-22 279.27
4344 2026-01-23 279.99
4345 2026-01-26 283.53
4346 2026-01-27 285.51
4347 2026-01-28 290.19
4348 2026-01-29 290.78
4349 2026-01-30 288.15
4350 2026-02-02 281.4
4351 2026-02-03 290.03
4352 2026-02-04 289.6
4353 2026-02-05 283.7
@@ -1,5 +1,4 @@
date,close date,close
2026-01-02,258.96
2026-01-05,263.47 2026-01-05,263.47
2026-01-06,268.56 2026-01-06,268.56
2026-01-07,267.9 2026-01-07,267.9
@@ -20,3 +19,6 @@ date,close
2026-01-28,290.19 2026-01-28,290.19
2026-01-29,290.78 2026-01-29,290.78
2026-01-30,288.15 2026-01-30,288.15
2026-02-02,281.4
2026-02-03,290.03
2026-02-04,289.6
1 date close
2026-01-02 258.96
2 2026-01-05 263.47
3 2026-01-06 268.56
4 2026-01-07 267.9
19 2026-01-28 290.19
20 2026-01-29 290.78
21 2026-01-30 288.15
22 2026-02-02 281.4
23 2026-02-03 290.03
24 2026-02-04 289.6
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+328 -323
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1259
View File
File diff suppressed because it is too large Load Diff
+11 -12
View File
@@ -16,6 +16,9 @@ class DataEngine:
self.symbol = symbol.strip().upper() if symbol else None self.symbol = symbol.strip().upper() if symbol else None
self.name = name self.name = name
# --- Fetch frequently ---
self.cache_expiry = 1 * 3600 # Set to 0 for forced refresh, 24 for normal
# 2. Setup the directory variable FIRST # 2. Setup the directory variable FIRST
# (This was likely below the file_path line, causing the crash) # (This was likely below the file_path line, causing the crash)
base_path = os.path.dirname(os.path.abspath(__file__)) base_path = os.path.dirname(os.path.abspath(__file__))
@@ -50,21 +53,17 @@ class DataEngine:
def ensure_data(self): def ensure_data(self):
"""Checks if file exists and is fresh (less than 24h old).""" """Checks if file exists and is fresh (less than 24h old)."""
CACHE_EXPIRY = 24 * 3600 # 24 hours # Force expiry to 0
CACHE_EXPIRY = 0
if os.path.exists(self.file_path): if os.path.exists(self.file_path):
# NEW: Check how old the file is # By changing this to 'if False', we force it to ignore the cache every time
file_age = time.time() - os.path.getmtime(self.file_path) if False: # file_age < CACHE_EXPIRY:
if file_age < CACHE_EXPIRY: return True
return True # Data is actually fresh
else: else:
print(f"DEBUG: {self.symbol} cache is stale ({round(file_age/3600)}h old). Refreshing...") print(f"DEBUG: {self.symbol} refreshing now...")
else:
print(f"DEBUG: {self.symbol} not found in cache. Attempting download...")
# If we reached here, it means we either have NO file or a STALE file # This calls your actual downloader
# Instead of just yfinance, call your specialized fetch_data()
# which uses the URLs from your TEMPLATES
return self.fetch_data() return self.fetch_data()
def load_instruments_from_csv(self, file_path='instruments.csv'): def load_instruments_from_csv(self, file_path='instruments.csv'):
@@ -276,7 +275,7 @@ class DataEngine:
def fetch_data(self): def fetch_data(self):
local_df = pd.DataFrame() local_df = pd.DataFrame()
CACHE_EXPIRY = 24 * 3600 CACHE_EXPIRY = 0
file_exists = os.path.exists(self.file_path) file_exists = os.path.exists(self.file_path)
# 1. Load Local Cache & Check Age # 1. Load Local Cache & Check Age
+16 -4
View File
@@ -1,5 +1,17 @@
name,cusip,provider name,cusip,provider
JPMorgan Evergreen,HK0000055829,jpm JPM Vietnam,HK0000055811,jpm
Allianz Oriental Income Cl A,LU0348783233:USD,agi JPM Pacific ,HK0000055746,jpm
SPMO ETF - USD,SPMO,yahoo JPM Korea ,LU0301634860,jpm
JPM Korea,LU0301634860,jpm JPM India ,MU0129U00005,jpm
JPM Evergreen,HK0000055829,jpm
JPM ASEAN,HK0000055555,jpm
JPM Japan,LU0129465034,jpm
JPM G HYB,LU0356780857,jpm
JPM Europe,LU0119078227,jpm
INDA INDIA,INDA,yahoo
Fidelity Indonesia,LU0055114457,ft
AGI Oriental Income,LU0348783233,ft
VT,VT,yahoo
VHYD,VHYD.L,yahoo
SPMO,SPMO,yahoo
EWJV,EWJV,yahoo
1 name cusip provider
2 JPMorgan Evergreen JPM Vietnam HK0000055829 HK0000055811 jpm
3 Allianz Oriental Income Cl A JPM Pacific LU0348783233:USD HK0000055746 agi jpm
4 SPMO ETF - USD JPM Korea SPMO LU0301634860 yahoo jpm
5 JPM Korea JPM India LU0301634860 MU0129U00005 jpm
6 JPM Evergreen HK0000055829 jpm
7 JPM ASEAN HK0000055555 jpm
8 JPM Japan LU0129465034 jpm
9 JPM G HYB LU0356780857 jpm
10 JPM Europe LU0119078227 jpm
11 INDA INDIA INDA yahoo
12 Fidelity Indonesia LU0055114457 ft
13 AGI Oriental Income LU0348783233 ft
14 VT VT yahoo
15 VHYD VHYD.L yahoo
16 SPMO SPMO yahoo
17 EWJV EWJV yahoo
+36
View File
@@ -0,0 +1,36 @@
beautifulsoup4==4.14.3
blinker==1.9.0
cachelib==0.13.0
certifi==2026.1.4
cffi==2.0.0
charset-normalizer==3.4.4
click==8.3.1
curl_cffi==0.13.0
Flask==3.1.2
Flask-Caching==2.3.1
frozendict==2.4.7
html5lib==1.1
idna==3.11
itsdangerous==2.2.0
Jinja2==3.1.6
lxml==6.0.2
MarkupSafe==3.0.3
multitasking==0.0.12
numpy==2.4.1
pandas==3.0.0
peewee==3.19.0
platformdirs==4.5.1
protobuf==6.33.4
pycparser==3.0
python-dateutil==2.9.0.post0
pytz==2025.2
requests==2.32.5
six==1.17.0
soupsieve==2.8.3
ta==0.11.0
typing_extensions==4.15.0
urllib3==2.6.3
webencodings==0.5.1
websockets==16.0
Werkzeug==3.1.5
yfinance==1.1.0
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 B

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 466 B

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.
+112 -36
View File
@@ -88,7 +88,8 @@
.table th:first-child { .table th:first-child {
position: sticky; position: sticky;
text-align: left !important; /* Force left alignment */ text-align: left !important; /* Force left alignment */
padding-left: 15px; /* Add space so text doesn't touch the edge */ padding-left: 15px;
color: #2d3748/* Add space so text doesn't touch the edge */
left: 0; left: 0;
z-index: 10; z-index: 10;
background-color: var(--sticky-bg); background-color: var(--sticky-bg);
@@ -101,6 +102,23 @@
/* Clean border to define the edge */ /* Clean border to define the edge */
border-right: 1px solid #ddd; border-right: 1px solid #ddd;
} }
/* Specific fix for Close and Chg% columns readability */
.table td:nth-child(3),
.table td:nth-child(4) {
font-weight: 600;
color: #1a202c !important; /* Extra bold and dark */
}
/* Navbar fix: Ensure buttons don't disappear on tiny screens */
.navbar-nav {
flex-direction: row !important; /* Keep links side-by-side on mobile */
gap: 10px;
}
.nav-link {
padding: 0.5rem !important;
font-size: 0.85rem;
}
/* 4. Zebra Striping + Sticky Fix */ /* 4. Zebra Striping + Sticky Fix */
.table-striped tbody tr:nth-of-type(odd) { .table-striped tbody tr:nth-of-type(odd) {
@@ -115,6 +133,39 @@
.text-up { color: var(--text-up); font-weight: 600; } .text-up { color: var(--text-up); font-weight: 600; }
.text-down { color: var(--text-down); font-weight: 600; } .text-down { color: var(--text-down); font-weight: 600; }
.table-date {
font-size: 0.8rem !important; /* Increased slightly to make it obvious */
font-weight: 800 !important; /* Extra bold */
font-family: 'Courier New', monospace !important;
display: inline-block; /* Sometimes helps with sizing */
white-space: nowrap; /* Prevents the date from snapping to 2 lines */
}
.status-dot {
width: 8px;
height: 8px;
background-color: #39FF14; /* Neon green for better visibility */
border-radius: 50%;
display: inline-block;
box-shadow: 0 0 10px rgba(57, 255, 20, 0.4);
animation: status-pulse 2s infinite;
}
/* Custom text class if Bootstrap classes aren't bright enough */
.text-white-50 {
color: rgba(255, 255, 255, 0.7) !important; /* Increased from 0.5 to 0.7 for clarity */
}
@keyframes status-pulse {
0% { transform: scale(0.95); opacity: 1; }
50% { transform: scale(1.1); opacity: 0.7; }
100% { transform: scale(0.95); opacity: 1; }
}
#lastSyncTime {
font-size: 0.85rem;
letter-spacing: 0.5px;
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
--header-bg: #1a202c; /* Darker header */ --header-bg: #1a202c; /* Darker header */
@@ -135,24 +186,27 @@
</style> </style>
</head> </head>
<body> <body>
<nav class="navbar navbar-dark mb-4" style="background-color: #1a202c !important;">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4"> <div class="container-fluid d-flex justify-content-between align-items-center">
<div class="container-fluid"> <div class="d-flex align-items-center">
<a class="navbar-brand" href="/"> <a class="navbar-brand me-4" href="/">
<i class="bi bi-graph-up-arrow me-2"></i>Finance Suite <i class="bi bi-graph-up-arrow me-2"></i>Finance Suite
</a> </a>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="d-flex gap-2">
<ul class="navbar-nav"> <a class="btn btn-sm btn-outline-light border-secondary" href="/">Dashboard</a>
<li class="nav-item"><a class="nav-link" href="/">Dashboard</a></li> <a class="btn btn-sm btn-outline-light border-secondary" href="/backtest">Backtest</a>
<li class="nav-item"><a class="nav-link" href="/backtest">Backtester</a></li> </div>
</ul> </div>
<div class="d-flex align-items-center" style="font-size: 0.8rem; letter-spacing: 0.3px;">
<div class="status-dot me-2"></div>
<span class="text-white-50">System Online</span>
<span class="mx-2 text-white-50 opacity-25">|</span>
<span class="text-white-50">Last Sync:</span>
<span id="lastSyncTime" class="ms-1 fw-bold text-white font-monospace">--:--:--</span>
</div> </div>
<span class="navbar-text text-light small d-none d-md-inline">
System Status: <span class="text-success">● Online</span>
</span>
</div> </div>
</nav> </nav>
<div class="container-fluid p-0"> <div class="container-fluid p-0">
<div class="card"> <div class="card">
<div class="card-header d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
@@ -219,20 +273,39 @@
console.log("Starting loadData..."); console.log("Starting loadData...");
const loading = document.getElementById('loading'); const loading = document.getElementById('loading');
const tbody = document.getElementById('tableBody'); const tbody = document.getElementById('tableBody');
const syncDisplay = document.getElementById('lastSyncTime'); // Get our new element
if (loading) loading.style.display = 'inline'; if (loading) loading.style.display = 'inline';
try { try {
const response = await fetch('/api/summary'); const response = await fetch('/api/summary');
const data = await response.json(); const data = await response.json();
const syncDisplay = document.getElementById('lastSyncTime');
if (syncDisplay) {
const now = new Date();
syncDisplay.innerText = now.toLocaleTimeString([], { hour12: false });
// Brief highlight effect
syncDisplay.classList.add('text-success');
setTimeout(() => syncDisplay.classList.remove('text-success'), 2000);
}
// --- Update the Sync Time Display ---
const now = new Date();
const timeStr = now.toLocaleTimeString([], { hour12: false }); // e.g. 02:45:10
const dateStr = now.toLocaleDateString();
if (syncDisplay) {
syncDisplay.innerHTML = `${dateStr} ${timeStr}`;
}
// -----------------------------------------
if (!data || data.length === 0) { if (!data || data.length === 0) {
tbody.innerHTML = '<tr><td colspan="10" class="p-4">No data found. Please run Sync.</td></tr>'; tbody.innerHTML = '<tr><td colspan="10" class="p-4 text-center">No data found. Please run Sync.</td></tr>';
return; return;
} }
// Get today's date string (YYYY-MM-DD) to check freshness
const todayStr = new Date().toISOString().split('T')[0];
let htmlContent = ''; let htmlContent = '';
const todayStr = new Date().toISOString().split('T')[0];
data.forEach(item => { data.forEach(item => {
// --- 1. Error Row Handling --- // --- 1. Error Row Handling ---
@@ -241,37 +314,34 @@
<tr> <tr>
<td>${item.name || 'Unknown'}</td> <td>${item.name || 'Unknown'}</td>
<td colspan="9" class="text-center p-3"> <td colspan="9" class="text-center p-3">
<span class="badge bg-warning text-dark"><i class="bi bi-exclamation-triangle"></i> Needs Sync</span> <span class="badge bg-warning text-dark">Needs Sync</span>
<small class="text-muted ms-2">Local CSV not found or corrupted.</small> <small class="text-muted ms-2">Local CSV not found or corrupted.</small>
</td> </td>
</tr>`; </tr>`;
return; return; // Skip to next item
} }
// --- 2. Date & Style Calculations ---
let displayDate = "N/A"; let displayDate = "N/A";
let dateColor = '#dc3545'; // Default red
if (item.last_date && item.last_date.includes('-')) { if (item.last_date && item.last_date.includes('-')) {
const parts = item.last_date.split('-'); const parts = item.last_date.split('-');
const formatted = `${parts[2]}/${parts[1]}`; displayDate = `${parts[2]}/${parts[1]}`;
dateColor = (item.last_date === todayStr) ? '#28a745' : '#dc3545';
// Get today's date in YYYY-MM-DD format to compare
const today = new Date().toISOString().split('T')[0];
// Pick color based on freshness
const color = (item.last_date === today) ? '#28a745' : '#dc3545';
displayDate = `<span style="color: ${color};">${formatted}</span>`;
} }
// --- 2. Calculations & Styling ---
const current = parseFloat(item.last_close) || 0; const current = parseFloat(item.last_close) || 0;
const low = parseFloat(item.low_52) || 0; const low = parseFloat(item.low_52) || 0;
const high = parseFloat(item.high_52) || 0; const high = parseFloat(item.high_52) || 0;
// 52W Range Progress Bar // 52W Range Progress Bar calculation
let rangePct = high > low ? Math.min(Math.max(((current - low) / (high - low)) * 100, 0), 100) : 0; let rangePct = high > low ? Math.min(Math.max(((current - low) / (high - low)) * 100, 0), 100) : 0;
const rangeColor = rangePct > 80 ? 'text-danger' : (rangePct < 20 ? 'text-success' : 'text-muted'); const rangeColor = rangePct > 80 ? 'text-danger' : (rangePct < 20 ? 'text-success' : 'text-muted');
// Date Freshness check (highlight if date matches today) // Freshness class for the badge
const isFresh = item.last_date === todayStr; const isFresh = item.last_date === todayStr;
const dateStyle = isFresh ? 'badge bg-success-subtle text-success border border-success-subtle' : 'text-muted'; const dateBadgeClass = isFresh ? 'badge bg-success-subtle text-success border border-success-subtle' : 'text-muted';
// --- 3. Construct Row --- // --- 3. Construct Row ---
htmlContent += ` htmlContent += `
@@ -279,7 +349,11 @@
<td> <td>
<div class="fw-bold">${item.name || item.symbol}</div> <div class="fw-bold">${item.name || item.symbol}</div>
</td> </td>
<td><span class="${dateStyle}" style="font-size: 0.75rem; padding: 2px 5px; border-radius: 4px;">${displayDate}</span></td> <td>
<span class="${dateBadgeClass} table-date" style="color: ${dateColor} !important; font-size: 0.75rem; padding: 2px 5px; border-radius: 4px;">
${displayDate}
</span>
</td>
<td class="fw-bold">${item.last_close}</td> <td class="fw-bold">${item.last_close}</td>
<td class="${item.change_pct >= 0 ? 'text-up' : 'text-down'}"> <td class="${item.change_pct >= 0 ? 'text-up' : 'text-down'}">
${item.change_pct >= 0 ? '+' : ''}${item.change_pct}% ${item.change_pct >= 0 ? '+' : ''}${item.change_pct}%
@@ -298,14 +372,16 @@
<td>${formatEma(item.last_ema200)}</td> <td>${formatEma(item.last_ema200)}</td>
<td>${formatKD(item.kd_values)}</td> <td>${formatKD(item.kd_values)}</td>
</tr>`; </tr>`;
}); }); // End forEach
// Batch update the DOM once // 4. Final Injection
tbody.innerHTML = htmlContent; tbody.innerHTML = htmlContent;
} catch (error) { } catch (error) {
console.error("Fetch error:", error); console.error("Fetch error:", error);
tbody.innerHTML = '<tr><td colspan="10" class="text-danger p-4">Error loading summary. Check server logs.</td></tr>'; if (tbody) {
tbody.innerHTML = '<tr><td colspan="10" class="text-danger p-4 text-center">Error loading summary. Check server logs.</td></tr>';
}
} finally { } finally {
if (loading) loading.style.display = 'none'; if (loading) loading.style.display = 'none';
} }