From 4396e49e42c0a359330ad1e5c6106b5abf8f8624 Mon Sep 17 00:00:00 2001 From: Administrator Date: Mon, 22 Dec 2025 11:07:50 +0800 Subject: [PATCH] =?UTF-8?q?bitmart=E4=BC=98=E5=8C=96=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 1111 | 3 +- bitmart_strategy.log | 21 + bitmart_trading.log | 46 +++ telegram/8619211027341.session | Bin 45056 -> 45056 bytes telegram/bot_session.session | Bin 40960 -> 45056 bytes telegram/sign.db | Bin 40960 -> 40960 bytes test.py | 712 ++++++++++++++++++++++----------- 交易/weex-结构优化.py | 30 +- 8 files changed, 559 insertions(+), 253 deletions(-) create mode 100644 bitmart_strategy.log create mode 100644 bitmart_trading.log diff --git a/1111 b/1111 index 24c36e0..b087fb6 100644 --- a/1111 +++ b/1111 @@ -10,7 +10,8 @@ websea 账号:yangyicheng6666@gmail.com 密码:Wg138333. - +yx20250715@gmail.com +Abc12345678 3. 更多交易信号 包住形态(原策略) diff --git a/bitmart_strategy.log b/bitmart_strategy.log new file mode 100644 index 0000000..35cc443 --- /dev/null +++ b/bitmart_strategy.log @@ -0,0 +1,21 @@ +2025-12-20 02:51:09.247 | WARNING | __main__:__init__:28 - 请确认Memo是否正确!默认Memo可能导致签名失败 +2025-12-20 02:51:09.252 | INFO | __main__:init_api_client:57 - 尝试初始化API客户端 (尝试 1/3) +2025-12-20 02:51:09.253 | ERROR | __main__:test_api_connection:112 - API连接测试失败: APIContract.get_depth() got an unexpected keyword argument 'limit' +2025-12-20 02:51:09.254 | SUCCESS | __main__:init_api_client:87 - API客户端初始化成功 +2025-12-20 02:51:09.254 | INFO | __main__:action:220 - 启动BitMart合约策略... +2025-12-20 02:51:09.254 | INFO | __main__:set_leverage_with_retry:121 - 尝试设置杠杆 (尝试 1/3) +2025-12-20 02:51:09.814 | ERROR | __main__:set_leverage_with_retry:149 - API异常: APIException(http status=401): response={"code":30005,"msg":"Header X-BM-SIGN is wrong","trace":"d9c107e3-7564-474e-9eaf-59667f869ae5"} + +2025-12-20 02:51:09.814 | INFO | __main__:set_leverage_with_retry:161 - 等待1秒后重试... +2025-12-20 02:51:10.815 | INFO | __main__:set_leverage_with_retry:121 - 尝试设置杠杆 (尝试 2/3) +2025-12-20 02:51:10.923 | ERROR | __main__:set_leverage_with_retry:149 - API异常: APIException(http status=401): response={"trace":"a7fa76a2-2e34-435b-acbe-90e37b47fe48","code":30005,"msg":"Header X-BM-SIGN is wrong"} + +2025-12-20 02:51:10.923 | INFO | __main__:set_leverage_with_retry:161 - 等待2秒后重试... +2025-12-20 02:51:12.923 | INFO | __main__:set_leverage_with_retry:121 - 尝试设置杠杆 (尝试 3/3) +2025-12-20 02:51:13.029 | ERROR | __main__:set_leverage_with_retry:149 - API异常: APIException(http status=401): response={"code":30005,"msg":"Header X-BM-SIGN is wrong","trace":"97b6c791-5812-4285-a659-dbba3f6170e6"} + +2025-12-20 02:51:13.029 | WARNING | __main__:set_leverage_with_retry:168 - 杠杆设置失败,但程序将继续运行 +2025-12-20 02:51:13.030 | WARNING | __main__:action:226 - 杠杆设置失败,使用系统默认杠杆继续运行 +2025-12-20 02:51:13.150 | SUCCESS | __main__:action:254 - 获取到新K线: 2025-12-20 02:30:00 +2025-12-20 02:51:13.150 | INFO | __main__:action:259 - 当前价格: 2959.49 +2025-12-20 02:51:13.311 | INFO | __main__:action:268 - 用户中断程序 diff --git a/bitmart_trading.log b/bitmart_trading.log new file mode 100644 index 0000000..4af609d --- /dev/null +++ b/bitmart_trading.log @@ -0,0 +1,46 @@ +2025-12-20 03:08:42.274 | INFO | __main__:initialize_api:66 - 尝试API配置 1/3 +2025-12-20 03:08:42.275 | INFO | __main__:initialize_api:67 - Memo: '' +2025-12-20 03:08:42.275 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts' +2025-12-20 03:08:42.276 | WARNING | __main__:initialize_api:84 - API配置 1 连接失败,尝试下一个配置 +2025-12-20 03:08:42.276 | INFO | __main__:initialize_api:66 - 尝试API配置 2/3 +2025-12-20 03:08:42.276 | INFO | __main__:initialize_api:67 - Memo: '合约交易' +2025-12-20 03:08:42.276 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts' +2025-12-20 03:08:42.276 | WARNING | __main__:initialize_api:84 - API配置 2 连接失败,尝试下一个配置 +2025-12-20 03:08:42.276 | INFO | __main__:initialize_api:66 - 尝试API配置 3/3 +2025-12-20 03:08:42.277 | INFO | __main__:initialize_api:67 - Memo: 'None' +2025-12-20 03:08:42.277 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts' +2025-12-20 03:08:42.277 | WARNING | __main__:initialize_api:84 - API配置 3 连接失败,尝试下一个配置 +2025-12-20 03:08:42.277 | ERROR | __main__:initialize_api:89 - 所有API配置都失败 +2025-12-20 03:08:42.281 | ERROR | __main__:action:408 - API初始化失败,程序无法运行 +2025-12-20 03:08:48.311 | INFO | __main__:initialize_api:66 - 尝试API配置 1/3 +2025-12-20 03:08:48.312 | INFO | __main__:initialize_api:67 - Memo: '' +2025-12-20 03:08:48.313 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts' +2025-12-20 03:08:48.313 | WARNING | __main__:initialize_api:84 - API配置 1 连接失败,尝试下一个配置 +2025-12-20 03:08:48.313 | INFO | __main__:initialize_api:66 - 尝试API配置 2/3 +2025-12-20 03:08:48.313 | INFO | __main__:initialize_api:67 - Memo: '合约交易' +2025-12-20 03:08:48.313 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts' +2025-12-20 03:08:48.313 | WARNING | __main__:initialize_api:84 - API配置 2 连接失败,尝试下一个配置 +2025-12-20 03:08:48.313 | INFO | __main__:initialize_api:66 - 尝试API配置 3/3 +2025-12-20 03:08:48.313 | INFO | __main__:initialize_api:67 - Memo: 'None' +2025-12-20 03:08:48.313 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts' +2025-12-20 03:08:48.313 | WARNING | __main__:initialize_api:84 - API配置 3 连接失败,尝试下一个配置 +2025-12-20 03:08:48.314 | ERROR | __main__:initialize_api:89 - 所有API配置都失败 +2025-12-20 03:08:48.317 | ERROR | __main__:action:408 - API初始化失败,程序无法运行 +2025-12-20 03:09:25.953 | INFO | __main__:initialize_api:66 - 尝试API配置 1/3 +2025-12-20 03:09:25.954 | INFO | __main__:initialize_api:67 - Memo: '' +2025-12-20 03:09:25.954 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts' +2025-12-20 03:09:25.954 | WARNING | __main__:initialize_api:84 - API配置 1 连接失败,尝试下一个配置 +2025-12-20 03:09:25.954 | INFO | __main__:initialize_api:66 - 尝试API配置 2/3 +2025-12-20 03:09:25.954 | INFO | __main__:initialize_api:67 - Memo: '合约交易' +2025-12-20 03:09:25.954 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts' +2025-12-20 03:09:25.954 | WARNING | __main__:initialize_api:84 - API配置 2 连接失败,尝试下一个配置 +2025-12-20 03:09:25.954 | INFO | __main__:initialize_api:66 - 尝试API配置 3/3 +2025-12-20 03:09:25.954 | INFO | __main__:initialize_api:67 - Memo: 'None' +2025-12-20 03:09:25.955 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts' +2025-12-20 03:09:25.955 | WARNING | __main__:initialize_api:84 - API配置 3 连接失败,尝试下一个配置 +2025-12-20 03:09:25.955 | ERROR | __main__:initialize_api:89 - 所有API配置都失败 +2025-12-20 03:09:25.958 | ERROR | __main__:action:408 - API初始化失败,程序无法运行 +2025-12-20 03:12:23.884 | INFO | __main__:action:396 - 启动BitMart合约交易策略... +2025-12-20 03:12:23.885 | INFO | __main__:test_api_connection:48 - 测试API连接... +2025-12-20 03:12:24.359 | ERROR | __main__:test_api_connection:90 - API连接测试异常: 'tuple' object has no attribute 'get' +2025-12-20 03:12:24.359 | ERROR | __main__:action:400 - API连接测试失败 diff --git a/telegram/8619211027341.session b/telegram/8619211027341.session index e9c22ab90403e4ffb5062e0a0416fed539e51fc0..1002c3b18b207030295c4824e769ad915cae16f7 100644 GIT binary patch delta 125 zcmZp8z|`=7X@V4!X6r;5Cm^{oVM#o*1n;BC>exDJsq331VBV(&pF--5{Wyx*myEuo&tMtrbl3fVDe_uyrV449)VSx X{}xST1Tv~OPb^Wi28m4Gy51WA`Y|dH delta 100 zcmZp8z|`=7X@V4!+Qx}8PC#;F!jgDqao!)3*%S0P3kn?K6%**`*t{ixje&`SokJ$Y z@m8Ro@s{s4llk%#*n=`%cAOMU+H9J4l!e)4$Jxz)izYGx8Rs@nEK#%uiA>(Q-Wve_ CP$mih diff --git a/telegram/bot_session.session b/telegram/bot_session.session index c81d8a7d300707256d82b156853fd10af5cb070b..4071167f24d4c69a3962c3699b695d841b81376c 100644 GIT binary patch delta 4482 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 YRywIRp5!Hp%Oi=DdJdp9mFgPi4GB7rs^O*-X z+)PB8g%u=PeD!lGT+~8D3L?5e&Vm^s3N%3nq&6xdWfNS~94IOV5=BvKF2Vybp)>p; zCrIt)PkQ`}+I+i#F7IdHpUT(I|C(Q&- 0.45% -> 危险模式,暂停开仓 + big_body_kill: float = 0.012 # 1m实体>1.2% -> 危险模式 + + # ===== 轮询节奏(减少REST压力)===== + klines_refresh_sec: int = 10 + tick_refresh_sec: int = 1 + status_notify_sec: int = 60 + + +class BitmartFuturesMeanReversionBot: + def __init__(self, cfg: StrategyConfig): + self.cfg = cfg + + # ✅ 强制只从环境变量读 self.api_key = "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8" self.secret_key = "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5" self.memo = "合约交易" - self.contract_symbol = "ETHUSDT" + if not self.api_key or not self.secret_key: + raise RuntimeError("请先设置环境变量 BITMART_API_KEY / BITMART_SECRET_KEY / BITMART_MEMO(可选)") self.contractAPI = APIContract(self.api_key, self.secret_key, self.memo, timeout=(5, 15)) - self.start = 0 # 持仓状态: -1 空, 0 无, 1 多 - self.open_avg_price = None # 开仓均价(字符串或float) - self.current_amount = 0 - self.position_cross = None + # 持仓状态: -1 空, 0 无, 1 多 + self.pos = 0 + self.entry_price = None + self.entry_ts = None + self.last_exit_ts = 0 - self.pbar = tqdm(total=10, desc="等待下次检查", ncols=80) + # 日内权益基准 + self.day_start_equity = None + self.trading_enabled = True + self.day_tag = datetime.date.today() - self.leverage = "10" # 低杠杆,安全做市 - self.open_type = "cross" - self.fixed_size = 1 # 每次挂单量 - self.spread_offset = 5.0 # 挂单偏移(USDT),建议5~10,避免立即成交 - self.max_position_threshold = 10 # 库存阈值,超过自动平仓 + # 缓存 + self._klines_cache = None + self._klines_cache_ts = 0 - # 新增:止盈止损参数(基于开仓均价的百分比) - self.take_profit_pct = 2.0 # 止盈 2% - self.stop_loss_pct = 1.0 # 止损 1%(可根据风险偏好调整) + self._last_status_notify_ts = 0 - self.price_precision = 0.1 - self.leverage_set = False - self.max_retries = 5 + self.pbar = tqdm(total=60, desc="运行中(秒)", ncols=90) + # ----------------- 通用工具 ----------------- def ding(self, msg, error=False): - prefix = "❌bitmart MM:" if error else "🔔bitmart MM:" + prefix = "❌bitmart:" if error else "🔔bitmart:" if error: - for i in range(10): - send_dingtalk_message(f"{prefix},{msg}") + for _ in range(3): + send_dingtalk_message(f"{prefix}{msg}") else: - send_dingtalk_message(f"{prefix},{msg}") + send_dingtalk_message(f"{prefix}{msg}") - def try_set_leverage(self): - if self.leverage_set: + def set_leverage(self) -> bool: + try: + resp = self.contractAPI.post_submit_leverage( + contract_symbol=self.cfg.contract_symbol, + leverage=self.cfg.leverage, + open_type=self.cfg.open_type + )[0] + if resp.get("code") == 1000: + logger.success(f"设置杠杆成功:{self.cfg.open_type} + {self.cfg.leverage}x") + return True + logger.error(f"设置杠杆失败: {resp}") + self.ding(f"设置杠杆失败: {resp}", error=True) + return False + except Exception as e: + logger.error(f"设置杠杆异常: {e}") + self.ding(f"设置杠杆异常: {e}", error=True) + return False + + # ----------------- 行情/指标 ----------------- + def get_klines_cached(self): + now = time.time() + if self._klines_cache is not None and (now - self._klines_cache_ts) < self.cfg.klines_refresh_sec: + return self._klines_cache + + kl = self.get_klines() + if kl: + self._klines_cache = kl + self._klines_cache_ts = now + return self._klines_cache + + def get_klines(self): + try: + end_time = int(time.time()) + start_time = end_time - 60 * self.cfg.lookback_min + + resp = self.contractAPI.get_kline( + contract_symbol=self.cfg.contract_symbol, + step=self.cfg.step_min, + start_time=start_time, + end_time=end_time + )[0] + + if resp.get("code") != 1000: + logger.error(f"获取K线失败: {resp}") + return None + + data = resp.get("data", []) + formatted = [] + for k in data: + 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(f"获取K线异常: {e}", error=True) + return None + + def get_last_price(self, fallback_close: float) -> float: + """ + 优先取更实时的最新价;若SDK不支持/字段不同,回退到K线close。 + """ + try: + if hasattr(self.contractAPI, "get_contract_details"): + r = self.contractAPI.get_contract_details(contract_symbol=self.cfg.contract_symbol)[0] + d = r.get("data") if isinstance(r, dict) else None + if isinstance(d, dict): + for key in ("last_price", "mark_price", "index_price"): + if key in d and d[key] is not None: + return float(d[key]) + + if hasattr(self.contractAPI, "get_ticker"): + r = self.contractAPI.get_ticker(contract_symbol=self.cfg.contract_symbol)[0] + d = r.get("data") if isinstance(r, dict) else None + if isinstance(d, dict): + for key in ("last_price", "price", "last", "close"): + if key in d and d[key] is not None: + return float(d[key]) + except Exception: + pass + + return float(fallback_close) + + @staticmethod + def ema(values, n: int) -> float: + k = 2 / (n + 1) + e = values[0] + for v in values[1:]: + e = v * k + e * (1 - k) + return e + + @staticmethod + def atr(klines, n: int) -> float: + if len(klines) < n + 1: + return 0.0 + trs = [] + for i in range(-n, 0): + cur = klines[i] + prev = klines[i - 1] + tr = max( + cur["high"] - cur["low"], + abs(cur["high"] - prev["close"]), + abs(cur["low"] - prev["close"]), + ) + trs.append(tr) + return sum(trs) / len(trs) + + def is_danger_market(self, klines, price: float) -> bool: + last = klines[-1] + body = abs(last["close"] - last["open"]) / last["open"] if last["open"] else 0.0 + if body >= self.cfg.big_body_kill: return True - for attempt in range(self.max_retries): - self.cancel_all_orders() - time.sleep(2) - - try: - response = self.contractAPI.post_submit_leverage( - contract_symbol=self.contract_symbol, - leverage=self.leverage, - open_type=self.open_type - )[0] - if response['code'] == 1000: - logger.success(f"全仓模式 + {self.leverage}x 杠杆设置成功") - self.leverage_set = True - return True - else: - logger.error(f"杠杆设置失败 (尝试 {attempt+1}): {response}") - except Exception as e: - logger.error(f"设置杠杆异常 (尝试 {attempt+1}): {e}") - - time.sleep(10) - - self.ding(error=True, msg="杠杆设置多次失败,请手动检查") - return False - - def get_depth(self): - try: - response = self.contractAPI.get_depth(contract_symbol=self.contract_symbol)[0] - if response['code'] == 1000: - data = response['data'] - best_bid = float(data['bids'][0][0]) if data['bids'] else None - best_ask = float(data['asks'][0][0]) if data['asks'] else None - return best_bid, best_ask - else: - logger.error(f"获取深度失败: {response}") - return None, None - except Exception as e: - logger.error(f"获取深度异常: {e}") - return None, None - - def cancel_all_orders(self): - try: - response = self.contractAPI.post_cancel_orders(contract_symbol=self.contract_symbol)[0] - if response['code'] == 1000: - logger.success("所有挂单已取消") - return True - else: - logger.error(f"取消挂单失败: {response}") - return False - except Exception as e: - logger.warning(f"取消挂单异常(忽略): {e}") - return False - - def place_limit_order(self, side: int, price: float, size: int): - price = round(price / self.price_precision) * self.price_precision - price_str = f"{price:.1f}" - - client_order_id = f"mm_{int(time.time())}_{uuid.uuid4().hex[:8]}" - - for attempt in range(self.max_retries): - try: - response = self.contractAPI.post_submit_order( - contract_symbol=self.contract_symbol, - client_order_id=client_order_id, - side=side, - mode=1, - type='limit', - leverage=self.leverage, - open_type=self.open_type, - price=price_str, - size=size - )[0] - - if response['code'] == 1000: - action = "挂买(开多)" if side == 1 else "挂卖(开空)" - logger.success(f"限价单挂单成功: {action}, 价格={price}, 张数={size}") - return True - else: - logger.error(f"挂单失败 (尝试 {attempt+1}): {response}") - except APIException as e: - logger.error(f"API挂单异常 (尝试 {attempt+1}): {e}") - - time.sleep(5) + a = self.atr(klines, self.cfg.atr_len) + atr_ratio = (a / price) if price > 0 else 0.0 + if atr_ratio >= self.cfg.atr_ratio_kill: + return True return False - def get_position_status(self): - try: - response = self.contractAPI.get_position(contract_symbol=self.contract_symbol)[0] - if response['code'] == 1000: - positions = response['data'] - if not positions: - self.start = 0 - self.current_amount = 0 - self.open_avg_price = None - return True - position = positions[0] - self.start = 1 if position['position_type'] == 1 else -1 - self.open_avg_price = float(position['open_avg_price']) if position['open_avg_price'] else 0.0 - self.current_amount = int(float(position.get('current_amount', 0))) - self.position_cross = position.get("position_cross") - return True - else: - return False - except Exception as e: - logger.error(f"持仓查询异常: {e}") - self.ding(error=True, msg="持仓查询异常") - return False + def dynamic_thresholds(self, atr_ratio: float): + """ + 返回动态 entry_dev / tp / sl + """ + entry_dev = max(self.cfg.entry_dev_floor, self.cfg.entry_k * atr_ratio) + tp = max(self.cfg.tp_floor, self.cfg.tp_k * atr_ratio) + sl = max(self.cfg.sl_floor, self.cfg.sl_k * atr_ratio) + return entry_dev, tp, sl - def get_available_balance(self): + # ----------------- 账户/仓位 ----------------- + def get_assets_available(self) -> float: try: - response = self.contractAPI.get_assets_detail()[0] - if response['code'] == 1000: - data = response['data'] - if isinstance(data, dict): - return float(data.get('available_balance', 0)) - elif isinstance(data, list): - for asset in data: - if asset.get('currency') == 'USDT': - return float(asset.get('available_balance', 0)) + resp = self.contractAPI.get_assets_detail()[0] + if resp.get("code") != 1000: + return 0.0 + data = resp.get("data") + if isinstance(data, dict): + return float(data.get("available_balance", 0)) + if isinstance(data, list): + for asset in data: + if asset.get("currency") == "USDT": + return float(asset.get("available_balance", 0)) return 0.0 except Exception as e: logger.error(f"余额查询异常: {e}") return 0.0 - def check_take_profit_stop_loss(self, current_price: float): - """检查是否触发止盈或止损(基于当前价格)""" - if self.current_amount == 0 or self.open_avg_price == 0.0: - return False, None - - if self.start == 1: # 多头 - pnl_pct = (current_price - self.open_avg_price) / self.open_avg_price * 100 - if pnl_pct >= self.take_profit_pct: - return True, "止盈平多" - if pnl_pct <= -self.stop_loss_pct: - return True, "止损平多" - - elif self.start == -1: # 空头 - pnl_pct = (self.open_avg_price - current_price) / self.open_avg_price * 100 - if pnl_pct >= self.take_profit_pct: - return True, "止盈平空" - if pnl_pct <= -self.stop_loss_pct: - return True, "止损平空" - - return False, None - - def close_position(self, reason: str = "库存阈值"): - """市场全平仓,并发送通知""" - if self.current_amount == 0: - return - - side = 3 if self.start == 1 else 2 # 3: 平多, 2: 平空 + def get_position_status(self) -> bool: try: - response = self.contractAPI.post_submit_order( - contract_symbol=self.contract_symbol, - client_order_id=f"close_{reason}_{int(time.time())}", - side=side, - mode=1, - type='market', - leverage=self.leverage, - open_type=self.open_type, - size=999999 - )[0] - if response['code'] == 1000: - direction_str = "多" if self.start == 1 else "空" - logger.success(f"{reason}平仓成功: 平{direction_str}") - self.ding(msg=f"{reason}触发,已平仓 {direction_str}头仓位") - self.start = 0 - self.current_amount = 0 - self.open_avg_price = None - return True - else: - logger.error(f"平仓失败: {response}") + resp = self.contractAPI.get_position(contract_symbol=self.cfg.contract_symbol)[0] + if resp.get("code") != 1000: return False - except APIException as e: - logger.error(f"API平仓异常: {e}") + + positions = resp.get("data", []) + if not positions: + self.pos = 0 + return True + + p = positions[0] + self.pos = 1 if p["position_type"] == 1 else -1 + return True + except Exception as e: + logger.error(f"持仓查询异常: {e}") + self.ding(f"持仓查询异常: {e}", error=True) return False + def get_equity_proxy(self) -> float: + return self.get_assets_available() + + def refresh_daily_baseline(self): + today = datetime.date.today() + if today != self.day_tag: + self.day_tag = today + self.day_start_equity = None + self.trading_enabled = True + self.ding(f"新的一天({today}):重置日内风控基准") + + def risk_kill_switch(self): + self.refresh_daily_baseline() + equity = self.get_equity_proxy() + if equity <= 0: + return + + if self.day_start_equity is None: + self.day_start_equity = equity + logger.info(f"日内权益基准设定:{equity:.2f} USDT") + return + + pnl = (equity - self.day_start_equity) / self.day_start_equity + if pnl <= -self.cfg.daily_loss_limit: + self.trading_enabled = False + self.ding(f"触发日止损:{pnl * 100:.2f}% -> 停机", error=True) + + if pnl >= self.cfg.daily_profit_cap: + self.trading_enabled = False + self.ding(f"达到日盈利封顶:{pnl * 100:.2f}% -> 停机") + + # ----------------- 下单 ----------------- + def calculate_size(self, price: float) -> int: + """ + 保守仓位估算:按 1张≈0.001ETH(你原假设) + """ + bal = self.get_assets_available() + if bal < 10: + return 0 + + margin = bal * self.cfg.risk_percent + lev = int(self.cfg.leverage) + + size = int((margin * lev) / (price * 0.001)) + size = max(self.cfg.min_size, size) + size = min(self.cfg.max_size, size) + return size + + def place_market_order(self, side: int, size: int) -> bool: + """ + side: + 1 开多 + 2 平空 + 3 平多 + 4 开空 + """ + if size <= 0: + return False + + client_order_id = f"mr_{int(time.time())}_{uuid.uuid4().hex[:8]}" + try: + resp = self.contractAPI.post_submit_order( + contract_symbol=self.cfg.contract_symbol, + client_order_id=client_order_id, + side=side, + mode=1, + type="market", + leverage=self.cfg.leverage, + open_type=self.cfg.open_type, + size=size + )[0] + + logger.info(f"order_resp: {resp}") + + if resp.get("code") == 1000: + return True + + self.ding(f"下单失败: {resp}", error=True) + return False + + except APIException as e: + logger.error(f"API下单异常: {e}") + self.ding(f"API下单异常: {e}", error=True) + return False + + except Exception as e: + logger.error(f"下单未知异常: {e}") + self.ding(f"下单未知异常: {e}", error=True) + return False + + def close_position_all(self): + 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: + self.pos = 0 + + # ----------------- 策略主逻辑 ----------------- + def in_cooldown(self) -> bool: + return (time.time() - self.last_exit_ts) < self.cfg.cooldown_sec_after_exit + + def maybe_enter(self, price: float, ema_value: float, entry_dev: float): + if self.pos != 0: + return + if self.in_cooldown(): + return + + dev = (price - ema_value) / ema_value if ema_value else 0.0 + size = self.calculate_size(price) + + logger.info( + f"enter_check: price={price:.2f}, ema={ema_value:.2f}, dev={dev*100:.3f}% " + f"(阈值={entry_dev*100:.3f}%), size={size}, pos={self.pos}" + ) + + if size <= 0: + return + + if dev <= -entry_dev: + if self.place_market_order(1, size): # 开多 + self.pos = 1 + self.entry_price = price + self.entry_ts = time.time() + self.ding(f"✅开多:dev={dev*100:.3f}% size={size} entry={price:.2f}") + + elif dev >= entry_dev: + if self.place_market_order(4, size): # 开空 + self.pos = -1 + self.entry_price = price + self.entry_ts = time.time() + self.ding(f"✅开空:dev={dev*100:.3f}% size={size} entry={price:.2f}") + + def maybe_exit(self, price: float, tp: float, sl: float): + 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 + + 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() + + elif pnl <= -sl: + self.close_position_all() + self.ding(f"🛑止损:pnl={pnl*100:.3f}% price={price:.2f} sl={sl*100:.3f}%", error=True) + self.entry_price, self.entry_ts = None, None + self.last_exit_ts = time.time() + + 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() + + def notify_status_throttled(self, price: float, ema_value: float, dev: float, bal: float, + atr_ratio: float, entry_dev: float, tp: float, sl: float): + now = time.time() + if (now - self._last_status_notify_ts) < self.cfg.status_notify_sec: + return + self._last_status_notify_ts = now + + direction_str = "多" if self.pos == 1 else ("空" if self.pos == -1 else "无") + msg = ( + f"【BitMart {self.cfg.contract_symbol}|均价回归微利(动态阈值)】\n" + f"方向:{direction_str}\n" + f"现价:{price:.2f}\n" + f"EMA{self.cfg.ema_len}:{ema_value:.2f}\n" + f"dev:{dev*100:.3f}%(阈值{entry_dev*100:.3f}%)\n" + f"ATR比:{atr_ratio*100:.3f}%\n" + f"tp/sl:{tp*100:.3f}% / {sl*100:.3f}%\n" + f"可用余额:{bal:.2f} USDT 杠杆:{self.cfg.leverage}x\n" + f"超时:{self.cfg.max_hold_sec}s 冷却:{self.cfg.cooldown_sec_after_exit}s" + ) + self.ding(msg) + def action(self): - logger.info("程序启动,将在循环中动态尝试设置杠杆") + if not self.set_leverage(): + self.ding("杠杆设置失败,停止运行", error=True) + return while True: - if not self.try_set_leverage(): - logger.warning("杠杆未设置成功,继续重试...") + now_dt = datetime.datetime.now() + self.pbar.n = now_dt.second + self.pbar.refresh() + klines = self.get_klines_cached() + if not klines or len(klines) < (self.cfg.ema_len + 5): + time.sleep(1) + continue + + last_k = klines[-1] + closes = [k["close"] for k in klines[-(self.cfg.ema_len + 1):]] + ema_value = self.ema(closes, self.cfg.ema_len) + + # 用更实时的价格触发(取不到就回退K线close) + price = self.get_last_price(fallback_close=float(last_k["close"])) + dev = (price - ema_value) / ema_value if ema_value else 0.0 + + # 动态阈值:跟随 ATR/Price + a = self.atr(klines, self.cfg.atr_len) + atr_ratio = (a / price) if price > 0 else 0.0 + entry_dev, tp, sl = self.dynamic_thresholds(atr_ratio) + + # 日内风控 + self.risk_kill_switch() + + # 刷新仓位 if not self.get_position_status(): - self.ding(error=True, msg="获取仓位信息失败!!!") - time.sleep(10) + time.sleep(1) continue - # 获取当前价格(使用中价) - best_bid, best_ask = self.get_depth() - if best_bid is None or best_ask is None: - time.sleep(10) + # 停机:平仓+不再开仓 + if not self.trading_enabled: + if self.pos != 0: + self.close_position_all() + time.sleep(5) continue - mid_price = (best_bid + best_ask) / 2 - # 检查止盈止损 - trigger, reason = self.check_take_profit_stop_loss(mid_price) - if trigger: - self.close_position(reason=reason) - # 平仓后继续下一轮(重新挂单) + # 危险市场:不新开仓(允许已有仓按tp/sl/超时退出) + if self.is_danger_market(klines, price): + logger.warning("危险模式:高波动/大实体K,暂停开仓") + self.maybe_exit(price, tp, sl) + time.sleep(self.cfg.tick_refresh_sec) + continue - # 检查库存阈值 - if abs(self.current_amount) > self.max_position_threshold: - self.close_position(reason="库存阈值") + # 先出场再入场 + self.maybe_exit(price, tp, sl) + self.maybe_enter(price, ema_value, entry_dev) - # 挂单 - bid_price = mid_price - self.spread_offset - ask_price = mid_price + self.spread_offset + # 状态通知(限频) + bal = self.get_assets_available() + self.notify_status_throttled(price, ema_value, dev, bal, atr_ratio, entry_dev, tp, sl) - self.cancel_all_orders() - - success_bid = self.place_limit_order(side=1, price=bid_price, size=self.fixed_size) - success_ask = self.place_limit_order(side=4, price=ask_price, size=self.fixed_size) - - # 统计盈亏百分比 - pnl_pct_str = "" - if self.current_amount != 0 and self.open_avg_price: - if self.start == 1: - pnl_pct = (mid_price - self.open_avg_price) / self.open_avg_price * 100 - else: - pnl_pct = (self.open_avg_price - mid_price) / self.open_avg_price * 100 - pnl_pct_str = f"浮动盈亏:{pnl_pct:+.2f}%" - - balance = self.get_available_balance() - leverage_status = "已设置" if self.leverage_set else "未同步(重试中)" - msg = ( - f"【BitMart {self.contract_symbol} MM】\n" - f"杠杆状态:{leverage_status}\n" - f"当前中价:{mid_price:.2f} USDT\n" - f"挂买价:{bid_price:.2f} ({'成功' if success_bid else '失败'})\n" - f"挂卖价:{ask_price:.2f} ({'成功' if success_ask else '失败'})\n" - f"持仓量:{self.current_amount} 张 {pnl_pct_str}\n" - f"账户可用余额:{balance:.2f} USDT\n" - f"止盈:+{self.take_profit_pct}% | 止损:-{self.stop_loss_pct}%" - ) - self.ding(msg=msg) - - self.pbar.reset() - time.sleep(10) + time.sleep(self.cfg.tick_refresh_sec) -if __name__ == '__main__': - BitmartMarketMaker().action() \ No newline at end of file +if __name__ == "__main__": + """ + Windows 设置环境变量示例(PowerShell): + setx BITMART_API_KEY "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8" + setx BITMART_SECRET_KEY "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5" + setx BITMART_MEMO "合约交易" + 重新打开终端再运行。 + """ + cfg = StrategyConfig() + bot = BitmartFuturesMeanReversionBot(cfg) + bot.action() diff --git a/交易/weex-结构优化.py b/交易/weex-结构优化.py index e074bcf..d9d9c6b 100644 --- a/交易/weex-结构优化.py +++ b/交易/weex-结构优化.py @@ -558,9 +558,9 @@ class MessageSender: legacy_direction = position_data.get('legacyOrderDirection', '') # 确定方向 - if position_side == 'SHORT' or legacy_direction == 'OPEN_SHORT': + if legacy_direction == 'OPEN_SHORT': direction = "空" - elif position_side == 'LONG' or legacy_direction == 'OPEN_LONG': + elif legacy_direction == 'OPEN_LONG': direction = "多" else: direction = "无" @@ -575,23 +575,31 @@ class MessageSender: pnl_rate = (open_avg_price - current_price) / open_avg_price * 100 pnl_str = f"{unrealized_pnl:+.2f} USDT ({pnl_rate:+.2f}%)" + + # 当前持仓名义价值 + current_value = fill_size * current_price + + return ( + "**【WEEX ETHUSDT 永续持仓监控】**\n\n" + f"**持仓方向**:{direction}\n" + f"**当前现价**:{current_price:.2f} USDT\n" + f"**开仓均价**:{open_avg_price:.2f} USDT\n" + f"**持仓数量(eth)**:{fill_size:.3f} ETH\n" + f"**持仓数量(usdt)**:{fill_value / 100:.2f} USDT\n" + f"**名义价值**:{current_value:.2f} USDT\n" + f"**浮动盈亏**:{pnl_str}\n" + f"**账户可用余额**:{available_balance:.2f} USDT" + ) else: pnl_str = "0.00 USDT" - # 当前持仓名义价值 - current_value = fill_size * current_price - return ( "**【WEEX ETHUSDT 永续持仓监控】**\n\n" - f"**持仓方向**:{direction}\n" + f"**持仓方向**:无\n" f"**当前现价**:{current_price:.2f} USDT\n" - f"**开仓均价**:{open_avg_price:.2f} USDT\n" - f"**持仓数量(eth)**:{fill_size:.3f} ETH\n" - f"**持仓数量(usdt)**:{fill_value / 100:.2f} USDT\n" - f"**名义价值**:{current_value:.2f} USDT\n" - f"**浮动盈亏**:{pnl_str}\n" f"**账户可用余额**:{available_balance:.2f} USDT" ) + else: return ( "**【WEEX ETHUSDT 永续持仓监控】**\n\n"