From 5173c05e65561fe7ca06b961bebe4aa79f4616ea Mon Sep 17 00:00:00 2001 From: aaron <> Date: Fri, 20 Feb 2026 22:02:17 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=90=88=E7=BA=A6=E5=B8=82?= =?UTF-8?q?=E5=9C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/crypto_agent/llm_signal_analyzer.py | 22 +- backend/app/services/binance_service.py | 228 ++++++++++++++++++ scripts/test_futures_data.py | 80 ++++++ 3 files changed, 328 insertions(+), 2 deletions(-) create mode 100755 scripts/test_futures_data.py diff --git a/backend/app/crypto_agent/llm_signal_analyzer.py b/backend/app/crypto_agent/llm_signal_analyzer.py index 5b64d5e..9086869 100644 --- a/backend/app/crypto_agent/llm_signal_analyzer.py +++ b/backend/app/crypto_agent/llm_signal_analyzer.py @@ -426,7 +426,9 @@ class LLMSignalAnalyzer: agent_type: 智能体类型,支持 'crypto', 'stock', 'smart' """ from app.config import get_settings + from app.services.binance_service import binance_service self.news_service = get_news_service() + self.binance_service = binance_service settings = get_settings() # 根据智能体类型选择模型配置 @@ -482,6 +484,17 @@ class LLMSignalAnalyzer: # 获取新闻数据 news_text = await self._get_news_context(symbol, symbols or [symbol]) + # 获取合约市场数据(仅加密货币) + futures_data = None + if self.agent_type == 'crypto': + try: + futures_data = self.binance_service.get_futures_market_data(symbol) + if futures_data: + logger.info(f"{symbol} 资金费率: {futures_data.get('funding_rate', {}).get('funding_rate_percent', 0):.4f}% | " + f"情绪: {futures_data.get('market_sentiment', '')}") + except Exception as e: + logger.warning(f"获取 {symbol} 合约数据失败: {e}") + # 根据智能体类型选择提示词 if self.agent_type == 'stock': system_prompt = self.STOCK_SYSTEM_PROMPT @@ -489,7 +502,7 @@ class LLMSignalAnalyzer: system_prompt = self.CRYPTO_SYSTEM_PROMPT # 构建数据提示 - data_prompt = self._build_data_prompt(symbol, data, news_text, position_info) + data_prompt = self._build_data_prompt(symbol, data, news_text, position_info, futures_data) # 调用 LLM response = llm_service.chat([ @@ -595,7 +608,8 @@ class LLMSignalAnalyzer: return "\n".join(lines) def _build_data_prompt(self, symbol: str, data: Dict[str, pd.DataFrame], - news_text: str = "", position_info: Dict[str, Any] = None) -> str: + news_text: str = "", position_info: Dict[str, Any] = None, + futures_data: Dict[str, Any] = None) -> str: """构建数据提示词""" parts = [f"# {symbol} 市场数据分析\n"] parts.append(f"分析时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") @@ -606,6 +620,10 @@ class LLMSignalAnalyzer: current_price = float(data['5m'].iloc[-1]['close']) parts.append(f"**当前价格**: ${current_price:,.2f}\n") + # === 新增:合约市场数据 === + if futures_data and self.agent_type == 'crypto': + parts.append(self.binance_service.format_futures_data_for_llm(symbol, futures_data)) + # === 新增:账户和持仓信息 === if position_info: parts.append("\n## 账户与持仓状态") diff --git a/backend/app/services/binance_service.py b/backend/app/services/binance_service.py index b457ba5..2bad1a7 100644 --- a/backend/app/services/binance_service.py +++ b/backend/app/services/binance_service.py @@ -22,6 +22,7 @@ class BinanceService: # Binance API 基础 URL BASE_URL = "https://api.binance.com" + FUTURES_URL = "https://fapi.binance.com" # 合约 API def __init__(self, api_key: str = "", api_secret: str = ""): """ @@ -272,6 +273,233 @@ class BinanceService: logger.error(f"获取 {symbol} 24h 统计失败: {e}") return None + def get_funding_rate(self, symbol: str) -> Optional[Dict[str, Any]]: + """ + 获取资金费率 + + Args: + symbol: 交易对,如 'BTCUSDT' + + Returns: + 包含资金费率信息的字典 + """ + try: + url = f"{self.FUTURES_URL}/fapi/v1/premiumIndex" + params = {'symbol': symbol} + response = self._session.get(url, params=params, timeout=10) + response.raise_for_status() + data = response.json() + + # 解析资金费率 + funding_rate = float(data.get('lastFundingRate', 0)) + mark_price = float(data.get('markPrice', 0)) + index_price = float(data.get('indexPrice', 0)) + next_funding_time = int(data.get('nextFundingTime', 0)) + + # 判断市场情绪 + if funding_rate > 0.01: # > 0.1% + sentiment = "极度贪婪" + sentiment_level = "extreme_greed" + elif funding_rate > 0.05: # > 0.05% + sentiment = "贪婪" + sentiment_level = "greed" + elif funding_rate < -0.01: # < -0.1% + sentiment = "极度恐惧" + sentiment_level = "extreme_fear" + elif funding_rate < -0.05: # < -0.05% + sentiment = "恐惧" + sentiment_level = "fear" + else: + sentiment = "中性" + sentiment_level = "neutral" + + return { + 'funding_rate': funding_rate, + 'funding_rate_percent': funding_rate * 100, # 转为百分比 + 'mark_price': mark_price, + 'index_price': index_price, + 'next_funding_time': next_funding_time, + '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: + url = f"{self.FUTURES_URL}/fapi/v1/openInterest" + params = {'symbol': symbol} + response = self._session.get(url, params=params, timeout=10) + response.raise_for_status() + data = response.json() + + open_interest = float(data.get('openInterest', 0)) + open_interest_value = float(data.get('openInterestValue', 0)) + + return { + 'open_interest': open_interest, + 'open_interest_value': open_interest_value, + 'timestamp': int(data.get('time', 0)) + } + except Exception as e: + logger.error(f"获取 {symbol} 持仓量失败: {e}") + return None + + def get_open_interest_hist(self, symbol: str, period: str = '5m', + limit: int = 30) -> Optional[List[Dict[str, Any]]]: + """ + 获取历史持仓量(用于计算变化趋势) + + Args: + symbol: 交易对 + period: 周期 (5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d) + limit: 获取数量 (最大 500) + + Returns: + 持仓量历史列表 + """ + try: + # Binance 使用正确的 API 端点 + url = f"{self.FUTURES_URL}/futures/data/openInterestHist" + params = { + 'symbol': symbol, + 'period': period, + 'limit': limit + } + response = self._session.get(url, params=params, timeout=10) + response.raise_for_status() + data = response.json() + + return data + 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) + + if not funding_rate or not open_interest: + logger.warning(f"获取 {symbol} 合约数据不完整") + return None + + # 获取历史持仓量计算趋势 + hist_oi = self.get_open_interest_hist(symbol, period='1h', limit=24) + oi_change = 0 + oi_change_percent = 0 + + if hist_oi and len(hist_oi) >= 2: + oi_24h_ago = float(hist_oi[-1].get('sumOpenInterest', 0)) + oi_now = float(hist_oi[0].get('sumOpenInterest', 0)) + oi_change = oi_now - oi_24h_ago + oi_change_percent = (oi_change / oi_24h_ago * 100) if oi_24h_ago > 0 else 0 + + # 计算溢价率 + premium_rate = 0 + if funding_rate.get('index_price', 0) > 0: + premium_rate = ((funding_rate['mark_price'] - funding_rate['index_price']) + / funding_rate['index_price'] * 100) + + return { + 'funding_rate': funding_rate, + 'open_interest': open_interest, + 'oi_change_24h': oi_change, + 'oi_change_percent_24h': oi_change_percent, + 'premium_rate': premium_rate, + 'market_sentiment': funding_rate.get('sentiment', ''), + 'sentiment_level': funding_rate.get('sentiment_level', '') + } + 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"• 标记价格: ${funding.get('mark_price', 0):,.2f}") + lines.append(f"• 指数价格: ${funding.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} 张") + lines.append(f"• 持仓金额: ${oi.get('open_interest_value', 0):,.0f}") + + # 持仓量变化 + oi_change = market_data.get('oi_change_percent_24h', 0) + if oi_change != 0: + lines.append(f"• 24h变化: {oi_change:+.2f}%") + if oi_change > 10: + lines.append(f"• ⚠️ 持仓大幅增加,资金加速流入") + elif oi_change < -10: + lines.append(f"• ⚠️ 持仓大幅减少,资金加速流出") + + # 溢价率 + 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) + # 全局实例 binance_service = BinanceService() diff --git a/scripts/test_futures_data.py b/scripts/test_futures_data.py new file mode 100755 index 0000000..c4ee093 --- /dev/null +++ b/scripts/test_futures_data.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +""" +测试合约市场数据获取 +""" +import sys +import os + +# 确保路径正确 +script_dir = os.path.dirname(os.path.abspath(__file__)) +project_root = os.path.dirname(script_dir) +backend_dir = os.path.join(project_root, 'backend') +sys.path.insert(0, backend_dir) + +from app.services.binance_service import binance_service + + +def main(): + print("=" * 60) + print("📊 测试 Binance 合约市场数据获取") + print("=" * 60) + + symbols = ['BTCUSDT', 'ETHUSDT'] + + for symbol in symbols: + print(f"\n{'='*60}") + print(f"📈 {symbol} 合约市场数据") + print(f"{'='*60}") + + # 1. 获取资金费率 + print(f"\n🔍 获取资金费率...") + funding_rate = binance_service.get_funding_rate(symbol) + if funding_rate: + print(f"✅ 资金费率: {funding_rate['funding_rate_percent']:.4f}%") + print(f" 市场情绪: {funding_rate['sentiment']}") + print(f" 标记价格: ${funding_rate['mark_price']:,.2f}") + print(f" 指数价格: ${funding_rate['index_price']:,.2f}") + + # 2. 获取持仓量 + print(f"\n🔍 获取持仓量...") + open_interest = binance_service.get_open_interest(symbol) + if open_interest: + print(f"✅ 持仓量: {open_interest['open_interest']:,.0f} 张") + print(f" 持仓金额: ${open_interest['open_interest_value']:,.0f}") + + # 3. 获取历史持仓量 + print(f"\n🔍 获取历史持仓量(计算24h变化)...") + hist_oi = binance_service.get_open_interest_hist(symbol, period='1h', limit=24) + if hist_oi and len(hist_oi) >= 2: + oi_now = float(hist_oi[0].get('sumOpenInterest', 0)) + oi_24h = float(hist_oi[-1].get('sumOpenInterest', 0)) + oi_change = oi_now - oi_24h + oi_change_pct = (oi_change / oi_24h * 100) if oi_24h > 0 else 0 + print(f"✅ 24h前: {oi_24h:,.0f} 张") + print(f" 当前: {oi_now:,.0f} 张") + print(f" 变化: {oi_change:+,.0f} 张 ({oi_change_pct:+.2f}%)") + + # 4. 获取综合数据 + print(f"\n🔍 获取综合合约数据...") + market_data = binance_service.get_futures_market_data(symbol) + if market_data: + print(f"✅ 综合数据获取成功") + print(f" 资金费率: {market_data['funding_rate']['funding_rate_percent']:.4f}%") + print(f" 持仓24h变化: {market_data['oi_change_percent_24h']:+.2f}%") + print(f" 溢价率: {market_data['premium_rate']:+.2f}%") + print(f" 市场情绪: {market_data['market_sentiment']}") + + # 5. 格式化给 LLM 的数据 + print(f"\n🤖 格式化给 LLM 的数据:") + print(f"{'─'*60}") + if market_data: + formatted = binance_service.format_futures_data_for_llm(symbol, market_data) + print(formatted) + + print("\n" + "=" * 60) + print("✅ 测试完成") + print("=" * 60) + + +if __name__ == "__main__": + main()