This commit is contained in:
薇薇安 2026-02-12 10:08:25 +08:00
parent 71f0378c5f
commit a033d1ea6d
4 changed files with 58 additions and 18 deletions

View File

@ -3,23 +3,42 @@ import asyncio
import logging import logging
import os import os
import sys import sys
import time
from datetime import datetime, timedelta from datetime import datetime, timedelta
from decimal import Decimal
from typing import List, Dict, Any, Optional
# Add project root to path # Add project root to path
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 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.connection import db
from backend.database.models import Trade, Account 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 # Configure logging
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s' format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
) )
logger = logging.getLogger(__name__) 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): async def fix_time_inversion_trades(client, account_id):
"""Fix trades where exit_time < entry_time""" """Fix trades where exit_time < entry_time"""
logger.info("Scanning for time-inverted trades...") logger.info("Scanning for time-inverted trades...")
@ -35,13 +54,13 @@ async def fix_time_inversion_trades(client, account_id):
if not trades: if not trades:
logger.info("No time-inverted trades found.") logger.info("No time-inverted trades found.")
return
for trade in trades: for trade in trades:
symbol = trade['symbol'] symbol = trade['symbol']
trade_id = trade['id'] trade_id = trade['id']
entry_time = trade['entry_time'] entry_time = trade['entry_time']
exit_time = trade['exit_time'] exit_time = trade['exit_time']
quantity = trade['quantity']
side = trade['side'] # OPEN side (BUY/SELL) 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)") 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) start_time_ms = int(entry_time * 1000)
try: try:
# Fetch user trades (fills) after entry time # Fetch user trades (fills) after entry time with retry
fills = await client.client.futures_account_trades( fills = await get_fills_with_retry(client, symbol, start_time_ms)
symbol=symbol,
startTime=start_time_ms,
limit=100
)
# Filter for closing trades # Filter for closing trades
# If entry side was BUY, we look for SELL. If SELL, look for BUY. # If entry side was BUY, we look for SELL. If SELL, look for BUY.
close_side = 'SELL' if side == 'BUY' else '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: if not closing_fills:
# No closing trades found. Position might still be open or we missed it? # 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: else:
# Found closing fills. Calculate metrics. # Found closing fills. Calculate metrics.
# We assume the latest fills correspond to this trade. # We assume the latest fills correspond to this trade.
# This is a simplification but better than "ghost" order.
# Sort by time desc # Sort by time desc
closing_fills.sort(key=lambda x: x['time'], reverse=True) 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: except Exception as e:
logger.error(f"Error processing trade #{trade_id}: {type(e).__name__}: {e}") logger.error(f"Error processing trade #{trade_id}: {e}")
import traceback
logger.error(traceback.format_exc())
async def backfill_commissions(client, account_id): async def backfill_commissions(client, account_id):
"""Backfill missing commission data for recent trades""" """Backfill missing commission data for recent trades"""

21
scripts/inspect_trade.py Normal file
View File

@ -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()

View File

@ -583,7 +583,7 @@ class PositionManager:
# 如果ATR可用使用ATR计算更准确的止损距离用于盈亏比止盈 # 如果ATR可用使用ATR计算更准确的止损距离用于盈亏比止盈
if atr is not None and atr > 0 and entry_price > 0: if atr is not None and atr > 0 and entry_price > 0:
atr_percent = atr / entry_price 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计算的止损距离更准确用于盈亏比止盈 # 使用ATR计算的止损距离更准确用于盈亏比止盈
stop_distance_for_tp = entry_price * atr_percent * atr_multiplier 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}),已自动调整以保证盈亏比") logger.info(f"{symbol} [优化] TP1距离 ({tp1_distance:.4f}) 小于 {min_rr_for_tp1}倍止损距离 ({min_tp1_distance:.4f}),已自动调整以保证盈亏比")
tp1_distance = min_tp1_distance 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价格 # 计算最终TP1价格
if tp1_distance > 0: if tp1_distance > 0:
if side == 'BUY': if side == 'BUY':

View File

@ -292,7 +292,7 @@ class RiskManager:
# 尝试获取止损价格(如果没有传入,尝试估算) # 尝试获取止损价格(如果没有传入,尝试估算)
target_stop_loss = stop_loss_price target_stop_loss = stop_loss_price
if target_stop_loss is None and atr and atr > 0 and entry_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': if side == 'BUY':
target_stop_loss = entry_price - (atr * atr_multiplier) target_stop_loss = entry_price - (atr * atr_multiplier)
elif side == 'SELL': elif side == 'SELL':
@ -431,7 +431,7 @@ class RiskManager:
if stop_loss_price is None: if stop_loss_price is None:
# 尝试使用ATR估算止损距离 # 尝试使用ATR估算止损距离
if atr and atr > 0: 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': if side == 'BUY':
estimated_stop_loss = entry_price - (atr * atr_multiplier) estimated_stop_loss = entry_price - (atr * atr_multiplier)
else: # SELL else: # SELL