From 4b6d73a5c429142aa9b133675fb2675596040101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Sat, 14 Feb 2026 11:34:37 +0800 Subject: [PATCH] 1 --- fix_trade_records.py | 189 +++++++++++++++++++++++++++++++ trading_system/binance_client.py | 6 +- 2 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 fix_trade_records.py diff --git a/fix_trade_records.py b/fix_trade_records.py new file mode 100644 index 0000000..e083bea --- /dev/null +++ b/fix_trade_records.py @@ -0,0 +1,189 @@ +import asyncio +import logging +import os +import sys +from datetime import datetime, timedelta + +# Add project root to path +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'backend')) + +from backend.database.connection import db +from backend.database.models import Trade, Account +from trading_system.binance_client import BinanceClient + +# Setup logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger("fix_trades") + +async def main(): + # Loop through active accounts + # Based on previous check, active accounts are 2, 3, 4 + active_account_ids = [2, 3, 4] + + # Check columns once + existing_columns = set() + try: + cols = db.execute_query("DESCRIBE trades") + existing_columns = {row['Field'] for row in cols} + except Exception as e: + logger.error(f"Failed to describe trades table: {e}") + return + + if 'realized_pnl' not in existing_columns: + logger.info("Adding 'realized_pnl' column to trades table...") + db.execute_update("ALTER TABLE trades ADD COLUMN realized_pnl DECIMAL(20, 8) NULL COMMENT '已实现盈亏'") + + if 'commission' not in existing_columns: + logger.info("Adding 'commission' column to trades table...") + db.execute_update("ALTER TABLE trades ADD COLUMN commission DECIMAL(20, 8) NULL COMMENT '手续费'") + + total_fixed = 0 + + for account_id in active_account_ids: + logger.info(f"Processing Account ID: {account_id}") + + # Get account credentials + creds = Account.get_credentials(account_id) + if not creds: + logger.error(f"No account credentials found for account {account_id}") + continue + + api_key, api_secret, use_testnet, status = creds + + if not api_key or not api_secret: + logger.warning(f"Skipping account {account_id}: No API key/secret") + continue + + if status != 'active': + logger.warning(f"Skipping account {account_id}: Status is {status}") + continue + + client = BinanceClient(api_key, api_secret, testnet=use_testnet) + try: + # Check for proxy in environment + proxy = os.environ.get('HTTP_PROXY') or os.environ.get('HTTPS_PROXY') + requests_params = {'proxy': proxy} if proxy else None + + await client.connect(requests_params=requests_params) + except Exception as e: + logger.error(f"Failed to connect to Binance for account {account_id}: {e}") + continue + + try: + # Get recent closed trades from DB (last 30 days) for this account + thirty_days_ago = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d %H:%M:%S') + + # Check if entry_time is int (unix timestamp) or string/datetime based on schema check + # Schema says entry_time is 'int unsigned', so it's a timestamp. + thirty_days_ago_ts = int((datetime.now() - timedelta(days=30)).timestamp()) + + query = """ + SELECT * FROM trades + WHERE status = 'closed' + AND account_id = %s + AND entry_time > %s + ORDER BY id DESC + """ + + trades = db.execute_query(query, (account_id, thirty_days_ago_ts)) + logger.info(f"Found {len(trades)} closed trades for account {account_id} from last 30 days.") + + updated_count = 0 + + for trade in trades: + symbol = trade['symbol'] + trade_id = trade['id'] + entry_time = trade['entry_time'] # Should be int + side = trade['side'] + + entry_ts_ms = entry_time * 1000 + + try: + # Get recent trades from Binance + recent_trades = await client.get_recent_trades(symbol, limit=50) + + # Filter trades after entry time + closing_trades = [ + t for t in recent_trades + if t.get('time', 0) > entry_ts_ms and float(t.get('realizedPnl', 0)) != 0 + ] + + if not closing_trades: + continue + + # Calculate actual values + total_pnl = 0.0 + total_comm = 0.0 + total_qty = 0.0 + total_val = 0.0 + + for t in closing_trades: + pnl_val = float(t.get('realizedPnl', 0)) + comm_val = float(t.get('commission', 0)) + qty_val = float(t.get('qty', 0)) + price_val = float(t.get('price', 0)) + + total_pnl += pnl_val + total_comm += comm_val + total_qty += qty_val + total_val += qty_val * price_val + + if total_qty == 0: + continue + + avg_exit_price = total_val / total_qty + + # Check if values differ significantly from DB + db_pnl = float(trade.get('pnl') or 0) + db_exit_price = float(trade.get('exit_price') or 0) + + needs_update = False + if abs(db_pnl - total_pnl) > 0.01: + needs_update = True + + if 'realized_pnl' not in trade or trade.get('realized_pnl') is None: + needs_update = True + + if needs_update: + logger.info(f"Fixing trade {trade_id} ({symbol}): PnL {db_pnl:.4f} -> {total_pnl:.4f}, ExitPrice {db_exit_price:.4f} -> {avg_exit_price:.4f}") + + # Recalculate pnl_percent based on entry price + entry_price = float(trade.get('entry_price', 1)) + if entry_price == 0: + entry_price = 1 + + if side == 'BUY': + pnl_percent = ((avg_exit_price - entry_price) / entry_price) * 100 + else: + pnl_percent = ((entry_price - avg_exit_price) / entry_price) * 100 + + # Update DB + update_sql = """ + UPDATE trades + SET pnl = %s, + pnl_percent = %s, + exit_price = %s, + realized_pnl = %s, + commission = %s + WHERE id = %s + """ + db.execute_update(update_sql, (total_pnl, pnl_percent, avg_exit_price, total_pnl, total_comm, trade_id)) + updated_count += 1 + + except Exception as e: + logger.error(f"Error processing trade {trade_id} ({symbol}): {e}") + + logger.info(f"Account {account_id}: Fixed {updated_count} trades.") + total_fixed += updated_count + + if client.client: + await client.client.close_connection() + + except Exception as e: + logger.error(f"Error processing account {account_id}: {e}") + + logger.info(f"Total fixed trades: {total_fixed}") + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/trading_system/binance_client.py b/trading_system/binance_client.py index 1fc8c97..d0d1ae3 100644 --- a/trading_system/binance_client.py +++ b/trading_system/binance_client.py @@ -155,13 +155,14 @@ class BinanceClient: # 注意:redis_cache 已在 __init__ 中初始化,这里不需要再次初始化 - async def connect(self, timeout: int = None, retries: int = None): + async def connect(self, timeout: int = None, retries: int = None, requests_params: Dict = None): """ 连接币安API Args: timeout: 连接超时时间(秒),默认从config读取 retries: 重试次数,默认从config读取 + requests_params: 请求参数(如代理配置),例如 {'proxy': 'http://127.0.0.1:7890'} """ # 连接前刷新API密钥(确保使用最新值,支持热更新) # 但如果 API 密钥为空(只用于获取公开行情),则跳过 @@ -185,7 +186,8 @@ class BinanceClient: self.client = await AsyncClient.create( api_key=self.api_key or None, # 空字符串转为 None api_secret=self.api_secret or None, - testnet=self.testnet + testnet=self.testnet, + requests_params=requests_params ) # 测试连接(带超时)