From e8ebfacaff334eea490c986c0a2da327af7a611a Mon Sep 17 00:00:00 2001 From: 27942 <1313123@342> Date: Sun, 8 Feb 2026 17:39:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AE=8C=E7=BE=8E=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=EF=BC=8C=E4=BC=98=E5=8C=96=E8=AE=A1=E7=AE=97=E7=9B=88?= =?UTF-8?q?=E4=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../三分之一,五分钟,反手条件充足修改版.py | 580 ++++++++++++------ 1 file changed, 386 insertions(+), 194 deletions(-) diff --git a/bitmart/三分之一,五分钟,反手条件充足修改版.py b/bitmart/三分之一,五分钟,反手条件充足修改版.py index 7d3cc12..41064a4 100644 --- a/bitmart/三分之一,五分钟,反手条件充足修改版.py +++ b/bitmart/三分之一,五分钟,反手条件充足修改版.py @@ -8,6 +8,21 @@ from DrissionPage import ChromiumOptions from bitmart.api_contract import APIContract +# ---------- 策略常量(影线 ATR 自适应 · 趋势/震荡双模式 · 高胜率版)---------- +K_PERIOD = 5 +ATR_LENGTH = 14 +MODE_LOOKBACK = 6 +MIN_BODY = 0.1 +DEFAULT_ORDER_SIZE = 25 +WICK_ORDER_RATIO = 0.3 +WICK_VALID = 0.9 # ≥ 0.9 ATR 才认为是可交易影线 +WICK_EXTREME = 1.2 # ≥ 1.2 ATR 为极端扫单 +TRAIL_RANGE = 0.2 # 震荡模式回撤止盈 = ATR * 0.2 +TRAIL_TREND = 0.35 # 趋势模式回撤止盈 = ATR * 0.35 +TP_ACTIVATION = 0.3 # 启动止盈需浮盈 ≥ ATR * 0.3 +SL_WICK_ATR = 0.3 # 影线单止损:多单 SL = low - ATR*0.3,空单 SL = high + ATR*0.3 +MODE_LOCK_BARS = 3 # 趋势模式锁定 K 根数 + class BitmartFuturesTransaction: def __init__(self, bit_id): @@ -24,51 +39,58 @@ class BitmartFuturesTransaction: self.start = 0 # 持仓状态: -1 空, 0 无, 1 多 - self.pbar = tqdm(total=30, desc="等待K线", ncols=80) # 可选:用于长时间等待时展示进度 + self.pbar = tqdm(total=30, desc="等待K线", ncols=80) - self.last_kline_time = None # 上一次处理的K线时间戳,用于判断是否是新K线 + self.last_kline_time = None - self.leverage = "100" # 高杠杆(全仓模式下可开更大仓位) - self.open_type = "cross" # 全仓模式 - self.risk_percent = 0 # 未使用;若启用则可为每次开仓占可用余额的百分比 + self.leverage = "100" + self.open_type = "cross" + self.risk_percent = 0 - self.open_avg_price = None # 开仓价格 - self.current_amount = None # 持仓量 + self.open_avg_price = None + self.current_amount = None self.bit_id = bit_id - self.default_order_size = 25 # 开仓/反手张数,统一在此修改 + self.default_order_size = DEFAULT_ORDER_SIZE # 策略相关变量 - self.prev_kline = None # 上一根K线 - self.current_kline = None # 当前K线 - self.prev_entity = None # 上一根K线实体大小 - self.current_open = None # 当前K线开盘价 + self.prev_kline = None + self.current_kline = None + + # 趋势/震荡模式状态 + self.mode = "TREND" # TREND | RANGE + self.mode_lock_remaining = 0 # 趋势模式锁定剩余 K 数 + + # 止盈用:入场价、持仓期间最优价(多=最高,空=最低) + self.entry_price = None + self.best_price = None + self.is_wick_position = False # 当前仓位是否为影线单(用于影线单止损) + + def _format_kline_list(self, response_data): + """将 API 返回的 K 线转为统一格式列表""" + formatted = [] + for k in response_data: + formatted.append({ + 'id': int(k["timestamp"]), + 'open': float(k["open_price"]), + 'high': float(k["high_price"]), + 'low': float(k["low_price"]), + 'close': float(k["close_price"]) + }) + formatted.sort(key=lambda x: x['id']) + return formatted def get_klines(self): - """获取最近2根K线(当前K线和上一根K线)""" + """获取最近 2 根 K 线(当前、上一根)""" try: end_time = int(time.time()) - # 获取足够多的条目确保有最新的K线 response = self.contractAPI.get_kline( contract_symbol=self.contract_symbol, - step=5, # 5分钟 - start_time=end_time - 3600 * 3, # 取最近3小时 + step=K_PERIOD, + start_time=end_time - 3600 * 3, end_time=end_time )[0]["data"] - - # 每根: [timestamp, open, high, low, close, volume] - formatted = [] - for k in response: - formatted.append({ - 'id': int(k["timestamp"]), - 'open': float(k["open_price"]), - 'high': float(k["high_price"]), - 'low': float(k["low_price"]), - 'close': float(k["close_price"]) - }) - formatted.sort(key=lambda x: x['id']) - - # 返回最近2根K线:倒数第二根(上一根)和最后一根(当前) + formatted = self._format_kline_list(response) if len(formatted) >= 2: return formatted[-2], formatted[-1] return None, None @@ -77,6 +99,97 @@ class BitmartFuturesTransaction: self.ding(text="获取K线异常", error=True) return None, None + def get_klines_batch(self, count): + """获取最近 count 根 5 分钟 K 线,用于 ATR 与模式判定。至少需要 ATR_LENGTH + MODE_LOOKBACK。""" + try: + end_time = int(time.time()) + response = self.contractAPI.get_kline( + contract_symbol=self.contract_symbol, + step=K_PERIOD, + start_time=end_time - 3600 * 4, + end_time=end_time + )[0]["data"] + formatted = self._format_kline_list(response) + if len(formatted) >= count: + return formatted[-count:] + return formatted + except Exception as e: + logger.error(f"获取K线批量异常: {e}") + return [] + + def compute_atr(self, klines, length=ATR_LENGTH): + """ATR(length),klines 为按时间升序的 K 线列表,每根含 high, low, close。""" + if not klines or len(klines) < length + 1: + return None + tr_list = [] + for i in range(1, len(klines)): + high, low = klines[i]['high'], 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 + # 前 length 个 TR 的均值作为第一个 ATR,然后平滑(这里用 SMA 简化) + atr = sum(tr_list[-length:]) / length + return atr + + def get_prev_kline_struct(self, prev_kline): + """上一根 K 线基础结构(与策略文档一致)""" + body_high = max(prev_kline['open'], prev_kline['close']) + body_low = min(prev_kline['open'], prev_kline['close']) + body_size = body_high - body_low + upper_wick = prev_kline['high'] - body_high + lower_wick = body_low - prev_kline['low'] + full_range = prev_kline['high'] - prev_kline['low'] + return { + 'body_high': body_high, + 'body_low': body_low, + 'body_size': body_size, + 'upper_wick': upper_wick, + 'lower_wick': lower_wick, + 'full_range': full_range if full_range > 0 else 1e-8, + } + + def detect_mode(self, klines): + """基于最近 MODE_LOOKBACK 根 K 线判定趋势/震荡,并更新 self.mode 与锁定。""" + if not klines or len(klines) < MODE_LOOKBACK: + return + lookback = klines[-MODE_LOOKBACK:] + body_sizes = [] + full_ranges = [] + upper_wicks = [] + lower_wicks = [] + bullish_count = sum(1 for k in lookback if k['close'] > k['open']) + bearish_count = len(lookback) - bullish_count + direction_consistency = max(bullish_count, bearish_count) / MODE_LOOKBACK + + for k in lookback: + s = self.get_prev_kline_struct(k) + body_sizes.append(s['body_size']) + full_ranges.append(s['full_range']) + upper_wicks.append(s['upper_wick']) + lower_wicks.append(s['lower_wick']) + avg_body = sum(body_sizes) / len(body_sizes) + avg_range = sum(full_ranges) / len(full_ranges) + avg_wick = sum(upper_wicks) / len(upper_wicks) + sum(lower_wicks) / len(lower_wicks) + body_ratio = avg_body / avg_range if avg_range > 0 else 0 + wick_ratio = avg_wick / avg_range if avg_range > 0 else 0 + + trend_score = sum([body_ratio >= 0.6, wick_ratio <= 0.35, direction_consistency >= 0.67]) + range_score = sum([body_ratio <= 0.4, wick_ratio >= 0.5, direction_consistency <= 0.5]) + + if self.mode_lock_remaining > 0: + return + if trend_score >= 2: + self.mode = "TREND" + self.mode_lock_remaining = MODE_LOCK_BARS + logger.info(f"模式切换为 TREND,锁定 {MODE_LOCK_BARS} 根K线") + elif range_score >= 2: + self.mode = "RANGE" + self.mode_lock_remaining = 0 + logger.info("模式切换为 RANGE") + # 其他情况沿用上一模式 + def get_current_price(self): """获取当前最新价格""" try: @@ -112,7 +225,7 @@ class BitmartFuturesTransaction: return None def get_position_status(self): - """获取当前持仓方向""" + """获取当前持仓方向,并维护 entry_price / best_price(无仓时清空)""" try: response = self.contractAPI.get_position(contract_symbol=self.contract_symbol)[0] if response['code'] == 1000: @@ -122,15 +235,21 @@ class BitmartFuturesTransaction: self.open_avg_price = None self.current_amount = None self.unrealized_pnl = None + self.entry_price = None + self.best_price = None + self.is_wick_position = False return True pos = positions[0] self.start = 1 if pos['position_type'] == 1 else -1 self.open_avg_price = float(pos['open_avg_price']) self.current_amount = float(pos['current_amount']) self.position_cross = pos["position_cross"] - # 直接从API获取未实现盈亏(Bitmart返回的是 unrealized_value 字段) self.unrealized_pnl = float(pos.get('unrealized_value', 0)) - logger.debug(f"持仓详情: 方向={self.start}, 开仓均价={self.open_avg_price}, " + if self.entry_price is None: + self.entry_price = self.open_avg_price + self.best_price = self.open_avg_price + self.is_wick_position = False + logger.debug(f"持仓: 方向={self.start}, 开仓均价={self.open_avg_price}, " f"持仓量={self.current_amount}, 未实现盈亏={self.unrealized_pnl:.2f}") return True else: @@ -187,16 +306,18 @@ class BitmartFuturesTransaction: def 开单(self, marketPriceLongOrder=0, limitPriceShortOrder=0, size=None, price=None): """ - marketPriceLongOrder 市价做多或者做空,1是做多,-1是做空 - limitPriceShortOrder 限价做多或者做空 + marketPriceLongOrder 市价做多或做空,1=多,-1=空 + limitPriceShortOrder 限价 + size 市价单张数,不传则用 default_order_size """ + order_size = size if size is not None else self.default_order_size if marketPriceLongOrder == -1: - # self.click_safe('x://button[normalize-space(text()) ="市价"]') - # self.page.ele('x://*[@id="size_0"]').input(vals=size, clear=True) + self.click_safe('x://button[normalize-space(text()) ="市价"]') + self.page.ele('x://*[@id="size_0"]').input(vals=order_size, clear=True) self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]') elif marketPriceLongOrder == 1: - # self.click_safe('x://button[normalize-space(text()) ="市价"]') - # self.page.ele('x://*[@id="size_0"]').input(vals=size, clear=True) + self.click_safe('x://button[normalize-space(text()) ="市价"]') + self.page.ele('x://*[@id="size_0"]').input(vals=order_size, clear=True) self.click_safe('x://span[normalize-space(text()) ="买入/做多"]') if limitPriceShortOrder == -1: @@ -219,135 +340,142 @@ class BitmartFuturesTransaction: else: logger.info(text) - def calculate_entity(self, kline): - """计算K线实体大小(绝对值)""" - return abs(kline['close'] - kline['open']) + def compute_third_prices(self, prev_kline, current_kline): + """ + 三分之一关键价位与形态过滤(与策略文档一致)。 + 返回: dict(long_trigger, short_trigger, break_long, break_short, + skip_long_by_lower_third, skip_short_by_upper_third, body_size, prev_struct) + """ + s = self.get_prev_kline_struct(prev_kline) + body_high, body_low, body_size = s['body_high'], s['body_low'], s['body_size'] - 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 + prev_bull = prev_kline['close'] > prev_kline['open'] + prev_bear = prev_kline['close'] < prev_kline['open'] + curr_open_above = current_kline['open'] > prev_kline['close'] + curr_open_below = current_kline['open'] < prev_kline['close'] + use_curr_open = (prev_bull and curr_open_above) or (prev_bear and curr_open_below) - 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 + if use_curr_open: + co = current_kline['open'] + long_trigger = co - body_size / 3 + short_trigger = co + body_size / 3 + break_long = co + body_size / 3 + break_short = co - body_size / 3 + else: + long_trigger = body_low + body_size / 3 + short_trigger = body_high - body_size / 3 + break_long = body_high + body_size / 3 + break_short = body_low - body_size / 3 + + prev_bear_curr_bull = prev_bear and (current_kline['close'] > current_kline['open']) + prev_bull_curr_bear = prev_bull and (current_kline['close'] < current_kline['open']) + skip_short_by_upper_third = prev_bear_curr_bull + skip_long_by_lower_third = prev_bull_curr_bear - def get_entity_edge(self, kline): - """获取K线实体边(收盘价或开盘价,取决于是阳线还是阴线)""" - # 阳线(收盘>开盘):实体上边=收盘价,实体下边=开盘价 - # 阴线(收盘<开盘):实体上边=开盘价,实体下边=收盘价 return { - 'upper': max(kline['open'], kline['close']), # 实体上边 - 'lower': min(kline['open'], kline['close']) # 实体下边 + 'long_trigger': long_trigger, + 'short_trigger': short_trigger, + 'break_long': break_long, + 'break_short': break_short, + 'skip_long_by_lower_third': skip_long_by_lower_third, + 'skip_short_by_upper_third': skip_short_by_upper_third, + 'body_size': body_size, + 'prev_struct': s, } - def check_signal(self, current_price, prev_kline, current_kline): - """ - 检查交易信号 - 返回: ('long', trigger_price) / ('short', trigger_price) / None - """ - # 计算上一根K线实体 - prev_entity = self.calculate_entity(prev_kline) + def check_sl(self, current_price, prev_kline, atr): + """影线单止损(优先级 1)。仅对 is_wick_position 生效。返回 'close' 或 None。""" + if atr is None or atr <= 0 or not self.is_wick_position: + return None + if self.start == 1: + sl = prev_kline['low'] - atr * SL_WICK_ATR + if current_price <= sl: + logger.info(f"影线多单止损: 价格 {current_price:.2f} <= SL {sl:.2f} (low - ATR*{SL_WICK_ATR})") + return 'close' + elif self.start == -1: + sl = prev_kline['high'] + atr * SL_WICK_ATR + if current_price >= sl: + logger.info(f"影线空单止损: 价格 {current_price:.2f} >= SL {sl:.2f} (high + ATR*{SL_WICK_ATR})") + return 'close' + return None - # 实体过小不交易(实体 < 0.1) - if prev_entity < 0.1: - logger.info(f"上一根K线实体过小: {prev_entity:.4f},跳过信号检测") + def check_tp(self, current_price, atr): + """回撤止盈(优先级 2)。返回 'close' 或 None。""" + if atr is None or atr <= 0 or self.entry_price is None or self.best_price is None: + return None + if abs(self.best_price - self.entry_price) < atr * TP_ACTIVATION: + return None + trail = TRAIL_RANGE * atr if self.mode == "RANGE" else TRAIL_TREND * atr + if self.start == 1: + if current_price <= self.best_price - trail: + logger.info(f"多单止盈: 价格 {current_price:.2f} <= best_price - TRAIL ({self.best_price:.2f} - {trail:.2f})") + return 'close' + elif self.start == -1: + if current_price >= self.best_price + trail: + logger.info(f"空单止盈: 价格 {current_price:.2f} >= best_price + TRAIL ({self.best_price:.2f} + {trail:.2f})") + return 'close' + return None + + def check_reversal(self, current_price, prev_kline, atr, third): + """反手信号(优先级 3),仅趋势模式。返回 (reverse_long/reverse_short, trigger_price) 或 None。""" + if self.mode != "TREND" or atr is None or atr <= 0: + return None + s = third['prev_struct'] + body_high, body_low = s['body_high'], s['body_low'] + upper_wick_strength = s['upper_wick'] / atr + lower_wick_strength = s['lower_wick'] / atr + + if self.start == 1: + if current_price <= third['short_trigger'] and not third['skip_short_by_upper_third']: + logger.info(f"持多反手做空(1/3): 价格 {current_price:.2f} <= short_trigger {third['short_trigger']:.2f}") + return ('reverse_short', third['short_trigger']) + if upper_wick_strength >= WICK_EXTREME and current_price <= body_low: + logger.info(f"持多反手做空(影线失败): upper_wick_strength={upper_wick_strength:.2f}, 价格<=body_low {body_low:.2f}") + return ('reverse_short', body_low) + elif self.start == -1: + if current_price >= third['long_trigger'] and not third['skip_long_by_lower_third']: + logger.info(f"持空反手做多(1/3): 价格 {current_price:.2f} >= long_trigger {third['long_trigger']:.2f}") + return ('reverse_long', third['long_trigger']) + if lower_wick_strength >= WICK_EXTREME and current_price >= body_high: + logger.info(f"持空反手做多(影线失败): lower_wick_strength={lower_wick_strength:.2f}, 价格>=body_high {body_high:.2f}") + return ('reverse_long', body_high) + return None + + def check_open(self, current_price, prev_kline, current_kline, atr, third): + """ + 新开仓(优先级 4)。趋势模式只吃实体突破;震荡模式只吃影线回撤。 + 返回: (signal_type, trigger_price, size, is_wick) 或 None。 + """ + if self.start != 0: + return None + s = third['prev_struct'] + body_high, body_low = s['body_high'], s['body_low'] + body_size = third['body_size'] + if body_size < MIN_BODY: return None - # 获取上一根K线的实体上下边 - prev_entity_edge = self.get_entity_edge(prev_kline) - prev_entity_upper = prev_entity_edge['upper'] # 实体上边 - prev_entity_lower = prev_entity_edge['lower'] # 实体下边 + if self.mode == "TREND": + if current_price >= third['break_long'] and not third['skip_long_by_lower_third']: + logger.info(f"趋势开多: 价格 {current_price:.2f} >= break_long {third['break_long']:.2f}") + return ('long', third['break_long'], self.default_order_size, False) + if current_price <= third['break_short'] and not third['skip_short_by_upper_third']: + logger.info(f"趋势开空: 价格 {current_price:.2f} <= break_short {third['break_short']:.2f}") + return ('short', third['break_short'], self.default_order_size, False) - # 优化:以下两种情况以当前这根的开盘价作为计算基准 - # 1) 上一根阳线 且 当前开盘价 > 上一根收盘价(跳空高开) - # 2) 上一根阴线 且 当前开盘价 < 上一根收盘价(跳空低开) - prev_is_bullish_for_calc = prev_kline['close'] > prev_kline['open'] - prev_is_bearish_for_calc = prev_kline['close'] < prev_kline['open'] - current_open_above_prev_close = current_kline['open'] > prev_kline['close'] - current_open_below_prev_close = current_kline['open'] < prev_kline['close'] - use_current_open_as_base = (prev_is_bullish_for_calc and current_open_above_prev_close) or (prev_is_bearish_for_calc and current_open_below_prev_close) + elif self.mode == "RANGE" and atr and atr > 0: + upper_wick_strength = s['upper_wick'] / atr + lower_wick_strength = s['lower_wick'] / atr + low, high = prev_kline['low'], prev_kline['high'] + wick_long_level = low + min(s['lower_wick'] * 0.5, atr * 0.6) + wick_short_level = high - min(s['upper_wick'] * 0.5, atr * 0.6) + size_wick = max(1, int(self.default_order_size * WICK_ORDER_RATIO)) - if use_current_open_as_base: - # 以当前K线开盘价为基准计算(跳空时用当前开盘价参与计算) - calc_lower = current_kline['open'] - calc_upper = current_kline['open'] # 同一基准,上下三分之一对称 - long_trigger = calc_lower + prev_entity / 3 - short_trigger = calc_upper - prev_entity / 3 - long_breakout = calc_upper + prev_entity / 3 - short_breakout = calc_lower - prev_entity / 3 - else: - # 原有计算方式 - long_trigger = prev_entity_lower + prev_entity / 3 # 做多触发价 = 实体下边 + 实体/3(下三分之一处) - short_trigger = prev_entity_upper - prev_entity / 3 # 做空触发价 = 实体上边 - 实体/3(上三分之一处) - long_breakout = prev_entity_upper + prev_entity / 3 # 做多突破价 = 实体上边 + 实体/3 - short_breakout = prev_entity_lower - prev_entity / 3 # 做空突破价 = 实体下边 - 实体/3 - - # 上一根阴线 + 当前阳线:做多形态,不按上一根K线上三分之一做空 - prev_is_bearish = prev_kline['close'] < prev_kline['open'] - current_is_bullish = current_kline['close'] > current_kline['open'] - skip_short_by_upper_third = prev_is_bearish and current_is_bullish - # 上一根阳线 + 当前阴线:做空形态,不按上一根K线下三分之一做多 - prev_is_bullish = prev_kline['close'] > prev_kline['open'] - current_is_bearish = current_kline['close'] < current_kline['open'] - skip_long_by_lower_third = prev_is_bullish and current_is_bearish - - if use_current_open_as_base: - if prev_is_bullish_for_calc and current_open_above_prev_close: - logger.info(f"上一根阳线且当前开盘价({current_kline['open']:.2f})>上一根收盘价({prev_kline['close']:.2f}),以当前开盘价为基准计算") - else: - logger.info(f"上一根阴线且当前开盘价({current_kline['open']:.2f})<上一根收盘价({prev_kline['close']:.2f}),以当前开盘价为基准计算") - logger.info(f"当前价格: {current_price:.2f}, 上一根实体: {prev_entity:.4f}") - 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}") - if skip_short_by_upper_third: - logger.info("上一根阴线+当前阳线(做多形态),不按上三分之一做空") - if skip_long_by_lower_third: - logger.info("上一根阳线+当前阴线(做空形态),不按下三分之一做多") - - # 无持仓时检查开仓信号 - if self.start == 0: - if current_price >= long_breakout and not skip_long_by_lower_third: - logger.info(f"触发做多信号!价格 {current_price:.2f} >= 突破价(上1/3外) {long_breakout:.2f}") - 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:.2f}") - return ('short', short_breakout) - - # 持仓时检查反手信号 - elif self.start == 1: # 持多仓 - # 反手条件1: 价格跌到上一根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线上阴线涨幅>0.01%,当前跌到上一根实体下边 - upper_shadow_pct = self.calculate_upper_shadow(prev_kline) - if upper_shadow_pct > 0.01 and current_price <= prev_entity_lower: - logger.info(f"持多反手做空!上阴线涨幅 {upper_shadow_pct:.4f}% > 0.01%," - f"价格 {current_price:.2f} <= 实体下边 {prev_entity_lower:.2f}") - return ('reverse_short', prev_entity_lower) - - elif self.start == -1: # 持空仓 - # 反手条件1: 价格涨到上一根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线下阴线跌幅>0.01%,当前涨到上一根实体上边 - lower_shadow_pct = self.calculate_lower_shadow(prev_kline) - if lower_shadow_pct > 0.01 and current_price >= prev_entity_upper: - logger.info(f"持空反手做多!下阴线跌幅 {lower_shadow_pct:.4f}% > 0.01%," - f"价格 {current_price:.2f} >= 实体上边 {prev_entity_upper:.2f}") - return ('reverse_long', prev_entity_upper) + if lower_wick_strength >= WICK_VALID and current_price >= wick_long_level and current_price > body_low: + logger.info(f"震荡影线开多: lower_wick_strength={lower_wick_strength:.2f}, 价格>={wick_long_level:.2f}") + return ('long', wick_long_level, size_wick, True) + if upper_wick_strength >= WICK_VALID and current_price <= wick_short_level and current_price < body_high: + logger.info(f"震荡影线开空: upper_wick_strength={upper_wick_strength:.2f}, 价格<={wick_short_level:.2f}") + return ('short', wick_short_level, size_wick, True) return None @@ -389,10 +517,35 @@ class BitmartFuturesTransaction: logger.error("查询持仓状态失败") return False - def execute_trade(self, signal, size=None): - """执行交易。size 不传或为 None 时使用 default_order_size。""" - signal_type, trigger_price = signal - size = self.default_order_size if size is None else size + def execute_close(self): + """执行平仓(用于止盈/止损)。""" + self.平仓() + time.sleep(1) + for _ in range(10): + if self.get_position_status() and self.start == 0: + break + time.sleep(1) + if self.start == 0: + self.entry_price = None + self.best_price = None + self.is_wick_position = False + logger.success("平仓成功") + return True + logger.warning("平仓后仍有持仓") + return False + + def execute_trade(self, signal, size=None, is_wick=False): + """ + 执行交易。signal = (type, trigger_price) 或 (type, trigger_price, size, is_wick)。 + size 不传或 None 时使用 default_order_size;开仓成功后设置 self.is_wick_position = is_wick。 + """ + signal_type = signal[0] + trigger_price = signal[1] + if len(signal) >= 4: + size = signal[2] + is_wick = signal[3] + else: + size = size if size is not None else self.default_order_size if signal_type == 'long': # 开多前先确认无持仓 @@ -404,12 +557,13 @@ class BitmartFuturesTransaction: logger.warning(f"开多前发现已有持仓 (方向: {self.start}),放弃开仓避免双向持仓") return False - logger.info(f"确认无持仓,执行开多") + logger.info(f"确认无持仓,执行开多 (size={size}, is_wick={is_wick})") self.开单(marketPriceLongOrder=1, size=size) - time.sleep(3) # 等待订单执行 - - # 验证开仓是否成功 + time.sleep(3) if self.verify_position_direction(1): + self.is_wick_position = is_wick + self.entry_price = self.open_avg_price + self.best_price = self.open_avg_price logger.success("开多成功") return True else: @@ -426,12 +580,13 @@ class BitmartFuturesTransaction: logger.warning(f"开空前发现已有持仓 (方向: {self.start}),放弃开仓避免双向持仓") return False - logger.info(f"确认无持仓,执行开空") + logger.info(f"确认无持仓,执行开空 (size={size}, is_wick={is_wick})") self.开单(marketPriceLongOrder=-1, size=size) - time.sleep(3) # 等待订单执行 - - # 验证开仓是否成功 + time.sleep(3) if self.verify_position_direction(-1): + self.is_wick_position = is_wick + self.entry_price = self.open_avg_price + self.best_price = self.open_avg_price logger.success("开空成功") return True else: @@ -456,6 +611,9 @@ class BitmartFuturesTransaction: time.sleep(3) if self.verify_position_direction(1): + self.is_wick_position = False + self.entry_price = self.open_avg_price + self.best_price = self.open_avg_price logger.success("反手做多成功") time.sleep(20) return True @@ -480,6 +638,9 @@ class BitmartFuturesTransaction: time.sleep(3) if self.verify_position_direction(-1): + self.is_wick_position = False + self.entry_price = self.open_avg_price + self.best_price = self.open_avg_price logger.success("反手做空成功") time.sleep(20) return True @@ -490,11 +651,10 @@ class BitmartFuturesTransaction: return False def action(self): - """主循环""" + """主循环:优先级 1 止损 -> 2 止盈 -> 3 反手 -> 4 新开仓""" - logger.info("开始运行三分之一策略交易...") + logger.info("开始运行:三分之一 + 五分钟 + 反手(影线 ATR 自适应 · 趋势/震荡双模式 · 高胜率版)") - # 启动时设置全仓高杠杆 if not self.set_leverage(): logger.error("杠杆设置失败,程序继续运行但可能下单失败") return @@ -504,7 +664,6 @@ class BitmartFuturesTransaction: while True: if page_start: - # 打开浏览器 for i in range(5): if self.openBrowser(): logger.info("浏览器打开成功") @@ -512,59 +671,92 @@ class BitmartFuturesTransaction: else: self.ding("打开浏览器失败!", error=True) return - - # 进入交易页面 self.page.get("https://derivatives.bitmart.com/zh-CN/futures/ETHUSDT") self.click_safe('x://button[normalize-space(text()) ="市价"]') - - self.page.ele('x://*[@id="size_0"]').input(vals=25, clear=True) - + self.page.ele('x://*[@id="size_0"]').input(vals=self.default_order_size, clear=True) page_start = False try: - # 1. 获取K线数据(当前K线和上一根K线) prev_kline, current_kline = self.get_klines() if not prev_kline or not current_kline: logger.warning("获取K线失败,等待重试...") time.sleep(5) continue - # 记录进入新的K线 current_kline_time = current_kline['id'] if self.last_kline_time != current_kline_time: self.last_kline_time = current_kline_time + if self.mode_lock_remaining > 0: + self.mode_lock_remaining -= 1 logger.info(f"进入新K线: {current_kline_time}") - # 2. 获取当前价格 current_price = self.get_current_price() if not current_price: logger.warning("获取价格失败,等待重试...") time.sleep(2) continue - # 3. 每次循环都通过SDK获取真实持仓状态(避免状态不同步导致双向持仓) if not self.get_position_status(): logger.warning("获取持仓状态失败,等待重试...") time.sleep(2) continue - logger.debug(f"当前持仓状态: {self.start} (0=无, 1=多, -1=空)") + # 批量 K 线用于 ATR 与模式判定 + batch_count = ATR_LENGTH + MODE_LOOKBACK + 2 + klines_batch = self.get_klines_batch(batch_count) + atr = self.compute_atr(klines_batch, ATR_LENGTH) if len(klines_batch) >= ATR_LENGTH + 1 else None + if len(klines_batch) >= MODE_LOOKBACK: + self.detect_mode(klines_batch) - # 4. 检查信号 - signal = self.check_signal(current_price, prev_kline, current_kline) + third = self.compute_third_prices(prev_kline, current_kline) - # 5. 有信号则执行交易 - if signal: - trade_success = self.execute_trade(signal) - if trade_success: - logger.success(f"交易执行完成: {signal[0]}, 当前持仓状态: {self.start}") + # 持仓时更新 best_price(多=最高价,空=最低价) + if self.start == 1 and self.best_price is not None: + self.best_price = max(self.best_price, current_price) + elif self.start == -1 and self.best_price is not None: + self.best_price = min(self.best_price, current_price) + + logger.debug(f"持仓: {self.start}, 模式: {self.mode}, ATR: {f'{atr:.4f}' if atr else 'N/A'}") + + acted = False + + # 1. 止损(仅影线单) + if self.start != 0: + sl_action = self.check_sl(current_price, prev_kline, atr) + if sl_action == 'close': + self.execute_close() + acted = True page_start = True - else: - logger.warning(f"交易执行失败或被阻止: {signal[0]}") - # 短暂等待后继续循环(同一根K线遇到信号就操作) + # 2. 止盈 + if not acted and self.start != 0: + tp_action = self.check_tp(current_price, atr) + if tp_action == 'close': + self.execute_close() + acted = True + page_start = True + + # 3. 反手(仅趋势模式) + if not acted and self.start != 0: + rev_signal = self.check_reversal(current_price, prev_kline, atr, third) + if rev_signal: + if self.execute_trade(rev_signal): + acted = True + page_start = True + else: + logger.warning(f"反手执行失败: {rev_signal[0]}") + + # 4. 新开仓(趋势=实体突破,震荡=影线回撤) + if not acted and self.start == 0: + open_signal = self.check_open(current_price, prev_kline, current_kline, atr, third) + if open_signal: + if self.execute_trade(open_signal): + acted = True + page_start = True + else: + logger.warning(f"开仓执行失败: {open_signal[0]}") + time.sleep(0.1) - if page_start: self.page.close() time.sleep(5)