292 lines
11 KiB
Python
292 lines
11 KiB
Python
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 BitmartFuturesTransaction:
|
||
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.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 # 持仓量
|
||
|
||
def is_bullish(self, c):
|
||
return float(c['close']) > float(c['open'])
|
||
|
||
def is_bearish(self, c):
|
||
return float(c['close']) < float(c['open'])
|
||
|
||
def is_trending(self, klines):
|
||
"""判断是否为单边行情,通过布林带或RSI"""
|
||
close_prices = [kline['close'] for kline in klines]
|
||
rsi_value = self.calculate_rsi(close_prices, 14) # 使用14期的RSI
|
||
if rsi_value > 70 or rsi_value < 30:
|
||
return True # 单边行情
|
||
return False # 震荡行情
|
||
|
||
def calculate_rsi(self, prices, period=14):
|
||
"""计算RSI指标"""
|
||
deltas = [prices[i] - prices[i - 1] for i in range(1, len(prices))]
|
||
gains = [delta if delta > 0 else 0 for delta in deltas]
|
||
losses = [-delta if delta < 0 else 0 for delta in deltas]
|
||
|
||
avg_gain = sum(gains[:period]) / period
|
||
avg_loss = sum(losses[:period]) / period
|
||
|
||
rs = avg_gain / avg_loss if avg_loss != 0 else 0
|
||
rsi = 100 - (100 / (1 + rs))
|
||
return rsi
|
||
|
||
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 * 10, # 取最近10小时
|
||
end_time=end_time
|
||
)[0]
|
||
if response['code'] == 1000:
|
||
return float(response['data'][0]["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 calculate_size(self):
|
||
"""计算开仓张数:使用可用余额的1%作为保证金"""
|
||
balance = self.get_available_balance()
|
||
self.balance = balance
|
||
if not balance or balance < 10:
|
||
logger.warning("余额不足,无法开仓")
|
||
return 0
|
||
|
||
price = self.get_current_price()
|
||
if not price:
|
||
price = 3000 # 保守估计,避免size过大
|
||
|
||
leverage = int(self.leverage)
|
||
|
||
margin = balance * self.risk_percent # 使用1%余额
|
||
# ETHUSDT 1张 ≈ 0.001 ETH
|
||
size = int((margin * leverage) / (price * 0.001))
|
||
size = max(1, size)
|
||
|
||
logger.info(f"余额 {balance:.2f} USDT → 使用 {margin:.2f} USDT (1%) → 开仓 {size} 张 (价格≈{price})")
|
||
return size
|
||
|
||
def place_market_order(self, side: int, size: int):
|
||
if size <= 0:
|
||
return False
|
||
|
||
client_order_id = f"auto_{int(time.time())}_{uuid.uuid4().hex[:8]}"
|
||
|
||
try:
|
||
response = self.contractAPI.post_submit_order(
|
||
contract_symbol=self.contract_symbol,
|
||
client_order_id=client_order_id,
|
||
side=side,
|
||
mode=1,
|
||
type='market',
|
||
leverage=self.leverage,
|
||
open_type=self.open_type,
|
||
size=size
|
||
)[0]
|
||
|
||
if response['code'] == 1000:
|
||
logger.success(
|
||
f"下单成功: {'开多' if side in [1] else '开空' if side in [4] else '平多' if side in [3] else '平空'} {size}张")
|
||
return True
|
||
else:
|
||
logger.error(f"下单失败: {response}")
|
||
return False
|
||
except APIException as e:
|
||
logger.error(f"API下单异常: {e}")
|
||
return False
|
||
|
||
def check_signal(self, prev, curr):
|
||
"""简化英戈尔夫形态"""
|
||
if self.is_bullish(curr) and self.is_bearish(prev) and float(curr['close']) >= float(prev['open']):
|
||
return "long"
|
||
if self.is_bearish(curr) and self.is_bullish(prev) and float(curr['close']) <= float(prev['open']):
|
||
return "short"
|
||
return None
|
||
|
||
def execute_trade(self):
|
||
"""执行交易逻辑,根据市场状态切换策略"""
|
||
klines = self.get_klines()
|
||
if not klines or len(klines) < 3:
|
||
return
|
||
|
||
if self.is_trending(klines): # 单边行情
|
||
self.direction = self.check_signal(klines[1], klines[2])
|
||
if self.direction:
|
||
logger.success(f"检测到{self.direction}信号,准备开仓(用余额1%)")
|
||
self.execute_trade() # 执行趋势跟随交易
|
||
|
||
else: # 震荡行情
|
||
self.execute_grid_trade() # 执行网格交易策略
|
||
|
||
def execute_grid_trade(self):
|
||
"""网格交易策略"""
|
||
logger.info("开始网格交易")
|
||
|
||
# 获取当前价格
|
||
current_price = self.get_current_price()
|
||
if not current_price:
|
||
logger.error("无法获取当前价格,网格交易无法执行")
|
||
return
|
||
|
||
# 假设的网格区间(可以根据需要调整)
|
||
grid_step = 10 # 每次10USDT为一个网格
|
||
grid_size = 1 # 每次开仓数量,单位ETH
|
||
|
||
# 计算上网格和下网格价格
|
||
lower_price = current_price - grid_step # 下网格价格
|
||
upper_price = current_price + grid_step # 上网格价格
|
||
|
||
# 生成买卖网格订单
|
||
try:
|
||
# 设置买单
|
||
buy_order = self.place_market_order(side=1, size=grid_size) # 开多
|
||
if buy_order:
|
||
logger.info(f"已成功设置买单,买入价格:{lower_price},数量:{grid_size} ETH")
|
||
|
||
# 设置卖单
|
||
sell_order = self.place_market_order(side=4, size=grid_size) # 开空
|
||
if sell_order:
|
||
logger.info(f"已成功设置卖单,卖出价格:{upper_price},数量:{grid_size} ETH")
|
||
|
||
except Exception as e:
|
||
logger.error(f"网格交易下单失败: {e}")
|
||
|
||
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 action(self):
|
||
# 启动时设置全仓高杠杆
|
||
if not self.set_leverage():
|
||
logger.error("杠杆设置失败,程序继续运行但可能下单失败")
|
||
return
|
||
|
||
while True:
|
||
current_minute = datetime.datetime.now().minute
|
||
if current_minute < 30:
|
||
self.pbar.n = current_minute
|
||
else:
|
||
self.pbar.n = current_minute - 30
|
||
self.pbar.refresh()
|
||
|
||
self.execute_trade()
|
||
time.sleep(2) # 高频交易,减少等待时间
|
||
|
||
|
||
if __name__ == '__main__':
|
||
BitmartFuturesTransaction().action()
|