diff --git a/scripts/fix_trade_records.py b/scripts/fix_trade_records.py index a5f018e..31804da 100644 --- a/scripts/fix_trade_records.py +++ b/scripts/fix_trade_records.py @@ -3,23 +3,42 @@ import asyncio import logging import os import sys +import time from datetime import datetime, timedelta +from decimal import Decimal +from typing import List, Dict, Any, Optional # Add project root to path sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from trading_system.binance_client import BinanceClient -from binance import AsyncClient from backend.database.connection import db from backend.database.models import Trade, Account +from backend.config_manager import ConfigManager +from trading_system.binance_client import BinanceClient +from binance import AsyncClient # Configure logging logging.basicConfig( level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s' + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) +async def get_fills_with_retry(client, symbol, start_time_ms, retries=3): + """Fetch fills with retry logic""" + for i in range(retries): + try: + return await client.client.futures_account_trades( + symbol=symbol, + startTime=start_time_ms, + limit=100 + ) + except Exception as e: + if i == retries - 1: + raise e + logger.warning(f"Error fetching fills for {symbol} (Attempt {i+1}/{retries}): {e}. Retrying...") + await asyncio.sleep(2) + async def fix_time_inversion_trades(client, account_id): """Fix trades where exit_time < entry_time""" logger.info("Scanning for time-inverted trades...") @@ -35,13 +54,13 @@ async def fix_time_inversion_trades(client, account_id): if not trades: logger.info("No time-inverted trades found.") + return for trade in trades: symbol = trade['symbol'] trade_id = trade['id'] entry_time = trade['entry_time'] exit_time = trade['exit_time'] - quantity = trade['quantity'] side = trade['side'] # OPEN side (BUY/SELL) logger.info(f"Found invalid trade #{trade_id} {symbol}: Entry={entry_time}, Exit={exit_time} (Diff: {exit_time - entry_time}s)") @@ -51,18 +70,14 @@ async def fix_time_inversion_trades(client, account_id): start_time_ms = int(entry_time * 1000) try: - # Fetch user trades (fills) after entry time - fills = await client.client.futures_account_trades( - symbol=symbol, - startTime=start_time_ms, - limit=100 - ) + # Fetch user trades (fills) after entry time with retry + fills = await get_fills_with_retry(client, symbol, start_time_ms) # Filter for closing trades # If entry side was BUY, we look for SELL. If SELL, look for BUY. close_side = 'SELL' if side == 'BUY' else 'BUY' - closing_fills = [f for f in fills if f['side'] == close_side and f['realizedPnl'] != '0'] + closing_fills = [f for f in fills if f['side'] == close_side and float(f['realizedPnl']) != 0] if not closing_fills: # No closing trades found. Position might still be open or we missed it? @@ -82,7 +97,6 @@ async def fix_time_inversion_trades(client, account_id): else: # Found closing fills. Calculate metrics. # We assume the latest fills correspond to this trade. - # This is a simplification but better than "ghost" order. # Sort by time desc closing_fills.sort(key=lambda x: x['time'], reverse=True) @@ -127,9 +141,7 @@ async def fix_time_inversion_trades(client, account_id): ) except Exception as e: - logger.error(f"Error processing trade #{trade_id}: {type(e).__name__}: {e}") - import traceback - logger.error(traceback.format_exc()) + logger.error(f"Error processing trade #{trade_id}: {e}") async def backfill_commissions(client, account_id): """Backfill missing commission data for recent trades""" diff --git a/scripts/inspect_trade.py b/scripts/inspect_trade.py new file mode 100644 index 0000000..94b3a69 --- /dev/null +++ b/scripts/inspect_trade.py @@ -0,0 +1,21 @@ + +import os +import sys +from backend.database.connection import db + +def inspect(): + try: + sql = "SELECT id, symbol, commission, commission_asset, exit_time, account_id FROM trades WHERE id = 3356" + res = db.execute_query(sql) + print(f"Trade 3356: {res}") + + # Also check trade 617 + sql = "SELECT id, symbol, entry_time, exit_time FROM trades WHERE id = 617" + res = db.execute_query(sql) + print(f"Trade 617: {res}") + + except Exception as e: + print(f"Error: {e}") + +if __name__ == "__main__": + inspect() diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 918a3b9..78ffebb 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -583,7 +583,7 @@ class PositionManager: # 如果ATR可用,使用ATR计算更准确的止损距离(用于盈亏比止盈) if atr is not None and atr > 0 and entry_price > 0: atr_percent = atr / entry_price - atr_multiplier = config.TRADING_CONFIG.get('ATR_STOP_LOSS_MULTIPLIER', 2.0) # 2026-01-29优化:默认2.0 + atr_multiplier = config.TRADING_CONFIG.get('ATR_STOP_LOSS_MULTIPLIER', 3.0) # 2026-02-12优化:默认3.0,避免噪音止损 # 使用ATR计算的止损距离(更准确,用于盈亏比止盈) stop_distance_for_tp = entry_price * atr_percent * atr_multiplier @@ -634,6 +634,13 @@ class PositionManager: logger.info(f"{symbol} [优化] TP1距离 ({tp1_distance:.4f}) 小于 {min_rr_for_tp1}倍止损距离 ({min_tp1_distance:.4f}),已自动调整以保证盈亏比") tp1_distance = min_tp1_distance + # ⚠️ 2026-02-12优化:确保TP1至少覆盖双向手续费(避免微利单实际上亏损) + # 手续费率估算:0.05% (Taker) * 2 (双向) * 1.5 (安全系数) = 0.15% 价格变动 + min_fee_distance = entry_price * 0.0015 + if min_fee_distance > tp1_distance: + logger.info(f"{symbol} [优化] TP1距离 ({tp1_distance:.4f}) 小于 手续费磨损距离 ({min_fee_distance:.4f}),已自动调整以覆盖成本") + tp1_distance = min_fee_distance + # 计算最终TP1价格 if tp1_distance > 0: if side == 'BUY': diff --git a/trading_system/risk_manager.py b/trading_system/risk_manager.py index 4a5ec40..505cc0d 100644 --- a/trading_system/risk_manager.py +++ b/trading_system/risk_manager.py @@ -292,7 +292,7 @@ class RiskManager: # 尝试获取止损价格(如果没有传入,尝试估算) target_stop_loss = stop_loss_price if target_stop_loss is None and atr and atr > 0 and entry_price: - atr_multiplier = config.TRADING_CONFIG.get('ATR_STOP_LOSS_MULTIPLIER', 2.5) + atr_multiplier = config.TRADING_CONFIG.get('ATR_STOP_LOSS_MULTIPLIER', 3.0) if side == 'BUY': target_stop_loss = entry_price - (atr * atr_multiplier) elif side == 'SELL': @@ -431,7 +431,7 @@ class RiskManager: if stop_loss_price is None: # 尝试使用ATR估算止损距离 if atr and atr > 0: - atr_multiplier = config.TRADING_CONFIG.get('ATR_STOP_LOSS_MULTIPLIER', 2.5) # 默认2.5,放宽止损提升胜率 + atr_multiplier = config.TRADING_CONFIG.get('ATR_STOP_LOSS_MULTIPLIER', 3.0) # 默认3.0,放宽止损提升胜率 if side == 'BUY': estimated_stop_loss = entry_price - (atr * atr_multiplier) else: # SELL