From e1cc39a18e9f87952d9565b5e5816c19832500aa Mon Sep 17 00:00:00 2001 From: 27942 <1313123@342> Date: Sat, 7 Feb 2026 22:33:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=93=88=E5=93=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../四分之一,五分钟,反手条件充足修改版.py | 138 ++++++++++++------ 1 file changed, 94 insertions(+), 44 deletions(-) diff --git a/bitmart/四分之一,五分钟,反手条件充足修改版.py b/bitmart/四分之一,五分钟,反手条件充足修改版.py index 1077a6c..3cbcc8f 100644 --- a/bitmart/四分之一,五分钟,反手条件充足修改版.py +++ b/bitmart/四分之一,五分钟,反手条件充足修改版.py @@ -31,9 +31,12 @@ class BitmartFuturesTransaction: self.leverage = "100" # 高杠杆(全仓模式下可开更大仓位) self.open_type = "cross" # 全仓模式 self.risk_percent = 0 # 未使用;若启用则可为每次开仓占可用余额的百分比 - self.trailing_activation_usd = 2 # 盈利达到此金额后启动移动止损 - self.trailing_distance_usd = 1.5 # 从最高盈利回撤此金额则平仓 - self.max_unrealized_pnl_seen = None # 持仓期间见过的最大盈利(用于移动止损) + + # ATR 移动止损:trailing_stop = 最高价 - ATR*k(多)/ 最低价 + ATR*k(空) + self.atr_period = 14 # ATR 周期 + self.atr_k = 2.0 # 稳健 2.5~3,激进 1.5~2 + self.max_high_since_open = None # 持多仓期间最高价 + self.min_low_since_open = None # 持空仓期间最低价 self.open_avg_price = None # 开仓价格 self.current_amount = None # 持仓量 @@ -42,7 +45,7 @@ class BitmartFuturesTransaction: self.default_order_size = 25 # 开仓/反手张数,统一在此修改 self.last_trade_kline_id = None # 上次新开仓所在K线ID,同一根K线只允许开一笔新仓(反手不受限) - self.accumulated_loss = 0 # 累计亏损:反手时上一笔亏损累加,移动止损激活阈值需覆盖此亏损 + self.accumulated_loss = 0 # 累计亏损(反手时记录,预留) # 策略相关变量 self.prev_kline = None # 上一根K线 @@ -51,7 +54,7 @@ class BitmartFuturesTransaction: self.current_open = None # 当前K线开盘价 def get_klines(self): - """获取最近2根K线(当前K线和上一根K线)""" + """获取最近多根K线(用于向前回溯查找实体足够大的K线)""" try: end_time = int(time.time()) # 获取足够多的条目确保有最新的K线 @@ -74,14 +77,14 @@ class BitmartFuturesTransaction: }) formatted.sort(key=lambda x: x['id']) - # 返回最近2根K线:倒数第二根(上一根)和最后一根(当前) + # 返回所有K线列表(按时间升序),至少需要2根 if len(formatted) >= 2: - return formatted[-2], formatted[-1] - return None, None + return formatted + return None except Exception as e: logger.error(f"获取K线异常: {e}") self.ding(text="获取K线异常", error=True) - return None, None + return None def get_current_price(self): """获取当前最新价格""" @@ -262,19 +265,55 @@ class BitmartFuturesTransaction: 'lower': min(kline['open'], kline['close']) # 实体下边 } - def check_signal(self, current_price, prev_kline, current_kline): + def calc_atr(self, klines, period=14): + """ + 计算 ATR(Average True Range) + klines: 按时间升序的K线列表,至少需要 period+1 根(第一根无 prev_close 用 high-low) + 返回: 最新一根K线对应的 ATR 值,不足数据时返回 None + """ + if not klines or len(klines) < period + 1: + return None + tr_list = [] + for i, k in enumerate(klines): + high, low = k['high'], k['low'] + if i == 0: + tr = high - low + else: + prev_close = klines[i - 1]['close'] + tr = max(high - low, abs(high - prev_close), abs(low - prev_close)) + tr_list.append(tr) + # 取最近 period 根 K 线的 TR 的简单平均 + atr = sum(tr_list[-period:]) / period + return atr + + def check_signal(self, current_price, klines): """ 检查交易信号 + klines: K线列表(按时间升序),最后一根为当前K线 返回: ('long', trigger_price) / ('short', trigger_price) / None """ + current_kline = klines[-1] + + # 从上一根K线开始往前回溯,找到实体足够大的K线(实体 >= 0.1) + prev_kline = None + for i in range(len(klines) - 2, -1, -1): # 从倒数第二根开始往前找 + candidate = klines[i] + entity = self.calculate_entity(candidate) + if entity >= 0.1: + prev_kline = candidate + if i < len(klines) - 2: + logger.info(f"上一根K线实体过小,回溯到第 {len(klines) - 1 - i} 根前的K线(实体: {entity:.4f})") + break + else: + logger.debug(f"回溯K线 {candidate['id']} 实体过小: {entity:.4f},继续往前") + + if prev_kline is None: + logger.info("所有历史K线实体均过小,跳过信号检测") + return None + # 计算上一根K线实体 prev_entity = self.calculate_entity(prev_kline) - # 实体过小不交易(实体 < 0.1) - if prev_entity < 0.1: - logger.info(f"上一根K线实体过小: {prev_entity:.4f},跳过信号检测") - return None - # 获取上一根K线的实体上下边 prev_entity_edge = self.get_entity_edge(prev_kline) prev_entity_upper = prev_entity_edge['upper'] # 实体上边 @@ -424,7 +463,8 @@ class BitmartFuturesTransaction: # 验证开仓是否成功 if self.verify_position_direction(1): - self.max_unrealized_pnl_seen = None # 新仓位重置移动止损记录 + self.max_high_since_open = None + self.min_low_since_open = None # ATR 移动止损重置 self.accumulated_loss = 0 # 新开仓重置累计亏损 logger.success("开多成功") return True @@ -448,7 +488,8 @@ class BitmartFuturesTransaction: # 验证开仓是否成功 if self.verify_position_direction(-1): - self.max_unrealized_pnl_seen = None # 新仓位重置移动止损记录 + self.min_low_since_open = None + self.max_high_since_open = None # ATR 移动止损重置 self.accumulated_loss = 0 # 新开仓重置累计亏损 logger.success("开空成功") return True @@ -479,7 +520,8 @@ class BitmartFuturesTransaction: time.sleep(3) if self.verify_position_direction(1): - self.max_unrealized_pnl_seen = None + self.max_high_since_open = None + self.min_low_since_open = None # ATR 移动止损重置 logger.success("反手做多成功") time.sleep(20) return True @@ -509,7 +551,8 @@ class BitmartFuturesTransaction: time.sleep(3) if self.verify_position_direction(-1): - self.max_unrealized_pnl_seen = None + self.min_low_since_open = None + self.max_high_since_open = None # ATR 移动止损重置 logger.success("反手做空成功") time.sleep(20) return True @@ -552,13 +595,15 @@ class BitmartFuturesTransaction: page_start = False try: - # 1. 获取K线数据(当前K线和上一根K线) - prev_kline, current_kline = self.get_klines() - if not prev_kline or not current_kline: + # 1. 获取K线数据 + klines = self.get_klines() + if not klines: logger.warning("获取K线失败,等待重试...") time.sleep(5) continue + current_kline = klines[-1] + # 记录进入新的K线 current_kline_time = current_kline['id'] if self.last_kline_time != current_kline_time: @@ -580,34 +625,39 @@ class BitmartFuturesTransaction: logger.debug(f"当前持仓状态: {self.start} (0=无, 1=多, -1=空)") - # 3.5 移动止损(激活阈值 = 基础阈值 + 累计亏损) + # 3.5 ATR 移动止损:每根 K 线 trailing_stop = 最高价 - ATR*k(多)/ 最低价 + ATR*k(空) if self.start != 0: - pnl_usd = self.get_unrealized_pnl_usd() - if pnl_usd is not None: - # 更新持仓期间最大盈利(用于移动止损) - if self.max_unrealized_pnl_seen is None: - self.max_unrealized_pnl_seen = pnl_usd - else: - self.max_unrealized_pnl_seen = max(self.max_unrealized_pnl_seen, pnl_usd) - # 移动止损激活阈值 = 基础阈值 + 累计亏损(需要先赚回之前反手的亏损) - effective_activation = self.trailing_activation_usd + self.accumulated_loss - if self.accumulated_loss > 0: - logger.debug(f"移动止损激活阈值: {self.trailing_activation_usd} + 累计亏损 {self.accumulated_loss:.2f} = {effective_activation:.2f} 美元") - # 移动止损:盈利曾达到 effective_activation 后,从最高盈利回撤 trailing_distance 则平仓 - if self.max_unrealized_pnl_seen >= effective_activation: - if pnl_usd < self.max_unrealized_pnl_seen - self.trailing_distance_usd: - logger.info(f"移动止损:当前盈利 {pnl_usd:.2f} 从最高 {self.max_unrealized_pnl_seen:.2f} 回撤 >= {self.trailing_distance_usd} 美元,平仓" - f"(激活阈值={effective_activation:.2f},含累计亏损 {self.accumulated_loss:.2f})") + # 更新持仓以来的最高/最低价(当前K线 high/low + 当前价) + if self.start == 1: # 持多 + cand_high = max(current_kline['high'], current_price) + self.max_high_since_open = cand_high if self.max_high_since_open is None else max(self.max_high_since_open, cand_high) + else: # 持空 + cand_low = min(current_kline['low'], current_price) + self.min_low_since_open = cand_low if self.min_low_since_open is None else min(self.min_low_since_open, cand_low) + + atr = self.calc_atr(klines, self.atr_period) + if atr is not None: + if self.start == 1: # 持多:价格跌破 trailing_stop 平仓 + trailing_stop = self.max_high_since_open - atr * self.atr_k + if current_price < trailing_stop: + logger.info(f"ATR移动止损(多):当前价 {current_price:.2f} < 跟踪止盈 {trailing_stop:.2f} (最高{self.max_high_since_open:.2f} - ATR{atr:.4f}*{self.atr_k}),平仓") self.平仓() - self.max_unrealized_pnl_seen = None - self.accumulated_loss = 0 # 止盈平仓后重置累计亏损 - self.last_trade_kline_id = current_kline_time # 平仓后标记当前K线,下一根K线才能再开仓 - logger.info(f"平仓后标记K线({current_kline_time}),等待下一根K线再开仓,累计亏损已重置") + self.max_high_since_open = None + self.last_trade_kline_id = current_kline_time + time.sleep(3) + continue + else: # 持空:价格升破 trailing_stop 平仓 + trailing_stop = self.min_low_since_open + atr * self.atr_k + if current_price > trailing_stop: + logger.info(f"ATR移动止损(空):当前价 {current_price:.2f} > 跟踪止盈 {trailing_stop:.2f} (最低{self.min_low_since_open:.2f} + ATR{atr:.4f}*{self.atr_k}),平仓") + self.平仓() + self.min_low_since_open = None + self.last_trade_kline_id = current_kline_time time.sleep(3) continue # 4. 检查信号 - signal = self.check_signal(current_price, prev_kline, current_kline) + signal = self.check_signal(current_price, klines) # 5. 同一根K线只允许开一笔新仓位(反手不受限制) if signal and signal[0] in ('long', 'short') and self.last_trade_kline_id == current_kline_time: