From a38f5ff05d5944597dc0985011f66f994aa1c853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Thu, 5 Feb 2026 19:38:18 +0800 Subject: [PATCH] 1 --- check_account_status.py | 93 ++++++++++++++++++++ check_trades.py | 78 +++++++++++++++++ debug_sltp.py | 61 ++++++++++++++ fix_db_positions.py | 131 +++++++++++++++++++++++++++++ trading_system/position_manager.py | 34 ++++++-- 5 files changed, 392 insertions(+), 5 deletions(-) create mode 100644 check_account_status.py create mode 100644 check_trades.py create mode 100644 debug_sltp.py create mode 100644 fix_db_positions.py diff --git a/check_account_status.py b/check_account_status.py new file mode 100644 index 0000000..ef3bc9f --- /dev/null +++ b/check_account_status.py @@ -0,0 +1,93 @@ +import asyncio +import sys +import os +from datetime import datetime + +# Add path +sys.path.insert(0, '/Users/vivian/work/python/auto_trade_sys') +sys.path.insert(0, '/Users/vivian/work/python/auto_trade_sys/backend') + +from trading_system.binance_client import BinanceClient +from config_manager import ConfigManager + +async def check_account(account_id): + print(f"\n{'='*20} Checking Account {account_id} {'='*20}") + try: + # Initialize ConfigManager for this account + config_manager = ConfigManager(account_id=account_id) + api_key = config_manager.get('BINANCE_API_KEY') + api_secret = config_manager.get('BINANCE_API_SECRET') + + if not api_key or not api_secret: + print(f"Skipping Account {account_id}: Missing API keys") + return + + # Initialize client with specific keys + client = BinanceClient(api_key=api_key, api_secret=api_secret) + # Connect + await client.connect() + + # Get account information + # Access the underlying AsyncClient + if not client.client: + print(f"Failed to connect to Binance for Account {account_id}") + return + + # Check positions + print("=== Current Positions ===") + positions = await client.client.futures_position_information() + + has_positions = False + for pos in positions: + symbol = pos['symbol'] + amt = float(pos['positionAmt']) + if amt != 0: + has_positions = True + entry_price = float(pos['entryPrice']) + mark_price = float(pos['markPrice']) + unrealized_profit = float(pos['unRealizedProfit']) + + # Calculate percentage + if entry_price > 0: + if amt > 0: + pnl_pct = (mark_price - entry_price) / entry_price * 100 + else: + pnl_pct = (entry_price - mark_price) / entry_price * 100 + else: + pnl_pct = 0 + + print(f"Symbol: {symbol}") + print(f" Amount: {amt}") + print(f" Entry Price: {entry_price}") + print(f" Mark Price: {mark_price}") + print(f" Unrealized PnL: {unrealized_profit:.2f} USDT ({pnl_pct:.2f}%)") + print("-" * 30) + + if not has_positions: + print("No open positions.") + + # Check open orders + print("\n=== Open Orders (for active positions) ===") + if has_positions: + for pos in positions: + if float(pos['positionAmt']) != 0: + symbol = pos['symbol'] + orders = await client.get_open_orders(symbol) + if orders: + print(f"Orders for {symbol}:") + for order in orders: + print(f" Type: {order['type']}, Side: {order['side']}, Price: {order['price']}, StopPrice: {order.get('stopPrice', 'N/A')}") + else: + print(f"No open orders for {symbol}") + + await client.close() + + except Exception as e: + print(f"Error checking Account {account_id}: {e}") + +async def main(): + for account_id in [1, 2, 3, 4]: + await check_account(account_id) + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/check_trades.py b/check_trades.py new file mode 100644 index 0000000..a8dd397 --- /dev/null +++ b/check_trades.py @@ -0,0 +1,78 @@ +import pymysql +import os +from datetime import datetime + +# DB Config +DB_HOST = 'localhost' +DB_USER = 'root' +DB_PASSWORD = '12345678' +DB_NAME = 'auto_trade_sys_new' + +def connect_db(): + return pymysql.connect( + host=DB_HOST, + user=DB_USER, + password=DB_PASSWORD, + database=DB_NAME, + charset='utf8mb4', + cursorclass=pymysql.cursors.DictCursor + ) + +def main(): + try: + conn = connect_db() + cursor = conn.cursor() + + print(f"Connected to {DB_NAME}") + + # Check all databases + print("\n=== All Databases ===") + cursor.execute("SHOW DATABASES") + for db in cursor.fetchall(): + print(db['Database']) + + # 1. Check Open Positions for Account 2 + print("\n=== Open Positions (Account 2) ===") + cursor.execute(""" + SELECT id, symbol, side, entry_price, quantity, leverage, stop_loss_price, take_profit_price, created_at + FROM trades + WHERE status = 'open' AND account_id = 2 + ORDER BY created_at DESC + """) + open_trades = cursor.fetchall() + + if not open_trades: + print("No open positions found for Account 2.") + else: + for trade in open_trades: + entry_time = datetime.fromtimestamp(trade['created_at']).strftime('%Y-%m-%d %H:%M:%S') + print(f"ID: {trade['id']}, Symbol: {trade['symbol']}, Side: {trade['side']}, Entry: {trade['entry_price']}, SL: {trade['stop_loss_price']}, TP: {trade['take_profit_price']}, Time: {entry_time}") + + # 2. Check Recent Closed Trades for Account 2 + cursor.execute(f"SELECT id, symbol, side, status, pnl, pnl_percent, stop_loss_price, take_profit_price, entry_time, quantity, entry_price FROM trades WHERE account_id = 2 ORDER BY id DESC LIMIT 10") + trades = cursor.fetchall() + print(f"Account 2 recent trades:") + for t in trades: + # Handle entry_time conversion if it's a timestamp or datetime + entry_time = t.get('entry_time') + print(f"ID: {t['id']}, Symbol: {t['symbol']}, Side: {t['side']}, Status: {t['status']}, PnL: {t['pnl']}, PnL%: {t['pnl_percent']}, SL: {t['stop_loss_price']}, TP: {t['take_profit_price']}, Time: {entry_time}, Qty: {t['quantity']}, Price: {t['entry_price']}") + + # 3. Check for the specific "All take profits" claim + print("\n=== Checking for Negative PnL with Take Profit Reason ===") + cursor.execute(""" + SELECT COUNT(*) as count + FROM trades + WHERE status != 'open' + AND exit_reason = 'take_profit' + AND pnl < 0 + """) + count = cursor.fetchone()['count'] + print(f"Number of 'take_profit' trades with negative PnL: {count}") + + conn.close() + + except Exception as e: + print(f"Error: {e}") + +if __name__ == "__main__": + main() diff --git a/debug_sltp.py b/debug_sltp.py new file mode 100644 index 0000000..fce6018 --- /dev/null +++ b/debug_sltp.py @@ -0,0 +1,61 @@ + +import asyncio +import logging +import sys +from pathlib import Path + +# Setup path +project_root = Path(__file__).parent +sys.path.insert(0, str(project_root)) +sys.path.insert(0, str(project_root / 'trading_system')) +sys.path.insert(0, str(project_root / 'backend')) + +# Mock config +from trading_system import config +config.TRADING_CONFIG = { + 'STOP_LOSS_PERCENT': 0.03, + 'TAKE_PROFIT_PERCENT': 0.05, + 'ATR_STOP_LOSS_MULTIPLIER': 2.5, + 'USE_ATR_STOP_LOSS': True +} + +from trading_system.risk_manager import RiskManager +from trading_system.position_manager import PositionManager + +# Mock classes +class MockClient: + pass + +async def test_risk_manager(): + print("Testing RiskManager...") + rm = RiskManager(None) + + entry_price = 100.0 + side = 'BUY' + quantity = 1.0 + leverage = 10 + stop_loss_pct = 0.03 + + # Case 1: No ATR, No Klines + sl = rm.get_stop_loss_price(entry_price, side, quantity, leverage, stop_loss_pct=stop_loss_pct) + print(f"Case 1 (Basic): SL = {sl}") + + # Case 2: With ATR + atr = 2.0 + sl_atr = rm.get_stop_loss_price(entry_price, side, quantity, leverage, stop_loss_pct=stop_loss_pct, atr=atr) + print(f"Case 2 (ATR={atr}): SL = {sl_atr}") + + # Case 3: SELL side + side = 'SELL' + sl_sell = rm.get_stop_loss_price(entry_price, side, quantity, leverage, stop_loss_pct=stop_loss_pct) + print(f"Case 3 (SELL): SL = {sl_sell}") + + # Case 4: Zero/None input + sl_none = rm.get_stop_loss_price(entry_price, side, quantity, leverage, stop_loss_pct=None) + print(f"Case 4 (None pct): SL = {sl_none}") + + sl_zero = rm.get_stop_loss_price(entry_price, side, quantity, leverage, stop_loss_pct=0) + print(f"Case 4 (Zero pct): SL = {sl_zero}") + +if __name__ == "__main__": + asyncio.run(test_risk_manager()) diff --git a/fix_db_positions.py b/fix_db_positions.py new file mode 100644 index 0000000..111b262 --- /dev/null +++ b/fix_db_positions.py @@ -0,0 +1,131 @@ + +import asyncio +import sys +import os +from pathlib import Path + +# Add project root to path +project_root = Path(__file__).parent +sys.path.insert(0, str(project_root)) +sys.path.insert(0, str(project_root / 'backend')) + +from database.connection import db +from database.models import Trade +from trading_system import config +from trading_system.risk_manager import RiskManager + +async def fix_missing_sltp(): + print("Checking for open trades with missing SL/TP across all accounts...") + + all_open_trades = [] + # Iterate through potential account IDs + for account_id in [1, 2, 3, 4]: + print(f"\nChecking Account {account_id}...") + + try: + # Check if Trade.get_all supports account_id argument + trades = Trade.get_all(status='open', account_id=account_id) + except TypeError: + print(f"Warning: Trade.get_all might not support account_id. Checking default.") + trades = Trade.get_all(status='open') + if account_id > 1: break + + if trades: + print(f"Found {len(trades)} open trades for Account {account_id}.") + all_open_trades.extend(trades) + else: + print(f"No open trades found for Account {account_id}.") + + open_trades = all_open_trades + if not open_trades: + print("No open trades found.") + return + + print(f"Found {len(open_trades)} open trades total.") + + # Initialize RiskManager (lightweight) + # We don't need a real client if we just use basic calculation + rm = RiskManager(None) + + updates = 0 + + for trade in open_trades: + t_id = trade['id'] + symbol = trade['symbol'] + entry_price = float(trade['entry_price'] or 0) + quantity = float(trade['quantity'] or 0) + side = trade['side'] + leverage = int(trade['leverage'] or 10) + sl = trade.get('stop_loss_price') + tp = trade.get('take_profit_price') + + if entry_price <= 0: + print(f"Skipping trade {t_id} ({symbol}): Invalid entry price {entry_price}") + continue + + needs_update = False + + # Calculate defaults + # Config defaults: SL 3%, TP 10% (or 30%?) + # Logic from position_manager: + stop_loss_pct = config.TRADING_CONFIG.get('STOP_LOSS_PERCENT', 0.03) + if stop_loss_pct > 1: stop_loss_pct /= 100.0 + + take_profit_pct = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.10) + if take_profit_pct > 1: take_profit_pct /= 100.0 + + # Calculate SL + if sl is None or float(sl) <= 0: + print(f"Trade {t_id} ({symbol}) missing SL. Calculating...") + # Use RiskManager logic manually or call it + # We'll use the basic margin-based logic + position_value = entry_price * quantity + margin = position_value / leverage if leverage > 0 else position_value + stop_loss_amount = margin * stop_loss_pct + + if side == 'BUY': + new_sl = entry_price - (stop_loss_amount / quantity) + else: + new_sl = entry_price + (stop_loss_amount / quantity) + + sl = new_sl + needs_update = True + print(f" -> Calculated SL: {sl:.4f} (Margin: {margin:.2f}, SL Amount: {stop_loss_amount:.2f})") + + # Calculate TP + if tp is None or float(tp) <= 0: + print(f"Trade {t_id} ({symbol}) missing TP. Calculating...") + # Use basic logic + # Default TP is usually SL distance * 3 or fixed pct + # Position manager uses config TAKE_PROFIT_PERCENT + + if take_profit_pct is None or take_profit_pct == 0: + take_profit_pct = stop_loss_pct * 3.0 + + tp_price = rm.get_take_profit_price( + entry_price, side, quantity, leverage, + take_profit_pct=take_profit_pct, + atr=None, + stop_distance=abs(entry_price - float(sl)) + ) + tp = tp_price + needs_update = True + print(f" -> Calculated TP: {tp:.4f}") + + if needs_update: + try: + db.execute_update( + "UPDATE trades SET stop_loss_price = %s, take_profit_price = %s WHERE id = %s", + (sl, tp, t_id) + ) + print(f"✓ Updated trade {t_id} ({symbol})") + updates += 1 + except Exception as e: + print(f"❌ Failed to update trade {t_id}: {e}") + else: + print(f"Trade {t_id} ({symbol}) OK.") + + print(f"Fixed {updates} trades.") + +if __name__ == "__main__": + asyncio.run(fix_missing_sltp()) diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index fc964d7..8a94620 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -57,16 +57,29 @@ if get_beijing_time is None: class PositionManager: """仓位管理类""" - def __init__(self, client: BinanceClient, risk_manager: RiskManager): + def __init__(self, client: BinanceClient, risk_manager: RiskManager, account_id: int = None): """ 初始化仓位管理器 Args: client: 币安客户端 risk_manager: 风险管理器 + account_id: 账户ID(默认从环境变量或配置获取) """ self.client = client self.risk_manager = risk_manager + + # 确定 account_id + if account_id is not None: + self.account_id = int(account_id) + else: + # 尝试从环境变量获取 + import os + try: + self.account_id = int(os.getenv("ATS_ACCOUNT_ID") or os.getenv("ACCOUNT_ID") or 1) + except: + self.account_id = 1 + self.active_positions: Dict[str, Dict] = {} self._monitor_tasks: Dict[str, asyncio.Task] = {} # WebSocket监控任务字典 self._monitoring_enabled = True # 是否启用实时监控 @@ -632,6 +645,7 @@ class PositionManager: notional_usdt=notional_usdt, margin_usdt=margin_usdt, entry_context=entry_context, # 入场思路与过程(便于事后分析策略执行效果) + account_id=self.account_id ) logger.info(f"✓ {symbol} 交易记录已保存到数据库 (ID: {trade_id}, 订单号: {entry_order_id}, 成交价: {entry_price:.4f}, 成交数量: {quantity:.4f})") except Exception as e: @@ -668,7 +682,8 @@ class PositionManager: 'strategyType': 'trend_following', # 策略类型(简化后只有趋势跟踪) 'atr': atr, 'maxProfit': 0.0, # 记录最大盈利(用于移动止损) - 'trailingStopActivated': False # 移动止损是否已激活 + 'trailingStopActivated': False, # 移动止损是否已激活 + 'account_id': self.account_id } self.active_positions[symbol] = position_info @@ -2018,8 +2033,8 @@ class PositionManager: binance_symbols = {p['symbol'] for p in binance_positions} logger.debug(f"币安实际持仓: {len(binance_symbols)} 个 ({', '.join(binance_symbols) if binance_symbols else '无'})") - # 2. 获取数据库中状态为open的交易记录 - db_open_trades = Trade.get_all(status='open') + # 2. 获取数据库中状态为open的交易记录(仅当前账号) + db_open_trades = Trade.get_all(status='open', account_id=self.account_id) db_open_symbols = {t['symbol'] for t in db_open_trades} logger.debug(f"数据库open状态: {len(db_open_symbols)} 个 ({', '.join(db_open_symbols) if db_open_symbols else '无'})") @@ -2035,7 +2050,7 @@ class PositionManager: # 4. 更新这些持仓的状态 for symbol in missing_in_binance: try: - trades = Trade.get_by_symbol(symbol, status='open') + trades = Trade.get_by_symbol(symbol, status='open', account_id=self.account_id) if not trades: logger.warning(f"{symbol} [状态同步] ⚠️ 数据库中没有找到open状态的交易记录,跳过") continue @@ -2370,6 +2385,15 @@ class PositionManager: exit_reason = "stop_loss" logger.info(f"{trade.get('symbol')} [同步] 特征判断:持仓{duration_minutes:.1f}分钟,亏损{pnl_percent_for_judge:.2f}% of margin,价格在止损方向,标记为止损") + # ⚠️ 2026-02-05 修复:防止亏损单被错误标记为止盈 + if pnl_percent_for_judge < 0 and exit_reason == "take_profit": + logger.warning(f"{symbol} [状态同步] ⚠️ 亏损单({pnl_percent_for_judge:.2f}%)被标记为止盈,强制修正为manual/stop_loss") + # 如果亏损较大,归为止损 + if pnl_percent_for_judge < -5.0: + exit_reason = "stop_loss" + else: + exit_reason = "manual" + # 4. 如果之前标记为 sync 且是 reduceOnly 订单,但价格不匹配止损/止盈,可能是其他自动平仓(如移动止损) if exit_reason == "sync" and is_reduce_only: # 检查是否是移动止损:如果价格接近入场价,可能是移动止损触发的