You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

895 lines
40 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ name }} - AI期货分析系统</title>
<link href="https://cdn.jsdelivr.net/npm/antd@5.12.8/dist/reset.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg-primary: #f0f2f5;
--bg-secondary: #ffffff;
--bg-tertiary: #f9fafb;
--text-primary: #333333;
--text-secondary: #666666;
--text-tertiary: #999999;
--border-color: #d9d9d9;
--header-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--card-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
--card-hover-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
}
body.dark-theme {
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--bg-tertiary: #3d3d3d;
--text-primary: #e0e0e0;
--text-secondary: #b0b0b0;
--text-tertiary: #808080;
--border-color: #444444;
--header-bg: linear-gradient(135deg, #334155 0%, #1e293b 100%);
--card-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
--card-hover-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background-color: var(--bg-primary);
color: var(--text-primary);
transition: all 0.3s ease;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.theme-toggle {
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 6px 12px;
font-size: 14px;
cursor: pointer;
color: var(--text-primary);
transition: all 0.3s ease;
}
.theme-toggle:hover {
background: var(--bg-secondary);
}
.header {
background: var(--header-bg);
color: white;
padding: 30px;
border-radius: 12px;
margin-bottom: 30px;
box-shadow: var(--card-shadow);
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
font-size: 24px;
font-weight: 600;
margin-bottom: 8px;
}
.header p {
font-size: 14px;
opacity: 0.9;
}
.back-link {
color: white;
text-decoration: none;
font-size: 14px;
padding: 8px 16px;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 6px;
transition: background-color 0.3s ease;
}
.back-link:hover {
background-color: rgba(255, 255, 255, 0.3);
}
.price-section {
background: var(--bg-secondary);
border-radius: 12px;
padding: 24px;
box-shadow: var(--card-shadow);
margin-bottom: 30px;
border: 1px solid var(--border-color);
}
.price-info {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20px;
}
.price-main {
flex: 1;
}
.price-value {
font-size: 48px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 8px;
}
.price-change {
font-size: 16px;
font-weight: 500;
}
.price-change.up {
color: #52c41a;
}
.price-change.down {
color: #ff4d4f;
}
.price-stats {
display: flex;
gap: 30px;
margin-top: 20px;
}
.stat-item {
text-align: center;
}
.stat-label {
font-size: 14px;
color: var(--text-secondary);
margin-bottom: 4px;
}
.stat-value {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
}
.chart-section {
background: var(--bg-secondary);
border-radius: 12px;
padding: 24px;
box-shadow: var(--card-shadow);
margin-bottom: 30px;
border: 1px solid var(--border-color);
}
.chart-title {
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 20px;
}
.chart-container {
width: 100%;
height: 400px;
}
.analysis-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.analysis-card {
background: var(--bg-secondary);
border-radius: 12px;
padding: 20px;
box-shadow: var(--card-shadow);
border: 1px solid var(--border-color);
}
.card-title {
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border-color);
}
.analysis-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid var(--border-color);
}
.analysis-label {
font-size: 14px;
color: var(--text-secondary);
}
.analysis-value {
font-size: 14px;
font-weight: 500;
color: var(--text-primary);
}
.direction-up {
color: #52c41a;
}
.direction-down {
color: #ff4d4f;
}
.warning {
color: #fa8c16;
}
.ai-analysis {
background: var(--bg-secondary);
border-radius: 12px;
padding: 24px;
box-shadow: var(--card-shadow);
margin-bottom: 30px;
border: 1px solid var(--border-color);
}
.ai-title {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.ai-icon {
width: 24px;
height: 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 14px;
font-weight: 700;
}
.ai-content {
background-color: var(--bg-tertiary);
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
border: 1px solid var(--border-color);
}
.recommendation {
background-color: #f0f5ff;
border-radius: 8px;
padding: 16px;
border-left: 4px solid #1890ff;
}
.recommendation-title {
font-size: 14px;
font-weight: 600;
color: #1890ff;
margin-bottom: 8px;
}
.recommendation-content {
font-size: 14px;
color: var(--text-primary);
}
.footer {
text-align: center;
padding: 20px;
color: var(--text-tertiary);
font-size: 14px;
margin-top: 40px;
}
@media (max-width: 768px) {
.header-content {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.price-info {
flex-direction: column;
gap: 20px;
}
.price-stats {
flex-wrap: wrap;
gap: 16px;
}
.analysis-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body class="dark-theme">
<div class="container">
<!-- 主题切换 -->
<div style="text-align: right; margin-bottom: 20px;">
<button class="theme-toggle" onclick="toggleTheme()">切换主题</button>
</div>
<!-- 头部 -->
<div class="header">
<div class="header-content">
<div>
<h1>{{ name }}</h1>
<p>品种详细分析</p>
</div>
<a href="/" class="back-link">返回多品种面板</a>
</div>
</div>
<!-- 价格信息 -->
<div class="price-section">
<div class="price-info">
<div class="price-main">
<div class="price-value">{{ current_price }}</div>
<div class="price-change
{% if price_change > 0 %}up{% else %}down{% endif %}">
{{ price_change }} ({{ price_change_pct }}%)
</div>
</div>
<div class="price-stats">
<div class="stat-item">
<div class="stat-label">胜率</div>
<div class="stat-value">{{ win_rate }}%</div>
</div>
<div class="stat-item">
<div class="stat-label">趋势</div>
<div class="stat-value
{% if trend_analysis.overall_trend == 'bullish' or trend_analysis.overall_trend == 'strong_bullish' or trend_analysis.overall_trend == 'weak_bullish' %}direction-up{% elif trend_analysis.overall_trend == 'bearish' or trend_analysis.overall_trend == 'strong_bearish' or trend_analysis.overall_trend == 'weak_bearish' %}direction-down{% endif %}">
{% if trend_analysis.overall_trend == 'strong_bullish' %}强多头
{% elif trend_analysis.overall_trend == 'strong_bearish' %}强空头
{% elif trend_analysis.overall_trend == 'weak_bullish' %}弱多头
{% elif trend_analysis.overall_trend == 'weak_bearish' %}弱空头
{% elif trend_analysis.overall_trend == 'bullish' %}多头
{% elif trend_analysis.overall_trend == 'bearish' %}空头
{% elif trend_analysis.overall_trend == 'neutral' %}中性
{% else %}{{ trend_analysis.overall_trend | capitalize }}
{% endif %}
</div>
</div>
<div class="stat-item">
<div class="stat-label">周期</div>
<div class="stat-value">{{ cycle }}</div>
</div>
<div class="stat-item">
<div class="stat-label">自选</div>
<div class="stat-value" style="cursor: pointer;" onclick="toggleFavorite('{{ symbol }}')">
<span id="favoriteBtn" style="font-size: 18px;">
{% if is_favorite %}★{% else %}☆{% endif %}
</span>
</div>
</div>
</div>
</div>
</div>
<!-- K线图表 -->
<div class="chart-section">
<div class="chart-title">K线走势图</div>
<div id="kline-chart" class="chart-container"></div>
</div>
<!-- 分析网格 -->
<div class="analysis-grid">
<!-- 趋势分析 -->
<div class="analysis-card">
<div class="card-title">趋势分析</div>
<div class="analysis-item">
<div class="analysis-label">趋势方向</div>
<div class="analysis-value
{% if trend_analysis.overall_trend == 'bullish' or trend_analysis.overall_trend == 'strong_bullish' or trend_analysis.overall_trend == 'weak_bullish' %}direction-up{% elif trend_analysis.overall_trend == 'bearish' or trend_analysis.overall_trend == 'strong_bearish' or trend_analysis.overall_trend == 'weak_bearish' %}direction-down{% endif %}">
{% if trend_analysis.overall_trend == 'strong_bullish' %}强多头
{% elif trend_analysis.overall_trend == 'strong_bearish' %}强空头
{% elif trend_analysis.overall_trend == 'weak_bullish' %}弱多头
{% elif trend_analysis.overall_trend == 'weak_bearish' %}弱空头
{% elif trend_analysis.overall_trend == 'bullish' %}多头
{% elif trend_analysis.overall_trend == 'bearish' %}空头
{% elif trend_analysis.overall_trend == 'neutral' %}中性
{% else %}{{ trend_analysis.overall_trend | capitalize }}
{% endif %}
</div>
</div>
<div class="analysis-item">
<div class="analysis-label">趋势强度</div>
<div class="analysis-value">{{ trend_analysis.trend_strength }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">ADX指标</div>
<div class="analysis-value">
{% if trend_analysis.adx is defined and trend_analysis.adx is not none %}
{{ trend_analysis.adx | round(2) }}
{% else %}
0
{% endif %}
</div>
</div>
<div class="analysis-item">
<div class="analysis-label">MA关系</div>
<div class="analysis-value">{{ trend_analysis.ma_relationship }}</div>
</div>
</div>
<!-- 风险分析 -->
<div class="analysis-card">
<div class="card-title">风险分析</div>
<div class="analysis-item">
<div class="analysis-label">ATR</div>
<div class="analysis-value">{{ risk_analysis.atr }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">多头止损</div>
<div class="analysis-value">{{ risk_analysis.stop_loss_long }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">空头止损</div>
<div class="analysis-value">{{ risk_analysis.stop_loss_short }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">仓位大小</div>
<div class="analysis-value">{{ risk_analysis.position_size }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">风险比例</div>
<div class="analysis-value">{{ risk_analysis.risk_ratio }}%</div>
</div>
</div>
<!-- 资金流向 -->
<div class="analysis-card">
<div class="card-title">资金流向</div>
<div class="analysis-item">
<div class="analysis-label">资金信号</div>
<div class="analysis-value
{% if fund_flow_analysis.fund_signal == 'positive' %}direction-up{% elif fund_flow_analysis.fund_signal == 'negative' %}direction-down{% endif %}">
{{ fund_flow_analysis.fund_signal | capitalize }}
</div>
</div>
<div class="analysis-item">
<div class="analysis-label">持仓变化</div>
<div class="analysis-value">
{% if fund_flow_analysis.open_interest_change is defined and fund_flow_analysis.open_interest_change is not none %}
{{ fund_flow_analysis.open_interest_change | round(2) }}%
{% else %}
0%
{% endif %}
</div>
</div>
<div class="analysis-item">
<div class="analysis-label">量价关系</div>
<div class="analysis-value">{{ fund_flow_analysis.volume_price_relationship }}</div>
</div>
</div>
<!-- 换月分析 -->
<div class="analysis-card">
<div class="card-title">换月分析</div>
<div class="analysis-item">
<div class="analysis-label">距离交割</div>
<div class="analysis-value warning">{{ rollover_analysis.days_to_delivery }} 天</div>
</div>
<div class="analysis-item">
<div class="analysis-label">预警级别</div>
<div class="analysis-value warning">{{ rollover_analysis.warning_level | capitalize }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">流动性风险</div>
<div class="analysis-value warning">{{ rollover_analysis.liquidity_risk }}</div>
</div>
</div>
</div>
<!-- AI分析 -->
<div class="ai-analysis">
<div class="ai-title">
<div class="ai-icon">AI</div>
AI智能分析
<div style="margin-left: auto; display: flex; align-items: center;">
<span style="font-size: 14px; margin-right: 8px; color: #666;">模型选择:</span>
<form action="/symbol/{{ symbol }}" method="get" style="display: flex; align-items: center;">
<select name="model" onchange="this.form.submit()" style="padding: 4px 8px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 14px; outline: none;">
{% for model in available_models %}
<option value="{{ model }}" {% if model_name == model %}selected{% endif %}>
{% if model == 'deepseek' %}DeepSeek
{% elif model == 'gpt' %}GPT
{% elif model == 'gemini' %}Gemini
{% else %}{{ model }}
{% endif %}
</option>
{% endfor %}
</select>
</form>
</div>
</div>
{% if ai_analysis.error %}
<!-- 模型不可用时的友好提示 -->
<div style="text-align: center; padding: 60px; background: #f9fafb; border-radius: 8px;">
<div style="font-size: 48px; margin-bottom: 20px;">⚠️</div>
<h3 style="font-size: 18px; font-weight: 600; color: #333; margin-bottom: 12px;">AI分析不可用</h3>
<p style="font-size: 14px; color: #666; margin-bottom: 24px;">
{% if ai_analysis.error == 'API密钥未配置' %}
当前模型的API密钥未配置请在.env文件中添加相应的API密钥
{% elif ai_analysis.error == 'API调用失败' %}
API调用失败请检查网络连接或API密钥是否正确
{% else %}
{{ ai_analysis.error }}
{% endif %}
</p>
<div style="display: flex; justify-content: center; gap: 16px;">
<a href="/symbol/{{ symbol }}?model=deepseek" style="padding: 8px 16px; background: #f0f0f0; color: #333; text-decoration: none; border-radius: 4px; font-size: 14px;">切换模型</a>
</div>
</div>
{% else %}
<!-- AI分析内容 -->
<div class="ai-content">
<!-- 趋势分析卡片 -->
<div class="analysis-card" style="margin-bottom: 20px;">
<div class="card-title">
<div style="display: flex; align-items: center;">
<div style="width: 8px; height: 16px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 4px; margin-right: 8px;"></div>
趋势判断
</div>
</div>
<div class="analysis-grid" style="gap: 16px;">
<div class="analysis-item" style="flex: 1; padding: 12px; background: #f9fafb; border-radius: 6px;">
<div class="analysis-label" style="margin-bottom: 4px;">趋势方向</div>
<div class="analysis-value
{% if ai_analysis.trend_judgment and ('多' in ai_analysis.trend_judgment) %}direction-up{% elif ai_analysis.trend_judgment and ('空' in ai_analysis.trend_judgment) %}direction-down{% endif %}"
style="font-size: 16px; font-weight: 600;">
{{ ai_analysis.trend_judgment | default('未知') }}
</div>
</div>
<div class="analysis-item" style="flex: 1; padding: 12px; background: #f9fafb; border-radius: 6px;">
<div class="analysis-label" style="margin-bottom: 4px;">胜率评估</div>
<div class="analysis-value" style="font-size: 16px; font-weight: 600;">{{ ai_analysis.win_rate_assessment | default('未知') }}</div>
</div>
<div class="analysis-item" style="flex: 1; padding: 12px; background: #f9fafb; border-radius: 6px;">
<div class="analysis-label" style="margin-bottom: 4px;">交易建议</div>
<div class="analysis-value
{% if ai_analysis.trade_recommendation and ('多' in ai_analysis.trade_recommendation) %}direction-up{% elif ai_analysis.trade_recommendation and ('空' in ai_analysis.trade_recommendation) %}direction-down{% endif %}"
style="font-size: 16px; font-weight: 600;">
{{ ai_analysis.trade_recommendation | default('未知') }}
</div>
</div>
</div>
<!-- 风险预警 -->
{% if ai_analysis.risk_warning and ai_analysis.risk_warning != '无' %}
<div style="margin-top: 16px; padding: 12px; background: #fff2f0; border: 1px solid #ffccc7; border-radius: 6px;">
<div style="font-size: 14px; font-weight: 600; color: #ff4d4f; margin-bottom: 4px;">⚠️ 风险预警</div>
<div style="font-size: 14px; color: #ff4d4f;">{{ ai_analysis.risk_warning }}</div>
</div>
{% endif %}
</div>
<!-- 分析逻辑 -->
{% if ai_analysis.analysis_logic %}
<div class="analysis-card" style="margin-bottom: 20px;">
<div class="card-title">
<div style="display: flex; align-items: center;">
<div style="width: 8px; height: 16px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 4px; margin-right: 8px;"></div>
分析逻辑
</div>
</div>
<div style="padding: 12px; background: #f9fafb; border-radius: 6px;">
{% if ai_analysis.analysis_logic is string %}
<!-- 处理字符串形式的分析逻辑 -->
{% if '1.' in ai_analysis.analysis_logic or '2.' in ai_analysis.analysis_logic or '3.' in ai_analysis.analysis_logic %}
<!-- 处理列表形式的分析逻辑 -->
<ul style="list-style: none; padding: 0; margin: 0;">
{% for item in ai_analysis.analysis_logic.split('\n') %}
{% if item.strip() %}
<li style="padding: 8px 0; border-bottom: 1px solid #f0f0f0;">
<div style="display: flex; align-items: flex-start;">
<div style="width: 20px; height: 20px; background: #667eea; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; margin-right: 12px; flex-shrink: 0;">
{% if loop.index <= 9 %}{{ loop.index }}{% else %}{{ loop.index }}{% endif %}
</div>
<div style="flex: 1;">{{ item.strip() }}</div>
</div>
</li>
{% endif %}
{% endfor %}
</ul>
{% else %}
<!-- 处理普通文本形式的分析逻辑 -->
<p style="margin: 0;">{{ ai_analysis.analysis_logic }}</p>
{% endif %}
{% else %}
<!-- 处理其他形式的分析逻辑 -->
<p style="margin: 0;">{{ ai_analysis.analysis_logic | default('无详细分析') }}</p>
{% endif %}
</div>
</div>
{% endif %}
</div>
<!-- 交易建议 -->
<div class="recommendation" style="margin-top: 20px;">
<div class="recommendation-title">
<div style="display: flex; align-items: center;">
<div style="width: 8px; height: 16px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 4px; margin-right: 8px;"></div>
详细交易建议
</div>
</div>
<div class="analysis-grid" style="margin-top: 16px; gap: 20px;">
<!-- 交易参数 -->
<div class="analysis-card" style="flex: 1;">
<div class="card-title">交易参数</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px;">
<div class="analysis-item" style="padding: 12px; background: #f9fafb; border-radius: 6px;">
<div class="analysis-label" style="margin-bottom: 4px;">交易方向</div>
<div class="analysis-value
{% if recommendation.direction == 'long' %}direction-up{% elif recommendation.direction == 'short' %}direction-down{% endif %}"
style="font-size: 16px; font-weight: 600;">
{% if recommendation.direction == 'long' %}多头
{% elif recommendation.direction == 'short' %}空头
{% else %}{{ recommendation.direction | default('未知') }}
{% endif %}
</div>
</div>
<div class="analysis-item" style="padding: 12px; background: #f9fafb; border-radius: 6px;">
<div class="analysis-label" style="margin-bottom: 4px;">入场价格</div>
<div class="analysis-value" style="font-size: 16px; font-weight: 600;">{{ recommendation.entry_price | default('未知') }}</div>
</div>
<div class="analysis-item" style="padding: 12px; background: #fff2f0; border-radius: 6px;">
<div class="analysis-label" style="margin-bottom: 4px;">止损价格</div>
<div class="analysis-value warning" style="font-size: 16px; font-weight: 600;">{{ recommendation.stop_loss | default('未知') }}</div>
</div>
<div class="analysis-item" style="padding: 12px; background: #f0f5ff; border-radius: 6px;">
<div class="analysis-label" style="margin-bottom: 4px;">目标价格</div>
<div class="analysis-value direction-up" style="font-size: 16px; font-weight: 600;">{{ recommendation.target_price | default('未知') }}</div>
</div>
<div class="analysis-item" style="padding: 12px; background: #f9fafb; border-radius: 6px;">
<div class="analysis-label" style="margin-bottom: 4px;">仓位大小</div>
<div class="analysis-value" style="font-size: 16px; font-weight: 600;">{{ recommendation.position_size | default('未知') }}手</div>
</div>
</div>
</div>
<!-- 执行计划 -->
<div class="analysis-card" style="flex: 1;">
<div class="card-title">执行计划</div>
<div style="padding: 12px; background: #f9fafb; border-radius: 6px;">
{% if recommendation.execution_plan %}
{% if '1.' in recommendation.execution_plan or '2.' in recommendation.execution_plan or '3.' in recommendation.execution_plan %}
<!-- 处理列表形式的执行计划 -->
<ul style="list-style: none; padding: 0; margin: 0;">
{% for item in recommendation.execution_plan.split('\n') %}
{% if item.strip() %}
<li style="padding: 8px 0; border-bottom: 1px solid #f0f0f0;">
<div style="display: flex; align-items: flex-start;">
<div style="width: 20px; height: 20px; background: #764ba2; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; margin-right: 12px; flex-shrink: 0;">
{% if loop.index <= 9 %}{{ loop.index }}{% else %}{{ loop.index }}{% endif %}
</div>
<div style="flex: 1;">{{ item.strip() }}</div>
</div>
</li>
{% endif %}
{% endfor %}
</ul>
{% else %}
<!-- 处理普通文本形式的执行计划 -->
<p style="margin: 0;">{{ recommendation.execution_plan }}</p>
{% endif %}
{% else %}
<p style="margin: 0; color: #999;">无详细计划</p>
{% endif %}
</div>
</div>
<!-- 风险提示 -->
<div class="analysis-card" style="flex: 1;">
<div class="card-title">风险提示</div>
<div style="padding: 12px; background: #fff2f0; border: 1px solid #ffccc7; border-radius: 6px;">
{% if recommendation.risk_tips %}
{% if '1.' in recommendation.risk_tips or '2.' in recommendation.risk_tips or '3.' in recommendation.risk_tips %}
<!-- 处理列表形式的风险提示 -->
<ul style="list-style: none; padding: 0; margin: 0;">
{% for item in recommendation.risk_tips.split('\n') %}
{% if item.strip() %}
<li style="padding: 8px 0; border-bottom: 1px solid #ffccc7;">
<div style="display: flex; align-items: flex-start;">
<div style="width: 20px; height: 20px; background: #ff4d4f; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; margin-right: 12px; flex-shrink: 0;">
⚠️
</div>
<div style="flex: 1; color: #ff4d4f;">{{ item.strip() }}</div>
</div>
</li>
{% endif %}
{% endfor %}
</ul>
{% else %}
<!-- 处理普通文本形式的风险提示 -->
<p style="margin: 0; color: #ff4d4f;">{{ recommendation.risk_tips }}</p>
{% endif %}
{% else %}
<p style="margin: 0; color: #ff4d4f;">无风险提示</p>
{% endif %}
</div>
</div>
</div>
</div>
{% endif %}
</div>
<!-- 底部 -->
<div class="footer">
<p>© 2024 AI期货分析系统 | 数据更新时间: {{ now() }}</p>
</div>
</div>
<script>
// 获取当前时间
function now() {
const date = new Date();
return date.toLocaleString('zh-CN');
}
// 添加到自选
function addToFavorite(symbol) {
fetch(`/api/selected/add/${symbol}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
alert('已添加到自选');
document.getElementById('favoriteBtn').textContent = '★';
})
.catch(error => {
console.error('添加自选失败:', error);
alert('添加自选失败');
});
}
// 从自选中删除
function removeFromFavorite(symbol) {
fetch(`/api/selected/remove/${symbol}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
alert('已从自选中删除');
document.getElementById('favoriteBtn').textContent = '☆';
})
.catch(error => {
console.error('删除自选失败:', error);
alert('删除自选失败');
});
}
// 切换自选状态
function toggleFavorite(symbol) {
const favoriteBtn = document.getElementById('favoriteBtn');
if (favoriteBtn.textContent === '☆') {
addToFavorite(symbol);
} else {
removeFromFavorite(symbol);
}
}
// 初始化K线图表
window.onload = function() {
const chart = echarts.init(document.getElementById('kline-chart'));
// 准备K线数据
const klineData = [
{% for item in kline_data %}
["{{ item.date }}", {{ item.open }}, {{ item.close }}, {{ item.low }}, {{ item.high }}],
{% endfor %}
];
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
legend: {
data: ['K线']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: klineData.map(item => item[0]),
boundaryGap: false
},
yAxis: {
type: 'value',
scale: true
},
series: [
{
name: 'K线',
type: 'candlestick',
data: klineData.map(item => [item[1], item[4], item[3], item[2]]),
itemStyle: {
color: '#52c41a',
color0: '#ff4d4f',
borderColor: '#52c41a',
borderColor0: '#ff4d4f'
}
}
]
};
chart.setOption(option);
// 响应式调整
window.addEventListener('resize', function() {
chart.resize();
});
};
// 主题切换功能
function toggleTheme() {
const body = document.body;
if (body.classList.contains('dark-theme')) {
body.classList.remove('dark-theme');
localStorage.setItem('theme', 'light');
} else {
body.classList.add('dark-theme');
localStorage.setItem('theme', 'dark');
}
}
// 初始化主题
document.addEventListener('DOMContentLoaded', function() {
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'light') {
document.body.classList.remove('dark-theme');
} else {
document.body.classList.add('dark-theme');
}
});
</script>
</body>
</html>