This commit is contained in:
27942
2026-02-07 22:33:42 +08:00
parent 644927145c
commit e1cc39a18e

View File

@@ -31,9 +31,12 @@ class BitmartFuturesTransaction:
self.leverage = "100" # 高杠杆(全仓模式下可开更大仓位)
self.open_type = "cross" # 全仓模式
self.risk_percent = 0 # 未使用;若启用则可为每次开仓占可用余额的百分比
self.trailing_activation_usd = 2 # 盈利达到此金额后启动移动止损
self.trailing_distance_usd = 1.5 # 从最高盈利回撤此金额则平仓
self.max_unrealized_pnl_seen = None # 持仓期间见过的最大盈利(用于移动止损)
# 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 # 持仓量
@@ -42,7 +45,7 @@ class BitmartFuturesTransaction:
self.default_order_size = 25 # 开仓/反手张数,统一在此修改
self.last_trade_kline_id = None # 上次新开仓所在K线ID同一根K线只允许开一笔新仓反手不受限
self.accumulated_loss = 0 # 累计亏损反手时上一笔亏损累加,移动止损激活阈值需覆盖此亏损
self.accumulated_loss = 0 # 累计亏损反手时记录,预留)
# 策略相关变量
self.prev_kline = None # 上一根K线
@@ -51,7 +54,7 @@ class BitmartFuturesTransaction:
self.current_open = None # 当前K线开盘价
def get_klines(self):
"""获取最近2根K线当前K线和上一根K线"""
"""获取最近根K线用于向前回溯查找实体足够大的K线"""
try:
end_time = int(time.time())
# 获取足够多的条目确保有最新的K线
@@ -74,14 +77,14 @@ class BitmartFuturesTransaction:
})
formatted.sort(key=lambda x: x['id'])
# 返回最近2根K线倒数第二根上一根和最后一根当前
# 返回所有K线列表按时间升序至少需要2根
if len(formatted) >= 2:
return formatted[-2], formatted[-1]
return None, None
return formatted
return None
except Exception as e:
logger.error(f"获取K线异常: {e}")
self.ding(text="获取K线异常", error=True)
return None, None
return None
def get_current_price(self):
"""获取当前最新价格"""
@@ -262,19 +265,55 @@ class BitmartFuturesTransaction:
'lower': min(kline['open'], kline['close']) # 实体下边
}
def check_signal(self, current_price, prev_kline, current_kline):
def calc_atr(self, klines, period=14):
"""
计算 ATRAverage True Range
klines: 按时间升序的K线列表至少需要 period+1 根(第一根无 prev_close 用 high-low
返回: 最新一根K线对应的 ATR 值,不足数据时返回 None
"""
if not klines or len(klines) < period + 1:
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
return atr
def check_signal(self, current_price, klines):
"""
检查交易信号
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'] # 实体上边
@@ -424,7 +463,8 @@ class BitmartFuturesTransaction:
# 验证开仓是否成功
if self.verify_position_direction(1):
self.max_unrealized_pnl_seen = None # 新仓位重置移动止损记录
self.max_high_since_open = None
self.min_low_since_open = None # ATR 移动止损重置
self.accumulated_loss = 0 # 新开仓重置累计亏损
logger.success("开多成功")
return True
@@ -448,7 +488,8 @@ class BitmartFuturesTransaction:
# 验证开仓是否成功
if self.verify_position_direction(-1):
self.max_unrealized_pnl_seen = None # 新仓位重置移动止损记录
self.min_low_since_open = None
self.max_high_since_open = None # ATR 移动止损重置
self.accumulated_loss = 0 # 新开仓重置累计亏损
logger.success("开空成功")
return True
@@ -479,7 +520,8 @@ class BitmartFuturesTransaction:
time.sleep(3)
if self.verify_position_direction(1):
self.max_unrealized_pnl_seen = None
self.max_high_since_open = None
self.min_low_since_open = None # ATR 移动止损重置
logger.success("反手做多成功")
time.sleep(20)
return True
@@ -509,7 +551,8 @@ class BitmartFuturesTransaction:
time.sleep(3)
if self.verify_position_direction(-1):
self.max_unrealized_pnl_seen = None
self.min_low_since_open = None
self.max_high_since_open = None # ATR 移动止损重置
logger.success("反手做空成功")
time.sleep(20)
return True
@@ -552,13 +595,15 @@ class BitmartFuturesTransaction:
page_start = False
try:
# 1. 获取K线数据当前K线和上一根K线
prev_kline, current_kline = self.get_klines()
if not prev_kline or not current_kline:
# 1. 获取K线数据
klines = self.get_klines()
if not klines:
logger.warning("获取K线失败等待重试...")
time.sleep(5)
continue
current_kline = klines[-1]
# 记录进入新的K线
current_kline_time = current_kline['id']
if self.last_kline_time != current_kline_time:
@@ -580,34 +625,39 @@ class BitmartFuturesTransaction:
logger.debug(f"当前持仓状态: {self.start} (0=无, 1=多, -1=空)")
# 3.5 移动止损(激活阈值 = 基础阈值 + 累计亏损
# 3.5 ATR 移动止损:每根 K 线 trailing_stop = 最高价 - ATR*k/ 最低价 + ATR*k
if self.start != 0:
pnl_usd = self.get_unrealized_pnl_usd()
if pnl_usd is not None:
# 更新持仓期间最大盈利(用于移动止损)
if self.max_unrealized_pnl_seen is None:
self.max_unrealized_pnl_seen = pnl_usd
else:
self.max_unrealized_pnl_seen = max(self.max_unrealized_pnl_seen, pnl_usd)
# 移动止损激活阈值 = 基础阈值 + 累计亏损(需要先赚回之前反手的亏损)
effective_activation = self.trailing_activation_usd + self.accumulated_loss
if self.accumulated_loss > 0:
logger.debug(f"移动止损激活阈值: {self.trailing_activation_usd} + 累计亏损 {self.accumulated_loss:.2f} = {effective_activation:.2f} 美元")
# 移动止损:盈利曾达到 effective_activation 后,从最高盈利回撤 trailing_distance 则平仓
if self.max_unrealized_pnl_seen >= effective_activation:
if pnl_usd < self.max_unrealized_pnl_seen - self.trailing_distance_usd:
logger.info(f"移动止损:当前盈利 {pnl_usd:.2f} 从最高 {self.max_unrealized_pnl_seen:.2f} 回撤 >= {self.trailing_distance_usd} 美元,平仓"
f"(激活阈值={effective_activation:.2f},含累计亏损 {self.accumulated_loss:.2f}")
# 更新持仓以来的最高/最低价当前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
if current_price < trailing_stop:
logger.info(f"ATR移动止损(多):当前价 {current_price:.2f} < 跟踪止盈 {trailing_stop:.2f} (最高{self.max_high_since_open:.2f} - ATR{atr:.4f}*{self.atr_k}),平仓")
self.平仓()
self.max_unrealized_pnl_seen = None
self.accumulated_loss = 0 # 止盈平仓后重置累计亏损
self.last_trade_kline_id = current_kline_time # 平仓后标记当前K线下一根K线才能再开仓
logger.info(f"平仓后标记K线({current_kline_time})等待下一根K线再开仓累计亏损已重置")
self.max_high_since_open = None
self.last_trade_kline_id = current_kline_time
time.sleep(3)
continue
else: # 持空:价格升破 trailing_stop 平仓
trailing_stop = self.min_low_since_open + atr * self.atr_k
if current_price > trailing_stop:
logger.info(f"ATR移动止损(空):当前价 {current_price:.2f} > 跟踪止盈 {trailing_stop:.2f} (最低{self.min_low_since_open:.2f} + ATR{atr:.4f}*{self.atr_k}),平仓")
self.平仓()
self.min_low_since_open = None
self.last_trade_kline_id = current_kline_time
time.sleep(3)
continue
# 4. 检查信号
signal = self.check_signal(current_price, prev_kline, current_kline)
signal = self.check_signal(current_price, klines)
# 5. 同一根K线只允许开一笔新仓位反手不受限制
if signal and signal[0] in ('long', 'short') and self.last_trade_kline_id == current_kline_time: