tushare_api.py

"""
QTrading Tushare 数据获取模块
"""

import pandas as pd
import tushare as ts
from typing import Optional, List
from config.config import config
from utils.logger import get_logger

logger = get_logger(__name__)

class TushareDataFetcher:
    """Tushare 数据获取器"""

    def __init__(self, token: str = None):
        """
        初始化 Tushare 数据获取器

        Args:
            token: Tushare 接口令牌(可选,默认从配置文件读取)
        """
        self.token = token or config.tushare.token
        self._pro = None
        self._init_tushare()

    def _init_tushare(self) -> None:
        """初始化 Tushare 接口"""
        try:
            ts.set_token(self.token)
            self._pro = ts.pro_api()
            logger.info("Tushare 接口初始化成功")
        except Exception as e:
            logger.error(f"Tushare 接口初始化失败: {e}")
            self._pro = None

    def get_daily_kline(self, ts_code: str, start_date: str, end_date: str) -> Optional[pd.DataFrame]:
        """
        获取股票日线数据

        Args:
            ts_code: 股票代码(如 '000001.SZ')
            start_date: 开始日期(YYYYMMDD)
            end_date: 结束日期(YYYYMMDD)

        Returns:
            pd.DataFrame: 日线数据(包含 ts_code, trade_date, open, high, low, close, vol, amount)
        """
        if self._pro is None:
            logger.error("Tushare 接口未初始化")
            return None

        try:
            logger.info(f"正在获取股票 {ts_code} 日线数据 ({start_date} - {end_date})")
            data = self._pro.daily(
                ts_code=ts_code,
                start_date=start_date,
                end_date=end_date
            )
            if data is not None and not data.empty:
                logger.info(f"成功获取 {len(data)} 条数据")
                return data
            else:
                logger.warning(f"未找到股票 {ts_code} 在指定日期范围内的数据")
                return None
        except Exception as e:
            logger.error(f"获取股票 {ts_code} 日线数据失败: {e}")
            return None

    def get_adj_factor(self, ts_code: str, start_date: str, end_date: str) -> Optional[pd.DataFrame]:
        """
        获取股票复权因子

        Args:
            ts_code: 股票代码(如 '000001.SZ')
            start_date: 开始日期(YYYYMMDD)
            end_date: 结束日期(YYYYMMDD)

        Returns:
            pd.DataFrame: 复权因子数据(包含 ts_code, trade_date, adj_factor)
        """
        if self._pro is None:
            logger.error("Tushare 接口未初始化")
            return None

        try:
            logger.info(f"正在获取股票 {ts_code} 复权因子 ({start_date} - {end_date})")
            data = self._pro.adj_factor(
                ts_code=ts_code,
                start_date=start_date,
                end_date=end_date
            )
            if data is not None and not data.empty:
                logger.info(f"成功获取 {len(data)} 条复权因子数据")
                return data
            else:
                logger.warning(f"未找到股票 {ts_code} 在指定日期范围内的复权因子")
                return None
        except Exception as e:
            logger.error(f"获取股票 {ts_code} 复权因子失败: {e}")
            return None

    def get_stock_basic(self, market: str = None) -> Optional[pd.DataFrame]:
        """
        获取股票基本信息

        Args:
            market: 市场类型(可选,如 'SZ' 或 'SH')

        Returns:
            pd.DataFrame: 股票基本信息
        """
        if self._pro is None:
            logger.error("Tushare 接口未初始化")
            return None

        try:
            logger.info("正在获取股票基本信息")
            data = self._pro.stock_basic(exchange='', list_status='L')
            if market:
                data = data[data['market'] == market]
            logger.info(f"成功获取 {len(data)} 只股票基本信息")
            return data
        except Exception as e:
            logger.error(f"获取股票基本信息失败: {e}")
            return None

    def get_index_daily(self, ts_code: str, start_date: str, end_date: str) -> Optional[pd.DataFrame]:
        """
        获取指数日线数据

        Args:
            ts_code: 指数代码(如 '000001.SH')
            start_date: 开始日期(YYYYMMDD)
            end_date: 结束日期(YYYYMMDD)

        Returns:
            pd.DataFrame: 指数日线数据
        """
        if self._pro is None:
            logger.error("Tushare 接口未初始化")
            return None

        try:
            logger.info(f"正在获取指数 {ts_code} 日线数据 ({start_date} - {end_date})")
            data = self._pro.index_daily(
                ts_code=ts_code,
                start_date=start_date,
                end_date=end_date
            )
            if data is not None and not data.empty:
                logger.info(f"成功获取 {len(data)} 条指数数据")
                return data
            else:
                logger.warning(f"未找到指数 {ts_code} 在指定日期范围内的数据")
                return None
        except Exception as e:
            logger.error(f"获取指数 {ts_code} 日线数据失败: {e}")
            return None

    def get_trade_cal(self, start_date: str, end_date: str) -> Optional[pd.DataFrame]:
        """
        获取交易日历

        Args:
            start_date: 开始日期(YYYYMMDD)
            end_date: 结束日期(YYYYMMDD)

        Returns:
            pd.DataFrame: 交易日历
        """
        if self._pro is None:
            logger.error("Tushare 接口未初始化")
            return None

        try:
            logger.info(f"正在获取交易日历 ({start_date} - {end_date})")
            data = self._pro.trade_cal(
                start_date=start_date,
                end_date=end_date
            )
            logger.info(f"成功获取 {len(data)} 个交易日")
            return data
        except Exception as e:
            logger.error(f"获取交易日历失败: {e}")
            return None