哈哈
This commit is contained in:
@@ -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):
|
||||
"""
|
||||
计算 ATR(Average 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:
|
||||
|
||||
Reference in New Issue
Block a user