2025-05-14 16:52:24 +08:00
|
|
|
|
<!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">
|
2025-05-19 17:02:52 +08:00
|
|
|
|
<link rel="stylesheet" href="../static/css/hsgt_monitor.css">
|
2025-05-14 16:52:24 +08:00
|
|
|
|
<!-- 自定义样式 -->
|
2025-05-19 17:02:52 +08:00
|
|
|
|
|
2025-05-14 16:52:24 +08:00
|
|
|
|
</head>
|
|
|
|
|
<body>
|
2025-05-19 17:02:52 +08:00
|
|
|
|
<!-- 导航栏 -->
|
|
|
|
|
<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>
|
2025-05-14 16:52:24 +08:00
|
|
|
|
<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>
|
|
|
|
|
|
2025-05-19 17:02:52 +08:00
|
|
|
|
<!-- 融资融券数据汇总信息 -->
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
<!-- 融资融券数据展示 -->
|
2025-05-14 16:52:24 +08:00
|
|
|
|
<div class="row mt-4">
|
2025-05-19 17:02:52 +08:00
|
|
|
|
<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">
|
2025-05-14 16:52:24 +08:00
|
|
|
|
<div class="col-12">
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-header">
|
2025-05-19 17:02:52 +08:00
|
|
|
|
融资融券市场风险分析
|
2025-05-14 16:52:24 +08:00
|
|
|
|
</div>
|
|
|
|
|
<div class="card-body">
|
2025-05-19 17:02:52 +08:00
|
|
|
|
|
|
|
|
|
<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>
|
2025-05-14 16:52:24 +08:00
|
|
|
|
</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>
|
2025-05-19 17:02:52 +08:00
|
|
|
|
|
|
|
|
|
<!-- 恐贪指数相关的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>
|
2025-05-14 16:52:24 +08:00
|
|
|
|
</body>
|
|
|
|
|
</html>
|