trading.ai/templates/dashboard.html
2025-08-14 10:06:19 +08:00

775 lines
27 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>AI选币系统</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #f8fafc;
color: #1e293b;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
}
/* 导航栏 */
.navbar {
background: white;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
padding: 1rem 0;
position: sticky;
top: 0;
z-index: 100;
}
.nav-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 2rem;
}
.logo {
font-size: 1.5rem;
font-weight: 700;
color: #3b82f6;
display: flex;
align-items: center;
gap: 8px;
}
.refresh-btn {
background: #3b82f6;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s;
}
.refresh-btn:hover {
background: #2563eb;
}
.refresh-btn:disabled {
background: #9ca3af;
cursor: not-allowed;
}
/* 主容器 */
.main-container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
/* 统计卡片 */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.stat-card {
background: white;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
text-align: center;
}
.stat-icon {
font-size: 2rem;
color: #3b82f6;
margin-bottom: 0.5rem;
}
.stat-value {
font-size: 2rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 0.25rem;
}
.stat-label {
color: #64748b;
font-size: 0.9rem;
}
/* 选币结果区域 */
.results-section {
background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.section-header {
padding: 1.5rem;
border-bottom: 1px solid #e2e8f0;
display: flex;
justify-content: space-between;
align-items: center;
}
.section-title {
font-size: 1.25rem;
font-weight: 600;
color: #1e293b;
display: flex;
align-items: center;
gap: 8px;
}
.last-update {
color: #64748b;
font-size: 0.875rem;
}
/* 时间分组 */
.time-group {
border-bottom: 1px solid #f1f5f9;
}
.time-group:last-child {
border-bottom: none;
}
.group-header {
background: #f8fafc;
padding: 1rem 1.5rem;
border-bottom: 1px solid #e2e8f0;
font-weight: 600;
color: #475569;
}
.group-coins {
padding: 1rem;
}
/* 币种网格 */
.coins-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 1rem;
}
.coin-card {
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 6px;
padding: 1rem;
transition: all 0.2s;
}
/* 多空信号样式 */
.long-signal {
border-left: 4px solid #10b981;
}
.short-signal {
border-left: 4px solid #ef4444;
}
.coin-symbol-container {
display: flex;
align-items: center;
gap: 0.5rem;
}
.signal-type-badge {
padding: 2px 6px;
border-radius: 3px;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
}
.long-badge {
background: #dcfce7;
color: #15803d;
border: 1px solid #16a34a;
}
.short-badge {
background: #fee2e2;
color: #b91c1c;
border: 1px solid #dc2626;
}
.coin-card:hover {
border-color: #3b82f6;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.1);
}
.coin-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.coin-symbol {
font-size: 1.125rem;
font-weight: 600;
color: #1e293b;
}
.coin-score {
background: #3b82f6;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.875rem;
font-weight: 600;
}
.coin-reason {
color: #475569;
margin-bottom: 1rem;
font-size: 0.875rem;
line-height: 1.5;
background: #f8fafc;
padding: 0.75rem;
border-radius: 4px;
border-left: 3px solid #3b82f6;
}
.price-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
margin-bottom: 0.75rem;
}
.price-item {
text-align: center;
}
.price-label {
font-size: 0.75rem;
color: #64748b;
margin-bottom: 0.25rem;
text-transform: uppercase;
font-weight: 500;
}
.price-value {
font-weight: 600;
font-size: 0.875rem;
color: #1e293b;
}
.price-positive {
color: #10b981;
}
.price-negative {
color: #ef4444;
}
.coin-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 0.75rem;
border-top: 1px solid #f1f5f9;
font-size: 0.75rem;
color: #64748b;
}
.strategy-short {
background: linear-gradient(45deg, #ff6b6b, #ee5a24);
color: white;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.7rem;
font-weight: 500;
}
.strategy-medium {
background: linear-gradient(45deg, #4834d4, #686de0);
color: white;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.7rem;
font-weight: 500;
}
.strategy-long {
background: linear-gradient(45deg, #00d2d3, #01a3a4);
color: white;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.7rem;
font-weight: 500;
}
.expiry-warning {
background: #fff3cd;
border: 1px solid #ffeaa7;
color: #856404;
padding: 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
margin-top: 0.5rem;
}
.time-remaining {
font-size: 0.75rem;
color: #64748b;
margin-top: 0.25rem;
}
.status-badge {
padding: 2px 6px;
border-radius: 3px;
font-size: 0.7rem;
font-weight: 500;
text-transform: uppercase;
}
.status-active { background: #10b981; color: white; }
.status-expired { background: #ef4444; color: white; }
.status-completed { background: #6366f1; color: white; }
/* 操作建议样式 */
.action-suggestion {
border-width: 2px;
border-style: solid;
}
/* 根据操作建议类型设置不同颜色 */
.action-buy-now {
background: #dcfce7;
border-color: #16a34a;
color: #15803d;
}
.action-buy-current {
background: #ddd6fe;
border-color: #7c3aed;
color: #6d28d9;
}
.action-buy-batch {
background: #e0f2fe;
border-color: #0284c7;
color: #0369a1;
}
.action-try-small {
background: #fef3c7;
border-color: #d97706;
color: #92400e;
}
.action-wait-pullback {
background: #fef9c3;
border-color: #ca8a04;
color: #a16207;
}
.action-wait-big-pullback {
background: #ffedd5;
border-color: #ea580c;
color: #c2410c;
}
.action-wait {
background: #f1f5f9;
border-color: #64748b;
color: #475569;
}
.action-careful {
background: #fee2e2;
border-color: #dc2626;
color: #b91c1c;
}
/* 加载状态 */
.loading {
display: none;
text-align: center;
padding: 3rem;
}
.loading i {
font-size: 2rem;
color: #3b82f6;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
margin-top: 0.75rem;
color: #64748b;
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 4rem 2rem;
}
.empty-state i {
font-size: 3rem;
color: #cbd5e1;
margin-bottom: 1rem;
}
.empty-state h3 {
font-size: 1.25rem;
color: #475569;
margin-bottom: 0.5rem;
}
.empty-state p {
color: #64748b;
}
/* 响应式 */
@media (max-width: 768px) {
.main-container {
padding: 1rem;
}
.nav-content {
padding: 0 1rem;
}
.coins-grid {
grid-template-columns: 1fr;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
</head>
<body>
<nav class="navbar">
<div class="nav-content">
<div class="logo">
<i class="fas fa-coins"></i>
AI选币系统
</div>
<button class="refresh-btn" onclick="runSelection()">
<i class="fas fa-sync-alt" id="refresh-icon"></i>
执行选币
</button>
</div>
</nav>
<div class="main-container">
<!-- 统计卡片 -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-chart-line"></i>
</div>
<div class="stat-value" id="total-selections">-</div>
<div class="stat-label">总选币数</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-play-circle"></i>
</div>
<div class="stat-value" id="active-selections">-</div>
<div class="stat-label">活跃选币</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-check-circle"></i>
</div>
<div class="stat-value" id="completed-selections">-</div>
<div class="stat-label">已完成</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-percentage"></i>
</div>
<div class="stat-value" id="avg-pnl">-%</div>
<div class="stat-label">平均收益</div>
</div>
</div>
<!-- 选币结果 -->
<div class="results-section">
<div class="section-header">
<div class="section-title">
<i class="fas fa-list"></i>
选币结果
</div>
<div class="last-update">
最后更新: {{ last_update }}
</div>
</div>
<div class="loading">
<i class="fas fa-spinner"></i>
<div class="loading-text">正在执行选币分析...</div>
</div>
<div id="selections-container">
{% if grouped_selections %}
{% for group_time, selections in grouped_selections.items() %}
<div class="time-group">
<div class="group-header">
{{ group_time }} ({{ selections|length }}个币种)
</div>
<div class="group-coins">
<div class="coins-grid" id="coins-grid-{{ loop.index }}">
{% for selection in selections %}
<div class="coin-card {{ 'short-signal' if selection.signal_type == 'SHORT' else 'long-signal' }}">
<div class="coin-header">
<div class="coin-symbol-container">
<div class="coin-symbol">{{ selection.symbol.replace('USDT', '') }}</div>
<div class="signal-type-badge {{ 'short-badge' if selection.signal_type == 'SHORT' else 'long-badge' }}">
{{ '做空' if selection.signal_type == 'SHORT' else '做多' }}
</div>
</div>
<div style="display: flex; gap: 0.5rem; align-items: center;">
<div class="coin-score">{{ "%.1f"|format(selection.score) }}</div>
<div class="strategy-badge strategy-{{ selection.strategy_type|replace('短线', 'short')|replace('中线', 'medium')|replace('长线', 'long') }}">
{{ selection.strategy_type }}
</div>
</div>
</div>
<!-- 操作建议 -->
<div class="action-suggestion" style="text-align: center; margin-bottom: 1rem; padding: 0.75rem; border-radius: 6px; font-weight: 600; font-size: 0.9rem;">
{{ selection.action_suggestion }}
</div>
<div class="coin-reason">
<strong>选择理由:</strong> {{ selection.reason }}
</div>
<div class="strategy-info" style="background: #f8fafc; padding: 0.5rem; border-radius: 4px; margin-bottom: 1rem; font-size: 0.8rem;">
<div style="display: flex; justify-content: space-between;">
<span>持仓周期: {{ selection.holding_period }}天</span>
<span>风险回报: 1:{{ "%.1f"|format(selection.risk_reward_ratio) }}</span>
</div>
{% if selection.expiry_time %}
<div class="time-remaining">
有效期至: {{ selection.expiry_time[:16] }}
</div>
{% endif %}
</div>
<div class="price-grid">
<div class="price-item">
<div class="price-label">
{% if selection.signal_type == 'SHORT' %}做空入场价{% else %}做多入场价{% endif %}
</div>
<div class="price-value">${{ "%.4f"|format(selection.entry_price) }}</div>
</div>
<div class="price-item">
<div class="price-label">当前市价</div>
{% if selection.current_price %}
<div class="price-value {{ 'price-positive' if (selection.signal_type == 'LONG' and selection.current_price < selection.entry_price) or (selection.signal_type == 'SHORT' and selection.current_price > selection.entry_price) else 'price-negative' }}">
${{ "%.4f"|format(selection.current_price) }}
{% if (selection.signal_type == 'LONG' and selection.current_price < selection.entry_price) or (selection.signal_type == 'SHORT' and selection.current_price > selection.entry_price) %}
<br><small>✓ 可入场</small>
{% else %}
<br><small>⚠ 价格不利</small>
{% endif %}
</div>
{% else %}
<div class="price-value">-</div>
{% endif %}
</div>
<div class="price-item">
<div class="price-label">止损位</div>
<div class="price-value price-negative">${{ "%.4f"|format(selection.stop_loss) }}</div>
</div>
<div class="price-item">
<div class="price-label">止盈位</div>
<div class="price-value price-positive">${{ "%.4f"|format(selection.take_profit) }}</div>
</div>
</div>
<div class="coin-footer">
<span>{{ selection.selection_time.split(' ')[1] }}</span>
<span class="status-badge status-{{ selection.status }}">{{ selection.status }}</span>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
<!-- 加载更多按钮 -->
{% if total_count >= current_limit %}
<div class="load-more-container" style="text-align: center; padding: 2rem;">
<button class="load-more-btn" onclick="loadMoreData()" style="background: #3b82f6; color: white; border: none; padding: 12px 24px; border-radius: 6px; font-weight: 500; cursor: pointer;">
<i class="fas fa-arrow-down"></i>
加载更多
</button>
</div>
{% endif %}
{% else %}
<div class="empty-state">
<i class="fas fa-search"></i>
<h3>暂无选币结果</h3>
<p>点击"执行选币"开始分析市场</p>
</div>
{% endif %}
</div>
</div>
</div>
<script>
// 当前分页状态
let currentOffset = {{ current_offset }};
let currentLimit = {{ current_limit }};
let isLoading = false;
// 加载更多数据
async function loadMoreData() {
if (isLoading) return;
isLoading = true;
const loadMoreBtn = document.querySelector('.load-more-btn');
const originalText = loadMoreBtn.innerHTML;
loadMoreBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 加载中...';
loadMoreBtn.disabled = true;
try {
const response = await fetch(`/api/selections?limit=${currentLimit}&offset=${currentOffset + currentLimit}`);
const data = await response.json();
if (data.status === 'success' && data.data.length > 0) {
// 这里应该追加新数据到现有列表
// 由于需要重新分组,我们简化为重新加载页面
const newUrl = new URL(window.location);
newUrl.searchParams.set('limit', currentLimit + data.data.length);
window.location.href = newUrl.toString();
} else {
loadMoreBtn.style.display = 'none';
}
} catch (error) {
console.error('加载更多数据失败:', error);
} finally {
isLoading = false;
loadMoreBtn.innerHTML = originalText;
loadMoreBtn.disabled = false;
}
}
async function loadStats() {
try {
const response = await fetch('/api/stats');
const data = await response.json();
if (data.status === 'success') {
document.getElementById('total-selections').textContent = data.data.total_selections;
document.getElementById('active-selections').textContent = data.data.active_selections;
document.getElementById('completed-selections').textContent = data.data.completed_selections;
document.getElementById('avg-pnl').textContent = data.data.avg_pnl + '%';
}
} catch (error) {
console.error('加载统计数据失败:', error);
}
}
// 执行选币
async function runSelection() {
const button = document.querySelector('.refresh-btn');
const icon = document.getElementById('refresh-icon');
const loading = document.querySelector('.loading');
const container = document.getElementById('selections-container');
button.disabled = true;
icon.classList.add('fa-spin');
loading.style.display = 'block';
container.style.display = 'none';
try {
const response = await fetch('/api/run_selection', {
method: 'POST'
});
const data = await response.json();
if (data.status === 'success') {
setTimeout(() => {
window.location.reload();
}, 2000);
} else {
alert('选币执行失败: ' + data.message);
loading.style.display = 'none';
container.style.display = 'block';
}
} catch (error) {
alert('选币执行失败: ' + error.message);
loading.style.display = 'none';
container.style.display = 'block';
} finally {
button.disabled = false;
icon.classList.remove('fa-spin');
}
}
// 页面初始化
document.addEventListener('DOMContentLoaded', function() {
loadStats();
styleActionSuggestions();
});
// 设置操作建议的样式
function styleActionSuggestions() {
const suggestions = document.querySelectorAll('.action-suggestion');
suggestions.forEach(function(element) {
const text = element.textContent.trim();
// 移除现有的样式类
element.classList.remove('action-buy-now', 'action-buy-current', 'action-buy-batch',
'action-try-small', 'action-wait-pullback', 'action-wait-big-pullback',
'action-wait', 'action-careful');
// 根据文本内容添加对应的样式类
if (text.includes('立即买入')) {
element.classList.add('action-buy-now');
} else if (text.includes('现价买入')) {
element.classList.add('action-buy-current');
} else if (text.includes('分批买入')) {
element.classList.add('action-buy-batch');
} else if (text.includes('小仓位试探')) {
element.classList.add('action-try-small');
} else if (text.includes('等待大幅回调')) {
element.classList.add('action-wait-big-pullback');
} else if (text.includes('等待回调买入')) {
element.classList.add('action-wait-pullback');
} else if (text.includes('谨慎观望')) {
element.classList.add('action-careful');
} else if (text.includes('暂时观望')) {
element.classList.add('action-wait');
} else {
element.classList.add('action-wait'); // 默认样式
}
});
}
// 定期刷新统计数据
setInterval(loadStats, 30000);
</script>
</body>
</html>