692 lines
23 KiB
Python
692 lines
23 KiB
Python
"""
|
||
Bitget UTA 数据服务 - 获取加密货币 K 线数据和技术指标
|
||
使用 requests 直接调用 REST API
|
||
"""
|
||
import pandas as pd
|
||
import numpy as np
|
||
import requests
|
||
from typing import Dict, List, Optional, Any
|
||
from app.utils.logger import logger
|
||
|
||
|
||
class BitgetService:
|
||
"""Bitget UTA 数据服务(使用 requests 直接调用 REST API)"""
|
||
|
||
# K线周期映射 - 注意 Bitget 使用大写 H
|
||
INTERVALS = {
|
||
'5m': '5m',
|
||
'15m': '15m',
|
||
'1h': '1H', # Bitget 大写
|
||
'4h': '4H' # Bitget 大写
|
||
}
|
||
|
||
# Bitget API 基础 URL
|
||
BASE_URL = "https://api.bitget.com"
|
||
TESTNET_URL = "https://api-testnet.bitget.com"
|
||
|
||
# 产品类型
|
||
CATEGORY_SPOT = 'SPOT'
|
||
CATEGORY_USDT_FUTURES = 'USDT-FUTURES'
|
||
CATEGORY_COIN_FUTURES = 'COIN-FUTURES'
|
||
CATEGORY_USDC_FUTURES = 'USDC-FUTURES'
|
||
|
||
def __init__(self, api_key: str = "", api_secret: str = "", use_testnet: bool = False):
|
||
"""
|
||
初始化 Bitget 服务
|
||
|
||
Args:
|
||
api_key: API 密钥(可选,公开数据不需要)
|
||
api_secret: API 密钥(可选)
|
||
use_testnet: 是否使用测试网
|
||
"""
|
||
self._api_key = api_key
|
||
self._api_secret = api_secret
|
||
self._base_url = self.TESTNET_URL if use_testnet else self.BASE_URL
|
||
self._session = requests.Session()
|
||
if api_key:
|
||
self._session.headers.update({
|
||
'ACCESS-KEY': api_key,
|
||
'ACCESS-SIGN': api_secret
|
||
})
|
||
logger.info(f"Bitget 服务初始化完成 ({'测试网' if use_testnet else '生产网'})")
|
||
|
||
def get_klines(self, symbol: str, interval: str, limit: int = 100,
|
||
category: str = None) -> pd.DataFrame:
|
||
"""
|
||
获取 K 线数据
|
||
|
||
Args:
|
||
symbol: 交易对,如 'BTCUSDT'
|
||
interval: K线周期,如 '5m', '15m', '1h', '4h'
|
||
limit: 获取数量(最大1000)
|
||
category: 产品类型,默认 USDT-FUTURES
|
||
|
||
Returns:
|
||
DataFrame 包含 OHLCV 数据
|
||
"""
|
||
try:
|
||
if category is None:
|
||
category = self.CATEGORY_USDT_FUTURES
|
||
|
||
bitget_interval = self.INTERVALS.get(interval, interval)
|
||
url = f"{self._base_url}/api/v3/market/candles"
|
||
params = {
|
||
'category': category,
|
||
'symbol': symbol,
|
||
'interval': bitget_interval,
|
||
'limit': str(min(limit, 1000)) # Bitget 最大 1000
|
||
}
|
||
|
||
response = self._session.get(url, params=params, timeout=10)
|
||
response.raise_for_status()
|
||
result = response.json()
|
||
|
||
# Bitget 返回格式: {"code": "00000", "msg": "success", "requestTime": ..., "data": [...]}
|
||
if result.get('code') != '00000':
|
||
logger.error(f"Bitget API 错误: {result.get('msg')}")
|
||
return pd.DataFrame()
|
||
|
||
klines = result.get('data', [])
|
||
return self._parse_klines(klines)
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取 {symbol} {interval} K线数据失败: {e}")
|
||
return pd.DataFrame()
|
||
|
||
def get_multi_timeframe_data(self, symbol: str,
|
||
category: str = None) -> Dict[str, pd.DataFrame]:
|
||
"""
|
||
获取多周期 K 线数据
|
||
|
||
Args:
|
||
symbol: 交易对
|
||
category: 产品类型,默认 USDT-FUTURES
|
||
|
||
Returns:
|
||
包含 5m, 15m, 1h, 4h 数据的字典
|
||
"""
|
||
# 不同周期使用不同的数据量,平衡分析深度和性能
|
||
# 5m: 200根 = 16.7小时(日内分析)
|
||
# 15m: 200根 = 2.1天(短线分析)
|
||
# 1h: 300根 = 12.5天(中线分析)
|
||
# 4h: 200根 = 33.3天(趋势分析)
|
||
limits = {
|
||
'5m': 200,
|
||
'15m': 200,
|
||
'1h': 300,
|
||
'4h': 200
|
||
}
|
||
|
||
data = {}
|
||
for interval in ['5m', '15m', '1h', '4h']:
|
||
df = self.get_klines(symbol, interval, limit=limits.get(interval, 100),
|
||
category=category)
|
||
if not df.empty:
|
||
df = self.calculate_indicators(df, interval)
|
||
data[interval] = df
|
||
|
||
logger.info(f"获取 {symbol} 多周期数据完成")
|
||
return data
|
||
|
||
def _parse_klines(self, klines: List) -> pd.DataFrame:
|
||
"""
|
||
解析 K 线数据为 DataFrame
|
||
|
||
Bitget 返回格式: [timestamp, open, high, low, close, base_volume, quote_volume]
|
||
"""
|
||
if not klines:
|
||
return pd.DataFrame()
|
||
|
||
df = pd.DataFrame(klines, columns=[
|
||
'open_time', 'open', 'high', 'low', 'close',
|
||
'base_volume', 'quote_volume'
|
||
])
|
||
|
||
# 转换数据类型
|
||
df['open_time'] = pd.to_datetime(df['open_time'].astype(int), unit='ms')
|
||
|
||
for col in ['open', 'high', 'low', 'close', 'base_volume', 'quote_volume']:
|
||
df[col] = df[col].astype(float)
|
||
|
||
# 重命名以匹配 Binance 格式
|
||
df = df.rename(columns={'base_volume': 'volume'})
|
||
|
||
# 只保留需要的列(与 Binance 保持一致)
|
||
df = df[['open_time', 'open', 'high', 'low', 'close', 'volume']]
|
||
|
||
# 按时间排序(Bitget 返回的数据可能是倒序)
|
||
df = df.sort_values('open_time').reset_index(drop=True)
|
||
|
||
return df
|
||
|
||
def calculate_indicators(self, df: pd.DataFrame, interval: str = '1h') -> pd.DataFrame:
|
||
"""
|
||
计算技术指标
|
||
|
||
Args:
|
||
df: K线数据 DataFrame
|
||
interval: K线周期,用于调整 MA 参数
|
||
|
||
Returns:
|
||
添加了技术指标的 DataFrame
|
||
"""
|
||
if df.empty:
|
||
return df
|
||
|
||
# 根据周期调整 MA 参数
|
||
ma_config = {
|
||
'5m': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50},
|
||
'15m': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50},
|
||
'1h': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50},
|
||
'4h': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50},
|
||
}
|
||
|
||
config = ma_config.get(interval, ma_config['1h'])
|
||
|
||
# 移动平均线
|
||
df['ma5'] = self._calculate_ma(df['close'], config['ma_short'])
|
||
df['ma10'] = self._calculate_ma(df['close'], config['ma_mid'])
|
||
df['ma20'] = self._calculate_ma(df['close'], config['ma_long'])
|
||
df['ma50'] = self._calculate_ma(df['close'], config['ma_extra'])
|
||
|
||
# EMA
|
||
df['ema12'] = self._calculate_ema(df['close'], 12)
|
||
df['ema26'] = self._calculate_ema(df['close'], 26)
|
||
|
||
# RSI
|
||
df['rsi'] = self._calculate_rsi(df['close'], 14)
|
||
|
||
# MACD
|
||
df['macd'], df['macd_signal'], df['macd_hist'] = self._calculate_macd(df['close'])
|
||
|
||
# 布林带
|
||
df['bb_upper'], df['bb_middle'], df['bb_lower'] = self._calculate_bollinger(df['close'])
|
||
|
||
# KDJ
|
||
df['k'], df['d'], df['j'] = self._calculate_kdj(df['high'], df['low'], df['close'])
|
||
|
||
# ATR
|
||
df['atr'] = self._calculate_atr(df['high'], df['low'], df['close'])
|
||
|
||
# 成交量均线
|
||
df['volume_ma5'] = self._calculate_ma(df['volume'], 5)
|
||
df['volume_ma20'] = self._calculate_ma(df['volume'], 20)
|
||
df['volume_ratio'] = df['volume'] / df['volume_ma20']
|
||
|
||
return df
|
||
|
||
@staticmethod
|
||
def _calculate_ma(data: pd.Series, period: int) -> pd.Series:
|
||
"""简单移动平均线"""
|
||
return data.rolling(window=period).mean()
|
||
|
||
@staticmethod
|
||
def _calculate_ema(data: pd.Series, period: int) -> pd.Series:
|
||
"""指数移动平均线"""
|
||
return data.ewm(span=period, adjust=False).mean()
|
||
|
||
@staticmethod
|
||
def _calculate_rsi(data: pd.Series, period: int = 14) -> pd.Series:
|
||
"""RSI 指标 - 使用 Wilder's Smoothing 方法"""
|
||
delta = data.diff()
|
||
|
||
gain = delta.where(delta > 0, 0)
|
||
loss = -delta.where(delta < 0, 0)
|
||
|
||
avg_gain = gain.ewm(alpha=1/period, adjust=False).mean()
|
||
avg_loss = loss.ewm(alpha=1/period, adjust=False).mean()
|
||
|
||
rs = avg_gain / avg_loss
|
||
rsi = 100 - (100 / (1 + rs))
|
||
|
||
return rsi
|
||
|
||
@staticmethod
|
||
def _calculate_macd(data: pd.Series, fast: int = 12, slow: int = 26, signal: int = 9):
|
||
"""MACD 指标"""
|
||
ema_fast = data.ewm(span=fast, adjust=False).mean()
|
||
ema_slow = data.ewm(span=slow, adjust=False).mean()
|
||
|
||
macd = ema_fast - ema_slow
|
||
signal_line = macd.ewm(span=signal, adjust=False).mean()
|
||
histogram = macd - signal_line
|
||
|
||
return macd, signal_line, histogram
|
||
|
||
@staticmethod
|
||
def _calculate_bollinger(data: pd.Series, period: int = 20, std_dev: float = 2.0):
|
||
"""布林带"""
|
||
middle = data.rolling(window=period).mean()
|
||
std = data.rolling(window=period).std()
|
||
|
||
upper = middle + (std * std_dev)
|
||
lower = middle - (std * std_dev)
|
||
|
||
return upper, middle, lower
|
||
|
||
@staticmethod
|
||
def _calculate_kdj(high: pd.Series, low: pd.Series, close: pd.Series,
|
||
period: int = 9, k_period: int = 3, d_period: int = 3):
|
||
"""KDJ 指标"""
|
||
low_min = low.rolling(window=period).min()
|
||
high_max = high.rolling(window=period).max()
|
||
|
||
rsv = (close - low_min) / (high_max - low_min) * 100
|
||
|
||
k = rsv.ewm(com=k_period - 1, adjust=False).mean()
|
||
d = k.ewm(com=d_period - 1, adjust=False).mean()
|
||
j = 3 * k - 2 * d
|
||
|
||
return k, d, j
|
||
|
||
@staticmethod
|
||
def _calculate_atr(high: pd.Series, low: pd.Series, close: pd.Series, period: int = 14):
|
||
"""ATR 平均真实波幅"""
|
||
tr1 = high - low
|
||
tr2 = abs(high - close.shift())
|
||
tr3 = abs(low - close.shift())
|
||
|
||
tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
|
||
atr = tr.rolling(window=period).mean()
|
||
|
||
return atr
|
||
|
||
def get_current_price(self, symbol: str, category: str = None) -> Optional[float]:
|
||
"""
|
||
获取当前价格
|
||
|
||
Args:
|
||
symbol: 交易对
|
||
category: 产品类型,默认 USDT-FUTURES
|
||
|
||
Returns:
|
||
当前价格
|
||
"""
|
||
try:
|
||
if category is None:
|
||
category = self.CATEGORY_USDT_FUTURES
|
||
|
||
url = f"{self._base_url}/api/v3/market/tickers"
|
||
params = {
|
||
'category': category,
|
||
'symbol': symbol
|
||
}
|
||
|
||
response = self._session.get(url, params=params, timeout=10)
|
||
response.raise_for_status()
|
||
result = response.json()
|
||
|
||
if result.get('code') != '00000':
|
||
logger.error(f"Bitget API 错误: {result.get('msg')}")
|
||
return None
|
||
|
||
data = result.get('data', [])
|
||
if not data:
|
||
return None
|
||
|
||
# 返回第一个(也是唯一的)ticker 的最新价格
|
||
return float(data[0].get('lastPrice', 0))
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取 {symbol} 当前价格失败: {e}")
|
||
return None
|
||
|
||
def get_ticker(self, symbol: str, category: str = None) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
获取完整的 Ticker 数据
|
||
|
||
Args:
|
||
symbol: 交易对
|
||
category: 产品类型,默认 USDT-FUTURES
|
||
|
||
Returns:
|
||
完整的 ticker 数据
|
||
"""
|
||
try:
|
||
if category is None:
|
||
category = self.CATEGORY_USDT_FUTURES
|
||
|
||
url = f"{self._base_url}/api/v3/market/tickers"
|
||
params = {
|
||
'category': category,
|
||
'symbol': symbol
|
||
}
|
||
|
||
response = self._session.get(url, params=params, timeout=10)
|
||
response.raise_for_status()
|
||
result = response.json()
|
||
|
||
if result.get('code') != '00000':
|
||
logger.error(f"Bitget API 错误: {result.get('msg')}")
|
||
return None
|
||
|
||
data = result.get('data', [])
|
||
if not data:
|
||
return None
|
||
|
||
return data[0]
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取 {symbol} ticker 失败: {e}")
|
||
return None
|
||
|
||
def get_symbol_info(self, symbol: str, category: str = None) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
获取交易对信息(包含精度配置)
|
||
|
||
Args:
|
||
symbol: 交易对
|
||
category: 产品类型,默认 USDT-FUTURES
|
||
|
||
Returns:
|
||
交易对信息,包含 pricePrecision 和 quantityPrecision
|
||
"""
|
||
try:
|
||
if category is None:
|
||
category = self.CATEGORY_USDT_FUTURES
|
||
|
||
url = f"{self._base_url}/api/v3/market/symbols"
|
||
params = {
|
||
'category': category,
|
||
'symbol': symbol
|
||
}
|
||
|
||
response = self._session.get(url, params=params, timeout=10)
|
||
response.raise_for_status()
|
||
result = response.json()
|
||
|
||
if result.get('code') != '00000':
|
||
logger.error(f"Bitget API 错误: {result.get('msg')}")
|
||
return None
|
||
|
||
data = result.get('data', [])
|
||
if not data:
|
||
return None
|
||
|
||
symbol_data = data[0]
|
||
|
||
# 返回精度信息
|
||
return {
|
||
'symbol': symbol_data.get('symbol'),
|
||
'status': symbol_data.get('status'),
|
||
'pricePrecision': int(symbol_data.get('pricePrecision', 2)),
|
||
'quantityPrecision': int(symbol_data.get('quantityPrecision', 2)),
|
||
'minTradeAmount': float(symbol_data.get('minTradeAmount', 0)),
|
||
'maxTradeAmount': float(symbol_data.get('maxTradeAmount', 0)),
|
||
'takerFeeRate': float(symbol_data.get('takerFeeRate', 0.001)),
|
||
'makerFeeRate': float(symbol_data.get('makerFeeRate', 0.001)),
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取 {symbol} 交易对信息失败: {e}")
|
||
return None
|
||
|
||
# 缓存交易对精度信息
|
||
_symbol_precision_cache: Dict[str, Dict[str, int]] = {}
|
||
|
||
def get_precision(self, symbol: str, category: str = None) -> Dict[str, int]:
|
||
"""
|
||
获取交易对价格和数量精度(带缓存)
|
||
|
||
Args:
|
||
symbol: 交易对
|
||
category: 产品类型,默认 USDT-FUTURES
|
||
|
||
Returns:
|
||
{'pricePrecision': 2, 'quantityPrecision': 2}
|
||
"""
|
||
# 检查缓存
|
||
if symbol in self._symbol_precision_cache:
|
||
return self._symbol_precision_cache[symbol]
|
||
|
||
# 获取交易对信息
|
||
info = self.get_symbol_info(symbol, category)
|
||
if info:
|
||
precision = {
|
||
'pricePrecision': info['pricePrecision'],
|
||
'quantityPrecision': info['quantityPrecision']
|
||
}
|
||
# 缓存结果
|
||
self._symbol_precision_cache[symbol] = precision
|
||
return precision
|
||
|
||
# 默认精度
|
||
return {'pricePrecision': 2, 'quantityPrecision': 2}
|
||
|
||
def round_price(self, symbol: str, price: float, category: str = None) -> float:
|
||
"""
|
||
根据交易对精度四舍五入价格
|
||
|
||
Args:
|
||
symbol: 交易对
|
||
price: 原始价格
|
||
category: 产品类型
|
||
|
||
Returns:
|
||
四舍五入后的价格
|
||
"""
|
||
precision = self.get_precision(symbol, category)
|
||
return round(price, precision['pricePrecision'])
|
||
|
||
def round_quantity(self, symbol: str, quantity: float, category: str = None) -> float:
|
||
"""
|
||
根据交易对精度四舍五入数量
|
||
|
||
Args:
|
||
symbol: 交易对
|
||
quantity: 原始数量
|
||
category: 产品类型
|
||
|
||
Returns:
|
||
四舍五入后的数量
|
||
"""
|
||
precision = self.get_precision(symbol, category)
|
||
return round(quantity, precision['quantityPrecision'])
|
||
|
||
def get_funding_rate(self, symbol: str) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
获取资金费率(包含标记价格和指数价格)
|
||
|
||
Args:
|
||
symbol: 交易对,如 'BTCUSDT'
|
||
|
||
Returns:
|
||
包含资金费率、标记价格、指数价格的字典
|
||
"""
|
||
try:
|
||
# 同时获取 ticker 数据(包含标记价格和指数价格)
|
||
ticker = self.get_ticker(symbol, self.CATEGORY_USDT_FUTURES)
|
||
if not ticker:
|
||
logger.error(f"获取 {symbol} ticker 数据失败")
|
||
return None
|
||
|
||
# 获取资金费率
|
||
url = f"{self._base_url}/api/v3/market/current-fund-rate"
|
||
params = {'symbol': symbol}
|
||
|
||
response = self._session.get(url, params=params, timeout=10)
|
||
response.raise_for_status()
|
||
result = response.json()
|
||
|
||
if result.get('code') != '00000':
|
||
logger.error(f"Bitget API 错误: {result.get('msg')}")
|
||
return None
|
||
|
||
data = result.get('data', [])
|
||
if not data:
|
||
return None
|
||
|
||
funding_data = data[0]
|
||
|
||
# 解析资金费率
|
||
funding_rate = float(funding_data.get('fundingRate', 0))
|
||
next_update = int(funding_data.get('nextUpdate', 0))
|
||
min_rate = float(funding_data.get('minFundingRate', 0))
|
||
max_rate = float(funding_data.get('maxFundingRate', 0))
|
||
|
||
# 从 ticker 获取标记价格和指数价格
|
||
mark_price = float(ticker.get('markPrice', 0))
|
||
index_price = float(ticker.get('indexPrice', 0))
|
||
|
||
# 判断市场情绪
|
||
if funding_rate > 0.01: # > 0.1%
|
||
sentiment = "极度贪婪"
|
||
sentiment_level = "extreme_greed"
|
||
elif funding_rate > 0.0005: # > 0.05%
|
||
sentiment = "贪婪"
|
||
sentiment_level = "greed"
|
||
elif funding_rate < -0.01: # < -0.1%
|
||
sentiment = "极度恐惧"
|
||
sentiment_level = "extreme_fear"
|
||
elif funding_rate < -0.0005: # < -0.05%
|
||
sentiment = "恐惧"
|
||
sentiment_level = "fear"
|
||
else:
|
||
sentiment = "中性"
|
||
sentiment_level = "neutral"
|
||
|
||
return {
|
||
'funding_rate': funding_rate,
|
||
'funding_rate_percent': funding_rate * 100,
|
||
'next_funding_time': next_update,
|
||
'min_funding_rate': min_rate,
|
||
'max_funding_rate': max_rate,
|
||
'mark_price': mark_price,
|
||
'index_price': index_price,
|
||
'sentiment': sentiment,
|
||
'sentiment_level': sentiment_level
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取 {symbol} 资金费率失败: {e}")
|
||
return None
|
||
|
||
def get_open_interest(self, symbol: str) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
获取持仓量
|
||
|
||
Args:
|
||
symbol: 交易对,如 'BTCUSDT'
|
||
|
||
Returns:
|
||
包含持仓量信息的字典
|
||
"""
|
||
try:
|
||
# 从 ticker 获取持仓量
|
||
ticker = self.get_ticker(symbol, self.CATEGORY_USDT_FUTURES)
|
||
if not ticker:
|
||
return None
|
||
|
||
open_interest = float(ticker.get('openInterest', 0))
|
||
|
||
return {
|
||
'open_interest': open_interest,
|
||
'timestamp': int(ticker.get('ts', 0))
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取 {symbol} 持仓量失败: {e}")
|
||
return None
|
||
|
||
def get_futures_market_data(self, symbol: str) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
获取合约市场综合数据(资金费率 + 持仓量)
|
||
|
||
Args:
|
||
symbol: 交易对
|
||
|
||
Returns:
|
||
综合市场数据
|
||
"""
|
||
try:
|
||
# 获取数据
|
||
funding_rate = self.get_funding_rate(symbol)
|
||
open_interest = self.get_open_interest(symbol)
|
||
ticker = self.get_ticker(symbol, self.CATEGORY_USDT_FUTURES)
|
||
|
||
if not funding_rate or not open_interest or not ticker:
|
||
logger.warning(f"获取 {symbol} 合约数据不完整")
|
||
return None
|
||
|
||
# 计算溢价率
|
||
premium_rate = 0
|
||
index_price = float(ticker.get('indexPrice', 0))
|
||
mark_price = float(ticker.get('markPrice', 0))
|
||
|
||
if index_price > 0:
|
||
premium_rate = ((mark_price - index_price) / index_price * 100)
|
||
|
||
return {
|
||
'funding_rate': funding_rate,
|
||
'open_interest': open_interest,
|
||
'premium_rate': premium_rate,
|
||
'market_sentiment': funding_rate.get('sentiment', ''),
|
||
'sentiment_level': funding_rate.get('sentiment_level', ''),
|
||
'mark_price': mark_price,
|
||
'index_price': index_price
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取 {symbol} 合约市场数据失败: {e}")
|
||
return None
|
||
|
||
def format_futures_data_for_llm(self, symbol: str,
|
||
market_data: Dict[str, Any]) -> str:
|
||
"""
|
||
格式化合约数据供 LLM 分析
|
||
|
||
Args:
|
||
symbol: 交易对
|
||
market_data: 合约市场数据
|
||
|
||
Returns:
|
||
格式化的文本
|
||
"""
|
||
if not market_data:
|
||
return ""
|
||
|
||
lines = [f"\n## {symbol} 合约市场数据\n"]
|
||
|
||
# 资金费率
|
||
funding = market_data.get('funding_rate', {})
|
||
if funding:
|
||
fr = funding.get('funding_rate_percent', 0)
|
||
sentiment = funding.get('sentiment', '')
|
||
lines.append(f"### 资金费率")
|
||
lines.append(f"• 当前费率: {fr:.4f}%")
|
||
lines.append(f"• 市场情绪: {sentiment}")
|
||
lines.append(f"• 标记价格: ${market_data.get('mark_price', 0):,.2f}")
|
||
lines.append(f"• 指数价格: ${market_data.get('index_price', 0):,.2f}")
|
||
|
||
# 资金费率分析
|
||
if fr > 0.1:
|
||
lines.append(f"• ⚠️ 极高费率,多头过度杠杆,警惕回调风险")
|
||
elif fr > 0.05:
|
||
lines.append(f"• 正费率,多头占优但未极端")
|
||
elif fr < -0.1:
|
||
lines.append(f"• ⚠️ 极低费率,空头过度杠杆,可能反弹")
|
||
elif fr < -0.05:
|
||
lines.append(f"• 负费率,空头占优但未极端")
|
||
|
||
# 持仓量
|
||
oi = market_data.get('open_interest', {})
|
||
if oi:
|
||
lines.append(f"\n### 持仓量")
|
||
lines.append(f"• 当前持仓: {oi.get('open_interest', 0):,.0f} 张")
|
||
|
||
# 溢价率
|
||
premium = market_data.get('premium_rate', 0)
|
||
if premium != 0:
|
||
lines.append(f"\n### 溢价分析")
|
||
lines.append(f"• 现货溢价: {premium:+.2f}%")
|
||
if premium > 1:
|
||
lines.append(f"• ⚠️ 高溢价,市场过热")
|
||
elif premium < -1:
|
||
lines.append(f"• ⚠️ 负溢价,市场偏冷")
|
||
|
||
return "\n".join(lines)
|
||
|
||
|
||
# 全局实例
|
||
bitget_service = BitgetService()
|