加入一个回测,
This commit is contained in:
443158
bb_sweep_results.csv
443158
bb_sweep_results.csv
File diff suppressed because it is too large
Load Diff
@@ -48,9 +48,10 @@ class BBDelayReversalConfig:
|
||||
|
||||
# 递增加仓 (D方案)
|
||||
PYRAMID_STEP = 0.01 # 每次加仓增加1%权益比例 (1%→2%→3%→4%)
|
||||
PYRAMID_MAX = 3 # 最多加仓3次 (首次开仓不算)
|
||||
PYRAMID_MAX = 1 # 最多加仓1次 (首次开仓不算)
|
||||
|
||||
# 风控
|
||||
STOP_LOSS_PCT = 0.50 # 亏损百分之50就平仓
|
||||
MAX_DAILY_LOSS = 50.0 # 日最大亏损(U),达到后停止交易
|
||||
COOLDOWN_SECONDS = 30 # 两次交易之间最小间隔(秒)
|
||||
|
||||
@@ -98,6 +99,12 @@ class BBDelayReversalTrader:
|
||||
self.position = 0
|
||||
self.open_avg_price = None
|
||||
self.current_amount = None
|
||||
self.unrealized_value = 0.0
|
||||
|
||||
# 半仓折返逻辑状态
|
||||
self.has_half_closed = False # 是否已经触发过“平一半”逻辑
|
||||
self.highest_since_open = None # 持有多仓以来的最高价
|
||||
self.lowest_since_open = None # 持有空仓以来的最低价
|
||||
|
||||
# 加仓状态
|
||||
self.pyramid_count = 0 # 当前已加仓次数 (0=仅首次开仓)
|
||||
@@ -195,14 +202,18 @@ class BBDelayReversalTrader:
|
||||
self.position = 0
|
||||
self.open_avg_price = None
|
||||
self.current_amount = None
|
||||
self.unrealized_value = 0.0
|
||||
self.has_half_closed = False
|
||||
self.highest_since_open = None
|
||||
self.lowest_since_open = None
|
||||
return True
|
||||
pos = positions[0]
|
||||
self.position = 1 if pos["position_type"] == 1 else -1
|
||||
self.open_avg_price = float(pos["open_avg_price"])
|
||||
self.current_amount = float(pos["current_amount"])
|
||||
unrealized = float(pos.get("unrealized_value", 0))
|
||||
self.unrealized_value = float(pos.get("unrealized_value", 0))
|
||||
logger.debug(f"持仓: dir={self.position} price={self.open_avg_price} "
|
||||
f"amt={self.current_amount} upnl={unrealized:.2f}")
|
||||
f"amt={self.current_amount} upnl={self.unrealized_value:.2f}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"查询持仓异常: {e}")
|
||||
@@ -258,8 +269,37 @@ class BBDelayReversalTrader:
|
||||
def browser_close_position(self) -> bool:
|
||||
"""浏览器点击市价全平"""
|
||||
logger.info("浏览器操作: 市价平仓")
|
||||
self.click_safe('x://button[normalize-space(text()) ="平仓"]')
|
||||
time.sleep(0.5)
|
||||
self.click_safe('x://*[@id="futureTradeForm"]/div[5]/div[3]/div[3]/span[5]') # 100%
|
||||
time.sleep(0.5)
|
||||
return self.click_safe('x://span[normalize-space(text()) ="市价"]')
|
||||
|
||||
def browser_half_close_long(self) -> bool:
|
||||
"""平一半多仓"""
|
||||
logger.info("浏览器操作: 平一半多仓")
|
||||
self.click_safe('x://button[normalize-space(text()) ="平仓"]')
|
||||
time.sleep(0.5)
|
||||
self.click_safe('x://*[@id="futureTradeForm"]/div[5]/div[3]/div[3]/span[3]') # 50%
|
||||
time.sleep(0.5)
|
||||
return self.click_safe('x://span[normalize-space(text()) ="卖出/平多"]')
|
||||
|
||||
def browser_half_close_short(self) -> bool:
|
||||
"""平一半空仓"""
|
||||
logger.info("浏览器操作: 平一半空仓")
|
||||
self.click_safe('x://button[normalize-space(text()) ="平仓"]')
|
||||
time.sleep(0.5)
|
||||
self.click_safe('x://*[@id="futureTradeForm"]/div[5]/div[3]/div[3]/span[3]') # 50%
|
||||
time.sleep(0.5)
|
||||
return self.click_safe('x://span[normalize-space(text()) ="买入/平空"]')
|
||||
|
||||
def reset_position_state(self):
|
||||
"""重置所有关于持仓的变量"""
|
||||
self.pyramid_count = 0
|
||||
self.has_half_closed = False
|
||||
self.highest_since_open = None
|
||||
self.lowest_since_open = None
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 仓位操作
|
||||
# ------------------------------------------------------------------
|
||||
@@ -469,7 +509,127 @@ class BBDelayReversalTrader:
|
||||
time.sleep(self.cfg.POLL_INTERVAL)
|
||||
continue
|
||||
|
||||
# 5. 信号判断
|
||||
# 4.5 止损检查
|
||||
if self.position != 0 and self.current_amount and self.open_avg_price:
|
||||
position_margin = (self.open_avg_price * self.current_amount) / self.cfg.LEVERAGE
|
||||
if position_margin > 0:
|
||||
roe = self.unrealized_value / position_margin
|
||||
if roe <= -self.cfg.STOP_LOSS_PCT:
|
||||
logger.warning(f"触发止损!当前亏损比例: {roe:.2%} (<= -{self.cfg.STOP_LOSS_PCT:.0%}),开始平仓")
|
||||
action = "止损平仓"
|
||||
reason = f"亏损比例 {roe:.2%} 达到 {self.cfg.STOP_LOSS_PCT:.0%} 止损线"
|
||||
self.browser_close_position()
|
||||
time.sleep(1)
|
||||
for _ in range(10):
|
||||
if self.get_position_status() and self.position == 0:
|
||||
break
|
||||
time.sleep(1)
|
||||
if self.position == 0:
|
||||
self.reset_position_state()
|
||||
self.write_trade_log(action, current_price, bb_upper, bb_mid, bb_lower, reason)
|
||||
logger.success("止损平仓成功")
|
||||
page_start = True
|
||||
try:
|
||||
self.page.close()
|
||||
except Exception:
|
||||
pass
|
||||
self.page = None
|
||||
time.sleep(5)
|
||||
continue
|
||||
else:
|
||||
logger.error(f"止损平仓失败,当前仍有持仓({self.position})")
|
||||
|
||||
# 5. 极值更新与半仓/全平反手逻辑 (需求: 如果当前做多,涨破中轨后回落碰中轨平一半;再跌到开仓价平全仓反手)
|
||||
if self.position == 1 and self.open_avg_price:
|
||||
if self.highest_since_open is None or current_price > self.highest_since_open:
|
||||
self.highest_since_open = current_price
|
||||
|
||||
# 规则 A1: 涨过中轨
|
||||
if self.highest_since_open >= bb_mid:
|
||||
# 从最高点回落触碰中轨,且未平过一半
|
||||
if not self.has_half_closed and (cur_low <= bb_mid or current_price <= bb_mid):
|
||||
if self.can_trade():
|
||||
logger.info("做多回落触碰中轨,执行平一半多仓")
|
||||
self.browser_half_close_long()
|
||||
time.sleep(2)
|
||||
self.get_position_status()
|
||||
self.has_half_closed = True
|
||||
self.write_trade_log("平半仓(多)", current_price, bb_upper, bb_mid, bb_lower, "多单涨破中轨后回落触碰中轨")
|
||||
# 切换回开仓面板以防后续逻辑出错
|
||||
self.click_safe('x://button[normalize-space(text()) ="开仓"]')
|
||||
time.sleep(0.5)
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
|
||||
# 规则 A2: 平过一半后,跌回开仓价 -> 全平反手开空
|
||||
if self.has_half_closed and (cur_low <= self.open_avg_price or current_price <= self.open_avg_price):
|
||||
if self.can_trade():
|
||||
logger.info("平半仓后跌回多单开仓价,全平反手开空")
|
||||
self.browser_close_position()
|
||||
time.sleep(1)
|
||||
for _ in range(10):
|
||||
if self.get_position_status() and self.position == 0:
|
||||
break
|
||||
time.sleep(1)
|
||||
self.reset_position_state()
|
||||
self.click_safe('x://button[normalize-space(text()) ="开仓"]')
|
||||
time.sleep(0.5)
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]')
|
||||
time.sleep(3)
|
||||
self.get_position_status()
|
||||
self.write_trade_log("全平反手(多转空)", current_price, bb_upper, bb_mid, bb_lower, "多单平半后跌回开仓价")
|
||||
page_start = True
|
||||
try: self.page.close()
|
||||
except: pass
|
||||
self.page = None
|
||||
time.sleep(5)
|
||||
continue
|
||||
|
||||
elif self.position == -1 and self.open_avg_price:
|
||||
if self.lowest_since_open is None or current_price < self.lowest_since_open:
|
||||
self.lowest_since_open = current_price
|
||||
|
||||
# 规则 B1: 跌破过中轨
|
||||
if self.lowest_since_open <= bb_mid:
|
||||
# 从最低点回升触碰中轨,且未平过一半
|
||||
if not self.has_half_closed and (cur_high >= bb_mid or current_price >= bb_mid):
|
||||
if self.can_trade():
|
||||
logger.info("做空反弹触碰中轨,执行平一半空仓")
|
||||
self.browser_half_close_short()
|
||||
time.sleep(2)
|
||||
self.get_position_status()
|
||||
self.has_half_closed = True
|
||||
self.write_trade_log("平半仓(空)", current_price, bb_upper, bb_mid, bb_lower, "空单跌破中轨后反弹触碰中轨")
|
||||
self.click_safe('x://button[normalize-space(text()) ="开仓"]')
|
||||
time.sleep(0.5)
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
|
||||
# 规则 B2: 平过一半后,涨回开仓价 -> 全平反手开多
|
||||
if self.has_half_closed and (cur_high >= self.open_avg_price or current_price >= self.open_avg_price):
|
||||
if self.can_trade():
|
||||
logger.info("平半仓后涨回空单开仓价,全平反手开多")
|
||||
self.browser_close_position()
|
||||
time.sleep(1)
|
||||
for _ in range(10):
|
||||
if self.get_position_status() and self.position == 0:
|
||||
break
|
||||
time.sleep(1)
|
||||
self.reset_position_state()
|
||||
self.click_safe('x://button[normalize-space(text()) ="开仓"]')
|
||||
time.sleep(0.5)
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
self.click_safe('x://span[normalize-space(text()) ="买入/做多"]')
|
||||
time.sleep(3)
|
||||
self.get_position_status()
|
||||
self.write_trade_log("全平反手(空转多)", current_price, bb_upper, bb_mid, bb_lower, "空单平半后涨回开仓价")
|
||||
page_start = True
|
||||
try: self.page.close()
|
||||
except: pass
|
||||
self.page = None
|
||||
time.sleep(5)
|
||||
continue
|
||||
|
||||
# 6. 基本信号判断 (上轨空/下轨多)
|
||||
kline_id = current_kline["id"]
|
||||
if kline_id == last_kline_id:
|
||||
time.sleep(self.cfg.POLL_INTERVAL)
|
||||
@@ -507,17 +667,22 @@ class BBDelayReversalTrader:
|
||||
logger.warning(f"平仓后仍有持仓({self.position}),放弃开空")
|
||||
time.sleep(self.cfg.POLL_INTERVAL)
|
||||
continue
|
||||
# 翻转时重置加仓计数
|
||||
self.pyramid_count = 0
|
||||
self.reset_position_state()
|
||||
# 平仓后在同一页面直接点卖出/做空
|
||||
logger.info("平仓完成,直接开空")
|
||||
self.click_safe('x://button[normalize-space(text()) ="开仓"]')
|
||||
time.sleep(0.5)
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]')
|
||||
time.sleep(3)
|
||||
if self.verify_position(-1):
|
||||
success = True
|
||||
elif self.position == 0:
|
||||
action = "开空"
|
||||
self.pyramid_count = 0
|
||||
self.reset_position_state()
|
||||
self.click_safe('x://button[normalize-space(text()) ="开仓"]')
|
||||
time.sleep(0.5)
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]')
|
||||
time.sleep(3)
|
||||
if self.verify_position(-1):
|
||||
@@ -529,6 +694,9 @@ class BBDelayReversalTrader:
|
||||
# 重新计算加仓金额并输入
|
||||
add_usdt = self.calc_order_usdt(is_add=True)
|
||||
if add_usdt > 0:
|
||||
self.click_safe('x://button[normalize-space(text()) ="开仓"]')
|
||||
time.sleep(0.5)
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
self.page.ele('x://*[@id="size_0"]').input(vals=add_usdt, clear=True)
|
||||
time.sleep(0.5)
|
||||
self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]')
|
||||
@@ -560,16 +728,21 @@ class BBDelayReversalTrader:
|
||||
logger.warning(f"平仓后仍有持仓({self.position}),放弃开多")
|
||||
time.sleep(self.cfg.POLL_INTERVAL)
|
||||
continue
|
||||
# 翻转时重置加仓计数
|
||||
self.pyramid_count = 0
|
||||
self.reset_position_state()
|
||||
logger.info("平仓完成,直接开多")
|
||||
self.click_safe('x://button[normalize-space(text()) ="开仓"]')
|
||||
time.sleep(0.5)
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
self.click_safe('x://span[normalize-space(text()) ="买入/做多"]')
|
||||
time.sleep(3)
|
||||
if self.verify_position(1):
|
||||
success = True
|
||||
elif self.position == 0:
|
||||
action = "开多"
|
||||
self.pyramid_count = 0
|
||||
self.reset_position_state()
|
||||
self.click_safe('x://button[normalize-space(text()) ="开仓"]')
|
||||
time.sleep(0.5)
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
self.click_safe('x://span[normalize-space(text()) ="买入/做多"]')
|
||||
time.sleep(3)
|
||||
if self.verify_position(1):
|
||||
@@ -581,6 +754,9 @@ class BBDelayReversalTrader:
|
||||
# 重新计算加仓金额并输入
|
||||
add_usdt = self.calc_order_usdt(is_add=True)
|
||||
if add_usdt > 0:
|
||||
self.click_safe('x://button[normalize-space(text()) ="开仓"]')
|
||||
time.sleep(0.5)
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
self.page.ele('x://*[@id="size_0"]').input(vals=add_usdt, clear=True)
|
||||
time.sleep(0.5)
|
||||
self.click_safe('x://span[normalize-space(text()) ="买入/做多"]')
|
||||
Reference in New Issue
Block a user