diff --git a/app/static/index.html b/app/static/index.html index d386778..2cd175f 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -936,6 +936,50 @@ + +
+
+
+
批量导出数据
+
导出所有配置品种的K线数据
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
@@ -1992,6 +2036,163 @@ showToast(`已导出 ${periodCount} 个周期数据`, 'success'); } + // 批量导出数据 + async function batchExportData() { + const dataType = document.getElementById('batchExportDataType').value; + const period = document.getElementById('batchExportPeriod').value; + const endDate = document.getElementById('batchExportEndTime').value; + + // 获取所有配置的品种 + try { + const res = await fetch(`${API}/config/symbols`); + const data = await res.json(); + + if (!data.symbols || data.symbols.length === 0) { + showToast('暂无可导出的品种,请先添加品种配置', 'error'); + return; + } + + // 过滤指定数据类型的品种 + const symbols = data.symbols.filter(s => s.data_type === dataType); + if (symbols.length === 0) { + showToast(`暂无${dataType === 'futures' ? '期货' : '股票'}品种配置`, 'error'); + return; + } + + const symbolList = symbols.map(s => s.symbol); + + // 显示进度条 + const progressContainer = document.getElementById('batchExportProgress'); + const progressFill = document.getElementById('batchExportProgressFill'); + const progressText = document.getElementById('batchExportProgressText'); + const resultContainer = document.getElementById('batchExportResult'); + + progressContainer.classList.remove('hidden'); + resultContainer.classList.add('hidden'); + + const endDateDisplay = endDate || new Date().toISOString().slice(0, 10); + addLog(`开始批量导出: ${dataType === 'futures' ? '期货' : '股票'} ${symbolList.length} 个品种, 结束日期: ${endDateDisplay}`); + + let successCount = 0; + let failedCount = 0; + let totalCandles = 0; + const failedSymbols = []; + + // 逐个获取并导出数据 + for (let i = 0; i < symbolList.length; i++) { + const symbol = symbolList[i]; + const progress = ((i + 1) / symbolList.length * 100).toFixed(0); + progressFill.style.width = `${progress}%`; + progressText.textContent = `正在导出: ${symbol} (${i + 1}/${symbolList.length})`; + + try { + // 构建查询参数 + const params = new URLSearchParams(); + if (period) params.append('period', period); + if (endDate) { + const endDateTime = new Date(endDate); + endDateTime.setHours(23, 59, 59, 999); + params.append('end_time', endDateTime.toISOString()); + } + + const queryString = params.toString(); + const url = `${API}/data/latest/${symbol}${queryString ? '?' + queryString : ''}`; + + const fetchRes = await fetch(url); + if (!fetchRes.ok) { + failedCount++; + failedSymbols.push(symbol); + continue; + } + + const fetchData = await fetchRes.json(); + + if (!fetchData.timeframes || fetchData.timeframes.length === 0) { + failedCount++; + failedSymbols.push(symbol); + continue; + } + + // 导出单个品种数据 + const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); + const filename = `${symbol}_${period || '多周期'}_截至${endDateDisplay}_${timestamp}.json`; + + const exportObj = { + symbol: fetchData.symbol || symbol, + type: fetchData.type || dataType, + period: period || 'all', + end_date: endDateDisplay, + current_price: fetchData.current_price, + timestamp: fetchData.timestamp || new Date().toISOString(), + timeframes: {} + }; + + fetchData.timeframes.forEach(tf => { + exportObj.timeframes[tf.period] = tf.candles || []; + }); + + const jsonStr = JSON.stringify(exportObj, null, 2); + const blob = new Blob([jsonStr], { type: 'application/json' }); + 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); + + const candleCount = fetchData.timeframes.reduce((sum, tf) => sum + (tf.candles ? tf.candles.length : 0), 0); + successCount++; + totalCandles += candleCount; + + // 添加小延迟避免浏览器阻止多次下载 + await new Promise(resolve => setTimeout(resolve, 200)); + + } catch (e) { + console.error(`导出 ${symbol} 失败:`, e); + failedCount++; + failedSymbols.push(symbol); + } + } + + // 显示结果 + progressContainer.classList.add('hidden'); + resultContainer.classList.remove('hidden'); + + let resultHtml = ` +
+

批量导出完成

+
+
成功: ${successCount} 个品种
+
失败: ${failedCount} 个品种
+
总计: ${totalCandles} 条K线
+
+ `; + + if (failedSymbols.length > 0) { + resultHtml += ` +
+
失败的品种:
+
${failedSymbols.join(', ')}
+
+ `; + } + + resultHtml += '
'; + resultContainer.innerHTML = resultHtml; + + addLog(`批量导出完成: 成功 ${successCount}, 失败 ${failedCount}, 总计 ${totalCandles} 条K线`, successCount > 0 ? 'success' : 'error'); + showToast(`批量导出完成: ${successCount} 个品种成功`, successCount > 0 ? 'success' : 'error'); + + } catch (e) { + console.error('批量导出失败:', e); + showToast(`批量导出失败: ${e.message}`, 'error'); + addLog(`批量导出失败: ${e.message}`, 'error'); + } + } + function renderKlineChart(timeframe, symbol) { const resultContainer = document.getElementById('queryResult'); @@ -3001,6 +3202,7 @@ function initDefaultDate() { const today = new Date().toISOString().slice(0, 10); document.getElementById('queryEndTime').value = today; + document.getElementById('batchExportEndTime').value = today; } // 初始化 diff --git a/data/buffer.db b/data/buffer.db index ea73a4e..54c9ba6 100644 Binary files a/data/buffer.db and b/data/buffer.db differ diff --git a/data/futures_analysis.db b/data/futures_analysis.db index bd04e8c..bbe6acd 100644 Binary files a/data/futures_analysis.db and b/data/futures_analysis.db differ