Single-File Data Dashboard
Core Pattern
Single index.html with embedded ECharts (CDN), fetch-based data layer, and
client-side computation. No server, no build step, no API keys (use public APIs).
Pitfalls
1. CORS: file:// blocks API fetches
When opening file:///path/dashboard.html, the browser origin is null.
Most public APIs reject null origin even if they otherwise support CORS.
✅ Always serve via local HTTP server:
cd /path/to/project && python -m http.server 8760
# Then open http://localhost:8760/dashboard.html
This is mandatory — never tell the user to double-click the HTML file.
2. Eastmoney API doesn't support ETFs
The standard stock K-line endpoint (push2his.eastmoney.com/api/qt/stock/kline/get)
returns empty data for ETFs (codes starting with 5 on Shanghai, 1 on Shenzhen).
✅ Use Tencent API as fallback for ETFs and all stocks:
| Priority | Source | Coverage |
|---|---|---|
| 1 (primary) | Eastmoney push2his.eastmoney.com |
Regular A-shares (6/0/3/2xxxxx) |
| 2 (fallback) | Tencent web.ifzq.gtimg.cn |
ETFs, all A-shares |
3. Tencent API is JSONP-wrapped
The Tencent minute/quote endpoint returns JSONP:
min_data=({...actual JSON...});
✅ Strip the wrapper before JSON.parse:
const text = await (await fetch(url)).text();
const jsonStr = text.replace(/^min_data=/, '').replace(/;$/, '');
const data = JSON.parse(jsonStr);
4. Tencent qt array index conventions
The real-time quote is a flat array with positional fields:
const qt = data.data[txKey].qt[txKey];
// qt[1] = name (Chinese)
// qt[3] = current price
// qt[4] = prev close
// qt[5] = open
// qt[6] = volume (today, shares)
// qt[33] = today high
// qt[34] = today low
// qt[37] = amount (today, yuan)
5. Tencent K-line format differs from Eastmoney
Eastmoney K-line: CSV string "date,open,close,high,low,volume,amount"
Tencent K-line: array [date, open, close, high, low, volume]
Note: Tencent does NOT include amount (成交额), set to 0.
6. Dual-API fallback pattern
Structure each fetch function to try primary first, fall through silently:
async function fetchX(code, market, prefix) {
// Try Eastmoney
try {
const data = await fetchEM(url);
if (data?.data?.klines?.length > 0) { dataSource = 'em'; return parse(data); }
} catch(e) { /* fall through */ }
// Fallback
addLog('切换到腾讯数据源...', 'info');
dataSource = 'tx';
return fetchTX(code, prefix);
}
Log the switch so the user knows which source is active.
Chinese Stock Data APIs
Eastmoney (primary — regular stocks)
- Real-time quote:
https://push2.eastmoney.com/api/qt/stock/get?secid={market}.{code}&fields=f43,f57,f58,f169,f170,...market: 1=SH, 0=SZf43: price×100,f58: name,f169: change×100,f170: changePct×100
- Daily K-line:
https://push2his.eastmoney.com/api/qt/stock/kline/get?secid={market}.{code}&klt=101&fqt=1&lmt=400&fields2=f51,f52,f53,f54,f55,f56,f57klt=101: daily,fqt=1: forward-adjusted- Response:
data.klines— array of CSV strings:date,open,close,high,low,volume,amount
- Minute intraday:
https://push2his.eastmoney.com/api/qt/stock/trends2/get?secid={market}.{code}&ndays=1&fields2=f51,f52,f53,f54,f55,f56,f57,f58data.prevClose: yesterday close,data.trends: array of CSV strings- Minute CSV:
time,prevClose?,price,avgPrice?,?,volume,?,avgPrice
Tencent (fallback — ETFs and all stocks)
- Daily K-line:
https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?param={prefix}{code},day,,,{count},qfqprefix:shorsz,qfq: forward-adjusted- Response:
data.{prefix}{code}.qfqday— array of[date, open, close, high, low, volume]
- Minute + Quote (combined):
https://web.ifzq.gtimg.cn/appstock/app/minute/query?_var=min_data&code={prefix}{code}- JSONP wrapper:
min_data=(...) - Minute data:
data.{key}.data.data— array of"HHMM price volume amount" - Real-time quote:
data.{key}.qt.{key}— positional array
- JSONP wrapper:
Market detection
function detectMarket(code) {
if (code.startsWith('6')) return [1, '1.'+code, 'sh']; // Shanghai stock
if (code.startsWith('5')) return [1, '1.'+code, 'sh']; // Shanghai ETF
if (code.startsWith('0')||code.startsWith('3')||code.startsWith('2')||code.startsWith('1'))
return [0, '0.'+code, 'sz']; // Shenzhen
return [0, '0.'+code, 'sz'];
}
// Returns: [eastmoneyMarket, eastmoneySecid, tencentPrefix]
ECharts Integration
Load from CDN in <head>:
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
Always call .dispose() before .init() to prevent memory leaks on re-render:
function getOrCreateChart(domId) {
if (chartInstances[domId]) { chartInstances[domId].dispose(); }
return echarts.init(document.getElementById(domId), 'dark');
}
Register window.addEventListener('resize', ...) to call .resize() on all instances.
Verification
For single-file HTML dashboards, check:
- The page loads without JS errors (open browser console)
- CORS is handled — verify via
http://localhost:8760/, notfile:// - Try both a regular stock (600519) and an ETF (515880) to test fallback
- Charts render with actual data points (not empty axes)
References
references/chinese-stock-apis.md— exact API response formats with field indices for Eastmoney and Tencent endpoints