This commit is contained in:
薇薇安 2026-01-17 00:31:00 +08:00
parent 4d084d7166
commit b45f6121b1
3 changed files with 225 additions and 207 deletions

View File

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

View File

@ -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
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
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
# 检查分步止盈(基于保证金收益比)
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:
# 计算止盈对应的保证金百分比
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 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 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 '未启动'}"
)
stop_loss_pct_margin = (stop_loss_amount / margin * 100) if margin > 0 else 0
# 如果盈利超过止盈目标但未触发,记录警告
if pnl_percent > take_profit_pct and not trigger_condition:
logger.error(
f"{symbol} [实时监控] ⚠️ 异常: 盈利{pnl_percent:.2f}% > 止盈目标{take_profit_pct:.2f}%,但未触发平仓!"
# 直接比较当前盈亏百分比与止损目标(基于保证金)
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}"
)

View File

@ -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}, "