trading.ai/web/templates/signals.html
2025-10-04 09:26:00 +08:00

933 lines
45 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.

{% extends "base.html" %}
{% block title %}交易信号 - AI 智能选股大师{% endblock %}
{% block content %}
<!-- 页面标题和筛选 -->
<div class="row mb-4">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center">
<!-- 操作按钮组 -->
<div class="d-flex gap-2 align-items-center">
<button id="clearSignalsBtn" class="btn btn-outline-danger btn-sm">
<i class="fas fa-trash me-1"></i>清空信号
</button>
<button id="runAnalysisBtn" class="btn btn-outline-success btn-sm">
<i class="fas fa-play me-1"></i>立即分析
</button>
<!-- 分析状态显示 -->
<div id="analysisStatus" class="ms-3" style="display: none;">
<div class="d-flex align-items-center">
<div class="spinner-border spinner-border-sm text-primary me-2" role="status">
<span class="visually-hidden">分析中...</span>
</div>
<div class="analysis-status-text">
<small class="text-muted fw-bold">正在分析中...</small>
<div class="mt-1">
<div class="progress" style="width: 150px; height: 6px;">
<div id="analysisProgress" class="progress-bar bg-success" role="progressbar" style="width: 0%"></div>
</div>
<small id="analysisInfo" class="text-muted">启动中...</small>
</div>
</div>
</div>
</div>
</div>
<!-- 筛选表单 -->
<form method="GET" class="d-flex gap-2 align-items-center">
<select name="strategy" class="form-select form-select-sm">
<option value="">所有策略</option>
<option value="K线形态策略" {% if strategy_name == 'K线形态策略' %}selected{% endif %}>K线形态策略</option>
</select>
<select name="timeframe" class="form-select form-select-sm">
<option value="">所有周期</option>
<option value="daily" {% if timeframe == 'daily' or not timeframe %}selected{% endif %}>日线</option>
<option value="weekly" {% if timeframe == 'weekly' %}selected{% endif %}>周线</option>
</select>
<select name="days" class="form-select form-select-sm">
<option value="7" {% if days == 7 %}selected{% endif %}>最近7天</option>
<option value="15" {% if days == 15 %}selected{% endif %}>最近15天</option>
<option value="30" {% if days == 30 %}selected{% endif %}>最近30天</option>
<option value="90" {% if days == 90 %}selected{% endif %}>最近90天</option>
</select>
<select name="per_page" class="form-select form-select-sm">
<option value="20" {% if per_page == 20 %}selected{% endif %}>每页20条</option>
<option value="50" {% if per_page == 50 %}selected{% endif %}>每页50条</option>
<option value="100" {% if per_page == 100 %}selected{% endif %}>每页100条</option>
</select>
<button type="submit" class="btn btn-primary btn-sm text-nowrap">
<i class="fas fa-filter me-1"></i>筛选
</button>
</form>
</div>
</div>
</div>
<!-- 信号表格 -->
<div class="row">
<div class="col-12">
<div class="card fade-in">
<div class="card-header">
<h5 class="mb-0 text-primary fw-bold">
<i class="fas fa-table me-2"></i>详细信号数据
</h5>
</div>
<div class="card-body p-0">
{% if signals_grouped %}
<div class="table-container">
{% for scan_hour, signals in signals_grouped.items() %}
<!-- 扫描时间分组标题 -->
<div class="scan-date-header bg-light px-3 py-2">
<h6 class="mb-0 text-primary fw-bold">
<i class="fas fa-clock me-2"></i>扫描时间: {{ scan_hour.strftime('%Y-%m-%d %H:%M') }}
<span class="badge bg-primary ms-2">{{ signals|length }} 条信号</span>
</h6>
</div>
<!-- 该扫描时间下的信号表格 -->
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th style="width: 250px;">股票</th>
<th style="width: 150px;">策略</th>
<th style="width: 100px;">周期</th>
<th style="width: 500px;">时间线流程</th>
</tr>
</thead>
<tbody>
{% for signal in signals %}
<tr>
<td>
<div class="d-flex align-items-center">
<a href="https://xueqiu.com/S/{% if signal.stock_code.endswith('.SZ') %}SZ{% elif signal.stock_code.endswith('.SH') %}SH{% elif signal.stock_code.startswith('0') or signal.stock_code.startswith('3') %}SZ{% else %}SH{% endif %}{{ signal.stock_code.split('.')[0] }}" target="_blank" class="stock-code-link me-2">
<div class="stock-code-badge">{{ signal.stock_code }}</div>
</a>
<div class="stock-name text-truncate">{{ signal.stock_name or '未知' }}</div>
</div>
</td>
<td>
<span class="badge bg-primary">{{ signal.strategy_name }}</span>
{% if signal.new_high_confirmed %}
<div class="mt-1">
<small class="badge bg-success">创新高回踩确认</small>
</div>
{% endif %}
</td>
<td>
<span class="badge bg-{% if signal.timeframe == 'daily' %}primary{% else %}success{% endif %}">
{{ '日线' if signal.timeframe == 'daily' else '周线' }}
</span>
</td>
<td style="min-width: 500px; padding: 12px 20px;">
{% if signal.new_high_confirmed %}
<!-- 新格式:优化的创新高回踩确认时间线 -->
<div class="timeline-enhanced-wide">
<div class="timeline-flow">
<div class="timeline-step-wide">
<div class="timeline-marker-wide pattern-marker">
<i class="fas fa-chart-line"></i>
</div>
<div class="timeline-content-wide">
<div class="timeline-title">📅 模式识别</div>
<div class="timeline-date-wide">{{ signal.signal_date | datetime_format('%m-%d') }}</div>
<div class="timeline-info">突破价: {{ signal.breakout_price | currency }}元</div>
</div>
</div>
<div class="timeline-arrow">
<i class="fas fa-arrow-right"></i>
</div>
{% if signal.new_high_date %}
<div class="timeline-step-wide">
<div class="timeline-marker-wide new-high-marker">
<i class="fas fa-rocket"></i>
</div>
<div class="timeline-content-wide">
<div class="timeline-title">🚀 创新高</div>
<div class="timeline-date-wide highlight">{{ signal.new_high_date | datetime_format('%m-%d') }}</div>
{% if signal.new_high_price %}
<div class="timeline-info">{{ signal.new_high_price | currency }}元</div>
{% endif %}
</div>
</div>
<div class="timeline-arrow">
<i class="fas fa-arrow-right"></i>
</div>
{% endif %}
{% if signal.confirmation_date %}
<div class="timeline-step-wide">
<div class="timeline-marker-wide confirmation-marker">
<i class="fas fa-check-circle"></i>
</div>
<div class="timeline-content-wide">
<div class="timeline-title">✅ 回踩确认</div>
<div class="timeline-date-wide highlight">{{ signal.confirmation_date | datetime_format('%m-%d') }}</div>
{% if signal.confirmation_days %}
<div class="timeline-info">用时{{ signal.confirmation_days }}天</div>
{% endif %}
</div>
</div>
{% endif %}
</div>
<!-- 技术指标概要 -->
<div class="timeline-summary mt-2">
<span class="badge bg-{% if signal.breakout_pct and signal.breakout_pct > 3 %}success{% elif signal.breakout_pct and signal.breakout_pct > 1 %}warning{% else %}secondary{% endif %} me-1 badge-sm">
突破{{ signal.breakout_pct | percentage }}
</span>
<span class="badge bg-{% if signal.final_yang_entity_ratio and signal.final_yang_entity_ratio > 0.6 %}success{% elif signal.final_yang_entity_ratio and signal.final_yang_entity_ratio > 0.4 %}warning{% else %}secondary{% endif %} me-1 badge-sm">
实体{{ signal.final_yang_entity_ratio | percentage }}
</span>
<span class="badge bg-{% if signal.above_ema20 %}success{% else %}secondary{% endif %} me-1 badge-sm">
EMA20{{ '✅' if signal.above_ema20 else '❌' }}
</span>
{% if signal.pullback_distance %}
<span class="badge bg-{% if signal.pullback_distance > -2 %}success{% elif signal.pullback_distance > -5 %}warning{% else %}danger{% endif %} me-1 badge-sm">
回踩{{ signal.pullback_distance | currency }}%
</span>
{% endif %}
<span class="badge bg-light text-muted me-1 badge-sm">
{{ signal.scan_time | datetime_format('%m-%d %H:%M') }}
</span>
</div>
</div>
{% else %}
<!-- 旧格式:兼容显示 -->
<div class="timeline-simple">
<div class="text-muted">{{ signal.signal_date | datetime_format('%Y-%m-%d') }}</div>
<div class="text-success fw-bold">{{ signal.breakout_price | currency }}元</div>
</div>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endfor %}
</div>
<!-- 分页 -->
{% if total_pages > 1 %}
<nav aria-label="信号分页" class="mt-4">
<ul class="pagination justify-content-center">
<!-- 上一页 -->
<li class="page-item {% if not has_prev %}disabled{% endif %}">
<a class="page-link" href="{% if has_prev %}{{ url_for('signals', page=current_page-1, strategy=strategy_name, timeframe=timeframe, days=days, per_page=per_page) }}{% else %}#{% endif %}">
<i class="fas fa-chevron-left"></i> 上一页
</a>
</li>
<!-- 页码 -->
{% set start_page = [1, current_page - 2]|max %}
{% set end_page = [total_pages, current_page + 2]|min %}
{% if start_page > 1 %}
<li class="page-item">
<a class="page-link" href="{{ url_for('signals', page=1, strategy=strategy_name, timeframe=timeframe, days=days, per_page=per_page) }}">1</a>
</li>
{% if start_page > 2 %}
<li class="page-item disabled"><span class="page-link">...</span></li>
{% endif %}
{% endif %}
{% for page_num in range(start_page, end_page + 1) %}
<li class="page-item {% if page_num == current_page %}active{% endif %}">
<a class="page-link" href="{{ url_for('signals', page=page_num, strategy=strategy_name, timeframe=timeframe, days=days, per_page=per_page) }}">{{ page_num }}</a>
</li>
{% endfor %}
{% if end_page < total_pages %}
{% if end_page < total_pages - 1 %}
<li class="page-item disabled"><span class="page-link">...</span></li>
{% endif %}
<li class="page-item">
<a class="page-link" href="{{ url_for('signals', page=total_pages, strategy=strategy_name, timeframe=timeframe, days=days, per_page=per_page) }}">{{ total_pages }}</a>
</li>
{% endif %}
<!-- 下一页 -->
<li class="page-item {% if not has_next %}disabled{% endif %}">
<a class="page-link" href="{% if has_next %}{{ url_for('signals', page=current_page+1, strategy=strategy_name, timeframe=timeframe, days=days, per_page=per_page) }}{% else %}#{% endif %}">
下一页 <i class="fas fa-chevron-right"></i>
</a>
</li>
</ul>
</nav>
{% endif %}
{% else %}
<div class="empty-state">
<i class="fas fa-signal"></i>
<h4>暂无信号数据</h4>
<p>请尝试调整筛选条件或稍后再试</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- 自定义模态框 -->
<!-- 密钥输入模态框 -->
<div class="modal fade" id="keyModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title">
<i class="fas fa-key me-2"></i>操作验证
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<div class="text-center mb-3">
<i class="fas fa-shield-alt text-primary" style="font-size: 3rem;"></i>
</div>
<p class="text-center text-muted mb-4">请输入操作验证密钥以继续</p>
<div class="form-group">
<label for="operationKey" class="form-label fw-bold">验证密钥</label>
<input type="password" class="form-control form-control-lg text-center"
id="operationKey" placeholder="请输入验证密钥">
<div class="invalid-feedback" id="keyError"></div>
</div>
</div>
<div class="modal-footer border-0 pt-0">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-1"></i>取消
</button>
<button type="button" class="btn btn-primary" id="confirmKey">
<i class="fas fa-check me-1"></i>确认
</button>
</div>
</div>
</div>
</div>
<!-- 确认操作模态框 -->
<div class="modal fade" id="confirmModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<div class="modal-header" id="confirmHeader">
<h5 class="modal-title" id="confirmTitle">
<i class="fas fa-question-circle me-2"></i>确认操作
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<div class="text-center mb-3">
<i id="confirmIcon" class="fas fa-exclamation-triangle text-warning" style="font-size: 3rem;"></i>
</div>
<p class="text-center" id="confirmMessage">确认要执行此操作吗?</p>
<div id="confirmDetails" class="mt-3" style="display: none;"></div>
</div>
<div class="modal-footer border-0 pt-0">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-1"></i>取消
</button>
<button type="button" class="btn" id="confirmAction">
<i class="fas fa-check me-1"></i>确认
</button>
</div>
</div>
</div>
</div>
<!-- 输入参数模态框 -->
<div class="modal fade" id="inputModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-info text-white">
<h5 class="modal-title" id="inputTitle">
<i class="fas fa-edit me-2"></i>参数设置
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<div class="form-group">
<label for="inputValue" class="form-label fw-bold" id="inputLabel">请输入参数</label>
<input type="number" class="form-control form-control-lg text-center"
id="inputValue" placeholder="">
<div class="form-text text-muted" id="inputHelp"></div>
<div class="invalid-feedback" id="inputError"></div>
</div>
</div>
<div class="modal-footer border-0 pt-0">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-1"></i>取消
</button>
<button type="button" class="btn btn-info" id="confirmInput">
<i class="fas fa-check me-1"></i>确认
</button>
</div>
</div>
</div>
</div>
<!-- 通知提示模态框 -->
<div class="modal fade" id="alertModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<div class="modal-header" id="alertHeader">
<h5 class="modal-title" id="alertTitle">
<i class="fas fa-info-circle me-2"></i>提示
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4 text-center">
<div class="mb-3">
<i id="alertIcon" class="fas fa-info-circle text-info" style="font-size: 3rem;"></i>
</div>
<p id="alertMessage" class="mb-0">操作完成</p>
<div id="alertDetails" class="mt-3 text-start" style="display: none;"></div>
</div>
<div class="modal-footer border-0 pt-0 justify-content-center">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" id="alertConfirm">
<i class="fas fa-check me-1"></i>确定
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
$(document).ready(function() {
// 表格行点击高亮
$('tbody tr').on('click', function() {
$(this).toggleClass('table-active');
});
// 工具提示
$('[data-bs-toggle="tooltip"]').tooltip();
// 如果没有timeframe参数自动设置为daily并重新加载
const urlParams = new URLSearchParams(window.location.search);
if (!urlParams.has('timeframe')) {
urlParams.set('timeframe', 'daily');
window.location.search = urlParams.toString();
}
// 全局变量用于存储操作回调
let currentOperationCallback = null;
let currentInputCallback = null;
let pendingOperationKey = null; // 存储密钥
// ========== 自定义模态框函数 ==========
// 显示验证密钥模态框
function showKeyModal(title, message, callback) {
$('#keyModal .modal-title').text(title);
$('#keyModal p').text(message);
$('#operationKey').val('').removeClass('is-invalid');
$('#keyError').text('');
currentOperationCallback = callback;
new bootstrap.Modal($('#keyModal')).show();
}
// 显示确认操作模态框
function showConfirmModal(title, message, type = 'warning', callback) {
const $header = $('#confirmHeader');
const $icon = $('#confirmIcon');
const $action = $('#confirmAction');
// 设置标题
$('#confirmTitle').html(`<i class="fas fa-question-circle me-2"></i>${title}`);
$('#confirmMessage').text(message);
// 根据类型设置样式
$header.removeClass('bg-danger bg-warning bg-info').addClass(`bg-${type}`);
$icon.removeClass('text-danger text-warning text-info').addClass(`text-${type}`);
$action.removeClass('btn-danger btn-warning btn-info').addClass(`btn-${type}`);
// 设置图标
const icons = {
'danger': 'fas fa-exclamation-triangle',
'warning': 'fas fa-exclamation-triangle',
'info': 'fas fa-info-circle'
};
$icon.attr('class', `${icons[type] || icons.warning} text-${type}`).css('font-size', '3rem');
currentOperationCallback = callback;
new bootstrap.Modal($('#confirmModal')).show();
}
// 显示输入参数模态框
function showInputModal(title, label, placeholder, helpText, callback) {
$('#inputTitle').html(`<i class="fas fa-edit me-2"></i>${title}`);
$('#inputLabel').text(label);
$('#inputValue').attr('placeholder', placeholder).val(placeholder).removeClass('is-invalid');
$('#inputHelp').text(helpText);
$('#inputError').text('');
currentInputCallback = callback;
new bootstrap.Modal($('#inputModal')).show();
}
// 显示通知提示模态框
function showAlertModal(title, message, type = 'info', details = null) {
const $header = $('#alertHeader');
const $icon = $('#alertIcon');
const $confirm = $('#alertConfirm');
// 设置标题
$('#alertTitle').html(`<i class="fas fa-info-circle me-2"></i>${title}`);
$('#alertMessage').text(message);
// 根据类型设置样式
const typeClass = type === 'error' ? 'error' : type;
$header.removeClass('bg-success bg-error bg-warning bg-info').addClass(`bg-${typeClass}`);
$icon.removeClass('text-success text-error text-warning text-info').addClass(`text-${typeClass}`);
// 设置图标
const icons = {
'success': 'fas fa-check-circle',
'error': 'fas fa-exclamation-circle',
'warning': 'fas fa-exclamation-triangle',
'info': 'fas fa-info-circle'
};
$icon.attr('class', `${icons[type] || icons.info} text-${typeClass}`).css('font-size', '3rem');
// 显示详情
if (details) {
$('#alertDetails').html(details).show();
} else {
$('#alertDetails').hide();
}
new bootstrap.Modal($('#alertModal')).show();
}
// ========== 模态框事件处理 ==========
// 验证密钥确认
$(document).on('click', '#confirmKey', function() {
const key = $('#operationKey').val().trim();
if (!key) {
$('#operationKey').addClass('is-invalid');
$('#keyError').text('请输入验证密钥');
return;
}
try {
const modalInstance = bootstrap.Modal.getInstance($('#keyModal')[0]);
if (modalInstance) {
modalInstance.hide();
} else {
$('#keyModal').modal('hide');
}
} catch (e) {
$('#keyModal').hide();
}
if (window.handleKeyConfirm) {
setTimeout(() => {
window.handleKeyConfirm(key);
}, 300);
}
});
// 确认操作
$(document).on('click', '#confirmAction', function() {
try {
const modalInstance = bootstrap.Modal.getInstance($('#confirmModal')[0]);
if (modalInstance) {
modalInstance.hide();
} else {
$('#confirmModal').modal('hide');
}
} catch (e) {
$('#confirmModal').hide();
}
if (window.handleConfirmAction) {
setTimeout(() => {
window.handleConfirmAction();
}, 300);
}
});
// 输入参数确认
$(document).on('click', '#confirmInput', function() {
const value = $('#inputValue').val().trim();
// 如果输入为空使用placeholder作为默认值
const finalValue = value || $('#inputValue').attr('placeholder');
if (!finalValue) {
$('#inputValue').addClass('is-invalid');
$('#inputError').text('请输入有效的参数值');
return;
}
try {
const modalInstance = bootstrap.Modal.getInstance($('#inputModal')[0]);
if (modalInstance) {
modalInstance.hide();
} else {
$('#inputModal').modal('hide');
}
} catch (e) {
$('#inputModal').hide();
}
if (window.handleInputConfirm) {
setTimeout(() => {
window.handleInputConfirm(finalValue);
}, 300);
}
});
// 回车键支持
$('#operationKey').on('keypress', function(e) {
if (e.which === 13) $('#confirmKey').click();
});
$('#inputValue').on('keypress', function(e) {
if (e.which === 13) $('#confirmInput').click();
});
// 清空验证错误
$('#operationKey').on('input', function() {
$(this).removeClass('is-invalid');
$('#keyError').text('');
});
$('#inputValue').on('input', function() {
$(this).removeClass('is-invalid');
$('#inputError').text('');
});
// 清空信号按钮
$('#clearSignalsBtn').on('click', function() {
// 显示密钥输入模态框
$('#keyModal .modal-title').text('操作验证');
$('#keyModal p').text('请输入操作验证密钥以清空信号数据');
$('#operationKey').val('').removeClass('is-invalid');
$('#keyError').text('');
// 设置全局标识
window.currentOperation = 'clear_signals';
new bootstrap.Modal($('#keyModal')).show();
});
// 全局处理密钥确认
window.handleKeyConfirm = function(operationKey) {
if (window.currentOperation === 'clear_signals') {
// 显示确认模态框
$('#confirmModal .modal-title').html('<i class="fas fa-question-circle me-2"></i>确认清空信号');
$('#confirmMessage').text('确认要清空最近7天的信号数据吗此操作不可恢复');
$('#confirmHeader').removeClass('bg-danger bg-warning bg-info').addClass('bg-danger');
$('#confirmIcon').removeClass('text-danger text-warning text-info').addClass('text-danger');
$('#confirmAction').removeClass('btn-danger btn-warning btn-info').addClass('btn-danger');
$('#confirmIcon').attr('class', 'fas fa-exclamation-triangle text-danger').css('font-size', '3rem');
// 存储密钥
window.pendingOperationKey = operationKey;
window.currentOperation = 'confirm_clear_signals';
new bootstrap.Modal($('#confirmModal')).show();
} else if (window.currentOperation === 'run_analysis') {
// 显示参数输入模态框
$('#inputModal .modal-title').html('<i class="fas fa-edit me-2"></i>设置分析参数');
$('#inputLabel').text('扫描股票数量');
$('#inputValue').attr('placeholder', '200').val('200').removeClass('is-invalid');
$('#inputHelp').text('请输入要扫描的股票数量 (范围: 1-1000)');
$('#inputError').text('');
// 存储密钥
window.pendingOperationKey = operationKey;
window.currentOperation = 'input_analysis_params';
new bootstrap.Modal($('#inputModal')).show();
}
};
// 全局处理确认操作
window.handleConfirmAction = function() {
if (window.currentOperation === 'confirm_clear_signals' && window.pendingOperationKey) {
executeClearSignals(window.pendingOperationKey);
window.currentOperation = null;
window.pendingOperationKey = null;
} else if (window.currentOperation === 'confirm_run_analysis' && window.pendingOperationKey && window.pendingStockCount) {
executeRunAnalysis(window.pendingOperationKey, window.pendingStockCount);
window.currentOperation = null;
window.pendingOperationKey = null;
window.pendingStockCount = null;
}
};
// 全局处理输入参数确认
window.handleInputConfirm = function(inputValue) {
if (window.currentOperation === 'input_analysis_params' && window.pendingOperationKey) {
const count = parseInt(inputValue) || 200;
if (count <= 0 || count > 1000) {
showAlertModal('参数错误', '股票数量必须在 1-1000 之间', 'warning');
return;
}
// 显示确认模态框
$('#confirmModal .modal-title').html('<i class="fas fa-question-circle me-2"></i>确认分析');
$('#confirmMessage').text(`确认要立即分析 ${count} 只热门股票吗?分析可能需要几分钟时间`);
$('#confirmHeader').removeClass('bg-danger bg-warning bg-info').addClass('bg-info');
$('#confirmIcon').removeClass('text-danger text-warning text-info').addClass('text-info');
$('#confirmAction').removeClass('btn-danger btn-warning btn-info').addClass('btn-info');
$('#confirmIcon').attr('class', 'fas fa-info-circle text-info').css('font-size', '3rem');
// 存储参数
window.pendingStockCount = count;
window.currentOperation = 'confirm_run_analysis';
new bootstrap.Modal($('#confirmModal')).show();
}
};
// 执行清空信号的实际操作
function executeClearSignals(operationKey) {
const btn = $('#clearSignalsBtn');
const originalText = btn.html();
btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin me-1"></i>清空中...');
$.ajax({
url: '/api/clear_signals',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
operation_key: operationKey,
days: 7,
strategy_name: ''
}),
success: function(response) {
if (response.success) {
showAlertModal(
'清空成功',
`成功删除了 ${response.deleted_count} 条信号记录`,
'success'
);
setTimeout(() => location.reload(), 2000);
} else {
// 检查是否是密钥验证失败
if (response.error && response.error.includes('密钥验证失败')) {
showAlertModal('验证失败', '操作密钥验证失败,请检查密钥是否正确', 'warning');
} else {
showAlertModal('清空失败', response.error || '未知错误', 'error');
}
}
},
error: function(xhr, status, error) {
let errorMessage = '网络错误或服务器异常';
// 检查HTTP状态码
if (xhr.status === 401 || xhr.status === 403) {
errorMessage = '操作密钥验证失败,请检查密钥是否正确';
} else if (xhr.responseJSON && xhr.responseJSON.error) {
errorMessage = xhr.responseJSON.error;
}
showAlertModal('操作失败', errorMessage, 'error');
console.error('清空信号失败:', error, xhr);
},
complete: function() {
btn.prop('disabled', false).html(originalText);
}
});
}
// 执行立即分析的实际操作
function executeRunAnalysis(operationKey, stockCount) {
$.ajax({
url: '/api/run_analysis',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
operation_key: operationKey,
stock_count: stockCount
}),
success: function(response) {
if (response.success) {
// 重置刷新标志
hasRefreshedAfterCompletion = false;
// 立即开始状态轮询
checkAnalysisStatus();
// 设置备用刷新机制10分钟后强制刷新以防状态检查失败
setTimeout(() => {
if (!hasRefreshedAfterCompletion) {
console.log('备用刷新机制触发:分析可能已完成但状态检查失败');
showAlertModal(
'刷新页面',
'分析可能已完成,正在刷新页面显示最新结果',
'info'
);
setTimeout(() => location.reload(), 1500);
}
}, 600000); // 10分钟
const details = `
<div class="text-start">
<p><strong>扫描股票:</strong> ${response.stock_count} 只</p>
<p><strong>启动时间:</strong> ${response.start_time}</p>
<p><strong>状态:</strong> 页面将自动显示分析进度</p>
</div>
`;
showAlertModal(
'分析任务已启动',
'正在后台扫描股票,请稍候...',
'success',
details
);
} else {
// 检查是否是密钥验证失败
if (response.error && response.error.includes('密钥验证失败')) {
showAlertModal('验证失败', '操作密钥验证失败,请检查密钥是否正确', 'warning');
} else {
showAlertModal('启动失败', response.error || '未知错误', 'error');
}
}
},
error: function(xhr, status, error) {
let errorMessage = '网络错误或服务器异常';
// 检查HTTP状态码
if (xhr.status === 401 || xhr.status === 403) {
errorMessage = '操作密钥验证失败,请检查密钥是否正确';
} else if (xhr.responseJSON && xhr.responseJSON.error) {
errorMessage = xhr.responseJSON.error;
}
showAlertModal('操作失败', errorMessage, 'error');
console.error('启动分析失败:', error, xhr);
}
});
}
// 全局变量
let analysisStatusInterval = null;
// 检查分析状态
function checkAnalysisStatus() {
$.ajax({
url: '/api/analysis_status',
method: 'GET',
success: function(response) {
if (response.success && response.data) {
updateAnalysisStatusUI(response.data);
}
},
error: function(xhr, status, error) {
console.error('获取分析状态失败:', error);
}
});
}
// 用于防止重复刷新的标志
let hasRefreshedAfterCompletion = false;
// 更新分析状态UI
function updateAnalysisStatusUI(status) {
const $statusDiv = $('#analysisStatus');
const $progress = $('#analysisProgress');
const $info = $('#analysisInfo');
const $runBtn = $('#runAnalysisBtn');
if (status.is_running) {
// 显示分析状态
$statusDiv.show();
$runBtn.prop('disabled', true).html('<i class="fas fa-cog fa-spin me-1"></i>分析中...');
// 更新进度条
$progress.css('width', status.progress + '%');
// 更新信息文本
const runningTime = Math.floor(status.running_time / 60);
const remainingTime = Math.max(0, Math.ceil((status.stock_count / 200 * 5) - (status.running_time / 60)));
if (status.progress >= 95) {
$info.text(`分析即将完成... ${status.progress}%`);
} else {
$info.text(`进度: ${status.progress}% | 已运行: ${runningTime}分钟 | 预计剩余: ${remainingTime}分钟`);
}
// 开始轮询(如果还没开始)
if (!analysisStatusInterval) {
analysisStatusInterval = setInterval(checkAnalysisStatus, 3000); // 每3秒检查一次
}
} else {
// 分析已完成,隐藏分析状态
$statusDiv.hide();
$runBtn.prop('disabled', false).html('<i class="fas fa-play me-1"></i>立即分析');
// 停止轮询
if (analysisStatusInterval) {
clearInterval(analysisStatusInterval);
analysisStatusInterval = null;
}
// 如果是刚完成分析且还没刷新过,则刷新页面
if (status.progress >= 95 && !hasRefreshedAfterCompletion) {
hasRefreshedAfterCompletion = true;
// 显示完成提示
$statusDiv.show();
$statusDiv.html(`
<div class="d-flex align-items-center">
<i class="fas fa-check-circle text-success me-2"></i>
<small class="text-success fw-bold">分析完成!正在加载新结果...</small>
</div>
`);
// 显示成功完成模态框
showAlertModal(
'分析完成',
'股票扫描分析已完成,页面即将刷新显示最新信号',
'success'
);
setTimeout(() => {
location.reload();
}, 2000);
}
}
}
// 立即分析按钮
$('#runAnalysisBtn').on('click', function() {
// 显示密钥输入模态框
$('#keyModal .modal-title').text('操作验证');
$('#keyModal p').text('请输入操作验证密钥以启动市场分析');
$('#operationKey').val('').removeClass('is-invalid');
$('#keyError').text('');
// 设置全局标识
window.currentOperation = 'run_analysis';
new bootstrap.Modal($('#keyModal')).show();
});
// 页面加载时检查是否有正在运行的分析
checkAnalysisStatus();
// 页面可见性改变时重新检查分析状态(用户切换回标签页时)
document.addEventListener('visibilitychange', function() {
if (!document.hidden) {
checkAnalysisStatus();
}
});
});
</script>
{% endblock %}