feat: Translate all Chinese content to English in webui module
- Translate all Chinese comments and strings in webui/app.py - Translate all Chinese comments and strings in webui/run.py - Translate all Chinese comments and strings in webui/start.sh - Translate all Chinese content in webui/README.md - Translate all Chinese content in webui/templates/index.html - Add prediction results directory for analysis - Complete internationalization of webui module
This commit is contained in:
+186
-186
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Kronos 金融预测 Web UI</title>
|
||||
<title>Kronos Financial Prediction Web UI</title>
|
||||
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||
<style>
|
||||
@@ -93,7 +93,7 @@
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
/* 预测质量参数样式 */
|
||||
/* Prediction quality parameter styles */
|
||||
.form-group input[type="range"] {
|
||||
width: 70%;
|
||||
margin-right: 10px;
|
||||
@@ -227,7 +227,7 @@
|
||||
color: #2d3748;
|
||||
}
|
||||
|
||||
/* 时间窗口选择器样式 */
|
||||
/* Time window selector styles */
|
||||
.time-window-container {
|
||||
background: #f7fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
@@ -301,7 +301,7 @@
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* 对比分析样式 */
|
||||
/* Comparison analysis styles */
|
||||
.comparison-section {
|
||||
background: #f7fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
@@ -441,79 +441,79 @@
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🚀 Kronos 金融预测 Web UI</h1>
|
||||
<p>基于AI的金融K线数据预测分析平台</p>
|
||||
<h1>🚀 Kronos Financial Prediction Web UI</h1>
|
||||
<p>AI-based financial K-line data prediction analysis platform</p>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="control-panel">
|
||||
<h2>🎯 控制面板</h2>
|
||||
<h2>🎯 Control Panel</h2>
|
||||
|
||||
<!-- 模型选择 -->
|
||||
<!-- Model Selection -->
|
||||
<div class="form-group">
|
||||
<label for="model-select">选择模型:</label>
|
||||
<label for="model-select">Select Model:</label>
|
||||
<select id="model-select">
|
||||
<option value="">请先加载可用模型</option>
|
||||
<option value="">Please load available models first</option>
|
||||
</select>
|
||||
<small class="form-text">选择要使用的Kronos模型</small>
|
||||
<small class="form-text">Select the Kronos model to use</small>
|
||||
</div>
|
||||
|
||||
<!-- 设备选择 -->
|
||||
<!-- Device Selection -->
|
||||
<div class="form-group">
|
||||
<label for="device-select">选择设备:</label>
|
||||
<label for="device-select">Select Device:</label>
|
||||
<select id="device-select">
|
||||
<option value="cpu">CPU</option>
|
||||
<option value="cuda">CUDA (NVIDIA GPU)</option>
|
||||
<option value="mps">MPS (Apple Silicon)</option>
|
||||
</select>
|
||||
<small class="form-text">选择模型运行的设备</small>
|
||||
<small class="form-text">Select the device to run the model on</small>
|
||||
</div>
|
||||
|
||||
<!-- 模型状态 -->
|
||||
<!-- Model Status -->
|
||||
<div id="model-status" class="status info" style="display: none;">
|
||||
模型状态信息
|
||||
Model status information
|
||||
</div>
|
||||
|
||||
<!-- 加载模型按钮 -->
|
||||
<!-- Load Model Button -->
|
||||
<button id="load-model-btn" class="btn btn-secondary">
|
||||
🔄 加载模型
|
||||
🔄 Load Model
|
||||
</button>
|
||||
|
||||
<hr style="margin: 20px 0; border: 1px solid #e2e8f0;">
|
||||
|
||||
<!-- 数据文件选择 -->
|
||||
<!-- Data File Selection -->
|
||||
<div class="form-group">
|
||||
<label for="data-file-select">选择数据文件:</label>
|
||||
<label for="data-file-select">Select Data File:</label>
|
||||
<select id="data-file-select">
|
||||
<option value="">请先加载数据文件列表</option>
|
||||
<option value="">Please load data file list first</option>
|
||||
</select>
|
||||
<small class="form-text">从data目录选择K线数据文件</small>
|
||||
<small class="form-text">Select K-line data file from data directory</small>
|
||||
</div>
|
||||
|
||||
<button id="load-data-btn" class="btn btn-secondary">
|
||||
📁 加载数据
|
||||
📁 Load Data
|
||||
</button>
|
||||
|
||||
<!-- 数据信息显示 -->
|
||||
<!-- Data Information Display -->
|
||||
<div id="data-info" class="data-info" style="display: none;">
|
||||
<h3>📊 数据信息</h3>
|
||||
<p><strong>行数:</strong> <span id="data-rows">-</span></p>
|
||||
<p><strong>列数:</strong> <span id="data-cols">-</span></p>
|
||||
<p><strong>时间范围:</strong> <span id="data-time-range">-</span></p>
|
||||
<p><strong>价格范围:</strong> <span id="data-price-range">-</span></p>
|
||||
<p><strong>时间频率:</strong> <span id="data-timeframe">-</span></p>
|
||||
<p><strong>预测列:</strong> <span id="data-prediction-cols">-</span></p>
|
||||
<h3>📊 Data Information</h3>
|
||||
<p><strong>Rows:</strong> <span id="data-rows">-</span></p>
|
||||
<p><strong>Columns:</strong> <span id="data-cols">-</span></p>
|
||||
<p><strong>Time Range:</strong> <span id="data-time-range">-</span></p>
|
||||
<p><strong>Price Range:</strong> <span id="data-price-range">-</span></p>
|
||||
<p><strong>Time Frequency:</strong> <span id="data-timeframe">-</span></p>
|
||||
<p><strong>Prediction Columns:</strong> <span id="data-prediction-cols">-</span></p>
|
||||
</div>
|
||||
|
||||
<hr style="margin: 20px 0; border: 1px solid #e2e8f0;">
|
||||
|
||||
<!-- 时间窗口选择器 -->
|
||||
<!-- Time Window Selector -->
|
||||
<div class="time-window-container">
|
||||
<h3>⏰ 时间窗口选择</h3>
|
||||
<h3>⏰ Time Window Selection</h3>
|
||||
<div class="time-window-info">
|
||||
<span id="window-start">开始: --</span>
|
||||
<span id="window-end">结束: --</span>
|
||||
<span id="window-size">窗口大小: 400+120=520个数据点</span>
|
||||
<span id="window-start">Start: --</span>
|
||||
<span id="window-end">End: --</span>
|
||||
<span id="window-size">Window Size: 400+120=520 data points</span>
|
||||
</div>
|
||||
|
||||
<div class="time-window-slider">
|
||||
@@ -523,104 +523,104 @@
|
||||
<div class="slider-handle end-handle" id="end-handle"></div>
|
||||
</div>
|
||||
<div class="slider-labels">
|
||||
<span id="min-label">最早</span>
|
||||
<span id="max-label">最新</span>
|
||||
<span id="min-label">Earliest</span>
|
||||
<span id="max-label">Latest</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<small class="form-text">拖动滑条选择520个数据点的时间窗口位置,绿色区域表示固定的400+120数据点范围</small>
|
||||
<small class="form-text">Drag slider to select time window position for 520 data points, green area represents fixed 400+120 data point range</small>
|
||||
</div>
|
||||
|
||||
<!-- 预测参数 -->
|
||||
<!-- Prediction Parameters -->
|
||||
<div class="form-group">
|
||||
<label for="lookback">回看窗口大小:</label>
|
||||
<label for="lookback">Lookback Window Size:</label>
|
||||
<input type="number" id="lookback" value="400" readonly>
|
||||
<small class="form-text">固定为400个数据点</small>
|
||||
<small class="form-text">Fixed at 400 data points</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="pred-len">预测长度:</label>
|
||||
<label for="pred-len">Prediction Length:</label>
|
||||
<input type="number" id="pred-len" value="120" readonly>
|
||||
<small class="form-text">固定为120个数据点</small>
|
||||
<small class="form-text">Fixed at 120 data points</small>
|
||||
</div>
|
||||
|
||||
<!-- 预测质量参数 -->
|
||||
<!-- Prediction Quality Parameters -->
|
||||
<div class="form-group">
|
||||
<label for="temperature">预测温度 (T):</label>
|
||||
<label for="temperature">Prediction Temperature (T):</label>
|
||||
<input type="range" id="temperature" value="1.0" min="0.1" max="2.0" step="0.1">
|
||||
<span id="temperature-value">1.0</span>
|
||||
<small class="form-text">控制预测的随机性,值越高预测越多样化,值越低预测越保守</small>
|
||||
<small class="form-text">Controls prediction randomness, higher values make predictions more diverse, lower values make predictions more conservative</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="top-p">核采样参数 (top_p):</label>
|
||||
<label for="top-p">Nucleus Sampling Parameter (top_p):</label>
|
||||
<input type="range" id="top-p" value="0.9" min="0.1" max="1.0" step="0.1">
|
||||
<span id="top-p-value">0.9</span>
|
||||
<small class="form-text">控制预测的多样性,值越高考虑的概率分布越广</small>
|
||||
<small class="form-text">Controls prediction diversity, higher values consider broader probability distributions</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="sample-count">样本数量:</label>
|
||||
<label for="sample-count">Sample Count:</label>
|
||||
<input type="number" id="sample-count" value="1" min="1" max="5" step="1">
|
||||
<small class="form-text">生成多个预测样本以提高质量(建议1-3个)</small>
|
||||
<small class="form-text">Generate multiple prediction samples to improve quality (recommended 1-3)</small>
|
||||
</div>
|
||||
|
||||
<button id="predict-btn" class="btn btn-success" disabled>
|
||||
🔮 开始预测
|
||||
🔮 Start Prediction
|
||||
</button>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<!-- Loading Status -->
|
||||
<div id="loading" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>正在处理,请稍候...</p>
|
||||
<p>Processing, please wait...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-container">
|
||||
<h2>📈 预测结果图表</h2>
|
||||
<h2>📈 Prediction Results Chart</h2>
|
||||
<div id="chart"></div>
|
||||
|
||||
<!-- 对比分析 -->
|
||||
<!-- Comparison Analysis -->
|
||||
<div id="comparison-section" class="comparison-section" style="display: none;">
|
||||
<h3>📊 预测 vs 实际数据对比</h3>
|
||||
<h3>📊 Prediction vs Actual Data Comparison</h3>
|
||||
<div id="comparison-info" class="comparison-info">
|
||||
<p><strong>预测类型:</strong> <span id="prediction-type">-</span></p>
|
||||
<p><strong>对比数据:</strong> <span id="comparison-data">-</span></p>
|
||||
<p><strong>Prediction Type:</strong> <span id="prediction-type">-</span></p>
|
||||
<p><strong>Comparison Data:</strong> <span id="comparison-data">-</span></p>
|
||||
</div>
|
||||
|
||||
<div class="error-stats">
|
||||
<div class="error-stat">
|
||||
<h4>平均绝对误差</h4>
|
||||
<h4>Mean Absolute Error</h4>
|
||||
<div class="value" id="mae">-</div>
|
||||
<div class="unit">价格单位</div>
|
||||
<div class="unit">Price Units</div>
|
||||
</div>
|
||||
<div class="error-stat">
|
||||
<h4>均方根误差</h4>
|
||||
<h4>Root Mean Square Error</h4>
|
||||
<div class="value" id="rmse">-</div>
|
||||
<div class="unit">价格单位</div>
|
||||
<div class="unit">Price Units</div>
|
||||
</div>
|
||||
<div class="error-stat">
|
||||
<h4>平均绝对百分比误差</h4>
|
||||
<h4>Mean Absolute Percentage Error</h4>
|
||||
<div class="value" id="mape">-</div>
|
||||
<div class="unit">%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="error-details">
|
||||
<h4>详细对比数据:</h4>
|
||||
<h4>Detailed Comparison Data:</h4>
|
||||
<div style="max-height: 300px; overflow-y: auto;">
|
||||
<table class="comparison-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>时间</th>
|
||||
<th>实际开盘</th>
|
||||
<th>预测开盘</th>
|
||||
<th>实际最高</th>
|
||||
<th>预测最高</th>
|
||||
<th>实际最低</th>
|
||||
<th>预测最低</th>
|
||||
<th>实际收盘</th>
|
||||
<th>预测收盘</th>
|
||||
<th>Time</th>
|
||||
<th>Actual Open</th>
|
||||
<th>Predicted Open</th>
|
||||
<th>Actual High</th>
|
||||
<th>Predicted High</th>
|
||||
<th>Actual Low</th>
|
||||
<th>Predicted Low</th>
|
||||
<th>Actual Close</th>
|
||||
<th>Predicted Close</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="comparison-tbody">
|
||||
@@ -634,58 +634,58 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 全局变量
|
||||
// Global variables
|
||||
let currentDataFile = null;
|
||||
let currentDataInfo = null;
|
||||
let availableModels = [];
|
||||
let modelLoaded = false;
|
||||
|
||||
// 页面加载完成后初始化
|
||||
// Initialize after page loads
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initializeApp();
|
||||
});
|
||||
|
||||
// 初始化应用
|
||||
// Initialize application
|
||||
async function initializeApp() {
|
||||
console.log('🚀 初始化 Kronos Web UI...');
|
||||
console.log('🚀 Initializing Kronos Web UI...');
|
||||
|
||||
// 加载可用模型
|
||||
// Load available models
|
||||
await loadAvailableModels();
|
||||
|
||||
// 加载数据文件列表
|
||||
// Load data file list
|
||||
await loadDataFiles();
|
||||
|
||||
// 设置事件监听器
|
||||
// Set up event listeners
|
||||
setupEventListeners();
|
||||
|
||||
// 初始化时间滑块
|
||||
// Initialize time slider
|
||||
initializeTimeSlider();
|
||||
|
||||
console.log('✅ 应用初始化完成');
|
||||
console.log('✅ Application initialization completed');
|
||||
}
|
||||
|
||||
// 加载可用模型
|
||||
// Load available models
|
||||
async function loadAvailableModels() {
|
||||
try {
|
||||
const response = await axios.get('/api/available-models');
|
||||
if (response.data.model_available) {
|
||||
availableModels = response.data.models;
|
||||
populateModelSelect();
|
||||
console.log('✅ 可用模型加载成功:', availableModels);
|
||||
console.log('✅ Available models loaded successfully:', availableModels);
|
||||
} else {
|
||||
console.warn('⚠️ Kronos模型库不可用');
|
||||
showStatus('warning', 'Kronos模型库不可用,将使用模拟预测');
|
||||
console.warn('⚠️ Kronos model library not available');
|
||||
showStatus('warning', 'Kronos model library not available, will use simulated prediction');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 加载可用模型失败:', error);
|
||||
showStatus('error', '加载可用模型失败');
|
||||
console.error('❌ Failed to load available models:', error);
|
||||
showStatus('error', 'Failed to load available models');
|
||||
}
|
||||
}
|
||||
|
||||
// 填充模型选择下拉框
|
||||
// Populate model selection dropdown
|
||||
function populateModelSelect() {
|
||||
const modelSelect = document.getElementById('model-select');
|
||||
modelSelect.innerHTML = '<option value="">请选择模型</option>';
|
||||
modelSelect.innerHTML = '<option value="">Please select model</option>';
|
||||
|
||||
Object.entries(availableModels).forEach(([key, model]) => {
|
||||
const option = document.createElement('option');
|
||||
@@ -695,13 +695,13 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 加载模型
|
||||
// Load model
|
||||
async function loadModel() {
|
||||
const modelKey = document.getElementById('model-select').value;
|
||||
const device = document.getElementById('device-select').value;
|
||||
|
||||
if (!modelKey) {
|
||||
showStatus('error', '请选择要加载的模型');
|
||||
showStatus('error', 'Please select a model to load');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -719,45 +719,45 @@
|
||||
showStatus('success', response.data.message);
|
||||
updateModelStatus();
|
||||
document.getElementById('predict-btn').disabled = false;
|
||||
console.log('✅ 模型加载成功:', response.data.model_info);
|
||||
console.log('✅ Model loaded successfully:', response.data.model_info);
|
||||
} else {
|
||||
showStatus('error', response.data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 模型加载失败:', error);
|
||||
showStatus('error', `模型加载失败: ${error.response?.data?.error || error.message}`);
|
||||
console.error('❌ Model loading failed:', error);
|
||||
showStatus('error', `Model loading failed: ${error.response?.data?.error || error.message}`);
|
||||
} finally {
|
||||
showLoading(false);
|
||||
document.getElementById('load-model-btn').disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新模型状态
|
||||
// Update model status
|
||||
async function updateModelStatus() {
|
||||
try {
|
||||
const response = await axios.get('/api/model-status');
|
||||
const status = response.data;
|
||||
|
||||
if (status.loaded) {
|
||||
showStatus('success', `模型已加载: ${status.current_model.name} on ${status.current_model.device}`);
|
||||
showStatus('success', `Model loaded: ${status.current_model.name} on ${status.current_model.device}`);
|
||||
} else if (status.available) {
|
||||
showStatus('info', '模型可用但未加载');
|
||||
showStatus('info', 'Model available but not loaded');
|
||||
} else {
|
||||
showStatus('warning', '模型库不可用');
|
||||
showStatus('warning', 'Model library not available');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 获取模型状态失败:', error);
|
||||
console.error('❌ Failed to get model status:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载数据文件列表
|
||||
// Load data file list
|
||||
async function loadDataFiles() {
|
||||
try {
|
||||
const response = await axios.get('/api/data-files');
|
||||
const dataFiles = response.data;
|
||||
|
||||
const dataFileSelect = document.getElementById('data-file-select');
|
||||
dataFileSelect.innerHTML = '<option value="">请选择数据文件</option>';
|
||||
dataFileSelect.innerHTML = '<option value="">Please select data file</option>';
|
||||
|
||||
dataFiles.forEach(file => {
|
||||
const option = document.createElement('option');
|
||||
@@ -766,19 +766,19 @@
|
||||
dataFileSelect.appendChild(option);
|
||||
});
|
||||
|
||||
console.log('✅ 数据文件列表加载成功:', dataFiles);
|
||||
console.log('✅ Data file list loaded successfully:', dataFiles);
|
||||
} catch (error) {
|
||||
console.error('❌ 加载数据文件列表失败:', error);
|
||||
showStatus('error', '加载数据文件列表失败');
|
||||
console.error('❌ Failed to load data file list:', error);
|
||||
showStatus('error', 'Failed to load data file list');
|
||||
}
|
||||
}
|
||||
|
||||
// 加载数据文件
|
||||
// Load data file
|
||||
async function loadData() {
|
||||
const filePath = document.getElementById('data-file-select').value;
|
||||
|
||||
if (!filePath) {
|
||||
showStatus('error', '请选择要加载的数据文件');
|
||||
showStatus('error', 'Please select a data file to load');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -796,56 +796,56 @@
|
||||
showDataInfo(response.data.data_info);
|
||||
showStatus('success', response.data.message);
|
||||
|
||||
// 更新预测按钮状态
|
||||
// Update prediction button status
|
||||
if (modelLoaded) {
|
||||
document.getElementById('predict-btn').disabled = false;
|
||||
}
|
||||
|
||||
console.log('✅ 数据加载成功:', response.data.data_info);
|
||||
console.log('✅ Data loaded successfully:', response.data.data_info);
|
||||
} else {
|
||||
showStatus('error', response.data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 数据加载失败:', error);
|
||||
showStatus('error', `数据加载失败: ${error.response?.data?.error || error.message}`);
|
||||
console.error('❌ Data loading failed:', error);
|
||||
showStatus('error', `Data loading failed: ${error.response?.data?.error || error.message}`);
|
||||
} finally {
|
||||
showLoading(false);
|
||||
document.getElementById('load-data-btn').disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示数据信息
|
||||
// Display data information
|
||||
function showDataInfo(dataInfo) {
|
||||
document.getElementById('data-info').style.display = 'block';
|
||||
document.getElementById('data-rows').textContent = dataInfo.rows;
|
||||
document.getElementById('data-cols').textContent = dataInfo.columns.length;
|
||||
document.getElementById('data-time-range').textContent = `${dataInfo.start_date} 至 ${dataInfo.end_date}`;
|
||||
document.getElementById('data-time-range').textContent = `${dataInfo.start_date} to ${dataInfo.end_date}`;
|
||||
document.getElementById('data-price-range').textContent = `${dataInfo.price_range.min.toFixed(4)} - ${dataInfo.price_range.max.toFixed(4)}`;
|
||||
document.getElementById('data-timeframe').textContent = dataInfo.timeframe;
|
||||
document.getElementById('data-prediction-cols').textContent = dataInfo.prediction_columns.join(', ');
|
||||
|
||||
// 初始化时间窗口滑条
|
||||
// Initialize time window slider
|
||||
initializeTimeWindowSlider(dataInfo);
|
||||
}
|
||||
|
||||
// 时间窗口滑条相关变量
|
||||
// Time window slider related variables
|
||||
let sliderData = null;
|
||||
let isDragging = false;
|
||||
let currentHandle = null;
|
||||
|
||||
// 初始化时间窗口滑条
|
||||
// Initialize time window slider
|
||||
function initializeTimeSlider() {
|
||||
// 设置滑条事件监听器
|
||||
// Set up slider event listeners
|
||||
setupSliderEventListeners();
|
||||
}
|
||||
|
||||
// 设置滑条事件监听器
|
||||
// Set up slider event listeners
|
||||
function setupSliderEventListeners() {
|
||||
const startHandle = document.getElementById('start-handle');
|
||||
const endHandle = document.getElementById('end-handle');
|
||||
const track = document.querySelector('.slider-track');
|
||||
|
||||
// 开始拖拽
|
||||
// Start dragging
|
||||
startHandle.addEventListener('mousedown', (e) => {
|
||||
isDragging = true;
|
||||
currentHandle = 'start';
|
||||
@@ -858,7 +858,7 @@
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
// 拖拽中
|
||||
// Dragging
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
if (!isDragging) return;
|
||||
|
||||
@@ -875,19 +875,19 @@
|
||||
updateSliderFromHandles();
|
||||
});
|
||||
|
||||
// 结束拖拽
|
||||
// End dragging
|
||||
document.addEventListener('mouseup', () => {
|
||||
isDragging = false;
|
||||
currentHandle = null;
|
||||
});
|
||||
|
||||
// 点击轨道直接设置位置
|
||||
// Click track to set position directly
|
||||
track.addEventListener('click', (e) => {
|
||||
const rect = track.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const percentage = Math.max(0, Math.min(1, x / rect.width));
|
||||
|
||||
// 判断点击位置更接近哪个手柄
|
||||
// Determine which handle is closer to the click position
|
||||
const startHandle = document.getElementById('start-handle');
|
||||
const endHandle = document.getElementById('end-handle');
|
||||
const startRect = startHandle.getBoundingClientRect();
|
||||
@@ -903,17 +903,17 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 更新开始手柄位置
|
||||
// Update start handle position
|
||||
function updateStartHandle(percentage) {
|
||||
const startHandle = document.getElementById('start-handle');
|
||||
const selection = document.getElementById('slider-selection');
|
||||
|
||||
// 固定窗口大小为520个数据点
|
||||
// Fixed window size of 520 data points
|
||||
const windowSize = 520;
|
||||
const totalRows = sliderData ? sliderData.totalRows : 1000;
|
||||
const windowPercentage = windowSize / totalRows;
|
||||
|
||||
// 确保开始手柄不会导致窗口超出数据范围
|
||||
// Ensure start handle doesn't cause window to exceed data range
|
||||
if (percentage + windowPercentage > 1) {
|
||||
percentage = 1 - windowPercentage;
|
||||
}
|
||||
@@ -922,22 +922,22 @@
|
||||
selection.style.left = (percentage * 100) + '%';
|
||||
selection.style.width = (windowPercentage * 100) + '%';
|
||||
|
||||
// 自动调整结束手柄位置,保持固定窗口大小
|
||||
// Automatically adjust end handle position to maintain fixed window size
|
||||
const endHandle = document.getElementById('end-handle');
|
||||
endHandle.style.left = ((percentage + windowPercentage) * 100) + '%';
|
||||
}
|
||||
|
||||
// 更新结束手柄位置
|
||||
// Update end handle position
|
||||
function updateEndHandle(percentage) {
|
||||
const endHandle = document.getElementById('end-handle');
|
||||
const selection = document.getElementById('slider-selection');
|
||||
|
||||
// 固定窗口大小为520个数据点
|
||||
// Fixed window size of 520 data points
|
||||
const windowSize = 520;
|
||||
const totalRows = sliderData ? sliderData.totalRows : 1000;
|
||||
const windowPercentage = windowSize / totalRows;
|
||||
|
||||
// 确保结束手柄不会导致窗口超出数据范围
|
||||
// Ensure end handle doesn't cause window to exceed data range
|
||||
if (percentage - windowPercentage < 0) {
|
||||
percentage = windowPercentage;
|
||||
}
|
||||
@@ -946,12 +946,12 @@
|
||||
selection.style.left = ((percentage - windowPercentage) * 100) + '%';
|
||||
selection.style.width = (windowPercentage * 100) + '%';
|
||||
|
||||
// 自动调整开始手柄位置,保持固定窗口大小
|
||||
// Automatically adjust start handle position to maintain fixed window size
|
||||
const startHandle = document.getElementById('start-handle');
|
||||
startHandle.style.left = ((percentage - windowPercentage) * 100) + '%';
|
||||
}
|
||||
|
||||
// 根据手柄位置更新滑条显示
|
||||
// Update slider display based on handle positions
|
||||
function updateSliderFromHandles() {
|
||||
const startHandle = document.getElementById('start-handle');
|
||||
const endHandle = document.getElementById('end-handle');
|
||||
@@ -961,7 +961,7 @@
|
||||
|
||||
if (!sliderData) return;
|
||||
|
||||
// 计算选中的时间范围
|
||||
// Calculate selected time range
|
||||
const totalTime = sliderData.endDate.getTime() - sliderData.startDate.getTime();
|
||||
const startTime = sliderData.startDate.getTime() + (totalTime * startPercentage);
|
||||
const endTime = sliderData.startDate.getTime() + (totalTime * endPercentage);
|
||||
@@ -969,49 +969,49 @@
|
||||
const startDate = new Date(startTime);
|
||||
const endDate = new Date(endTime);
|
||||
|
||||
// 更新显示信息
|
||||
document.getElementById('window-start').textContent = `开始: ${startDate.toLocaleDateString()}`;
|
||||
document.getElementById('window-end').textContent = `结束: ${endDate.toLocaleDateString()}`;
|
||||
// Update display information
|
||||
document.getElementById('window-start').textContent = `Start: ${startDate.toLocaleDateString()}`;
|
||||
document.getElementById('window-end').textContent = `End: ${endDate.toLocaleDateString()}`;
|
||||
|
||||
// 显示固定的窗口大小
|
||||
document.getElementById('window-size').textContent = `窗口大小: 400 + 120 = 520 个数据点 (固定)`;
|
||||
// Display fixed window size
|
||||
document.getElementById('window-size').textContent = `Window Size: 400 + 120 = 520 data points (fixed)`;
|
||||
|
||||
// 输入框值保持固定
|
||||
// Input field values remain fixed
|
||||
document.getElementById('lookback').value = 400;
|
||||
document.getElementById('pred-len').value = 120;
|
||||
}
|
||||
|
||||
// 根据输入框更新滑条
|
||||
// Update slider based on input fields
|
||||
function updateSliderFromInputs() {
|
||||
if (!sliderData) return;
|
||||
|
||||
// 固定窗口大小:400 + 120 = 520个数据点
|
||||
// Fixed window size: 400 + 120 = 520 data points
|
||||
const lookback = 400;
|
||||
const predLen = 120;
|
||||
const windowSize = lookback + predLen; // 固定为520
|
||||
const windowSize = lookback + predLen; // Fixed at 520
|
||||
|
||||
// 计算滑条位置
|
||||
// Calculate slider position
|
||||
const totalRows = sliderData.totalRows;
|
||||
|
||||
if (windowSize > totalRows) {
|
||||
// 如果窗口大小超过总数据量,显示错误
|
||||
showStatus('error', `数据量不足,需要至少${windowSize}个数据点,当前只有${totalRows}个`);
|
||||
// If window size exceeds total data amount, show error
|
||||
showStatus('error', `Insufficient data, need at least ${windowSize} data points, currently only ${totalRows} available`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算滑条位置(默认选择数据的前半部分)
|
||||
const startPercentage = 0.1; // 从10%开始
|
||||
// Calculate slider position (default select first half of data)
|
||||
const startPercentage = 0.1; // Start from 10%
|
||||
const endPercentage = startPercentage + (windowSize / totalRows);
|
||||
|
||||
// 更新手柄位置
|
||||
// Update handle positions
|
||||
updateStartHandle(startPercentage);
|
||||
updateEndHandle(endPercentage);
|
||||
|
||||
// 更新显示信息
|
||||
// Update display information
|
||||
updateSliderFromHandles();
|
||||
}
|
||||
|
||||
// 初始化时间窗口滑条
|
||||
// Initialize time window slider
|
||||
function initializeTimeWindowSlider(dataInfo) {
|
||||
sliderData = {
|
||||
startDate: new Date(dataInfo.start_date),
|
||||
@@ -1020,23 +1020,23 @@
|
||||
timeframe: dataInfo.timeframe
|
||||
};
|
||||
|
||||
// 设置滑条标签
|
||||
// Set slider labels
|
||||
document.getElementById('min-label').textContent = dataInfo.start_date.split('T')[0];
|
||||
document.getElementById('max-label').textContent = dataInfo.end_date.split('T')[0];
|
||||
|
||||
// 初始化滑条位置
|
||||
// Initialize slider position
|
||||
updateSliderFromInputs();
|
||||
}
|
||||
|
||||
// 开始预测
|
||||
// Start prediction
|
||||
async function startPrediction() {
|
||||
if (!currentDataFile) {
|
||||
showStatus('error', '请先加载数据文件');
|
||||
showStatus('error', 'Please load data file first');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modelLoaded) {
|
||||
showStatus('error', '请先加载模型');
|
||||
showStatus('error', 'Please load model first');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1047,21 +1047,21 @@
|
||||
const lookback = parseInt(document.getElementById('lookback').value);
|
||||
const predLen = parseInt(document.getElementById('pred-len').value);
|
||||
|
||||
// 从时间窗口滑条获取选择的时间范围
|
||||
// Get selected time range from time window slider
|
||||
const startHandle = document.getElementById('start-handle');
|
||||
const startPercentage = parseFloat(startHandle.style.left) / 100;
|
||||
|
||||
if (!sliderData) {
|
||||
showStatus('error', '时间窗口滑条未初始化');
|
||||
showStatus('error', 'Time window slider not initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算选择的时间范围
|
||||
// Calculate selected time range
|
||||
const totalTime = sliderData.endDate.getTime() - sliderData.startDate.getTime();
|
||||
const startTime = sliderData.startDate.getTime() + (totalTime * startPercentage);
|
||||
const startDate = new Date(startTime);
|
||||
|
||||
// 获取预测质量参数
|
||||
// Get prediction quality parameters
|
||||
const temperature = parseFloat(document.getElementById('temperature').value);
|
||||
const topP = parseFloat(document.getElementById('top-p').value);
|
||||
const sampleCount = parseInt(document.getElementById('sample-count').value);
|
||||
@@ -1070,39 +1070,39 @@
|
||||
file_path: currentDataFile,
|
||||
lookback: lookback,
|
||||
pred_len: predLen,
|
||||
start_date: startDate.toISOString().slice(0, 16), // 格式化为 YYYY-MM-DDTHH:MM
|
||||
start_date: startDate.toISOString().slice(0, 16), // Format as YYYY-MM-DDTHH:MM
|
||||
temperature: temperature,
|
||||
top_p: topP,
|
||||
sample_count: sampleCount
|
||||
};
|
||||
|
||||
console.log('🚀 开始预测,参数:', predictionParams);
|
||||
console.log('🚀 Starting prediction, parameters:', predictionParams);
|
||||
|
||||
const response = await axios.post('/api/predict', predictionParams);
|
||||
|
||||
if (response.data.success) {
|
||||
// 显示预测结果
|
||||
// Display prediction results
|
||||
displayPredictionResult(response.data);
|
||||
showStatus('success', response.data.message);
|
||||
} else {
|
||||
showStatus('error', response.data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 预测失败:', error);
|
||||
showStatus('error', `预测失败: ${error.response?.data?.error || error.message}`);
|
||||
console.error('❌ Prediction failed:', error);
|
||||
showStatus('error', `Prediction failed: ${error.response?.data?.error || error.message}`);
|
||||
} finally {
|
||||
showLoading(false);
|
||||
document.getElementById('predict-btn').disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示预测结果
|
||||
// Display prediction results
|
||||
function displayPredictionResult(result) {
|
||||
// 显示图表
|
||||
// Display chart
|
||||
const chartData = JSON.parse(result.chart);
|
||||
Plotly.newPlot('chart', chartData.data, chartData.layout);
|
||||
|
||||
// 显示对比分析(如果有实际数据)
|
||||
// Display comparison analysis (if actual data exists)
|
||||
if (result.has_comparison) {
|
||||
displayComparisonAnalysis(result);
|
||||
} else {
|
||||
@@ -1110,27 +1110,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 显示对比分析
|
||||
// Display comparison analysis
|
||||
function displayComparisonAnalysis(result) {
|
||||
document.getElementById('comparison-section').style.display = 'block';
|
||||
|
||||
// 更新对比信息
|
||||
// Update comparison information
|
||||
document.getElementById('prediction-type').textContent = result.prediction_type;
|
||||
document.getElementById('comparison-data').textContent = `${result.actual_data.length} 个实际数据点`;
|
||||
document.getElementById('comparison-data').textContent = `${result.actual_data.length} actual data points`;
|
||||
|
||||
// 计算误差统计
|
||||
// Calculate error statistics
|
||||
const errorStats = getPredictionQuality(result.prediction_results, result.actual_data);
|
||||
|
||||
// 显示误差统计
|
||||
// Display error statistics
|
||||
document.getElementById('mae').textContent = errorStats.mae.toFixed(4);
|
||||
document.getElementById('rmse').textContent = errorStats.rmse.toFixed(4);
|
||||
document.getElementById('mape').textContent = errorStats.mape.toFixed(2);
|
||||
|
||||
// 填充对比表格
|
||||
// Fill comparison table
|
||||
fillComparisonTable(result.prediction_results, result.actual_data);
|
||||
}
|
||||
|
||||
// 计算预测质量指标
|
||||
// Calculate prediction quality metrics
|
||||
function getPredictionQuality(predictions, actuals) {
|
||||
if (!predictions || !actuals || predictions.length === 0 || actuals.length === 0) {
|
||||
return { mae: 0, rmse: 0, mape: 0 };
|
||||
@@ -1143,7 +1143,7 @@
|
||||
const pred = predictions[i];
|
||||
const act = actuals[i];
|
||||
|
||||
// 使用收盘价计算误差
|
||||
// Use closing price to calculate errors
|
||||
const error = Math.abs(pred.close - act.close);
|
||||
const percentError = (error / act.close) * 100;
|
||||
|
||||
@@ -1159,7 +1159,7 @@
|
||||
return { mae, rmse, mape };
|
||||
}
|
||||
|
||||
// 填充对比表格
|
||||
// Fill comparison table
|
||||
function fillComparisonTable(predictions, actuals) {
|
||||
const tbody = document.getElementById('comparison-tbody');
|
||||
tbody.innerHTML = '';
|
||||
@@ -1186,18 +1186,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 设置事件监听器
|
||||
// Set up event listeners
|
||||
function setupEventListeners() {
|
||||
// 加载模型按钮
|
||||
// Load model button
|
||||
document.getElementById('load-model-btn').addEventListener('click', loadModel);
|
||||
|
||||
// 加载数据按钮
|
||||
// Load data button
|
||||
document.getElementById('load-data-btn').addEventListener('click', loadData);
|
||||
|
||||
// 预测按钮
|
||||
// Prediction button
|
||||
document.getElementById('predict-btn').addEventListener('click', startPrediction);
|
||||
|
||||
// 预测质量参数滑块
|
||||
// Prediction quality parameter sliders
|
||||
document.getElementById('temperature').addEventListener('input', function() {
|
||||
document.getElementById('temperature-value').textContent = this.value;
|
||||
});
|
||||
@@ -1206,25 +1206,25 @@
|
||||
document.getElementById('top-p-value').textContent = this.value;
|
||||
});
|
||||
|
||||
// 回看窗口大小变化时更新滑条
|
||||
// Update slider when lookback window size changes
|
||||
document.getElementById('lookback').addEventListener('input', updateSliderFromInputs);
|
||||
document.getElementById('pred-len').addEventListener('input', updateSliderFromInputs);
|
||||
}
|
||||
|
||||
// 显示状态信息
|
||||
// Display status information
|
||||
function showStatus(type, message) {
|
||||
const statusDiv = document.getElementById('model-status');
|
||||
statusDiv.className = `status ${type}`;
|
||||
statusDiv.textContent = message;
|
||||
statusDiv.style.display = 'block';
|
||||
|
||||
// 自动隐藏
|
||||
// Auto-hide
|
||||
setTimeout(() => {
|
||||
statusDiv.style.display = 'none';
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// 显示/隐藏加载状态
|
||||
// Show/hide loading status
|
||||
function showLoading(show) {
|
||||
const loadingDiv = document.getElementById('loading');
|
||||
if (show) {
|
||||
|
||||
Reference in New Issue
Block a user