This commit is contained in:
27942
2025-12-25 14:28:11 +08:00
parent b32fca08ab
commit eb537ac3c5

View File

@@ -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
# 目前动态计算阀值的速度是多少