diff --git a/bitmart/交易.py b/bitmart/交易.py index c77bde2..b4eb9bf 100644 --- a/bitmart/交易.py +++ b/bitmart/交易.py @@ -74,6 +74,7 @@ class BitmartFuturesTransaction: self.take_profit_reentry_threshold = None # 多仓止盈平仓后,价格 <= 此值则开多(同向) self.take_profit_triggered_kline_id_short = None # 空仓:本K线内是否出现过 open~low 的极值触发 self.take_profit_reentry_threshold_short = None # 空仓止盈平仓后,价格 >= 此值则开空(同向) + self.last_take_profit_kline_id = None # 本K线已止盈一次(不区分多空)则不再止盈 self.optimized_params_file = Path(__file__).resolve().parent / "atr_best_params.json" self.strategy_log_dir = Path(__file__).resolve().parent # 策略开仓日志目录 @@ -740,30 +741,35 @@ class BitmartFuturesTransaction: # 3.5 多仓止盈(自动四等分):open~high 达到极值后,回到 3/4 平仓 if self.start == 1: - long_levels = self.calc_long_take_profit_levels(current_kline) - if long_levels is not None: - retrace_close_threshold = long_levels["close_threshold"] - # 使用当前K线 high 判定是否已触发到 4/4 极值,避免依赖分钟收盘价漏判 - if long_levels["extreme"] > long_levels["open"]: - self.take_profit_triggered_kline_id = current_kline_time - if ( - self.take_profit_triggered_kline_id == current_kline_time - and current_price <= retrace_close_threshold - ): - reason = ( - f"当前K线开盘价={long_levels['open']:.2f},最高价={long_levels['extreme']:.2f}(4/4);" - f"现价{current_price:.2f}已回调至≤{retrace_close_threshold:.2f}(3/4),按规则止盈平多" - ) - self._log_take_profit_action("止盈平多仓", reason) - self.平仓() - self.take_profit_reentry_threshold = long_levels["reentry_threshold"] - self.take_profit_triggered_kline_id = None - page_start = True - time.sleep(1) + # 同一根K线多仓只允许止盈一次,避免“止盈->再开->再止盈”循环 + if self.last_take_profit_kline_id == current_kline_time: + signal = self.check_signal(current_price, prev_kline, current_kline) + else: + long_levels = self.calc_long_take_profit_levels(current_kline) + if long_levels is not None: + retrace_close_threshold = long_levels["close_threshold"] + # 使用当前K线 high 判定是否已触发到 4/4 极值,避免依赖分钟收盘价漏判 + if long_levels["extreme"] > long_levels["open"]: + self.take_profit_triggered_kline_id = current_kline_time + if ( + self.take_profit_triggered_kline_id == current_kline_time + and current_price <= retrace_close_threshold + ): + reason = ( + f"当前K线开盘价={long_levels['open']:.2f},最高价={long_levels['extreme']:.2f}(4/4);" + f"现价{current_price:.2f}已回调至≤{retrace_close_threshold:.2f}(3/4),按规则止盈平多" + ) + self._log_take_profit_action("止盈平多仓", reason) + self.平仓() + self.take_profit_reentry_threshold = long_levels["reentry_threshold"] + self.take_profit_triggered_kline_id = None + self.last_take_profit_kline_id = current_kline_time + page_start = True + time.sleep(1) + else: + signal = self.check_signal(current_price, prev_kline, current_kline) else: signal = self.check_signal(current_price, prev_kline, current_kline) - else: - signal = self.check_signal(current_price, prev_kline, current_kline) # 3.6 止盈平多后:价格继续回调到 2/4 则同向开多 elif self.start == 0 and self.take_profit_reentry_threshold is not None: if current_price <= self.take_profit_reentry_threshold: @@ -786,30 +792,35 @@ class BitmartFuturesTransaction: signal = self.check_signal(current_price, prev_kline, current_kline) # 3.7 空仓止盈(自动四等分):open~low 达到极值后,回到 3/4 平仓 elif self.start == -1: - short_levels = self.calc_short_take_profit_levels(current_kline) - if short_levels is not None: - retrace_close_threshold_short = short_levels["close_threshold"] - # 使用当前K线 low 判定是否已触发到 4/4 极值,避免依赖分钟收盘价漏判 - if short_levels["extreme"] < short_levels["open"]: - self.take_profit_triggered_kline_id_short = current_kline_time - if ( - self.take_profit_triggered_kline_id_short == current_kline_time - and current_price >= retrace_close_threshold_short - ): - reason = ( - f"当前K线开盘价={short_levels['open']:.2f},最低价={short_levels['extreme']:.2f}(4/4);" - f"现价{current_price:.2f}已反弹至≥{retrace_close_threshold_short:.2f}(3/4),按规则止盈平空" - ) - self._log_take_profit_action("止盈平空仓", reason) - self.平仓() - self.take_profit_reentry_threshold_short = short_levels["reentry_threshold"] - self.take_profit_triggered_kline_id_short = None - page_start = True - time.sleep(1) + # 同一根K线空仓只允许止盈一次,避免“止盈->再开->再止盈”循环 + if self.last_take_profit_kline_id == current_kline_time: + signal = self.check_signal(current_price, prev_kline, current_kline) + else: + short_levels = self.calc_short_take_profit_levels(current_kline) + if short_levels is not None: + retrace_close_threshold_short = short_levels["close_threshold"] + # 使用当前K线 low 判定是否已触发到 4/4 极值,避免依赖分钟收盘价漏判 + if short_levels["extreme"] < short_levels["open"]: + self.take_profit_triggered_kline_id_short = current_kline_time + if ( + self.take_profit_triggered_kline_id_short == current_kline_time + and current_price >= retrace_close_threshold_short + ): + reason = ( + f"当前K线开盘价={short_levels['open']:.2f},最低价={short_levels['extreme']:.2f}(4/4);" + f"现价{current_price:.2f}已反弹至≥{retrace_close_threshold_short:.2f}(3/4),按规则止盈平空" + ) + self._log_take_profit_action("止盈平空仓", reason) + self.平仓() + self.take_profit_reentry_threshold_short = short_levels["reentry_threshold"] + self.take_profit_triggered_kline_id_short = None + self.last_take_profit_kline_id = current_kline_time + page_start = True + time.sleep(1) + else: + signal = self.check_signal(current_price, prev_kline, current_kline) else: signal = self.check_signal(current_price, prev_kline, current_kline) - else: - signal = self.check_signal(current_price, prev_kline, current_kline) # 3.8 止盈平空后:价格继续反弹到 2/4 则同向开空 elif self.start == 0 and self.take_profit_reentry_threshold_short is not None: if current_price >= self.take_profit_reentry_threshold_short: