fewfef
This commit is contained in:
@@ -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
|
||||
|
||||
# 目前动态计算阀值的速度是多少
|
||||
Reference in New Issue
Block a user