Download templates/risk_monitor.html from gitdeem/stock: direct link, hf CLI and curl.
- Browser
- Download file 40.8 kB
-
https://huggingface.co/spaces/gitdeem/stock/resolve/main/templates/risk_monitor.html
- Command line
-
hf download hf://spaces/gitdeem/stock/templates/risk_monitor.html
-
curl -L -o risk_monitor.html https://huggingface.co/spaces/gitdeem/stock/resolve/main/templates/risk_monitor.html
40.8 kB
| {% extends "layout.html" %} | |
| {% block title %}风险监控 - 智能分析系统{% 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"> | |
| <ul class="nav nav-tabs" id="risk-tabs" role="tablist"> | |
| <li class="nav-item" role="presentation"> | |
| <button class="nav-link active" id="stock-risk-tab" data-bs-toggle="tab" data-bs-target="#stock-risk" type="button" role="tab" aria-controls="stock-risk" aria-selected="true">个股风险</button> | |
| </li> | |
| <li class="nav-item" role="presentation"> | |
| <button class="nav-link" id="portfolio-risk-tab" data-bs-toggle="tab" data-bs-target="#portfolio-risk" type="button" role="tab" aria-controls="portfolio-risk" aria-selected="false">组合风险</button> | |
| </li> | |
| </ul> | |
| <div class="tab-content mt-3" id="risk-tabs-content"> | |
| <div class="tab-pane fade show active" id="stock-risk" role="tabpanel" aria-labelledby="stock-risk-tab"> | |
| <form id="stock-risk-form" class="row g-2"> | |
| <div class="col-md-4"> | |
| <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"> | |
| <button type="submit" class="btn btn-primary btn-sm w-100"> | |
| <i class="fas fa-search"></i> 分析风险 | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| <div class="tab-pane fade" id="portfolio-risk" role="tabpanel" aria-labelledby="portfolio-risk-tab"> | |
| <div class="alert alert-info"> | |
| <i class="fas fa-info-circle"></i> 分析投资组合的整体风险,识别高风险股票和风险集中度。 | |
| </div> | |
| <button id="analyze-portfolio-btn" class="btn btn-primary btn-sm"> | |
| <i class="fas fa-briefcase"></i> 分析我的投资组合 | |
| </button> | |
| </div> | |
| </div> | |
| </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> | |
| </div> | |
| <!-- 个股风险分析结果 --> | |
| <div id="stock-risk-result" style="display: none;"> | |
| <div class="row g-3 mb-3"> | |
| <div class="col-md-6"> | |
| <div class="card h-100"> | |
| <div class="card-header py-2 d-flex justify-content-between align-items-center"> | |
| <h5 class="mb-0">风险概览</h5> | |
| <span id="risk-level-badge" class="badge"></span> | |
| </div> | |
| <div class="card-body"> | |
| <div class="row mb-3"> | |
| <div class="col-md-7"> | |
| <h3 id="stock-name" class="mb-0 fs-4"></h3> | |
| <p id="stock-info" class="text-muted mb-0 small"></p> | |
| </div> | |
| <div class="col-md-5 text-end"> | |
| <div id="risk-gauge-chart" style="height: 120px;"></div> | |
| </div> | |
| </div> | |
| <div class="row"> | |
| <div class="col-12"> | |
| <h6>风险预警</h6> | |
| <div id="risk-alerts" class="mt-2"> | |
| <!-- 风险预警内容将在JS中动态填充 --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="col-md-6"> | |
| <div class="card h-100"> | |
| <div class="card-header py-2"> | |
| <h5 class="mb-0">风险构成</h5> | |
| </div> | |
| <div class="card-body"> | |
| <div id="risk-radar-chart" style="height: 220px;"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="row g-3 mb-3"> | |
| <div class="col-md-6"> | |
| <div class="card h-100"> | |
| <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>波动率指标</h6> | |
| <p><span class="text-muted">当前波动率:</span> <span id="current-volatility" class="fw-bold"></span></p> | |
| <p><span class="text-muted">变化率:</span> <span id="volatility-change" class="fw-bold"></span></p> | |
| <p><span class="text-muted">风险等级:</span> <span id="volatility-risk-level" class="fw-bold"></span></p> | |
| <p class="small text-muted" id="volatility-comment"></p> | |
| </div> | |
| <div class="col-md-6"> | |
| <div id="volatility-chart" style="height: 150px;"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="col-md-6"> | |
| <div class="card h-100"> | |
| <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>趋势指标</h6> | |
| <p><span class="text-muted">当前趋势:</span> <span id="current-trend" class="fw-bold"></span></p> | |
| <p><span class="text-muted">均线关系:</span> <span id="ma-relationship" class="fw-bold"></span></p> | |
| <p><span class="text-muted">风险等级:</span> <span id="trend-risk-level" class="fw-bold"></span></p> | |
| <p class="small text-muted" id="trend-comment"></p> | |
| </div> | |
| <div class="col-md-6"> | |
| <div id="trend-chart" style="height: 150px;"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="row g-3 mb-3"> | |
| <div class="col-md-6"> | |
| <div class="card h-100"> | |
| <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>反转信号</h6> | |
| <p><span class="text-muted">反转信号数:</span> <span id="reversal-signals" class="fw-bold"></span></p> | |
| <p><span class="text-muted">可能方向:</span> <span id="reversal-direction" class="fw-bold"></span></p> | |
| <p><span class="text-muted">风险等级:</span> <span id="reversal-risk-level" class="fw-bold"></span></p> | |
| <p class="small text-muted" id="reversal-comment"></p> | |
| </div> | |
| <div class="col-md-6"> | |
| <div id="reversal-chart" style="height: 150px;"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="col-md-6"> | |
| <div class="card h-100"> | |
| <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>成交量指标</h6> | |
| <p><span class="text-muted">成交量比率:</span> <span id="volume-ratio" class="fw-bold"></span></p> | |
| <p><span class="text-muted">成交量模式:</span> <span id="volume-pattern" class="fw-bold"></span></p> | |
| <p><span class="text-muted">风险等级:</span> <span id="volume-risk-level" class="fw-bold"></span></p> | |
| <p class="small text-muted" id="volume-comment"></p> | |
| </div> | |
| <div class="col-md-6"> | |
| <div id="volume-chart" style="height: 150px;"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 投资组合风险分析结果 --> | |
| <div id="portfolio-risk-result" style="display: none;"> | |
| <div class="row g-3 mb-3"> | |
| <div class="col-md-6"> | |
| <div class="card h-100"> | |
| <div class="card-header py-2 d-flex justify-content-between align-items-center"> | |
| <h5 class="mb-0">组合风险概览</h5> | |
| <span id="portfolio-risk-level-badge" class="badge"></span> | |
| </div> | |
| <div class="card-body"> | |
| <div class="row mb-3"> | |
| <div class="col-md-7"> | |
| <h3 class="mb-0 fs-4">我的投资组合</h3> | |
| <p class="text-muted mb-0 small">包含 <span id="portfolio-stock-count">0</span> 只股票</p> | |
| </div> | |
| <div class="col-md-5 text-end"> | |
| <div id="portfolio-risk-gauge-chart" style="height: 120px;"></div> | |
| </div> | |
| </div> | |
| <div class="row"> | |
| <div class="col-12"> | |
| <h6>风险集中度</h6> | |
| <p><span class="text-muted">行业集中度:</span> <span id="industry-concentration" class="fw-bold"></span></p> | |
| <p><span class="text-muted">高风险股票占比:</span> <span id="high-risk-concentration" class="fw-bold"></span></p> | |
| <div id="portfolio-risk-alerts" class="mt-2"> | |
| <!-- 风险预警内容将在JS中动态填充 --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="col-md-6"> | |
| <div class="card h-100"> | |
| <div class="card-header py-2"> | |
| <h5 class="mb-0">行业分布</h5> | |
| </div> | |
| <div class="card-body"> | |
| <div id="industry-distribution-chart" style="height: 220px;"></div> | |
| </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 p-0"> | |
| <div class="table-responsive"> | |
| <table class="table table-sm table-striped table-hover mb-0"> | |
| <thead> | |
| <tr> | |
| <th>代码</th> | |
| <th>名称</th> | |
| <th>风险评分</th> | |
| <th>风险等级</th> | |
| <th>权重</th> | |
| <th>主要风险</th> | |
| <th>操作</th> | |
| </tr> | |
| </thead> | |
| <tbody id="high-risk-stocks-table"> | |
| <!-- 高风险股票列表将在JS中动态填充 --> | |
| </tbody> | |
| </table> | |
| </div> | |
| </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 p-0"> | |
| <div class="table-responsive"> | |
| <table class="table table-sm table-striped table-hover mb-0"> | |
| <thead> | |
| <tr> | |
| <th>代码</th> | |
| <th>名称</th> | |
| <th>预警类型</th> | |
| <th>风险等级</th> | |
| <th>预警信息</th> | |
| </tr> | |
| </thead> | |
| <tbody id="risk-alerts-table"> | |
| <!-- 风险预警列表将在JS中动态填充 --> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {% endblock %} | |
| {% block scripts %} | |
| <script> | |
| $(document).ready(function() { | |
| // 个股风险分析表单提交 | |
| $('#stock-risk-form').submit(function(e) { | |
| e.preventDefault(); | |
| const stockCode = $('#stock-code').val().trim(); | |
| const marketType = $('#market-type').val(); | |
| if (!stockCode) { | |
| showError('请输入股票代码!'); | |
| return; | |
| } | |
| analyzeStockRisk(stockCode, marketType); | |
| }); | |
| // 分析投资组合风险按钮点击 | |
| $('#analyze-portfolio-btn').click(function() { | |
| analyzePortfolioRisk(); | |
| }); | |
| }); | |
| function analyzeStockRisk(stockCode, marketType) { | |
| $('#loading-panel').show(); | |
| $('#stock-risk-result').hide(); | |
| $('#portfolio-risk-result').hide(); | |
| $.ajax({ | |
| url: '/api/risk_analysis', | |
| type: 'POST', | |
| contentType: 'application/json', | |
| data: JSON.stringify({ | |
| stock_code: stockCode, | |
| market_type: marketType | |
| }), | |
| success: function(response) { | |
| $('#loading-panel').hide(); | |
| renderStockRiskAnalysis(response, stockCode); | |
| $('#stock-risk-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 analyzePortfolioRisk() { | |
| // 尝试从本地存储获取投资组合数据 | |
| const savedPortfolio = localStorage.getItem('portfolio'); | |
| if (!savedPortfolio) { | |
| showError('您的投资组合为空,请先添加股票到投资组合'); | |
| return; | |
| } | |
| const portfolio = JSON.parse(savedPortfolio); | |
| if (portfolio.length === 0) { | |
| showError('您的投资组合为空,请先添加股票到投资组合'); | |
| return; | |
| } | |
| $('#loading-panel').show(); | |
| $('#stock-risk-result').hide(); | |
| $('#portfolio-risk-result').hide(); | |
| $.ajax({ | |
| url: '/api/portfolio_risk', | |
| type: 'POST', | |
| contentType: 'application/json', | |
| data: JSON.stringify({ | |
| portfolio: portfolio | |
| }), | |
| success: function(response) { | |
| $('#loading-panel').hide(); | |
| renderPortfolioRiskAnalysis(response, portfolio); | |
| $('#portfolio-risk-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 renderStockRiskAnalysis(data, stockCode) { | |
| // 设置基本信息 | |
| $('#stock-name').text(data.stock_name || stockCode); | |
| $('#stock-info').text(data.industry || '未知行业'); | |
| // 设置风险等级 | |
| const riskScore = data.total_risk_score || 0; | |
| const riskLevel = data.risk_level || '未知'; | |
| const riskLevelBadgeClass = getRiskLevelBadgeClass(riskLevel); | |
| $('#risk-level-badge').text(riskLevel).removeClass().addClass(`badge ${riskLevelBadgeClass}`); | |
| // 设置风险预警 | |
| renderRiskAlerts(data.alerts || []); | |
| // 渲染风险仪表图 | |
| renderRiskGaugeChart(riskScore); | |
| // 渲染风险雷达图 | |
| renderRiskRadarChart(data); | |
| // 设置波动率风险 | |
| const volatilityRisk = data.volatility_risk || {}; | |
| $('#current-volatility').text(formatPercent(volatilityRisk.value || 0, 2)); | |
| const volatilityChange = volatilityRisk.change || 0; | |
| const volatilityChangeClass = volatilityChange >= 0 ? 'trend-up' : 'trend-down'; | |
| $('#volatility-change').text(formatPercent(volatilityChange, 2)).addClass(volatilityChangeClass); | |
| $('#volatility-risk-level').text(volatilityRisk.risk_level || '未知'); | |
| $('#volatility-comment').text('波动率反映价格波动的剧烈程度,高波动率意味着高风险'); | |
| // 设置趋势风险 | |
| const trendRisk = data.trend_risk || {}; | |
| $('#current-trend').text(trendRisk.trend || '未知'); | |
| $('#ma-relationship').text(trendRisk.ma_relationship || '未知'); | |
| $('#trend-risk-level').text(trendRisk.risk_level || '未知'); | |
| $('#trend-comment').text('下降趋势中的股票有更高的风险,特别是在空头排列时'); | |
| // 设置反转风险 | |
| const reversalRisk = data.reversal_risk || {}; | |
| $('#reversal-signals').text(reversalRisk.reversal_signals || 0); | |
| $('#reversal-direction').text(reversalRisk.direction || '未知'); | |
| $('#reversal-risk-level').text(reversalRisk.risk_level || '未知'); | |
| $('#reversal-comment').text('多个技术指标同时出现反转信号,意味着趋势可能即将改变'); | |
| // 设置成交量风险 | |
| const volumeRisk = data.volume_risk || {}; | |
| $('#volume-ratio').text(formatNumber(volumeRisk.volume_ratio || 0, 2)); | |
| $('#volume-pattern').text(volumeRisk.pattern || '未知'); | |
| $('#volume-risk-level').text(volumeRisk.risk_level || '未知'); | |
| $('#volume-comment').text('成交量异常变化常常预示价格波动,尤其是量价背离时'); | |
| // 渲染各个风险维度的图表 | |
| renderVolatilityChart([5.2, 3.8, 4.5, 7.2, 6.3]); | |
| renderTrendChart([110, 108, 106, 102, 98]); | |
| renderReversalChart([55, 60, 65, 72, 68]); | |
| renderVolumeChart([1.2, 0.8, 1.5, 2.8, 2.1]); | |
| } | |
| function renderPortfolioRiskAnalysis(data, portfolio) { | |
| // 设置基本信息 | |
| $('#portfolio-stock-count').text(portfolio.length); | |
| // 设置风险等级 | |
| const riskScore = data.portfolio_risk_score || 0; | |
| const riskLevel = data.risk_level || '未知'; | |
| const riskLevelBadgeClass = getRiskLevelBadgeClass(riskLevel); | |
| $('#portfolio-risk-level-badge').text(riskLevel).removeClass().addClass(`badge ${riskLevelBadgeClass}`); | |
| // 设置风险集中度 | |
| const riskConcentration = data.risk_concentration || {}; | |
| $('#industry-concentration').text(`${riskConcentration.max_industry || '未知'} (${formatPercent(riskConcentration.max_industry_weight || 0, 1)})`); | |
| $('#high-risk-concentration').text(formatPercent(riskConcentration.high_risk_weight || 0, 1)); | |
| // 设置风险预警 | |
| renderPortfolioRiskAlerts(data.alerts || []); | |
| // 渲染投资组合风险仪表图 | |
| renderPortfolioRiskGaugeChart(riskScore); | |
| // 渲染行业分布图 | |
| renderIndustryDistributionChart(portfolio); | |
| // 渲染高风险股票列表 | |
| renderHighRiskStocksTable(data.high_risk_stocks || []); | |
| // 渲染风险预警列表 | |
| renderRiskAlertsTable(data.alerts || []); | |
| } | |
| function renderRiskAlerts(alerts) { | |
| let html = ''; | |
| if (alerts.length === 0) { | |
| html = '<div class="alert alert-success">未发现显著风险因素</div>'; | |
| } else { | |
| alerts.forEach(alert => { | |
| const alertClass = getRiskAlertClass(alert.level); | |
| html += ` | |
| <div class="alert ${alertClass} mb-2"> | |
| <strong>${alert.type}风险:</strong> ${alert.message} | |
| </div> | |
| `; | |
| }); | |
| } | |
| $('#risk-alerts').html(html); | |
| } | |
| function renderPortfolioRiskAlerts(alerts) { | |
| let html = ''; | |
| if (alerts.length === 0) { | |
| html = '<div class="alert alert-success">投资组合风险均衡,未发现显著风险集中</div>'; | |
| } else { | |
| let alertCount = 0; | |
| alerts.forEach(alert => { | |
| if (alertCount < 3) { // 只显示前3条 | |
| const alertClass = getRiskAlertClass(alert.level); | |
| html += ` | |
| <div class="alert ${alertClass} mb-2"> | |
| <strong>${alert.stock_code}:</strong> ${alert.message} | |
| </div> | |
| `; | |
| alertCount++; | |
| } | |
| }); | |
| if (alerts.length > 3) { | |
| html += `<p class="text-muted small">还有 ${alerts.length - 3} 条风险预警,请查看下方详情表格</p>`; | |
| } | |
| } | |
| $('#portfolio-risk-alerts').html(html); | |
| } | |
| function renderRiskGaugeChart(score) { | |
| const options = { | |
| series: [score], | |
| chart: { | |
| height: 120, | |
| type: 'radialBar', | |
| }, | |
| plotOptions: { | |
| radialBar: { | |
| hollow: { | |
| size: '70%', | |
| }, | |
| dataLabels: { | |
| show: true, | |
| name: { | |
| show: false | |
| }, | |
| value: { | |
| fontSize: '16px', | |
| fontWeight: 'bold', | |
| offsetY: 5 | |
| } | |
| }, | |
| track: { | |
| background: '#f2f2f2' | |
| } | |
| } | |
| }, | |
| fill: { | |
| colors: [getRiskColor(score)] | |
| }, | |
| labels: ['风险分数'] | |
| }; | |
| const chart = new ApexCharts(document.querySelector("#risk-gauge-chart"), options); | |
| chart.render(); | |
| } | |
| function renderPortfolioRiskGaugeChart(score) { | |
| const options = { | |
| series: [score], | |
| chart: { | |
| height: 120, | |
| type: 'radialBar', | |
| }, | |
| plotOptions: { | |
| radialBar: { | |
| hollow: { | |
| size: '70%', | |
| }, | |
| dataLabels: { | |
| show: true, | |
| name: { | |
| show: false | |
| }, | |
| value: { | |
| fontSize: '16px', | |
| fontWeight: 'bold', | |
| offsetY: 5 | |
| } | |
| }, | |
| track: { | |
| background: '#f2f2f2' | |
| } | |
| } | |
| }, | |
| fill: { | |
| colors: [getRiskColor(score)] | |
| }, | |
| labels: ['风险分数'] | |
| }; | |
| const chart = new ApexCharts(document.querySelector("#portfolio-risk-gauge-chart"), options); | |
| chart.render(); | |
| } | |
| function renderRiskRadarChart(data) { | |
| const volatilityRisk = data.volatility_risk?.score || 0; | |
| const trendRisk = data.trend_risk?.score || 0; | |
| const reversalRisk = data.reversal_risk?.score || 0; | |
| const volumeRisk = data.volume_risk?.score || 0; | |
| const options = { | |
| series: [{ | |
| name: '风险评分', | |
| data: [volatilityRisk, trendRisk, reversalRisk, volumeRisk] | |
| }], | |
| chart: { | |
| height: 220, | |
| type: 'radar', | |
| toolbar: { | |
| show: false | |
| } | |
| }, | |
| xaxis: { | |
| categories: ['波动率风险', '趋势风险', '反转风险', '成交量风险'] | |
| }, | |
| fill: { | |
| opacity: 0.7, | |
| colors: ['#dc3545'] | |
| }, | |
| markers: { | |
| size: 4 | |
| }, | |
| title: { | |
| text: '风险雷达图', | |
| align: 'center', | |
| style: { | |
| fontSize: '14px' | |
| } | |
| } | |
| }; | |
| const chart = new ApexCharts(document.querySelector("#risk-radar-chart"), options); | |
| chart.render(); | |
| } | |
| function renderIndustryDistributionChart(portfolio) { | |
| // 根据投资组合计算行业分布 | |
| const industries = {}; | |
| let totalWeight = 0; | |
| portfolio.forEach(stock => { | |
| const industry = stock.industry || '未知'; | |
| const weight = stock.weight || 1; | |
| if (industries[industry]) { | |
| industries[industry] += weight; | |
| } else { | |
| industries[industry] = weight; | |
| } | |
| totalWeight += weight; | |
| }); | |
| const series = []; | |
| const labels = []; | |
| for (const industry in industries) { | |
| if (industries.hasOwnProperty(industry)) { | |
| series.push(industries[industry]); | |
| labels.push(industry); | |
| } | |
| } | |
| const options = { | |
| series: series, | |
| chart: { | |
| height: 220, | |
| type: 'pie', | |
| }, | |
| labels: labels, | |
| colors: ['#4e73df', '#1cc88a', '#36b9cc', '#f6c23e', '#e74a3b', '#858796', '#5a5c69', '#6f42c1'], | |
| legend: { | |
| position: 'bottom' | |
| }, | |
| tooltip: { | |
| y: { | |
| formatter: function(value) { | |
| return value + ' (' + ((value / totalWeight) * 100).toFixed(1) + '%)'; | |
| } | |
| } | |
| } | |
| }; | |
| const chart = new ApexCharts(document.querySelector("#industry-distribution-chart"), options); | |
| chart.render(); | |
| } | |
| function renderVolatilityChart(data) { | |
| const options = { | |
| series: [{ | |
| name: '波动率(%)', | |
| data: data | |
| }], | |
| chart: { | |
| height: 150, | |
| type: 'line', | |
| toolbar: { | |
| show: false | |
| } | |
| }, | |
| stroke: { | |
| curve: 'smooth', | |
| width: 3 | |
| }, | |
| xaxis: { | |
| labels: { | |
| show: false | |
| } | |
| }, | |
| yaxis: { | |
| labels: { | |
| formatter: function(val) { | |
| return val.toFixed(1) + '%'; | |
| }, | |
| style: { | |
| fontSize: '10px' | |
| } | |
| } | |
| }, | |
| colors: ['#dc3545'], | |
| tooltip: { | |
| y: { | |
| formatter: function(value) { | |
| return value.toFixed(2) + '%'; | |
| } | |
| } | |
| }, | |
| markers: { | |
| size: 3 | |
| } | |
| }; | |
| const chart = new ApexCharts(document.querySelector("#volatility-chart"), options); | |
| chart.render(); | |
| } | |
| function renderTrendChart(data) { | |
| const options = { | |
| series: [{ | |
| name: '价格', | |
| data: data | |
| }, { | |
| name: 'MA20', | |
| data: [109, 107, 105, 103, 101] | |
| }], | |
| chart: { | |
| height: 150, | |
| type: 'line', | |
| toolbar: { | |
| show: false | |
| } | |
| }, | |
| stroke: { | |
| curve: 'straight', | |
| width: [3, 2] | |
| }, | |
| xaxis: { | |
| labels: { | |
| show: false | |
| } | |
| }, | |
| yaxis: { | |
| labels: { | |
| formatter: function(val) { | |
| return val.toFixed(0); | |
| }, | |
| style: { | |
| fontSize: '10px' | |
| } | |
| } | |
| }, | |
| colors: ['#dc3545', '#007bff'], | |
| tooltip: { | |
| y: { | |
| formatter: function(value) { | |
| return value.toFixed(2); | |
| } | |
| } | |
| }, | |
| markers: { | |
| size: 3 | |
| }, | |
| legend: { | |
| show: false | |
| } | |
| }; | |
| const chart = new ApexCharts(document.querySelector("#trend-chart"), options); | |
| chart.render(); | |
| } | |
| function renderReversalChart(data) { | |
| const options = { | |
| series: [{ | |
| name: 'RSI', | |
| data: data | |
| }], | |
| chart: { | |
| height: 150, | |
| type: 'line', | |
| toolbar: { | |
| show: false | |
| } | |
| }, | |
| stroke: { | |
| curve: 'smooth', | |
| width: 3 | |
| }, | |
| xaxis: { | |
| labels: { | |
| show: false | |
| } | |
| }, | |
| yaxis: { | |
| min: 0, | |
| max: 100, | |
| labels: { | |
| formatter: function(val) { | |
| return val.toFixed(0); | |
| }, | |
| style: { | |
| fontSize: '10px' | |
| } | |
| } | |
| }, | |
| colors: ['#ffc107'], | |
| tooltip: { | |
| y: { | |
| formatter: function(value) { | |
| return value.toFixed(2); | |
| } | |
| } | |
| }, | |
| markers: { | |
| size: 3 | |
| }, | |
| annotations: { | |
| yaxis: [{ | |
| y: 70, | |
| borderColor: '#dc3545', | |
| label: { | |
| text: '超买', | |
| style: { | |
| color: '#fff', | |
| background: '#dc3545' | |
| } | |
| } | |
| }, { | |
| y: 30, | |
| borderColor: '#28a745', | |
| label: { | |
| text: '超卖', | |
| style: { | |
| color: '#fff', | |
| background: '#28a745' | |
| } | |
| } | |
| }] | |
| } | |
| }; | |
| const chart = new ApexCharts(document.querySelector("#reversal-chart"), options); | |
| chart.render(); | |
| } | |
| function renderVolumeChart(data) { | |
| const options = { | |
| series: [{ | |
| name: '成交量比率', | |
| data: data | |
| }], | |
| chart: { | |
| height: 150, | |
| type: 'bar', | |
| toolbar: { | |
| show: false | |
| } | |
| }, | |
| xaxis: { | |
| labels: { | |
| show: false | |
| } | |
| }, | |
| yaxis: { | |
| labels: { | |
| formatter: function(val) { | |
| return val.toFixed(1) + 'x'; | |
| }, | |
| style: { | |
| fontSize: '10px' | |
| } | |
| } | |
| }, | |
| colors: ['#4e73df'], | |
| tooltip: { | |
| y: { | |
| formatter: function(value) { | |
| return value.toFixed(2) + 'x'; | |
| } | |
| } | |
| }, | |
| plotOptions: { | |
| bar: { | |
| columnWidth: '50%' | |
| } | |
| }, | |
| dataLabels: { | |
| enabled: false | |
| } | |
| }; | |
| const chart = new ApexCharts(document.querySelector("#volume-chart"), options); | |
| chart.render(); | |
| } | |
| function renderHighRiskStocksTable(highRiskStocks) { | |
| let html = ''; | |
| if (highRiskStocks.length === 0) { | |
| html = '<tr><td colspan="7" class="text-center">未发现高风险股票</td></tr>'; | |
| } else { | |
| highRiskStocks.forEach(stock => { | |
| const riskScoreClass = getRiskScoreClass(stock.risk_score); | |
| const riskLevelBadgeClass = getRiskLevelBadgeClass(stock.risk_level); | |
| html += ` | |
| <tr> | |
| <td>${stock.stock_code}</td> | |
| <td>${stock.stock_name || '未知'}</td> | |
| <td><span class="badge ${riskScoreClass}">${stock.risk_score}</span></td> | |
| <td><span class="badge ${riskLevelBadgeClass}">${stock.risk_level}</span></td> | |
| <td>${formatPercent(stock.weight || 0, 1)}</td> | |
| <td>${stock.main_risk || '未知'}</td> | |
| <td> | |
| <a href="/stock_detail/${stock.stock_code}" class="btn btn-sm btn-outline-info me-1"> | |
| <i class="fas fa-chart-line"></i> | |
| </a> | |
| <a href="/risk_monitor?stock=${stock.stock_code}" class="btn btn-sm btn-outline-danger"> | |
| <i class="fas fa-exclamation-triangle"></i> | |
| </a> | |
| </td> | |
| </tr> | |
| `; | |
| }); | |
| } | |
| $('#high-risk-stocks-table').html(html); | |
| } | |
| function renderRiskAlertsTable(alerts) { | |
| let html = ''; | |
| if (alerts.length === 0) { | |
| html = '<tr><td colspan="5" class="text-center">暂无风险预警</td></tr>'; | |
| } else { | |
| alerts.forEach(alert => { | |
| const riskLevelBadgeClass = getRiskLevelBadgeClass(alert.level); | |
| html += ` | |
| <tr> | |
| <td>${alert.stock_code}</td> | |
| <td>${alert.stock_name || '未知'}</td> | |
| <td>${alert.type}</td> | |
| <td><span class="badge ${riskLevelBadgeClass}">${alert.level}</span></td> | |
| <td>${alert.message}</td> | |
| </tr> | |
| `; | |
| }); | |
| } | |
| $('#risk-alerts-table').html(html); | |
| } | |
| function getRiskLevelBadgeClass(level) { | |
| switch (level) { | |
| case '极高': | |
| return 'bg-danger'; | |
| case '高': | |
| return 'bg-warning text-dark'; | |
| case '中等': | |
| return 'bg-info text-dark'; | |
| case '低': | |
| return 'bg-success'; | |
| case '极低': | |
| return 'bg-secondary'; | |
| default: | |
| return 'bg-secondary'; | |
| } | |
| } | |
| function getRiskAlertClass(level) { | |
| switch (level) { | |
| case '高': | |
| return 'alert-danger'; | |
| case '中': | |
| return 'alert-warning'; | |
| case '低': | |
| return 'alert-info'; | |
| default: | |
| return 'alert-secondary'; | |
| } | |
| } | |
| function getRiskScoreClass(score) { | |
| if (score >= 80) return 'bg-danger'; | |
| if (score >= 60) return 'bg-warning text-dark'; | |
| if (score >= 40) return 'bg-info text-dark'; | |
| return 'bg-success'; | |
| } | |
| function getRiskColor(score) { | |
| if (score >= 80) return '#dc3545'; // 红色 | |
| if (score >= 60) return '#ffc107'; // 黄色 | |
| if (score >= 40) return '#17a2b8'; // 蓝色 | |
| return '#28a745'; // 绿色 | |
| } | |
| </script> | |
| {% endblock %} |