266 lines
8.1 KiB
Python
266 lines
8.1 KiB
Python
|
|
"""
|
|||
|
|
Wind 数据获取模块
|
|||
|
|
提供统一的接口获取股票历史数据、财务数据和技术指标
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from WindPy import w
|
|||
|
|
import pandas as pd
|
|||
|
|
from datetime import datetime, timedelta
|
|||
|
|
from typing import List, Dict, Optional, Tuple
|
|||
|
|
|
|||
|
|
|
|||
|
|
class WindDataFetcher:
|
|||
|
|
"""Wind数据获取器"""
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
"""初始化Wind连接"""
|
|||
|
|
self._start()
|
|||
|
|
|
|||
|
|
def _start(self):
|
|||
|
|
"""启动Wind连接"""
|
|||
|
|
result = w.start()
|
|||
|
|
if result.ErrorCode != 0:
|
|||
|
|
raise ConnectionError(f"Wind连接失败: {result.Data}")
|
|||
|
|
print("Wind连接成功")
|
|||
|
|
|
|||
|
|
def _stop(self):
|
|||
|
|
"""关闭Wind连接"""
|
|||
|
|
w.stop()
|
|||
|
|
|
|||
|
|
def __enter__(self):
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|||
|
|
self._stop()
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
def _handle_wsd_result(result, field_names):
|
|||
|
|
"""处理wsd返回结果"""
|
|||
|
|
if result.ErrorCode != 0:
|
|||
|
|
raise ValueError(f"数据获取失败: {result.Data}")
|
|||
|
|
|
|||
|
|
# 转换为DataFrame
|
|||
|
|
data_dict = dict(zip(field_names, result.Data))
|
|||
|
|
df = pd.DataFrame(data_dict, columns=field_names)
|
|||
|
|
df['date'] = result.Times
|
|||
|
|
return df
|
|||
|
|
|
|||
|
|
def get_daily_data(self,
|
|||
|
|
stock_code: str,
|
|||
|
|
start_date: str,
|
|||
|
|
end_date: str,
|
|||
|
|
fields: List[str]) -> pd.DataFrame:
|
|||
|
|
"""
|
|||
|
|
获取日线数据
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
stock_code: 股票代码,如 '300866.SZ'
|
|||
|
|
start_date: 开始日期,格式 'YYYY-MM-DD'
|
|||
|
|
end_date: 结束日期,格式 'YYYY-MM-DD'
|
|||
|
|
fields: 数据字段列表
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
DataFrame包含日期和指定字段的数据
|
|||
|
|
"""
|
|||
|
|
result = w.wsd(stock_code, fields, start_date, end_date)
|
|||
|
|
return self._handle_wsd_result(result, fields)
|
|||
|
|
|
|||
|
|
def get_stock_basic(self, stock_code: str) -> Dict:
|
|||
|
|
"""
|
|||
|
|
获取股票基本信息
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
stock_code: 股票代码
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
包含股票名称、行业、上市日期等信息的字典
|
|||
|
|
"""
|
|||
|
|
fields = ['sec_name', 'industry', 'ipo_date', 'list_date', 'trade_status']
|
|||
|
|
result = w.wss(stock_code, fields)
|
|||
|
|
|
|||
|
|
if result.ErrorCode != 0:
|
|||
|
|
raise ValueError(f"基本信息获取失败: {result.Data}")
|
|||
|
|
|
|||
|
|
return dict(zip(fields, result.Data[0]))
|
|||
|
|
|
|||
|
|
def get_technical_indicators(self,
|
|||
|
|
stock_code: str,
|
|||
|
|
start_date: str,
|
|||
|
|
end_date: str) -> pd.DataFrame:
|
|||
|
|
"""
|
|||
|
|
获取技术指标数据
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
stock_code: 股票代码
|
|||
|
|
start_date: 开始日期
|
|||
|
|
end_date: 结束日期
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
DataFrame包含MA、成交量等技术指标
|
|||
|
|
"""
|
|||
|
|
fields = [
|
|||
|
|
'close', 'open', 'high', 'low', 'volume', 'amt', 'pct_chg',
|
|||
|
|
'ma5', 'ma10', 'ma20', 'ma60',
|
|||
|
|
'pe_ttm', 'pb_lf', 'ps_ttm', 'pcf_ncf_ttm'
|
|||
|
|
]
|
|||
|
|
return self.get_daily_data(stock_code, start_date, end_date, fields)
|
|||
|
|
|
|||
|
|
def get_financial_data(self,
|
|||
|
|
stock_code: str,
|
|||
|
|
report_date: str = '') -> Dict:
|
|||
|
|
"""
|
|||
|
|
获取财务数据
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
stock_code: 股票代码
|
|||
|
|
report_date: 报告期,格式 'YYYYMMDD',默认最新
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
包含ROE、营收、净利润等财务指标的字典
|
|||
|
|
"""
|
|||
|
|
fields = [
|
|||
|
|
'roe_wgt', 'roa2', 'net_profit_to_profit', 'total_revenue_ps',
|
|||
|
|
'profit_to_gr', 'ebit_ps', 'assets_to_eqt', 'debt_to_assets',
|
|||
|
|
'current_ratio', 'quick_ratio', 'op_income_to_revenue'
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
result = w.wss(stock_code, fields, 'rptDate={}'.format(report_date))
|
|||
|
|
|
|||
|
|
if result.ErrorCode != 0:
|
|||
|
|
raise ValueError(f"财务数据获取失败: {result.Data}")
|
|||
|
|
|
|||
|
|
return dict(zip(fields, result.Data[0]))
|
|||
|
|
|
|||
|
|
def get_weekly_data(self,
|
|||
|
|
stock_code: str,
|
|||
|
|
start_date: str,
|
|||
|
|
end_date: str,
|
|||
|
|
fields: List[str] = None) -> pd.DataFrame:
|
|||
|
|
"""
|
|||
|
|
获取并汇总周线数据
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
stock_code: 股票代码
|
|||
|
|
start_date: 开始日期
|
|||
|
|
end_date: 结束日期
|
|||
|
|
fields: 数据字段,默认为基本OHLCV
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
DataFrame包含周线数据
|
|||
|
|
"""
|
|||
|
|
if fields is None:
|
|||
|
|
fields = ['close', 'open', 'high', 'low', 'volume', 'amt', 'pct_chg']
|
|||
|
|
|
|||
|
|
# 获取日线数据
|
|||
|
|
df_daily = self.get_daily_data(stock_code, start_date, end_date, fields)
|
|||
|
|
|
|||
|
|
# 转换为周线数据
|
|||
|
|
df_daily.set_index('date', inplace=True)
|
|||
|
|
df_weekly = df_daily.resample('W').agg({
|
|||
|
|
'open': 'first',
|
|||
|
|
'high': 'max',
|
|||
|
|
'low': 'min',
|
|||
|
|
'close': 'last',
|
|||
|
|
'volume': 'sum',
|
|||
|
|
'amt': 'sum',
|
|||
|
|
'pct_chg': 'sum' # 周涨跌幅近似为日涨跌幅之和
|
|||
|
|
}).dropna()
|
|||
|
|
|
|||
|
|
df_weekly.reset_index(inplace=True)
|
|||
|
|
return df_weekly
|
|||
|
|
|
|||
|
|
def get_stock_list_by_sector(self, sector_name: str, limit: int = 10) -> List[str]:
|
|||
|
|
"""
|
|||
|
|
获取某行业的龙头股票列表
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
sector_name: 行业名称
|
|||
|
|
limit: 返回数量
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
股票代码列表
|
|||
|
|
"""
|
|||
|
|
# 使用Wind的行业板块数据
|
|||
|
|
fields = ['sec_name', 'ipo_date']
|
|||
|
|
# 这里简化处理,实际需要根据Wind板块接口调整
|
|||
|
|
print(f"注意: 行业股票列表获取需要根据实际Wind板块接口实现")
|
|||
|
|
return []
|
|||
|
|
|
|||
|
|
def calculate_volatility(self, df: pd.DataFrame, annualized: bool = True) -> float:
|
|||
|
|
"""
|
|||
|
|
计算波动率
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
df: 包含收盘价的DataFrame
|
|||
|
|
annualized: 是否年化
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
波动率(百分比)
|
|||
|
|
"""
|
|||
|
|
returns = df['close'].pct_change().dropna()
|
|||
|
|
volatility = returns.std()
|
|||
|
|
|
|||
|
|
if annualized:
|
|||
|
|
# 日数据年化系数:sqrt(252),周数据年化系数:sqrt(52)
|
|||
|
|
n = len(df)
|
|||
|
|
if n > 250: # 日线数据
|
|||
|
|
volatility *= 252 ** 0.5
|
|||
|
|
else: # 周线数据
|
|||
|
|
volatility *= 52 ** 0.5
|
|||
|
|
|
|||
|
|
return volatility * 100 # 转换为百分比
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 便捷函数
|
|||
|
|
def fetch_stock_report_data(stock_code: str,
|
|||
|
|
days_back: int = 90,
|
|||
|
|
weekly: bool = True) -> Tuple[pd.DataFrame, Dict]:
|
|||
|
|
"""
|
|||
|
|
获取股票报告所需的数据
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
stock_code: 股票代码
|
|||
|
|
days_back: 获取多少天的数据
|
|||
|
|
weekly: 是否为周线数据
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
(price_df, financial_dict) 价格数据和财务数据
|
|||
|
|
"""
|
|||
|
|
end_date = datetime.now().strftime('%Y-%m-%d')
|
|||
|
|
start_date = (datetime.now() - timedelta(days=days_back)).strftime('%Y-%m-%d')
|
|||
|
|
|
|||
|
|
with WindDataFetcher() as fetcher:
|
|||
|
|
# 获取价格数据
|
|||
|
|
if weekly:
|
|||
|
|
price_df = fetcher.get_weekly_data(stock_code, start_date, end_date)
|
|||
|
|
else:
|
|||
|
|
price_df = fetcher.get_technical_indicators(stock_code, start_date, end_date)
|
|||
|
|
|
|||
|
|
# 获取财务数据
|
|||
|
|
financial_data = fetcher.get_financial_data(stock_code)
|
|||
|
|
|
|||
|
|
# 获取基本信息
|
|||
|
|
basic_info = fetcher.get_stock_basic(stock_code)
|
|||
|
|
|
|||
|
|
# 合并财务信息
|
|||
|
|
financial_data.update(basic_info)
|
|||
|
|
|
|||
|
|
return price_df, financial_data
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == '__main__':
|
|||
|
|
# 测试代码
|
|||
|
|
with WindDataFetcher() as fetcher:
|
|||
|
|
# 示例:获取某股票的数据
|
|||
|
|
code = '300866.SZ'
|
|||
|
|
start = '2025-01-01'
|
|||
|
|
end = '2026-02-06'
|
|||
|
|
|
|||
|
|
print(f"获取 {code} 的日线数据...")
|
|||
|
|
df = fetcher.get_technical_indicators(code, start, end)
|
|||
|
|
print(df.tail())
|
|||
|
|
|
|||
|
|
print(f"\n获取 {code} 的基本信息...")
|
|||
|
|
info = fetcher.get_stock_basic(code)
|
|||
|
|
print(info)
|