stock_fundamentals/src/templates/hsgt_monitor.html

863 lines
38 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>沪深港通资金流向监控</title>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="../static/css/bootstrap.min.css">
<link rel="stylesheet" href="../static/css/hsgt_monitor.css">
<!-- 自定义样式 -->
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/">股票估值分析工具</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/">个股估值分析</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/industry">行业估值分析</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/hsgt">资金情况</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid py-4">
<div class="row mb-4">
<div class="col-12">
<h2 class="text-center">沪深港通资金流向监控
<button id="refreshBtn" class="btn btn-sm btn-outline-primary refresh-btn">
<i class="bi bi-arrow-clockwise"></i> 刷新数据
</button>
</h2>
<p class="text-center update-time" id="updateTime"></p>
</div>
</div>
<!-- 北向资金卡片 -->
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">
北向资金流向 (单位:亿元)
</div>
<div class="card-body">
<p class="flow-direction text-center">提示:该走势图为陆股通指数成分股大单资金流向,非北向资金,但具备一定参考价值。</p>
<div class="row mb-3">
<div class="col-md-4">
<div class="stat-card" id="northTotal">
<div class="stat-value">--</div>
<div class="stat-title">北向资金净流入(亿元)</div>
</div>
</div>
<div class="col-md-4">
<div class="stat-card" id="northSH">
<div class="stat-value">--</div>
<div class="stat-title">沪股通净流入(亿元)</div>
</div>
</div>
<div class="col-md-4">
<div class="stat-card" id="northSZ">
<div class="stat-value">--</div>
<div class="stat-title">深股通净流入(亿元)</div>
</div>
</div>
</div>
<div id="northChart" class="chart-container"></div>
</div>
</div>
</div>
<!-- 南向资金卡片 -->
<div class="col-md-6">
<div class="card">
<div class="card-header">
南向资金流向 (单位:亿元)
</div>
<div class="card-body">
<p class="flow-direction text-center">从内地流入港股的资金</p>
<div class="row mb-3">
<div class="col-md-4">
<div class="stat-card" id="southTotal">
<div class="stat-value">--</div>
<div class="stat-title">南向资金净流入(亿元)</div>
</div>
</div>
<div class="col-md-4">
<div class="stat-card" id="southHKSH">
<div class="stat-value">--</div>
<div class="stat-title">沪市港股通净流入(亿元)</div>
</div>
</div>
<div class="col-md-4">
<div class="stat-card" id="southHKSZ">
<div class="stat-value">--</div>
<div class="stat-title">深市港股通净流入(亿元)</div>
</div>
</div>
</div>
<div id="southChart" class="chart-container"></div>
</div>
</div>
</div>
</div>
<!-- 融资融券数据汇总信息 -->
<div class="row mt-3 mb-2">
<div class="col-12 text-center">
<p class="summary-text" id="summaryBalanceChangeDesc">--</p>
<p class="summary-text" id="summarySecuritiesChangeDesc">--</p>
</div>
</div>
<!-- 融资融券数据展示 -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span>融资融券数据监控 (单位:亿元)</span>
<div class="btn-group" role="group">
<button type="button" class="btn btn-sm btn-outline-primary active" data-metric="total_rzrq_balance">融资融券余额合计</button>
<button type="button" class="btn btn-sm btn-outline-primary" data-metric="total_financing_buy">融资买入额合计</button>
<button type="button" class="btn btn-sm btn-outline-primary" data-metric="total_financing_balance">融资余额合计</button>
<button type="button" class="btn btn-sm btn-outline-primary" data-metric="financing_repayment">融资偿还</button>
<button type="button" class="btn btn-sm btn-outline-primary" data-metric="securities_balance">融券余额</button>
<button id="rzrqRefreshBtn" class="btn btn-sm btn-outline-secondary ms-2">
<i class="bi bi-arrow-clockwise"></i> 刷新
</button>
</div>
</div>
<div class="card-body">
<div id="rzrqChart" class="chart-container"></div>
<p class="update-time text-center" id="rzrqUpdateTime"></p>
</div>
</div>
</div>
</div>
<!-- 融资融券风险分析 -->
<!-- <div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
融资融券市场风险分析
</div>
<div class="card-body">
<div class="row mb-4">
<div class="col-md-12">
<div class="alert" id="overallRiskAlert">
<h5 class="alert-heading">市场综合风险评估: <span id="overallRiskLevel">加载中...</span></h5>
<p id="overallRiskDesc">正在分析融资融券数据,评估市场风险...</p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<div class="card h-100">
<div class="card-header">
融资融券余额变化
</div>
<div class="card-body">
<div class="d-flex justify-content-between mb-2">
<span>变化率:</span>
<span id="balanceChangeRate" class="fw-bold">--</span>
</div>
<div class="d-flex justify-content-between mb-2">
<span>风险等级:</span>
<span id="balanceRiskLevel">--</span>
</div>
<p id="balanceRiskDesc" class="mt-2 small text-muted">详细分析如下。</p>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card h-100">
<div class="card-header">
融资偿还与买入比率
</div>
<div class="card-body">
<div class="d-flex justify-content-between mb-2">
<span>平均比率:</span>
<span id="repayBuyRatio" class="fw-bold">--</span>
</div>
<div class="d-flex justify-content-between mb-2">
<span>风险等级:</span>
<span id="repayRiskLevel">--</span>
</div>
<p id="repayRiskDesc" class="mt-2 small">--</p>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card h-100">
<div class="card-header">
融券余额变化 (空头力量)
</div>
<div class="card-body">
<div class="d-flex justify-content-between mb-2">
<span>变化率:</span>
<span id="securitiesChangeRate" class="fw-bold">--</span>
</div>
<div class="d-flex justify-content-between mb-2">
<span>风险等级:</span>
<span id="securitiesRiskLevel">--</span>
</div>
<p id="securitiesRiskDesc" class="mt-2 small text-muted">详细分析如下。</p>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card h-100">
<div class="card-header">
融资占比分析
</div>
<div class="card-body">
<div class="d-flex justify-content-between mb-2">
<span>融资占比:</span>
<span id="financingRatio" class="fw-bold">--</span>
</div>
<div class="d-flex justify-content-between mb-2">
<span>历史百分位:</span>
<span id="financingRatioPercentile">--</span>
</div>
<p class="mt-2 small text-muted">融资占比反映了市场中多头使用杠杆的程度。百分位数越高表示融资占比处于历史较高水平。</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div> -->
<!-- 恐贪指数展示 -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span>市场恐贪指数 (Fear & Greed Index)</span>
<button id="addFearGreedBtn" class="btn btn-sm btn-primary">
<i class="bi bi-plus-circle"></i> 新增数据
</button>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-4">
<div class="stat-card" id="fearGreedValue">
<div class="stat-value">--</div>
<div class="stat-title">最新恐贪指数</div>
</div>
</div>
<div class="col-md-4">
<div class="stat-card" id="fearGreedStatus">
<div class="stat-value">--</div>
<div class="stat-title">市场情绪状态</div>
</div>
</div>
<div class="col-md-4">
<div class="stat-card" id="fearGreedDate">
<div class="stat-value">--</div>
<div class="stat-title">更新日期</div>
</div>
</div>
</div>
<div id="fearGreedChart" class="chart-container"></div>
<p class="update-time text-center" id="fearGreedUpdateTime"></p>
</div>
</div>
</div>
</div>
<!-- 新增恐贪指数数据的模态框 -->
<div class="modal fade" id="addFearGreedModal" tabindex="-1" aria-labelledby="addFearGreedModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addFearGreedModalLabel">新增恐贪指数数据</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="addFearGreedForm">
<div class="mb-3">
<label for="indexValue" class="form-label">恐贪指数值 (0-100)</label>
<input type="number" class="form-control" id="indexValue" min="0" max="100" step="0.01" required>
<div class="form-text">输入0-100之间的数值保留两位小数</div>
</div>
<div class="mb-3">
<label for="tradingDate" class="form-label">交易日期</label>
<input type="date" class="form-control" id="tradingDate" required>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="submitFearGreed">提交</button>
</div>
</div>
</div>
</div>
</div>
<!-- JavaScript 库 -->
<script src="../static/js/jquery.min.js"></script>
<script src="../static/js/bootstrap.bundle.min.js"></script>
<script src="../static/js/echarts.min.js"></script>
<script src="../static/js/hsgt_monitor.js"></script>
<!-- 恐贪指数相关的JavaScript -->
<script>
// 初始化恐贪指数图表
let fearGreedChart = null;
let fearGreedIndexSelector = null;
let fearGreedChartData = null; // 存储恐贪指数数据
// 加载恐贪指数数据
function loadFearGreedData() {
$.ajax({
url: '/api/fear_greed/data',
type: 'GET',
dataType: 'json',
success: function(response) {
if (response.status === 'success') {
fearGreedChartData = response.data; // 存储恐贪指数数据
updateFearGreedUI(response.data);
// 初始化恐贪指数选择器,只在首次加载时初始化
if (!fearGreedIndexSelector) {
initFearGreedIndexSelector();
}
} else {
console.error('加载恐贪指数数据失败:', response.message);
}
},
error: function(xhr, status, error) {
console.error('请求恐贪指数数据失败:', error);
}
});
}
// 更新恐贪指数UI
function updateFearGreedUI(data) {
// 更新统计卡片
if (data.latest) {
$('#fearGreedValue .stat-value').text(data.latest.index_value.toFixed(2));
$('#fearGreedStatus .stat-value').text(data.latest_status);
$('#fearGreedDate .stat-value').text(data.latest.trading_date);
// 根据状态设置颜色
const value = data.latest.index_value;
let statusColor;
if (value < 25) {
statusColor = '#d9534f'; // 红色,极度恐慌
} else if (value < 40) {
statusColor = '#f0ad4e'; // 橙色,恐慌
} else if (value < 50) {
statusColor = '#5bc0de'; // 浅蓝色,偏向恐慌
} else if (value < 60) {
statusColor = '#5cb85c'; // 绿色,中性
} else if (value < 75) {
statusColor = '#0275d8'; // 蓝色,偏向贪婪
} else if (value < 90) {
statusColor = '#f0ad4e'; // 橙色,贪婪
} else {
statusColor = '#d9534f'; // 红色,极度贪婪
}
$('#fearGreedStatus .stat-value').css('color', statusColor);
}
// 更新更新时间 - 显示最新数据的更新时间
$('#fearGreedUpdateTime').text('最后更新: ' + data.update_time);
// 初始化/更新图表
initFearGreedChart(data.dates, data.values);
}
// 初始化恐贪指数图表
function initFearGreedChart(dates, values) {
if (!fearGreedChart) {
fearGreedChart = echarts.init(document.getElementById('fearGreedChart'));
}
const option = {
tooltip: {
trigger: 'axis',
formatter: function(params) {
const param = params[0];
const value = param.value;
let status;
if (value < 25) {
status = '极度恐慌';
} else if (value < 40) {
status = '恐慌';
} else if (value < 50) {
status = '偏向恐慌';
} else if (value < 60) {
status = '中性';
} else if (value < 75) {
status = '偏向贪婪';
} else if (value < 90) {
status = '贪婪';
} else {
status = '极度贪婪';
}
return `${param.axisValue}<br/>恐贪指数: ${value.toFixed(2)}<br/>状态: ${status}`;
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: dates
},
yAxis: {
type: 'value',
min: 0,
max: 100,
axisLabel: {
formatter: '{value}'
}
},
visualMap: {
show: false,
dimension: 1,
pieces: [
{gt: 0, lte: 25, color: '#d9534f'}, // 极度恐慌
{gt: 25, lte: 40, color: '#f0ad4e'}, // 恐慌
{gt: 40, lte: 50, color: '#5bc0de'}, // 偏向恐慌
{gt: 50, lte: 60, color: '#5cb85c'}, // 中性
{gt: 60, lte: 75, color: '#0275d8'}, // 偏向贪婪
{gt: 75, lte: 90, color: '#f0ad4e'}, // 贪婪
{gt: 90, lte: 100, color: '#d9534f'} // 极度贪婪
]
},
series: [
{
name: '恐贪指数',
type: 'line',
data: values,
markLine: {
silent: true,
lineStyle: {
color: '#999'
},
data: [
{
yAxis: 25,
label: {
formatter: '极度恐慌'
}
},
{
yAxis: 50,
label: {
formatter: '中性'
}
},
{
yAxis: 75,
label: {
formatter: '贪婪'
}
}
]
}
}
]
};
fearGreedChart.setOption(option);
}
// 初始化恐贪指数选择器
function initFearGreedIndexSelector() {
fearGreedIndexSelector = new IndexSelector('fearGreedChart', {
// 获取图表当前显示的日期范围
getDateRange: function() {
// 获取恐贪指数图表的日期范围
if (fearGreedChartData && fearGreedChartData.dates && fearGreedChartData.dates.length) {
return {
startDate: fearGreedChartData.dates[0],
endDate: fearGreedChartData.dates[fearGreedChartData.dates.length - 1]
};
}
return { startDate: null, endDate: null };
},
// 指数数据更新时的回调
onChange: function(selectedIndices) {
updateFearGreedChartWithIndices(selectedIndices);
}
});
}
// 将数据对齐到日期范围
function alignDataToDateRange(sourceDates, sourceValues, targetDates) {
const result = new Array(targetDates.length).fill(null);
const dateMap = {};
// 创建源数据日期到值的映射
sourceDates.forEach((date, index) => {
dateMap[date] = sourceValues[index];
});
// 映射到目标日期
targetDates.forEach((date, index) => {
if (dateMap[date] !== undefined) {
result[index] = dateMap[date];
}
});
return result;
}
// 更新图表时添加指数数据
function updateFearGreedChartWithIndices(indices) {
if (!fearGreedChart) return;
// 获取图表当前选项
const option = fearGreedChart.getOption();
// 保留原始恐贪指数系列
const originalSeries = option.series.filter(s => s.name === '恐贪指数');
// 清除所有系列,并重新添加原始恐贪指数系列
option.series = [...originalSeries];
// 添加指数数据
indices.forEach(index => {
if (!index.data || !index.data.dates) return;
// 将指数数据对齐到日期范围
const alignedData = alignDataToDateRange(index.data.dates, index.data.values, option.xAxis[0].data);
// 如果没有第二Y轴创建新的Y轴用于指数
if (!option.yAxis.some(axis => axis.name === '指数值')) {
option.yAxis.push({
name: '指数值',
type: 'value',
position: 'right',
min: 'dataMin',
max: 'dataMax',
splitLine: {
show: false
},
axisLabel: {
formatter: '{value}'
}
});
}
// 添加新的指数系列
option.series.push({
name: `${index.name}指数`,
type: 'line',
yAxisIndex: 1, // 使用第二个Y轴
data: alignedData,
symbol: 'none',
smooth: true,
lineStyle: {
width: 2,
color: index.color
},
itemStyle: {
color: index.color
}
});
});
// 更新图表
option.legend = {
data: [
'恐贪指数',
...indices.map(i => `${i.name}指数`)
],
selected: {
'恐贪指数': true,
...indices.reduce((acc, index) => {
acc[`${index.name}指数`] = true;
return acc;
}, {})
}
};
fearGreedChart.setOption(option, true);
}
// 窗口大小改变时调整图表大小
window.addEventListener('resize', function() {
if (fearGreedChart) {
fearGreedChart.resize();
}
});
// 添加恐贪指数数据
function addFearGreedData() {
const indexValue = parseFloat($('#indexValue').val());
const tradingDate = $('#tradingDate').val();
if (isNaN(indexValue) || indexValue < 0 || indexValue > 100) {
alert('请输入有效的恐贪指数值(0-100)');
return;
}
if (!tradingDate) {
alert('请选择交易日期');
return;
}
$.ajax({
url: '/api/fear_greed/add',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
index_value: indexValue,
trading_date: tradingDate
}),
success: function(response) {
if (response.status === 'success') {
// 关闭模态框
$('#addFearGreedModal').modal('hide');
// 重新加载数据
loadFearGreedData();
// 重置表单
$('#addFearGreedForm')[0].reset();
// 显示成功消息
// alert('恐贪指数数据添加成功!');
} else {
alert('添加失败: ' + response.message);
}
},
error: function(xhr, status, error) {
console.error('添加恐贪指数数据失败:', error);
alert('添加失败: ' + error);
}
});
}
// 事件绑定
$(document).ready(function() {
// 加载恐贪指数数据
loadFearGreedData();
// 新增数据按钮点击事件
$('#addFearGreedBtn').click(function() {
// 设置默认日期为今天
$('#tradingDate').val(new Date().toISOString().split('T')[0]);
// 显示模态框
$('#addFearGreedModal').modal('show');
});
// 提交按钮点击事件
$('#submitFearGreed').click(function() {
addFearGreedData();
});
// 刷新按钮点击事件(与恐贪指数一起刷新)
$('#refreshBtn').click(function() {
loadFearGreedData();
});
});
</script>
<!-- 指数选择器通用组件 -->
<script>
// 通用指数选择器组件
class IndexSelector {
constructor(targetChartId, options = {}) {
this.targetChartId = targetChartId;
this.targetChart = null;
this.indices = [];
this.selectedIndices = [];
this.colors = options.colors || ['#8A2BE2', '#FF1493', '#FF7F50', '#00CED1', '#32CD32'];
this.maxIndices = options.maxIndices || 3;
this.containerClass = options.containerClass || 'index-selector';
this.onChange = options.onChange || (() => {});
this.options = options;
// 创建选择器DOM元素
this.createSelectorDOM();
// 加载指数列表
this.loadIndicesList();
}
// 创建选择器DOM
createSelectorDOM() {
const container = document.createElement('div');
container.className = this.containerClass;
container.style.cssText = 'margin-left: 10px; display: inline-block;';
const select = document.createElement('select');
select.className = 'form-select form-select-sm';
select.id = `${this.targetChartId}-index-select`;
select.innerHTML = '<option value="">添加指数叠加...</option>';
container.appendChild(select);
// 添加到目标图表的header旁边
const chartHeader = document.querySelector(`#${this.targetChartId}`).closest('.card').querySelector('.card-header');
const btnGroup = chartHeader.querySelector('.btn-group') || chartHeader;
btnGroup.appendChild(container);
// 添加事件监听
select.addEventListener('change', (e) => this.handleSelectChange(e));
}
// 加载指数列表
loadIndicesList() {
fetch('/api/indices/list')
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
this.indices = data.data;
this.updateSelectOptions();
}
})
.catch(error => console.error('加载指数列表失败:', error));
}
// 更新下拉选项
updateSelectOptions() {
const select = document.getElementById(`${this.targetChartId}-index-select`);
// 保留第一个选项
select.innerHTML = '<option value="">添加指数叠加...</option>';
// 添加未选择的指数到下拉列表
this.indices.forEach(index => {
if (!this.selectedIndices.some(i => i.code === index.code)) {
const option = document.createElement('option');
option.value = index.code;
option.textContent = index.name;
select.appendChild(option);
}
});
}
// 处理选择变更
handleSelectChange(e) {
const indexCode = e.target.value;
if (!indexCode) return;
const index = this.indices.find(i => i.code === indexCode);
if (!index) return;
// 检查选择的指数数量限制
if (this.selectedIndices.length >= this.maxIndices) {
alert(`最多只能叠加${this.maxIndices}个指数!`);
e.target.value = '';
return;
}
// 添加到已选择列表
const colorIndex = this.selectedIndices.length % this.colors.length;
const selectedIndex = {
...index,
color: this.colors[colorIndex],
visible: true
};
this.selectedIndices.push(selectedIndex);
// 添加指数标签
this.addIndexLabel(selectedIndex);
// 加载并显示指数数据
this.loadIndexData(selectedIndex);
// 重置选择框
e.target.value = '';
this.updateSelectOptions();
}
// 添加指数标签
addIndexLabel(index) {
const container = document.querySelector(`#${this.targetChartId}`).closest('.card-body');
const labelsContainer = container.querySelector('.index-labels') || (() => {
const div = document.createElement('div');
div.className = 'index-labels d-flex flex-wrap mt-2';
container.insertBefore(div, container.firstChild);
return div;
})();
const label = document.createElement('div');
label.className = 'badge bg-light text-dark me-2 mb-2 p-2 d-flex align-items-center';
label.style.borderLeft = `3px solid ${index.color}`;
label.innerHTML = `
<span class="me-2">${index.name}</span>
<button type="button" class="btn-close btn-close-sm" aria-label="移除"></button>
`;
label.querySelector('.btn-close').addEventListener('click', () => {
this.removeIndex(index.code);
labelsContainer.removeChild(label);
});
labelsContainer.appendChild(label);
}
// 加载指数数据
loadIndexData(index) {
// 获取图表当前日期范围
const chartDates = this.options.getDateRange ?
this.options.getDateRange() :
{ startDate: null, endDate: null };
fetch(`/api/indices/data?code=${index.code}&start_date=${chartDates.startDate || ''}&end_date=${chartDates.endDate || ''}`)
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
// 更新指数数据
index.data = data.data;
// 调用回调函数更新图表
this.onChange(this.selectedIndices);
}
})
.catch(error => console.error(`加载指数 ${index.name} 数据失败:`, error));
}
// 移除指数
removeIndex(indexCode) {
const index = this.selectedIndices.findIndex(i => i.code === indexCode);
if (index !== -1) {
this.selectedIndices.splice(index, 1);
this.updateSelectOptions();
// 调用回调函数更新图表
this.onChange(this.selectedIndices);
}
}
// 获取所有已选择的指数
getSelectedIndices() {
return [...this.selectedIndices];
}
}
</script>
</body>
</html>