skyfffire 3 days ago
parent
commit
c35fe95e62
2 changed files with 95 additions and 203 deletions
  1. 0 137
      DASHBOARD_EVENTS_FEATURE.md
  2. 95 66
      src/leadlag/templates/dashboard.html

+ 0 - 137
DASHBOARD_EVENTS_FEATURE.md

@@ -1,137 +0,0 @@
-# 交易事件可视化功能说明
-
-## 📊 功能概述
-
-已成功实现**方案1(时间轴标记)+ 简化表格**,将交易事件直观地展示在价格图表和BPS图表上。
-
-## ✨ 新增功能
-
-### 1. 图表标记系统
-
-在价格走势图和BPS图表上,所有交易事件都会以**可视化标记**的形式显示:
-
-#### 标记类型和颜色:
-- 📈 **开多仓** (open_long): 绿色向上箭头
-- 📉 **开空仓** (open_short): 红色向下箭头  
-- 🔼 **平多仓** (close_long): 蓝色三角形
-- 🔽 **平空仓** (close_short): 紫色三角形
-
-#### 标记特性:
-- **垂直虚线**: 标识事件发生的精确时间
-- **图标标记**: 在价格点上显示事件类型
-- **颜色编码**: 快速识别事件类型
-- **可点击**: 点击标记查看详细信息
-
-### 2. 交互式事件详情
-
-点击图表上的任何事件标记,会弹出详细信息窗口,显示:
-- ⏰ 事件时间
-- 💱 交易对
-- 💰 成交价格
-- 📊 交易数量
-- 🎯 交易方向
-- 📈 Ask/Bid价差
-- 🔗 交易哈希
-
-### 3. 智能Tooltip提示
-
-当鼠标悬停在图表上时:
-- 显示当前时间点的价格数据
-- **自动检测**附近5秒内的交易事件
-- 在tooltip中显示事件信息
-- 无需点击即可快速查看
-
-### 4. 简化的事件表格
-
-表格现在只显示**最近10条**交易事件:
-- 减少页面滚动
-- 提高加载速度
-- 保留关键信息
-- 显示总事件数量
-- 提示用户查看图表获取完整信息
-
-### 5. 视觉提示
-
-在事件记录区域添加了醒目的提示框:
-```
-💡 提示:图表上的标记点显示所有交易事件。点击标记可查看详情。
-📈开多 | 📉开空 | 🔼平多 | 🔽平空
-```
-
-## 🎯 使用方法
-
-### 查看事件标记
-1. 打开Dashboard
-2. 选择时间范围和交易对
-3. 在价格图表和BPS图表上查看彩色标记
-4. 标记会随图表缩放和平移自动调整
-
-### 查看事件详情
-**方法1**: 点击图表上的标记点
-**方法2**: 鼠标悬停查看tooltip
-**方法3**: 查看下方的事件表格(最近10条)
-
-### 图表操作
-- **缩放**: 鼠标滚轮或拖动底部滑块
-- **平移**: 按住鼠标拖动
-- **重置**: 点击"重置缩放"按钮
-- **关闭详情**: 点击模态框外部或"关闭"按钮
-
-## 🔧 技术实现
-
-### 核心函数
-- `addEventMarkersToCharts()`: 在图表上添加事件标记
-- `showEventDetails()`: 显示事件详情模态框
-- `getEventIcon()`: 获取事件类型对应的图标
-- `formatEventType()`: 格式化事件类型显示
-
-### 数据流
-1. 从API加载交易事件数据
-2. 存储到 `tradingEventsData` 全局变量
-3. 在图表渲染后调用 `addEventMarkersToCharts()`
-4. 使用ECharts的 `markLine` 和 `markPoint` 功能
-5. 绑定点击事件监听器
-
-### 同步机制
-- 价格图表和BPS图表同时添加标记
-- 缩略图选择后自动重新添加标记
-- 时间范围变化后自动更新标记
-
-## 📈 优势
-
-1. **直观性**: 一眼看出交易时机与价格的关系
-2. **完整性**: 显示所有交易事件,不受表格限制
-3. **交互性**: 点击查看详情,悬停快速预览
-4. **性能**: 简化表格提高页面加载速度
-5. **美观性**: 彩色标记和图标增强视觉效果
-
-## 🎨 颜色方案
-
-| 事件类型 | 颜色 | 符号 | 含义 |
-|---------|------|------|------|
-| 开多仓 | 🟢 绿色 (#28a745) | ↑ | 看涨建仓 |
-| 开空仓 | 🔴 红色 (#dc3545) | ↓ | 看跌建仓 |
-| 平多仓 | 🔵 蓝色 (#007bff) | △ | 多头平仓 |
-| 平空仓 | 🟣 紫色 (#6f42c1) | ▽ | 空头平仓 |
-
-## 🚀 下一步优化建议
-
-1. **事件过滤**: 添加按事件类型过滤的功能
-2. **事件统计**: 显示成功率、盈亏统计
-3. **事件连线**: 连接开仓和平仓事件
-4. **性能优化**: 大量事件时的渲染优化
-5. **导出功能**: 导出事件数据为CSV/Excel
-
-## 📝 注意事项
-
-- 事件标记会在图表重绘后自动添加
-- 点击标记时会暂停图表的其他交互
-- 模态框可以通过点击外部区域关闭
-- Tooltip中的事件检测范围为±5秒
-
----
-
-**实现日期**: 2025-11-05  
-**版本**: v1.0  
-**状态**: ✅ 已完成并测试
-

+ 95 - 66
src/leadlag/templates/dashboard.html

@@ -327,7 +327,10 @@
                 <span style="color: #555;">
                     图表上的标记点显示所有交易事件。点击标记可查看详情。
                     <span style="margin-left: 10px;">
-                        📈开多 | 📉开空 | 🔼平多 | 🔽平空
+                        ⬆️ 买入(向上箭头) | ⬇️ 卖出(向下箭头)
+                    </span>
+                    <span style="margin-left: 10px; font-size: 0.9em;">
+                        �开仓 �平仓
                     </span>
                 </span>
             </div>
@@ -582,7 +585,7 @@
                             if (nearbyEvents.length > 0) {
                                 result += '<br/><strong style="color: #667eea;">📌 交易事件:</strong><br/>';
                                 nearbyEvents.forEach(event => {
-                                    const icon = getEventIcon(event.event_type);
+                                    const icon = getEventIcon(event.event_type, event.side);
                                     const eventName = formatEventType(event.event_type);
                                     result += `${icon} ${eventName} @ ${event.price ? event.price.toFixed(6) : '-'}<br/>`;
                                 });
@@ -744,7 +747,7 @@
                             if (nearbyEvents.length > 0) {
                                 result += '<br/><strong style="color: #667eea;">📌 交易事件:</strong><br/>';
                                 nearbyEvents.forEach(event => {
-                                    const icon = getEventIcon(event.event_type);
+                                    const icon = getEventIcon(event.event_type, event.side);
                                     const eventName = formatEventType(event.event_type);
                                     result += `${icon} ${eventName} @ ${event.price ? event.price.toFixed(6) : '-'}<br/>`;
                                 });
@@ -899,34 +902,38 @@
             tradingEventsData.forEach(event => {
                 const timestamp = event.timestamp * 1000; // 转换为毫秒
                 const eventType = event.event_type;
+                const side = event.side; // long 或 short
+
+                // 确定颜色和符号 - 根据方向:买=向上,卖=向下
+                let color, symbol, symbolSize, label, symbolRotate = 0;
 
-                // 确定颜色和符号
-                let color, symbol, symbolSize, label;
+                // 判断是开仓还是平仓
+                const isOpen = eventType.includes('open');
+                const isClose = eventType.includes('close');
 
-                if (eventType.includes('open_long')) {
-                    color = '#28a745'; // 绿色
-                    symbol = 'arrow'; // 向上箭头
+                // 根据side确定箭头方向
+                if (side === 'long') {
+                    // 买入 = 向上箭头
+                    symbol = 'arrow';
                     symbolSize = 15;
-                    label = '开多';
-                } else if (eventType.includes('open_short')) {
-                    color = '#dc3545'; // 红色
-                    symbol = 'arrow'; // 向下箭头(会旋转180度)
+                    symbolRotate = 0; // 向上
+
+                    color = '#28a745';
+                    label = '买入';
+                } else if (side === 'short') {
+                    // 卖出 = 向下箭头
+                    symbol = 'arrow';
                     symbolSize = 15;
-                    label = '开空';
-                } else if (eventType.includes('close_long')) {
-                    color = '#007bff'; // 蓝色
-                    symbol = 'triangle'; // 三角形
-                    symbolSize = 12;
-                    label = '平多';
-                } else if (eventType.includes('close_short')) {
-                    color = '#6f42c1'; // 紫色
-                    symbol = 'triangle'; // 三角形
-                    symbolSize = 12;
-                    label = '平空';
+                    symbolRotate = 180; // 向下
+
+                    color = '#dc3545';
+                    label = '卖出';
                 } else {
+                    // 未知方向
                     color = '#6c757d'; // 灰色
                     symbol = 'circle';
                     symbolSize = 10;
+                    symbolRotate = 0;
                     label = '其他';
                 }
 
@@ -950,7 +957,7 @@
                     value: label,
                     symbol: symbol,
                     symbolSize: symbolSize,
-                    symbolRotate: eventType.includes('short') ? 180 : 0, // 空头箭头向下
+                    symbolRotate: symbolRotate, // 使用计算好的旋转角度
                     itemStyle: {
                         color: color,
                         borderColor: '#fff',
@@ -1040,7 +1047,7 @@
             const details = `
                 <div style="background: white; padding: 20px; border-radius: 10px; box-shadow: 0 4px 20px rgba(0,0,0,0.2); max-width: 500px; margin: 20px auto;">
                     <h3 style="margin-top: 0; color: #667eea; border-bottom: 2px solid #667eea; padding-bottom: 10px;">
-                        ${getEventIcon(event.event_type)} ${formatEventType(event.event_type)}
+                        ${getEventIcon(event.event_type, event.side)} ${formatEventType(event.event_type)}
                     </h3>
                     <table style="width: 100%; border-collapse: collapse;">
                         <tr>
@@ -1294,7 +1301,7 @@
                                 <td>${new Date(event.timestamp * 1000).toLocaleString()}</td>
                                 <td>${event.symbol || '-'}</td>
                                 <td class="${getEventClass(event.event_type)}">
-                                    ${getEventIcon(event.event_type)} ${formatEventType(event.event_type)}
+                                    ${getEventIcon(event.event_type, event.side)} ${formatEventType(event.event_type)}
                                 </td>
                                 <td>${event.price ? event.price.toFixed(6) : '-'}</td>
                                 <td>${event.quantity || '-'}</td>
@@ -1326,14 +1333,28 @@
             return '';
         }
 
-        // 获取事件图标
-        function getEventIcon(eventType) {
-            if (eventType.includes('open_long')) return '📈';
-            if (eventType.includes('open_short')) return '📉';
-            if (eventType.includes('close_long')) return '🔼';
-            if (eventType.includes('close_short')) return '🔽';
-            if (eventType.includes('open')) return '🟢';
-            if (eventType.includes('close')) return '🔴';
+        // 获取事件图标 - 根据side:买=向上,卖=向下
+        function getEventIcon(eventType, side) {
+            // 如果没有传side参数,尝试从eventType推断
+            if (!side) {
+                if (eventType.includes('long')) side = 'long';
+                else if (eventType.includes('short')) side = 'short';
+            }
+
+            const isOpen = eventType.includes('open');
+            const isClose = eventType.includes('close');
+
+            if (side === 'long') {
+                // 买入 = 向上箭头
+                if (isOpen) return '�'; // 开多
+                if (isClose) return '🔼'; // 平多
+                return '⬆️'; // 默认买入
+            } else if (side === 'short') {
+                // 卖出 = 向下箭头
+                if (isOpen) return '�'; // 开空
+                if (isClose) return '�'; // 平空
+                return '⬇️'; // 默认卖出
+            }
             return '⚪';
         }
         
@@ -1463,46 +1484,54 @@
         // 根据选中的时间范围更新主图表
         function updateMainChartsWithSelectedRange() {
             if (!selectedTimeRange) return;
-            
+
             // 计算选中时间范围的小时数
             const timeRangeHours = (selectedTimeRange.end - selectedTimeRange.start) / 3600;
             const symbol = document.getElementById('symbolFilter').value.trim();
-            
-            // 从完整的价格数据API获取选定时间范围的数据
-            fetch(`/api/price_data?hours=${timeRangeHours}&symbol=${symbol}&start_time=${selectedTimeRange.start}`)
-                .then(response => response.json())
-                .then(result => {
-                    if (result.success && result.data && result.data.length > 0) {
-                        // 过滤出选中时间范围内的数据
-                        const filteredData = result.data.filter(d => 
-                            d.timestamp >= selectedTimeRange.start && d.timestamp <= selectedTimeRange.end
-                        );
-                        
-                        if (filteredData.length > 0) {
-                            // 更新价格图表
-                            displayPriceChart(filteredData);
-                            // 更新BPS图表
-                            displayBpsChart(filteredData);
-
-                            // 更新时间范围显示
-                            updateTimeRangeDisplay();
+
+            // 同时获取价格数据和交易事件
+            Promise.all([
+                fetch(`/api/price_data?hours=${timeRangeHours}&symbol=${symbol}&start_time=${selectedTimeRange.start}&db_path=${currentDbPath}`).then(r => r.json()),
+                fetch(`/api/trading_events?hours=${timeRangeHours}&symbol=${symbol}&db_path=${currentDbPath}`).then(r => r.json())
+            ])
+            .then(([priceResult, eventsResult]) => {
+                if (priceResult.success && priceResult.data && priceResult.data.length > 0) {
+                    // 过滤出选中时间范围内的数据
+                    const filteredData = priceResult.data.filter(d =>
+                        d.timestamp >= selectedTimeRange.start && d.timestamp <= selectedTimeRange.end
+                    );
+
+                    if (filteredData.length > 0) {
+                        // 更新价格图表
+                        displayPriceChart(filteredData);
+                        // 更新BPS图表
+                        displayBpsChart(filteredData);
+
+                        // 更新时间范围显示
+                        updateTimeRangeDisplay();
+
+                        // 更新交易事件数据
+                        if (eventsResult.success && eventsResult.data) {
+                            tradingEventsData = eventsResult.data.filter(e =>
+                                e.timestamp >= selectedTimeRange.start && e.timestamp <= selectedTimeRange.end
+                            );
+                            displayTradingEvents(tradingEventsData);
 
                             // 重新添加事件标记
-                            if (tradingEventsData && tradingEventsData.length > 0) {
-                                setTimeout(() => {
-                                    addEventMarkersToCharts();
-                                }, 200);
-                            }
-                        } else {
-                            console.log('过滤后没有数据');
+                            setTimeout(() => {
+                                addEventMarkersToCharts();
+                            }, 200);
                         }
                     } else {
-                        console.log('API返回数据为空或失败:', result);
+                        console.log('过滤后没有数据');
                     }
-                })
-                .catch(error => {
-                    console.error('获取选定时间范围数据失败:', error);
-                });
+                } else {
+                    console.log('API返回数据为空或失败:', priceResult);
+                }
+            })
+            .catch(error => {
+                console.error('获取选定时间范围数据失败:', error);
+            });
         }
         
         // 更新时间范围显示