From f8eca1ed592237edae46466dbe8a823c1719d35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Wed, 4 Feb 2026 15:00:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BA=A4=E6=98=93=E7=9A=84?= =?UTF-8?q?=E6=AD=A2=E7=9B=88=E4=BA=8F=E6=8D=9F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- analyze_bad_trades.py | 41 ++++++++++++++++++++++++ trading_system/position_manager.py | 50 +++++++++++++++++++++++++++--- 2 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 analyze_bad_trades.py diff --git a/analyze_bad_trades.py b/analyze_bad_trades.py new file mode 100644 index 0000000..6e2bc75 --- /dev/null +++ b/analyze_bad_trades.py @@ -0,0 +1,41 @@ + +import json +import sys + +def analyze_trades(file_path): + try: + with open(file_path, 'r', encoding='utf-8') as f: + trades = json.load(f) + except Exception as e: + print(f"Error reading file: {e}") + return + + print(f"Analyzing {len(trades)} trades...") + + tp_losses = [] + sync_exits = [] + + for t in trades: + pnl = t.get('盈亏', 0) + reason = t.get('离场原因', '') + duration = t.get('持仓时长分钟') + + if reason == 'take_profit' and pnl < 0: + tp_losses.append(t) + + if reason == 'sync' and duration == 0: + sync_exits.append(t) + + print("\n[Anomalies 1: Negative PnL with 'take_profit' reason]") + for t in tp_losses: + print(f"ID: {t.get('交易ID')} | Symbol: {t.get('交易对')} | Side: {t.get('方向')} | " + f"Entry: {t.get('入场价')} | Exit: {t.get('出场价')} | " + f"PnL: {t.get('盈亏')} | TP Price: {t.get('止盈价')}") + + print("\n[Anomalies 2: Immediate Sync Exits (Duration 0)]") + for t in sync_exits: + print(f"ID: {t.get('交易ID')} | Symbol: {t.get('交易对')} | PnL: {t.get('盈亏')} | " + f"Entry Time: {t.get('入场时间')} | Exit Time: {t.get('平仓时间')}") + +if __name__ == "__main__": + analyze_trades('/Users/vivian/work/python/auto_trade_sys/交易记录_2026-02-04T06-46-43.json') diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 972ba5b..fc964d7 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -2040,7 +2040,41 @@ class PositionManager: logger.warning(f"{symbol} [状态同步] ⚠️ 数据库中没有找到open状态的交易记录,跳过") continue - logger.info(f"{symbol} [状态同步] 找到 {len(trades)} 条open状态的交易记录,开始更新...") + # 过滤掉刚刚开仓的交易(给予60秒的同步缓冲期) + # 避免因API延迟导致刚开的仓位被误判为"丢失"从而被错误关闭 + import time + now_ts = int(time.time()) + recent_trades = [] + valid_trades = [] + + for t in trades: + try: + entry_time = t.get('entry_time') + if entry_time: + # 处理 entry_time 可能是 datetime 对象或时间戳的情况 + entry_ts = 0 + if hasattr(entry_time, 'timestamp'): + entry_ts = int(entry_time.timestamp()) + elif isinstance(entry_time, (int, float)): + entry_ts = int(entry_time) + # 如果是刚刚开仓(60秒内),跳过同步关闭 + if now_ts - entry_ts < 60: + recent_trades.append(t) + continue + except Exception as e: + logger.debug(f"{symbol} [状态同步] 检查开仓时间出错: {e}") + + valid_trades.append(t) + + if recent_trades: + logger.info(f"{symbol} [状态同步] 发现 {len(recent_trades)} 个刚刚开仓的交易(<60s),跳过同步关闭(防止误杀)") + + if not valid_trades: + if not recent_trades: + logger.warning(f"{symbol} [状态同步] 没有符合条件的交易记录需要更新") + continue + + logger.info(f"{symbol} [状态同步] 找到 {len(valid_trades)} 条open状态的交易记录,开始更新...") except Exception as get_trades_error: logger.error( f"{symbol} [状态同步] ❌ 获取交易记录失败: " @@ -2050,7 +2084,7 @@ class PositionManager: logger.debug(f"{symbol} [状态同步] 错误详情:\n{traceback.format_exc()}") continue - for trade in trades: + for trade in valid_trades: trade_id = trade.get('id') if not trade_id: logger.warning(f"{symbol} [状态同步] ⚠️ 交易记录缺少ID字段,跳过: {trade}") @@ -3072,7 +3106,11 @@ class PositionManager: take_profit_1_pct_margin = take_profit_1_pct_margin_config * 100 # 转换为百分比 # 直接比较当前盈亏百分比与第一目标(基于保证金,使用配置值) - if pnl_percent_margin >= take_profit_1_pct_margin: + # ⚠️ 2026-02-04 修复:增加最小盈利检查,防止因配置过低或滑点导致负盈利 + # 手续费估算:0.05% * 2 = 0.1% 价格变动。10x杠杆下约1%保证金。保留2%作为安全边际。 + min_profit_margin = 2.0 + + if pnl_percent_margin >= take_profit_1_pct_margin and pnl_percent_margin > min_profit_margin: take_profit_pct_config = config.TRADING_CONFIG.get('TAKE_PROFIT_1_PERCENT', 0.15) if take_profit_pct_config > 1: take_profit_pct_config = take_profit_pct_config / 100.0 @@ -3135,7 +3173,8 @@ class PositionManager: remaining_pnl_pct_margin = (remaining_pnl_amount / remaining_margin * 100) if remaining_margin > 0 else 0 # 直接比较剩余仓位盈亏百分比与第二目标(基于保证金) - if remaining_pnl_pct_margin >= take_profit_2_pct_margin: + # ⚠️ 2026-02-04 修复:增加最小盈利检查 + if remaining_pnl_pct_margin >= take_profit_2_pct_margin and remaining_pnl_pct_margin > 2.0: should_close = True # ⚠️ 2026-01-27优化:细分状态,区分"第一目标止盈后第二目标止盈" exit_reason = 'take_profit_partial_then_take_profit' @@ -3182,7 +3221,8 @@ class PositionManager: ) # 直接比较当前盈亏百分比与止盈目标(基于保证金,使用配置值) - if pnl_percent_margin >= take_profit_pct_margin: + # ⚠️ 2026-02-04 修复:增加最小盈利检查 + if pnl_percent_margin >= take_profit_pct_margin and pnl_percent_margin > 2.0: should_close = True exit_reason = 'take_profit'