2026-03-05 12:51:12 +08:00
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
|
|
from tqdm import tqdm
|
|
|
|
|
|
from loguru import logger
|
|
|
|
|
|
from bit_tools import openBrowser
|
|
|
|
|
|
from DrissionPage import ChromiumPage
|
|
|
|
|
|
from DrissionPage import ChromiumOptions
|
|
|
|
|
|
|
|
|
|
|
|
from bitmart.api_contract import APIContract
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BitmartFuturesTransaction:
|
|
|
|
|
|
def __init__(self, bit_id):
|
|
|
|
|
|
|
|
|
|
|
|
self.page: ChromiumPage | None = None
|
|
|
|
|
|
|
|
|
|
|
|
self.api_key = "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8"
|
|
|
|
|
|
self.secret_key = "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5"
|
|
|
|
|
|
self.memo = "合约交易"
|
|
|
|
|
|
|
|
|
|
|
|
self.contract_symbol = "ETHUSDT"
|
|
|
|
|
|
|
|
|
|
|
|
self.contractAPI = APIContract(self.api_key, self.secret_key, self.memo, timeout=(5, 15))
|
|
|
|
|
|
|
|
|
|
|
|
self.start = 0 # 持仓状态: -1 空, 0 无, 1 多
|
|
|
|
|
|
self.direction = None
|
|
|
|
|
|
|
|
|
|
|
|
self.pbar = tqdm(total=30, desc="等待K线", ncols=80)
|
|
|
|
|
|
|
|
|
|
|
|
self.last_kline_time = None
|
|
|
|
|
|
|
|
|
|
|
|
self.leverage = "100" # 高杠杆(全仓模式下可开更大仓位)
|
|
|
|
|
|
self.open_type = "cross" # 全仓模式(你的“成本开仓”需求)
|
|
|
|
|
|
self.risk_percent = 0.01 # 每次开仓使用可用余额的 1%
|
|
|
|
|
|
|
|
|
|
|
|
self.open_avg_price = None # 开仓价格
|
|
|
|
|
|
self.current_amount = None # 持仓量
|
|
|
|
|
|
|
|
|
|
|
|
self.bit_id = bit_id
|
|
|
|
|
|
|
|
|
|
|
|
def get_klines(self):
|
|
|
|
|
|
"""获取最近3根30分钟K线(step=30)"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
end_time = int(time.time())
|
|
|
|
|
|
# 获取足够多的条目确保有最新3根
|
|
|
|
|
|
response = self.contractAPI.get_kline(
|
|
|
|
|
|
contract_symbol=self.contract_symbol,
|
|
|
|
|
|
step=30, # 30分钟
|
|
|
|
|
|
start_time=end_time - 3600 * 10, # 取最近10小时
|
|
|
|
|
|
end_time=end_time
|
|
|
|
|
|
)[0]["data"]
|
|
|
|
|
|
|
|
|
|
|
|
# 每根: [timestamp, open, high, low, close, volume]
|
|
|
|
|
|
formatted = []
|
|
|
|
|
|
for k in response:
|
|
|
|
|
|
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 # 最近3根: kline_1 (最老), kline_2, kline_3 (最新)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"获取K线异常: {e}")
|
|
|
|
|
|
self.ding(error=True, msg="获取K线异常")
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def get_current_price(self):
|
|
|
|
|
|
"""获取当前最新价格,用于计算张数"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
end_time = int(time.time())
|
|
|
|
|
|
response = self.contractAPI.get_kline(
|
|
|
|
|
|
contract_symbol=self.contract_symbol,
|
|
|
|
|
|
step=1, # 1分钟
|
|
|
|
|
|
start_time=end_time - 3600 * 3, # 取最近10小时
|
|
|
|
|
|
end_time=end_time
|
|
|
|
|
|
)[0]
|
|
|
|
|
|
if response['code'] == 1000:
|
|
|
|
|
|
return float(response['data'][-1]["close_price"])
|
|
|
|
|
|
return None
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"获取价格异常: {e}")
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def get_available_balance(self):
|
|
|
|
|
|
"""获取合约账户可用USDT余额"""
|
|
|
|
|
|
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))
|
|
|
|
|
|
return None
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"余额查询异常: {e}")
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
# 获取当前持仓方向
|
|
|
|
|
|
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
|
|
|
|
|
|
return True
|
|
|
|
|
|
self.start = 1 if positions[0]['position_type'] == 1 else -1
|
|
|
|
|
|
self.open_avg_price = positions[0]['open_avg_price']
|
|
|
|
|
|
self.current_amount = positions[0]['current_amount']
|
|
|
|
|
|
self.position_cross = positions[0]["position_cross"]
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"持仓查询异常: {e}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
# 设置杠杆和全仓
|
|
|
|
|
|
def set_leverage(self):
|
|
|
|
|
|
"""程序启动时设置全仓 + 高杠杆"""
|
|
|
|
|
|
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 杠杆设置成功")
|
|
|
|
|
|
return True
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.error(f"杠杆设置失败: {response}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"设置杠杆异常: {e}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def openBrowser(self):
|
|
|
|
|
|
"""打开 TGE 对应浏览器实例"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
bit_port = openBrowser(id=self.bit_id)
|
|
|
|
|
|
co = ChromiumOptions()
|
|
|
|
|
|
co.set_local_port(port=bit_port)
|
|
|
|
|
|
self.page = ChromiumPage(addr_or_opts=co)
|
|
|
|
|
|
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):
|
|
|
|
|
|
self.click_safe('x://button[normalize-space(text()) ="平仓"]')
|
|
|
|
|
|
self.click_safe('x://*[@id="futureTradeForm"]/div[5]/div[3]/div[3]/span[3]')
|
|
|
|
|
|
self.click_safe('x://span[normalize-space(text()) ="卖出/平多"]')
|
|
|
|
|
|
|
|
|
|
|
|
def 平一半空仓(self):
|
|
|
|
|
|
self.click_safe('x://button[normalize-space(text()) ="平仓"]')
|
|
|
|
|
|
self.click_safe('x://*[@id="futureTradeForm"]/div[5]/div[3]/div[3]/span[3]')
|
|
|
|
|
|
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 ding(self, text, error=False):
|
|
|
|
|
|
logger.info(text)
|
|
|
|
|
|
|
|
|
|
|
|
def action(self):
|
|
|
|
|
|
# 启动时设置全仓高杠杆
|
|
|
|
|
|
if not self.set_leverage():
|
|
|
|
|
|
logger.error("杠杆设置失败,程序继续运行但可能下单失败")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 1. 打开浏览器
|
|
|
|
|
|
if not self.openBrowser():
|
|
|
|
|
|
self.ding("打开 TGE 失败!", error=True)
|
|
|
|
|
|
return
|
|
|
|
|
|
logger.info("TGE 端口获取成功")
|
|
|
|
|
|
|
|
|
|
|
|
self.get_klines()
|
|
|
|
|
|
|
|
|
|
|
|
# self.close_extra_tabs()
|
|
|
|
|
|
# self.page.get("https://derivatives.bitmart.com/zh-CN/futures/ETHUSDT")
|
|
|
|
|
|
#
|
|
|
|
|
|
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
|
|
|
|
|
# self.click_safe('x://button[normalize-space(text()) ="限价"]')
|
|
|
|
|
|
#
|
|
|
|
|
|
# self.page.ele('x://*[@id="price_0"]').input(vals=3000, clear=True)
|
|
|
|
|
|
# self.page.ele('x://*[@id="size_0"]').input(1)
|
|
|
|
|
|
# self.click_safe('x://span[normalize-space(text()) ="买入/做多"]')
|
|
|
|
|
|
# self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]')
|
|
|
|
|
|
|
2026-03-05 15:51:31 +08:00
|
|
|
|
self.click_safe('x://button[normalize-space(text()) ="开仓"]')
|
2026-03-05 12:51:12 +08:00
|
|
|
|
self.click_safe('x://button[normalize-space(text()) ="平仓"]')
|
|
|
|
|
|
self.click_safe('x://span[normalize-space(text()) ="买入/平空"]')
|
|
|
|
|
|
self.click_safe('x://span[normalize-space(text()) ="卖出/平多"]')
|
|
|
|
|
|
self.click_safe('x://*[@id="futureTradeForm"]/div[5]/div[3]/div[3]/span[3]')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
|
BitmartFuturesTransaction(bit_id="f2320f57e24c45529a009e1541e25961").action()
|