This commit is contained in:
ddrwode
2026-02-21 15:38:23 +08:00
parent 2d9914ec19
commit f0fe26acbf
5 changed files with 126 additions and 40 deletions

View File

@@ -275,14 +275,44 @@ class BitmartFuturesTransaction:
return False
def 平仓(self):
"""平仓操作"""
"""平仓操作(优先显式平仓按钮,失败时回退到反向市价)"""
self.click_safe('x://span[normalize-space(text()) ="市价"]')
# 优先点击页面上的平仓按钮,避免误开反向仓
close_selectors = [
'x://span[contains(normalize-space(text()),"一键平仓")]',
'x://button[contains(normalize-space(text()),"一键平仓")]',
'x://span[contains(normalize-space(text()),"全部平仓")]',
'x://button[contains(normalize-space(text()),"全部平仓")]',
'x://span[contains(normalize-space(text()),"平多")]',
'x://button[contains(normalize-space(text()),"平多")]',
'x://span[contains(normalize-space(text()),"平空")]',
'x://button[contains(normalize-space(text()),"平空")]',
]
for xpath in close_selectors:
if self.click_safe(xpath):
logger.info(f"平仓点击成功: {xpath}")
return True
# 回退:若没有显式平仓按钮,使用反向市价尝试平仓
if self.start == 1 and self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]'):
logger.info("未找到显式平仓按钮,使用反向市价卖出平多")
return True
if self.start == -1 and self.click_safe('x://span[normalize-space(text()) ="买入/做多"]'):
logger.info("未找到显式平仓按钮,使用反向市价买入平空")
return True
logger.warning("平仓操作未触发任何按钮")
return False
def 开单(self, marketPriceLongOrder=0, limitPriceShortOrder=0, size=None, price=None):
"""
marketPriceLongOrder 市价做多或者做空1是做多-1是做空
limitPriceShortOrder 限价做多或者做空
"""
if size is not None:
self.input_size(size)
time.sleep(0.2)
if marketPriceLongOrder == -1:
# self.click_safe('x://button[normalize-space(text()) ="市价"]')
# self.page.ele('x://*[@id="size_0"]').input(vals=size, clear=True)
@@ -456,7 +486,7 @@ class BitmartFuturesTransaction:
return False
return True
def can_reverse(self, current_price, trigger_price):
def can_reverse(self, current_price, trigger_price=None):
"""反手前过滤:冷却时间 + 最小价差"""
now = time.time()
if self.last_reverse_time and now - self.last_reverse_time < self.reverse_cooldown_seconds:
@@ -464,9 +494,12 @@ class BitmartFuturesTransaction:
logger.info(f"反手冷却中,剩余 {remain:.0f}")
return False
if trigger_price and trigger_price > 0:
if trigger_price is not None and trigger_price > 0:
move_pct = abs(current_price - trigger_price) / trigger_price * 100
if move_pct < self.reverse_min_move_pct:
# 触发价与现价相同(或近似相同)时,不做最小价差过滤,避免策略被永久拦截
if abs(current_price - trigger_price) < 1e-9:
logger.debug("反手价差过滤跳过:触发价与现价一致")
elif move_pct < self.reverse_min_move_pct:
logger.info(f"反手价差不足: {move_pct:.4f}% < {self.reverse_min_move_pct}%")
return False
@@ -514,10 +547,11 @@ class BitmartFuturesTransaction:
"""执行交易。size 不传或为 None 时使用 default_order_size。"""
signal_type, trigger_price = signal
size = self.default_order_size if size is None else size
trigger_price_text = f"{trigger_price:.2f}" if isinstance(trigger_price, (int, float)) else "N/A"
if signal_type == 'long':
# 开多前先确认无持仓
logger.info(f"准备开多,触发价: {trigger_price:.2f}")
logger.info(f"准备开多,触发价: {trigger_price_text}")
if not self.get_position_status():
logger.error("开仓前查询持仓状态失败,放弃开仓")
return False
@@ -543,7 +577,7 @@ class BitmartFuturesTransaction:
elif signal_type == 'short':
# 开空前先确认无持仓
logger.info(f"准备开空,触发价: {trigger_price:.2f}")
logger.info(f"准备开空,触发价: {trigger_price_text}")
if not self.get_position_status():
logger.error("开仓前查询持仓状态失败,放弃开仓")
return False
@@ -569,8 +603,10 @@ class BitmartFuturesTransaction:
elif signal_type == 'reverse_long':
# 平空 + 开多(反手做多):先平仓,确认无仓后再开多,避免双向持仓
logger.info(f"执行反手做多,触发价: {trigger_price:.2f}")
self.平仓()
logger.info(f"执行反手做多,触发价: {trigger_price_text}")
if not self.平仓():
logger.warning("反手做多:平仓按钮未触发,放弃本次反手")
return False
time.sleep(1) # 给交易所处理平仓的时间
# 轮询确认已无持仓再开多(最多等约 10 秒)
for _ in range(10):
@@ -597,8 +633,10 @@ class BitmartFuturesTransaction:
elif signal_type == 'reverse_short':
# 平多 + 开空(反手做空):先平仓,确认无仓后再开空
logger.info(f"执行反手做空,触发价: {trigger_price:.2f}")
self.平仓()
logger.info(f"执行反手做空,触发价: {trigger_price_text}")
if not self.平仓():
logger.warning("反手做空:平仓按钮未触发,放弃本次反手")
return False
time.sleep(1)
for _ in range(10):
if self.get_position_status() and self.start == 0:
@@ -717,12 +755,14 @@ class BitmartFuturesTransaction:
if self.start == 0:
signal = ('long', current_price)
elif self.start == -1:
signal = ('reverse_long', current_price)
reverse_trigger = self.open_avg_price if self.open_avg_price else None
signal = ('reverse_long', reverse_trigger)
elif raw == 2:
if self.start == 0:
signal = ('short', current_price)
elif self.start == 1:
signal = ('reverse_short', current_price)
reverse_trigger = self.open_avg_price if self.open_avg_price else None
signal = ('reverse_short', reverse_trigger)
# 5. 反手过滤:冷却时间 + 最小价差
if signal and signal[0].startswith('reverse_'):