Initial commit: cc-cursor 全链路量化研究平台
7 Sprints 全部完成: Sprint 0: 基础设施 (DataManager + MariaDB) Sprint 1: 因子引擎 (34因子/12分类) Sprint 2: VectorBT 回测 (5策略+截面) Sprint 3: Optuna 优化 (+Walk-Forward) Sprint 4: ML 模型 (LightGBM+CatBoost) Sprint 5: Qwen 情绪因子 (三源新闻+日期对齐) Sprint 6: Agent 系统 (4Agent+日报.md/.html) 生产加固 (15项): Tushare双源fallback, SSH自动恢复, pool_pre_ping, save_daily先删后插, load_dotenv绝对路径, 日报5d/20d修复, RiskAgent改上证指数, 昨日对比+数据截止, mac_report utf8mb4, CLAUDE-*.md 9条已知Bug, demo全参数化, djapi数据源归一化, indexDatas API修正 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
"""
|
||||
⚠️ 已废弃 — 2026-06-05
|
||||
|
||||
AkShare 股息率数据获取功能已归一化到 data_source.py。
|
||||
如需 AkShare 日线数据,请使用:
|
||||
|
||||
from .data_source import get_daily
|
||||
df = get_daily(ts_code, start, end, source='akshare')
|
||||
|
||||
本文件保留仅用于向后兼容,所有公开函数委托给统一数据源。
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from .data_source import get_daily
|
||||
|
||||
from .stock_utils import tscodeCheck, date_format_correction
|
||||
from .smoothBrush import smooth_dataframe_brush
|
||||
|
||||
GAP_DAYS = 360 # TTM 计算窗口
|
||||
|
||||
|
||||
def _parse_tscode(tscode: str) -> tuple:
|
||||
"""将 tscode(如 000001.SZ)转为 akshare 格式的 symbol 和 market"""
|
||||
code = tscode.split('.')[0]
|
||||
suffix = tscode.split('.')[1].lower()
|
||||
market_map = {'sz': 'sz', 'sh': 'sh', 'bj': 'bj'}
|
||||
return code, market_map.get(suffix, suffix)
|
||||
|
||||
|
||||
def _fetch_daily_price(symbol: str, start_date: str, end_date: str) -> pd.DataFrame:
|
||||
"""通过 akshare 获取前复权日线行情"""
|
||||
df = ak.stock_zh_a_hist(
|
||||
symbol=symbol,
|
||||
period='daily',
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
adjust='qfq'
|
||||
)
|
||||
if df.empty:
|
||||
return pd.DataFrame()
|
||||
|
||||
df = df.rename(columns={
|
||||
'日期': 'trade_date',
|
||||
'开盘': 'open',
|
||||
'收盘': 'close',
|
||||
'最高': 'high',
|
||||
'最低': 'low',
|
||||
'成交量': 'vol',
|
||||
'成交额': 'amount',
|
||||
'换手率': 'turnover_rate',
|
||||
})
|
||||
df['trade_date'] = df['trade_date'].astype(str).str.replace('-', '')
|
||||
return df
|
||||
|
||||
|
||||
def _fetch_dividends(symbol: str, market: str) -> pd.DataFrame:
|
||||
"""通过 akshare 获取历史分红记录"""
|
||||
try:
|
||||
df = ak.stock_dividend_cninfo(stock=symbol, symbol=market + symbol)
|
||||
except Exception:
|
||||
return pd.DataFrame()
|
||||
|
||||
if df.empty:
|
||||
return pd.DataFrame()
|
||||
|
||||
# 列名映射(akshare 返回中文列名)
|
||||
col_map = {
|
||||
'除权除息日': 'ex_date',
|
||||
'每股派息': 'cash_div_tax',
|
||||
}
|
||||
df = df.rename(columns={k: v for k, v in col_map.items() if k in df.columns})
|
||||
|
||||
if 'ex_date' not in df.columns or 'cash_div_tax' not in df.columns:
|
||||
return pd.DataFrame()
|
||||
|
||||
df['ex_date'] = df['ex_date'].astype(str).str.replace('-', '').str[:8]
|
||||
df['cash_div_tax'] = pd.to_numeric(df['cash_div_tax'], errors='coerce').fillna(0)
|
||||
return df[['ex_date', 'cash_div_tax']]
|
||||
|
||||
|
||||
def get_akshare_dividend_yield(tscode: str, start_date: str = None, end_date: str = None) -> pd.DataFrame:
|
||||
"""
|
||||
通过 akshare 获取股价和分红数据,计算股息率。
|
||||
|
||||
Args:
|
||||
tscode: 股票代码,如 '000001.SZ'
|
||||
start_date: 起始日期 yyyyMMdd
|
||||
end_date: 结束日期 yyyyMMdd
|
||||
|
||||
Returns:
|
||||
DataFrame: ts_code, trade_date, close, cash_div_tax, cash_div_year, div_yield
|
||||
"""
|
||||
tscode = tscodeCheck(tscode)
|
||||
today = datetime.now().strftime('%Y%m%d')
|
||||
|
||||
if start_date:
|
||||
start_date = date_format_correction(start_date)
|
||||
else:
|
||||
start_date = '20200101'
|
||||
if end_date:
|
||||
end_date = date_format_correction(end_date)
|
||||
else:
|
||||
end_date = today
|
||||
|
||||
if end_date > today:
|
||||
end_date = today
|
||||
|
||||
symbol, market = _parse_tscode(tscode)
|
||||
|
||||
# 1. 获取日线行情(运算时起始日期往前推 GAP_DAYS)
|
||||
calc_start = datetime.strptime(start_date, '%Y%m%d') - timedelta(days=GAP_DAYS)
|
||||
calc_start_str = calc_start.strftime('%Y%m%d')
|
||||
|
||||
df_price = _fetch_daily_price(symbol, calc_start_str, end_date)
|
||||
if df_price.empty:
|
||||
return pd.DataFrame()
|
||||
df_price = df_price.sort_values('trade_date').reset_index(drop=True)
|
||||
|
||||
# 2. 获取分红数据
|
||||
df_div = _fetch_dividends(symbol, market)
|
||||
|
||||
# 3. 计算 TTM 分红序列
|
||||
if df_div.empty:
|
||||
df_price['cash_div_tax'] = 0.0
|
||||
df_price['cash_div_year'] = 0.0
|
||||
else:
|
||||
# 将分红按 ex_date 合并到交易日历
|
||||
df_div['ex_date'] = pd.to_datetime(df_div['ex_date'], format='%Y%m%d')
|
||||
df_price['trade_date_dt'] = pd.to_datetime(df_price['trade_date'], format='%Y%m%d')
|
||||
|
||||
# 按日期合并
|
||||
div_dict = df_div.set_index('ex_date')['cash_div_tax'].to_dict()
|
||||
|
||||
def calc_ttm_div(trade_dt):
|
||||
window_start = trade_dt - timedelta(days=GAP_DAYS)
|
||||
total = 0.0
|
||||
for ex_dt, cash in div_dict.items():
|
||||
if window_start < ex_dt <= trade_dt:
|
||||
total += cash
|
||||
return total
|
||||
|
||||
cash_div_tax_list = []
|
||||
cash_div_year_list = []
|
||||
|
||||
for _, row in df_price.iterrows():
|
||||
td = row['trade_date_dt']
|
||||
cash = div_dict.get(td, 0.0)
|
||||
cash_div_tax_list.append(cash)
|
||||
cash_div_year_list.append(calc_ttm_div(td))
|
||||
|
||||
df_price['cash_div_tax'] = cash_div_tax_list
|
||||
df_price['cash_div_year'] = cash_div_year_list
|
||||
df_price = df_price.drop(columns=['trade_date_dt'])
|
||||
|
||||
# 4. 毛刺平滑
|
||||
df_price = smooth_dataframe_brush(
|
||||
df_price,
|
||||
target_columns=['cash_div_year'],
|
||||
window_size=31,
|
||||
threshold_factor=0.5,
|
||||
max_brush_length=15
|
||||
)
|
||||
|
||||
# 5. 截取请求的时间范围
|
||||
df_result = df_price[
|
||||
(df_price['trade_date'] >= start_date) & (df_price['trade_date'] <= end_date)
|
||||
].copy()
|
||||
|
||||
# 6. 计算股息率
|
||||
df_result['div_yield'] = 0.0
|
||||
mask = (df_result['close'] > 0) & (df_result['cash_div_year'] > 0)
|
||||
df_result.loc[mask, 'div_yield'] = (
|
||||
(df_result.loc[mask, 'cash_div_year'] / df_result.loc[mask, 'close']) * 100
|
||||
).round(4)
|
||||
|
||||
df_result['ts_code'] = tscode
|
||||
final_cols = ['ts_code', 'trade_date', 'close', 'cash_div_tax', 'cash_div_year', 'div_yield']
|
||||
return df_result[final_cols].reset_index(drop=True)
|
||||
Reference in New Issue
Block a user