| |
| import pandas as pd |
| import numpy as np |
| from typing import Dict, List, Any, Optional |
| import datetime |
| import logging |
| import io |
| from collections import OrderedDict |
|
|
|
|
| class DataServiceMemory: |
| """ |
| 数据服务类,负责获取和管理股票数据 |
| """ |
|
|
| def __init__(self, config: Dict[str, Any] = None): |
| """ |
| 初始化数据服务 |
| |
| Args: |
| config: 配置信息,包括数据源配置等 |
| """ |
| self.config = config or {} |
| self.logger = logging.getLogger(__name__) |
| |
| self.data_cache = OrderedDict() |
| self.stock_data = {} |
| self.factor_data = {} |
| self.backtest_results = {} |
| |
| self.CACHE_MAX_SIZE = 100 |
|
|
| def get_stock_list(self) -> List[Dict[str, Any]]: |
| """ |
| 获取股票列表,从API实时获取,不写入本地 |
| |
| Returns: |
| 股票列表,包含股票代码、名称等信息 |
| """ |
| |
| |
| return [ |
| {"code": "000001.SZ", "name": "平安银行", "industry": "银行"}, |
| {"code": "000002.SZ", "name": "万科A", "industry": "房地产"}, |
| {"code": "000063.SZ", "name": "中兴通讯", "industry": "通信设备"}, |
| {"code": "600036.SH", "name": "招商银行", "industry": "银行"}, |
| {"code": "600519.SH", "name": "贵州茅台", "industry": "白酒"} |
| ] |
|
|
| def get_daily_data( |
| self, |
| stock_codes: List[str], |
| start_date: str, |
| end_date: str) -> pd.DataFrame: |
| """ |
| 获取日线数据,从API实时获取,并缓存 |
| |
| Args: |
| stock_codes: 股票代码列表 |
| start_date: 开始日期,格式:YYYY-MM-DD |
| end_date: 结束日期,格式:YYYY-MM-DD |
| |
| Returns: |
| 日线数据DataFrame |
| """ |
| cache_key = f"{','.join(stock_codes)}_{start_date}_{end_date}" |
|
|
| |
| if cache_key in self.data_cache: |
| self.logger.info(f"从缓存获取日线数据: {cache_key}") |
| |
| self.data_cache.move_to_end(cache_key) |
| return self.data_cache[cache_key] |
|
|
| |
| |
| self.logger.info(f"从API获取日线数据: {stock_codes}, {start_date} - {end_date}") |
|
|
| |
| start = datetime.datetime.strptime(start_date, "%Y-%m-%d") |
| end = datetime.datetime.strptime(end_date, "%Y-%m-%d") |
| date_range = [ |
| start + |
| datetime.timedelta( |
| days=x) for x in range( |
| (end - |
| start).days + |
| 1)] |
| date_strings = [d.strftime("%Y-%m-%d") for d in date_range] |
|
|
| |
| data = [] |
| for code in stock_codes: |
| base_price = np.random.randint(10, 100) |
| for date in date_strings: |
| price_change = np.random.normal(0, 1) * base_price * 0.01 |
| open_price = base_price + price_change |
| close_price = open_price + np.random.normal(0, 1) * base_price * 0.005 |
| high_price = max(open_price, close_price) + \ |
| np.random.normal(0, 1) * base_price * 0.003 |
| low_price = min(open_price, close_price) - \ |
| np.random.normal(0, 1) * base_price * 0.003 |
| volume = np.random.randint(1000000, 10000000) |
|
|
| data.append({ |
| "code": code, |
| "date": date, |
| "open": open_price, |
| "high": high_price, |
| "low": low_price, |
| "close": close_price, |
| "volume": volume, |
| "amount": volume * close_price |
| }) |
|
|
| base_price = close_price |
|
|
| |
| df = pd.DataFrame(data) |
|
|
| |
| self.data_cache[cache_key] = df |
| |
| if len(self.data_cache) > self.CACHE_MAX_SIZE: |
| oldest_key = next(iter(self.data_cache)) |
| self.data_cache.popitem(last=False) |
| self.logger.info(f"缓存超出限制,移除最旧条目: {oldest_key}") |
|
|
| return df |
|
|
| def get_financial_data( |
| self, |
| stock_codes: List[str], |
| period: str = "latest") -> pd.DataFrame: |
| """ |
| 获取财务数据,从API实时获取,并缓存 |
| |
| Args: |
| stock_codes: 股票代码列表 |
| period: 财报期间,如 'latest', '20220331', '20211231' 等 |
| |
| Returns: |
| 财务数据DataFrame |
| """ |
| cache_key = f"financial_{','.join(stock_codes)}_{period}" |
|
|
| |
| if cache_key in self.data_cache: |
| self.logger.info(f"从缓存获取财务数据: {cache_key}") |
| |
| self.data_cache.move_to_end(cache_key) |
| return self.data_cache[cache_key] |
|
|
| |
| |
| self.logger.info(f"从API获取财务数据: {stock_codes}, {period}") |
|
|
| |
| data = [] |
| for code in stock_codes: |
| revenue = np.random.randint(1000000, 1000000000) |
| net_profit = revenue * np.random.uniform(0.05, 0.2) |
| total_assets = revenue * np.random.uniform(1, 5) |
| total_equity = total_assets * np.random.uniform(0.3, 0.7) |
|
|
| data.append({ |
| "code": code, |
| "period": period, |
| "revenue": revenue, |
| "net_profit": net_profit, |
| "total_assets": total_assets, |
| "total_equity": total_equity, |
| "roe": net_profit / total_equity, |
| "roa": net_profit / total_assets, |
| "net_margin": net_profit / revenue, |
| "debt_ratio": (total_assets - total_equity) / total_assets |
| }) |
|
|
| |
| df = pd.DataFrame(data) |
|
|
| |
| self.data_cache[cache_key] = df |
| |
| if len(self.data_cache) > self.CACHE_MAX_SIZE: |
| oldest_key = next(iter(self.data_cache)) |
| self.data_cache.popitem(last=False) |
| self.logger.info(f"缓存超出限制,移除最旧条目: {oldest_key}") |
|
|
| return df |
|
|
| def save_backtest_result(self, strategy_id: str, result_data: Dict[str, Any]): |
| """ |
| 保存回测结果 |
| |
| Args: |
| strategy_id: 策略ID |
| result_data: 回测结果数据 |
| """ |
| self.backtest_results[strategy_id] = result_data |
| self.logger.info(f"回测结果已保存: {strategy_id}") |
|
|
| def get_backtest_result(self, strategy_id: str) -> Optional[Dict[str, Any]]: |
| """ |
| 从缓存获取回测结果 |
| |
| Args: |
| strategy_id: 策略ID |
| |
| Returns: |
| 回测结果数据,如果不存在则返回None |
| """ |
| return self.backtest_results.get(strategy_id) |
|
|
| def clear_cache(self): |
| """ |
| 清除内存缓存 |
| """ |
| self.data_cache.clear() |
| self.logger.info("缓存已清除") |
|
|
| def export_data(self, data: pd.DataFrame, format: str = "csv") -> bytes: |
| """ |
| 导出数据为字节流 |
| |
| Args: |
| data: 要导出的数据 |
| format: 导出格式,支持 'csv', 'excel', 'json' |
| |
| Returns: |
| 字节数据 |
| """ |
| buffer = io.BytesIO() |
|
|
| if format.lower() == "csv": |
| data.to_csv(buffer, index=False, encoding="utf-8") |
| elif format.lower() == "excel": |
| data.to_excel(buffer, index=False, engine="openpyxl") |
| elif format.lower() == "json": |
| buffer.write(data.to_json(orient="records").encode("utf-8")) |
| else: |
| raise ValueError(f"不支持的导出格式: {format}") |
|
|
| buffer.seek(0) |
| return buffer.getvalue() |
|
|