Backup before system reinstall

This commit is contained in:
llama-research
2025-09-06 07:37:16 +00:00
parent f9856c31e5
commit e78aefac88
66 changed files with 16347 additions and 1917 deletions

View File

@@ -0,0 +1 @@
# 稷下学宫引擎模块

View File

@@ -0,0 +1,43 @@
# 设计八仙与数据源的智能映射
immortal_data_mapping = {
'吕洞宾': {
'specialty': 'technical_analysis', # 技术分析专家
'preferred_data_types': ['historical', 'price'],
'data_providers': ['OpenBB', 'RapidAPI']
},
'何仙姑': {
'specialty': 'risk_metrics', # 风险控制专家
'preferred_data_types': ['price', 'profile'],
'data_providers': ['RapidAPI', 'OpenBB']
},
'张果老': {
'specialty': 'historical_data', # 历史数据分析师
'preferred_data_types': ['historical'],
'data_providers': ['OpenBB', 'RapidAPI']
},
'韩湘子': {
'specialty': 'sector_analysis', # 新兴资产专家
'preferred_data_types': ['profile', 'news'],
'data_providers': ['RapidAPI', 'OpenBB']
},
'汉钟离': {
'specialty': 'market_movers', # 热点追踪
'preferred_data_types': ['news', 'price'],
'data_providers': ['RapidAPI', 'OpenBB']
},
'蓝采和': {
'specialty': 'value_discovery', # 潜力股发现
'preferred_data_types': ['screener', 'profile'],
'data_providers': ['OpenBB', 'RapidAPI']
},
'铁拐李': {
'specialty': 'contrarian_analysis', # 逆向思维专家
'preferred_data_types': ['profile', 'short_interest'],
'data_providers': ['RapidAPI', 'OpenBB']
},
'曹国舅': {
'specialty': 'macro_economics', # 宏观经济分析师
'preferred_data_types': ['profile', 'institutional_holdings'],
'data_providers': ['OpenBB', 'RapidAPI']
}
}

View File

@@ -0,0 +1,38 @@
from abc import ABC, abstractmethod
from typing import List, Optional
from src.jixia.models.financial_data_models import StockQuote, HistoricalPrice, CompanyProfile, FinancialNews
class DataProvider(ABC):
"""金融数据提供商抽象基类"""
@abstractmethod
def get_quote(self, symbol: str) -> Optional[StockQuote]:
"""获取股票报价"""
pass
@abstractmethod
def get_historical_prices(self, symbol: str, days: int = 30) -> List[HistoricalPrice]:
"""获取历史价格数据"""
pass
@abstractmethod
def get_company_profile(self, symbol: str) -> Optional[CompanyProfile]:
"""获取公司概况"""
pass
@abstractmethod
def get_news(self, symbol: str, limit: int = 10) -> List[FinancialNews]:
"""获取相关新闻"""
pass
@property
@abstractmethod
def name(self) -> str:
"""数据提供商名称"""
pass
@property
@abstractmethod
def priority(self) -> int:
"""优先级(数字越小优先级越高)"""
pass

View File

@@ -0,0 +1,109 @@
from typing import List, Optional
import asyncio
from src.jixia.engines.data_abstraction import DataProvider
from src.jixia.models.financial_data_models import StockQuote, HistoricalPrice, CompanyProfile, FinancialNews
from src.jixia.engines.rapidapi_adapter import RapidAPIDataProvider
from src.jixia.engines.openbb_adapter import OpenBBDataProvider
class DataAbstractionLayer:
"""金融数据抽象层管理器"""
def __init__(self):
self.providers: List[DataProvider] = []
self._initialize_providers()
def _initialize_providers(self):
"""初始化所有可用的数据提供商"""
# 根据配置和环境动态加载适配器
try:
self.providers.append(OpenBBDataProvider())
except Exception as e:
print(f"警告: OpenBBDataProvider 初始化失败: {e}")
try:
self.providers.append(RapidAPIDataProvider())
except Exception as e:
print(f"警告: RapidAPIDataProvider 初始化失败: {e}")
# 按优先级排序
self.providers.sort(key=lambda p: p.priority)
print(f"数据抽象层初始化完成,已加载 {len(self.providers)} 个数据提供商")
for provider in self.providers:
print(f" - {provider.name} (优先级: {provider.priority})")
def get_quote(self, symbol: str) -> Optional[StockQuote]:
"""获取股票报价(带故障转移)"""
for provider in self.providers:
try:
quote = provider.get_quote(symbol)
if quote:
print(f"✅ 通过 {provider.name} 获取到 {symbol} 的报价")
return quote
except Exception as e:
print(f"警告: {provider.name} 获取报价失败: {e}")
continue
print(f"❌ 所有数据提供商都无法获取 {symbol} 的报价")
return None
async def get_quote_async(self, symbol: str) -> Optional[StockQuote]:
"""异步获取股票报价(带故障转移)"""
for provider in self.providers:
try:
# 如果提供商支持异步方法,则使用异步方法
if hasattr(provider, 'get_quote_async'):
quote = await provider.get_quote_async(symbol)
else:
# 否则在执行器中运行同步方法
quote = await asyncio.get_event_loop().run_in_executor(
None, provider.get_quote, symbol
)
if quote:
print(f"✅ 通过 {provider.name} 异步获取到 {symbol} 的报价")
return quote
except Exception as e:
print(f"警告: {provider.name} 异步获取报价失败: {e}")
continue
print(f"❌ 所有数据提供商都无法异步获取 {symbol} 的报价")
return None
def get_historical_prices(self, symbol: str, days: int = 30) -> List[HistoricalPrice]:
"""获取历史价格数据(带故障转移)"""
for provider in self.providers:
try:
prices = provider.get_historical_prices(symbol, days)
if prices:
print(f"✅ 通过 {provider.name} 获取到 {symbol} 的历史价格数据")
return prices
except Exception as e:
print(f"警告: {provider.name} 获取历史价格失败: {e}")
continue
print(f"❌ 所有数据提供商都无法获取 {symbol} 的历史价格数据")
return []
def get_company_profile(self, symbol: str) -> Optional[CompanyProfile]:
"""获取公司概况(带故障转移)"""
for provider in self.providers:
try:
profile = provider.get_company_profile(symbol)
if profile:
print(f"✅ 通过 {provider.name} 获取到 {symbol} 的公司概况")
return profile
except Exception as e:
print(f"警告: {provider.name} 获取公司概况失败: {e}")
continue
print(f"❌ 所有数据提供商都无法获取 {symbol} 的公司概况")
return None
def get_news(self, symbol: str, limit: int = 10) -> List[FinancialNews]:
"""获取相关新闻(带故障转移)"""
for provider in self.providers:
try:
news = provider.get_news(symbol, limit)
if news:
print(f"✅ 通过 {provider.name} 获取到 {symbol} 的相关新闻")
return news
except Exception as e:
print(f"警告: {provider.name} 获取新闻失败: {e}")
continue
print(f"❌ 所有数据提供商都无法获取 {symbol} 的相关新闻")
return []

View File

@@ -0,0 +1,37 @@
import time
from typing import Any, Optional
from functools import lru_cache
class DataCache:
"""金融数据缓存"""
def __init__(self):
self._cache = {}
self._cache_times = {}
self.default_ttl = 60 # 默认缓存时间(秒)
def get(self, key: str) -> Optional[Any]:
"""获取缓存数据"""
if key in self._cache:
# 检查是否过期
if time.time() - self._cache_times[key] < self.default_ttl:
return self._cache[key]
else:
# 删除过期缓存
del self._cache[key]
del self._cache_times[key]
return None
def set(self, key: str, value: Any, ttl: Optional[int] = None):
"""设置缓存数据"""
self._cache[key] = value
self._cache_times[key] = time.time()
if ttl:
# 可以为特定数据设置不同的TTL
pass # 实际实现中需要更复杂的TTL管理机制
@lru_cache(maxsize=128)
def get_quote_cache(self, symbol: str) -> Optional[Any]:
"""LRU缓存装饰器示例"""
# 这个方法将自动缓存最近128个调用的结果
pass

View File

@@ -0,0 +1,49 @@
from typing import Dict, Any
from datetime import datetime
class DataQualityMonitor:
"""数据质量监控"""
def __init__(self):
self.provider_stats = {}
def record_access(self, provider_name: str, success: bool, response_time: float, data_size: int):
"""记录数据访问统计"""
if provider_name not in self.provider_stats:
self.provider_stats[provider_name] = {
'total_requests': 0,
'successful_requests': 0,
'failed_requests': 0,
'total_response_time': 0,
'total_data_size': 0,
'last_access': None
}
stats = self.provider_stats[provider_name]
stats['total_requests'] += 1
if success:
stats['successful_requests'] += 1
else:
stats['failed_requests'] += 1
stats['total_response_time'] += response_time
stats['total_data_size'] += data_size
stats['last_access'] = datetime.now()
def get_provider_health(self, provider_name: str) -> Dict[str, Any]:
"""获取提供商健康状况"""
if provider_name not in self.provider_stats:
return {'status': 'unknown'}
stats = self.provider_stats[provider_name]
success_rate = stats['successful_requests'] / stats['total_requests'] if stats['total_requests'] > 0 else 0
avg_response_time = stats['total_response_time'] / stats['total_requests'] if stats['total_requests'] > 0 else 0
status = 'healthy' if success_rate > 0.95 and avg_response_time < 2.0 else 'degraded' if success_rate > 0.8 else 'unhealthy'
return {
'status': status,
'success_rate': success_rate,
'avg_response_time': avg_response_time,
'total_requests': stats['total_requests'],
'last_access': stats['last_access']
}

View File

@@ -0,0 +1,462 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
稷下学宫负载均衡器
实现八仙论道的API负载分担策略
"""
import time
import random
import requests
from datetime import datetime, timezone
from typing import Dict, List, Any, Optional, Tuple
from dataclasses import dataclass
from collections import defaultdict
import json
import os
@dataclass
class APIResult:
"""API调用结果"""
success: bool
data: Dict[str, Any]
api_used: str
response_time: float
error: Optional[str] = None
cached: bool = False
class RateLimiter:
"""速率限制器"""
def __init__(self):
self.api_calls = defaultdict(list)
self.limits = {
'alpha_vantage': {'per_minute': 500, 'per_month': 500000},
'yahoo_finance_15': {'per_minute': 500, 'per_month': 500000},
'webull': {'per_minute': 500, 'per_month': 500000},
'seeking_alpha': {'per_minute': 500, 'per_month': 500000}
}
def is_rate_limited(self, api_name: str) -> bool:
"""检查是否达到速率限制"""
now = time.time()
calls = self.api_calls[api_name]
# 清理1分钟前的记录
self.api_calls[api_name] = [call_time for call_time in calls if now - call_time < 60]
# 检查每分钟限制
if len(self.api_calls[api_name]) >= self.limits[api_name]['per_minute'] * 0.9: # 90%阈值
return True
return False
def record_call(self, api_name: str):
"""记录API调用"""
self.api_calls[api_name].append(time.time())
class APIHealthChecker:
"""API健康检查器"""
def __init__(self):
self.health_status = {
'alpha_vantage': {'healthy': True, 'last_check': 0, 'consecutive_failures': 0},
'yahoo_finance_15': {'healthy': True, 'last_check': 0, 'consecutive_failures': 0},
'webull': {'healthy': True, 'last_check': 0, 'consecutive_failures': 0},
'seeking_alpha': {'healthy': True, 'last_check': 0, 'consecutive_failures': 0}
}
self.check_interval = 300 # 5分钟检查一次
def is_healthy(self, api_name: str) -> bool:
"""检查API是否健康"""
status = self.health_status[api_name]
now = time.time()
# 如果距离上次检查超过间隔时间,进行健康检查
if now - status['last_check'] > self.check_interval:
self._perform_health_check(api_name)
return status['healthy']
def _perform_health_check(self, api_name: str):
"""执行健康检查"""
# 这里可以实现具体的健康检查逻辑
# 暂时简化为基于连续失败次数判断
status = self.health_status[api_name]
status['last_check'] = time.time()
# 如果连续失败超过3次标记为不健康
if status['consecutive_failures'] > 3:
status['healthy'] = False
else:
status['healthy'] = True
def record_success(self, api_name: str):
"""记录成功调用"""
self.health_status[api_name]['consecutive_failures'] = 0
self.health_status[api_name]['healthy'] = True
def record_failure(self, api_name: str):
"""记录失败调用"""
self.health_status[api_name]['consecutive_failures'] += 1
class DataNormalizer:
"""数据标准化处理器"""
def normalize_stock_quote(self, raw_data: dict, api_source: str) -> dict:
"""将不同API的股票报价数据标准化"""
try:
if api_source == 'alpha_vantage':
return self._normalize_alpha_vantage_quote(raw_data)
elif api_source == 'yahoo_finance_15':
return self._normalize_yahoo_quote(raw_data)
elif api_source == 'webull':
return self._normalize_webull_quote(raw_data)
elif api_source == 'seeking_alpha':
return self._normalize_seeking_alpha_quote(raw_data)
else:
return {'error': f'Unknown API source: {api_source}'}
except Exception as e:
return {'error': f'Data normalization failed: {str(e)}'}
def _normalize_alpha_vantage_quote(self, data: dict) -> dict:
"""标准化Alpha Vantage数据格式"""
global_quote = data.get('Global Quote', {})
return {
'symbol': global_quote.get('01. symbol'),
'price': float(global_quote.get('05. price', 0)),
'change': float(global_quote.get('09. change', 0)),
'change_percent': global_quote.get('10. change percent', '0%'),
'volume': int(global_quote.get('06. volume', 0)),
'high': float(global_quote.get('03. high', 0)),
'low': float(global_quote.get('04. low', 0)),
'source': 'alpha_vantage',
'timestamp': global_quote.get('07. latest trading day')
}
def _normalize_yahoo_quote(self, data: dict) -> dict:
"""标准化Yahoo Finance数据格式"""
body = data.get('body', {})
return {
'symbol': body.get('symbol'),
'price': float(body.get('regularMarketPrice', 0)),
'change': float(body.get('regularMarketChange', 0)),
'change_percent': f"{body.get('regularMarketChangePercent', 0):.2f}%",
'volume': int(body.get('regularMarketVolume', 0)),
'high': float(body.get('regularMarketDayHigh', 0)),
'low': float(body.get('regularMarketDayLow', 0)),
'source': 'yahoo_finance_15',
'timestamp': body.get('regularMarketTime')
}
def _normalize_webull_quote(self, data: dict) -> dict:
"""标准化Webull数据格式"""
if 'stocks' in data and len(data['stocks']) > 0:
stock = data['stocks'][0]
return {
'symbol': stock.get('symbol'),
'price': float(stock.get('close', 0)),
'change': float(stock.get('change', 0)),
'change_percent': f"{stock.get('changeRatio', 0):.2f}%",
'volume': int(stock.get('volume', 0)),
'high': float(stock.get('high', 0)),
'low': float(stock.get('low', 0)),
'source': 'webull',
'timestamp': stock.get('timeStamp')
}
return {'error': 'No stock data found in Webull response'}
def _normalize_seeking_alpha_quote(self, data: dict) -> dict:
"""标准化Seeking Alpha数据格式"""
if 'data' in data and len(data['data']) > 0:
stock_data = data['data'][0]
attributes = stock_data.get('attributes', {})
return {
'symbol': attributes.get('slug'),
'price': float(attributes.get('lastPrice', 0)),
'change': float(attributes.get('dayChange', 0)),
'change_percent': f"{attributes.get('dayChangePercent', 0):.2f}%",
'volume': int(attributes.get('volume', 0)),
'source': 'seeking_alpha',
'market_cap': attributes.get('marketCap'),
'pe_ratio': attributes.get('peRatio')
}
return {'error': 'No data found in Seeking Alpha response'}
class JixiaLoadBalancer:
"""稷下学宫负载均衡器"""
def __init__(self, rapidapi_key: str):
self.rapidapi_key = rapidapi_key
self.rate_limiter = RateLimiter()
self.health_checker = APIHealthChecker()
self.data_normalizer = DataNormalizer()
self.cache = {} # 简单的内存缓存
self.cache_ttl = 300 # 5分钟缓存
# API配置
self.api_configs = {
'alpha_vantage': {
'host': 'alpha-vantage.p.rapidapi.com',
'endpoints': {
'stock_quote': '/query?function=GLOBAL_QUOTE&symbol={symbol}',
'company_overview': '/query?function=OVERVIEW&symbol={symbol}',
'earnings': '/query?function=EARNINGS&symbol={symbol}'
}
},
'yahoo_finance_15': {
'host': 'yahoo-finance15.p.rapidapi.com',
'endpoints': {
'stock_quote': '/api/yahoo/qu/quote/{symbol}',
'market_movers': '/api/yahoo/co/collections/day_gainers',
'market_news': '/api/yahoo/ne/news'
}
},
'webull': {
'host': 'webull.p.rapidapi.com',
'endpoints': {
'stock_quote': '/stock/search?keyword={symbol}',
'market_movers': '/market/get-active-gainers'
}
},
'seeking_alpha': {
'host': 'seeking-alpha.p.rapidapi.com',
'endpoints': {
'company_overview': '/symbols/get-profile?symbols={symbol}',
'market_news': '/news/list?category=market-news'
}
}
}
# 八仙API分配策略
self.immortal_api_mapping = {
'stock_quote': {
'吕洞宾': 'alpha_vantage', # 主力剑仙用最快的API
'何仙姑': 'yahoo_finance_15', # 风控专家用稳定的API
'张果老': 'webull', # 技术分析师用搜索强的API
'韩湘子': 'alpha_vantage', # 基本面研究用专业API
'汉钟离': 'yahoo_finance_15', # 量化专家用市场数据API
'蓝采和': 'webull', # 情绪分析师用活跃数据API
'曹国舅': 'seeking_alpha', # 宏观分析师用分析API
'铁拐李': 'alpha_vantage' # 逆向投资用基础数据API
},
'company_overview': {
'吕洞宾': 'alpha_vantage',
'何仙姑': 'seeking_alpha',
'张果老': 'alpha_vantage',
'韩湘子': 'seeking_alpha',
'汉钟离': 'alpha_vantage',
'蓝采和': 'seeking_alpha',
'曹国舅': 'seeking_alpha',
'铁拐李': 'alpha_vantage'
},
'market_movers': {
'吕洞宾': 'yahoo_finance_15',
'何仙姑': 'webull',
'张果老': 'yahoo_finance_15',
'韩湘子': 'webull',
'汉钟离': 'yahoo_finance_15',
'蓝采和': 'webull',
'曹国舅': 'yahoo_finance_15',
'铁拐李': 'webull'
},
'market_news': {
'吕洞宾': 'yahoo_finance_15',
'何仙姑': 'seeking_alpha',
'张果老': 'yahoo_finance_15',
'韩湘子': 'seeking_alpha',
'汉钟离': 'yahoo_finance_15',
'蓝采和': 'seeking_alpha',
'曹国舅': 'seeking_alpha',
'铁拐李': 'yahoo_finance_15'
}
}
# 故障转移优先级
self.failover_priority = {
'alpha_vantage': ['webull', 'yahoo_finance_15'],
'yahoo_finance_15': ['webull', 'alpha_vantage'],
'webull': ['alpha_vantage', 'yahoo_finance_15'],
'seeking_alpha': ['yahoo_finance_15', 'alpha_vantage']
}
def get_data_for_immortal(self, immortal_name: str, data_type: str, symbol: str = None) -> APIResult:
"""为特定仙人获取数据"""
print(f"🎭 {immortal_name} 正在获取 {data_type} 数据...")
# 检查缓存
cache_key = f"{immortal_name}_{data_type}_{symbol}"
cached_result = self._get_cached_data(cache_key)
if cached_result:
print(f" 📦 使用缓存数据")
return cached_result
# 获取该仙人的首选API
if data_type not in self.immortal_api_mapping:
return APIResult(False, {}, '', 0, f"Unsupported data type: {data_type}")
preferred_api = self.immortal_api_mapping[data_type][immortal_name]
# 尝试首选API
result = self._try_api(preferred_api, data_type, symbol)
if result.success:
self._cache_data(cache_key, result)
print(f" ✅ 成功从 {preferred_api} 获取数据 (响应时间: {result.response_time:.2f}s)")
return result
# 故障转移到备用API
print(f" ⚠️ {preferred_api} 不可用尝试备用API...")
backup_apis = self.failover_priority.get(preferred_api, [])
for backup_api in backup_apis:
if data_type in self.api_configs[backup_api]['endpoints']:
result = self._try_api(backup_api, data_type, symbol)
if result.success:
self._cache_data(cache_key, result)
print(f" ✅ 成功从备用API {backup_api} 获取数据 (响应时间: {result.response_time:.2f}s)")
return result
# 所有API都失败
print(f" ❌ 所有API都不可用")
return APIResult(False, {}, '', 0, "All APIs failed")
def _try_api(self, api_name: str, data_type: str, symbol: str = None) -> APIResult:
"""尝试调用指定API"""
# 检查API健康状态和速率限制
if not self.health_checker.is_healthy(api_name):
return APIResult(False, {}, api_name, 0, "API is unhealthy")
if self.rate_limiter.is_rate_limited(api_name):
return APIResult(False, {}, api_name, 0, "Rate limited")
# 构建请求
config = self.api_configs[api_name]
if data_type not in config['endpoints']:
return APIResult(False, {}, api_name, 0, f"Endpoint {data_type} not supported")
endpoint = config['endpoints'][data_type]
if symbol and '{symbol}' in endpoint:
endpoint = endpoint.format(symbol=symbol)
url = f"https://{config['host']}{endpoint}"
headers = {
'X-RapidAPI-Key': self.rapidapi_key,
'X-RapidAPI-Host': config['host']
}
# 发起请求
start_time = time.time()
try:
response = requests.get(url, headers=headers, timeout=10)
response_time = time.time() - start_time
self.rate_limiter.record_call(api_name)
if response.status_code == 200:
data = response.json()
# 数据标准化
if data_type == 'stock_quote':
normalized_data = self.data_normalizer.normalize_stock_quote(data, api_name)
else:
normalized_data = data
self.health_checker.record_success(api_name)
return APIResult(True, normalized_data, api_name, response_time)
else:
error_msg = f"HTTP {response.status_code}: {response.text[:200]}"
self.health_checker.record_failure(api_name)
return APIResult(False, {}, api_name, response_time, error_msg)
except Exception as e:
response_time = time.time() - start_time
self.health_checker.record_failure(api_name)
return APIResult(False, {}, api_name, response_time, str(e))
def _get_cached_data(self, cache_key: str) -> Optional[APIResult]:
"""获取缓存数据"""
if cache_key in self.cache:
cached_item = self.cache[cache_key]
if time.time() - cached_item['timestamp'] < self.cache_ttl:
result = cached_item['result']
result.cached = True
return result
else:
# 缓存过期,删除
del self.cache[cache_key]
return None
def _cache_data(self, cache_key: str, result: APIResult):
"""缓存数据"""
self.cache[cache_key] = {
'result': result,
'timestamp': time.time()
}
def get_load_distribution(self) -> dict:
"""获取负载分布统计"""
api_calls = {}
total_calls = 0
for api_name, calls in self.rate_limiter.api_calls.items():
call_count = len(calls)
api_calls[api_name] = call_count
total_calls += call_count
if total_calls == 0:
return {}
distribution = {}
for api_name, call_count in api_calls.items():
health_status = self.health_checker.health_status[api_name]
distribution[api_name] = {
'calls': call_count,
'percentage': (call_count / total_calls) * 100,
'healthy': health_status['healthy'],
'consecutive_failures': health_status['consecutive_failures']
}
return distribution
def conduct_immortal_debate(self, topic_symbol: str) -> Dict[str, APIResult]:
"""进行八仙论道,每个仙人获取不同的数据"""
print(f"\n🏛️ 稷下学宫八仙论道开始 - 主题: {topic_symbol}")
print("=" * 60)
immortals = ['吕洞宾', '何仙姑', '张果老', '韩湘子', '汉钟离', '蓝采和', '曹国舅', '铁拐李']
debate_results = {}
# 每个仙人获取股票报价数据
for immortal in immortals:
result = self.get_data_for_immortal(immortal, 'stock_quote', topic_symbol)
debate_results[immortal] = result
if result.success:
data = result.data
if 'price' in data:
print(f" 💰 {immortal}: ${data['price']:.2f} ({data.get('change_percent', 'N/A')}) via {result.api_used}")
time.sleep(0.2) # 避免过快请求
print("\n📊 负载分布统计:")
distribution = self.get_load_distribution()
for api_name, stats in distribution.items():
print(f" {api_name}: {stats['calls']} 次调用 ({stats['percentage']:.1f}%) - {'健康' if stats['healthy'] else '异常'}")
return debate_results
# 使用示例
if __name__ == "__main__":
# 从环境变量获取API密钥
rapidapi_key = os.getenv('RAPIDAPI_KEY')
if not rapidapi_key:
print("❌ 请设置RAPIDAPI_KEY环境变量")
exit(1)
# 创建负载均衡器
load_balancer = JixiaLoadBalancer(rapidapi_key)
# 进行八仙论道
results = load_balancer.conduct_immortal_debate('TSLA')
print("\n🎉 八仙论道完成!")

View File

@@ -0,0 +1,75 @@
from typing import List, Optional
from src.jixia.engines.data_abstraction import DataProvider
from src.jixia.models.financial_data_models import StockQuote, HistoricalPrice, CompanyProfile, FinancialNews
from src.jixia.engines.openbb_engine import OpenBBEngine
class OpenBBDataProvider(DataProvider):
"""OpenBB引擎适配器"""
def __init__(self):
self.engine = OpenBBEngine()
self._name = "OpenBB"
self._priority = 1 # 最高优先级
def get_quote(self, symbol: str) -> Optional[StockQuote]:
result = self.engine.get_immortal_data("吕洞宾", "price", symbol)
if result.success and result.data:
# 解析OpenBB返回的数据并转换为StockQuote
# 注意这里需要根据OpenBB实际返回的数据结构进行调整
data = result.data
if isinstance(data, list) and len(data) > 0:
item = data[0] # 取第一条数据
elif hasattr(data, '__dict__'):
item = data
else:
item = {}
# 提取价格信息根据openbb_stock_data.py中的字段
price = 0
if hasattr(item, 'close'):
price = float(item.close)
elif isinstance(item, dict) and 'close' in item:
price = float(item['close'])
volume = 0
if hasattr(item, 'volume'):
volume = int(item.volume)
elif isinstance(item, dict) and 'volume' in item:
volume = int(item['volume'])
# 日期处理
timestamp = None
if hasattr(item, 'date'):
timestamp = item.date
elif isinstance(item, dict) and 'date' in item:
timestamp = item['date']
return StockQuote(
symbol=symbol,
price=price,
change=0, # 需要计算
change_percent=0, # 需要计算
volume=volume,
timestamp=timestamp
)
return None
def get_historical_prices(self, symbol: str, days: int = 30) -> List[HistoricalPrice]:
# 实现历史价格数据获取逻辑
pass
def get_company_profile(self, symbol: str) -> Optional[CompanyProfile]:
# 实现公司概况获取逻辑
pass
def get_news(self, symbol: str, limit: int = 10) -> List[FinancialNews]:
# 实现新闻获取逻辑
pass
@property
def name(self) -> str:
return self._name
@property
def priority(self) -> int:
return self._priority

View File

@@ -0,0 +1,225 @@
#!/usr/bin/env python3
"""
OpenBB 集成引擎
为八仙论道提供更丰富的金融数据支撑
"""
from typing import Dict, List, Any, Optional
from dataclasses import dataclass
@dataclass
class ImmortalConfig:
"""八仙配置数据类"""
primary: str
specialty: str
@dataclass
class APIResult:
"""API调用结果数据类"""
success: bool
data: Optional[Any] = None
provider_used: Optional[str] = None
error: Optional[str] = None
class OpenBBEngine:
"""OpenBB 集成引擎"""
def __init__(self):
"""
初始化 OpenBB 引擎
"""
# 延迟导入 OpenBB避免未安装时报错
self._obb = None
# 八仙专属数据源分配
self.immortal_sources: Dict[str, ImmortalConfig] = {
'吕洞宾': ImmortalConfig( # 乾-技术分析专家
primary='yfinance',
specialty='technical_analysis'
),
'何仙姑': ImmortalConfig( # 坤-风险控制专家
primary='yfinance',
specialty='risk_metrics'
),
'张果老': ImmortalConfig( # 兑-历史数据分析师
primary='yfinance',
specialty='historical_data'
),
'韩湘子': ImmortalConfig( # 艮-新兴资产专家
primary='yfinance',
specialty='sector_analysis'
),
'汉钟离': ImmortalConfig( # 离-热点追踪
primary='yfinance',
specialty='market_movers'
),
'蓝采和': ImmortalConfig( # 坎-潜力股发现
primary='yfinance',
specialty='screener'
),
'曹国舅': ImmortalConfig( # 震-机构分析
primary='yfinance',
specialty='institutional_holdings'
),
'铁拐李': ImmortalConfig( # 巽-逆向投资
primary='yfinance',
specialty='short_interest'
)
}
print("✅ OpenBB 引擎初始化完成")
def _ensure_openbb(self):
"""Lazy import OpenBB v4 obb router."""
if self._obb is not None:
return True
try:
from openbb import obb # type: ignore
self._obb = obb
return True
except Exception:
self._obb = None
return False
def get_immortal_data(self, immortal_name: str, data_type: str, symbol: str = 'AAPL') -> APIResult:
"""
为特定八仙获取专属数据
Args:
immortal_name: 八仙名称
data_type: 数据类型
symbol: 股票代码
Returns:
API调用结果
"""
if immortal_name not in self.immortal_sources:
return APIResult(success=False, error=f'Unknown immortal: {immortal_name}')
immortal_config = self.immortal_sources[immortal_name]
print(f"🧙‍♂️ {immortal_name} 请求 {data_type} 数据 (股票: {symbol})")
# 根据数据类型调用不同的 OpenBB 函数
try:
if not self._ensure_openbb():
return APIResult(success=False, error='OpenBB 未安装,请先安装 openbb>=4 并在 requirements.txt 启用')
obb = self._obb
if data_type == 'price':
result = obb.equity.price.quote(symbol=symbol, provider=immortal_config.primary)
return APIResult(
success=True,
data=getattr(result, 'results', getattr(result, 'to_dict', lambda: None)()),
provider_used=immortal_config.primary
)
elif data_type == 'historical':
result = obb.equity.price.historical(symbol=symbol, provider=immortal_config.primary)
return APIResult(
success=True,
data=getattr(result, 'results', getattr(result, 'to_dict', lambda: None)()),
provider_used=immortal_config.primary
)
elif data_type == 'profile':
result = obb.equity.profile(symbol=symbol, provider=immortal_config.primary)
return APIResult(
success=True,
data=getattr(result, 'results', getattr(result, 'to_dict', lambda: None)()),
provider_used=immortal_config.primary
)
elif data_type == 'news':
result = obb.news.company(symbol=symbol)
return APIResult(
success=True,
data=getattr(result, 'results', getattr(result, 'to_dict', lambda: None)()),
provider_used='news_api'
)
elif data_type == 'earnings':
result = obb.equity.earnings.earnings_historical(symbol=symbol, provider=immortal_config.primary)
return APIResult(
success=True,
data=getattr(result, 'results', getattr(result, 'to_dict', lambda: None)()),
provider_used=immortal_config.primary
)
elif data_type == 'dividends':
result = obb.equity.fundamental.dividend(symbol=symbol, provider=immortal_config.primary)
return APIResult(
success=True,
data=getattr(result, 'results', getattr(result, 'to_dict', lambda: None)()),
provider_used=immortal_config.primary
)
elif data_type == 'screener':
# 使用简单的筛选器作为替代
result = obb.equity.screener.etf(
provider=immortal_config.primary
)
return APIResult(
success=True,
data=getattr(result, 'results', getattr(result, 'to_dict', lambda: None)()),
provider_used=immortal_config.primary
)
else:
return APIResult(success=False, error=f'Unsupported data type: {data_type}')
except Exception as e:
return APIResult(success=False, error=f'OpenBB 调用失败: {str(e)}')
def simulate_jixia_debate(self, topic_symbol: str = 'TSLA') -> Dict[str, APIResult]:
"""
模拟稷下学宫八仙论道
Args:
topic_symbol: 辩论主题股票代码
Returns:
八仙辩论结果
"""
print(f"🏛️ 稷下学宫八仙论道 - 主题: {topic_symbol} (OpenBB 版本)")
print("=" * 60)
debate_results: Dict[str, APIResult] = {}
# 数据类型映射
data_type_mapping = {
'technical_analysis': 'historical', # 技术分析使用历史价格数据
'risk_metrics': 'price', # 风险控制使用当前价格数据
'historical_data': 'historical', # 历史数据分析使用历史价格数据
'sector_analysis': 'profile', # 新兴资产分析使用公司概况
'market_movers': 'news', # 热点追踪使用新闻
'screener': 'screener', # 潜力股发现使用筛选器
'institutional_holdings': 'profile', # 机构分析使用公司概况
'short_interest': 'profile' # 逆向投资使用公司概况
}
# 八仙依次发言
for immortal_name, config in self.immortal_sources.items():
print(f"\n🎭 {immortal_name} ({config.specialty}) 发言:")
data_type = data_type_mapping.get(config.specialty, 'price')
result = self.get_immortal_data(immortal_name, data_type, topic_symbol)
if result.success:
debate_results[immortal_name] = result
print(f" 💬 观点: 基于{result.provider_used}数据的{config.specialty}分析")
# 显示部分数据示例
if result.data:
if isinstance(result.data, list) and len(result.data) > 0:
sample = result.data[0]
print(f" 📊 数据示例: {sample}")
elif hasattr(result.data, '__dict__'):
# 如果是对象,显示前几个属性
attrs = vars(result.data)
sample = {k: v for k, v in list(attrs.items())[:3]}
print(f" 📊 数据示例: {sample}")
else:
print(f" 📊 数据示例: {result.data}")
else:
print(f" 😔 暂时无法获取数据: {result.error}")
return debate_results
if __name__ == "__main__":
# 测试 OpenBB 引擎
print("🧪 OpenBB 引擎测试")
engine = OpenBBEngine()
engine.simulate_jixia_debate('AAPL')

View File

@@ -0,0 +1,161 @@
#!/usr/bin/env python3
"""
OpenBB 股票数据获取模块
"""
from datetime import datetime, timedelta
from typing import List, Dict, Any, Optional
def get_stock_data(symbol: str, days: int = 90) -> Optional[List[Dict[str, Any]]]:
"""
获取指定股票在指定天数内的历史数据
Args:
symbol (str): 股票代码 (如 'AAPL')
days (int): 时间窗口默认90天
Returns:
List[Dict[str, Any]]: 股票历史数据列表如果失败则返回None
"""
try:
# 计算开始日期
end_date = datetime.now()
start_date = end_date - timedelta(days=days)
print(f"🔍 正在获取 {symbol}{days} 天的数据...")
print(f" 时间范围: {start_date.strftime('%Y-%m-%d')}{end_date.strftime('%Y-%m-%d')}")
# 使用OpenBB获取数据延迟导入
try:
from openbb import obb # type: ignore
except Exception as e:
print(f"⚠️ OpenBB 未安装或导入失败: {e}")
return None
result = obb.equity.price.historical(
symbol=symbol,
provider='yfinance',
start_date=start_date.strftime('%Y-%m-%d'),
end_date=end_date.strftime('%Y-%m-%d')
)
results = getattr(result, 'results', None)
if results:
print(f"✅ 成功获取 {len(results)} 条记录")
return results
else:
print("❌ 未获取到数据")
return None
except Exception as e:
print(f"❌ 获取数据时出错: {str(e)}")
return None
def get_etf_data(symbol: str, days: int = 90) -> Optional[List[Dict[str, Any]]]:
"""
获取指定ETF在指定天数内的历史数据
Args:
symbol (str): ETF代码 (如 'SPY')
days (int): 时间窗口默认90天
Returns:
List[Dict[str, Any]]: ETF历史数据列表如果失败则返回None
"""
try:
# 计算开始日期
end_date = datetime.now()
start_date = end_date - timedelta(days=days)
print(f"🔍 正在获取 {symbol}{days} 天的数据...")
print(f" 时间范围: {start_date.strftime('%Y-%m-%d')}{end_date.strftime('%Y-%m-%d')}")
# 使用OpenBB获取数据延迟导入
try:
from openbb import obb # type: ignore
except Exception as e:
print(f"⚠️ OpenBB 未安装或导入失败: {e}")
return None
result = obb.etf.price.historical(
symbol=symbol,
provider='yfinance',
start_date=start_date.strftime('%Y-%m-%d'),
end_date=end_date.strftime('%Y-%m-%d')
)
results = getattr(result, 'results', None)
if results:
print(f"✅ 成功获取 {len(results)} 条记录")
return results
else:
print("❌ 未获取到数据")
return None
except Exception as e:
print(f"❌ 获取数据时出错: {str(e)}")
return None
def format_stock_data(data: List[Dict[str, Any]]) -> None:
"""
格式化并打印股票数据
Args:
data (List[Dict[str, Any]]): 股票数据列表
"""
if not data:
print("😔 没有数据可显示")
return
print(f"\n📊 股票数据预览 (显示最近5条记录):")
print("-" * 80)
print(f"{'日期':<12} {'开盘':<10} {'最高':<10} {'最低':<10} {'收盘':<10} {'成交量':<15}")
print("-" * 80)
# 只显示最近5条记录
for item in data[-5:]:
print(f"{str(item.date):<12} {item.open:<10.2f} {item.high:<10.2f} {item.low:<10.2f} {item.close:<10.2f} {item.volume:<15,}")
def format_etf_data(data: List[Dict[str, Any]]) -> None:
"""
格式化并打印ETF数据
Args:
data (List[Dict[str, Any]]): ETF数据列表
"""
if not data:
print("😔 没有数据可显示")
return
print(f"\n📊 ETF数据预览 (显示最近5条记录):")
print("-" * 80)
print(f"{'日期':<12} {'开盘':<10} {'最高':<10} {'最低':<10} {'收盘':<10} {'成交量':<15}")
print("-" * 80)
# 只显示最近5条记录
for item in data[-5:]:
print(f"{str(item.date):<12} {item.open:<10.2f} {item.high:<10.2f} {item.low:<10.2f} {item.close:<10.2f} {item.volume:<15,}")
def main():
"""主函数"""
# 示例获取AAPL股票和SPY ETF的数据
symbols = [("AAPL", "stock"), ("SPY", "etf")]
time_windows = [90, 720]
for symbol, asset_type in symbols:
for days in time_windows:
print(f"\n{'='*60}")
print(f"获取 {symbol} {days} 天数据")
print(f"{'='*60}")
if asset_type == "stock":
data = get_stock_data(symbol, days)
if data:
format_stock_data(data)
else:
data = get_etf_data(symbol, days)
if data:
format_etf_data(data)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,329 @@
#!/usr/bin/env python3
"""
稷下学宫永动机引擎
为八仙论道提供无限数据支撑
重构版本:
- 移除硬编码密钥
- 添加类型注解
- 改进错误处理
- 统一配置管理
"""
import requests
import time
from datetime import datetime
from typing import Dict, List, Any, Optional
from dataclasses import dataclass
@dataclass
class ImmortalConfig:
"""八仙配置数据类"""
primary: str
backup: List[str]
specialty: str
@dataclass
class APIResult:
"""API调用结果数据类"""
success: bool
data: Optional[Dict[str, Any]] = None
api_used: Optional[str] = None
usage_count: Optional[int] = None
error: Optional[str] = None
class JixiaPerpetualEngine:
"""稷下学宫永动机引擎"""
def __init__(self, rapidapi_key: str):
"""
初始化永动机引擎
Args:
rapidapi_key: RapidAPI密钥从环境变量或Doppler获取
"""
if not rapidapi_key:
raise ValueError("RapidAPI密钥不能为空")
self.rapidapi_key = rapidapi_key
# 八仙专属API分配 - 基于4个可用API优化
self.immortal_apis: Dict[str, ImmortalConfig] = {
'吕洞宾': ImmortalConfig( # 乾-技术分析专家
primary='alpha_vantage',
backup=['yahoo_finance_1'],
specialty='comprehensive_analysis'
),
'何仙姑': ImmortalConfig( # 坤-风险控制专家
primary='yahoo_finance_1',
backup=['webull'],
specialty='risk_management'
),
'张果老': ImmortalConfig( # 兑-历史数据分析师
primary='seeking_alpha',
backup=['alpha_vantage'],
specialty='fundamental_analysis'
),
'韩湘子': ImmortalConfig( # 艮-新兴资产专家
primary='webull',
backup=['yahoo_finance_1'],
specialty='emerging_trends'
),
'汉钟离': ImmortalConfig( # 离-热点追踪
primary='yahoo_finance_1',
backup=['webull'],
specialty='hot_trends'
),
'蓝采和': ImmortalConfig( # 坎-潜力股发现
primary='webull',
backup=['alpha_vantage'],
specialty='undervalued_stocks'
),
'曹国舅': ImmortalConfig( # 震-机构分析
primary='seeking_alpha',
backup=['alpha_vantage'],
specialty='institutional_analysis'
),
'铁拐李': ImmortalConfig( # 巽-逆向投资
primary='alpha_vantage',
backup=['seeking_alpha'],
specialty='contrarian_analysis'
)
}
# API池配置 - 只保留4个可用的API
self.api_configs: Dict[str, str] = {
'alpha_vantage': 'alpha-vantage.p.rapidapi.com', # 1.26s ⚡
'webull': 'webull.p.rapidapi.com', # 1.56s ⚡
'yahoo_finance_1': 'yahoo-finance15.p.rapidapi.com', # 2.07s
'seeking_alpha': 'seeking-alpha.p.rapidapi.com' # 3.32s
}
# 使用统计
self.usage_tracker: Dict[str, int] = {api: 0 for api in self.api_configs.keys()}
def get_immortal_data(self, immortal_name: str, data_type: str, symbol: str = 'AAPL') -> APIResult:
"""
为特定八仙获取专属数据
Args:
immortal_name: 八仙名称
data_type: 数据类型
symbol: 股票代码
Returns:
API调用结果
"""
if immortal_name not in self.immortal_apis:
return APIResult(success=False, error=f'Unknown immortal: {immortal_name}')
immortal_config = self.immortal_apis[immortal_name]
print(f"🧙‍♂️ {immortal_name} 请求 {data_type} 数据 (股票: {symbol})")
# 尝试主要API
result = self._call_api(immortal_config.primary, data_type, symbol)
if result.success:
print(f" ✅ 使用主要API: {immortal_config.primary}")
return result
# 故障转移到备用API
for backup_api in immortal_config.backup:
print(f" 🔄 故障转移到: {backup_api}")
result = self._call_api(backup_api, data_type, symbol)
if result.success:
print(f" ✅ 备用API成功: {backup_api}")
return result
print(f" ❌ 所有API都失败了")
return APIResult(success=False, error='All APIs failed')
def _call_api(self, api_name: str, data_type: str, symbol: str) -> APIResult:
"""
调用指定API
Args:
api_name: API名称
data_type: 数据类型
symbol: 股票代码
Returns:
API调用结果
"""
if api_name not in self.api_configs:
return APIResult(success=False, error=f'API {api_name} not configured')
host = self.api_configs[api_name]
headers = {
'X-RapidAPI-Key': self.rapidapi_key,
'X-RapidAPI-Host': host,
'Content-Type': 'application/json'
}
endpoint = self._get_endpoint(api_name, data_type, symbol)
if not endpoint:
return APIResult(success=False, error=f'No endpoint for {data_type} on {api_name}')
url = f"https://{host}{endpoint}"
try:
response = requests.get(url, headers=headers, timeout=8)
self.usage_tracker[api_name] += 1
if response.status_code == 200:
return APIResult(
success=True,
data=response.json(),
api_used=api_name,
usage_count=self.usage_tracker[api_name]
)
else:
return APIResult(
success=False,
error=f'HTTP {response.status_code}: {response.text[:100]}'
)
except requests.exceptions.Timeout:
return APIResult(success=False, error='Request timeout')
except requests.exceptions.RequestException as e:
return APIResult(success=False, error=f'Request error: {str(e)}')
except Exception as e:
return APIResult(success=False, error=f'Unexpected error: {str(e)}')
def _get_endpoint(self, api_name: str, data_type: str, symbol: str) -> Optional[str]:
"""
根据API和数据类型返回合适的端点
Args:
api_name: API名称
data_type: 数据类型
symbol: 股票代码
Returns:
API端点路径
"""
endpoint_mapping = {
'alpha_vantage': {
'quote': f'/query?function=GLOBAL_QUOTE&symbol={symbol}',
'overview': f'/query?function=OVERVIEW&symbol={symbol}',
'earnings': f'/query?function=EARNINGS&symbol={symbol}',
'profile': f'/query?function=OVERVIEW&symbol={symbol}',
'analysis': f'/query?function=OVERVIEW&symbol={symbol}'
},
'yahoo_finance_1': {
'quote': f'/api/yahoo/qu/quote/{symbol}',
'gainers': '/api/yahoo/co/collections/day_gainers',
'losers': '/api/yahoo/co/collections/day_losers',
'search': f'/api/yahoo/qu/quote/{symbol}',
'analysis': f'/api/yahoo/qu/quote/{symbol}',
'profile': f'/api/yahoo/qu/quote/{symbol}'
},
'seeking_alpha': {
'profile': f'/symbols/get-profile?symbols={symbol}',
'news': '/news/list?category=market-news',
'analysis': f'/symbols/get-profile?symbols={symbol}',
'quote': f'/symbols/get-profile?symbols={symbol}'
},
'webull': {
'search': f'/stock/search?keyword={symbol}',
'quote': f'/stock/search?keyword={symbol}',
'analysis': f'/stock/search?keyword={symbol}',
'gainers': '/market/get-active-gainers',
'profile': f'/stock/search?keyword={symbol}'
}
}
api_endpoints = endpoint_mapping.get(api_name, {})
return api_endpoints.get(data_type, api_endpoints.get('quote'))
def simulate_jixia_debate(self, topic_symbol: str = 'TSLA') -> Dict[str, APIResult]:
"""
模拟稷下学宫八仙论道
Args:
topic_symbol: 辩论主题股票代码
Returns:
八仙辩论结果
"""
print(f"🏛️ 稷下学宫八仙论道 - 主题: {topic_symbol}")
print("=" * 60)
debate_results: Dict[str, APIResult] = {}
# 数据类型映射
data_type_mapping = {
'comprehensive_analysis': 'overview',
'etf_tracking': 'quote',
'fundamental_analysis': 'profile',
'emerging_trends': 'news',
'hot_trends': 'gainers',
'undervalued_stocks': 'search',
'institutional_analysis': 'profile',
'contrarian_analysis': 'analysis'
}
# 八仙依次发言
for immortal_name, config in self.immortal_apis.items():
print(f"\n🎭 {immortal_name} ({config.specialty}) 发言:")
data_type = data_type_mapping.get(config.specialty, 'quote')
result = self.get_immortal_data(immortal_name, data_type, topic_symbol)
if result.success:
debate_results[immortal_name] = result
print(f" 💬 观点: 基于{result.api_used}数据的{config.specialty}分析")
else:
print(f" 😔 暂时无法获取数据: {result.error}")
time.sleep(0.5) # 避免过快请求
return debate_results
def get_usage_stats(self) -> Dict[str, Any]:
"""
获取使用统计信息
Returns:
统计信息字典
"""
total_calls = sum(self.usage_tracker.values())
active_apis = len([api for api, count in self.usage_tracker.items() if count > 0])
unused_apis = [api for api, count in self.usage_tracker.items() if count == 0]
return {
'total_calls': total_calls,
'active_apis': active_apis,
'total_apis': len(self.api_configs),
'average_calls_per_api': total_calls / len(self.api_configs) if self.api_configs else 0,
'usage_by_api': {api: count for api, count in self.usage_tracker.items() if count > 0},
'unused_apis': unused_apis,
'unused_count': len(unused_apis)
}
def print_perpetual_stats(self) -> None:
"""打印永动机统计信息"""
stats = self.get_usage_stats()
print(f"\n📊 永动机运行统计:")
print("=" * 60)
print(f"总API调用次数: {stats['total_calls']}")
print(f"活跃API数量: {stats['active_apis']}/{stats['total_apis']}")
print(f"平均每API调用: {stats['average_calls_per_api']:.1f}")
if stats['usage_by_api']:
print(f"\n各API使用情况:")
for api, count in stats['usage_by_api'].items():
print(f" {api}: {count}")
print(f"\n🎯 未使用的API储备: {stats['unused_count']}")
if stats['unused_apis']:
unused_display = ', '.join(stats['unused_apis'][:5])
if len(stats['unused_apis']) > 5:
unused_display += '...'
print(f"储备API: {unused_display}")
print(f"\n💡 永动机效果:")
print(f"{stats['total_apis']}个API订阅智能调度")
print(f" • 智能故障转移,永不断线")
print(f" • 八仙专属API个性化数据")
print(f" • 成本优化,效果最大化!")

View File

@@ -0,0 +1,48 @@
from typing import List, Optional
from src.jixia.engines.data_abstraction import DataProvider
from src.jixia.models.financial_data_models import StockQuote, HistoricalPrice, CompanyProfile, FinancialNews
from src.jixia.engines.perpetual_engine import JixiaPerpetualEngine
from config.settings import get_rapidapi_key
class RapidAPIDataProvider(DataProvider):
"""RapidAPI永动机引擎适配器"""
def __init__(self):
self.engine = JixiaPerpetualEngine(get_rapidapi_key())
self._name = "RapidAPI"
self._priority = 2 # 中等优先级
def get_quote(self, symbol: str) -> Optional[StockQuote]:
result = self.engine.get_immortal_data("吕洞宾", "quote", symbol)
if result.success and result.data:
# 解析RapidAPI返回的数据并转换为StockQuote
# 这里需要根据实际API返回的数据结构进行调整
return StockQuote(
symbol=symbol,
price=result.data.get("price", 0),
change=result.data.get("change", 0),
change_percent=result.data.get("change_percent", 0),
volume=result.data.get("volume", 0),
timestamp=result.data.get("timestamp")
)
return None
def get_historical_prices(self, symbol: str, days: int = 30) -> List[HistoricalPrice]:
# 实现历史价格数据获取逻辑
pass
def get_company_profile(self, symbol: str) -> Optional[CompanyProfile]:
# 实现公司概况获取逻辑
pass
def get_news(self, symbol: str, limit: int = 10) -> List[FinancialNews]:
# 实现新闻获取逻辑
pass
@property
def name(self) -> str:
return self._name
@property
def priority(self) -> int:
return self._priority