diff --git a/backend_api_python/app/data_sources/base.py b/backend_api_python/app/data_sources/base.py index 9b54d242f..5eff84f81 100644 --- a/backend_api_python/app/data_sources/base.py +++ b/backend_api_python/app/data_sources/base.py @@ -140,10 +140,10 @@ def log_result( if klines: latest_time = datetime.fromtimestamp(klines[-1]['time']) time_diff = (datetime.now() - latest_time).total_seconds() - logger.info( - f"{self.name}: {symbol} 获取 {len(klines)} 条数据, " - f"最新时间: {latest_time}, 延迟: {time_diff:.0f}秒" - ) + # logger.info( + # f"{self.name}: {symbol} 获取 {len(klines)} 条数据, " + # f"最新时间: {latest_time}, 延迟: {time_diff:.0f}秒" + # ) # 检查数据是否过旧 max_diff = TIMEFRAME_SECONDS.get(timeframe, 3600) * 2 diff --git a/backend_api_python/app/routes/portfolio.py b/backend_api_python/app/routes/portfolio.py index d21e3bf25..210dd2b83 100644 --- a/backend_api_python/app/routes/portfolio.py +++ b/backend_api_python/app/routes/portfolio.py @@ -255,7 +255,7 @@ def add_position(): INSERT INTO qd_manual_positions (user_id, market, symbol, name, side, quantity, entry_price, entry_time, notes, tags, group_name, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) - ON CONFLICT(user_id, market, symbol, side) DO UPDATE SET + ON CONFLICT(user_id, market, symbol, side, group_name) DO UPDATE SET name = excluded.name, quantity = excluded.quantity, entry_price = excluded.entry_price, diff --git a/backend_api_python/app/services/portfolio_monitor.py b/backend_api_python/app/services/portfolio_monitor.py index 0b63334a6..0c35f0311 100644 --- a/backend_api_python/app/services/portfolio_monitor.py +++ b/backend_api_python/app/services/portfolio_monitor.py @@ -91,7 +91,7 @@ def _get_positions_for_monitor(position_ids: List[int] = None, user_id: int = No placeholders = ','.join(['?' for _ in position_ids]) cur.execute( f""" - SELECT id, market, symbol, name, side, quantity, entry_price + SELECT id, market, symbol, name, side, quantity, entry_price, group_name FROM qd_manual_positions WHERE user_id = ? AND id IN ({placeholders}) """, @@ -100,7 +100,7 @@ def _get_positions_for_monitor(position_ids: List[int] = None, user_id: int = No else: cur.execute( """ - SELECT id, market, symbol, name, side, quantity, entry_price + SELECT id, market, symbol, name, side, quantity, entry_price, group_name FROM qd_manual_positions WHERE user_id = ? """, @@ -116,6 +116,7 @@ def _get_positions_for_monitor(position_ids: List[int] = None, user_id: int = No entry_price = float(row.get('entry_price') or 0) quantity = float(row.get('quantity') or 0) side = row.get('side') or 'long' + group_name = row.get('group_name') # Get current price (use realtime price API) current_price = 0 @@ -143,7 +144,8 @@ def _get_positions_for_monitor(position_ids: List[int] = None, user_id: int = No 'entry_price': entry_price, 'current_price': current_price, 'pnl': round(pnl, 2), - 'pnl_percent': pnl_percent + 'pnl_percent': pnl_percent, + 'group_name': group_name }) return positions @@ -168,6 +170,7 @@ def _run_ai_analysis(positions: List[Dict[str, Any]], config: Dict[str, Any]) -> market = pos.get('market') symbol = pos.get('symbol') name = pos.get('name') or symbol + group_name = pos.get('group_name') if not market or not symbol: continue @@ -193,6 +196,7 @@ def _run_ai_analysis(positions: List[Dict[str, Any]], config: Dict[str, Any]) -> 'market': market, 'symbol': symbol, 'name': name, + 'group_name': group_name, 'entry_price': pos.get('entry_price'), 'current_price': pos.get('current_price'), 'pnl': pos.get('pnl'), @@ -454,6 +458,7 @@ def _build_html_report( symbol = pa.get('symbol', '') name = pa.get('name', symbol) market = pa.get('market', '') + group_name = pa.get('group_name', '') if pa.get('error'): html += f''' @@ -538,7 +543,7 @@ def _build_html_report( ''' # Generate unique ID for collapsible sections (use symbol hash to avoid special chars) - section_id_base = hashlib.md5(f"{symbol}_{market}".encode()).hexdigest()[:8] + section_id_base = hashlib.md5(f"{symbol}_{market}_{group_name}".encode()).hexdigest()[:8] # Collapsible: Trader Analysis if trader_reasoning: diff --git a/backend_api_python/app/services/signal_notifier.py b/backend_api_python/app/services/signal_notifier.py index ce2c8b32d..8f4175348 100644 --- a/backend_api_python/app/services/signal_notifier.py +++ b/backend_api_python/app/services/signal_notifier.py @@ -24,6 +24,7 @@ import os import smtplib import time +import traceback from datetime import datetime, timezone from email.message import EmailMessage from typing import Any, Dict, List, Optional, Tuple @@ -482,6 +483,7 @@ def _notify_browser( return True, "" except Exception as e: logger.warning(f"browser notify persist failed: {e}") + logger.error('browser.error', traceback=traceback.format_exc()) return False, str(e) def _notify_webhook( @@ -571,6 +573,7 @@ def _post_once(timeout: float) -> requests.Response: return False, f"http_{resp2.status_code}:{(resp2.text or '')[:300]}" return False, f"http_{resp.status_code}:{(resp.text or '')[:300]}" except Exception as e: + logger.error('webhook.error', traceback=traceback.format_exc()) return False, str(e) def _notify_discord(self, *, url: str, payload: Dict[str, Any], fallback_text: str) -> Tuple[bool, str]: @@ -646,6 +649,7 @@ def _post(payload_json: Dict[str, Any]) -> requests.Response: pass return False, f"http_{resp.status_code}:{(resp.text or '')[:300]}" except Exception as e: + logger.error('discord.error', traceback=traceback.format_exc()) return False, str(e) def _notify_telegram( @@ -680,6 +684,7 @@ def _notify_telegram( return True, "" return False, f"http_{resp.status_code}:{(resp.text or '')[:300]}" except Exception as e: + logger.error('telegram.error', traceback=traceback.format_exc()) return False, str(e) def _notify_email(self, *, to_email: str, subject: str, body_text: str, body_html: str = "") -> Tuple[bool, str]: @@ -718,6 +723,7 @@ def _notify_email(self, *, to_email: str, subject: str, body_text: str, body_htm server.send_message(msg) return True, "" except Exception as e: + logger.error('email.error', traceback=traceback.format_exc()) return False, str(e) def _notify_phone(self, *, to_phone: str, body: str) -> Tuple[bool, str]: @@ -734,6 +740,7 @@ def _notify_phone(self, *, to_phone: str, body: str) -> Tuple[bool, str]: return True, "" return False, f"http_{resp.status_code}:{(resp.text or '')[:300]}" except Exception as e: + logger.error('phone.error', traceback=traceback.format_exc()) return False, str(e) diff --git a/backend_api_python/migrations/init.sql b/backend_api_python/migrations/init.sql index bedb14aec..66175283b 100644 --- a/backend_api_python/migrations/init.sql +++ b/backend_api_python/migrations/init.sql @@ -439,7 +439,7 @@ CREATE TABLE IF NOT EXISTS qd_manual_positions ( group_name VARCHAR(100) DEFAULT '', created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), - UNIQUE(user_id, market, symbol, side) + UNIQUE(user_id, market, symbol, side, group_name) ); CREATE INDEX IF NOT EXISTS idx_manual_positions_user_id ON qd_manual_positions(user_id);