diff --git a/.DS_Store b/.DS_Store index e709523..81c6a1e 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/1111 b/1111 index d61f410..79052d1 100644 --- a/1111 +++ b/1111 @@ -50,3 +50,5 @@ bitmart\框架.py 读取这个代码文件夹 167 +2003 +155 diff --git a/bitmart/交易.py b/bitmart/交易.py index 95ab350..f9c1c27 100644 --- a/bitmart/交易.py +++ b/bitmart/交易.py @@ -1,5 +1,6 @@ import time import json +from datetime import datetime from pathlib import Path from tqdm import tqdm @@ -15,17 +16,7 @@ PRECOMPUTED_TRADE_PARAMS = { # 你已经算好参数时,把 enabled 改成 True,并在下面填入你的值。 # 直接运行本文件即可生效,不需要任何命令行参数。 "enabled": False, - "params": { - # 示例(按需修改): - # "use_atr_dynamic_threshold": True, - # "atr_length": 14, - # "breakout_buffer_atr_mult": 0.12, - # "shadow_threshold_atr_mult": 0.18, - # "stop_loss_atr_mult": 0.45, - # "take_profit_atr_mult": 0.95, - # "trailing_start_atr_mult": 0.6, - # "trailing_backoff_atr_mult": 0.3, - } + "params": {} } @@ -74,28 +65,17 @@ class BitmartFuturesTransaction: self.current_open = None # 当前K线开盘价 # 策略优化参数 - self.min_prev_entity_pct = 0.1 # 上一根K线实体最小百分比(%) - self.breakout_buffer_pct = 0.03 # 突破缓冲百分比(%),过滤假突破 - self.shadow_threshold_pct = 0.15 # 上下影线触发反手的最小阈值(%) + self.min_prev_entity_pct = 0.1 # 上一根K线实体涨幅(%),仅当实体涨幅 > 此值才用作“上一根” - # ATR 动态阈值(启用后:动态值与固定值取较大者) - self.use_atr_dynamic_threshold = True - self.atr_length = 14 - self.current_atr = None - self.breakout_buffer_atr_mult = 0.12 - self.shadow_threshold_atr_mult = 0.18 - - # 风控参数 - self.stop_loss_pct = 0.35 # 固定止损(%) - self.take_profit_pct = 0.8 # 固定止盈(%) - self.trailing_start_pct = 0.5 # 浮盈达到该值后启动移动止盈(%) - self.trailing_backoff_pct = 0.25 # 从最优价回撤达到该值触发移动止盈(%) - self.stop_loss_atr_mult = 0.45 - self.take_profit_atr_mult = 0.95 - self.trailing_start_atr_mult = 0.6 - self.trailing_backoff_atr_mult = 0.3 - self.max_favorable_price = None # 多仓期间的最优价格 - self.min_favorable_price = None # 空仓期间的最优价格 + # 多仓止盈:基于当前K线开盘价,涨 0.4% 后回调 0.3% 平仓,涨回 0.2% 同向开多 + self.take_profit_rise_pct = 0.4 # 触发止盈的涨幅(%) + self.take_profit_retrace_pct = 0.3 # 回调此幅度(%)则平仓 + self.reentry_rise_pct = 0.2 # 平仓后涨回此幅度(%)则开多 + self.take_profit_triggered_kline_id = None # 本K线内是否已出现过“涨 0.4%” + self.take_profit_reentry_threshold = None # 止盈平仓后,价格 >= 此值则开多(同向) + # 空仓止盈:跌 0.4% 后反弹 0.3% 平空,再跌到 0.2% 同向开空(复用上面三个百分比) + self.take_profit_triggered_kline_id_short = None # 本K线内是否已出现过“跌 0.4%” + self.take_profit_reentry_threshold_short = None # 止盈平空后,价格 <= 此值则开空(同向) self.optimized_params_file = Path(__file__).resolve().parent / "atr_best_params.json" self.apply_precomputed_params() @@ -125,7 +105,7 @@ class BitmartFuturesTransaction: }) formatted.sort(key=lambda x: x['id']) - # 返回最近多根K线列表,供主循环按「实体>0.1%」向前选取上一根 + # 返回最近多根K线列表,供主循环按「实体涨幅>0.1%」向前选取上一根 if len(formatted) >= 2: return formatted return None @@ -179,7 +159,6 @@ class BitmartFuturesTransaction: self.open_avg_price = None self.current_amount = None self.unrealized_pnl = None - self.reset_trailing_state() return True pos = positions[0] self.start = 1 if pos['position_type'] == 1 else -1 @@ -285,6 +264,11 @@ class BitmartFuturesTransaction: else: logger.info(text) + def _log_take_profit_action(self, operation: str, reason: str): + """止盈相关操作日志:时间、操作、原因""" + time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + logger.info(f"[止盈日志] 时间={time_str} | 操作={operation} | 原因={reason}") + def load_optimized_params(self): """从本地优化结果文件加载参数(可选)。""" try: @@ -298,23 +282,7 @@ class BitmartFuturesTransaction: logger.info(f"检测到优化参数文件但 apply_live != true,跳过加载: {self.optimized_params_file}") return params = data.get("params_for_trade_py", data) - allow_keys = { - "min_prev_entity_pct", - "breakout_buffer_pct", - "shadow_threshold_pct", - "stop_loss_pct", - "take_profit_pct", - "trailing_start_pct", - "trailing_backoff_pct", - "use_atr_dynamic_threshold", - "atr_length", - "breakout_buffer_atr_mult", - "shadow_threshold_atr_mult", - "stop_loss_atr_mult", - "take_profit_atr_mult", - "trailing_start_atr_mult", - "trailing_backoff_atr_mult", - } + allow_keys = {"min_prev_entity_pct"} applied = [] for key, val in params.items(): if key in allow_keys and hasattr(self, key): @@ -339,23 +307,7 @@ class BitmartFuturesTransaction: logger.warning("PRECOMPUTED_TRADE_PARAMS.params 不是字典,忽略") return - allow_keys = { - "min_prev_entity_pct", - "breakout_buffer_pct", - "shadow_threshold_pct", - "stop_loss_pct", - "take_profit_pct", - "trailing_start_pct", - "trailing_backoff_pct", - "use_atr_dynamic_threshold", - "atr_length", - "breakout_buffer_atr_mult", - "shadow_threshold_atr_mult", - "stop_loss_atr_mult", - "take_profit_atr_mult", - "trailing_start_atr_mult", - "trailing_backoff_atr_mult", - } + allow_keys = {"min_prev_entity_pct"} applied = [] for key, val in params.items(): if key in allow_keys and hasattr(self, key): @@ -365,192 +317,10 @@ class BitmartFuturesTransaction: except Exception as e: logger.warning(f"应用文件内预计算参数失败,将继续使用默认参数: {e}") - def compute_atr(self, klines, length=None): - """ - 计算 ATR(SMA 版本) - klines 需按时间升序,且每根含 high/low/close - """ - length = self.atr_length if length is None else length - if not klines or len(klines) < length + 1: - return None - tr_list = [] - for i in range(1, len(klines)): - high = klines[i]['high'] - low = klines[i]['low'] - prev_close = klines[i - 1]['close'] - tr = max(high - low, abs(high - prev_close), abs(low - prev_close)) - tr_list.append(tr) - if len(tr_list) < length: - return None - return sum(tr_list[-length:]) / length - - def resolve_dynamic_distance(self, base_price, fixed_pct, atr_value, atr_mult): - """ - 把阈值统一换算为价格距离: - - 固定阈值: base_price * fixed_pct - - ATR 阈值: atr_value * atr_mult - 启用 ATR 后取较大者,避免在高波动时阈值过窄。 - """ - fixed_distance = base_price * fixed_pct / 100 - if self.use_atr_dynamic_threshold and atr_value and atr_value > 0: - return max(fixed_distance, atr_value * atr_mult) - return fixed_distance - - def get_upper_shadow_abs(self, kline): - """上影线绝对长度""" - return max(0.0, kline['high'] - max(kline['open'], kline['close'])) - - def get_lower_shadow_abs(self, kline): - """下影线绝对长度""" - return max(0.0, min(kline['open'], kline['close']) - kline['low']) - - def get_shadow_threshold_distance(self, kline, atr_value, side): - """ - 影线触发阈值(价格距离) - side: 'upper' / 'lower' - """ - if side == 'upper': - base_price = max(kline['open'], kline['close']) - else: - base_price = min(kline['open'], kline['close']) - return self.resolve_dynamic_distance( - base_price=base_price, - fixed_pct=self.shadow_threshold_pct, - atr_value=atr_value, - atr_mult=self.shadow_threshold_atr_mult - ) - - def reset_trailing_state(self): - """重置移动止盈状态""" - self.max_favorable_price = None - self.min_favorable_price = None - - def set_trailing_anchor_by_position(self): - """根据当前持仓初始化移动止盈锚点""" - if self.start == 1 and self.open_avg_price: - self.max_favorable_price = self.open_avg_price - self.min_favorable_price = None - elif self.start == -1 and self.open_avg_price: - self.min_favorable_price = self.open_avg_price - self.max_favorable_price = None - else: - self.reset_trailing_state() - - def check_risk_exit(self, current_price, atr_value=None): - """ - 风控退出检查: - 1) 固定止损 - 2) 固定止盈 - 3) 移动止盈 - 返回: (reason, detail) / None - """ - if self.start == 0 or not self.open_avg_price: - self.reset_trailing_state() - return None - - sl_distance = self.resolve_dynamic_distance( - base_price=self.open_avg_price, - fixed_pct=self.stop_loss_pct, - atr_value=atr_value, - atr_mult=self.stop_loss_atr_mult - ) - tp_distance = self.resolve_dynamic_distance( - base_price=self.open_avg_price, - fixed_pct=self.take_profit_pct, - atr_value=atr_value, - atr_mult=self.take_profit_atr_mult - ) - trail_start_distance = self.resolve_dynamic_distance( - base_price=self.open_avg_price, - fixed_pct=self.trailing_start_pct, - atr_value=atr_value, - atr_mult=self.trailing_start_atr_mult - ) - trail_backoff_distance = self.resolve_dynamic_distance( - base_price=self.open_avg_price, - fixed_pct=self.trailing_backoff_pct, - atr_value=atr_value, - atr_mult=self.trailing_backoff_atr_mult - ) - - # 多仓 - if self.start == 1: - profit_distance = current_price - self.open_avg_price - loss_distance = self.open_avg_price - current_price - if self.max_favorable_price is None: - self.max_favorable_price = max(self.open_avg_price, current_price) - else: - self.max_favorable_price = max(self.max_favorable_price, current_price) - - if loss_distance >= sl_distance: - return ( - 'stop_loss', - f"多仓回撤 {loss_distance:.3f} >= 止损阈值 {sl_distance:.3f}" - ) - if profit_distance >= tp_distance: - return ( - 'take_profit', - f"多仓盈利 {profit_distance:.3f} >= 止盈阈值 {tp_distance:.3f}" - ) - - if profit_distance >= trail_start_distance and self.max_favorable_price: - retrace_distance = self.max_favorable_price - current_price - if retrace_distance >= trail_backoff_distance: - return ( - 'trailing_stop', - f"多仓回撤 {retrace_distance:.3f} >= 移动回撤阈值 {trail_backoff_distance:.3f}" - ) - - # 空仓 - elif self.start == -1: - profit_distance = self.open_avg_price - current_price - loss_distance = current_price - self.open_avg_price - if self.min_favorable_price is None: - self.min_favorable_price = min(self.open_avg_price, current_price) - else: - self.min_favorable_price = min(self.min_favorable_price, current_price) - - if loss_distance >= sl_distance: - return ( - 'stop_loss', - f"空仓回撤 {loss_distance:.3f} >= 止损阈值 {sl_distance:.3f}" - ) - if profit_distance >= tp_distance: - return ( - 'take_profit', - f"空仓盈利 {profit_distance:.3f} >= 止盈阈值 {tp_distance:.3f}" - ) - - if profit_distance >= trail_start_distance and self.min_favorable_price: - retrace_distance = current_price - self.min_favorable_price - if retrace_distance >= trail_backoff_distance: - return ( - 'trailing_stop', - f"空仓回撤 {retrace_distance:.3f} >= 移动回撤阈值 {trail_backoff_distance:.3f}" - ) - - return None - def calculate_entity(self, kline): """计算K线实体大小(绝对值)""" return abs(kline['close'] - kline['open']) - def calculate_upper_shadow(self, kline): - """计算上阴线(上影线)涨幅百分比""" - # 上阴线 = (最高价 - max(开盘价, 收盘价)) / max(开盘价, 收盘价) - body_top = max(kline['open'], kline['close']) - if body_top == 0: - return 0 - return (kline['high'] - body_top) / body_top * 100 - - def calculate_lower_shadow(self, kline): - """计算下阴线(下影线)跌幅百分比""" - # 下阴线 = (min(开盘价, 收盘价) - 最低价) / min(开盘价, 收盘价) - body_bottom = min(kline['open'], kline['close']) - if body_bottom == 0: - return 0 - return (body_bottom - kline['low']) / body_bottom * 100 - def get_entity_edge(self, kline): """获取K线实体边(收盘价或开盘价,取决于是阳线还是阴线)""" # 阳线(收盘>开盘):实体上边=收盘价,实体下边=开盘价 @@ -560,12 +330,12 @@ class BitmartFuturesTransaction: 'lower': min(kline['open'], kline['close']) # 实体下边 } - def check_signal(self, current_price, prev_kline, current_kline, atr_value=None): + def check_signal(self, current_price, prev_kline, current_kline): """ 检查交易信号 返回: ('long', trigger_price) / ('short', trigger_price) / None """ - # 计算上一根K线实体(主循环已保证所选 prev 实体 > 0.1%) + # 计算上一根K线实体(主循环已保证所选 prev 实体涨幅 > 0.1%) prev_entity = self.calculate_entity(prev_kline) # 获取上一根K线的实体上下边 @@ -597,15 +367,6 @@ class BitmartFuturesTransaction: long_breakout = prev_entity_upper + prev_entity / 3 # 做多突破价 = 实体上边 + 实体/3 short_breakout = prev_entity_lower - prev_entity / 3 # 做空突破价 = 实体下边 - 实体/3 - # 突破缓冲:实体比例 + 固定百分比 + ATR 动态阈值,三者取最大值 - breakout_buffer = max( - prev_entity * 0.1, - current_price * self.breakout_buffer_pct / 100, - (atr_value * self.breakout_buffer_atr_mult) if (self.use_atr_dynamic_threshold and atr_value and atr_value > 0) else 0 - ) - long_breakout_effective = long_breakout + breakout_buffer - short_breakout_effective = short_breakout - breakout_buffer - # 上一根阴线 + 当前阳线:做多形态,不按上一根K线上三分之一做空 prev_is_bearish = prev_kline['close'] < prev_kline['open'] current_is_bullish = current_kline['close'] > current_kline['open'] @@ -624,16 +385,6 @@ class BitmartFuturesTransaction: logger.info(f"上一根实体上边: {prev_entity_upper:.2f}, 下边: {prev_entity_lower:.2f}") logger.info(f"做多触发价(下1/3): {long_trigger:.2f}, 做空触发价(上1/3): {short_trigger:.2f}") logger.info(f"突破做多价(上1/3外): {long_breakout:.2f}, 突破做空价(下1/3外): {short_breakout:.2f}") - logger.info( - f"突破缓冲: {breakout_buffer:.4f} ({self.breakout_buffer_pct:.3f}%)," - f"有效做多突破价: {long_breakout_effective:.2f},有效做空突破价: {short_breakout_effective:.2f}" - ) - if atr_value: - logger.info( - f"ATR({self.atr_length})={atr_value:.4f}, " - f"突破ATR倍数={self.breakout_buffer_atr_mult:.3f}, " - f"影线ATR倍数={self.shadow_threshold_atr_mult:.3f}" - ) if skip_short_by_upper_third: logger.info("上一根阴线+当前阳线(做多形态),不按上三分之一做空") if skip_long_by_lower_third: @@ -641,50 +392,30 @@ class BitmartFuturesTransaction: # 无持仓时检查开仓信号 if self.start == 0: - if current_price >= long_breakout_effective and not skip_long_by_lower_third: + if current_price >= long_breakout and not skip_long_by_lower_third: logger.info( - f"触发做多信号!价格 {current_price:.2f} >= 有效突破价(上1/3外+缓冲) {long_breakout_effective:.2f}" + f"触发做多信号!价格 {current_price:.2f} >= 突破价(上1/3外) {long_breakout:.2f}" ) - return ('long', long_breakout_effective) - elif current_price <= short_breakout_effective and not skip_short_by_upper_third: + return ('long', long_breakout) + elif current_price <= short_breakout and not skip_short_by_upper_third: logger.info( - f"触发做空信号!价格 {current_price:.2f} <= 有效突破价(下1/3外-缓冲) {short_breakout_effective:.2f}" + f"触发做空信号!价格 {current_price:.2f} <= 突破价(下1/3外) {short_breakout:.2f}" ) - return ('short', short_breakout_effective) + return ('short', short_breakout) # 持仓时检查反手信号 elif self.start == 1: # 持多仓 - # 反手条件1: 价格跌到上一根K线的上三分之一处(做空触发价);上一根阴线+当前阳线做多时跳过 + # 反手条件: 价格跌到上一根K线的上三分之一处(做空触发价);上一根阴线+当前阳线做多时跳过 if current_price <= short_trigger and not skip_short_by_upper_third: logger.info(f"持多反手做空!价格 {current_price:.2f} <= 触发价(上1/3) {short_trigger:.2f}") return ('reverse_short', short_trigger) - # 反手条件2: 上一根K线上影线涨幅超过阈值,当前跌到上一根实体下边 - upper_shadow_pct = self.calculate_upper_shadow(prev_kline) - upper_shadow_abs = self.get_upper_shadow_abs(prev_kline) - upper_shadow_threshold = self.get_shadow_threshold_distance(prev_kline, atr_value=atr_value, side='upper') - if upper_shadow_abs > upper_shadow_threshold and current_price <= prev_entity_lower: - logger.info( - f"持多反手做空!上阴线涨幅 {upper_shadow_pct:.4f}%,上影线长度 {upper_shadow_abs:.4f} > 阈值 {upper_shadow_threshold:.4f}," - f"价格 {current_price:.2f} <= 实体下边 {prev_entity_lower:.2f}") - return ('reverse_short', prev_entity_lower) - elif self.start == -1: # 持空仓 - # 反手条件1: 价格涨到上一根K线的下三分之一处(做多触发价);上一根阳线+当前阴线做空时跳过 + # 反手条件: 价格涨到上一根K线的下三分之一处(做多触发价);上一根阳线+当前阴线做空时跳过 if current_price >= long_trigger and not skip_long_by_lower_third: logger.info(f"持空反手做多!价格 {current_price:.2f} >= 触发价(下1/3) {long_trigger:.2f}") return ('reverse_long', long_trigger) - # 反手条件2: 上一根K线下影线跌幅超过阈值,当前涨到上一根实体上边 - lower_shadow_pct = self.calculate_lower_shadow(prev_kline) - lower_shadow_abs = self.get_lower_shadow_abs(prev_kline) - lower_shadow_threshold = self.get_shadow_threshold_distance(prev_kline, atr_value=atr_value, side='lower') - if lower_shadow_abs > lower_shadow_threshold and current_price >= prev_entity_upper: - logger.info( - f"持空反手做多!下阴线跌幅 {lower_shadow_pct:.4f}%,下影线长度 {lower_shadow_abs:.4f} > 阈值 {lower_shadow_threshold:.4f}," - f"价格 {current_price:.2f} >= 实体上边 {prev_entity_upper:.2f}") - return ('reverse_long', prev_entity_upper) - return None def can_open(self, current_kline_id): @@ -770,7 +501,6 @@ class BitmartFuturesTransaction: if self.verify_position_direction(1): self.last_open_time = time.time() self.last_open_kline_id = getattr(self, "_current_kline_id_for_open", None) - self.set_trailing_anchor_by_position() logger.success("开多成功") return True else: @@ -795,7 +525,6 @@ class BitmartFuturesTransaction: if self.verify_position_direction(-1): self.last_open_time = time.time() self.last_open_kline_id = getattr(self, "_current_kline_id_for_open", None) - self.set_trailing_anchor_by_position() logger.success("开空成功") return True else: @@ -822,7 +551,6 @@ class BitmartFuturesTransaction: if self.verify_position_direction(1): logger.success("反手做多成功") self.last_reverse_time = time.time() - self.set_trailing_anchor_by_position() time.sleep(20) return True else: @@ -848,7 +576,6 @@ class BitmartFuturesTransaction: if self.verify_position_direction(-1): logger.success("反手做空成功") self.last_reverse_time = time.time() - self.set_trailing_anchor_by_position() time.sleep(20) return True else: @@ -890,7 +617,7 @@ class BitmartFuturesTransaction: page_start = False try: - # 1. 获取K线数据,当前K线=最后一根;上一根=从后往前第一根实体>0.1%的K线 + # 1. 获取K线数据,当前K线=最后一根;上一根=从后往前第一根实体涨幅>0.1%的K线 formatted = self.get_klines() if not formatted or len(formatted) < 2: logger.warning("获取K线失败,等待重试...") @@ -898,10 +625,6 @@ class BitmartFuturesTransaction: continue current_kline = formatted[-1] - # ATR 使用已完成K线优先(排除当前进行中的K线) - atr_source = formatted[:-1] if len(formatted) > self.atr_length + 1 else formatted - atr_value = self.compute_atr(atr_source, self.atr_length) - self.current_atr = atr_value prev_kline = None for i in range(len(formatted) - 2, -1, -1): k = formatted[i] @@ -911,7 +634,7 @@ class BitmartFuturesTransaction: prev_kline = k break if prev_kline is None: - logger.info(f"没有实体>{self.min_prev_entity_pct:.3f}%的上一根K线,跳过信号检测") + logger.info(f"没有实体涨幅>{self.min_prev_entity_pct:.3f}%的上一根K线,跳过信号检测") time.sleep(0.1) continue @@ -920,6 +643,11 @@ class BitmartFuturesTransaction: if self.last_kline_time != current_kline_time: self.last_kline_time = current_kline_time logger.info(f"进入新K线: {current_kline_time}") + # 新K线内重新判断多/空止盈触发 + if self.start == 1: + self.take_profit_triggered_kline_id = None + if self.start == -1: + self.take_profit_triggered_kline_id_short = None # 2. 获取当前价格 current_price = self.get_current_price() @@ -935,33 +663,95 @@ class BitmartFuturesTransaction: continue logger.debug(f"当前持仓状态: {self.start} (0=无, 1=多, -1=空)") - logger.debug(f"当前 ATR({self.atr_length}): {f'{atr_value:.4f}' if atr_value else 'N/A'}") - # 4. 持仓中优先检查风控退出(止损/止盈/移动止盈) - risk_exit = self.check_risk_exit(current_price, atr_value=atr_value) - if risk_exit: - reason, detail = risk_exit - logger.warning(f"触发风控退出: {reason},{detail}") - self.平仓() - time.sleep(1) - if self.verify_no_position(max_retries=8, retry_interval=1): - self.last_open_time = time.time() - self.last_open_kline_id = current_kline_time - self.reset_trailing_state() - logger.success("风控平仓成功") + kline_open = current_kline['open'] + signal = None + + # 3.5 多仓止盈:当前K线涨 0.4% 后回调 0.3% 则平仓 + if self.start == 1: + rise_threshold = kline_open * (1 + self.take_profit_rise_pct / 100) + retrace_close_threshold = kline_open * (1 + (self.take_profit_rise_pct - self.take_profit_retrace_pct) / 100) + if current_price >= rise_threshold: + 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线开盘价={kline_open:.2f},曾涨至≥{rise_threshold:.2f}(+{self.take_profit_rise_pct}%)," + f"现价{current_price:.2f}已回调至≤{retrace_close_threshold:.2f}(+{self.take_profit_rise_pct - self.take_profit_retrace_pct}%),按规则止盈平多" + ) + self._log_take_profit_action("止盈平多仓", reason) + self.平仓() + self.take_profit_reentry_threshold = kline_open * (1 + self.reentry_rise_pct / 100) + self.take_profit_triggered_kline_id = None page_start = True - if self.page: - try: - self.page.close() - time.sleep(5) - except Exception: - pass - continue - logger.error("风控平仓后仍有持仓,等待下一轮重试") - continue - - # 5. 检查信号 - signal = self.check_signal(current_price, prev_kline, current_kline, atr_value=atr_value) + time.sleep(1) + else: + signal = self.check_signal(current_price, prev_kline, current_kline) + # 3.6 止盈平多后:价格涨回 0.2% 则同向开多 + elif self.start == 0 and self.take_profit_reentry_threshold is not None: + if current_price >= self.take_profit_reentry_threshold: + reason = ( + f"止盈平仓后价格涨回≥{self.take_profit_reentry_threshold:.2f}(相对当根K线开盘+{self.reentry_rise_pct}%),按规则同向开多" + ) + self._log_take_profit_action("止盈后同向开多", reason) + self.开单(marketPriceLongOrder=1, size=self.default_order_size) + time.sleep(3) + if self.verify_position_direction(1): + self.last_open_time = time.time() + self.last_open_kline_id = current_kline_time + logger.success("止盈后再开多成功") + page_start = True + else: + logger.error("止盈后再开多验证失败") + self.take_profit_reentry_threshold = None + else: + signal = self.check_signal(current_price, prev_kline, current_kline) + # 3.7 空仓止盈:当前K线跌 0.4% 后反弹 0.3% 则平空 + elif self.start == -1: + drop_threshold = kline_open * (1 - self.take_profit_rise_pct / 100) + retrace_close_threshold_short = kline_open * (1 - (self.take_profit_rise_pct - self.take_profit_retrace_pct) / 100) + if current_price <= drop_threshold: + 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线开盘价={kline_open:.2f},曾跌至≤{drop_threshold:.2f}(-{self.take_profit_rise_pct}%)," + f"现价{current_price:.2f}已反弹至≥{retrace_close_threshold_short:.2f}(-{self.take_profit_rise_pct - self.take_profit_retrace_pct}%),按规则止盈平空" + ) + self._log_take_profit_action("止盈平空仓", reason) + self.平仓() + self.take_profit_reentry_threshold_short = kline_open * (1 - self.reentry_rise_pct / 100) + self.take_profit_triggered_kline_id_short = None + page_start = True + time.sleep(1) + else: + signal = self.check_signal(current_price, prev_kline, current_kline) + # 3.8 止盈平空后:价格再跌到 0.2% 则同向开空 + elif self.start == 0 and self.take_profit_reentry_threshold_short is not None: + if current_price <= self.take_profit_reentry_threshold_short: + reason = ( + f"止盈平空后价格再跌至≤{self.take_profit_reentry_threshold_short:.2f}(相对当根K线开盘-{self.reentry_rise_pct}%),按规则同向开空" + ) + self._log_take_profit_action("止盈后同向开空", reason) + self.开单(marketPriceLongOrder=-1, size=self.default_order_size) + time.sleep(3) + if self.verify_position_direction(-1): + self.last_open_time = time.time() + self.last_open_kline_id = current_kline_time + logger.success("止盈后再开空成功") + page_start = True + else: + logger.error("止盈后再开空验证失败") + self.take_profit_reentry_threshold_short = None + else: + signal = self.check_signal(current_price, prev_kline, current_kline) + else: + # 4. 检查信号 + signal = self.check_signal(current_price, prev_kline, current_kline) # 6. 反手过滤:冷却时间 if signal and signal[0].startswith('reverse_'):