863 lines
38 KiB
HTML
863 lines
38 KiB
HTML
<!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> |