diff --git a/bitmart/均线自动化开单.py b/bitmart/均线自动化开单.py index 9616cce..f38140d 100644 --- a/bitmart/均线自动化开单.py +++ b/bitmart/均线自动化开单.py @@ -1,11 +1,12 @@ -import os import time import uuid import datetime -from dataclasses import dataclass +import requests from tqdm import tqdm from loguru import logger +from dataclasses import dataclass +from DrissionPage import ChromiumPage, ChromiumOptions from bitmart.api_contract import APIContract from bitmart.lib.cloud_exceptions import APIException @@ -108,7 +109,18 @@ class StrategyConfig: class BitmartFuturesMeanReversionBot: - def __init__(self, cfg: StrategyConfig): + def __init__(self, cfg: StrategyConfig, tge_id=None): + + self.tge_url = "http://127.0.0.1:50326" + self.tge_id = tge_id + + self.tge_headers = { + "Authorization": "Bearer asp_174003986c9b0799677c5b2c1adb76e402735d753bc91a91", + "Content-Type": "application/json" + } + + self.page: ChromiumPage | None = None + self.cfg = cfg self.api_key = "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8" @@ -539,29 +551,31 @@ class BitmartFuturesMeanReversionBot: size = min(self.cfg.max_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=平空 + """ 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 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://span[normalize-space(text()) ="买入/做多"]') + else: + self.click_safe('x://span[normalize-space(text()) ="市价"]') - if resp.get("code") == 1000: + # if resp.get("code") == 1000: return True - self.ding(f"下单失败: {resp}", error=True) + # self.ding(f"下单失败: {resp}", error=True) return False except APIException as e: @@ -621,7 +635,12 @@ class BitmartFuturesMeanReversionBot: 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): - """检查并执行入场""" + """ + 检查并执行入场 + 【开单入口函数】此函数负责判断是否开单以及开单方向 + - 开多:价格低于EMA超过阈值时 + - 开空:价格高于EMA超过阈值时 + """ if self.pos != 0: return if self.in_cooldown(): @@ -653,15 +672,21 @@ class BitmartFuturesMeanReversionBot: f"size={size}, penalty={penalty_active}, last_sl_dir={self.last_sl_dir}" ) + # ========== 【开单位置1】开多单 ========== + # 方向:多(做多,买入) + # 条件:价格偏离EMA向下超过阈值 (dev <= long_th) if dev <= long_th: - if self.place_market_order(1, size): + if self.place_market_order(1, size): # side=1 表示开多 self.pos = 1 self.entry_price = price self.entry_ts = time.time() self.ding(f"✅开多:dev={dev * 100:.3f}% size={size} entry={price:.2f}") + # ========== 【开单位置2】开空单 ========== + # 方向:空(做空,卖出) + # 条件:价格偏离EMA向上超过阈值 (dev >= short_th) elif dev >= short_th: - if self.place_market_order(4, size): + if self.place_market_order(4, size): # side=4 表示开空 self.pos = -1 self.entry_price = price self.entry_ts = time.time() @@ -750,12 +775,103 @@ class BitmartFuturesMeanReversionBot: ) self.ding(msg) + def openBrowser(self): + """打开 TGE 对应浏览器实例""" + try: + res = requests.post( + f"{self.tge_url}/api/browser/start", + json={"envId": self.tge_id}, + headers=self.tge_headers + ) + self.tge_port = res.json()["data"]["port"] + return True + except: + return False + + def take_over_browser(self): + """接管浏览器""" + try: + co = ChromiumOptions() + co.set_local_port(self.tge_port) + self.page = ChromiumPage(addr_or_opts=co) + self.page.set.window.max() + return True + except: + return False + + def close_extra_tabs(self): + """关闭多余 tab""" + try: + for idx, tab in enumerate(self.page.get_tabs()): + if idx > 0: + tab.close() + return True + except: + return False + + def click_safe(self, xpath, sleep=0.5): + """安全点击""" + try: + ele = self.page.ele(xpath) + if not ele: + return False + ele.scroll.to_see(center=True) + time.sleep(sleep) + ele.click() + return True + except: + return False + + def 平仓(self): + self.click_safe('x://span[normalize-space(text()) ="市价"]') + + def 开单(self, marketPriceLongOrder=0, limitPriceShortOrder=0, size=None, price=None): + """ + marketPriceLongOrder 市价最多或者做空,1是做多,-1是做空 + limitPriceShortOrder 限价最多或者做空 + """ + + if marketPriceLongOrder == -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 marketPriceLongOrder == 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()) ="买入/做多"]') + + if limitPriceShortOrder == -1: + self.click_safe('x://button[normalize-space(text()) ="限价"]') + self.page.ele('x://*[@id="price_0"]').input(vals=price, clear=True) + time.sleep(1) + self.page.ele('x://*[@id="size_0"]').input(1) + self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]') + elif limitPriceShortOrder == 1: + self.click_safe('x://button[normalize-space(text()) ="限价"]') + self.page.ele('x://*[@id="price_0"]').input(vals=price, clear=True) + time.sleep(1) + self.page.ele('x://*[@id="size_0"]').input(1) + self.click_safe('x://span[normalize-space(text()) ="买入/做多"]') + def action(self): """主循环""" if not self.set_leverage(): self.ding("杠杆设置失败,停止运行", error=True) return + # 1. 打开浏览器 + if not self.openBrowser(): + self.ding("打开 TGE 失败!", error=True) + return + logger.info("TGE 端口获取成功") + + # 2. 接管浏览器 + if not self.take_over_browser(): + self.ding("接管浏览器失败!", error=True) + return + logger.info("浏览器接管成功") + self.page.get("https://derivatives.bitmart.com/zh-CN/futures/ETHUSDT") + while True: now_dt = datetime.datetime.now() self.pbar.n = now_dt.second @@ -847,7 +963,7 @@ if __name__ == "__main__": export BITMART_MEMO "合约交易" """ cfg = StrategyConfig() - bot = BitmartFuturesMeanReversionBot(cfg) + bot = BitmartFuturesMeanReversionBot(cfg, tge_id=196495) # 设置日志级别为INFO以便查看详细计算过程 logger.remove() @@ -862,5 +978,3 @@ if __name__ == "__main__": logger.error(f"程序异常退出: {e}") bot.ding(f"❌ 策略异常退出: {e}", error=True) raise - -# 目前动态计算阀值的速度是多少 \ No newline at end of file