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 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"""

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计算更准确的止损距离用于盈亏比止盈
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':

View File

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