优化完美代码,优化计算盈亏
This commit is contained in:
@@ -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):
|
||||
"""
|
||||
计算 ATR(Average 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:
|
||||
Reference in New Issue
Block a user