Files
lm_code/test.py
2025-12-19 19:16:41 +08:00

300 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import time
import uuid
import datetime
from tqdm import tqdm
from loguru import logger
from bitmart.api_contract import APIContract
from bitmart.lib.cloud_exceptions import APIException
from 交易.tools import send_dingtalk_message
class BitmartMarketMaker:
def __init__(self):
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.open_avg_price = None # 开仓均价字符串或float
self.current_amount = 0
self.position_cross = None
self.pbar = tqdm(total=10, desc="等待下次检查", ncols=80)
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.take_profit_pct = 2.0 # 止盈 2%
self.stop_loss_pct = 1.0 # 止损 1%(可根据风险偏好调整)
self.price_precision = 0.1
self.leverage_set = False
self.max_retries = 5
def ding(self, msg, error=False):
prefix = "❌bitmart MM" if error else "🔔bitmart MM"
if error:
for i in range(10):
send_dingtalk_message(f"{prefix}{msg}")
else:
send_dingtalk_message(f"{prefix}{msg}")
def try_set_leverage(self):
if self.leverage_set:
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)
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 get_available_balance(self):
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 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: 平空
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}")
return False
except APIException as e:
logger.error(f"API平仓异常: {e}")
return False
def action(self):
logger.info("程序启动,将在循环中动态尝试设置杠杆")
while True:
if not self.try_set_leverage():
logger.warning("杠杆未设置成功,继续重试...")
if not self.get_position_status():
self.ding(error=True, msg="获取仓位信息失败!!!")
time.sleep(10)
continue
# 获取当前价格(使用中价)
best_bid, best_ask = self.get_depth()
if best_bid is None or best_ask is None:
time.sleep(10)
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)
# 平仓后继续下一轮(重新挂单)
# 检查库存阈值
if abs(self.current_amount) > self.max_position_threshold:
self.close_position(reason="库存阈值")
# 挂单
bid_price = mid_price - self.spread_offset
ask_price = mid_price + self.spread_offset
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)
if __name__ == '__main__':
BitmartMarketMaker().action()