优化完美代码,优化计算盈亏

This commit is contained in:
27942
2026-02-08 01:41:08 +08:00
parent 438cffd31a
commit 4ed52e6e93

View File

@@ -28,24 +28,31 @@ class BitmartFuturesTransaction:
self.last_kline_time = None # 上一次处理的K线时间戳用于判断是否是新K线
# 反手频率控制
self.reverse_cooldown_seconds = 1.5 * 60 # 反手冷却时间(秒)
self.reverse_min_move_pct = 0.05 # 反手最小价差过滤(百分比)
self.last_reverse_time = None # 上次反手时间
# 开仓频率控制
self.open_cooldown_seconds = 60 # 开仓冷却时间(秒),两次开仓至少间隔此时长
self.last_open_time = None # 上次开仓时间
self.last_open_kline_id = None # 上次开仓所在 K 线 id同一根 K 线只允许开仓一次
self.leverage = "100" # 高杠杆(全仓模式下可开更大仓位)
self.open_type = "cross" # 全仓模式
self.risk_percent = 0 # 未使用;若启用则可为每次开仓占可用余额的百分比
# ATR 移动止损trailing_stop = 最高价 - ATR*k/ 最低价 + ATR*k
self.atr_period = 14 # ATR 周期
self.atr_k = 2.0 # 稳健 2.5~3激进 1.5~2
self.max_high_since_open = None # 持多仓期间最高价
self.min_low_since_open = None # 持空仓期间最低价
self.open_avg_price = None # 开仓价格
self.current_amount = None # 持仓量
self.bit_id = bit_id
self.default_order_size = 25 # 开仓/反手张数,统一在此修改
self.last_trade_kline_id = None # 上次新开仓所在K线ID同一根K线只允许开一笔新仓反手不受限
self.accumulated_loss = 0 # 累计亏损(反手时记录,预留)
# ATR 回调止盈参数
self.atr_period = 14 # ATR 周期
self.atr_multiplier = 2.5 # ATR 倍数(稳健 2.5~3激进 1.5~2
self.highest_since_entry = None # 开仓以来最高价(多单追踪)
self.lowest_since_entry = None # 开仓以来最低价(空单追踪)
# 策略相关变量
self.prev_kline = None # 上一根K线
@@ -54,7 +61,7 @@ class BitmartFuturesTransaction:
self.current_open = None # 当前K线开盘价
def get_klines(self):
"""获取最近根K线用于向前回溯查找实体足够大的K线"""
"""获取最近2根K线当前K线和上一根K线"""
try:
end_time = int(time.time())
# 获取足够多的条目确保有最新的K线
@@ -77,14 +84,41 @@ class BitmartFuturesTransaction:
})
formatted.sort(key=lambda x: x['id'])
# 返回所有K线列表按时间升序至少需要2根
# 返回最近2根K线倒数第二根上一根和最后一根当前
if len(formatted) >= 2:
return formatted
return None
return formatted[-2], formatted[-1]
return None, None
except Exception as e:
logger.error(f"获取K线异常: {e}")
self.ding(text="获取K线异常", error=True)
return None
return None, None
def get_all_klines(self):
"""获取所有可用的5分钟K线用于ATR计算等按时间升序排列"""
try:
end_time = int(time.time())
response = self.contractAPI.get_kline(
contract_symbol=self.contract_symbol,
step=5,
start_time=end_time - 3600 * 3,
end_time=end_time
)[0]["data"]
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'])
return formatted
except Exception as e:
logger.error(f"获取K线异常: {e}")
self.ding(text="获取K线异常", error=True)
return []
def get_current_price(self):
"""获取当前最新价格"""
@@ -265,55 +299,95 @@ class BitmartFuturesTransaction:
'lower': min(kline['open'], kline['close']) # 实体下边
}
def calc_atr(self, klines, period=14):
def calculate_atr(self, klines):
"""
计算 ATRAverage True Range
klines: 按时间升序的K线列表至少需要 period+1 第一根无 prev_close high-low
返回: 最新一根K线对应的 ATR 不足数据时返回 None
计算 ATR(14)
klines: 按时间升序的完整K线列表含当前未收盘K线
返回: ATR 值或 None
"""
if not klines or len(klines) < period + 1:
# 排除最后一根当前未收盘使用已收盘K线
closed = klines[:-1] if len(klines) > 1 else klines
if len(closed) < self.atr_period + 1:
logger.warning(f"已收盘K线不足({len(closed)}),需要至少 {self.atr_period + 1}无法计算ATR")
return None
tr_list = []
for i, k in enumerate(klines):
high, low = k['high'], k['low']
if i == 0:
tr = high - low
else:
prev_close = klines[i - 1]['close']
tr = max(high - low, abs(high - prev_close), abs(low - prev_close))
tr_list.append(tr)
# 取最近 period 根 K 线的 TR 的简单平均
atr = sum(tr_list[-period:]) / period
# 计算 True Range 序列
true_ranges = []
for i in range(1, len(closed)):
h = closed[i]['high']
l = closed[i]['low']
pc = closed[i - 1]['close']
tr = max(h - l, abs(h - pc), abs(l - pc))
true_ranges.append(tr)
# 取最近 atr_period 个 TR 的简单平均作为 ATR
recent_trs = true_ranges[-self.atr_period:]
atr = sum(recent_trs) / len(recent_trs)
return atr
def check_signal(self, current_price, klines):
def check_trailing_stop(self, current_price, atr):
"""
检查 ATR 回调止盈是否触发
- 多单: 从开仓以来最高价回落 >= ATR * k 平仓
- 空单: 从开仓以来最低价反弹 >= ATR * k 平仓
返回: True=触发止盈, False=未触发
"""
if self.start == 0 or atr is None:
return False
if self.start == 1: # 持多仓
# 首次追踪(程序重启等情况),从开仓均价开始
if self.highest_since_entry is None:
self.highest_since_entry = self.open_avg_price if self.open_avg_price else current_price
if current_price > self.highest_since_entry:
self.highest_since_entry = current_price
trailing_stop = self.highest_since_entry - atr * self.atr_multiplier
logger.debug(f"[ATR止盈] 多单 | 最高={self.highest_since_entry:.2f} | "
f"ATR={atr:.4f} | k={self.atr_multiplier} | "
f"止盈线={trailing_stop:.2f} | 当前={current_price:.2f}")
if current_price <= trailing_stop:
logger.info(f"ATR回调止盈触发(多)! 当前价 {current_price:.2f} <= "
f"止盈线 {trailing_stop:.2f} "
f"(最高 {self.highest_since_entry:.2f} - "
f"{self.atr_multiplier} x ATR {atr:.4f})")
return True
elif self.start == -1: # 持空仓
# 首次追踪(程序重启等情况),从开仓均价开始
if self.lowest_since_entry is None:
self.lowest_since_entry = self.open_avg_price if self.open_avg_price else current_price
if current_price < self.lowest_since_entry:
self.lowest_since_entry = current_price
trailing_stop = self.lowest_since_entry + atr * self.atr_multiplier
logger.debug(f"[ATR止盈] 空单 | 最低={self.lowest_since_entry:.2f} | "
f"ATR={atr:.4f} | k={self.atr_multiplier} | "
f"止盈线={trailing_stop:.2f} | 当前={current_price:.2f}")
if current_price >= trailing_stop:
logger.info(f"ATR回调止盈触发(空)! 当前价 {current_price:.2f} >= "
f"止盈线 {trailing_stop:.2f} "
f"(最低 {self.lowest_since_entry:.2f} + "
f"{self.atr_multiplier} x ATR {atr:.4f})")
return True
return False
def check_signal(self, current_price, prev_kline, current_kline):
"""
检查交易信号
klines: K线列表按时间升序最后一根为当前K线
返回: ('long', trigger_price) / ('short', trigger_price) / None
"""
current_kline = klines[-1]
# 从上一根K线开始往前回溯找到实体足够大的K线实体 >= 0.1
prev_kline = None
for i in range(len(klines) - 2, -1, -1): # 从倒数第二根开始往前找
candidate = klines[i]
entity = self.calculate_entity(candidate)
if entity >= 0.1:
prev_kline = candidate
if i < len(klines) - 2:
logger.info(f"上一根K线实体过小回溯到第 {len(klines) - 1 - i} 根前的K线实体: {entity:.4f}")
break
else:
logger.debug(f"回溯K线 {candidate['id']} 实体过小: {entity:.4f},继续往前")
if prev_kline is None:
logger.info("所有历史K线实体均过小跳过信号检测")
return None
# 计算上一根K线实体
prev_entity = self.calculate_entity(prev_kline)
# 实体过小不交易(实体 < 0.1
if prev_entity < 0.1:
logger.info(f"上一根K线实体过小: {prev_entity:.4f},跳过信号检测")
return None
# 获取上一根K线的实体上下边
prev_entity_edge = self.get_entity_edge(prev_kline)
prev_entity_upper = prev_entity_edge['upper'] # 实体上边
@@ -331,17 +405,17 @@ class BitmartFuturesTransaction:
if use_current_open_as_base:
# 以当前K线开盘价为基准计算跳空时用当前开盘价参与计算
calc_lower = current_kline['open']
calc_upper = current_kline['open'] # 同一基准,上下分之一对称
long_trigger = calc_lower + prev_entity / 4
short_trigger = calc_upper - prev_entity / 4
long_breakout = calc_upper + prev_entity / 4
short_breakout = calc_lower - prev_entity / 4
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 / 4 # 做多触发价 = 实体下边 + 实体/4(下分之一处)
short_trigger = prev_entity_upper - prev_entity / 4 # 做空触发价 = 实体上边 - 实体/4(上分之一处)
long_breakout = prev_entity_upper + prev_entity / 4 # 做多突破价 = 实体上边 + 实体/4
short_breakout = prev_entity_lower - prev_entity / 4 # 做空突破价 = 实体下边 - 实体/4
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']
@@ -359,27 +433,27 @@ class BitmartFuturesTransaction:
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/4): {long_trigger:.2f}, 做空触发价(上1/4): {short_trigger:.2f}")
logger.info(f"突破做多价(上1/4外): {long_breakout:.2f}, 突破做空价(下1/4外): {short_breakout:.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("上一根阴线+当前阳线(做多形态),不按上分之一做空")
logger.info("上一根阴线+当前阳线(做多形态),不按上分之一做空")
if skip_long_by_lower_third:
logger.info("上一根阳线+当前阴线(做空形态),不按下分之一做多")
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/4外) {long_breakout:.2f}")
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/4外) {short_breakout:.2f}")
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/4) {short_trigger:.2f}")
logger.info(f"持多反手做空!价格 {current_price:.2f} <= 触发价(上1/3) {short_trigger:.2f}")
return ('reverse_short', short_trigger)
# 反手条件2: 上一根K线上阴线涨幅>0.01%,当前跌到上一根实体下边
@@ -392,7 +466,7 @@ class BitmartFuturesTransaction:
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/4) {long_trigger:.2f}")
logger.info(f"持空反手做多!价格 {current_price:.2f} >= 触发价(下1/3) {long_trigger:.2f}")
return ('reverse_long', long_trigger)
# 反手条件2: 上一根K线下阴线跌幅>0.01%,当前涨到上一根实体上边
@@ -404,6 +478,34 @@ class BitmartFuturesTransaction:
return None
def can_open(self, current_kline_id):
"""开仓前过滤:同一根 K 线只开一次 + 开仓冷却时间。仅用于 long/short 新开仓。"""
now = time.time()
if self.last_open_kline_id is not None and self.last_open_kline_id == current_kline_id:
logger.info(f"开仓频率控制:本 K 线({current_kline_id})已开过仓,跳过")
return False
if self.last_open_time is not None and now - self.last_open_time < self.open_cooldown_seconds:
remain = self.open_cooldown_seconds - (now - self.last_open_time)
logger.info(f"开仓冷却中,剩余 {remain:.0f}")
return False
return True
def can_reverse(self, current_price, trigger_price):
"""反手前过滤:冷却时间 + 最小价差"""
now = time.time()
if self.last_reverse_time and now - self.last_reverse_time < self.reverse_cooldown_seconds:
remain = self.reverse_cooldown_seconds - (now - self.last_reverse_time)
logger.info(f"反手冷却中,剩余 {remain:.0f}")
return False
if trigger_price and trigger_price > 0:
move_pct = abs(current_price - trigger_price) / trigger_price * 100
if move_pct < self.reverse_min_move_pct:
logger.info(f"反手价差不足: {move_pct:.4f}% < {self.reverse_min_move_pct}%")
return False
return True
def verify_no_position(self, max_retries=5, retry_interval=3):
"""
验证当前无持仓
@@ -463,9 +565,10 @@ class BitmartFuturesTransaction:
# 验证开仓是否成功
if self.verify_position_direction(1):
self.max_high_since_open = None
self.min_low_since_open = None # ATR 移动止损重置
self.accumulated_loss = 0 # 新开仓重置累计亏损
self.last_open_time = time.time()
self.last_open_kline_id = getattr(self, "_current_kline_id_for_open", None)
self.highest_since_entry = self.open_avg_price # ATR止盈从开仓价开始追踪
self.lowest_since_entry = None
logger.success("开多成功")
return True
else:
@@ -488,9 +591,10 @@ class BitmartFuturesTransaction:
# 验证开仓是否成功
if self.verify_position_direction(-1):
self.min_low_since_open = None
self.max_high_since_open = None # ATR 移动止损重置
self.accumulated_loss = 0 # 新开仓重置累计亏损
self.last_open_time = time.time()
self.last_open_kline_id = getattr(self, "_current_kline_id_for_open", None)
self.lowest_since_entry = self.open_avg_price # ATR止盈从开仓价开始追踪
self.highest_since_entry = None
logger.success("开空成功")
return True
else:
@@ -500,11 +604,6 @@ class BitmartFuturesTransaction:
elif signal_type == 'reverse_long':
# 平空 + 开多(反手做多):先平仓,确认无仓后再开多,避免双向持仓
logger.info(f"执行反手做多,触发价: {trigger_price:.2f}")
# 反手前记录当前仓位的未实现盈亏,亏损则累加到 accumulated_loss
pre_reverse_pnl = self.get_unrealized_pnl_usd()
if pre_reverse_pnl is not None and pre_reverse_pnl < 0:
self.accumulated_loss += abs(pre_reverse_pnl)
logger.info(f"反手前亏损 {pre_reverse_pnl:.2f} 美元,累计亏损更新为 {self.accumulated_loss:.2f} 美元")
self.平仓()
time.sleep(1) # 给交易所处理平仓的时间
# 轮询确认已无持仓再开多(最多等约 10 秒)
@@ -520,9 +619,10 @@ class BitmartFuturesTransaction:
time.sleep(3)
if self.verify_position_direction(1):
self.max_high_since_open = None
self.min_low_since_open = None # ATR 移动止损重置
self.highest_since_entry = self.open_avg_price # ATR止盈从开仓价开始追踪
self.lowest_since_entry = None
logger.success("反手做多成功")
self.last_reverse_time = time.time()
time.sleep(20)
return True
else:
@@ -532,11 +632,6 @@ class BitmartFuturesTransaction:
elif signal_type == 'reverse_short':
# 平多 + 开空(反手做空):先平仓,确认无仓后再开空
logger.info(f"执行反手做空,触发价: {trigger_price:.2f}")
# 反手前记录当前仓位的未实现盈亏,亏损则累加到 accumulated_loss
pre_reverse_pnl = self.get_unrealized_pnl_usd()
if pre_reverse_pnl is not None and pre_reverse_pnl < 0:
self.accumulated_loss += abs(pre_reverse_pnl)
logger.info(f"反手前亏损 {pre_reverse_pnl:.2f} 美元,累计亏损更新为 {self.accumulated_loss:.2f} 美元")
self.平仓()
time.sleep(1)
for _ in range(10):
@@ -551,9 +646,10 @@ class BitmartFuturesTransaction:
time.sleep(3)
if self.verify_position_direction(-1):
self.min_low_since_open = None
self.max_high_since_open = None # ATR 移动止损重置
self.lowest_since_entry = self.open_avg_price # ATR止盈从开仓价开始追踪
self.highest_since_entry = None
logger.success("反手做空成功")
self.last_reverse_time = time.time()
time.sleep(20)
return True
else:
@@ -565,7 +661,7 @@ class BitmartFuturesTransaction:
def action(self):
"""主循环"""
logger.info("开始运行分之一策略交易...")
logger.info("开始运行分之一策略交易...")
# 启动时设置全仓高杠杆
if not self.set_leverage():
@@ -595,14 +691,14 @@ class BitmartFuturesTransaction:
page_start = False
try:
# 1. 获取K线数据
klines = self.get_klines()
if not klines:
# 1. 获取完整K线数据(用于信号检测 + ATR计算
all_klines = self.get_all_klines()
if len(all_klines) < 2:
logger.warning("获取K线失败等待重试...")
time.sleep(5)
continue
current_kline = klines[-1]
prev_kline = all_klines[-2]
current_kline = all_klines[-1]
# 记录进入新的K线
current_kline_time = current_kline['id']
@@ -625,88 +721,49 @@ class BitmartFuturesTransaction:
logger.debug(f"当前持仓状态: {self.start} (0=无, 1=多, -1=空)")
# 3.5 ATR 移动止损:每根 K 线 trailing_stop = 最高价 - ATR*k/ 最低价 + ATR*k
# 3.5 ATR 回调止盈检查(有持仓时
if self.start != 0:
# 更新持仓以来的最高/最低价当前K线 high/low + 当前价)
if self.start == 1: # 持多
cand_high = max(current_kline['high'], current_price)
self.max_high_since_open = cand_high if self.max_high_since_open is None else max(self.max_high_since_open, cand_high)
else: # 持空
cand_low = min(current_kline['low'], current_price)
self.min_low_since_open = cand_low if self.min_low_since_open is None else min(self.min_low_since_open, cand_low)
atr = self.calc_atr(klines, self.atr_period)
if atr is not None:
if self.start == 1: # 持多:价格跌破 trailing_stop 平仓
trailing_stop = self.max_high_since_open - atr * self.atr_k
logger.debug(f"ATR止损(多)trailing_stop={trailing_stop:.2f}, 当前价={current_price:.2f}, "
f"最高={self.max_high_since_open:.2f}, ATR={atr:.4f}*{self.atr_k}")
if current_price < trailing_stop:
logger.info(f"ATR移动止损(多)触发:当前价 {current_price:.2f} < trailing_stop {trailing_stop:.2f} "
f"(最高{self.max_high_since_open:.2f} - ATR{atr:.4f}*{self.atr_k}),执行平仓")
self.平仓()
time.sleep(1)
# 轮询验证平仓是否成功最多等约10秒
closed = False
for _ in range(10):
if self.get_position_status() and self.start == 0:
closed = True
break
time.sleep(1)
if closed:
logger.success("ATR移动止损(多)平仓成功")
self.max_high_since_open = None
self.min_low_since_open = None
self.last_trade_kline_id = current_kline_time
page_start = True
self.page.close()
time.sleep(5)
else:
logger.error("ATR移动止损(多)平仓失败,仓位仍在,下次循环重试")
# 不重置 max_high_since_open保留原止损线下次继续触发
continue
else: # 持空:价格升破 trailing_stop 平仓
trailing_stop = self.min_low_since_open + atr * self.atr_k
logger.debug(f"ATR止损(空)trailing_stop={trailing_stop:.2f}, 当前价={current_price:.2f}, "
f"最低={self.min_low_since_open:.2f}, ATR={atr:.4f}*{self.atr_k}")
if current_price > trailing_stop:
logger.info(f"ATR移动止损(空)触发:当前价 {current_price:.2f} > trailing_stop {trailing_stop:.2f} "
f"(最低{self.min_low_since_open:.2f} + ATR{atr:.4f}*{self.atr_k}),执行平仓")
self.平仓()
time.sleep(1)
# 轮询验证平仓是否成功最多等约10秒
closed = False
for _ in range(10):
if self.get_position_status() and self.start == 0:
closed = True
break
time.sleep(1)
if closed:
logger.success("ATR移动止损(空)平仓成功")
self.min_low_since_open = None
self.max_high_since_open = None
self.last_trade_kline_id = current_kline_time
page_start = True
self.page.close()
time.sleep(5)
else:
logger.error("ATR移动止损(空)平仓失败,仓位仍在,下次循环重试")
# 不重置 min_low_since_open保留原止损线下次继续触发
continue
atr = self.calculate_atr(all_klines)
if self.check_trailing_stop(current_price, atr):
# 触发回调止盈,执行平仓
self.平仓()
time.sleep(3)
# 轮询确认平仓成功最多约10秒
for _ in range(10):
if self.get_position_status() and self.start == 0:
break
time.sleep(1)
if self.start == 0:
logger.success("ATR回调止盈平仓成功")
self.highest_since_entry = None
self.lowest_since_entry = None
page_start = True
else:
logger.warning("ATR回调止盈平仓后仍有持仓下次循环重试")
if page_start:
self.page.close()
time.sleep(5)
continue
# 4. 检查信号
signal = self.check_signal(current_price, klines)
signal = self.check_signal(current_price, prev_kline, current_kline)
# 5. 同一根K线只允许开一笔新仓位反手不受限制
if signal and signal[0] in ('long', 'short') and self.last_trade_kline_id == current_kline_time:
logger.info(f"同一根K线({current_kline_time})已开过新仓等待下一根K线反手不受此限制")
signal = None
# 5. 反手过滤:冷却时间 + 最小价差
if signal and signal[0].startswith('reverse_'):
if not self.can_reverse(current_price, signal[1]):
signal = None
# 5.5 开仓频率过滤:同一根 K 线只开一次 + 开仓冷却
if signal and signal[0] in ('long', 'short'):
if not self.can_open(current_kline_time):
signal = None
else:
self._current_kline_id_for_open = current_kline_time # 供 execute_trade 成功后记录
# 6. 有信号则执行交易
if signal:
trade_success = self.execute_trade(signal)
if trade_success:
self.last_trade_kline_id = current_kline_time # 记录开仓所在K线
logger.success(f"交易执行完成: {signal[0]}, 当前持仓状态: {self.start}")
page_start = True
else: