浏览代码

feat: 添加交易策略监控面板及数据库支持

实现交易策略监控面板功能,包含以下主要变更:
1. 新增web_dashboard.py实现基于Flask的监控面板
2. 添加database.py提供交易数据存储功能
3. 在strategy.py中集成数据库记录功能
4. 新增测试脚本test_database.py和运行脚本run_dashboard.py
5. 添加前端模板dashboard.html实现可视化界面
6. 更新requirements.txt添加Flask依赖
skyfffire 4 天之前
父节点
当前提交
dcac82fbb2
共有 8 个文件被更改,包括 1524 次插入0 次删除
  1. 1 0
      requirements.txt
  2. 58 0
      run_dashboard.py
  3. 390 0
      src/leadlag/database.py
  4. 160 0
      src/leadlag/strategy.py
  5. 565 0
      src/leadlag/templates/dashboard.html
  6. 224 0
      src/leadlag/web_dashboard.py
  7. 126 0
      test_database.py
  8. 二进制
      test_trading_data.db

+ 1 - 0
requirements.txt

@@ -4,3 +4,4 @@ aiohttp
 websockets
 websockets
 lighter-sdk
 lighter-sdk
 toml
 toml
+flask

+ 58 - 0
run_dashboard.py

@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+"""
+启动交易策略监控面板
+"""
+
+import os
+import sys
+from pathlib import Path
+
+# 添加项目路径到Python路径
+project_root = Path(__file__).parent
+sys.path.insert(0, str(project_root / "src"))
+
+from leadlag.web_dashboard import TradingDashboard
+
+def main():
+    """启动Web Dashboard"""
+    # 数据库路径 - 优先使用测试数据库
+    test_db_path = project_root / "test_trading_data.db"
+    main_db_path = project_root / "trading_data.db"
+    
+    # 选择存在的数据库文件
+    if test_db_path.exists():
+        db_path = test_db_path
+        print("🧪 使用测试数据库")
+    elif main_db_path.exists():
+        db_path = main_db_path
+        print("📊 使用主数据库")
+    else:
+        db_path = main_db_path
+        print("⚠️  数据库文件不存在,将创建新数据库")
+    
+    print("=" * 60)
+    print("🚀 启动交易策略监控面板")
+    print("=" * 60)
+    print(f"📁 数据库路径: {db_path}")
+    print(f"🌐 访问地址: http://127.0.0.1:5000")
+    print("=" * 60)
+    
+    # 检查数据库是否存在
+    if not db_path.exists():
+        print("⚠️  警告: 数据库文件不存在,请先运行交易策略生成数据")
+        print(f"   数据库将在策略运行时自动创建: {db_path}")
+        print()
+    
+    # 创建并启动dashboard
+    dashboard = TradingDashboard(str(db_path))
+    
+    try:
+        dashboard.run(host='127.0.0.1', port=5000, debug=False)
+    except KeyboardInterrupt:
+        print("\n👋 监控面板已停止")
+    except Exception as e:
+        print(f"❌ 启动失败: {e}")
+        sys.exit(1)
+
+if __name__ == '__main__':
+    main()

+ 390 - 0
src/leadlag/database.py

@@ -0,0 +1,390 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+数据库模块
+用于记录价格数据和交易事件,支持后续的数据分析和可视化
+"""
+
+import sqlite3
+import logging
+import os
+from datetime import datetime
+from typing import Optional, List, Dict, Any
+import json
+
+logger = logging.getLogger("database")
+
+class TradingDatabase:
+    """交易数据库类,负责记录价格数据和交易事件"""
+    
+    def __init__(self, db_path: str = None):
+        """
+        初始化数据库连接
+        
+        Args:
+            db_path: 数据库文件路径,如果为None则使用默认路径
+        """
+        if db_path is None:
+            # 使用项目根目录下的data文件夹
+            script_dir = os.path.dirname(os.path.abspath(__file__))
+            project_root = os.path.dirname(os.path.dirname(script_dir))
+            data_dir = os.path.join(project_root, "data")
+            
+            # 确保data目录存在
+            os.makedirs(data_dir, exist_ok=True)
+            
+            # 使用日期作为数据库文件名
+            today = datetime.now().strftime("%Y%m%d")
+            db_path = os.path.join(data_dir, f"trading_data_{today}.db")
+        
+        self.db_path = db_path
+        self.connection = None
+        self._init_database()
+    
+    def _init_database(self):
+        """初始化数据库连接和表结构"""
+        try:
+            self.connection = sqlite3.connect(self.db_path, check_same_thread=False)
+            self.connection.row_factory = sqlite3.Row  # 使结果可以通过列名访问
+            self._create_tables()
+            logger.info(f"数据库初始化成功: {self.db_path}")
+        except Exception as e:
+            logger.error(f"数据库初始化失败: {e}")
+            raise
+    
+    def _create_tables(self):
+        """创建数据库表结构"""
+        cursor = self.connection.cursor()
+        
+        # 价格数据表
+        cursor.execute("""
+            CREATE TABLE IF NOT EXISTS price_data (
+                id INTEGER PRIMARY KEY AUTOINCREMENT,
+                timestamp REAL NOT NULL,
+                datetime_str TEXT NOT NULL,
+                symbol TEXT NOT NULL,
+                lighter_price REAL,
+                binance_price REAL,
+                spread_bps REAL,
+                lighter_bid REAL,
+                lighter_ask REAL,
+                lighter_bid_size REAL,
+                lighter_ask_size REAL,
+                binance_volume REAL,
+                raw_data TEXT,
+                created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+            )
+        """)
+        
+        # 交易事件表
+        cursor.execute("""
+            CREATE TABLE IF NOT EXISTS trading_events (
+                id INTEGER PRIMARY KEY AUTOINCREMENT,
+                timestamp REAL NOT NULL,
+                datetime_str TEXT NOT NULL,
+                symbol TEXT NOT NULL,
+                event_type TEXT NOT NULL,  -- 'open_long', 'open_short', 'close_long', 'close_short'
+                price REAL NOT NULL,
+                quantity REAL NOT NULL,
+                side TEXT NOT NULL,  -- 'long', 'short'
+                strategy_state TEXT,
+                spread_bps REAL,
+                lighter_price REAL,
+                binance_price REAL,
+                order_id TEXT,
+                tx_hash TEXT,
+                success BOOLEAN,
+                error_message TEXT,
+                metadata TEXT,  -- JSON格式的额外信息
+                created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+            )
+        """)
+        
+        # 创建索引以提高查询性能
+        cursor.execute("CREATE INDEX IF NOT EXISTS idx_price_timestamp ON price_data(timestamp)")
+        cursor.execute("CREATE INDEX IF NOT EXISTS idx_price_symbol ON price_data(symbol)")
+        cursor.execute("CREATE INDEX IF NOT EXISTS idx_events_timestamp ON trading_events(timestamp)")
+        cursor.execute("CREATE INDEX IF NOT EXISTS idx_events_symbol ON trading_events(symbol)")
+        cursor.execute("CREATE INDEX IF NOT EXISTS idx_events_type ON trading_events(event_type)")
+        
+        self.connection.commit()
+        logger.info("数据库表结构创建完成")
+    
+    def record_price_data(self, 
+                         symbol: str,
+                         lighter_price: Optional[float] = None,
+                         binance_price: Optional[float] = None,
+                         spread_bps: Optional[float] = None,
+                         lighter_bid: Optional[float] = None,
+                         lighter_ask: Optional[float] = None,
+                         lighter_bid_size: Optional[float] = None,
+                         lighter_ask_size: Optional[float] = None,
+                         binance_volume: Optional[float] = None,
+                         raw_data: Optional[Dict] = None):
+        """
+        记录价格数据
+        
+        Args:
+            symbol: 交易对符号
+            lighter_price: Lighter价格
+            binance_price: Binance价格
+            spread_bps: 价差(基点)
+            lighter_bid: Lighter买价
+            lighter_ask: Lighter卖价
+            lighter_bid_size: Lighter买量
+            lighter_ask_size: Lighter卖量
+            binance_volume: Binance成交量
+            raw_data: 原始数据(字典格式)
+        """
+        try:
+            timestamp = datetime.now().timestamp()
+            datetime_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
+            
+            cursor = self.connection.cursor()
+            cursor.execute("""
+                INSERT INTO price_data (
+                    timestamp, datetime_str, symbol, lighter_price, binance_price, 
+                    spread_bps, lighter_bid, lighter_ask, lighter_bid_size, 
+                    lighter_ask_size, binance_volume, raw_data
+                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+            """, (
+                timestamp, datetime_str, symbol, lighter_price, binance_price,
+                spread_bps, lighter_bid, lighter_ask, lighter_bid_size,
+                lighter_ask_size, binance_volume, 
+                json.dumps(raw_data) if raw_data else None
+            ))
+            
+            self.connection.commit()
+            
+        except Exception as e:
+            logger.error(f"记录价格数据失败: {e}")
+    
+    def record_trading_event(self,
+                           symbol: str,
+                           event_type: str,
+                           price: float,
+                           quantity: float,
+                           side: str,
+                           strategy_state: Optional[str] = None,
+                           spread_bps: Optional[float] = None,
+                           lighter_price: Optional[float] = None,
+                           binance_price: Optional[float] = None,
+                           order_id: Optional[str] = None,
+                           tx_hash: Optional[str] = None,
+                           success: Optional[bool] = None,
+                           error_message: Optional[str] = None,
+                           metadata: Optional[Dict] = None):
+        """
+        记录交易事件
+        
+        Args:
+            symbol: 交易对符号
+            event_type: 事件类型 ('open_long', 'open_short', 'close_long', 'close_short')
+            price: 交易价格
+            quantity: 交易数量
+            side: 交易方向 ('long', 'short')
+            strategy_state: 策略状态
+            spread_bps: 当时的价差
+            lighter_price: 当时的Lighter价格
+            binance_price: 当时的Binance价格
+            order_id: 订单ID
+            tx_hash: 交易哈希
+            success: 是否成功
+            error_message: 错误信息
+            metadata: 额外的元数据
+        """
+        try:
+            timestamp = datetime.now().timestamp()
+            datetime_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
+            
+            cursor = self.connection.cursor()
+            cursor.execute("""
+                INSERT INTO trading_events (
+                    timestamp, datetime_str, symbol, event_type, price, quantity, 
+                    side, strategy_state, spread_bps, lighter_price, binance_price,
+                    order_id, tx_hash, success, error_message, metadata
+                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+            """, (
+                timestamp, datetime_str, symbol, event_type, price, quantity,
+                side, strategy_state, spread_bps, lighter_price, binance_price,
+                order_id, tx_hash, success, error_message,
+                json.dumps(metadata) if metadata else None
+            ))
+            
+            self.connection.commit()
+            logger.info(f"记录交易事件: {event_type} {side} {quantity}@{price}")
+            
+        except Exception as e:
+            logger.error(f"记录交易事件失败: {e}")
+    
+    def get_price_data(self, 
+                      symbol: Optional[str] = None,
+                      start_time: Optional[float] = None,
+                      end_time: Optional[float] = None,
+                      limit: Optional[int] = None) -> List[Dict]:
+        """
+        获取价格数据
+        
+        Args:
+            symbol: 交易对符号
+            start_time: 开始时间戳
+            end_time: 结束时间戳
+            limit: 限制返回条数
+            
+        Returns:
+            价格数据列表
+        """
+        try:
+            cursor = self.connection.cursor()
+            
+            query = "SELECT * FROM price_data WHERE 1=1"
+            params = []
+            
+            if symbol:
+                query += " AND symbol = ?"
+                params.append(symbol)
+            
+            if start_time:
+                query += " AND timestamp >= ?"
+                params.append(start_time)
+            
+            if end_time:
+                query += " AND timestamp <= ?"
+                params.append(end_time)
+            
+            query += " ORDER BY timestamp DESC"
+            
+            if limit:
+                query += " LIMIT ?"
+                params.append(limit)
+            
+            cursor.execute(query, params)
+            rows = cursor.fetchall()
+            
+            return [dict(row) for row in rows]
+            
+        except Exception as e:
+            logger.error(f"获取价格数据失败: {e}")
+            return []
+    
+    def get_trading_events(self,
+                          symbol: Optional[str] = None,
+                          event_type: Optional[str] = None,
+                          start_time: Optional[float] = None,
+                          end_time: Optional[float] = None,
+                          limit: Optional[int] = None) -> List[Dict]:
+        """
+        获取交易事件
+        
+        Args:
+            symbol: 交易对符号
+            event_type: 事件类型
+            start_time: 开始时间戳
+            end_time: 结束时间戳
+            limit: 限制返回条数
+            
+        Returns:
+            交易事件列表
+        """
+        try:
+            cursor = self.connection.cursor()
+            
+            query = "SELECT * FROM trading_events WHERE 1=1"
+            params = []
+            
+            if symbol:
+                query += " AND symbol = ?"
+                params.append(symbol)
+            
+            if event_type:
+                query += " AND event_type = ?"
+                params.append(event_type)
+            
+            if start_time:
+                query += " AND timestamp >= ?"
+                params.append(start_time)
+            
+            if end_time:
+                query += " AND timestamp <= ?"
+                params.append(end_time)
+            
+            query += " ORDER BY timestamp DESC"
+            
+            if limit:
+                query += " LIMIT ?"
+                params.append(limit)
+            
+            cursor.execute(query, params)
+            rows = cursor.fetchall()
+            
+            return [dict(row) for row in rows]
+            
+        except Exception as e:
+            logger.error(f"获取交易事件失败: {e}")
+            return []
+    
+    def get_statistics(self, symbol: Optional[str] = None) -> Dict[str, Any]:
+        """
+        获取统计信息
+        
+        Args:
+            symbol: 交易对符号
+            
+        Returns:
+            统计信息字典
+        """
+        try:
+            cursor = self.connection.cursor()
+            stats = {}
+            
+            # 价格数据统计
+            query = "SELECT COUNT(*) as count FROM price_data"
+            params = []
+            if symbol:
+                query += " WHERE symbol = ?"
+                params.append(symbol)
+            
+            cursor.execute(query, params)
+            stats['price_data_count'] = cursor.fetchone()[0]
+            
+            # 交易事件统计
+            query = "SELECT COUNT(*) as count FROM trading_events"
+            params = []
+            if symbol:
+                query += " WHERE symbol = ?"
+                params.append(symbol)
+            
+            cursor.execute(query, params)
+            stats['trading_events_count'] = cursor.fetchone()[0]
+            
+            # 按事件类型统计
+            query = "SELECT event_type, COUNT(*) as count FROM trading_events"
+            params = []
+            if symbol:
+                query += " WHERE symbol = ?"
+                params.append(symbol)
+            query += " GROUP BY event_type"
+            
+            cursor.execute(query, params)
+            event_stats = {}
+            for row in cursor.fetchall():
+                event_stats[row[0]] = row[1]
+            stats['events_by_type'] = event_stats
+            
+            return stats
+            
+        except Exception as e:
+            logger.error(f"获取统计信息失败: {e}")
+            return {}
+    
+    def close(self):
+        """关闭数据库连接"""
+        if self.connection:
+            self.connection.close()
+            logger.info("数据库连接已关闭")
+    
+    def __enter__(self):
+        return self
+    
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self.close()

+ 160 - 0
src/leadlag/strategy.py

@@ -10,6 +10,7 @@ from enum import Enum
 import os
 import os
 import lighter
 import lighter
 import time
 import time
+from .database import TradingDatabase
 
 
 
 
 # 配置日志
 # 配置日志
@@ -102,6 +103,10 @@ class TradingStrategy:
         
         
         # WebSocket连接引用(由listener设置)
         # WebSocket连接引用(由listener设置)
         self.websocket_connection = None
         self.websocket_connection = None
+        
+        # 初始化数据库
+        self.database = TradingDatabase()
+        logger.info(f"数据库初始化完成: {self.database.db_path}")
 
 
         # 从配置文件读取Lighter相关参数
         # 从配置文件读取Lighter相关参数
         lighter_config = config.get('lighter', {})
         lighter_config = config.get('lighter', {})
@@ -166,6 +171,9 @@ class TradingStrategy:
         # 打印实时行情
         # 打印实时行情
         await self._print_market_data(market_data)
         await self._print_market_data(market_data)
         
         
+        # 记录价格数据到数据库
+        await self._record_price_data(market_data)
+        
         # 根据当前状态执行相应逻辑
         # 根据当前状态执行相应逻辑
         if self.state == StrategyState.WAITING_INIT:
         if self.state == StrategyState.WAITING_INIT:
             await self._handle_waiting_init()
             await self._handle_waiting_init()
@@ -205,6 +213,57 @@ class TradingStrategy:
         # price_diff_str = f"{price_diff_bps}bps" if price_diff_bps is not None else "N/A"
         # price_diff_str = f"{price_diff_bps}bps" if price_diff_bps is not None else "N/A"
         # logger.info(f"[{symbol}] Binance: 最新价={binance_price} | Lighter: 最新价={lighter_price} | 价差={price_diff_str}")
         # logger.info(f"[{symbol}] Binance: 最新价={binance_price} | Lighter: 最新价={lighter_price} | 价差={price_diff_str}")
     
     
+    async def _record_price_data(self, market_data):
+        """记录价格数据到数据库"""
+        try:
+            symbol = market_data.get('symbol')
+            binance_price = market_data.get('binance_price')
+            lighter_price = market_data.get('lighter_price')
+            orderbook = market_data.get('orderbook', {})
+            
+            # 计算价差(bps)
+            spread_bps = None
+            if binance_price and lighter_price:
+                binance_price_float = float(binance_price) if isinstance(binance_price, str) else binance_price
+                lighter_price_float = float(lighter_price) if isinstance(lighter_price, str) else lighter_price
+                spread_bps = (lighter_price_float - binance_price_float) / binance_price_float * 10000 if binance_price_float else 0
+            
+            # 提取orderbook信息
+            lighter_bid = None
+            lighter_ask = None
+            lighter_bid_size = None
+            lighter_ask_size = None
+            
+            if orderbook:
+                # 从orderbook中提取买卖价和数量
+                if 'bids' in orderbook and orderbook['bids']:
+                    best_bid = orderbook['bids'][0] if isinstance(orderbook['bids'][0], list) else orderbook['bids'][0]
+                    if isinstance(best_bid, list) and len(best_bid) >= 2:
+                        lighter_bid = float(best_bid[0])
+                        lighter_bid_size = float(best_bid[1])
+                
+                if 'asks' in orderbook and orderbook['asks']:
+                    best_ask = orderbook['asks'][0] if isinstance(orderbook['asks'][0], list) else orderbook['asks'][0]
+                    if isinstance(best_ask, list) and len(best_ask) >= 2:
+                        lighter_ask = float(best_ask[0])
+                        lighter_ask_size = float(best_ask[1])
+            
+            # 记录到数据库
+            self.database.record_price_data(
+                symbol=symbol,
+                lighter_price=float(lighter_price) if lighter_price else None,
+                binance_price=float(binance_price) if binance_price else None,
+                spread_bps=spread_bps,
+                lighter_bid=lighter_bid,
+                lighter_ask=lighter_ask,
+                lighter_bid_size=lighter_bid_size,
+                lighter_ask_size=lighter_ask_size,
+                raw_data=market_data
+            )
+            
+        except Exception as e:
+            logger.error(f"记录价格数据失败: {e}")
+    
     async def _handle_waiting_init(self):
     async def _handle_waiting_init(self):
         """处理等待初始化状态"""
         """处理等待初始化状态"""
         # 初始化完成后转到空闲监听状态
         # 初始化完成后转到空闲监听状态
@@ -266,6 +325,27 @@ class TradingStrategy:
         
         
         logger.info(f"开始开仓:方向={side_desc},数量={self.trade_quantity},价格={price},价格偏移量={price_offset}")
         logger.info(f"开始开仓:方向={side_desc},数量={self.trade_quantity},价格={price},价格偏移量={price_offset}")
         
         
+        # 记录开仓尝试事件
+        spread_bps = (lighter_price_float - float(binance_price)) / float(binance_price) * 10000 if binance_price else None
+        event_type = 'open_long' if self.position_side == 'long' else 'open_short'
+        
+        self.database.record_trading_event(
+            symbol=self.target_symbol,
+            event_type=event_type,
+            price=price,
+            quantity=self.trade_quantity,
+            side=self.position_side,
+            strategy_state=self.state.name,
+            spread_bps=spread_bps,
+            lighter_price=lighter_price_float,
+            binance_price=float(binance_price) if binance_price else None,
+            metadata={
+                'price_offset_bps': price_offset_bps,
+                'price_offset': price_offset,
+                'is_ask': is_ask
+            }
+        )
+        
         tx_hash, error = await self.create_order_and_send_tx(
         tx_hash, error = await self.create_order_and_send_tx(
             orderbook=orderbook,
             orderbook=orderbook,
             quantity=self.trade_quantity,
             quantity=self.trade_quantity,
@@ -276,11 +356,40 @@ class TradingStrategy:
         
         
         if error:
         if error:
             logger.error(f"开仓失败: {error}")
             logger.error(f"开仓失败: {error}")
+            # 更新交易事件为失败
+            self.database.record_trading_event(
+                symbol=self.target_symbol,
+                event_type=event_type + '_failed',
+                price=price,
+                quantity=self.trade_quantity,
+                side=self.position_side,
+                strategy_state=self.state.name,
+                spread_bps=spread_bps,
+                lighter_price=lighter_price_float,
+                binance_price=float(binance_price) if binance_price else None,
+                success=False,
+                error_message=str(error)
+            )
             logger.info(f"状态转换: EXECUTING_OPEN -> IDLE_MONITORING")
             logger.info(f"状态转换: EXECUTING_OPEN -> IDLE_MONITORING")
             self.state = StrategyState.IDLE_MONITORING
             self.state = StrategyState.IDLE_MONITORING
             # 开仓失败,保持在 EXECUTING_OPEN 状态,等待重试
             # 开仓失败,保持在 EXECUTING_OPEN 状态,等待重试
             return
             return
         
         
+        # 记录开仓成功事件
+        self.database.record_trading_event(
+            symbol=self.target_symbol,
+            event_type=event_type + '_success',
+            price=price,
+            quantity=self.trade_quantity,
+            side=self.position_side,
+            strategy_state=self.state.name,
+            spread_bps=spread_bps,
+            lighter_price=lighter_price_float,
+            binance_price=float(binance_price) if binance_price else None,
+            tx_hash=tx_hash,
+            success=True
+        )
+        
         # 记录开仓时间
         # 记录开仓时间
         self.last_trade_time = time.time()
         self.last_trade_time = time.time()
         
         
@@ -399,6 +508,28 @@ class TradingStrategy:
         logger.info(f"开始平仓:方向={'做空' if self.position_side == 'short' else '做多'},数量={position_quantity}")
         logger.info(f"开始平仓:方向={'做空' if self.position_side == 'short' else '做多'},数量={position_quantity}")
         logger.info(f"价格计算:lighter现价={lighter_price_float},偏移={price_offset_bps}bps({price_offset:.6f}),最终价格={close_price:.6f}")
         logger.info(f"价格计算:lighter现价={lighter_price_float},偏移={price_offset_bps}bps({price_offset:.6f}),最终价格={close_price:.6f}")
         
         
+        # 记录平仓尝试事件
+        spread_bps = (lighter_price_float - float(binance_price)) / float(binance_price) * 10000 if binance_price else None
+        event_type = 'close_short' if self.position_side == 'short' else 'close_long'
+        
+        self.database.record_trading_event(
+            symbol=self.target_symbol,
+            event_type=event_type,
+            price=close_price,
+            quantity=position_quantity,
+            side=self.position_side,
+            strategy_state=self.state.name,
+            spread_bps=spread_bps,
+            lighter_price=lighter_price_float,
+            binance_price=float(binance_price) if binance_price else None,
+            metadata={
+                'price_offset_bps': price_offset_bps,
+                'price_offset': price_offset,
+                'is_ask': is_ask,
+                'position_quantity': position_quantity
+            }
+        )
+        
         tx_hash, error = await self.create_order_and_send_tx(
         tx_hash, error = await self.create_order_and_send_tx(
             orderbook=orderbook,
             orderbook=orderbook,
             quantity=position_quantity,
             quantity=position_quantity,
@@ -409,10 +540,39 @@ class TradingStrategy:
         
         
         if error:
         if error:
             logger.error(f"平仓失败: {error}")
             logger.error(f"平仓失败: {error}")
+            # 记录平仓失败事件
+            self.database.record_trading_event(
+                symbol=self.target_symbol,
+                event_type=event_type + '_failed',
+                price=close_price,
+                quantity=position_quantity,
+                side=self.position_side,
+                strategy_state=self.state.name,
+                spread_bps=spread_bps,
+                lighter_price=lighter_price_float,
+                binance_price=float(binance_price) if binance_price else None,
+                success=False,
+                error_message=str(error)
+            )
             # 平仓失败,保持在执行平仓状态,等待下次重试
             # 平仓失败,保持在执行平仓状态,等待下次重试
             logger.info(f"平仓失败,保持在 EXECUTING_CLOSE 状态等待重试")
             logger.info(f"平仓失败,保持在 EXECUTING_CLOSE 状态等待重试")
             return
             return
         
         
+        # 记录平仓成功事件
+        self.database.record_trading_event(
+            symbol=self.target_symbol,
+            event_type=event_type + '_success',
+            price=close_price,
+            quantity=position_quantity,
+            side=self.position_side,
+            strategy_state=self.state.name,
+            spread_bps=spread_bps,
+            lighter_price=lighter_price_float,
+            binance_price=float(binance_price) if binance_price else None,
+            tx_hash=tx_hash,
+            success=True
+        )
+        
         # 记录平仓时间
         # 记录平仓时间
         self.last_trade_time = time.time()
         self.last_trade_time = time.time()
         
         

+ 565 - 0
src/leadlag/templates/dashboard.html

@@ -0,0 +1,565 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>交易策略监控面板</title>
+    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
+    <script src="https://cdn.jsdelivr.net/npm/date-fns@2.29.3/index.min.js"></script>
+    <style>
+        * {
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+        }
+        
+        body {
+            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+            background-color: #f5f5f5;
+            color: #333;
+        }
+        
+        .header {
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            padding: 20px;
+            text-align: center;
+            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+        }
+        
+        .header h1 {
+            margin-bottom: 10px;
+            font-size: 2.5em;
+        }
+        
+        .header p {
+            opacity: 0.9;
+            font-size: 1.1em;
+        }
+        
+        .container {
+            max-width: 1400px;
+            margin: 0 auto;
+            padding: 20px;
+        }
+        
+        .controls {
+            background: white;
+            padding: 20px;
+            border-radius: 10px;
+            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+            margin-bottom: 20px;
+            display: flex;
+            gap: 20px;
+            align-items: center;
+            flex-wrap: wrap;
+        }
+        
+        .control-group {
+            display: flex;
+            flex-direction: column;
+            gap: 5px;
+        }
+        
+        .control-group label {
+            font-weight: 600;
+            color: #555;
+        }
+        
+        .control-group select,
+        .control-group input {
+            padding: 8px 12px;
+            border: 2px solid #ddd;
+            border-radius: 5px;
+            font-size: 14px;
+        }
+        
+        .control-group select:focus,
+        .control-group input:focus {
+            outline: none;
+            border-color: #667eea;
+        }
+        
+        .refresh-btn {
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border: none;
+            padding: 10px 20px;
+            border-radius: 5px;
+            cursor: pointer;
+            font-size: 14px;
+            font-weight: 600;
+            transition: transform 0.2s;
+        }
+        
+        .refresh-btn:hover {
+            transform: translateY(-2px);
+        }
+        
+        .stats-grid {
+            display: grid;
+            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+            gap: 20px;
+            margin-bottom: 20px;
+        }
+        
+        .stat-card {
+            background: white;
+            padding: 20px;
+            border-radius: 10px;
+            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+            text-align: center;
+        }
+        
+        .stat-card h3 {
+            color: #667eea;
+            margin-bottom: 10px;
+            font-size: 1.1em;
+        }
+        
+        .stat-card .value {
+            font-size: 2em;
+            font-weight: bold;
+            color: #333;
+        }
+        
+        .chart-container {
+            background: white;
+            padding: 20px;
+            border-radius: 10px;
+            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+            margin-bottom: 20px;
+        }
+        
+        .chart-container h2 {
+            margin-bottom: 20px;
+            color: #333;
+            border-bottom: 2px solid #667eea;
+            padding-bottom: 10px;
+        }
+        
+        .chart-wrapper {
+            position: relative;
+            height: 400px;
+        }
+        
+        .events-container {
+            background: white;
+            padding: 20px;
+            border-radius: 10px;
+            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+        }
+        
+        .events-table {
+            width: 100%;
+            border-collapse: collapse;
+            margin-top: 10px;
+        }
+        
+        .events-table th,
+        .events-table td {
+            padding: 12px;
+            text-align: left;
+            border-bottom: 1px solid #ddd;
+        }
+        
+        .events-table th {
+            background-color: #f8f9fa;
+            font-weight: 600;
+            color: #555;
+        }
+        
+        .events-table tr:hover {
+            background-color: #f8f9fa;
+        }
+        
+        .event-success {
+            color: #28a745;
+            font-weight: bold;
+        }
+        
+        .event-failed {
+            color: #dc3545;
+            font-weight: bold;
+        }
+        
+        .event-open {
+            color: #007bff;
+        }
+        
+        .event-close {
+            color: #6f42c1;
+        }
+        
+        .loading {
+            text-align: center;
+            padding: 40px;
+            color: #666;
+        }
+        
+        .error {
+            background-color: #f8d7da;
+            color: #721c24;
+            padding: 15px;
+            border-radius: 5px;
+            margin: 10px 0;
+        }
+        
+        @media (max-width: 768px) {
+            .controls {
+                flex-direction: column;
+                align-items: stretch;
+            }
+            
+            .stats-grid {
+                grid-template-columns: 1fr;
+            }
+        }
+    </style>
+</head>
+<body>
+    <div class="header">
+        <h1>🚀 交易策略监控面板</h1>
+        <p>实时监控价格数据和交易事件</p>
+    </div>
+    
+    <div class="container">
+        <div class="controls">
+            <div class="control-group">
+                <label for="timeRange">时间范围</label>
+                <select id="timeRange">
+                    <option value="1">最近1小时</option>
+                    <option value="6">最近6小时</option>
+                    <option value="24" selected>最近24小时</option>
+                    <option value="72">最近3天</option>
+                    <option value="168">最近7天</option>
+                </select>
+            </div>
+            
+            <div class="control-group">
+                <label for="symbolFilter">交易对</label>
+                <input type="text" id="symbolFilter" placeholder="输入交易对 (如: BTCUSDT)">
+            </div>
+            
+            <button class="refresh-btn" onclick="refreshData()">🔄 刷新数据</button>
+        </div>
+        
+        <div class="stats-grid" id="statsGrid">
+            <!-- 统计卡片将通过JavaScript动态生成 -->
+        </div>
+        
+        <div class="chart-container">
+            <h2>📈 价格走势图</h2>
+            <div class="chart-wrapper">
+                <canvas id="priceChart"></canvas>
+            </div>
+        </div>
+        
+        <div class="events-container">
+            <h2>📋 交易事件记录</h2>
+            <div id="eventsContent">
+                <div class="loading">正在加载数据...</div>
+            </div>
+        </div>
+    </div>
+
+    <script>
+        let priceChart = null;
+        
+        // 初始化页面
+        document.addEventListener('DOMContentLoaded', function() {
+            refreshData();
+            // 每30秒自动刷新
+            setInterval(refreshData, 30000);
+        });
+        
+        // 刷新数据
+        async function refreshData() {
+            const timeRange = document.getElementById('timeRange').value;
+            const symbol = document.getElementById('symbolFilter').value.trim();
+            
+            try {
+                await Promise.all([
+                    loadStats(),
+                    loadPriceData(timeRange, symbol),
+                    loadTradingEvents(timeRange, symbol)
+                ]);
+            } catch (error) {
+                console.error('刷新数据失败:', error);
+                showError('数据加载失败: ' + error.message);
+            }
+        }
+        
+        // 加载统计数据
+        async function loadStats() {
+            try {
+                const response = await fetch('/api/stats');
+                const result = await response.json();
+                
+                if (result.success) {
+                    displayStats(result.data);
+                } else {
+                    throw new Error(result.error);
+                }
+            } catch (error) {
+                console.error('加载统计数据失败:', error);
+            }
+        }
+        
+        // 显示统计数据
+        function displayStats(stats) {
+            const statsGrid = document.getElementById('statsGrid');
+            
+            const cards = [
+                {
+                    title: '价格数据记录',
+                    value: stats.price_data.total_records.toLocaleString(),
+                    icon: '📊'
+                },
+                {
+                    title: '成功交易',
+                    value: stats.success_rate.successful,
+                    icon: '✅'
+                },
+                {
+                    title: '成功率',
+                    value: stats.success_rate.rate.toFixed(1) + '%',
+                    icon: '🎯'
+                },
+                {
+                    title: '总交易次数',
+                    value: stats.success_rate.total,
+                    icon: '🔄'
+                }
+            ];
+            
+            statsGrid.innerHTML = cards.map(card => `
+                <div class="stat-card">
+                    <h3>${card.icon} ${card.title}</h3>
+                    <div class="value">${card.value}</div>
+                </div>
+            `).join('');
+        }
+        
+        // 加载价格数据
+        async function loadPriceData(hours, symbol) {
+            try {
+                const params = new URLSearchParams({ hours });
+                if (symbol) params.append('symbol', symbol);
+                
+                const response = await fetch(`/api/price_data?${params}`);
+                const result = await response.json();
+                
+                if (result.success) {
+                    displayPriceChart(result.data);
+                } else {
+                    throw new Error(result.error);
+                }
+            } catch (error) {
+                console.error('加载价格数据失败:', error);
+                showError('价格数据加载失败: ' + error.message);
+            }
+        }
+        
+        // 显示价格图表
+        function displayPriceChart(data) {
+            const ctx = document.getElementById('priceChart').getContext('2d');
+            
+            if (priceChart) {
+                priceChart.destroy();
+            }
+            
+            const labels = data.map(d => new Date(d.timestamp).toLocaleTimeString());
+            
+            priceChart = new Chart(ctx, {
+                type: 'line',
+                data: {
+                    labels: labels,
+                    datasets: [
+                        {
+                            label: 'Binance价格',
+                            data: data.map(d => d.binance_price),
+                            borderColor: '#ff6b6b',
+                            backgroundColor: 'rgba(255, 107, 107, 0.1)',
+                            tension: 0.1,
+                            pointRadius: 2
+                        },
+                        {
+                            label: 'Lighter价格',
+                            data: data.map(d => d.lighter_price),
+                            borderColor: '#4ecdc4',
+                            backgroundColor: 'rgba(78, 205, 196, 0.1)',
+                            tension: 0.1,
+                            pointRadius: 2
+                        },
+                        {
+                            label: 'Lighter买价',
+                            data: data.map(d => d.lighter_bid),
+                            borderColor: '#45b7d1',
+                            backgroundColor: 'rgba(69, 183, 209, 0.1)',
+                            tension: 0.1,
+                            pointRadius: 1,
+                            borderDash: [5, 5]
+                        },
+                        {
+                            label: 'Lighter卖价',
+                            data: data.map(d => d.lighter_ask),
+                            borderColor: '#f9ca24',
+                            backgroundColor: 'rgba(249, 202, 36, 0.1)',
+                            tension: 0.1,
+                            pointRadius: 1,
+                            borderDash: [5, 5]
+                        }
+                    ]
+                },
+                options: {
+                    responsive: true,
+                    maintainAspectRatio: false,
+                    scales: {
+                        y: {
+                            beginAtZero: false,
+                            title: {
+                                display: true,
+                                text: '价格 (USDT)'
+                            }
+                        },
+                        x: {
+                            title: {
+                                display: true,
+                                text: '时间'
+                            }
+                        }
+                    },
+                    plugins: {
+                        legend: {
+                            position: 'top'
+                        },
+                        tooltip: {
+                            mode: 'index',
+                            intersect: false
+                        }
+                    },
+                    interaction: {
+                        mode: 'nearest',
+                        axis: 'x',
+                        intersect: false
+                    }
+                }
+            });
+        }
+        
+        // 加载交易事件
+        async function loadTradingEvents(hours, symbol) {
+            try {
+                const params = new URLSearchParams({ hours });
+                if (symbol) params.append('symbol', symbol);
+                
+                const response = await fetch(`/api/trading_events?${params}`);
+                const result = await response.json();
+                
+                if (result.success) {
+                    displayTradingEvents(result.data);
+                } else {
+                    throw new Error(result.error);
+                }
+            } catch (error) {
+                console.error('加载交易事件失败:', error);
+                showError('交易事件加载失败: ' + error.message);
+            }
+        }
+        
+        // 显示交易事件
+        function displayTradingEvents(events) {
+            const eventsContent = document.getElementById('eventsContent');
+            
+            if (events.length === 0) {
+                eventsContent.innerHTML = '<div class="loading">暂无交易事件</div>';
+                return;
+            }
+            
+            const table = `
+                <table class="events-table">
+                    <thead>
+                        <tr>
+                            <th>时间</th>
+                            <th>交易对</th>
+                            <th>事件类型</th>
+                            <th>价格</th>
+                            <th>数量</th>
+                            <th>方向</th>
+                            <th>状态</th>
+                            <th>交易哈希</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        ${events.map(event => `
+                            <tr>
+                                <td>${new Date(event.timestamp).toLocaleString()}</td>
+                                <td>${event.symbol}</td>
+                                <td class="${getEventClass(event.event_type)}">${formatEventType(event.event_type)}</td>
+                                <td>${event.price ? event.price.toFixed(6) : '-'}</td>
+                                <td>${event.quantity || '-'}</td>
+                                <td>${event.side || '-'}</td>
+                                <td class="${event.success ? 'event-success' : 'event-failed'}">
+                                    ${event.success ? '✅ 成功' : '❌ 失败'}
+                                </td>
+                                <td>
+                                    ${event.tx_hash ? 
+                                        `<a href="#" title="${event.tx_hash}">${event.tx_hash.substring(0, 10)}...</a>` : 
+                                        '-'
+                                    }
+                                </td>
+                            </tr>
+                        `).join('')}
+                    </tbody>
+                </table>
+            `;
+            
+            eventsContent.innerHTML = table;
+        }
+        
+        // 获取事件类型的CSS类
+        function getEventClass(eventType) {
+            if (eventType.includes('open')) return 'event-open';
+            if (eventType.includes('close')) return 'event-close';
+            return '';
+        }
+        
+        // 格式化事件类型
+        function formatEventType(eventType) {
+            const typeMap = {
+                'open_long': '开多仓',
+                'open_short': '开空仓',
+                'close_long': '平多仓',
+                'close_short': '平空仓',
+                'open_long_success': '开多仓成功',
+                'open_short_success': '开空仓成功',
+                'close_long_success': '平多仓成功',
+                'close_short_success': '平空仓成功',
+                'open_long_failed': '开多仓失败',
+                'open_short_failed': '开空仓失败',
+                'close_long_failed': '平多仓失败',
+                'close_short_failed': '平空仓失败'
+            };
+            
+            return typeMap[eventType] || eventType;
+        }
+        
+        // 显示错误信息
+        function showError(message) {
+            const container = document.querySelector('.container');
+            const errorDiv = document.createElement('div');
+            errorDiv.className = 'error';
+            errorDiv.textContent = message;
+            container.insertBefore(errorDiv, container.firstChild);
+            
+            // 5秒后自动移除错误信息
+            setTimeout(() => {
+                if (errorDiv.parentNode) {
+                    errorDiv.parentNode.removeChild(errorDiv);
+                }
+            }, 5000);
+        }
+    </script>
+</body>
+</html>

+ 224 - 0
src/leadlag/web_dashboard.py

@@ -0,0 +1,224 @@
+"""
+Web Dashboard for Trading Strategy Visualization
+展示价格数据和交易事件的Web界面
+"""
+
+from flask import Flask, render_template, jsonify, request
+import sqlite3
+import json
+from datetime import datetime, timedelta
+import os
+from typing import Dict, List, Any
+
+class TradingDashboard:
+    def __init__(self, db_path: str = "trading_data.db"):
+        self.app = Flask(__name__)
+        self.db_path = db_path
+        self.setup_routes()
+    
+    def setup_routes(self):
+        """设置路由"""
+        @self.app.route('/')
+        def index():
+            return render_template('dashboard.html')
+        
+        @self.app.route('/api/price_data')
+        def get_price_data():
+            """获取价格数据API"""
+            hours = request.args.get('hours', 24, type=int)
+            symbol = request.args.get('symbol', '')
+            
+            try:
+                data = self.get_price_data(hours, symbol)
+                return jsonify({
+                    'success': True,
+                    'data': data
+                })
+            except Exception as e:
+                return jsonify({
+                    'success': False,
+                    'error': str(e)
+                }), 500
+        
+        @self.app.route('/api/trading_events')
+        def get_trading_events():
+            """获取交易事件API"""
+            hours = request.args.get('hours', 24, type=int)
+            symbol = request.args.get('symbol', '')
+            
+            try:
+                events = self.get_trading_events(hours, symbol)
+                return jsonify({
+                    'success': True,
+                    'data': events
+                })
+            except Exception as e:
+                return jsonify({
+                    'success': False,
+                    'error': str(e)
+                }), 500
+        
+        @self.app.route('/api/stats')
+        def get_stats():
+            """获取统计数据API"""
+            try:
+                stats = self.get_statistics()
+                return jsonify({
+                    'success': True,
+                    'data': stats
+                })
+            except Exception as e:
+                return jsonify({
+                    'success': False,
+                    'error': str(e)
+                }), 500
+    
+    def get_price_data(self, hours: int = 24, symbol: str = '') -> List[Dict[str, Any]]:
+        """获取价格数据"""
+        conn = sqlite3.connect(self.db_path)
+        conn.row_factory = sqlite3.Row
+        
+        try:
+            query = """
+                SELECT timestamp, symbol, binance_price, lighter_price, 
+                       lighter_bid, lighter_ask, spread_bps
+                FROM price_data 
+                WHERE timestamp > datetime('now', '-{} hours')
+            """.format(hours)
+            
+            if symbol:
+                query += " AND symbol = ?"
+                cursor = conn.execute(query, (symbol,))
+            else:
+                cursor = conn.execute(query)
+            
+            rows = cursor.fetchall()
+            
+            data = []
+            for row in rows:
+                data.append({
+                    'timestamp': row['timestamp'],
+                    'symbol': row['symbol'],
+                    'binance_price': row['binance_price'],
+                    'lighter_price': row['lighter_price'],
+                    'lighter_bid': row['lighter_bid'],
+                    'lighter_ask': row['lighter_ask'],
+                    'spread_bps': row['spread_bps']
+                })
+            
+            return data
+        
+        finally:
+            conn.close()
+    
+    def get_trading_events(self, hours: int = 24, symbol: str = '') -> List[Dict[str, Any]]:
+        """获取交易事件"""
+        conn = sqlite3.connect(self.db_path)
+        conn.row_factory = sqlite3.Row
+        
+        try:
+            query = """
+                SELECT timestamp, symbol, event_type, price, quantity, 
+                       side, success, tx_hash, error_message, metadata
+                FROM trading_events 
+                WHERE timestamp > datetime('now', '-{} hours')
+                ORDER BY timestamp DESC
+            """.format(hours)
+            
+            if symbol:
+                query = query.replace("ORDER BY", "AND symbol = ? ORDER BY")
+                cursor = conn.execute(query, (symbol,))
+            else:
+                cursor = conn.execute(query)
+            
+            rows = cursor.fetchall()
+            
+            events = []
+            for row in rows:
+                metadata = json.loads(row['metadata']) if row['metadata'] else {}
+                events.append({
+                    'timestamp': row['timestamp'],
+                    'symbol': row['symbol'],
+                    'event_type': row['event_type'],
+                    'price': row['price'],
+                    'quantity': row['quantity'],
+                    'side': row['side'],
+                    'success': bool(row['success']),
+                    'tx_hash': row['tx_hash'],
+                    'error_message': row['error_message'],
+                    'metadata': metadata
+                })
+            
+            return events
+        
+        finally:
+            conn.close()
+    
+    def get_statistics(self) -> Dict[str, Any]:
+        """获取统计数据"""
+        conn = sqlite3.connect(self.db_path)
+        conn.row_factory = sqlite3.Row
+        
+        try:
+            # 获取基本统计
+            stats = {}
+            
+            # 价格数据统计
+            cursor = conn.execute("""
+                SELECT COUNT(*) as total_records,
+                       MIN(timestamp) as first_record,
+                       MAX(timestamp) as last_record
+                FROM price_data
+            """)
+            price_stats = cursor.fetchone()
+            
+            # 交易事件统计
+            cursor = conn.execute("""
+                SELECT event_type, COUNT(*) as count
+                FROM trading_events
+                GROUP BY event_type
+            """)
+            event_stats = cursor.fetchall()
+            
+            # 成功率统计
+            cursor = conn.execute("""
+                SELECT 
+                    SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successful,
+                    COUNT(*) as total
+                FROM trading_events
+                WHERE event_type LIKE '%_success' OR event_type LIKE '%_failed'
+            """)
+            success_stats = cursor.fetchone()
+            
+            stats = {
+                'price_data': {
+                    'total_records': price_stats['total_records'],
+                    'first_record': price_stats['first_record'],
+                    'last_record': price_stats['last_record']
+                },
+                'trading_events': {
+                    event['event_type']: event['count'] 
+                    for event in event_stats
+                },
+                'success_rate': {
+                    'successful': success_stats['successful'] or 0,
+                    'total': success_stats['total'] or 0,
+                    'rate': (success_stats['successful'] / success_stats['total'] * 100) 
+                           if success_stats['total'] > 0 else 0
+                }
+            }
+            
+            return stats
+        
+        finally:
+            conn.close()
+    
+    def run(self, host='127.0.0.1', port=5000, debug=True):
+        """启动Web服务器"""
+        print(f"启动交易策略监控面板: http://{host}:{port}")
+        self.app.run(host=host, port=port, debug=debug)
+
+if __name__ == '__main__':
+    # 创建并启动dashboard
+    dashboard = TradingDashboard()
+    dashboard.run()

+ 126 - 0
test_database.py

@@ -0,0 +1,126 @@
+#!/usr/bin/env python3
+"""
+测试数据库功能
+"""
+
+import sys
+import time
+import random
+from pathlib import Path
+
+# 添加项目路径到Python路径
+project_root = Path(__file__).parent
+sys.path.insert(0, str(project_root / "src"))
+
+from leadlag.database import TradingDatabase
+
+def test_database():
+    """测试数据库功能"""
+    print("🧪 开始测试数据库功能...")
+    
+    # 创建测试数据库
+    db_path = "test_trading_data.db"
+    db = TradingDatabase(db_path)
+    
+    print(f"✅ 数据库创建成功: {db_path}")
+    
+    # 测试价格数据记录
+    print("\n📊 测试价格数据记录...")
+    
+    base_price = 50000.0
+    for i in range(10):
+        # 模拟价格波动
+        binance_price = base_price + random.uniform(-100, 100)
+        lighter_price = binance_price + random.uniform(-5, 5)
+        spread_bps = (lighter_price - binance_price) / binance_price * 10000
+        
+        db.record_price_data(
+            symbol="BTCUSDT",
+            binance_price=binance_price,
+            lighter_price=lighter_price,
+            lighter_bid=lighter_price - 0.5,
+            lighter_ask=lighter_price + 0.5,
+            lighter_bid_size=random.uniform(0.1, 1.0),
+            lighter_ask_size=random.uniform(0.1, 1.0),
+            spread_bps=spread_bps,
+            raw_data={"test": True, "index": i}
+        )
+        
+        print(f"  记录 {i+1}: Binance={binance_price:.2f}, Lighter={lighter_price:.2f}, Spread={spread_bps:.2f}bps")
+        time.sleep(0.1)  # 模拟时间间隔
+    
+    # 测试交易事件记录
+    print("\n🔄 测试交易事件记录...")
+    
+    events = [
+        ("open_long", 49950.0, 0.1, "long", True),
+        ("open_long_success", 49950.0, 0.1, "long", True),
+        ("close_long", 50050.0, 0.1, "long", True),
+        ("close_long_success", 50050.0, 0.1, "long", True),
+        ("open_short", 50100.0, 0.1, "short", False),
+        ("open_short_failed", 50100.0, 0.1, "short", False)
+    ]
+    
+    for event_type, price, quantity, side, success in events:
+        db.record_trading_event(
+            symbol="BTCUSDT",
+            event_type=event_type,
+            price=price,
+            quantity=quantity,
+            side=side,
+            strategy_state="EXECUTING_OPEN" if "open" in event_type else "EXECUTING_CLOSE",
+            spread_bps=random.uniform(-10, 10),
+            lighter_price=price + random.uniform(-1, 1),
+            binance_price=price + random.uniform(-2, 2),
+            success=success,
+            tx_hash="0x123456789abcdef" if success else None,
+            error_message="Test error" if not success else None,
+            metadata={"test": True, "event": event_type}
+        )
+        
+        status = "✅ 成功" if success else "❌ 失败"
+        print(f"  事件: {event_type} - 价格={price:.2f}, 数量={quantity}, {status}")
+    
+    # 测试数据查询
+    print("\n📈 测试数据查询...")
+    
+    # 查询价格数据 (最近1小时)
+    current_time = time.time()
+    one_hour_ago = current_time - 3600
+    
+    price_data = db.get_price_data(start_time=one_hour_ago, limit=100)
+    print(f"  价格数据记录数: {len(price_data)}")
+    
+    # 查询交易事件
+    trading_events = db.get_trading_events(start_time=one_hour_ago, limit=100)
+    print(f"  交易事件记录数: {len(trading_events)}")
+    
+    # 查询统计信息
+    stats = db.get_statistics()
+    print(f"  统计信息: {stats}")
+    
+    print("\n🎉 数据库功能测试完成!")
+    print(f"📁 测试数据库文件: {Path(db_path).absolute()}")
+    
+    return db_path
+
+def main():
+    """主函数"""
+    try:
+        test_db_path = test_database()
+        
+        print("\n" + "="*60)
+        print("🚀 现在可以启动Web Dashboard查看测试数据:")
+        print(f"   python run_dashboard.py")
+        print("   或者修改 run_dashboard.py 中的数据库路径为:")
+        print(f"   {Path(test_db_path).absolute()}")
+        print("="*60)
+        
+    except Exception as e:
+        print(f"❌ 测试失败: {e}")
+        import traceback
+        traceback.print_exc()
+        sys.exit(1)
+
+if __name__ == '__main__':
+    main()

二进制
test_trading_data.db