diff --git a/交易/bitmart-五分之一策略交易.py b/交易/bitmart-五分之一策略交易.py index 64a05e8..e2c316c 100644 --- a/交易/bitmart-五分之一策略交易.py +++ b/交易/bitmart-五分之一策略交易.py @@ -76,6 +76,13 @@ class BitmartOneFifthStrategy: # 当检测到反手信号时,当前价格必须在触发价附近才执行 # 避免"先涨后跌"或"先跌后涨"的情况下错误开仓 self.reverse_price_tolerance = 2.0 # 2美元容差 + + # 基于开仓价格的反手信号参数 + # 记录开仓时使用的前一根K线实体大小(用于计算反手触发价) + self.entry_prev_body = None # 开仓时前一根K线的实体大小 + self.entry_price = None # 开仓价格(用于计算反手触发价) + self.entry_kline_id = None # 开仓时的K线ID(用于判断是否在同一根K线内) + self.first_minute_reverse_executed = False # 当前K线是否已执行过第一分钟反手 # ========================= 五分之一策略核心 ========================= @@ -163,6 +170,91 @@ class BitmartOneFifthStrategy: return 'short' if d_short < d_long else 'long' return None + def check_first_minute_reverse_signal(self, curr_kline, kline_data): + """ + 检测基于开仓价格的反手信号(只在3分钟K线的第一分钟有效) + + 反手规则: + - 空仓反手开多:开空仓后,价格涨到 开仓价格 + 前一根K线实体/5 → 平空开多 + - 多仓反手开空:开多仓后,价格跌到 开仓价格 - 前一根K线实体/5 → 平多开空 + + :param curr_kline: 当前3分钟K线数据 + :param kline_data: 所有K线数据(用于获取前一根K线实体) + :return: (方向, 触发价格) 或 (None, None) + """ + # 检查是否有持仓 + if self.start == 0: + return None, None + + curr_kline_id = curr_kline['id'] + + # 如果K线切换了,重置第一分钟反手标记 + if self.entry_kline_id != curr_kline_id: + self.first_minute_reverse_executed = False + self.entry_kline_id = curr_kline_id # 更新当前K线ID + + # 检查当前K线是否已执行过第一分钟反手 + if self.first_minute_reverse_executed: + return None, None + + # 获取开仓价格(如果没有记录,使用API返回的开仓均价) + entry_price = self.entry_price + if entry_price is None and self.open_avg_price: + entry_price = float(self.open_avg_price) + if entry_price is None: + return None, None + + # 获取前一根有效K线的实体大小 + valid_prev_idx, valid_prev = self.find_valid_prev_bar(kline_data, len(kline_data) - 1, self.min_body_size) + if valid_prev is None: + return None, None + prev_body = self.get_body_size(valid_prev) + + # 计算反手触发价格 + reverse_offset = prev_body / 5 + + # 获取第一分钟K线 + bars_1m = self.get_1m_bars_for_3m_bar(curr_kline) + if not bars_1m or len(bars_1m) < 1: + return None, None + + first_1m = bars_1m[0] # 第一分钟K线 + first_1m_high = float(first_1m['high']) + first_1m_low = float(first_1m['low']) + first_1m_close = float(first_1m['close']) + + if self.start == -1: + # 持有空仓,检测反手开多信号 + # 反手触发价 = 开仓价格 + 前一根实体/5 + reverse_long_trigger = entry_price + reverse_offset + + # 检查第一分钟是否触及反手触发价 + if first_1m_high >= reverse_long_trigger: + # 第一分钟高点触及反手触发价,并且当前价格在触发价附近 + if first_1m_close >= reverse_long_trigger - self.reverse_price_tolerance: + logger.info(f"🔄 第一分钟反手信号检测:持空仓,第一分钟高点{first_1m_high:.2f}>=反手触发价{reverse_long_trigger:.2f}") + logger.info(f" 开仓价={entry_price:.2f}, 前一根实体={prev_body:.2f}, 实体/5={reverse_offset:.2f}") + return 'long', reverse_long_trigger + else: + logger.debug(f"第一分钟反手信号被过滤:当前价格{first_1m_close:.2f}已远离触发价{reverse_long_trigger:.2f}") + + elif self.start == 1: + # 持有多仓,检测反手开空信号 + # 反手触发价 = 开仓价格 - 前一根实体/5 + reverse_short_trigger = entry_price - reverse_offset + + # 检查第一分钟是否触及反手触发价 + if first_1m_low <= reverse_short_trigger: + # 第一分钟低点触及反手触发价,并且当前价格在触发价附近 + if first_1m_close <= reverse_short_trigger + self.reverse_price_tolerance: + logger.info(f"🔄 第一分钟反手信号检测:持多仓,第一分钟低点{first_1m_low:.2f}<=反手触发价{reverse_short_trigger:.2f}") + logger.info(f" 开仓价={entry_price:.2f}, 前一根实体={prev_body:.2f}, 实体/5={reverse_offset:.2f}") + return 'short', reverse_short_trigger + else: + logger.debug(f"第一分钟反手信号被过滤:当前价格{first_1m_close:.2f}已远离触发价{reverse_short_trigger:.2f}") + + return None, None + def check_realtime_trigger(self, kline_data, current_position=0): """ 实时检测当前3分钟K线是否触发信号 @@ -495,6 +587,62 @@ class BitmartOneFifthStrategy: if not self.get_position_status(): logger.warning("获取仓位信息失败,使用缓存的持仓状态") + # ========== 第一分钟反手信号检测 ========== + # 如果有持仓,先检测基于开仓价格的第一分钟反手信号 + first_min_direction = None + first_min_trigger_price = None + if self.start != 0: + first_min_direction, first_min_trigger_price = self.check_first_minute_reverse_signal(curr, kline_data) + + # 如果检测到第一分钟反手信号,优先执行 + if first_min_direction: + curr_kline_id = curr['id'] + if self.last_trade_kline_id != curr_kline_id: + logger.info(f"{'=' * 50}") + logger.info(f"🔄 第一分钟反手信号触发!方向: {first_min_direction}, 触发价: {first_min_trigger_price:.2f}") + logger.info(f" 开仓价: {self.entry_price:.2f}, 前一根实体/5: {self.entry_prev_body/5:.2f}") + logger.info(f" 当前持仓: {self.start} (1=多, -1=空)") + + balance = self.get_available_balance() + trade_size = (balance or 0) * self.risk_percent + + if first_min_direction == 'long' and self.start == -1: + logger.info("📈 第一分钟反手:平空仓,反手开多") + self.平仓() + time.sleep(1) + self.开单(marketPriceLongOrder=1, size=trade_size) + self.first_minute_reverse_executed = True + self.last_trade_kline_id = curr_kline_id + # 更新开仓信息 + valid_prev_idx, valid_prev = self.find_valid_prev_bar(kline_data, len(kline_data) - 1, self.min_body_size) + if valid_prev: + self.entry_prev_body = self.get_body_size(valid_prev) + self.entry_price = float(curr['close']) + self.entry_kline_id = curr_kline_id + self.get_position_status() + self._send_position_message(curr) + + elif first_min_direction == 'short' and self.start == 1: + logger.info("📉 第一分钟反手:平多仓,反手开空") + self.平仓() + time.sleep(1) + self.开单(marketPriceLongOrder=-1, size=trade_size) + self.first_minute_reverse_executed = True + self.last_trade_kline_id = curr_kline_id + # 更新开仓信息 + valid_prev_idx, valid_prev = self.find_valid_prev_bar(kline_data, len(kline_data) - 1, self.min_body_size) + if valid_prev: + self.entry_prev_body = self.get_body_size(valid_prev) + self.entry_price = float(curr['close']) + self.entry_kline_id = curr_kline_id + self.get_position_status() + self._send_position_message(curr) + + logger.info(f"{'=' * 50}") + time.sleep(self.check_interval) + continue + + # ========== 原有的五分之一信号检测 ========== # 传入当前持仓状态,确保有持仓时只关注反向信号 direction, trigger_price, valid_prev, curr_kline = self.check_realtime_trigger( kline_data, current_position=self.start @@ -560,6 +708,13 @@ class BitmartOneFifthStrategy: self.last_trigger_direction = direction if executed: self.last_trade_kline_id = curr_kline_id + # 记录开仓信息,用于第一分钟反手信号检测 + self.entry_price = trigger_price # 开仓触发价格 + self.entry_prev_body = self.get_body_size(valid_prev) # 前一根K线实体大小 + self.entry_kline_id = curr_kline_id # 开仓时的K线ID + self.first_minute_reverse_executed = False # 重置第一分钟反手标记 + logger.info(f" 记录开仓信息:开仓价={self.entry_price:.2f}, 前一根实体={self.entry_prev_body:.2f}, K线ID={self.entry_kline_id}") + self.get_position_status() self._send_position_message(curr_kline) last_report_time = time.time()