加入一个回测,
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
基于回测策略 bb_backtest_march_2026.py
|
基于回测策略 bb_backtest_march_2026.py
|
||||||
使用框架的API查询 + 浏览器自动化交易
|
使用框架的API查询 + 浏览器自动化交易
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
import time
|
import time
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
@@ -37,7 +38,8 @@ class BBDelayReversalConfig:
|
|||||||
LEVERAGE = "50"
|
LEVERAGE = "50"
|
||||||
OPEN_TYPE = "isolated" # 逐仓模式
|
OPEN_TYPE = "isolated" # 逐仓模式
|
||||||
MARGIN_PCT = 0.01 # 首次开仓1%
|
MARGIN_PCT = 0.01 # 首次开仓1%
|
||||||
|
STOP_LOSS_RATIO = 0.5 # 浮亏达到总保证金50%止损
|
||||||
|
|
||||||
# 运行参数
|
# 运行参数
|
||||||
POLL_INTERVAL = 5
|
POLL_INTERVAL = 5
|
||||||
KLINE_STEP = 5
|
KLINE_STEP = 5
|
||||||
@@ -84,15 +86,69 @@ class BBDelayReversalTrader:
|
|||||||
# 交易控制
|
# 交易控制
|
||||||
self.last_trade_time = 0.0
|
self.last_trade_time = 0.0
|
||||||
self.last_kline_id = None
|
self.last_kline_id = None
|
||||||
|
self.last_closed_kline_id = None
|
||||||
self.cooldown_seconds = 10 # 交易冷却时间
|
self.cooldown_seconds = 10 # 交易冷却时间
|
||||||
|
|
||||||
# 日志
|
# 日志
|
||||||
self.log_dir = Path(__file__).resolve().parent
|
self.log_dir = Path(__file__).resolve().parent
|
||||||
|
self.state_file = self.log_dir / "bb_delay_reversal_state.json"
|
||||||
logger.add(
|
logger.add(
|
||||||
self.log_dir / "bb_delay_trade_{time:YYYY-MM-DD}.log",
|
self.log_dir / "bb_delay_trade_{time:YYYY-MM-DD}.log",
|
||||||
rotation="1 day", retention="30 days",
|
rotation="1 day", retention="30 days",
|
||||||
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
|
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
|
||||||
)
|
)
|
||||||
|
self.load_state()
|
||||||
|
|
||||||
|
def load_state(self):
|
||||||
|
"""加载本地策略状态,便于重启后延续运行。"""
|
||||||
|
if not self.state_file.exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(self.state_file.read_text(encoding="utf-8"))
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"读取本地状态失败,忽略旧状态: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.position_count = int(data.get("position_count", self.position_count))
|
||||||
|
self.total_margin = float(data.get("total_margin", self.total_margin))
|
||||||
|
self.mid_closed_half = bool(data.get("mid_closed_half", self.mid_closed_half))
|
||||||
|
self.delay_reverse_price = data.get("delay_reverse_price")
|
||||||
|
self.delay_reverse_type = data.get("delay_reverse_type")
|
||||||
|
self.delay_reverse_kline_id = data.get("delay_reverse_kline_id")
|
||||||
|
self.last_kline_id = data.get("last_kline_id")
|
||||||
|
self.last_closed_kline_id = data.get("last_closed_kline_id")
|
||||||
|
|
||||||
|
if self.delay_reverse_price is not None:
|
||||||
|
self.delay_reverse_price = float(self.delay_reverse_price)
|
||||||
|
if self.delay_reverse_kline_id is not None:
|
||||||
|
self.delay_reverse_kline_id = int(self.delay_reverse_kline_id)
|
||||||
|
if self.last_kline_id is not None:
|
||||||
|
self.last_kline_id = int(self.last_kline_id)
|
||||||
|
if self.last_closed_kline_id is not None:
|
||||||
|
self.last_closed_kline_id = int(self.last_closed_kline_id)
|
||||||
|
|
||||||
|
logger.info("已加载本地策略状态")
|
||||||
|
|
||||||
|
def save_state(self):
|
||||||
|
"""保存关键状态,降低重启造成的状态丢失。"""
|
||||||
|
data = {
|
||||||
|
"position_count": self.position_count,
|
||||||
|
"total_margin": self.total_margin,
|
||||||
|
"mid_closed_half": self.mid_closed_half,
|
||||||
|
"delay_reverse_price": self.delay_reverse_price,
|
||||||
|
"delay_reverse_type": self.delay_reverse_type,
|
||||||
|
"delay_reverse_kline_id": self.delay_reverse_kline_id,
|
||||||
|
"last_kline_id": self.last_kline_id,
|
||||||
|
"last_closed_kline_id": self.last_closed_kline_id,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
self.state_file.write_text(
|
||||||
|
json.dumps(data, ensure_ascii=False, indent=2),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"写入本地状态失败: {e}")
|
||||||
|
|
||||||
# ========== API查询方法 ==========
|
# ========== API查询方法 ==========
|
||||||
|
|
||||||
@@ -160,6 +216,18 @@ class BBDelayReversalTrader:
|
|||||||
def get_position_status(self) -> bool:
|
def get_position_status(self) -> bool:
|
||||||
"""查询持仓状态(使用框架方法)"""
|
"""查询持仓状态(使用框架方法)"""
|
||||||
try:
|
try:
|
||||||
|
old_state = (
|
||||||
|
self.position,
|
||||||
|
self.position_count,
|
||||||
|
self.entry_price,
|
||||||
|
self.current_amount,
|
||||||
|
self.total_margin,
|
||||||
|
self.mid_closed_half,
|
||||||
|
self.delay_reverse_price,
|
||||||
|
self.delay_reverse_type,
|
||||||
|
self.delay_reverse_kline_id,
|
||||||
|
)
|
||||||
|
old_position = self.position
|
||||||
response = self.contractAPI.get_position(contract_symbol=self.cfg.CONTRACT_SYMBOL)[0]
|
response = self.contractAPI.get_position(contract_symbol=self.cfg.CONTRACT_SYMBOL)[0]
|
||||||
if response['code'] == 1000:
|
if response['code'] == 1000:
|
||||||
positions = response['data']
|
positions = response['data']
|
||||||
@@ -168,12 +236,55 @@ class BBDelayReversalTrader:
|
|||||||
self.position_count = 0
|
self.position_count = 0
|
||||||
self.entry_price = 0
|
self.entry_price = 0
|
||||||
self.current_amount = 0
|
self.current_amount = 0
|
||||||
|
self.total_margin = 0
|
||||||
self.mid_closed_half = False
|
self.mid_closed_half = False
|
||||||
|
self.delay_reverse_price = None
|
||||||
|
self.delay_reverse_type = None
|
||||||
|
self.delay_reverse_kline_id = None
|
||||||
|
if (
|
||||||
|
self.position,
|
||||||
|
self.position_count,
|
||||||
|
self.entry_price,
|
||||||
|
self.current_amount,
|
||||||
|
self.total_margin,
|
||||||
|
self.mid_closed_half,
|
||||||
|
self.delay_reverse_price,
|
||||||
|
self.delay_reverse_type,
|
||||||
|
self.delay_reverse_kline_id,
|
||||||
|
) != old_state:
|
||||||
|
self.save_state()
|
||||||
return True
|
return True
|
||||||
pos = positions[0]
|
pos = positions[0]
|
||||||
self.position = 1 if pos['position_type'] == 1 else -1
|
self.position = 1 if pos['position_type'] == 1 else -1
|
||||||
self.entry_price = float(pos['open_avg_price'])
|
self.entry_price = float(pos['open_avg_price'])
|
||||||
self.current_amount = float(pos['current_amount'])
|
self.current_amount = float(pos['current_amount'])
|
||||||
|
|
||||||
|
if old_position not in (0, self.position):
|
||||||
|
logger.warning("检测到持仓方向变化,重置本地策略状态")
|
||||||
|
self.position_count = 1
|
||||||
|
self.mid_closed_half = False
|
||||||
|
self.delay_reverse_price = None
|
||||||
|
self.delay_reverse_type = None
|
||||||
|
self.delay_reverse_kline_id = None
|
||||||
|
elif self.position_count == 0:
|
||||||
|
self.position_count = 1
|
||||||
|
|
||||||
|
if self.total_margin <= 0 and self.current_amount > 0:
|
||||||
|
leverage = float(self.cfg.LEVERAGE)
|
||||||
|
self.total_margin = self.current_amount * self.entry_price / leverage
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.position,
|
||||||
|
self.position_count,
|
||||||
|
self.entry_price,
|
||||||
|
self.current_amount,
|
||||||
|
self.total_margin,
|
||||||
|
self.mid_closed_half,
|
||||||
|
self.delay_reverse_price,
|
||||||
|
self.delay_reverse_type,
|
||||||
|
self.delay_reverse_kline_id,
|
||||||
|
) != old_state:
|
||||||
|
self.save_state()
|
||||||
logger.debug(f"持仓: {'多' if self.position > 0 else '空'} | "
|
logger.debug(f"持仓: {'多' if self.position > 0 else '空'} | "
|
||||||
f"价格={self.entry_price:.2f} | 数量={self.current_amount:.4f}")
|
f"价格={self.entry_price:.2f} | 数量={self.current_amount:.4f}")
|
||||||
return True
|
return True
|
||||||
@@ -213,6 +324,17 @@ class BBDelayReversalTrader:
|
|||||||
upper = mid + self.cfg.BB_STD * std
|
upper = mid + self.cfg.BB_STD * std
|
||||||
lower = mid - self.cfg.BB_STD * std
|
lower = mid - self.cfg.BB_STD * std
|
||||||
return mid, upper, lower
|
return mid, upper, lower
|
||||||
|
|
||||||
|
def calc_shifted_bollinger_for_index(self, closed_klines: list, target_index: int):
|
||||||
|
"""计算某根已收盘K线对应的右移一根布林带。"""
|
||||||
|
if target_index < self.cfg.BB_PERIOD:
|
||||||
|
return None
|
||||||
|
|
||||||
|
closes = [
|
||||||
|
k['close']
|
||||||
|
for k in closed_klines[target_index - self.cfg.BB_PERIOD:target_index]
|
||||||
|
]
|
||||||
|
return self.calc_bollinger(closes)
|
||||||
|
|
||||||
# ========== 浏览器自动化 ==========
|
# ========== 浏览器自动化 ==========
|
||||||
|
|
||||||
@@ -346,8 +468,8 @@ class BBDelayReversalTrader:
|
|||||||
logger.error(f"全平仓确认超时,当前仓位={self.position}")
|
logger.error(f"全平仓确认超时,当前仓位={self.position}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def open_with_balance_and_confirm(self, direction: str) -> bool:
|
def open_with_balance_and_confirm(self, direction: str, previous_amount: float = 0.0) -> bool:
|
||||||
"""按余额比例开仓并确认方向正确"""
|
"""按余额比例开仓/加仓,并确认方向与数量变化正确。"""
|
||||||
balance = self.get_balance()
|
balance = self.get_balance()
|
||||||
if not balance:
|
if not balance:
|
||||||
logger.error("余额获取失败,无法开仓")
|
logger.error("余额获取失败,无法开仓")
|
||||||
@@ -372,8 +494,57 @@ class BBDelayReversalTrader:
|
|||||||
logger.error(f"开仓结果不一致: 期望={expected_pos}, 实际={self.position}")
|
logger.error(f"开仓结果不一致: 期望={expected_pos}, 实际={self.position}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if previous_amount > 0:
|
||||||
|
min_increase = max(previous_amount * 0.05, 1e-8)
|
||||||
|
if self.current_amount <= previous_amount + min_increase:
|
||||||
|
logger.error(
|
||||||
|
f"开仓后数量未明显增加: 之前={previous_amount:.4f}, 当前={self.current_amount:.4f}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
self.total_margin += usdt_amount
|
||||||
|
else:
|
||||||
|
self.total_margin = usdt_amount
|
||||||
|
|
||||||
|
self.save_state()
|
||||||
logger.success(f"✓ 开{'多' if direction == 'long' else '空'}成功")
|
logger.success(f"✓ 开{'多' if direction == 'long' else '空'}成功")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def close_partial_and_confirm(
|
||||||
|
self,
|
||||||
|
ratio: float,
|
||||||
|
previous_amount: float,
|
||||||
|
timeout_seconds: int = 15,
|
||||||
|
poll_seconds: float = 1.0,
|
||||||
|
) -> bool:
|
||||||
|
"""执行部分平仓,并确认仓位数量明显下降。"""
|
||||||
|
if self.position == 0 or previous_amount <= 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
expected_pos = self.position
|
||||||
|
if not self.browser_close_position(ratio):
|
||||||
|
logger.error("部分平仓指令发送失败")
|
||||||
|
return False
|
||||||
|
|
||||||
|
deadline = time.time() + timeout_seconds
|
||||||
|
while time.time() < deadline:
|
||||||
|
time.sleep(poll_seconds)
|
||||||
|
if not self.get_position_status():
|
||||||
|
continue
|
||||||
|
if self.position != expected_pos:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.current_amount <= previous_amount * 0.75:
|
||||||
|
self.total_margin *= (1 - ratio)
|
||||||
|
self.save_state()
|
||||||
|
logger.success(
|
||||||
|
f"✓ 平仓{int(ratio*100)}%确认完成 | 之前={previous_amount:.4f}, 当前={self.current_amount:.4f}"
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
logger.error(
|
||||||
|
f"平仓{int(ratio*100)}%确认超时,数量未明显下降 | 之前={previous_amount:.4f}, 当前={self.current_amount:.4f}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
# ========== 延迟反转逻辑 ==========
|
# ========== 延迟反转逻辑 ==========
|
||||||
|
|
||||||
@@ -382,13 +553,34 @@ class BBDelayReversalTrader:
|
|||||||
self.delay_reverse_type = reverse_type
|
self.delay_reverse_type = reverse_type
|
||||||
self.delay_reverse_price = trigger_price
|
self.delay_reverse_price = trigger_price
|
||||||
self.delay_reverse_kline_id = kline_id
|
self.delay_reverse_kline_id = kline_id
|
||||||
|
self.save_state()
|
||||||
logger.warning(f"⚠️ 延迟反转触发: {reverse_type} @ {trigger_price:.2f} | K线ID: {kline_id}")
|
logger.warning(f"⚠️ 延迟反转触发: {reverse_type} @ {trigger_price:.2f} | K线ID: {kline_id}")
|
||||||
|
|
||||||
def clear_delay_reversal(self):
|
def clear_delay_reversal(self):
|
||||||
"""清除延迟反转状态"""
|
"""清除延迟反转状态"""
|
||||||
|
had_state = (
|
||||||
|
self.delay_reverse_price is not None
|
||||||
|
or self.delay_reverse_type is not None
|
||||||
|
or self.delay_reverse_kline_id is not None
|
||||||
|
)
|
||||||
self.delay_reverse_price = None
|
self.delay_reverse_price = None
|
||||||
self.delay_reverse_type = None
|
self.delay_reverse_type = None
|
||||||
self.delay_reverse_kline_id = None
|
self.delay_reverse_kline_id = None
|
||||||
|
if had_state:
|
||||||
|
self.save_state()
|
||||||
|
|
||||||
|
def check_stop_loss(self, high: float, low: float) -> bool:
|
||||||
|
"""检查是否达到总保证金50%的止损阈值。"""
|
||||||
|
if self.position == 0 or self.current_amount <= 0 or self.total_margin <= 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
stop_price = low if self.position > 0 else high
|
||||||
|
if self.position > 0:
|
||||||
|
unrealized_pnl = self.current_amount * (stop_price - self.entry_price)
|
||||||
|
else:
|
||||||
|
unrealized_pnl = self.current_amount * (self.entry_price - stop_price)
|
||||||
|
|
||||||
|
return unrealized_pnl <= -self.total_margin * self.cfg.STOP_LOSS_RATIO
|
||||||
|
|
||||||
def check_delay_reversal(self, current_kline, prev_kline, prev_upper, prev_lower) -> tuple | None:
|
def check_delay_reversal(self, current_kline, prev_kline, prev_upper, prev_lower) -> tuple | None:
|
||||||
"""
|
"""
|
||||||
@@ -496,9 +688,6 @@ class BBDelayReversalTrader:
|
|||||||
logger.info(f"初始持仓: {self.position}")
|
logger.info(f"初始持仓: {self.position}")
|
||||||
|
|
||||||
page_start = True
|
page_start = True
|
||||||
kline_history = [] # 保存历史K线
|
|
||||||
prev_bb_upper = None # 保存上一根K线的上轨
|
|
||||||
prev_bb_lower = None # 保存上一根K线的下轨
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@@ -540,7 +729,6 @@ class BBDelayReversalTrader:
|
|||||||
time.sleep(self.cfg.POLL_INTERVAL)
|
time.sleep(self.cfg.POLL_INTERVAL)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 使用已收盘K线计算BB
|
|
||||||
closed_klines = klines[:-1]
|
closed_klines = klines[:-1]
|
||||||
current_kline = klines[-1]
|
current_kline = klines[-1]
|
||||||
|
|
||||||
@@ -557,35 +745,128 @@ class BBDelayReversalTrader:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
bb_mid, bb_upper, bb_lower = bb
|
bb_mid, bb_upper, bb_lower = bb
|
||||||
|
signal_kline = closed_klines[-1]
|
||||||
|
signal_bb = self.calc_shifted_bollinger_for_index(
|
||||||
|
closed_klines,
|
||||||
|
len(closed_klines) - 1,
|
||||||
|
)
|
||||||
|
|
||||||
# 当前价优先使用当前5m实时K线close,避免1m收盘价滞后导致观感偏差
|
|
||||||
current_price = float(current_kline['close'])
|
current_price = float(current_kline['close'])
|
||||||
|
|
||||||
cur_high = current_kline['high']
|
cur_high = current_kline['high']
|
||||||
cur_low = current_kline['low']
|
cur_low = current_kline['low']
|
||||||
kline_id = current_kline['id']
|
kline_id = current_kline['id']
|
||||||
|
|
||||||
# 触轨判断
|
|
||||||
touched_upper = cur_high >= bb_upper
|
touched_upper = cur_high >= bb_upper
|
||||||
touched_lower = cur_low <= bb_lower
|
touched_lower = cur_low <= bb_lower
|
||||||
touched_middle = cur_low <= bb_mid <= cur_high
|
touched_middle = cur_low <= bb_mid <= cur_high
|
||||||
|
|
||||||
# 延迟反转状态显示
|
|
||||||
delay_status = ""
|
delay_status = ""
|
||||||
if self.delay_reverse_price is not None:
|
if self.delay_reverse_price is not None:
|
||||||
delay_status = f" | 🔄延迟反转中: {self.delay_reverse_type} @ {self.delay_reverse_price:.2f}"
|
delay_status = f" | 🔄延迟反转中: {self.delay_reverse_type} @ {self.delay_reverse_price:.2f}"
|
||||||
|
|
||||||
|
signal_status = ""
|
||||||
|
if signal_bb is not None:
|
||||||
|
signal_mid, signal_upper, signal_lower = signal_bb
|
||||||
|
signal_status = (
|
||||||
|
f" | 收盘K H/L={signal_kline['high']:.2f}/{signal_kline['low']:.2f}"
|
||||||
|
f" BB={signal_lower:.2f}/{signal_mid:.2f}/{signal_upper:.2f}"
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"价格={current_price:.2f} | "
|
f"价格={current_price:.2f} | "
|
||||||
f"BB: {bb_lower:.2f}/{bb_mid:.2f}/{bb_upper:.2f} | "
|
f"BB: {bb_lower:.2f}/{bb_mid:.2f}/{bb_upper:.2f} | "
|
||||||
f"触上={touched_upper} 触下={touched_lower} 触中={touched_middle} | "
|
f"触上={touched_upper} 触下={touched_lower} 触中={touched_middle} | "
|
||||||
f"仓位={self.position}{delay_status}"
|
f"仓位={self.position}{delay_status}{signal_status}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 同步持仓
|
|
||||||
if not self.get_position_status():
|
if not self.get_position_status():
|
||||||
time.sleep(self.cfg.POLL_INTERVAL)
|
time.sleep(self.cfg.POLL_INTERVAL)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# ===== 收盘K确认逻辑:止损 / 中轨平半 / 回开仓价反手 =====
|
||||||
|
if signal_bb is not None and signal_kline['id'] != self.last_closed_kline_id:
|
||||||
|
signal_mid, _, _ = signal_bb
|
||||||
|
signal_high = signal_kline['high']
|
||||||
|
signal_low = signal_kline['low']
|
||||||
|
signal_ts = datetime.fromtimestamp(signal_kline['id'] / 1000).strftime('%Y-%m-%d %H:%M')
|
||||||
|
closed_processed = True
|
||||||
|
|
||||||
|
if self.check_stop_loss(signal_high, signal_low):
|
||||||
|
logger.warning(f"🛑 收盘K触发止损: {signal_ts}")
|
||||||
|
if self.close_all_and_confirm():
|
||||||
|
self.last_closed_kline_id = signal_kline['id']
|
||||||
|
self.last_kline_id = kline_id
|
||||||
|
self.update_trade_time()
|
||||||
|
self.save_state()
|
||||||
|
time.sleep(self.cfg.POLL_INTERVAL)
|
||||||
|
continue
|
||||||
|
closed_processed = False
|
||||||
|
|
||||||
|
elif self.position != 0 and self.delay_reverse_price is None:
|
||||||
|
had_mid_closed_half = self.mid_closed_half
|
||||||
|
|
||||||
|
if self.position > 0:
|
||||||
|
if had_mid_closed_half and signal_low <= self.entry_price:
|
||||||
|
logger.info(f"💰 收盘K回到开仓价 {self.entry_price:.2f},全平并反手开空")
|
||||||
|
if self.close_all_and_confirm() and self.open_with_balance_and_confirm('short'):
|
||||||
|
self.position_count = 1
|
||||||
|
self.mid_closed_half = False
|
||||||
|
self.last_closed_kline_id = signal_kline['id']
|
||||||
|
self.last_kline_id = kline_id
|
||||||
|
self.update_trade_time()
|
||||||
|
self.save_state()
|
||||||
|
logger.success("✓ 收盘K反手完成")
|
||||||
|
time.sleep(self.cfg.POLL_INTERVAL)
|
||||||
|
continue
|
||||||
|
closed_processed = False
|
||||||
|
elif not had_mid_closed_half and signal_low <= signal_mid <= signal_high:
|
||||||
|
logger.info(f"📊 收盘K触中轨 {signal_mid:.2f},平50%")
|
||||||
|
previous_amount = self.current_amount
|
||||||
|
if self.close_partial_and_confirm(0.5, previous_amount):
|
||||||
|
self.mid_closed_half = True
|
||||||
|
self.last_closed_kline_id = signal_kline['id']
|
||||||
|
self.last_kline_id = kline_id
|
||||||
|
self.update_trade_time()
|
||||||
|
self.save_state()
|
||||||
|
logger.success("✓ 收盘K平半完成")
|
||||||
|
time.sleep(self.cfg.POLL_INTERVAL)
|
||||||
|
continue
|
||||||
|
closed_processed = False
|
||||||
|
|
||||||
|
else:
|
||||||
|
if had_mid_closed_half and signal_high >= self.entry_price:
|
||||||
|
logger.info(f"💰 收盘K回到开仓价 {self.entry_price:.2f},全平并反手开多")
|
||||||
|
if self.close_all_and_confirm() and self.open_with_balance_and_confirm('long'):
|
||||||
|
self.position_count = 1
|
||||||
|
self.mid_closed_half = False
|
||||||
|
self.last_closed_kline_id = signal_kline['id']
|
||||||
|
self.last_kline_id = kline_id
|
||||||
|
self.update_trade_time()
|
||||||
|
self.save_state()
|
||||||
|
logger.success("✓ 收盘K反手完成")
|
||||||
|
time.sleep(self.cfg.POLL_INTERVAL)
|
||||||
|
continue
|
||||||
|
closed_processed = False
|
||||||
|
elif not had_mid_closed_half and signal_low <= signal_mid <= signal_high:
|
||||||
|
logger.info(f"📊 收盘K触中轨 {signal_mid:.2f},平50%")
|
||||||
|
previous_amount = self.current_amount
|
||||||
|
if self.close_partial_and_confirm(0.5, previous_amount):
|
||||||
|
self.mid_closed_half = True
|
||||||
|
self.last_closed_kline_id = signal_kline['id']
|
||||||
|
self.last_kline_id = kline_id
|
||||||
|
self.update_trade_time()
|
||||||
|
self.save_state()
|
||||||
|
logger.success("✓ 收盘K平半完成")
|
||||||
|
time.sleep(self.cfg.POLL_INTERVAL)
|
||||||
|
continue
|
||||||
|
closed_processed = False
|
||||||
|
|
||||||
|
if not closed_processed:
|
||||||
|
time.sleep(self.cfg.POLL_INTERVAL)
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.last_closed_kline_id = signal_kline['id']
|
||||||
|
self.save_state()
|
||||||
|
|
||||||
# 避免同一K线重复触发
|
# 避免同一K线重复触发
|
||||||
if kline_id == self.last_kline_id:
|
if kline_id == self.last_kline_id:
|
||||||
@@ -594,9 +875,11 @@ class BBDelayReversalTrader:
|
|||||||
|
|
||||||
# ===== 延迟反转确认(优先级最高)=====
|
# ===== 延迟反转确认(优先级最高)=====
|
||||||
if self.delay_reverse_price is not None:
|
if self.delay_reverse_price is not None:
|
||||||
prev_kline = kline_history[-1] if len(kline_history) > 0 else None
|
prev_kline = signal_kline if signal_bb is not None else None
|
||||||
|
prev_upper = signal_bb[1] if signal_bb is not None else None
|
||||||
|
prev_lower = signal_bb[2] if signal_bb is not None else None
|
||||||
reversal = self.check_delay_reversal(
|
reversal = self.check_delay_reversal(
|
||||||
current_kline, prev_kline, prev_bb_upper, prev_bb_lower
|
current_kline, prev_kline, prev_upper, prev_lower
|
||||||
)
|
)
|
||||||
|
|
||||||
if reversal:
|
if reversal:
|
||||||
@@ -612,102 +895,42 @@ class BBDelayReversalTrader:
|
|||||||
self.clear_delay_reversal()
|
self.clear_delay_reversal()
|
||||||
self.last_kline_id = kline_id
|
self.last_kline_id = kline_id
|
||||||
self.update_trade_time()
|
self.update_trade_time()
|
||||||
|
self.save_state()
|
||||||
logger.success("✓ 延迟反转完成!")
|
logger.success("✓ 延迟反转完成!")
|
||||||
else:
|
else:
|
||||||
logger.error("延迟反转取消:全平仓未确认")
|
logger.error("延迟反转取消:全平仓未确认")
|
||||||
|
time.sleep(self.cfg.POLL_INTERVAL)
|
||||||
# 更新历史
|
|
||||||
kline_history.append(current_kline)
|
|
||||||
prev_bb_upper = bb_upper
|
|
||||||
prev_bb_lower = bb_lower
|
|
||||||
continue
|
|
||||||
|
|
||||||
# ===== 中轨平仓(延迟反转期间不执行)=====
|
|
||||||
if self.position != 0 and touched_middle and self.delay_reverse_price is None:
|
|
||||||
if not self.mid_closed_half:
|
|
||||||
logger.info("📊 触中轨,平50%")
|
|
||||||
if not self.can_trade():
|
|
||||||
continue
|
|
||||||
self.browser_close_position(0.5)
|
|
||||||
time.sleep(3)
|
|
||||||
self.get_position_status()
|
|
||||||
self.mid_closed_half = True
|
|
||||||
self.last_kline_id = kline_id
|
|
||||||
self.update_trade_time()
|
|
||||||
logger.success(f"✓ 平半成功")
|
|
||||||
# 更新历史
|
|
||||||
kline_history.append(current_kline)
|
|
||||||
prev_bb_upper = bb_upper
|
|
||||||
prev_bb_lower = bb_lower
|
|
||||||
continue
|
|
||||||
|
|
||||||
# ===== 回到开仓价全平+反手(仅在已平半的情况下)=====
|
|
||||||
if self.position != 0 and self.mid_closed_half and self.delay_reverse_price is None:
|
|
||||||
should_close = False
|
|
||||||
if self.position > 0 and cur_low <= self.entry_price:
|
|
||||||
should_close = True
|
|
||||||
elif self.position < 0 and cur_high >= self.entry_price:
|
|
||||||
should_close = True
|
|
||||||
|
|
||||||
if should_close:
|
|
||||||
logger.info(f"💰 回到开仓价 {self.entry_price:.2f},全平+反手")
|
|
||||||
if not self.can_trade():
|
|
||||||
continue
|
|
||||||
|
|
||||||
old_direction = 'long' if self.position > 0 else 'short'
|
|
||||||
new_direction = 'short' if old_direction == 'long' else 'long'
|
|
||||||
|
|
||||||
if self.close_all_and_confirm():
|
|
||||||
if self.open_with_balance_and_confirm(new_direction):
|
|
||||||
self.position_count = 1
|
|
||||||
self.mid_closed_half = False
|
|
||||||
self.last_kline_id = kline_id
|
|
||||||
self.update_trade_time()
|
|
||||||
logger.success("✓ 反手完成!")
|
|
||||||
else:
|
|
||||||
logger.error("反手取消:全平仓未确认")
|
|
||||||
|
|
||||||
# 更新历史
|
|
||||||
kline_history.append(current_kline)
|
|
||||||
prev_bb_upper = bb_upper
|
|
||||||
prev_bb_lower = bb_lower
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# ===== 开仓(空仓时)=====
|
# ===== 开仓(空仓时)=====
|
||||||
if self.position == 0:
|
if self.position == 0:
|
||||||
self.clear_delay_reversal()
|
if self.delay_reverse_price is not None:
|
||||||
|
self.clear_delay_reversal()
|
||||||
balance = self.get_balance()
|
|
||||||
if not balance:
|
|
||||||
time.sleep(self.cfg.POLL_INTERVAL)
|
|
||||||
continue
|
|
||||||
|
|
||||||
usdt_amount = round(balance * self.cfg.MARGIN_PCT, 2)
|
|
||||||
|
|
||||||
if touched_upper:
|
if touched_upper:
|
||||||
logger.info(f"🔴 空仓触上轨,开空 {usdt_amount}U")
|
logger.info("🔴 空仓触上轨,开空")
|
||||||
if not self.can_trade():
|
if not self.can_trade():
|
||||||
|
time.sleep(self.cfg.POLL_INTERVAL)
|
||||||
continue
|
continue
|
||||||
self.browser_open_position('short', usdt_amount)
|
if self.open_with_balance_and_confirm('short'):
|
||||||
time.sleep(3)
|
|
||||||
self.get_position_status()
|
|
||||||
if self.position == -1:
|
|
||||||
self.position_count = 1
|
self.position_count = 1
|
||||||
|
self.mid_closed_half = False
|
||||||
self.last_kline_id = kline_id
|
self.last_kline_id = kline_id
|
||||||
self.update_trade_time()
|
self.update_trade_time()
|
||||||
|
self.save_state()
|
||||||
logger.success(f"✓ 开空成功")
|
logger.success(f"✓ 开空成功")
|
||||||
|
|
||||||
elif touched_lower:
|
elif touched_lower:
|
||||||
logger.info(f"🟢 空仓触下轨,开多 {usdt_amount}U")
|
logger.info("🟢 空仓触下轨,开多")
|
||||||
if not self.can_trade():
|
if not self.can_trade():
|
||||||
|
time.sleep(self.cfg.POLL_INTERVAL)
|
||||||
continue
|
continue
|
||||||
self.browser_open_position('long', usdt_amount)
|
if self.open_with_balance_and_confirm('long'):
|
||||||
time.sleep(3)
|
|
||||||
self.get_position_status()
|
|
||||||
if self.position == 1:
|
|
||||||
self.position_count = 1
|
self.position_count = 1
|
||||||
|
self.mid_closed_half = False
|
||||||
self.last_kline_id = kline_id
|
self.last_kline_id = kline_id
|
||||||
self.update_trade_time()
|
self.update_trade_time()
|
||||||
|
self.save_state()
|
||||||
logger.success(f"✓ 开多成功")
|
logger.success(f"✓ 开多成功")
|
||||||
|
|
||||||
# ===== 延迟反转触发(有持仓且未在延迟反转中)=====
|
# ===== 延迟反转触发(有持仓且未在延迟反转中)=====
|
||||||
@@ -715,51 +938,41 @@ class BBDelayReversalTrader:
|
|||||||
logger.warning("⚠️ 多仓触上轨,标记延迟反转")
|
logger.warning("⚠️ 多仓触上轨,标记延迟反转")
|
||||||
self.mark_delay_reversal('long_to_short', bb_upper, kline_id)
|
self.mark_delay_reversal('long_to_short', bb_upper, kline_id)
|
||||||
self.last_kline_id = kline_id
|
self.last_kline_id = kline_id
|
||||||
|
self.save_state()
|
||||||
|
|
||||||
elif self.position < 0 and touched_lower and self.delay_reverse_price is None:
|
elif self.position < 0 and touched_lower and self.delay_reverse_price is None:
|
||||||
logger.warning("⚠️ 空仓触下轨,标记延迟反转")
|
logger.warning("⚠️ 空仓触下轨,标记延迟反转")
|
||||||
self.mark_delay_reversal('short_to_long', bb_lower, kline_id)
|
self.mark_delay_reversal('short_to_long', bb_lower, kline_id)
|
||||||
self.last_kline_id = kline_id
|
self.last_kline_id = kline_id
|
||||||
|
self.save_state()
|
||||||
|
|
||||||
# ===== 加仓(仅首次开仓后,且未在延迟反转中)=====
|
# ===== 加仓(仅首次开仓后,且未在延迟反转中)=====
|
||||||
elif self.position_count == 1 and self.delay_reverse_price is None:
|
elif self.position_count == 1 and self.delay_reverse_price is None:
|
||||||
balance = self.get_balance()
|
previous_amount = self.current_amount
|
||||||
if balance:
|
|
||||||
usdt_amount = round(balance * self.cfg.MARGIN_PCT, 2)
|
if self.position > 0 and touched_lower:
|
||||||
|
logger.info("➕ 多仓触下轨,加仓")
|
||||||
if self.position > 0 and touched_lower:
|
if not self.can_trade():
|
||||||
logger.info(f"➕ 多仓触下轨,加仓 {usdt_amount}U")
|
time.sleep(self.cfg.POLL_INTERVAL)
|
||||||
if not self.can_trade():
|
continue
|
||||||
continue
|
if self.open_with_balance_and_confirm('long', previous_amount=previous_amount):
|
||||||
self.browser_open_position('long', usdt_amount)
|
self.position_count = 2
|
||||||
time.sleep(3)
|
self.last_kline_id = kline_id
|
||||||
self.get_position_status()
|
self.update_trade_time()
|
||||||
if self.position == 1:
|
self.save_state()
|
||||||
self.position_count = 2
|
logger.success(f"✓ 加仓成功")
|
||||||
self.last_kline_id = kline_id
|
|
||||||
self.update_trade_time()
|
elif self.position < 0 and touched_upper:
|
||||||
logger.success(f"✓ 加仓成功")
|
logger.info("➕ 空仓触上轨,加仓")
|
||||||
|
if not self.can_trade():
|
||||||
elif self.position < 0 and touched_upper:
|
time.sleep(self.cfg.POLL_INTERVAL)
|
||||||
logger.info(f"➕ 空仓触上轨,加仓 {usdt_amount}U")
|
continue
|
||||||
if not self.can_trade():
|
if self.open_with_balance_and_confirm('short', previous_amount=previous_amount):
|
||||||
continue
|
self.position_count = 2
|
||||||
self.browser_open_position('short', usdt_amount)
|
self.last_kline_id = kline_id
|
||||||
time.sleep(3)
|
self.update_trade_time()
|
||||||
self.get_position_status()
|
self.save_state()
|
||||||
if self.position == -1:
|
logger.success(f"✓ 加仓成功")
|
||||||
self.position_count = 2
|
|
||||||
self.last_kline_id = kline_id
|
|
||||||
self.update_trade_time()
|
|
||||||
logger.success(f"✓ 加仓成功")
|
|
||||||
|
|
||||||
# 更新K线历史和布林带历史
|
|
||||||
kline_history.append(current_kline)
|
|
||||||
if len(kline_history) > 100:
|
|
||||||
kline_history = kline_history[-100:]
|
|
||||||
|
|
||||||
prev_bb_upper = bb_upper
|
|
||||||
prev_bb_lower = bb_lower
|
|
||||||
|
|
||||||
time.sleep(self.cfg.POLL_INTERVAL)
|
time.sleep(self.cfg.POLL_INTERVAL)
|
||||||
|
|
||||||
|
|||||||
106551
bb_sweep_results.csv
106551
bb_sweep_results.csv
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user