# 金融数据抽象层设计

## 概述

在"炼妖壶-稷下学宫AI辩论系统"中，我们需要构建一个统一的金融数据抽象层，以支持多种数据源（包括现有的RapidAPI永动机引擎、新增的OpenBB集成引擎，以及未来可能添加的其他数据提供商）。该抽象层将为上层AI智能体提供一致的数据接口，同时隐藏底层数据源的具体实现细节。

## 设计目标

1. **统一接口**：为所有金融数据访问提供一致的API
2. **可扩展性**：易于添加新的数据提供商
3. **容错性**：当主数据源不可用时，能够自动切换到备用数据源
4. **性能优化**：支持缓存和异步数据获取
5. **类型安全**：使用Python类型注解确保数据结构的一致性

## 核心组件

### 1. 数据模型 (Data Models)

定义标准化的金融数据结构：

```python
# src/jixia/models/financial_data_models.py

from dataclasses import dataclass
from typing import Optional, List
from datetime import datetime

@dataclass
class StockQuote:
    symbol: str
    price: float
    change: float
    change_percent: float
    volume: int
    timestamp: datetime

@dataclass
class HistoricalPrice:
    date: datetime
    open: float
    high: float
    low: float
    close: float
    volume: int

@dataclass
class CompanyProfile:
    symbol: str
    name: str
    industry: str
    sector: str
    market_cap: float
    pe_ratio: Optional[float]
    dividend_yield: Optional[float]

@dataclass
class FinancialNews:
    title: str
    summary: str
    url: str
    timestamp: datetime
    sentiment: Optional[float]  # -1 (负面) to 1 (正面)
```

### 2. 抽象基类 (Abstract Base Class)

定义数据提供商的通用接口：

```python
# src/jixia/engines/data_abstraction.py

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
```

### 3. Provider适配器 (Provider Adapters)

为每个具体的数据源实现适配器：

#### RapidAPI永动机引擎适配器

```python
# src/jixia/engines/rapidapi_adapter.py

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
```

#### OpenBB引擎适配器

```python
# src/jixia/engines/openbb_adapter.py

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
            return StockQuote(
                symbol=symbol,
                price=result.data.get("close", 0),
                change=0,  # 需要计算
                change_percent=0,  # 需要计算
                volume=result.data.get("volume", 0),
                timestamp=result.data.get("date")
            )
        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
```

### 4. 数据抽象层管理器 (Data Abstraction Layer Manager)

管理多个数据提供商并提供统一接口：

```python
# src/jixia/engines/data_abstraction_layer.py

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
import asyncio

class DataAbstractionLayer:
    """金融数据抽象层管理器"""
    
    def __init__(self):
        self.providers: List[DataProvider] = []
        self._initialize_providers()
    
    def _initialize_providers(self):
        """初始化所有可用的数据提供商"""
        # 根据配置和环境动态加载适配器
        try:
            from src.jixia.engines.rapidapi_adapter import RapidAPIDataProvider
            self.providers.append(RapidAPIDataProvider())
        except ImportError:
            pass  # RapidAPI引擎不可用
        
        try:
            from src.jixia.engines.openbb_adapter import OpenBBDataProvider
            self.providers.append(OpenBBDataProvider())
        except ImportError:
            pass  # OpenBB引擎不可用
        
        # 按优先级排序
        self.providers.sort(key=lambda p: p.priority)
    
    def get_quote(self, symbol: str) -> Optional[StockQuote]:
        """获取股票报价（带故障转移）"""
        for provider in self.providers:
            try:
                quote = provider.get_quote(symbol)
                if quote:
                    return quote
            except Exception as e:
                print(f"警告: {provider.name} 获取报价失败: {e}")
                continue
        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:
                    return quote
            except Exception as e:
                print(f"警告: {provider.name} 获取报价失败: {e}")
                continue
        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:
                    return prices
            except Exception as e:
                print(f"警告: {provider.name} 获取历史价格失败: {e}")
                continue
        return []
    
    def get_company_profile(self, symbol: str) -> Optional[CompanyProfile]:
        """获取公司概况（带故障转移）"""
        for provider in self.providers:
            try:
                profile = provider.get_company_profile(symbol)
                if profile:
                    return profile
            except Exception as e:
                print(f"警告: {provider.name} 获取公司概况失败: {e}")
                continue
        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:
                    return news
            except Exception as e:
                print(f"警告: {provider.name} 获取新闻失败: {e}")
                continue
        return []
```

## 八仙与数据源的智能映射

```python
# src/jixia/engines/baxian_data_mapping.py

# 设计八仙与数据源的智能映射
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']
    }
}
```

## 缓存策略

为了提高性能，我们将实现多级缓存策略：

```python
# src/jixia/engines/data_cache.py

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
```

## 数据质量监控机制

为了确保数据的准确性和可靠性，我们将实现数据质量监控：

```python
# src/jixia/engines/data_quality_monitor.py

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']
        }
```

## 使用示例

```python
# 示例：在智能体中使用数据抽象层
from src.jixia.engines.data_abstraction_layer import DataAbstractionLayer
from src.jixia.models.financial_data_models import StockQuote

# 初始化数据抽象层
dal = DataAbstractionLayer()

# 获取股票报价
quote = dal.get_quote("AAPL")
if quote:
    print(f"Apple股价: ${quote.price}")
else:
    print("无法获取股价数据")

# 异步获取报价
import asyncio
async def async_example():
    quote = await dal.get_quote_async("GOOGL")
    if quote:
        print(f"Google股价: ${quote.price}")

# asyncio.run(async_example())
```

## 总结

这个金融数据抽象层设计提供了以下优势：

1. **统一接口**：所有智能体都可以通过相同的接口访问任何数据源
2. **故障转移**：当主数据源不可用时，自动切换到备用数据源
3. **可扩展性**：可以轻松添加新的数据提供商适配器
4. **性能优化**：通过缓存机制提高数据访问速度
5. **质量监控**：实时监控各数据源的健康状况
6. **文化融合**：通过八仙与数据源的智能映射，保持项目的文化特色

这将为"炼妖壶-稷下学宫AI辩论系统"提供一个强大、可靠且可扩展的金融数据基础。