bitmart优化完成
This commit is contained in:
@@ -2,6 +2,7 @@ import time
|
||||
import uuid
|
||||
import datetime
|
||||
import requests
|
||||
from typing import Tuple
|
||||
|
||||
from tqdm import tqdm
|
||||
from loguru import logger
|
||||
@@ -107,6 +108,19 @@ class StrategyConfig:
|
||||
post_sl_mult_max: float = 1.16
|
||||
post_sl_vol_alpha: float = 0.20
|
||||
|
||||
# =========================================================
|
||||
# ✅ 正常平仓条件:价格回归到EMA附近时平仓
|
||||
# =========================================================
|
||||
normal_exit_threshold: float = 0.0003 # 0.03%,价格回归到EMA附近时平仓
|
||||
normal_exit_min_profit: float = 0.0002 # 0.02%,正常平仓最小盈利要求
|
||||
|
||||
# =========================================================
|
||||
# ✅ 手续费配置(固定10u,30倍杠杆)
|
||||
# =========================================================
|
||||
fixed_margin: float = 10.0 # 固定每单10u保证金
|
||||
platform_fee_rate: float = 0.0005 # 平台手续费:开仓价值的万分之五
|
||||
rebate_rate: float = 0.90 # 返佣比例:90%
|
||||
|
||||
|
||||
class BitmartFuturesMeanReversionBot:
|
||||
def __init__(self, cfg: StrategyConfig, bit_id=None):
|
||||
@@ -132,6 +146,10 @@ class BitmartFuturesMeanReversionBot:
|
||||
self.entry_ts = None
|
||||
self.last_exit_ts = 0
|
||||
|
||||
# 开仓信息(用于手续费计算)
|
||||
self.entry_margin = None # 开仓保证金(固定10u)
|
||||
self.entry_position_value = None # 开仓时的仓位价值(保证金 * 杠杆)
|
||||
|
||||
# 日内权益基准
|
||||
self.day_start_equity = None
|
||||
self.trading_enabled = True
|
||||
@@ -532,20 +550,30 @@ class BitmartFuturesMeanReversionBot:
|
||||
|
||||
# ----------------- 下单 -----------------
|
||||
def calculate_size(self, price: float) -> int:
|
||||
"""
|
||||
计算仓位大小
|
||||
固定每单10u保证金,30倍杠杆
|
||||
"""
|
||||
bal = self.get_assets_available()
|
||||
if bal < 10:
|
||||
if bal < self.cfg.fixed_margin:
|
||||
logger.warning(f"余额不足:{bal:.2f} USDT < {self.cfg.fixed_margin} USDT")
|
||||
return 0
|
||||
|
||||
margin = bal * self.cfg.risk_percent
|
||||
# 固定保证金10u
|
||||
margin = self.cfg.fixed_margin
|
||||
lev = int(self.cfg.leverage)
|
||||
|
||||
# ⚠️ 沿用你的原假设:1张≈0.001ETH
|
||||
# 仓位价值 = 保证金 * 杠杆 = 10 * 30 = 300u
|
||||
# size = 仓位价值 / (价格 * 0.001)
|
||||
size = int((margin * lev) / (price * 0.001))
|
||||
size = max(self.cfg.min_size, size)
|
||||
size = min(self.cfg.max_size, size)
|
||||
|
||||
logger.info(f"计算仓位:保证金={margin}u, 杠杆={lev}x, 仓位价值={margin * lev}u, size={size}")
|
||||
return size
|
||||
|
||||
def place_market_order(self, side: int, size: int, ) -> bool:
|
||||
def place_market_order(self, side: int, size: int) -> bool:
|
||||
"""
|
||||
【下单函数】实际执行下单操作
|
||||
side: 1=开多, 4=开空, 2=平多, 3=平空
|
||||
@@ -554,43 +582,221 @@ class BitmartFuturesMeanReversionBot:
|
||||
return False
|
||||
|
||||
try:
|
||||
|
||||
# 开多单
|
||||
if side == 1:
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
self.page.ele('x://*[@id="size_0"]').input(size)
|
||||
self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]')
|
||||
elif side == 4:
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
self.page.ele('x://*[@id="size_0"]').input(size)
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
self.page.ele('x://*[@id="size_0"]').input(size, clear=True)
|
||||
self.click_safe('x://span[normalize-space(text()) ="买入/做多"]')
|
||||
else:
|
||||
self.click_safe('x://span[normalize-space(text()) ="市价"]')
|
||||
|
||||
# if resp.get("code") == 1000:
|
||||
logger.info(f"✅ 开多单: size={size}")
|
||||
return True
|
||||
|
||||
# self.ding(f"下单失败: {resp}", error=True)
|
||||
return False
|
||||
# 开空单
|
||||
elif side == 4:
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
self.page.ele('x://*[@id="size_0"]').input(size, clear=True)
|
||||
self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]')
|
||||
logger.info(f"✅ 开空单: size={size}")
|
||||
return True
|
||||
|
||||
except APIException as e:
|
||||
logger.error(f"API下单异常: {e}")
|
||||
self.ding(f"API下单异常: {e}", error=True)
|
||||
return False
|
||||
# 平多单(平多 = 卖出)
|
||||
elif side == 2:
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
time.sleep(0.3) # 等待界面响应
|
||||
# 平仓时size可以设置大一些确保全部平仓
|
||||
self.page.ele('x://*[@id="size_0"]').input(size, clear=True)
|
||||
time.sleep(0.3)
|
||||
self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]')
|
||||
logger.info(f"✅ 平多单操作执行: size={size}")
|
||||
return True
|
||||
|
||||
# 平空单(平空 = 买入)
|
||||
elif side == 3:
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
time.sleep(0.3) # 等待界面响应
|
||||
self.page.ele('x://*[@id="size_0"]').input(size, clear=True)
|
||||
time.sleep(0.3)
|
||||
self.click_safe('x://span[normalize-space(text()) ="买入/做多"]')
|
||||
logger.info(f"✅ 平空单操作执行: size={size}")
|
||||
return True
|
||||
|
||||
else:
|
||||
logger.error(f"未知的订单方向: side={side}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"下单未知异常: {e}")
|
||||
self.ding(f"下单未知异常: {e}", error=True)
|
||||
logger.error(f"下单异常: {e}")
|
||||
self.ding(f"下单异常: {e}", error=True)
|
||||
return False
|
||||
|
||||
def close_position_all(self):
|
||||
def calculate_fee(self, position_value: float) -> float:
|
||||
"""
|
||||
计算手续费
|
||||
平台手续费 = 仓位价值 * 0.0005(万分之五)
|
||||
实际手续费 = 平台手续费 * (1 - 返佣比例) = 平台手续费 * 0.1
|
||||
"""
|
||||
platform_fee = position_value * self.cfg.platform_fee_rate
|
||||
actual_fee = platform_fee * (1 - self.cfg.rebate_rate)
|
||||
return actual_fee
|
||||
|
||||
def calculate_net_pnl(self, price: float) -> Tuple[float, float, float]:
|
||||
"""
|
||||
计算扣除手续费后的净盈亏
|
||||
返回: (净盈亏比例, 总手续费, 毛盈亏比例)
|
||||
|
||||
注意:在杠杆交易中,盈亏比例 = 杠杆倍数 * 价格变动比例
|
||||
例如:30倍杠杆,价格涨1%,实际盈亏是30%
|
||||
"""
|
||||
if self.pos == 0 or self.entry_price is None or self.entry_margin is None or self.entry_position_value is None:
|
||||
return 0.0, 0.0, 0.0
|
||||
|
||||
leverage = int(self.cfg.leverage)
|
||||
|
||||
# 计算价格变动比例
|
||||
if self.pos == 1:
|
||||
ok = self.place_market_order(3, 999999)
|
||||
if ok:
|
||||
self.pos = 0
|
||||
elif self.pos == -1:
|
||||
ok = self.place_market_order(2, 999999)
|
||||
if ok:
|
||||
price_change_ratio = (price - self.entry_price) / self.entry_price
|
||||
else:
|
||||
price_change_ratio = (self.entry_price - price) / self.entry_price
|
||||
|
||||
# 计算毛盈亏比例(考虑杠杆)
|
||||
gross_pnl_ratio = leverage * price_change_ratio
|
||||
|
||||
# 计算开仓手续费
|
||||
entry_fee = self.calculate_fee(self.entry_position_value)
|
||||
|
||||
# 计算平仓手续费(使用当前价格计算平仓时的仓位价值)
|
||||
# 平仓时的仓位价值 ≈ 开仓时的仓位价值(假设size不变)
|
||||
exit_position_value = self.entry_position_value
|
||||
exit_fee = self.calculate_fee(exit_position_value)
|
||||
|
||||
# 总手续费
|
||||
total_fee = entry_fee + exit_fee
|
||||
|
||||
# 手续费相对于保证金的比率
|
||||
fee_ratio = total_fee / self.entry_margin
|
||||
|
||||
# 净盈亏 = 毛盈亏 - 手续费比率
|
||||
net_pnl_ratio = gross_pnl_ratio - fee_ratio
|
||||
|
||||
return net_pnl_ratio, total_fee, gross_pnl_ratio
|
||||
|
||||
def close_position_all(self) -> bool:
|
||||
"""
|
||||
平掉所有持仓
|
||||
重试机制:最多尝试3次,每次平仓后通过SDK验证是否成功
|
||||
返回: True=平仓成功, False=平仓失败
|
||||
"""
|
||||
if self.pos == 0:
|
||||
logger.info("当前无持仓,无需平仓")
|
||||
return True
|
||||
|
||||
max_retries = 3
|
||||
retry_delay = 1.0 # 每次重试间隔1秒
|
||||
|
||||
for attempt in range(1, max_retries + 1):
|
||||
logger.info(f"平仓尝试 {attempt}/{max_retries}...")
|
||||
|
||||
# 记录平仓前的持仓状态
|
||||
old_pos = self.pos
|
||||
|
||||
# 执行平仓操作
|
||||
if self.pos == 1:
|
||||
# 平多单,使用side=2
|
||||
ok = self.place_market_order(2, 999999)
|
||||
if not ok:
|
||||
logger.warning(f"平多单操作失败 (尝试 {attempt}/{max_retries})")
|
||||
if attempt < max_retries:
|
||||
time.sleep(retry_delay)
|
||||
continue
|
||||
else:
|
||||
self.ding(f"平多单失败:已重试{max_retries}次仍失败", error=True)
|
||||
return False
|
||||
elif self.pos == -1:
|
||||
# 平空单,使用side=3
|
||||
ok = self.place_market_order(3, 999999)
|
||||
if not ok:
|
||||
logger.warning(f"平空单操作失败 (尝试 {attempt}/{max_retries})")
|
||||
if attempt < max_retries:
|
||||
time.sleep(retry_delay)
|
||||
continue
|
||||
else:
|
||||
self.ding(f"平空单失败:已重试{max_retries}次仍失败", error=True)
|
||||
return False
|
||||
else:
|
||||
logger.info("持仓状态异常,无需平仓")
|
||||
return True
|
||||
|
||||
# 等待订单执行
|
||||
time.sleep(1.5) # 等待订单执行
|
||||
|
||||
# 通过SDK验证是否平仓成功
|
||||
verify_success = self._verify_position_closed(old_pos)
|
||||
|
||||
if verify_success:
|
||||
# 平仓成功,清空状态
|
||||
self.pos = 0
|
||||
self.entry_margin = None
|
||||
self.entry_position_value = None
|
||||
logger.success(f"✅ 平仓成功 (尝试 {attempt}/{max_retries})")
|
||||
return True
|
||||
else:
|
||||
# 平仓失败,准备重试
|
||||
logger.warning(f"平仓验证失败,持仓仍存在 (尝试 {attempt}/{max_retries})")
|
||||
if attempt < max_retries:
|
||||
time.sleep(retry_delay)
|
||||
# 重新获取持仓状态
|
||||
self.get_position_status()
|
||||
else:
|
||||
self.ding(f"平仓失败:已重试{max_retries}次,持仓仍未平掉", error=True)
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
def _verify_position_closed(self, expected_old_pos: int) -> bool:
|
||||
"""
|
||||
验证持仓是否已平仓
|
||||
通过SDK查询持仓状态,确认是否真的平仓成功
|
||||
返回: True=平仓成功, False=仍有持仓
|
||||
"""
|
||||
try:
|
||||
# 调用SDK查询持仓状态
|
||||
resp = self.contractAPI.get_position(contract_symbol=self.cfg.contract_symbol)[0]
|
||||
|
||||
if resp.get("code") != 1000:
|
||||
logger.warning(f"查询持仓状态失败: {resp}")
|
||||
return False
|
||||
|
||||
positions = resp.get("data", [])
|
||||
|
||||
# 如果没有持仓,说明平仓成功
|
||||
if not positions or len(positions) == 0:
|
||||
logger.info("✅ SDK验证:持仓已平仓")
|
||||
return True
|
||||
|
||||
# 检查持仓是否还存在
|
||||
# position_type: 1=多, 2=空 (根据get_position_status的逻辑)
|
||||
for p in positions:
|
||||
position_type = p.get("position_type", 0)
|
||||
# 根据get_position_status的逻辑:1=多(pos=1), 其他=空(pos=-1)
|
||||
current_pos = 1 if position_type == 1 else -1
|
||||
|
||||
if current_pos == expected_old_pos:
|
||||
# 持仓仍然存在(与平仓前的方向一致)
|
||||
logger.warning(
|
||||
f"⚠️ SDK验证:持仓仍存在 (position_type={position_type}, 方向={'多' if current_pos == 1 else '空'})")
|
||||
return False
|
||||
|
||||
# 持仓已改变或不存在(平仓成功)
|
||||
logger.info("✅ SDK验证:持仓已平仓或已改变")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"验证持仓状态异常: {e}")
|
||||
# 验证失败时,保守处理:认为可能未平仓
|
||||
return False
|
||||
|
||||
# ----------------- 止损后机制 -----------------
|
||||
def _reentry_penalty_active(self, dev: float, entry_dev: float) -> bool:
|
||||
@@ -674,7 +880,11 @@ class BitmartFuturesMeanReversionBot:
|
||||
self.pos = 1
|
||||
self.entry_price = price
|
||||
self.entry_ts = time.time()
|
||||
self.ding(f"✅开多:dev={dev * 100:.3f}% size={size} entry={price:.2f}")
|
||||
# 记录开仓信息(用于手续费计算)
|
||||
self.entry_margin = self.cfg.fixed_margin
|
||||
self.entry_position_value = self.entry_margin * int(self.cfg.leverage)
|
||||
self.ding(
|
||||
f"✅开多:dev={dev * 100:.3f}% size={size} entry={price:.2f} margin={self.entry_margin}u value={self.entry_position_value}u")
|
||||
|
||||
# ========== 【开单位置2】开空单 ==========
|
||||
# 方向:空(做空,卖出)
|
||||
@@ -684,56 +894,130 @@ class BitmartFuturesMeanReversionBot:
|
||||
self.pos = -1
|
||||
self.entry_price = price
|
||||
self.entry_ts = time.time()
|
||||
self.ding(f"✅开空:dev={dev * 100:.3f}% size={size} entry={price:.2f}")
|
||||
# 记录开仓信息(用于手续费计算)
|
||||
self.entry_margin = self.cfg.fixed_margin
|
||||
self.entry_position_value = self.entry_margin * int(self.cfg.leverage)
|
||||
self.ding(
|
||||
f"✅开空:dev={dev * 100:.3f}% size={size} entry={price:.2f} margin={self.entry_margin}u value={self.entry_position_value}u")
|
||||
|
||||
def maybe_exit(self, price: float, tp: float, sl: float, vol_scale: float):
|
||||
"""检查并执行出场"""
|
||||
def maybe_exit(self, price: float, ema_value: float, tp: float, sl: float, vol_scale: float):
|
||||
"""
|
||||
检查并执行出场
|
||||
【平仓函数】包含四种平仓条件,所有平仓都会考虑手续费:
|
||||
1. 正常平仓:价格回归到EMA附近时平仓(扣除手续费后仍有盈利)
|
||||
2. 止盈:达到止盈阈值(扣除手续费后仍有盈利)
|
||||
3. 止损:达到止损阈值
|
||||
4. 超时平仓:持仓时间超过限制(扣除手续费后仍有盈利)
|
||||
"""
|
||||
if self.pos == 0 or self.entry_price is None or self.entry_ts is None:
|
||||
return
|
||||
|
||||
hold = time.time() - self.entry_ts
|
||||
|
||||
if self.pos == 1:
|
||||
pnl = (price - self.entry_price) / self.entry_price
|
||||
else:
|
||||
pnl = (self.entry_price - price) / self.entry_price
|
||||
# 计算毛盈亏和净盈亏(扣除手续费后)
|
||||
# calculate_net_pnl已经考虑了杠杆倍数
|
||||
net_pnl, total_fee, gross_pnl = self.calculate_net_pnl(price)
|
||||
|
||||
# 计算价格偏离EMA的程度
|
||||
dev = (price - ema_value) / ema_value if ema_value else 0.0
|
||||
|
||||
# 计算止损倍数(考虑止损后放宽)
|
||||
sl_mult = 1.0
|
||||
if self.post_sl_dir == self.pos and self.post_sl_dir != 0:
|
||||
sl_mult = self._post_sl_dynamic_mult()
|
||||
effective_sl = sl * sl_mult
|
||||
|
||||
if pnl >= tp:
|
||||
self.close_position_all()
|
||||
self.ding(f"🎯止盈:pnl={pnl * 100:.3f}% price={price:.2f} tp={tp * 100:.3f}%")
|
||||
self.entry_price, self.entry_ts = None, None
|
||||
self.last_exit_ts = time.time()
|
||||
# ========== 【平仓条件1】正常平仓:价格回归到EMA附近 ==========
|
||||
# 这是均值回归策略的核心:当价格回归到EMA附近时,说明均值回归已经完成,应该平仓
|
||||
# 但必须扣除手续费后仍有盈利才平仓
|
||||
if abs(dev) <= self.cfg.normal_exit_threshold:
|
||||
if net_pnl > 0: # 扣除手续费后仍有盈利
|
||||
if self.close_position_all():
|
||||
self.ding(
|
||||
f"📊正常平仓:价格回归EMA附近 dev={dev * 100:.3f}% "
|
||||
f"毛盈亏={gross_pnl * 100:.3f}% 净盈亏={net_pnl * 100:.3f}% "
|
||||
f"手续费={total_fee:.4f}u price={price:.2f}"
|
||||
)
|
||||
self.entry_price, self.entry_ts = None, None
|
||||
self.entry_margin, self.entry_position_value = None, None
|
||||
self.last_exit_ts = time.time()
|
||||
return
|
||||
else:
|
||||
logger.error("正常平仓失败,持仓可能仍存在")
|
||||
# 平仓失败时不更新状态,下次循环会继续尝试
|
||||
return
|
||||
|
||||
elif pnl <= -effective_sl:
|
||||
# ========== 【平仓条件2】止盈 ==========
|
||||
# 达到止盈阈值,且扣除手续费后仍有盈利
|
||||
if gross_pnl >= tp:
|
||||
if net_pnl > 0: # 扣除手续费后仍有盈利
|
||||
if self.close_position_all():
|
||||
self.ding(
|
||||
f"🎯止盈:毛盈亏={gross_pnl * 100:.3f}% 净盈亏={net_pnl * 100:.3f}% "
|
||||
f"手续费={total_fee:.4f}u price={price:.2f} tp={tp * 100:.3f}%"
|
||||
)
|
||||
self.entry_price, self.entry_ts = None, None
|
||||
self.entry_margin, self.entry_position_value = None, None
|
||||
self.last_exit_ts = time.time()
|
||||
return
|
||||
else:
|
||||
logger.error("止盈平仓失败,持仓可能仍存在")
|
||||
return
|
||||
|
||||
# ========== 【平仓条件3】止损 ==========
|
||||
# 止损使用毛盈亏判断(因为止损是风险控制,即使扣除手续费后亏损也要止损)
|
||||
if gross_pnl <= -effective_sl:
|
||||
sl_dir = self.pos
|
||||
|
||||
self.close_position_all()
|
||||
self.ding(
|
||||
f"🛑止损:pnl={pnl * 100:.3f}% price={price:.2f} "
|
||||
f"sl={sl * 100:.3f}% effective_sl={effective_sl * 100:.3f}%(×{sl_mult:.2f})",
|
||||
error=True
|
||||
)
|
||||
if self.close_position_all():
|
||||
self.ding(
|
||||
f"🛑止损:毛盈亏={gross_pnl * 100:.3f}% 净盈亏={net_pnl * 100:.3f}% "
|
||||
f"手续费={total_fee:.4f}u price={price:.2f} "
|
||||
f"sl={sl * 100:.3f}% effective_sl={effective_sl * 100:.3f}%(×{sl_mult:.2f})",
|
||||
error=True
|
||||
)
|
||||
|
||||
self.last_sl_dir = sl_dir
|
||||
self.last_sl_ts = time.time()
|
||||
self.last_sl_dir = sl_dir
|
||||
self.last_sl_ts = time.time()
|
||||
|
||||
self.post_sl_dir = sl_dir
|
||||
self.post_sl_ts = time.time()
|
||||
self.post_sl_vol_scale = float(vol_scale)
|
||||
self.post_sl_dir = sl_dir
|
||||
self.post_sl_ts = time.time()
|
||||
self.post_sl_vol_scale = float(vol_scale)
|
||||
|
||||
self.entry_price, self.entry_ts = None, None
|
||||
self.last_exit_ts = time.time()
|
||||
self.entry_price, self.entry_ts = None, None
|
||||
self.entry_margin, self.entry_position_value = None, None
|
||||
self.last_exit_ts = time.time()
|
||||
return
|
||||
else:
|
||||
logger.error("止损平仓失败,持仓可能仍存在,风险较高!")
|
||||
self.ding("⚠️ 止损平仓失败,请手动检查持仓!", error=True)
|
||||
# 即使平仓失败,也更新止损状态,避免重复触发
|
||||
self.last_sl_dir = sl_dir
|
||||
self.last_sl_ts = time.time()
|
||||
return
|
||||
|
||||
elif hold >= self.cfg.max_hold_sec:
|
||||
self.close_position_all()
|
||||
self.ding(f"⏱超时:hold={int(hold)}s pnl={pnl * 100:.3f}% price={price:.2f}")
|
||||
self.entry_price, self.entry_ts = None, None
|
||||
self.last_exit_ts = time.time()
|
||||
# ========== 【平仓条件4】超时平仓 ==========
|
||||
# 超时平仓也要考虑手续费,只有扣除手续费后仍有盈利才平仓
|
||||
if hold >= self.cfg.max_hold_sec:
|
||||
if net_pnl > 0: # 扣除手续费后仍有盈利
|
||||
if self.close_position_all():
|
||||
self.ding(
|
||||
f"⏱超时:hold={int(hold)}s 毛盈亏={gross_pnl * 100:.3f}% "
|
||||
f"净盈亏={net_pnl * 100:.3f}% 手续费={total_fee:.4f}u price={price:.2f}"
|
||||
)
|
||||
self.entry_price, self.entry_ts = None, None
|
||||
self.entry_margin, self.entry_position_value = None, None
|
||||
self.last_exit_ts = time.time()
|
||||
return
|
||||
else:
|
||||
logger.error("超时平仓失败,持仓可能仍存在")
|
||||
return
|
||||
else:
|
||||
# 超时但扣除手续费后亏损,继续持有等待盈利
|
||||
logger.debug(
|
||||
f"⏱超时但净盈亏为负,继续持有:hold={int(hold)}s "
|
||||
f"毛盈亏={gross_pnl * 100:.3f}% 净盈亏={net_pnl * 100:.3f}%"
|
||||
)
|
||||
|
||||
def notify_status_throttled(self, price: float, ema_value: float, dev: float, bal: float,
|
||||
atr_ratio: float, base_ratio: float, vol_scale: float,
|
||||
@@ -754,6 +1038,13 @@ class BitmartFuturesMeanReversionBot:
|
||||
|
||||
base_age = int(now - self._base_ratio_ts) if self._base_ratio_ts else -1
|
||||
|
||||
# 计算当前盈亏(如果有持仓)
|
||||
current_gross_pnl = 0.0
|
||||
current_net_pnl = 0.0
|
||||
current_fee = 0.0
|
||||
if self.pos != 0 and self.entry_price:
|
||||
current_net_pnl, current_fee, current_gross_pnl = self.calculate_net_pnl(price)
|
||||
|
||||
msg = (
|
||||
f"【BitMart {self.cfg.contract_symbol}|1m均值回归(动态阈值)】\n"
|
||||
f"📊 状态:{direction_str}\n"
|
||||
@@ -762,11 +1053,23 @@ class BitmartFuturesMeanReversionBot:
|
||||
f"🌊 波动率:ATR比={atr_ratio * 100:.3f}% | 基准={base_ratio * 100:.3f}% | 缩放={vol_scale:.2f}\n"
|
||||
f"🎯 动态Floor:入场={entry_floor * 100:.3f}% | 止盈={tp_floor * 100:.3f}% | 止损={sl_floor * 100:.3f}%\n"
|
||||
f"💰 止盈/止损:{tp * 100:.3f}% / {sl * 100:.3f}% (盈亏比:{tp / sl:.2f})\n"
|
||||
f"📊 正常平仓阈值:±{self.cfg.normal_exit_threshold * 100:.3f}%\n"
|
||||
f"🔄 基准刷新:{self.cfg.base_ratio_refresh_sec}s (已过={base_age}s)\n"
|
||||
f"⚠️ 止损同向加门槛:{'开启' if penalty_active else '关闭'} (方向={self.last_sl_dir})\n"
|
||||
f"💳 可用余额:{bal:.2f} USDT | 杠杆:{self.cfg.leverage}x\n"
|
||||
f"⏱️ 持仓限制:{self.cfg.max_hold_sec}s | 冷却:{self.cfg.cooldown_sec_after_exit}s"
|
||||
f"💳 可用余额:{bal:.2f} USDT | 杠杆:{self.cfg.leverage}x | 固定保证金:{self.cfg.fixed_margin}u\n"
|
||||
)
|
||||
|
||||
if self.pos != 0 and self.entry_price is not None and self.entry_position_value is not None:
|
||||
msg += (
|
||||
f"📈 当前盈亏:毛盈亏={current_gross_pnl * 100:.3f}% | "
|
||||
f"净盈亏={current_net_pnl * 100:.3f}% | "
|
||||
f"手续费={current_fee:.4f}u\n"
|
||||
f"📍 入场价:{self.entry_price:.2f} | "
|
||||
f"仓位价值:{self.entry_position_value:.2f}u\n"
|
||||
)
|
||||
|
||||
msg += f"⏱️ 持仓限制:{self.cfg.max_hold_sec}s | 冷却:{self.cfg.cooldown_sec_after_exit}s"
|
||||
|
||||
self.ding(msg)
|
||||
|
||||
def openBrowser(self):
|
||||
@@ -914,7 +1217,11 @@ class BitmartFuturesMeanReversionBot:
|
||||
# 7. 检查交易是否启用
|
||||
if not self.trading_enabled:
|
||||
if self.pos != 0:
|
||||
self.close_position_all()
|
||||
if self.close_position_all():
|
||||
logger.info("交易被禁用,已平仓")
|
||||
else:
|
||||
logger.error("交易被禁用,但平仓失败,请手动检查!")
|
||||
self.ding("⚠️ 交易被禁用但平仓失败,请手动检查持仓!", error=True)
|
||||
logger.warning("交易被禁用(风控触发),等待...")
|
||||
time.sleep(5)
|
||||
continue
|
||||
@@ -922,12 +1229,14 @@ class BitmartFuturesMeanReversionBot:
|
||||
# 8. 检查危险市场
|
||||
if self.is_danger_market(klines, price):
|
||||
logger.warning("危险模式:高波动/大实体K,暂停开仓")
|
||||
self.maybe_exit(price, tp, sl, vol_scale)
|
||||
self.maybe_exit(price, ema_value, tp, sl, vol_scale)
|
||||
time.sleep(self.cfg.tick_refresh_sec)
|
||||
continue
|
||||
|
||||
# 9. 执行交易逻辑
|
||||
self.maybe_exit(price, tp, sl, vol_scale)
|
||||
# 先检查平仓(包括正常平仓、止盈、止损)
|
||||
self.maybe_exit(price, ema_value, tp, sl, vol_scale)
|
||||
# 再检查开仓
|
||||
self.maybe_enter(price, ema_value, entry_dev)
|
||||
|
||||
# 10. 状态通知
|
||||
@@ -971,3 +1280,4 @@ if __name__ == "__main__":
|
||||
logger.error(f"程序异常退出: {e}")
|
||||
bot.ding(f"❌ 策略异常退出: {e}", error=True)
|
||||
raise
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
telegram/sign.db
BIN
telegram/sign.db
Binary file not shown.
Reference in New Issue
Block a user