No commits in common. '3d2c3aca86aaa361415c4a4097481d5b31531cfc' and 'f70ea009efdf3c053e3f741455585b575c7b2fee' have entirely different histories.

  1. 4849
      data/Stock_5min_A股.csv
  2. 513
      webui/app.py
  3. 656
      webui/templates/index.html

4849
data/Stock_5min_A股.csv
File diff suppressed because it is too large
View File

513
webui/app.py

@ -9,6 +9,9 @@ from flask_cors import CORS
import sys
import warnings
import datetime
import baostock as bs
import re
warnings.filterwarnings('ignore')
# Add project root directory to path
@ -107,6 +110,7 @@ def load_data_files():
return data_files
def load_data_file(file_path):
"""Load data file"""
try:
@ -154,6 +158,7 @@ def load_data_file(file_path):
except Exception as e:
return None, f"Failed to load file: {str(e)}"
def save_prediction_results(file_path, prediction_type, prediction_results, actual_data, input_data, prediction_params):
"""Save prediction results to file"""
try:
@ -391,7 +396,6 @@ def save_prediction_results(file_path, prediction_type, prediction_results, actu
def create_prediction_chart(df, pred_df, lookback, pred_len, actual_df=None, historical_start_idx=0):
"""Create prediction chart"""
print(f"🔍 创建图表调试:")
print(f" 历史数据: {len(df) if df is not None else 0} 行")
print(f" 预测数据: {len(pred_df) if pred_df is not None else 0} 行")
@ -538,17 +542,359 @@ def create_prediction_chart(df, pred_df, lookback, pred_len, actual_df=None, his
error_fig.update_layout(title='Chart Rendering Error')
return error_fig.to_json()
# 计算指标
def calculate_indicators(df):
indicators = {}
# 计算移动平均线 (MA)
indicators['ma5'] = df['close'].rolling(window=5).mean()
indicators['ma10'] = df['close'].rolling(window=10).mean()
indicators['ma20'] = df['close'].rolling(window=20).mean()
# 计算MACD
exp12 = df['close'].ewm(span=12, adjust=False).mean()
exp26 = df['close'].ewm(span=26, adjust=False).mean()
indicators['macd'] = exp12 - exp26
indicators['signal'] = indicators['macd'].ewm(span=9, adjust=False).mean()
indicators['macd_hist'] = indicators['macd'] - indicators['signal']
# 计算RSI
delta = df['close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
rs = gain / loss
indicators['rsi'] = 100 - (100 / (1 + rs))
# 计算布林带
indicators['bb_mid'] = df['close'].rolling(window=20).mean()
indicators['bb_std'] = df['close'].rolling(window=20).std()
indicators['bb_upper'] = indicators['bb_mid'] + 2 * indicators['bb_std']
indicators['bb_lower'] = indicators['bb_mid'] - 2 * indicators['bb_std']
# 计算随机震荡指标
low_min = df['low'].rolling(window=14).min()
high_max = df['high'].rolling(window=14).max()
indicators['stoch_k'] = 100 * ((df['close'] - low_min) / (high_max - low_min))
indicators['stoch_d'] = indicators['stoch_k'].rolling(window=3).mean()
# 滚动窗口均值策略
indicators['rwms_window'] = 90
indicators['rwms_mean'] = df['close'].rolling(window=90).mean()
indicators['rwms_signal'] = (df['close'] > indicators['rwms_mean']).astype(int)
# 三重指数平均(TRIX)策略
# 计算收盘价的EMA
ema1 = df['close'].ewm(span=12, adjust=False).mean()
# 计算EMA的EMA
ema2 = ema1.ewm(span=12, adjust=False).mean()
# 计算EMA的EMA的EMA
ema3 = ema2.ewm(span=12, adjust=False).mean()
# 计算TRIX
indicators['trix'] = (ema3 - ema3.shift(1)) / ema3.shift(1) * 100
# 计算信号线
indicators['trix_signal'] = indicators['trix'].ewm(span=9, adjust=False).mean()
return indicators
# 创建图表
def create_technical_chart(df, pred_df, lookback, pred_len, diagram_type, actual_df=None, historical_start_idx=0):
print(f" 🔍 数据内容: {len(df) if df is not None else 0} 行")
print(f" 🔍 图表类型: {diagram_type}")
# 数据范围
if historical_start_idx + lookback <= len(df):
historical_df = df.iloc[historical_start_idx:historical_start_idx + lookback]
else:
available_lookback = min(lookback, len(df) - historical_start_idx)
historical_df = df.iloc[historical_start_idx:historical_start_idx + available_lookback]
# 计算指标
historical_indicators = calculate_indicators(historical_df)
fig = go.Figure()
# 成交量图表
if diagram_type == 'Volume Chart (VOL)':
fig.add_trace(go.Bar(
x = historical_df['timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y = historical_df['volume'].tolist() if 'volume' in historical_df.columns else [],
name = 'Historical Volume',
marker_color='#42A5F5'
))
if actual_df is not None and len(actual_df) > 0 and 'volume' in actual_df.columns:
if 'timestamps' in df.columns and len(historical_df) > 0:
last_timestamp = historical_df['timestamps'].iloc[-1]
time_diff = df['timestamps'].iloc[1] - df['timestamps'].iloc[0] if len(df) > 1 else pd.Timedelta(
hours=1)
actual_timestamps = pd.date_range(start=last_timestamp + time_diff, periods=len(actual_df),freq=time_diff)
else:
actual_timestamps = range(len(historical_df), len(historical_df) + len(actual_df))
fig.add_trace(go.Bar(
x = actual_timestamps.tolist() if hasattr(actual_timestamps, 'tolist') else list(actual_timestamps),
y = actual_df['volume'].tolist(),
name = 'Actual Volume',
marker_color='#FF9800'
))
fig.update_layout(yaxis_title='Volume')
# 移动平均线
elif diagram_type == 'Moving Average (MA)':
fig.add_trace(go.Scatter(
x = historical_df['timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y = historical_indicators['ma5'],
name='MA5',
line=dict(color='#26A69A', width=1)
))
fig.add_trace(go.Scatter(
x = historical_df[
'timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y = historical_indicators['ma10'],
name = 'MA10',
line = dict(color = '#42A5F5', width = 1)
))
fig.add_trace(go.Scatter(
x = historical_df[
'timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y = historical_indicators['ma20'],
name = 'MA20',
line = dict(color = '#7E57C2', width = 1)
))
fig.add_trace(go.Scatter(
x = historical_df[
'timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y = historical_df['close'],
name = 'Close Price',
line = dict(color = '#212121', width = 1, dash = 'dash')
))
fig.update_layout(yaxis_title = 'Price')
# MACD指标
elif diagram_type == 'MACD Indicator (MACD)':
fig.add_trace(go.Scatter(
x = historical_df[
'timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y = historical_indicators['macd'],
name = 'MACD',
line = dict(color = '#26A69A', width = 1)
))
fig.add_trace(go.Scatter(
x = historical_df[
'timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y = historical_indicators['signal'],
name = 'Signal',
line = dict(color = '#EF5350', width = 1)
))
fig.add_trace(go.Bar(
x = historical_df[
'timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y = historical_indicators['macd_hist'],
name = 'MACD Histogram',
marker_color = '#42A5F5'
))
# 零轴线
fig.add_hline(y = 0, line_dash = "dash", line_color = "gray")
fig.update_layout(yaxis_title = 'MACD')
# RSI指标
elif diagram_type == 'RSI Indicator (RSI)':
fig.add_trace(go.Scatter(
x = historical_df[
'timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y = historical_indicators['rsi'],
name = 'RSI',
line = dict(color = '#26A69A', width = 1)
))
# 超买超卖线
fig.add_hline(y = 70, line_dash = "dash", line_color = "red", name = 'Overbought')
fig.add_hline(y = 30, line_dash = "dash", line_color = "green", name = 'Oversold')
fig.update_layout(yaxis_title = 'RSI', yaxis_range = [0, 100])
# 布林带
elif diagram_type == 'Bollinger Bands (BB)':
fig.add_trace(go.Scatter(
x = historical_df[
'timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y = historical_indicators['bb_upper'],
name = 'Upper Band',
line = dict(color = '#EF5350', width = 1)
))
fig.add_trace(go.Scatter(
x = historical_df[
'timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y = historical_indicators['bb_mid'],
name = 'Middle Band (MA20)',
line = dict(color = '#42A5F5', width = 1)
))
fig.add_trace(go.Scatter(
x = historical_df[
'timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y = historical_indicators['bb_lower'],
name = 'Lower Band',
line = dict(color = '#26A69A', width = 1)
))
fig.add_trace(go.Scatter(
x = historical_df[
'timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y = historical_df['close'],
name = 'Close Price',
line = dict(color = '#212121', width = 1)
))
fig.update_layout(yaxis_title = 'Price')
# 随机震荡指标
elif diagram_type == 'Stochastic Oscillator (STOCH)':
fig.add_trace(go.Scatter(
x = historical_df[
'timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y = historical_indicators['stoch_k'],
name = '%K',
line = dict(color = '#26A69A', width = 1)
))
fig.add_trace(go.Scatter(
x = historical_df[
'timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y = historical_indicators['stoch_d'],
name = '%D',
line = dict(color = '#EF5350', width = 1)
))
fig.add_hline(y = 80, line_dash = "dash", line_color = "red", name = 'Overbought')
fig.add_hline(y = 20, line_dash = "dash", line_color = "green", name = 'Oversold')
fig.update_layout(yaxis_title = 'Stochastic', yaxis_range = [0, 100])
# 滚动窗口均值策略
elif diagram_type == 'Rolling Window Mean Strategy':
fig.add_trace(go.Scatter(
x = historical_df[
'timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y = historical_df['close'],
name = 'Close Price',
line = dict(color = '#212121', width = 1.5)
))
fig.add_trace(go.Scatter(
x = historical_df[
'timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y = historical_indicators['rwms_mean'],
name = f'Rolling Mean ({historical_indicators["rwms_window"]} periods)',
line = dict(color = '#42A5F5', width = 1.5, dash = 'dash')
))
buy_signals = historical_df[historical_indicators['rwms_signal'] == 1]
fig.add_trace(go.Scatter(
x = buy_signals['timestamps'].tolist() if 'timestamps' in buy_signals.columns else buy_signals.index.tolist(),
y = buy_signals['close'],
mode = 'markers',
name = 'Buy Signal',
marker = dict(color = '#26A69A', size = 8, symbol = 'triangle-up')
))
sell_signals = historical_df[historical_indicators['rwms_signal'] == 0]
fig.add_trace(go.Scatter(
x = sell_signals[
'timestamps'].tolist() if 'timestamps' in sell_signals.columns else sell_signals.index.tolist(),
y = sell_signals['close'],
mode = 'markers',
name = 'Sell Signal',
marker = dict(color = '#EF5350', size = 8, symbol = 'triangle-down')
))
fig.update_layout(
yaxis_title = 'Price',
title = f'Rolling Window Mean Strategy (Window Size: {historical_indicators["rwms_window"]})'
)
# TRIX指标图表
elif diagram_type == 'TRIX Indicator (TRIX)':
fig.add_trace(go.Scatter(
x=historical_df[
'timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y=historical_indicators['trix'],
name='TRIX',
line=dict(color='#26A69A', width=1)
))
fig.add_trace(go.Scatter(
x=historical_df[
'timestamps'].tolist() if 'timestamps' in historical_df.columns else historical_df.index.tolist(),
y=historical_indicators['trix_signal'],
name='TRIX Signal',
line=dict(color='#EF5350', width=1)
))
fig.add_hline(y=0, line_dash="dash", line_color="gray")
fig.update_layout(
yaxis_title='TRIX (%)',
title='Triple Exponential Average (TRIX) Strategy'
)
# 布局设置
fig.update_layout(
title = f'{diagram_type} - Technical Indicator (Real Data Only)',
xaxis_title = 'Time',
template = 'plotly_white',
height = 400,
showlegend = True,
margin = dict(t = 50, b = 30)
)
if 'timestamps' in historical_df.columns:
all_timestamps = historical_df['timestamps'].tolist()
if actual_df is not None and len(actual_df) > 0 and 'timestamps' in df.columns:
if 'actual_timestamps' in locals():
all_timestamps.extend(actual_timestamps.tolist())
if all_timestamps:
all_timestamps = sorted(all_timestamps)
fig.update_xaxes(
range=[all_timestamps[0], all_timestamps[-1]],
rangeslider_visible=False,
type='date'
)
try:
chart_json = fig.to_json()
print(f"✅ 技术指标图表序列化完成,长度: {len(chart_json)}")
return chart_json
except Exception as e:
print(f"❌ 技术指标图表序列化失败: {e}")
error_fig = go.Figure()
error_fig.update_layout(title='Chart Rendering Error')
return error_fig.to_json()
@app.route('/')
def index():
"""Home page"""
return render_template('index.html')
@app.route('/api/data-files')
def get_data_files():
"""Get available data file list"""
data_files = load_data_files()
return jsonify(data_files)
@app.route('/api/load-data', methods=['POST'])
def load_data():
"""Load data file"""
@ -907,6 +1253,7 @@ def load_data():
# except Exception as e:
# return jsonify({'error': f'Prediction failed: {str(e)}'}), 500
@app.route('/api/predict', methods=['POST'])
def predict():
"""Perform prediction"""
@ -1198,6 +1545,7 @@ def predict():
# except Exception as e:
# return jsonify({'error': f'Model loading failed: {str(e)}'}), 500
@app.route('/api/load-model', methods=['POST'])
def load_model():
global tokenizer, model, predictor
@ -1278,6 +1626,7 @@ def load_model():
except Exception as e:
return jsonify({'error': f'Model loading failed: {str(e)}'}), 500
@app.route('/api/available-models')
def get_available_models():
"""Get available model list"""
@ -1286,6 +1635,7 @@ def get_available_models():
'model_available': MODEL_AVAILABLE
})
@app.route('/api/model-status')
def get_model_status():
"""Get model status"""
@ -1313,6 +1663,167 @@ def get_model_status():
'message': 'Kronos model library not available, please install related dependencies'
})
@app.route('/api/stock-data', methods=['POST'])
def Stock_Data():
try:
data = request.get_json()
stock_code = data.get('stock_code', '').strip()
# 股票代码不能为空
if not stock_code:
return jsonify({
'success': False,
'error': f'Stock code cannot be empty'
}), 400
# 股票代码格式验证
if not re.match(r'^[a-z]+\.\d+$', stock_code):
return jsonify({
'success': False,
'error': f'The stock code you entered is invalid'
}), 400
# 登录 baostock
lg = bs.login()
if lg.error_code != '0':
return jsonify({
'success': False,
'error': f'Login failed: {lg.error_msg}'
}), 400
rs = bs.query_history_k_data_plus(
stock_code,
"time,open,high,low,close,volume,amount",
start_date = '2024-06-01',
end_date = '2024-10-31',
frequency = "5",
adjustflag = "3"
)
# 检查获取结果
if rs.error_code != '0':
bs.logout()
return jsonify({
'success': False,
'error': f'Failed to retrieve data, please enter a valid stock code'
}), 400
# 提取数据
data_list = []
while rs.next():
data_list.append(rs.get_row_data())
# 登出系统
bs.logout()
columns = rs.fields
df = pd.DataFrame(data_list, columns=columns)
# 数值列转换
df = df.rename(columns={'time': 'timestamps'})
numeric_columns = ['timestamps','open', 'high', 'low', 'close', 'volume', 'amount']
for col in numeric_columns:
df[col] = pd.to_numeric(df[col], errors='coerce')
df['timestamps'] = pd.to_datetime(df['timestamps'].astype(str), format='%Y%m%d%H%M%S%f')
# 去除无效数据
df = df.dropna()
# 保存
data_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'data')
os.makedirs(data_dir, exist_ok=True)
filename = f"Stock_5min_A股.csv"
file_path = os.path.join(data_dir, filename)
df.to_csv(
file_path,
index = False,
encoding = 'utf-8',
mode = 'w'
)
data_files = load_data_files()
return jsonify({
'success': True,
'message': f'Stock data saved successfully: {filename}',
'file_name': filename
})
except Exception as e:
return jsonify({
'success': False,
'error': f'Error processing stock data: {str(e)}'
}), 500
@app.route('/api/generate-chart', methods=['POST'])
def generate_chart():
try:
data = request.get_json()
# 验证参数
required_fields = ['file_path', 'lookback', 'diagram_type', 'historical_start_idx']
for field in required_fields:
if field not in data:
return jsonify({'success': False, 'error': f'Missing required field: {field}'}), 400
# 解析参数
file_path = data['file_path']
lookback = int(data['lookback'])
diagram_type = data['diagram_type']
historical_start_idx = int(data['historical_start_idx'])
# 加载数据
df, error = load_data_file(file_path)
if error:
return jsonify({'success': False, 'error': error}), 400
if len(df) < lookback + historical_start_idx:
return jsonify({
'success': False,
'error': f'Insufficient data length, need at least {lookback + historical_start_idx} rows'
}), 400
pred_df = None
actual_df = None
# 生成图表
chart_json = create_technical_chart(
df=df,
pred_df=pred_df,
lookback=lookback,
pred_len=0,
diagram_type=diagram_type,
actual_df=actual_df,
historical_start_idx=historical_start_idx
)
# 表格数据
table_data_start = historical_start_idx
table_data_end = historical_start_idx + lookback
table_df = df.iloc[table_data_start:table_data_end]
table_data = table_df.to_dict('records')
return jsonify({
'success': True,
'chart': json.loads(chart_json),
'table_data': table_data,
'message': 'Technical chart generated successfully'
})
except Exception as e:
return jsonify({
'success': False,
'error': f'Failed to generate technical chart: {str(e)}'
}), 500
if __name__ == '__main__':
print("Starting Kronos Web UI...")
print(f"Model availability: {MODEL_AVAILABLE}")

656
webui/templates/index.html

@ -1,12 +1,15 @@
<!DOCTYPE html>
<html lang="zh-CN">
<html lang = "zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset = "UTF-8">
<meta name = "viewport" content = "width = device-width, initial-scale = 1.0">
<title>Kronos Financial Prediction Web UI</title>
{# <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>#}
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
{# <script src = "https://cdn.plot.ly/plotly-latest.min.js"></script>#}
<script src = "https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
<script src = "https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<style>
* {
margin: 0;
@ -151,7 +154,14 @@
}
.btn-warning {
background: linear-gradient(135deg, #ed8936 0%, #dd6b20 100%);
background: linear-gradient(135deg, #ffc19d 0%, #ffc19d 100%);
}
.chart-grid {
display: grid;
grid-template-columns: 1fr auto;
gap: 10px;
align-items: center;
}
.status {
@ -161,6 +171,13 @@
font-weight: 500;
}
.indicator-status {
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
font-weight: 500;
}
.status.success {
background: #c6f6d5;
color: #22543d;
@ -401,6 +418,26 @@
100% { transform: rotate(360deg); }
}
.indicator-loading {
display: none;
text-align: center;
padding: 20px;
}
.indicator-loading.show {
display: block;
}
.indicator-loading .spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #6dea66;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
.model-info {
background: #e6fffa;
border: 1px solid #81e6d9;
@ -438,100 +475,126 @@
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class = "container">
<div class = "header">
<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">
<div class = "main-content">
<div class = "control-panel">
<h2>🎯 Control Panel</h2>
<!-- Model Selection -->
<div class="form-group">
<label for="model-select">Select Model:</label>
<select id="model-select">
<option value="">Please load available models first</option>
<div class = "form-group">
<label for = "model-select">Select Model:</label>
<select id = "model-select">
<option value = "">Please load available models first</option>
</select>
<small class="form-text">Select the Kronos model to use</small>
<small class = "form-text">Select the Kronos model to use</small>
</div>
<!-- Device Selection -->
<div class="form-group">
<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>
<div class = "form-group">
<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">Select the device to run the model on</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;">
<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">
<button id = "load-model-btn" class = "btn btn-secondary">
🔄 Load Model
</button>
<hr style = "margin: 20px 0; border: 1px solid #e2e8f0;">
<!-- Stock data acquisition -->
<div class = "form-group">
<label class = "sr-only" for = "stock_code">Ticker Symbol Input:</label>
<input type = "text" class = "form-control" id = "stock_code" name = 'stock_code'
value = "{{ stock_code }}" placeholder = "例如:sh.600000" >
<small class="form-text">Enter the ticker symbol you want to analyze</small>
</div>
<button id="stock-data-btn" class="btn btn-secondary">
📄 Stock Data
</button>
<hr style="margin: 20px 0; border: 1px solid #e2e8f0;">
<!-- Data File Selection -->
<div class="form-group">
<label for="data-file-select">Select Data File:</label>
<select id="data-file-select">
<option value="">Please load data file list first</option>
<div class = "form-group">
<label for = "data-file-select">Select Data File:</label>
<select id = "data-file-select">
<option value = "">Please load data file list first</option>
</select>
<small class="form-text">Select K-line data file from data directory</small>
<small class = "form-text">Select K-line data file from data directory</small>
</div>
<button id="load-data-btn" class="btn btn-secondary">
<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;">
<div id = "data-info" class = "data-info" style = "display: none;">
<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>
<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;">
<hr style = "margin: 20px 0; border: 1px solid #e2e8f0;">
<!-- Time Window Selector -->
<div class="time-window-container">
<div class = "time-window-container">
<h3>⏰ Time Window Selection</h3>
<div class="time-window-info">
<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 class = "time-window-info">
<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">
<div class="slider-track">
<div class="slider-handle start-handle" id="start-handle"></div>
<div class="slider-selection" id="slider-selection"></div>
<div class="slider-handle end-handle" id="end-handle"></div>
<div class = "time-window-slider">
<div class = "slider-track">
<div class = "slider-handle start-handle" id = "start-handle"></div>
<div class = "slider-selection" id = "slider-selection"></div>
<div class = "slider-handle end-handle" id = "end-handle"></div>
</div>
<div class="slider-labels">
<span id="min-label">Earliest</span>
<span id="max-label">Latest</span>
<div class = "slider-labels">
<span id = "min-label">Earliest</span>
<span id = "max-label">Latest</span>
</div>
</div>
<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>
<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">Lookback Window Size:</label>
@ -545,31 +608,36 @@
<small class="form-text">Fixed at 120 data points</small>
</div>
<!-- Prediction Quality Parameters -->
<div class="form-group">
<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">Controls prediction randomness, higher values make predictions more diverse, lower values make predictions more conservative</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">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">Controls prediction diversity, higher values consider broader probability distributions</small>
<small class="form-text">Controls prediction diversity, higher values consider broader probability
distributions</small>
</div>
<div class="form-group">
<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">Generate multiple prediction samples to improve quality (recommended 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>
@ -577,6 +645,7 @@
</div>
</div>
<div class="chart-container">
<h2>📈 Prediction Results Chart</h2>
<div id="chart"></div>
@ -612,17 +681,17 @@
<div style="max-height: 300px; overflow-y: auto;">
<table class="comparison-table">
<thead>
<tr>
<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>
<tr>
<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">
</tbody>
@ -630,10 +699,72 @@
</div>
</div>
</div>
<br>
<h2>📶 Technical Indicator Chart</h2>
<div id="indicator-status" class="indicator-status" style="display: none;"></div>
<div class="form-group">
<label for="diagram_type">Select Diagram Type:</label>
<div class="chart-grid">
<select id="diagram_type" class="form-control">
<option value="Volume Chart (VOL)">Volume Chart (VOL)</option>
<option value="Moving Average (MA)">Moving Average (MA)</option>
<option value="MACD Indicator (MACD)">MACD Indicator (MACD)</option>
<option value="RSI Indicator (RSI)">RSI Indicator (RSI)</option>
<option value="Bollinger Bands (BB)">Bollinger Bands (BB)</option>
<option value="Stochastic Oscillator (STOCH)">Stochastic Oscillator (STOCH)</option>
<option value="Rolling Window Mean Strategy">Rolling Window Mean Strategy</option>
<option value="TRIX Indicator (TRIX)">Triple Exponential Average (TRIX) Strategy</option>
</select>
<button id="generate-chart-btn" class="btn btn-warning">
✏️ Generate chart
</button>
</div>
<small class="form-text">Select the type to draw the relevant chart</small>
</div>
<div id="indicator-loading" class="indicator-loading">
<div class="spinner"></div>
<p>Generating chart, please wait...</p>
</div>
<div id="indicator-chart"></div>
<!-- Data Presentation -->
<div id="data-presentation" class="comparison-section" style="display: none;">
<h3>💹 Financial Data Visualization</h3>
<div class="error-details">
<h4>Detailed Financial Data:</h4>
<div style="max-height: 300px; overflow-y: auto;">
<table class="comparison-table">
<thead>
<tr>
<th>Timestamps</th>
<th>Open</th>
<th>High</th>
<th>Low</th>
<th>Close</th>
<th>Volume</th>
<th>Amount</th>
</tr>
</thead>
<tbody id="data-tbody">
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// Global variables
let currentDataFile = null;
@ -641,11 +772,13 @@
let availableModels = [];
let modelLoaded = false;
// Initialize after page loads
document.addEventListener('DOMContentLoaded', function() {
initializeApp();
});
// Initialize application
async function initializeApp() {
console.log('🚀 Initializing Kronos Web UI...');
@ -665,6 +798,7 @@
console.log('✅ Application initialization completed');
}
// Load available models
async function loadAvailableModels() {
try {
@ -700,6 +834,7 @@
}
}
// Populate model selection dropdown
function populateModelSelect() {
const modelSelect = document.getElementById('model-select');
@ -713,6 +848,7 @@
});
}
// Load model
async function loadModel() {
const modelKey = document.getElementById('model-select').value;
@ -750,6 +886,7 @@
}
}
// Update model status
async function updateModelStatus() {
try {
@ -768,6 +905,59 @@
}
}
//Stock Data按钮
document.addEventListener('DOMContentLoaded', function() {
const generateChartBtn = document.getElementById('stock-data-btn');
if (generateChartBtn) {
generateChartBtn.addEventListener('click', StockData);
console.log('Stock Data button event listener bound');
} else {
console.error('stock-data-btn element not found');
}
});
async function StockData() {
console.log('Get stock data...');
const stockCodeInput = document.getElementById('stock_code');
const generateBtn = document.getElementById('stock-data-btn');
const stockCode = stockCodeInput.value.trim();
generateBtn.disabled = true;
try {
if (!stockCode) {
showStatus('error', 'Stock code cannot be empty');
return;
}
const stockCodeRegex = /^[a-z]+\.\d+$/;
if (!stockCodeRegex.test(stockCode)) {
showStatus('error', 'The ticker symbol is in the wrong format');
return;
}
showLoading(true);
const response = await axios.post('/api/stock-data', {stock_code: stockCode});
if (response.data.success) {
showStatus('success', `Successfully fetched data for ${stockCode}`);
loadDataFiles();
stockCodeInput.value = '';
} else {
showStatus('error', response.data.error || 'Failed to fetch stock data');
}
} catch (error) {
console.error('❌ Failed to fetch stock data:', error);
showStatus('error', `Failed to fetch data`);
} finally {
showLoading(false);
if (generateBtn) generateBtn.disabled = false;
}
}
// Load data file list
async function loadDataFiles() {
try {
@ -791,6 +981,7 @@
}
}
// Load data file
async function loadData() {
const filePath = document.getElementById('data-file-select').value;
@ -832,6 +1023,7 @@
}
}
// Display data information
function showDataInfo(dataInfo) {
document.getElementById('data-info').style.display = 'block';
@ -846,17 +1038,20 @@
initializeTimeWindowSlider(dataInfo);
}
// Time window slider related variables
// 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');
@ -893,12 +1088,14 @@
updateSliderFromHandles();
});
// End dragging
document.addEventListener('mouseup', () => {
isDragging = false;
currentHandle = null;
});
// Click track to set position directly
track.addEventListener('click', (e) => {
const rect = track.getBoundingClientRect();
@ -921,6 +1118,7 @@
});
}
// Update start handle position
function updateStartHandle(percentage) {
const startHandle = document.getElementById('start-handle');
@ -945,6 +1143,7 @@
endHandle.style.left = ((percentage + windowPercentage) * 100) + '%';
}
// Update end handle position
function updateEndHandle(percentage) {
const endHandle = document.getElementById('end-handle');
@ -969,6 +1168,7 @@
startHandle.style.left = ((percentage - windowPercentage) * 100) + '%';
}
// Update slider display based on handle positions
function updateSliderFromHandles() {
const startHandle = document.getElementById('start-handle');
@ -999,6 +1199,7 @@
document.getElementById('pred-len').value = 120;
}
// Update slider based on input fields
function updateSliderFromInputs() {
if (!sliderData) return;
@ -1029,6 +1230,7 @@
updateSliderFromHandles();
}
// Initialize time window slider
function initializeTimeWindowSlider(dataInfo) {
sliderData = {
@ -1046,6 +1248,134 @@
updateSliderFromInputs();
}
// Binding Generate chart
document.addEventListener('DOMContentLoaded', function() {
const generateChartBtn = document.getElementById('generate-chart-btn');
if (generateChartBtn) {
generateChartBtn.addEventListener('click', generateTechnicalChart);
console.log('Generate chart button event listener bound');
} else {
console.error('generate-chart-btn element not found');
}
});
// Generate technical chart
async function generateTechnicalChart() {
console.log('Generating technical indicator chart...');
const indicatorLoading = document.getElementById('indicator-loading');
indicatorLoading.classList.add('show');
const generateBtn = document.getElementById('generate-chart-btn');
generateBtn.disabled = true;
try {
await new Promise(resolve => setTimeout(resolve, 2000));
// get arguments
const startHandle = document.getElementById('start-handle');
const endHandle = document.getElementById('end-handle');
const startPercentage = parseFloat(startHandle.style.left) / 100;
const endPercentage = parseFloat(endHandle.style.left) / 100;
const totalRows = sliderData ? sliderData.totalRows : 0;
const historicalStartIdx = Math.floor(startPercentage * totalRows);
const lookback = Math.floor((endPercentage - startPercentage) * totalRows);
const predLen = parseInt(document.getElementById('pred-len').value);
const filePath = document.getElementById('data-file-select').value;
const diagramType = document.getElementById('diagram_type').value;
// validate arguments
if (!filePath) throw new Error('Please select a data file first');
if (isNaN(lookback) || isNaN(predLen)) throw new Error('Invalid parameters');
// fetch chart data
const response = await fetch('/api/generate-chart', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
file_path: filePath,
lookback: lookback,
pred_len: predLen,
diagram_type: diagramType,
historical_start_idx: historicalStartIdx
})
});
const result = await response.json();
if (!result.success) throw new Error(result.error || 'Failed to generate chart');
// render the chart
const chartContainer = document.getElementById('indicator-chart');
if (chartContainer) {
if (chartContainer.data) Plotly.purge(chartContainer);
Plotly.newPlot(
chartContainer,
result.chart.data,
result.chart.layout,
{ responsive: true }
);
} else {
throw new Error('Indicator chart container not found');
}
document.getElementById('data-presentation').style.display = 'block';
if (result.table_data) {
fillDataTable(result.table_data);
} else {
console.warn('No table data returned from server');
fillDataTable([]);
}
showIndicatorStatus('success', `chart (${diagramType}) generated successfully`);
} catch (error) {
console.error('Chart generation error:', error);
showIndicatorStatus('error', `Chart generation failed: ${error.message}`);
} finally {
indicatorLoading.classList.remove('show');
generateBtn.disabled = false;
}
}
// Fill data table
function fillDataTable(data) {
const tbody = document.getElementById('data-tbody');
tbody.innerHTML = '';
if (!data || data.length === 0) {
const emptyRow = document.createElement('tr');
emptyRow.innerHTML = '<td colspan = "7" style = "text-align:center">暂无数据</td>';
tbody.appendChild(emptyRow);
return;
}
data.forEach(item => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${new Date(item.timestamps).toLocaleString()}</td>
<td>${item.open.toFixed(4)}</td>
<td>${item.high.toFixed(4)}</td>
<td>${item.low.toFixed(4)}</td>
<td>${item.close.toFixed(4)}</td>
<td>${item.volume ? item.volume.toLocaleString() : '-'}</td>
<td>${item.amount ? item.amount.toFixed(2) : '-'}</td>
`;
tbody.appendChild(row);
});
}
// Start prediction
async function startPrediction() {
if (!currentDataFile) {
@ -1125,89 +1455,90 @@
}
}
// 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 {#}
{# document.getElementById('comparison-section').style.display = 'none';#}
{# }#}
{#}#}
function displayPredictionResult(result) {
console.log('🔍 DEBUG - displayPredictionResult called');
console.log('Result keys:', Object.keys(result));
console.log('Has chart:', !!result.chart);
console.log('Chart type:', typeof result.chart);
console.log('Chart length:', result.chart ? result.chart.length : 0);
console.log('Has comparison:', result.has_comparison);
console.log('Actual data length:', result.actual_data ? result.actual_data.length : 0);
try {
// Parse and display chart
if (result.chart) {
console.log('📊 Parsing chart data...');
const chartData = JSON.parse(result.chart);
console.log('📈 Chart data parsed successfully:', chartData);
// Clear previous chart
const chartDiv = document.getElementById('chart');
chartDiv.innerHTML = '';
// Create new chart with error handling
Plotly.newPlot('chart', chartData.data, chartData.layout, {
responsive: true
}).then(function () {
console.log('✅ Chart rendered successfully');
// 确保图表容器可见
chartDiv.style.display = 'block';
}).catch(function (error) {
console.error('❌ Chart rendering failed:', error);
showStatus('error', `图表渲染失败: ${error.message}`);
// 显示错误信息
chartDiv.innerHTML = `
<div style="text-align: center; padding: 50px; color: #666;">
<h3>图表加载失败</h3>
<p>错误信息: ${error.message}</p>
<p>请检查控制台获取详细信息</p>
</div>
`;
});
} else {
console.error('❌ No chart data in response');
showStatus('error', '服务器返回的图表数据为空');
}
// // 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 {#}
// {# document.getElementById('comparison-section').style.display = 'none';#}
// {# }#}
// {#}#}
function displayPredictionResult(result) {
console.log('🔍 DEBUG - displayPredictionResult called');
console.log('Result keys:', Object.keys(result));
console.log('Has chart:', !!result.chart);
console.log('Chart type:', typeof result.chart);
console.log('Chart length:', result.chart ? result.chart.length : 0);
console.log('Has comparison:', result.has_comparison);
console.log('Actual data length:', result.actual_data ? result.actual_data.length : 0);
// Display comparison analysis (if actual data exists)
if (result.has_comparison && result.actual_data && result.actual_data.length > 0) {
console.log('📊 Displaying comparison analysis');
displayComparisonAnalysis(result);
} else {
console.log('ℹ️ No comparison data available');
document.getElementById('comparison-section').style.display = 'none';
}
} catch (error) {
console.error('❌ Error displaying prediction result:', error);
showStatus('error', `结果显示失败: ${error.message}`);
try {
// Parse and display chart
if (result.chart) {
console.log('📊 Parsing chart data...');
const chartData = JSON.parse(result.chart);
console.log('📈 Chart data parsed successfully:', chartData);
// 显示错误信息在图表区域
// Clear previous chart
const chartDiv = document.getElementById('chart');
chartDiv.innerHTML = `
<div style="text-align: center; padding: 50px; color: #666;">
<h3>数据处理失败</h3>
<p>错误信息: ${error.message}</p>
<p>请检查数据格式是否正确</p>
</div>
`;
chartDiv.innerHTML = '';
// Create new chart with error handling
Plotly.newPlot('chart', chartData.data, chartData.layout, {
responsive: true
}).then(function () {
console.log('✅ Chart rendered successfully');
// 确保图表容器可见
chartDiv.style.display = 'block';
}).catch(function (error) {
console.error('❌ Chart rendering failed:', error);
showStatus('error', `图表渲染失败: ${error.message}`);
// 显示错误信息
chartDiv.innerHTML = `
<div style="text-align: center; padding: 50px; color: #666;">
<h3>图表加载失败</h3>
<p>错误信息: ${error.message}</p>
<p>请检查控制台获取详细信息</p>
</div>
`;
});
} else {
console.error('❌ No chart data in response');
showStatus('error', '服务器返回的图表数据为空');
}
// Display comparison analysis (if actual data exists)
if (result.has_comparison && result.actual_data && result.actual_data.length > 0) {
console.log('📊 Displaying comparison analysis');
displayComparisonAnalysis(result);
} else {
console.log('ℹ️ No comparison data available');
document.getElementById('comparison-section').style.display = 'none';
}
} catch (error) {
console.error('❌ Error displaying prediction result:', error);
showStatus('error', `结果显示失败: ${error.message}`);
// 显示错误信息在图表区域
const chartDiv = document.getElementById('chart');
chartDiv.innerHTML = `
<div style="text-align: center; padding: 50px; color: #666;">
<h3>数据处理失败</h3>
<p>错误信息: ${error.message}</p>
<p>请检查数据格式是否正确</p>
</div>
`;
}
}
// Display comparison analysis
@ -1230,6 +1561,7 @@
fillComparisonTable(result.prediction_results, result.actual_data);
}
// Calculate prediction quality metrics
function getPredictionQuality(predictions, actuals) {
if (!predictions || !actuals || predictions.length === 0 || actuals.length === 0) {
@ -1259,7 +1591,8 @@
return { mae, rmse, mape };
}
// Fill comparison table
// Fill comparison table
function fillComparisonTable(predictions, actuals) {
const tbody = document.getElementById('comparison-tbody');
tbody.innerHTML = '';
@ -1286,6 +1619,7 @@
}
}
// Set up event listeners
function setupEventListeners() {
// Load model button
@ -1297,6 +1631,9 @@
// Prediction button
document.getElementById('predict-btn').addEventListener('click', startPrediction);
// Generate chart button
document.addEventListener('DOMContentLoaded', function(){
// Prediction quality parameter sliders
document.getElementById('temperature').addEventListener('input', function() {
document.getElementById('temperature-value').textContent = this.value;
@ -1309,8 +1646,18 @@
// Update slider when lookback window size changes
document.getElementById('lookback').addEventListener('input', updateSliderFromInputs);
document.getElementById('pred-len').addEventListener('input', updateSliderFromInputs);
const chartButton = document.getElementById('load-chart-btn');
if (chartButton) {
chartButton.addEventListener('click', generatechart);
console.log('Chart button event listener bound');
} else {
console.error('load-chart-btn element not found');
}
});
}
// Display status information
function showStatus(type, message) {
const statusDiv = document.getElementById('model-status');
@ -1324,6 +1671,20 @@
}, 5000);
}
function showIndicatorStatus(type, message) {
const statusDiv = document.getElementById('indicator-status');
statusDiv.className = `indicator-status status ${type}`;
statusDiv.textContent = message;
statusDiv.style.display = 'block';
// Auto-hide after 5 seconds
setTimeout(() => {
statusDiv.style.display = 'none';
}, 5000);
}
// Show/hide loading status
function showLoading(show) {
const loadingDiv = document.getElementById('loading');
@ -1333,6 +1694,7 @@
loadingDiv.classList.remove('show');
}
}
</script>
</body>
</html>
Loading…
Cancel
Save