diff --git a/app/api/trade_review.py b/app/api/trade_review.py index a87a0e8..fe9afd6 100644 --- a/app/api/trade_review.py +++ b/app/api/trade_review.py @@ -255,6 +255,8 @@ def get_kline_with_trades( from app.database import SessionLocal as MainSessionLocal from app.models import MarketData + logger.info(f"[K线查询] 请求参数: symbol={symbol}, period={period}, start_date={start_date}, end_date={end_date}") + # 判断是品种代码还是完整合约代码 variety_code = symbol.upper() if re.match(r'^[A-Za-z]+\d', symbol): @@ -265,29 +267,58 @@ def get_kline_with_trades( # 品种代码如 AG,从配置中查找当前合约 kline_symbol = _resolve_symbol_from_config(variety_code) + logger.info(f"[K线查询] 解析结果: variety_code={variety_code}, kline_symbol={kline_symbol}") + main_db = MainSessionLocal() try: + # 尝试精确匹配(先尝试原始symbol,再尝试小写) market_data = main_db.query(MarketData).filter( MarketData.symbol == kline_symbol, MarketData.period == period, ).first() + logger.info(f"[K线查询] 精确匹配查询: symbol={kline_symbol}, period={period}, found={market_data is not None}") + + if not market_data or not market_data.candles_json: + # 尝试小写匹配 + market_data = main_db.query(MarketData).filter( + MarketData.symbol == kline_symbol.lower(), + MarketData.period == period, + ).first() + logger.info(f"[K线查询] 小写匹配查询: symbol={kline_symbol.lower()}, period={period}, found={market_data is not None}") + if not market_data or not market_data.candles_json: - # 尝试模糊匹配:查找以该品种代码开头的合约 + # 尝试模糊匹配:查找以该品种代码开头的合约(不区分大小写) market_data = main_db.query(MarketData).filter( MarketData.symbol.like(f'{variety_code}%'), MarketData.period == period, ).first() + logger.info(f"[K线查询] 模糊匹配查询: like '{variety_code}%', period={period}, found={market_data is not None}") + if market_data: + logger.info(f"[K线查询] 模糊匹配找到: symbol={market_data.symbol}") if not market_data or not market_data.candles_json: + # 尝试小写模糊匹配 + market_data = main_db.query(MarketData).filter( + MarketData.symbol.like(f'{variety_code.lower()}%'), + MarketData.period == period, + ).first() + logger.info(f"[K线查询] 小写模糊匹配查询: like '{variety_code.lower()}%', period={period}, found={market_data is not None}") + if market_data: + logger.info(f"[K线查询] 小写模糊匹配找到: symbol={market_data.symbol}") + + if not market_data or not market_data.candles_json: + logger.warning(f"[K线查询] 未找到K线数据: {kline_symbol} {period}") return {"success": False, "message": f"未找到 {kline_symbol} 的 {period} K线数据"} candles = json.loads(market_data.candles_json) kline_symbol = market_data.symbol + logger.info(f"[K线查询] 成功获取K线数据: symbol={kline_symbol}, candles_count={len(candles)}") finally: main_db.close() # 获取该品种的交易记录 + logger.info(f"[K线查询] 开始查询交易记录: variety={variety_code}, start_date={start_date}, end_date={end_date}") query = db.query(TradeRecord).filter(TradeRecord.variety == variety_code) if start_date: query = query.filter(TradeRecord.trade_date >= start_date) @@ -295,6 +326,10 @@ def get_kline_with_trades( query = query.filter(TradeRecord.trade_date <= end_date) trades = query.order_by(TradeRecord.trade_date, TradeRecord.trade_time).all() + logger.info(f"[K线查询] 交易记录查询完成: 找到 {len(trades)} 条记录") + if len(trades) > 0: + logger.info(f"[K线查询] 交易记录示例: {[{'symbol': t.symbol, 'variety': t.variety, 'date': t.trade_date, 'direction': t.direction} for t in trades[:3]]}") + trade_markers = [] for t in trades: trade_markers.append({ @@ -309,6 +344,8 @@ def get_kline_with_trades( "commission": t.commission, }) + logger.info(f"[K线查询] 返回数据: symbol={kline_symbol}, period={period}, candles={len(candles)}, markers={len(trade_markers)}") + return { "success": True, "data": { diff --git a/app/static/futures_analysis.html b/app/static/futures_analysis.html index a588ea2..0c03578 100644 --- a/app/static/futures_analysis.html +++ b/app/static/futures_analysis.html @@ -546,6 +546,19 @@ .tr-kline-chart { width: 100%; height: 500px; background: var(--bg-card); border-radius: 16px; box-shadow: var(--shadow-sm); } .tr-chart-container { width: 100%; height: 300px; margin-bottom: 16px; background: var(--bg-card); border-radius: 16px; box-shadow: var(--shadow-sm); } + /* 交易详情弹窗样式 */ + .tr-detail-info { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; margin-bottom: 20px; padding: 16px; background: #F5F5F7; border-radius: 12px; } + .tr-detail-info-item { display: flex; flex-direction: column; gap: 4px; } + .tr-detail-info-label { font-size: 12px; color: var(--text-tertiary); font-weight: 500; } + .tr-detail-info-value { font-size: 14px; font-weight: 600; color: var(--text-primary); } + .tr-detail-info-value.profit { color: var(--color-down); } + .tr-detail-info-value.loss { color: var(--color-up); } + .tr-detail-section { margin-top: 20px; } + .tr-detail-section-title { font-size: 15px; font-weight: 700; color: var(--text-primary); margin-bottom: 12px; padding-left: 8px; border-left: 3px solid var(--accent-gold); } + .tr-detail-ai-content { padding: 16px; background: #F5F5F7; border-radius: 12px; min-height: 100px; font-size: 14px; line-height: 1.8; color: var(--text-primary); white-space: pre-wrap; } + .tr-pair-symbol-link { cursor: pointer; color: var(--accent-gold); text-decoration: none; font-weight: 700; } + .tr-pair-symbol-link:hover { text-decoration: underline; } + .tr-pairs-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 16px; } .tr-pair-card { background: var(--bg-card); border-radius: 16px; padding: 18px; box-shadow: var(--shadow-sm); transition: all 0.2s; position: relative; overflow: hidden; } .tr-pair-card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); } @@ -987,7 +1000,6 @@