|
{% extends "layout.html" %}
|
|
|
|
{% block title %}情景预测 - {{ stock_code }} - 智能分析系统{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid py-3">
|
|
<div id="alerts-container"></div>
|
|
|
|
<div class="row mb-3">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header py-2">
|
|
<h5 class="mb-0">多情景预测</h5>
|
|
</div>
|
|
<div class="card-body py-2">
|
|
<form id="scenario-form" class="row g-2">
|
|
<div class="col-md-3">
|
|
<div class="input-group input-group-sm">
|
|
<span class="input-group-text">股票代码</span>
|
|
<input type="text" class="form-control" id="stock-code" placeholder="例如: 600519" required>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="input-group input-group-sm">
|
|
<span class="input-group-text">市场</span>
|
|
<select class="form-select" id="market-type">
|
|
<option value="A" selected>A股</option>
|
|
<option value="HK">港股</option>
|
|
<option value="US">美股</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="input-group input-group-sm">
|
|
<span class="input-group-text">预测天数</span>
|
|
<select class="form-select" id="days">
|
|
<option value="30">30天</option>
|
|
<option value="60" selected>60天</option>
|
|
<option value="90">90天</option>
|
|
<option value="180">180天</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<button type="submit" class="btn btn-primary btn-sm w-100">
|
|
<i class="fas fa-chart-line"></i> 预测
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="loading-panel" class="text-center py-5" style="display: none;">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
<p class="mt-3 mb-0">正在生成预测结果...</p>
|
|
<p class="text-muted small mt-2">
|
|
<i class="fas fa-info-circle"></i>
|
|
AI分析需要一些时间,请耐心等待
|
|
</p>
|
|
</div>
|
|
|
|
<div id="scenario-result" style="display: none;">
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header py-2">
|
|
<h5 class="mb-0">价格预测图</h5>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div id="price-prediction-chart" style="height: 400px;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-4">
|
|
<div class="card h-100">
|
|
<div class="card-header py-2 bg-success text-white">
|
|
<h5 class="mb-0">乐观情景</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between mb-3">
|
|
<div>
|
|
<h6>目标价</h6>
|
|
<h3 id="optimistic-price" class="text-success">--</h3>
|
|
</div>
|
|
<div>
|
|
<h6>预期涨幅</h6>
|
|
<h3 id="optimistic-change" class="text-success">--</h3>
|
|
</div>
|
|
</div>
|
|
<p id="optimistic-analysis" class="small"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card h-100">
|
|
<div class="card-header py-2 bg-primary text-white">
|
|
<h5 class="mb-0">中性情景</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between mb-3">
|
|
<div>
|
|
<h6>目标价</h6>
|
|
<h3 id="neutral-price" class="text-primary">--</h3>
|
|
</div>
|
|
<div>
|
|
<h6>预期涨幅</h6>
|
|
<h3 id="neutral-change" class="text-primary">--</h3>
|
|
</div>
|
|
</div>
|
|
<p id="neutral-analysis" class="small"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card h-100">
|
|
<div class="card-header py-2 bg-danger text-white">
|
|
<h5 class="mb-0">悲观情景</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between mb-3">
|
|
<div>
|
|
<h6>目标价</h6>
|
|
<h3 id="pessimistic-price" class="text-danger">--</h3>
|
|
</div>
|
|
<div>
|
|
<h6>预期涨幅</h6>
|
|
<h3 id="pessimistic-change" class="text-danger">--</h3>
|
|
</div>
|
|
</div>
|
|
<p id="pessimistic-analysis" class="small"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header py-2">
|
|
<h5 class="mb-0">风险与机会</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6 class="text-danger"><i class="fas fa-exclamation-triangle"></i> 风险因素</h6>
|
|
<ul id="risk-factors" class="small"></ul>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6 class="text-success"><i class="fas fa-lightbulb"></i> 有利因素</h6>
|
|
<ul id="opportunity-factors" class="small"></ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
$(document).ready(function() {
|
|
$('#scenario-form').submit(function(e) {
|
|
e.preventDefault();
|
|
const stockCode = $('#stock-code').val().trim();
|
|
const marketType = $('#market-type').val();
|
|
const days = $('#days').val();
|
|
|
|
if (!stockCode) {
|
|
showError('请输入股票代码!');
|
|
return;
|
|
}
|
|
|
|
fetchScenarioPrediction(stockCode, marketType, days);
|
|
});
|
|
});
|
|
|
|
function fetchScenarioPrediction(stockCode, marketType, days) {
|
|
$('#loading-panel').show();
|
|
$('#scenario-result').hide();
|
|
|
|
$.ajax({
|
|
url: '/api/scenario_predict',
|
|
type: 'POST',
|
|
contentType: 'application/json',
|
|
data: JSON.stringify({
|
|
stock_code: stockCode,
|
|
market_type: marketType,
|
|
days: parseInt(days)
|
|
}),
|
|
success: function(response) {
|
|
$('#loading-panel').hide();
|
|
renderScenarioPrediction(response, stockCode);
|
|
$('#scenario-result').show();
|
|
},
|
|
error: function(xhr, status, error) {
|
|
$('#loading-panel').hide();
|
|
let errorMsg = '获取情景预测失败';
|
|
if (xhr.responseJSON && xhr.responseJSON.error) {
|
|
errorMsg += ': ' + xhr.responseJSON.error;
|
|
} else if (error) {
|
|
errorMsg += ': ' + error;
|
|
}
|
|
showError(errorMsg);
|
|
}
|
|
});
|
|
}
|
|
|
|
function renderScenarioPrediction(data, stockCode) {
|
|
|
|
$('#optimistic-price').text('¥' + formatNumber(data.optimistic.target_price, 2));
|
|
$('#optimistic-change').text(formatPercent(data.optimistic.change_percent, 2));
|
|
$('#optimistic-analysis').text(data.optimistic_analysis || '暂无分析');
|
|
|
|
|
|
$('#neutral-price').text('¥' + formatNumber(data.neutral.target_price, 2));
|
|
$('#neutral-change').text(formatPercent(data.neutral.change_percent, 2));
|
|
$('#neutral-analysis').text(data.neutral_analysis || '暂无分析');
|
|
|
|
|
|
$('#pessimistic-price').text('¥' + formatNumber(data.pessimistic.target_price, 2));
|
|
$('#pessimistic-change').text(formatPercent(data.pessimistic.change_percent, 2));
|
|
$('#pessimistic-analysis').text(data.pessimistic_analysis || '暂无分析');
|
|
|
|
|
|
setDefaultRiskOpportunityFactors();
|
|
|
|
|
|
renderPricePredictionChart(data);
|
|
}
|
|
|
|
function setDefaultRiskOpportunityFactors() {
|
|
|
|
const riskFactors = [
|
|
'宏观经济下行压力增大',
|
|
'行业政策收紧可能性',
|
|
'原材料价格上涨',
|
|
'市场竞争加剧',
|
|
'技术迭代风险'
|
|
];
|
|
|
|
|
|
const opportunityFactors = [
|
|
'行业景气度持续向好',
|
|
'公司新产品上市',
|
|
'成本控制措施见效',
|
|
'产能扩张计划',
|
|
'国际市场开拓机会'
|
|
];
|
|
|
|
|
|
$('#risk-factors').html('');
|
|
riskFactors.forEach(factor => {
|
|
$('#risk-factors').append(`<li>${factor}</li>`);
|
|
});
|
|
|
|
$('#opportunity-factors').html('');
|
|
opportunityFactors.forEach(factor => {
|
|
$('#opportunity-factors').append(`<li>${factor}</li>`);
|
|
});
|
|
}
|
|
|
|
function renderPricePredictionChart(data) {
|
|
|
|
const currentPrice = data.current_price;
|
|
|
|
|
|
const dates = Object.keys(data.optimistic.path);
|
|
const optimisticPrices = Object.values(data.optimistic.path);
|
|
const neutralPrices = Object.values(data.neutral.path);
|
|
const pessimisticPrices = Object.values(data.pessimistic.path);
|
|
|
|
const options = {
|
|
series: [
|
|
{
|
|
name: '乐观情景',
|
|
data: optimisticPrices.map((price, i) => ({
|
|
x: new Date(dates[i]),
|
|
y: price
|
|
}))
|
|
},
|
|
{
|
|
name: '中性情景',
|
|
data: neutralPrices.map((price, i) => ({
|
|
x: new Date(dates[i]),
|
|
y: price
|
|
}))
|
|
},
|
|
{
|
|
name: '悲观情景',
|
|
data: pessimisticPrices.map((price, i) => ({
|
|
x: new Date(dates[i]),
|
|
y: price
|
|
}))
|
|
}
|
|
],
|
|
chart: {
|
|
height: 400,
|
|
type: 'line',
|
|
zoom: {
|
|
enabled: true
|
|
},
|
|
toolbar: {
|
|
show: true
|
|
}
|
|
},
|
|
colors: ['#20E647', '#2E93fA', '#FF4560'],
|
|
dataLabels: {
|
|
enabled: false
|
|
},
|
|
stroke: {
|
|
curve: 'smooth',
|
|
width: [3, 3, 3]
|
|
},
|
|
title: {
|
|
text: '多情景预测',
|
|
align: 'left'
|
|
},
|
|
grid: {
|
|
borderColor: '#e7e7e7',
|
|
row: {
|
|
colors: ['#f3f3f3', 'transparent'],
|
|
opacity: 0.5
|
|
},
|
|
},
|
|
markers: {
|
|
size: 1
|
|
},
|
|
xaxis: {
|
|
type: 'datetime',
|
|
title: {
|
|
text: '日期'
|
|
}
|
|
},
|
|
yaxis: {
|
|
title: {
|
|
text: '价格 (¥)'
|
|
},
|
|
labels: {
|
|
formatter: function(val) {
|
|
return formatNumber(val, 2);
|
|
}
|
|
}
|
|
},
|
|
legend: {
|
|
position: 'top',
|
|
horizontalAlign: 'right'
|
|
},
|
|
tooltip: {
|
|
shared: true,
|
|
intersect: false,
|
|
y: {
|
|
formatter: function(value) {
|
|
return '¥' + formatNumber(value, 2);
|
|
}
|
|
}
|
|
},
|
|
annotations: {
|
|
yaxis: [
|
|
{
|
|
y: currentPrice,
|
|
borderColor: '#000',
|
|
label: {
|
|
borderColor: '#000',
|
|
style: {
|
|
color: '#fff',
|
|
background: '#000'
|
|
},
|
|
text: '当前价格: ¥' + formatNumber(currentPrice, 2)
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
const chart = new ApexCharts(document.querySelector("#price-prediction-chart"), options);
|
|
chart.render();
|
|
}
|
|
</script>
|
|
{% endblock %} |