diff --git a/trading_system/config.py b/trading_system/config.py index 4c342d6..a3b9e94 100644 --- a/trading_system/config.py +++ b/trading_system/config.py @@ -168,8 +168,10 @@ def _get_trading_config(): 'MIN_CHANGE_PERCENT': 0.5, # 降低到0.5%以获取更多推荐(推荐系统可以更宽松) 'TOP_N_SYMBOLS': 50, # 每次扫描后处理的交易对数量(增加到50以获取更多推荐) 'MAX_SCAN_SYMBOLS': 500, # 扫描的最大交易对数量(0表示扫描所有) - 'STOP_LOSS_PERCENT': 0.03, - 'TAKE_PROFIT_PERCENT': 0.05, + 'STOP_LOSS_PERCENT': 0.03, # 止损百分比(相对于保证金),默认3% + 'TAKE_PROFIT_PERCENT': 0.05, # 止盈百分比(相对于保证金),默认5% + 'MIN_STOP_LOSS_PRICE_PCT': 0.01, # 最小止损价格变动百分比(如0.01表示1%),防止止损过紧,默认1% + 'MIN_TAKE_PROFIT_PRICE_PCT': 0.015, # 最小止盈价格变动百分比(如0.015表示1.5%),防止止盈过紧,默认1.5% 'SCAN_INTERVAL': 3600, 'KLINE_INTERVAL': '1h', 'PRIMARY_INTERVAL': '1h', diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 4dff62d..387933e 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -668,57 +668,45 @@ class PositionManager: stop_loss = position_info.get('stopLoss') if stop_loss is None: logger.warning(f"{symbol} 止损价未设置,跳过止损检查") - elif position_info['side'] == 'BUY' and current_price <= stop_loss: - logger.warning( - f"{symbol} 触发止损: {current_price:.4f} <= {stop_loss:.4f} " - f"(盈亏: {pnl_percent:.2f}%)" - ) - # 确定平仓原因 - exit_reason = 'trailing_stop' if position_info.get('trailingStopActivated') else 'stop_loss' - # 更新数据库 - if DB_AVAILABLE: - trade_id = position_info.get('tradeId') - if trade_id: - try: - Trade.update_exit( - trade_id=trade_id, - exit_price=current_price, - exit_reason=exit_reason, - pnl=pnl_percent * entry_price * quantity / 100, - pnl_percent=pnl_percent - ) - except Exception as e: - logger.warning(f"更新止损记录失败: {e}") - if await self.close_position(symbol, reason=exit_reason): - closed_positions.append(symbol) - continue + # 检查止损(基于保证金收益比) + stop_loss = position_info.get('stopLoss') + if stop_loss is not None: + # 计算止损对应的保证金百分比目标 + if position_info['side'] == 'BUY': + stop_loss_amount = (entry_price - stop_loss) * quantity + else: # SELL + stop_loss_amount = (stop_loss - entry_price) * quantity + + stop_loss_pct_margin = (stop_loss_amount / margin * 100) if margin > 0 else 0 + + # 直接比较当前盈亏百分比与止损目标(基于保证金) + if pnl_percent_margin <= -stop_loss_pct_margin: + logger.warning( + f"{symbol} 触发止损(基于保证金): " + f"当前盈亏={pnl_percent_margin:.2f}% of margin <= 止损目标=-{stop_loss_pct_margin:.2f}% of margin | " + f"当前价={current_price:.4f}, 止损价={stop_loss:.4f}" + ) + # 确定平仓原因 + exit_reason = 'trailing_stop' if position_info.get('trailingStopActivated') else 'stop_loss' + # 更新数据库 + if DB_AVAILABLE: + trade_id = position_info.get('tradeId') + if trade_id: + try: + Trade.update_exit( + trade_id=trade_id, + exit_price=current_price, + exit_reason=exit_reason, + pnl=pnl_amount, + pnl_percent=pnl_percent_margin + ) + except Exception as e: + logger.warning(f"更新止损记录失败: {e}") + if await self.close_position(symbol, reason=exit_reason): + closed_positions.append(symbol) + continue - if stop_loss is not None and position_info['side'] == 'SELL' and current_price >= stop_loss: - logger.warning( - f"{symbol} 触发止损: {current_price:.4f} >= {stop_loss:.4f} " - f"(盈亏: {pnl_percent:.2f}%)" - ) - # 确定平仓原因 - exit_reason = 'trailing_stop' if position_info.get('trailingStopActivated') else 'stop_loss' - # 更新数据库 - if DB_AVAILABLE: - trade_id = position_info.get('tradeId') - if trade_id: - try: - Trade.update_exit( - trade_id=trade_id, - exit_price=current_price, - exit_reason=exit_reason, - pnl=pnl_percent * entry_price * quantity / 100, - pnl_percent=pnl_percent - ) - except Exception as e: - logger.warning(f"更新止损记录失败: {e}") - if await self.close_position(symbol, reason=exit_reason): - closed_positions.append(symbol) - continue - - # 检查分步止盈 + # 检查分步止盈(基于保证金收益比) take_profit_1 = position_info.get('takeProfit1') # 第一目标(盈亏比1:1) take_profit_2 = position_info.get('takeProfit2', position_info.get('takeProfit')) # 第二目标 partial_profit_taken = position_info.get('partialProfitTaken', False) @@ -726,10 +714,19 @@ class PositionManager: # 第一目标:盈亏比1:1,了结50%仓位 if not partial_profit_taken and take_profit_1 is not None: - if position_info['side'] == 'BUY' and current_price >= take_profit_1: + # 计算第一目标对应的保证金百分比 + if position_info['side'] == 'BUY': + take_profit_1_amount = (take_profit_1 - entry_price) * quantity + else: # SELL + take_profit_1_amount = (entry_price - take_profit_1) * quantity + take_profit_1_pct_margin = (take_profit_1_amount / margin * 100) if margin > 0 else 0 + + # 直接比较当前盈亏百分比与第一目标(基于保证金) + if pnl_percent_margin >= take_profit_1_pct_margin: logger.info( - f"{symbol} 触发第一目标止盈(盈亏比1:1): {current_price:.4f} >= {take_profit_1:.4f} " - f"(盈亏: {pnl_percent:.2f}%)" + f"{symbol} 触发第一目标止盈(盈亏比1:1,基于保证金): " + f"当前盈亏={pnl_percent_margin:.2f}% of margin >= 目标={take_profit_1_pct_margin:.2f}% of margin | " + f"当前价={current_price:.4f}, 目标价={take_profit_1:.4f}" ) # 部分平仓50% partial_quantity = quantity * 0.5 @@ -754,11 +751,7 @@ class PositionManager: logger.info(f"{symbol} 剩余仓位止损移至成本价,配合移动止损博取更大利润") except Exception as e: logger.error(f"{symbol} 部分止盈失败: {e}") - elif take_profit_1 is not None and position_info['side'] == 'SELL' and current_price <= take_profit_1: - logger.info( - f"{symbol} 触发第一目标止盈(盈亏比1:1): {current_price:.4f} <= {take_profit_1:.4f} " - f"(盈亏: {pnl_percent:.2f}%)" - ) + # 做空方向已经在上面统一处理了(基于保证金收益比) # 部分平仓50% partial_quantity = quantity * 0.5 try: @@ -782,12 +775,29 @@ class PositionManager: except Exception as e: logger.error(f"{symbol} 部分止盈失败: {e}") - # 第二目标:原始止盈价,平掉剩余仓位 + # 第二目标:原始止盈价,平掉剩余仓位(基于保证金收益比) if partial_profit_taken and take_profit_2 is not None: - if position_info['side'] == 'BUY' and current_price >= take_profit_2: + # 计算第二目标对应的保证金百分比 + if position_info['side'] == 'BUY': + take_profit_2_amount = (take_profit_2 - entry_price) * remaining_quantity + else: # SELL + take_profit_2_amount = (entry_price - take_profit_2) * remaining_quantity + # 使用剩余仓位的保证金 + remaining_margin = (entry_price * remaining_quantity) / leverage if leverage > 0 else (entry_price * remaining_quantity) + take_profit_2_pct_margin = (take_profit_2_amount / remaining_margin * 100) if remaining_margin > 0 else 0 + # 计算剩余仓位的当前盈亏 + if position_info['side'] == 'BUY': + remaining_pnl_amount = (current_price - entry_price) * remaining_quantity + else: + remaining_pnl_amount = (entry_price - current_price) * remaining_quantity + 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: logger.info( - f"{symbol} 触发第二目标止盈: {current_price:.4f} >= {take_profit_2:.4f} " - f"(盈亏: {pnl_percent:.2f}%)" + f"{symbol} 触发第二目标止盈(基于保证金): " + f"剩余仓位盈亏={remaining_pnl_pct_margin:.2f}% of margin >= 目标={take_profit_2_pct_margin:.2f}% of margin | " + f"当前价={current_price:.4f}, 目标价={take_profit_2:.4f}" ) exit_reason = 'take_profit' # 更新数据库 @@ -799,31 +809,8 @@ class PositionManager: trade_id=trade_id, exit_price=current_price, exit_reason=exit_reason, - pnl=pnl_percent * entry_price * quantity / 100, - pnl_percent=pnl_percent - ) - except Exception as e: - logger.warning(f"更新止盈记录失败: {e}") - if await self.close_position(symbol, reason=exit_reason): - closed_positions.append(symbol) - continue - elif take_profit_2 is not None and position_info['side'] == 'SELL' and current_price <= take_profit_2: - logger.info( - f"{symbol} 触发第二目标止盈: {current_price:.4f} <= {take_profit_2:.4f} " - f"(盈亏: {pnl_percent:.2f}%)" - ) - exit_reason = 'take_profit' - # 更新数据库 - if DB_AVAILABLE: - trade_id = position_info.get('tradeId') - if trade_id: - try: - Trade.update_exit( - trade_id=trade_id, - exit_price=current_price, - exit_reason=exit_reason, - pnl=pnl_percent * entry_price * quantity / 100, - pnl_percent=pnl_percent + pnl=pnl_amount, + pnl_percent=pnl_percent_margin ) except Exception as e: logger.warning(f"更新止盈记录失败: {e}") @@ -831,55 +818,41 @@ class PositionManager: closed_positions.append(symbol) continue else: - # 如果未部分止盈,但达到第二目标,直接全部平仓 + # 如果未部分止盈,但达到止盈目标,直接全部平仓(基于保证金收益比) take_profit = position_info.get('takeProfit') - if take_profit is not None and position_info['side'] == 'BUY' and current_price >= take_profit: - logger.info( - f"{symbol} 触发止盈: {current_price:.4f} >= {take_profit:.4f} " - f"(盈亏: {pnl_percent:.2f}%)" - ) - exit_reason = 'take_profit' - # 更新数据库 - if DB_AVAILABLE: - trade_id = position_info.get('tradeId') - if trade_id: - try: - Trade.update_exit( - trade_id=trade_id, - exit_price=current_price, - exit_reason=exit_reason, - pnl=pnl_percent * entry_price * quantity / 100, - pnl_percent=pnl_percent - ) - except Exception as e: - logger.warning(f"更新止盈记录失败: {e}") - if await self.close_position(symbol, reason=exit_reason): - closed_positions.append(symbol) - continue - - if take_profit is not None and position_info['side'] == 'SELL' and current_price <= take_profit: - logger.info( - f"{symbol} 触发止盈: {current_price:.4f} <= {take_profit:.4f} " - f"(盈亏: {pnl_percent:.2f}%)" - ) - exit_reason = 'take_profit' - # 更新数据库 - if DB_AVAILABLE: - trade_id = position_info.get('tradeId') - if trade_id: - try: - Trade.update_exit( - trade_id=trade_id, - exit_price=current_price, - exit_reason=exit_reason, - pnl=pnl_percent * entry_price * quantity / 100, - pnl_percent=pnl_percent - ) - except Exception as e: - logger.warning(f"更新止盈记录失败: {e}") - if await self.close_position(symbol, reason=exit_reason): - closed_positions.append(symbol) - continue + if take_profit is not None: + # 计算止盈对应的保证金百分比 + if position_info['side'] == 'BUY': + take_profit_amount = (take_profit - entry_price) * quantity + else: # SELL + take_profit_amount = (entry_price - take_profit) * quantity + take_profit_pct_margin = (take_profit_amount / margin * 100) if margin > 0 else 0 + + # 直接比较当前盈亏百分比与止盈目标(基于保证金) + if pnl_percent_margin >= take_profit_pct_margin: + logger.info( + f"{symbol} 触发止盈(基于保证金): " + f"当前盈亏={pnl_percent_margin:.2f}% of margin >= 目标={take_profit_pct_margin:.2f}% of margin | " + f"当前价={current_price:.4f}, 止盈价={take_profit:.4f}" + ) + exit_reason = 'take_profit' + # 更新数据库 + if DB_AVAILABLE: + trade_id = position_info.get('tradeId') + if trade_id: + try: + Trade.update_exit( + trade_id=trade_id, + exit_price=current_price, + exit_reason=exit_reason, + pnl=pnl_amount, + pnl_percent=pnl_percent_margin + ) + except Exception as e: + logger.warning(f"更新止盈记录失败: {e}") + if await self.close_position(symbol, reason=exit_reason): + closed_positions.append(symbol) + continue except Exception as e: logger.error(f"检查止损止盈失败: {e}") @@ -1434,75 +1407,68 @@ class PositionManager: f"(保护{trailing_protect*100:.1f}% of margin = {protect_amount:.4f} USDT)" ) - # 检查止损 - stop_loss = position_info['stopLoss'] + # 检查止损(基于保证金收益比) + stop_loss = position_info.get('stopLoss') should_close = False exit_reason = None - if position_info['side'] == 'BUY' and current_price <= stop_loss: - should_close = True - exit_reason = 'trailing_stop' if position_info.get('trailingStopActivated') else 'stop_loss' - logger.warning( - f"{symbol} [实时监控] 触发止损: {current_price:.4f} <= {stop_loss:.4f} " - f"(盈亏: {pnl_percent:.2f}%)" - ) - elif position_info['side'] == 'SELL' and current_price >= stop_loss: - should_close = True - exit_reason = 'trailing_stop' if position_info.get('trailingStopActivated') else 'stop_loss' - logger.warning( - f"{symbol} [实时监控] 触发止损: {current_price:.4f} >= {stop_loss:.4f} " - f"(盈亏: {pnl_percent:.2f}%)" - ) - - # 检查止盈 - if not should_close: - take_profit = float(position_info['takeProfit']) - # 计算止盈百分比(用于诊断) + if stop_loss is not None: + # 计算止损对应的保证金百分比目标 + # 止损金额 = (开仓价 - 止损价) × 数量 或 (止损价 - 开仓价) × 数量 if position_info['side'] == 'BUY': - take_profit_pct = ((take_profit - entry_price) / entry_price) * 100 + stop_loss_amount = (entry_price - stop_loss) * quantity else: # SELL - take_profit_pct = ((entry_price - take_profit) / entry_price) * 100 + stop_loss_amount = (stop_loss - entry_price) * quantity - # 每5%盈利记录一次诊断日志(帮助排查问题) - # 使用更宽松的条件,避免因为浮点数精度问题导致日志不输出 - if pnl_percent >= 5.0: - # 每5%记录一次,但允许一些容差 - should_log = (int(pnl_percent) % 5 == 0) or (pnl_percent >= 10.0 and pnl_percent < 10.5) - if should_log: - trigger_condition = current_price_float >= take_profit if position_info['side'] == 'BUY' else current_price_float <= take_profit - logger.warning( - f"{symbol} [实时监控] 诊断: 盈利{pnl_percent:.2f}% | " - f"当前价: {current_price_float:.4f} | " - f"入场价: {entry_price:.4f} | " - f"止盈价: {take_profit:.4f} ({take_profit_pct:.2f}%) | " - f"方向: {position_info['side']} | " - f"是否触发: {trigger_condition} | " - f"价格差: {abs(current_price_float - take_profit):.4f} | " - f"监控状态: {'运行中' if symbol in self._monitor_tasks else '未启动'}" - ) - - # 如果盈利超过止盈目标但未触发,记录警告 - if pnl_percent > take_profit_pct and not trigger_condition: - logger.error( - f"{symbol} [实时监控] ⚠️ 异常: 盈利{pnl_percent:.2f}% > 止盈目标{take_profit_pct:.2f}%,但未触发平仓!" + stop_loss_pct_margin = (stop_loss_amount / margin * 100) if margin > 0 else 0 + + # 直接比较当前盈亏百分比与止损目标(基于保证金) + if pnl_percent_margin <= -stop_loss_pct_margin: + should_close = True + exit_reason = 'trailing_stop' if position_info.get('trailingStopActivated') else 'stop_loss' + logger.warning( + f"{symbol} [实时监控] 触发止损(基于保证金): " + f"当前盈亏={pnl_percent_margin:.2f}% of margin <= 止损目标=-{stop_loss_pct_margin:.2f}% of margin | " + f"当前价={current_price_float:.4f}, 止损价={stop_loss:.4f}" + ) + + # 检查止盈(基于保证金收益比) + if not should_close: + take_profit = position_info.get('takeProfit') + if take_profit is not None: + # 计算止盈对应的保证金百分比目标 + # 止盈金额 = (止盈价 - 开仓价) × 数量 或 (开仓价 - 止盈价) × 数量 + if position_info['side'] == 'BUY': + take_profit_amount = (take_profit - entry_price) * quantity + else: # SELL + take_profit_amount = (entry_price - take_profit) * quantity + + take_profit_pct_margin = (take_profit_amount / margin * 100) if margin > 0 else 0 + + # 每5%盈利记录一次诊断日志(帮助排查问题) + if pnl_percent_margin >= 5.0: + should_log = (int(pnl_percent_margin) % 5 == 0) or (pnl_percent_margin >= 10.0 and pnl_percent_margin < 10.5) + if should_log: + trigger_condition = pnl_percent_margin >= take_profit_pct_margin + logger.warning( + f"{symbol} [实时监控] 诊断: 盈利{pnl_percent_margin:.2f}% of margin | " + f"当前价: {current_price_float:.4f} | " + f"入场价: {entry_price:.4f} | " + f"止盈价: {take_profit:.4f} (目标: {take_profit_pct_margin:.2f}% of margin) | " + f"方向: {position_info['side']} | " + f"是否触发: {trigger_condition} | " + f"监控状态: {'运行中' if symbol in self._monitor_tasks else '未启动'}" ) - price_diff = current_price_float - take_profit if position_info['side'] == 'BUY' else take_profit - current_price_float - logger.error(f" 当前价: {current_price_float:.4f}, 止盈价: {take_profit:.4f}, 价格差: {price_diff:.4f}") - - if position_info['side'] == 'BUY' and current_price_float >= take_profit: - should_close = True - exit_reason = 'take_profit' - logger.info( - f"{symbol} [实时监控] 触发止盈: {current_price_float:.4f} >= {take_profit:.4f} " - f"(盈亏: {pnl_percent:.2f}%, 止盈目标: {take_profit_pct:.2f}%)" - ) - elif position_info['side'] == 'SELL' and current_price_float <= take_profit: - should_close = True - exit_reason = 'take_profit' - logger.info( - f"{symbol} [实时监控] 触发止盈: {current_price_float:.4f} <= {take_profit:.4f} " - f"(盈亏: {pnl_percent:.2f}%, 止盈目标: {take_profit_pct:.2f}%)" - ) + + # 直接比较当前盈亏百分比与止盈目标(基于保证金) + if pnl_percent_margin >= take_profit_pct_margin: + should_close = True + exit_reason = 'take_profit' + logger.info( + f"{symbol} [实时监控] 触发止盈(基于保证金): " + f"当前盈亏={pnl_percent_margin:.2f}% of margin >= 止盈目标={take_profit_pct_margin:.2f}% of margin | " + f"当前价={current_price_float:.4f}, 止盈价={take_profit:.4f}" + ) # 如果触发止损止盈,执行平仓 if should_close: @@ -1510,8 +1476,8 @@ class PositionManager: f"{symbol} [自动平仓] 开始执行平仓操作 | " f"原因: {exit_reason} | " f"入场价: {entry_price:.4f} | " - f"当前价: {current_price:.4f} | " - f"盈亏: {pnl_percent:.2f}% | " + f"当前价: {current_price_float:.4f} | " + f"盈亏: {pnl_percent_margin:.2f}% of margin ({pnl_amount:.4f} USDT) | " f"数量: {quantity:.4f}" ) diff --git a/trading_system/risk_manager.py b/trading_system/risk_manager.py index e1bc81c..bf9358d 100644 --- a/trading_system/risk_manager.py +++ b/trading_system/risk_manager.py @@ -432,13 +432,38 @@ class RiskManager: # 计算止损金额(相对于保证金) stop_loss_amount = margin * stop_loss_percent - # 计算止损价(基于止损金额) + # 计算基于保证金的止损价 # 止损金额 = (开仓价 - 止损价) × 数量 # 所以:止损价 = 开仓价 - (止损金额 / 数量) if side == 'BUY': # 做多,止损价低于入场价 - stop_loss_price = entry_price - (stop_loss_amount / quantity) + stop_loss_price_margin = entry_price - (stop_loss_amount / quantity) else: # 做空,止损价高于入场价 - stop_loss_price = entry_price + (stop_loss_amount / quantity) + stop_loss_price_margin = entry_price + (stop_loss_amount / quantity) + + # 同时计算基于价格百分比的止损价(作为最小值保护) + # 获取最小价格变动百分比(如果配置了) + min_price_change_pct = self.config.get('MIN_STOP_LOSS_PRICE_PCT', None) + if min_price_change_pct is not None: + # 基于价格百分比的止损价 + if side == 'BUY': + stop_loss_price_price = entry_price * (1 - min_price_change_pct) + else: + stop_loss_price_price = entry_price * (1 + min_price_change_pct) + + # 取更宽松的止损价(对做多取更大的值,对做空取更小的值) + if side == 'BUY': + stop_loss_price = max(stop_loss_price_margin, stop_loss_price_price) + else: + stop_loss_price = min(stop_loss_price_margin, stop_loss_price_price) + + logger.info( + f"止损计算 ({side}): 基于保证金={stop_loss_price_margin:.4f}, " + f"基于价格={stop_loss_price_price:.4f}, " + f"最终止损={stop_loss_price:.4f} (取更宽松)" + ) + else: + # 没有配置最小价格变动,直接使用基于保证金的止损价 + stop_loss_price = stop_loss_price_margin # 如果提供了技术分析数据,可以调整止损价(但不能超过基于保证金的止损范围) if klines and len(klines) >= 10: @@ -543,13 +568,38 @@ class RiskManager: # 计算止盈金额(相对于保证金) take_profit_amount = margin * take_profit_percent - # 计算止盈价(基于止盈金额) + # 计算基于保证金的止盈价 # 止盈金额 = (止盈价 - 开仓价) × 数量 # 所以:止盈价 = 开仓价 + (止盈金额 / 数量) if side == 'BUY': # 做多,止盈价高于入场价 - take_profit_price = entry_price + (take_profit_amount / quantity) + take_profit_price_margin = entry_price + (take_profit_amount / quantity) else: # 做空,止盈价低于入场价 - take_profit_price = entry_price - (take_profit_amount / quantity) + take_profit_price_margin = entry_price - (take_profit_amount / quantity) + + # 同时计算基于价格百分比的止盈价(作为最小值保护) + # 获取最小价格变动百分比(如果配置了) + min_price_change_pct = self.config.get('MIN_TAKE_PROFIT_PRICE_PCT', None) + if min_price_change_pct is not None: + # 基于价格百分比的止盈价 + if side == 'BUY': + take_profit_price_price = entry_price * (1 + min_price_change_pct) + else: + take_profit_price_price = entry_price * (1 - min_price_change_pct) + + # 取更宽松的止盈价(对做多取更大的值,对做空取更小的值) + if side == 'BUY': + take_profit_price = max(take_profit_price_margin, take_profit_price_price) + else: + take_profit_price = min(take_profit_price_margin, take_profit_price_price) + + logger.info( + f"止盈计算 ({side}): 基于保证金={take_profit_price_margin:.4f}, " + f"基于价格={take_profit_price_price:.4f}, " + f"最终止盈={take_profit_price:.4f} (取更宽松)" + ) + else: + # 没有配置最小价格变动,直接使用基于保证金的止盈价 + take_profit_price = take_profit_price_margin logger.info( f"止盈计算 ({side}): 入场价={entry_price:.4f}, 数量={quantity:.4f}, "