skyfffire 2 dienas atpakaļ
vecāks
revīzija
8be295abff
3 mainītis faili ar 108 papildinājumiem un 63 dzēšanām
  1. 26 0
      check_timestamp.py
  2. 60 56
      src/leadlag/templates/dashboard.html
  3. 22 7
      src/leadlag/web_dashboard.py

+ 26 - 0
check_timestamp.py

@@ -0,0 +1,26 @@
+import sqlite3
+from datetime import datetime
+
+conn = sqlite3.connect('data/trading_data_20251104.db')
+cursor = conn.cursor()
+
+# 获取最早和最晚的时间戳
+cursor.execute('SELECT MIN(timestamp), MAX(timestamp) FROM price_data')
+min_ts, max_ts = cursor.fetchone()
+
+print(f'最早时间戳: {min_ts}')
+print(f'  UTC: {datetime.utcfromtimestamp(min_ts)}')
+print(f'  本地: {datetime.fromtimestamp(min_ts)}')
+print()
+
+print(f'最晚时间戳: {max_ts}')
+print(f'  UTC: {datetime.utcfromtimestamp(max_ts)}')
+print(f'  本地: {datetime.fromtimestamp(max_ts)}')
+print()
+
+# 计算时间跨度
+span_hours = (max_ts - min_ts) / 3600
+print(f'时间跨度: {span_hours:.2f} 小时')
+
+conn.close()
+

+ 60 - 56
src/leadlag/templates/dashboard.html

@@ -516,10 +516,10 @@
             priceChart = echarts.init(container);
 
             // 准备数据
-            const timestamps = data.map(d => new Date(d.timestamp * 1000).toLocaleTimeString());
-            const binancePrices = data.map(d => d.binance_price);
-            const lighterBids = data.map(d => d.lighter_bid);
-            const lighterAsks = data.map(d => d.lighter_ask);
+            // 对于时间类型的 X 轴,数据格式应该是 [[timestamp, value], ...]
+            const binancePrices = data.map(d => [d.timestamp * 1000, d.binance_price]);
+            const lighterBids = data.map(d => [d.timestamp * 1000, d.lighter_bid]);
+            const lighterAsks = data.map(d => [d.timestamp * 1000, d.lighter_ask]);
 
             const option = {
                 title: {
@@ -544,8 +544,7 @@
                     containLabel: true
                 },
                 xAxis: {
-                    type: 'category',
-                    data: timestamps,
+                    type: 'time',
                     boundaryGap: false
                 },
                 yAxis: {
@@ -561,7 +560,8 @@
                         type: 'line',
                         smooth: true,
                         itemStyle: { color: '#ff6b6b' },
-                        areaStyle: { color: 'rgba(255, 107, 107, 0.1)' }
+                        areaStyle: { color: 'rgba(255, 107, 107, 0.1)' },
+                        showSymbol: false
                     },
                     {
                         name: 'Lighter买价',
@@ -569,7 +569,8 @@
                         type: 'line',
                         smooth: true,
                         itemStyle: { color: '#45b7d1' },
-                        lineStyle: { type: 'dashed' }
+                        lineStyle: { type: 'dashed' },
+                        showSymbol: false
                     },
                     {
                         name: 'Lighter卖价',
@@ -577,7 +578,8 @@
                         type: 'line',
                         smooth: true,
                         itemStyle: { color: '#f9ca24' },
-                        lineStyle: { type: 'dashed' }
+                        lineStyle: { type: 'dashed' },
+                        showSymbol: false
                     }
                 ],
                 dataZoom: [
@@ -627,9 +629,9 @@
             bpsChart = echarts.init(container);
 
             // 准备数据
-            const timestamps = data.map(d => new Date(d.timestamp * 1000).toLocaleTimeString());
-            const askBps = data.map(d => d.ask_bps);
-            const bidBps = data.map(d => d.bid_bps);
+            // 对于时间类型的 X 轴,数据格式应该是 [[timestamp, value], ...]
+            const askBps = data.map(d => [d.timestamp * 1000, d.ask_bps]);
+            const bidBps = data.map(d => [d.timestamp * 1000, d.bid_bps]);
 
             const option = {
                 title: {
@@ -654,8 +656,7 @@
                     containLabel: true
                 },
                 xAxis: {
-                    type: 'category',
-                    data: timestamps,
+                    type: 'time',
                     boundaryGap: false
                 },
                 yAxis: {
@@ -669,7 +670,8 @@
                         type: 'line',
                         smooth: true,
                         itemStyle: { color: '#e74c3c' },
-                        areaStyle: { color: 'rgba(231, 76, 60, 0.1)' }
+                        areaStyle: { color: 'rgba(231, 76, 60, 0.1)' },
+                        showSymbol: false
                     },
                     {
                         name: 'Bid价差 (bps)',
@@ -677,7 +679,8 @@
                         type: 'line',
                         smooth: true,
                         itemStyle: { color: '#27ae60' },
-                        areaStyle: { color: 'rgba(39, 174, 96, 0.1)' }
+                        areaStyle: { color: 'rgba(39, 174, 96, 0.1)' },
+                        showSymbol: false
                     }
                 ],
                 dataZoom: [
@@ -749,8 +752,8 @@
             priceThumbnail = echarts.init(container);
 
             // 准备数据
-            const timestamps = data.map(d => new Date(d.timestamp * 1000).toLocaleTimeString());
-            const binancePrices = data.map(d => d.binance_price);
+            // 对于时间类型的 X 轴,数据格式应该是 [[timestamp, value], ...]
+            const seriesData = data.map(d => [d.timestamp * 1000, d.binance_price]);
 
             const option = {
                 tooltip: {
@@ -764,8 +767,7 @@
                     containLabel: true
                 },
                 xAxis: {
-                    type: 'category',
-                    data: timestamps,
+                    type: 'time',
                     boundaryGap: false,
                     axisLine: { show: true },
                     axisTick: { show: true },
@@ -783,12 +785,13 @@
                 series: [
                     {
                         name: 'Binance价格',
-                        data: binancePrices,
+                        data: seriesData,
                         type: 'line',
                         smooth: true,
                         itemStyle: { color: '#ff6b6b' },
                         areaStyle: { color: 'rgba(255, 107, 107, 0.2)' },
-                        lineStyle: { width: 1.5 }
+                        lineStyle: { width: 1.5 },
+                        showSymbol: false
                     }
                 ]
             };
@@ -914,46 +917,40 @@
         function setupThumbnailClickHandler(containerId, chart) {
             const container = document.getElementById(containerId);
 
-            // 直接在容器上添加点击事件
-            container.addEventListener('click', function(event) {
+            // 使用 ECharts 的点击事件,而不是 DOM 点击事件
+            chart.on('click', function(params) {
                 if (!thumbnailData || thumbnailData.length === 0) {
                     console.log('缩略图数据为空');
                     return;
                 }
 
-                // 获取容器的位置和大小
-                const rect = container.getBoundingClientRect();
-                const x = event.clientX - rect.left;
-                const containerWidth = rect.width;
-
-                // 计算点击位置对应的数据索引
-                const dataIndex = Math.floor((x / containerWidth) * thumbnailData.length);
-                const clickedIndex = Math.min(dataIndex, thumbnailData.length - 1);
-                const clickedDataPoint = thumbnailData[clickedIndex];
-
-                if (clickedDataPoint) {
-                    // 选择前后10分钟的数据
-                    const clickedTime = clickedDataPoint.timestamp;
-                    const tenMinutes = 10 * 60; // 10分钟的秒数
+                // 从点击事件获取时间戳(ECharts 会自动处理时间轴的转换)
+                const clickedTime = params.value[0] / 1000; // 转换为秒
+                const tenMinutes = 10 * 60; // 10分钟的秒数
 
-                    console.log('点击的数据点:', clickedDataPoint);
-                    console.log('点击时间戳:', clickedTime);
-                    console.log('点击索引:', clickedIndex);
+                // 调试信息
+                const clickedDate = new Date(clickedTime * 1000);
+                console.log('点击的时间戳:', clickedTime);
+                console.log('点击时间(本地):', clickedDate.toLocaleString());
+                console.log('点击时间(UTC):', clickedDate.toUTCString());
 
-                    selectedTimeRange = {
-                        start: clickedTime - tenMinutes,
-                        end: clickedTime + tenMinutes,
-                        center: clickedTime
-                    };
+                selectedTimeRange = {
+                    start: clickedTime - tenMinutes,
+                    end: clickedTime + tenMinutes,
+                    center: clickedTime
+                };
 
-                    console.log('选择的时间范围:', selectedTimeRange);
+                const startDate = new Date(selectedTimeRange.start * 1000);
+                const endDate = new Date(selectedTimeRange.end * 1000);
+                console.log('选择的时间范围:', selectedTimeRange);
+                console.log('选择范围(本地):', startDate.toLocaleString(), '-', endDate.toLocaleString());
+                console.log('选择范围(UTC):', startDate.toUTCString(), '-', endDate.toUTCString());
 
-                    // 更新主图表显示选中的时间范围数据
-                    updateMainChartsWithSelectedRange();
+                // 更新主图表显示选中的时间范围数据
+                updateMainChartsWithSelectedRange();
 
-                    // 显示选择区域
-                    showThumbnailSelection(containerId, clickedIndex);
-                }
+                // 显示选择区域
+                showThumbnailSelection(containerId, clickedTime);
             });
 
             // 鼠标移动时改变光标
@@ -967,7 +964,7 @@
         }
         
         // 显示缩略图选择区域
-        function showThumbnailSelection(canvasId, dataIndex) {
+        function showThumbnailSelection(canvasId, clickedTime) {
             const thumbnailWrapper = document.getElementById(canvasId);
 
             // 移除之前的选择区域
@@ -981,9 +978,16 @@
             selection.className = 'thumbnail-selection';
 
             // 计算选择区域的位置和宽度
-            const totalDataPoints = thumbnailData.length;
-            const selectionWidth = (20 / totalDataPoints) * 100; // 20分钟范围对应的百分比宽度
-            const centerPosition = (dataIndex / totalDataPoints) * 100;
+            // 基于时间戳计算位置
+            const minTime = thumbnailData[0].timestamp;
+            const maxTime = thumbnailData[thumbnailData.length - 1].timestamp;
+            const totalTimeSpan = maxTime - minTime;
+
+            const tenMinutes = 10 * 60; // 10分钟的秒数
+            const selectionTimeSpan = tenMinutes * 2; // 前后各10分钟
+
+            const centerPosition = ((clickedTime - minTime) / totalTimeSpan) * 100;
+            const selectionWidth = (selectionTimeSpan / totalTimeSpan) * 100;
             const leftPosition = Math.max(0, centerPosition - selectionWidth / 2);
             const actualWidth = Math.min(selectionWidth, 100 - leftPosition);
 

+ 22 - 7
src/leadlag/web_dashboard.py

@@ -163,18 +163,22 @@ class TradingDashboard:
         """获取价格数据"""
         if start_time is not None:
             # If start_time is provided, find the correct database file
-            start_date_str = datetime.fromtimestamp(start_time).strftime('%Y%m%d')
+            # 注意:使用 UTC 时间来计算日期,而不是本地时间
+            start_date_str = datetime.utcfromtimestamp(start_time).strftime('%Y%m%d')
+            print(f"[DEBUG] start_time={start_time}, start_date_str={start_date_str}")
             found_db = False
             for db_info in self.get_available_databases():
                 if f"trading_data_{start_date_str}.db" in db_info['name']:
                     db_path = db_info['path']
                     found_db = True
+                    print(f"[DEBUG] Found database: {db_path}")
                     break
             if not found_db:
                 # 如果找不到对应日期的数据库,使用最新的可用数据库
                 available_dbs = self.get_available_databases()
                 if available_dbs:
                     db_path = available_dbs[0]['path']
+                    print(f"[DEBUG] Database not found, using latest: {db_path}")
                 elif db_path is None:
                     db_path = self.db_path
         elif db_path is None:
@@ -196,17 +200,18 @@ class TradingDashboard:
             # 如果提供了start_time,使用它作为起始时间
             start_timestamp = start_time
             end_timestamp = start_time + (hours * 3600)  # 从start_time开始的hours小时
+            print(f"[DEBUG] Query time range: {datetime.utcfromtimestamp(start_timestamp)} to {datetime.utcfromtimestamp(end_timestamp)}")
         else:
             # 否则使用当前时间往前推hours小时
             end_time = datetime.now()
             start_time_dt = end_time - timedelta(hours=hours)
             start_timestamp = start_time_dt.timestamp()
             end_timestamp = end_time.timestamp()
-        
+
         # 构建查询 - 更新字段名
         query = """
         SELECT timestamp, symbol, binance_price, lighter_ask, lighter_bid, ask_bps, bid_bps
-        FROM price_data 
+        FROM price_data
         WHERE timestamp >= ? AND timestamp <= ?
         """
         params = [start_timestamp, end_timestamp]
@@ -220,7 +225,12 @@ class TradingDashboard:
         cursor.execute(query, params)
         rows = cursor.fetchall()
         conn.close()
-        
+
+        print(f"[DEBUG] Query returned {len(rows)} rows")
+        if rows:
+            print(f"[DEBUG] First row timestamp: {datetime.utcfromtimestamp(rows[0][0])}")
+            print(f"[DEBUG] Last row timestamp: {datetime.utcfromtimestamp(rows[-1][0])}")
+
         # 转换为字典格式
         data = []
         for row in rows:
@@ -233,7 +243,7 @@ class TradingDashboard:
                 'ask_bps': float(row[5]) if row[5] else None,
                 'bid_bps': float(row[6]) if row[6] else None
             })
-        
+
         return data
     
     def get_thumbnail_data(self, hours: float = 24, symbol: str = '', db_path: str = None) -> List[Dict[str, Any]]:
@@ -277,7 +287,12 @@ class TradingDashboard:
         cursor.execute(query, params)
         rows = cursor.fetchall()
         conn.close()
-        
+
+        print(f"[DEBUG THUMBNAIL] Query returned {len(rows)} rows")
+        if rows:
+            print(f"[DEBUG THUMBNAIL] First row timestamp: {datetime.utcfromtimestamp(rows[0][0])}")
+            print(f"[DEBUG THUMBNAIL] Last row timestamp: {datetime.utcfromtimestamp(rows[-1][0])}")
+
         # 转换为字典格式
         data = []
         for row in rows:
@@ -286,7 +301,7 @@ class TradingDashboard:
                 'symbol': row[1],
                 'binance_price': float(row[2]) if row[2] else None
             })
-        
+
         return data
     
     def get_trading_events(self, hours: float = 24, symbol: str = '', db_path: str = None) -> List[Dict[str, Any]]: