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,135 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
# 尝试相对导入(作为包的一部分)
|
||||
from .config import TS_TOKEN, START_DATE, END_DATE, PRECISION_CONFIG
|
||||
from .stock_utils import *
|
||||
except (ImportError, SystemError):
|
||||
# 失败则使用绝对导入(直接运行脚本)
|
||||
from config import TS_TOKEN, START_DATE, END_DATE, PRECISION_CONFIG
|
||||
from stock_utils import *
|
||||
|
||||
'''
|
||||
给定个股代码和起止日期:
|
||||
1. 调用tushare接口查询个股分红数据,接口文档:https://tushare.pro/document/2?doc_id=103 查询日期范围内的所有分红记录
|
||||
2. 返回调整后的分红记录,包含以下字段:
|
||||
- ts_code: 股票代码
|
||||
- end_date: 分红年度
|
||||
- ann_date: 公告日期
|
||||
- ex_date: 除权除息日
|
||||
- cash_div_tax: 每股现金分红(含税)
|
||||
- div_proc: 实施进度,仅筛选 div_proc='实施'的记录
|
||||
3. 调用tushare 的trade_cal接口,查询起止日期内的所有交易日
|
||||
4. 建一个新的df,在起止日期内填充,规则:
|
||||
- 下列运算过程中将起始时间往前推 gap_days,赋值360天。运算结束后,截取起止时间内的数据
|
||||
- trade_date: 以交易日历为准,填充所有交易日。
|
||||
- 填入当ex_date=trade_date的日期,填入:ex_date,cash_div_tax,其余日期ex_date留空,cash_div_tax 置 0
|
||||
- cash_div_year:逐行计算填入,以trade_date往前计算过去gap_days天内的cash_div_tax之和
|
||||
5. 调用tushare的个股日线行情接口:https://tushare.pro/document/2?doc_id=27,查询起止日期内的个股日线行情数据,包含以下字段:
|
||||
- ts_code: 股票代码
|
||||
- trade_date: 交易日期
|
||||
- close: 收盘价
|
||||
6. 将分红数据和日线行情数据按交易日期合并,得到最终结果,包含以下字段:
|
||||
- ts_code: 股票代码
|
||||
- trade_date: 交易日期
|
||||
- close: 收盘价
|
||||
- cach_div_tax: 每股现金分红(含税)
|
||||
- cach_div_year: 每股现金TTM年度分红(含税)
|
||||
- div_yield: 股息率,计算公式为 (cach_div_year / close) * 100,保留PRECISION_CONFIG位小数,如果cash_div_year为0则div_yield也为0
|
||||
7. 返回最终结果的DataFrame
|
||||
'''
|
||||
|
||||
|
||||
# 初始化tushare
|
||||
from .data_source import get_tushare_pro
|
||||
pro = get_tushare_pro()
|
||||
|
||||
def get_dividend_yield(ts_code, start_date, end_date):
|
||||
# 检查日期范围是否超过当前日期,若超过则调整为当前日期
|
||||
today = datetime.now().strftime("%Y%m%d")
|
||||
if end_date > today:
|
||||
end_date = today
|
||||
if start_date > today:
|
||||
start_date = today
|
||||
|
||||
gap_days = 360 # 定义TTM计算窗口期为360天
|
||||
|
||||
# 1. 获取分红数据:查询指定股票的分红信息
|
||||
df_div = pro.dividend(ts_code=ts_code, fields='ts_code,end_date,ann_date,ex_date,cash_div_tax,div_proc')
|
||||
# 筛选实施状态的分红记录
|
||||
df_div = df_div[df_div['div_proc'] == '实施']
|
||||
print(f"分红原始数据:\n {df_div.to_string()} ")
|
||||
|
||||
# 2. 获取交易日历:查询指定日期范围内的开盘日
|
||||
df_cal = pro.trade_cal(exchange='', start_date=start_date, end_date=end_date, is_open='1')
|
||||
trade_dates = df_cal['cal_date'].tolist()
|
||||
|
||||
# 3. 创建基础DataFrame:将计算窗口期向前扩展gap_days天
|
||||
extended_start = pd.to_datetime(start_date) - pd.Timedelta(days=gap_days)
|
||||
extended_start_str = extended_start.strftime('%Y%m%d')
|
||||
# 获取扩展后的交易日历
|
||||
df_cal_ext = pro.trade_cal(exchange='', start_date=extended_start_str, end_date=end_date, is_open='1')
|
||||
df_base = pd.DataFrame({'trade_date': df_cal_ext['cal_date']})
|
||||
|
||||
# 4. 合并分红数据:将分红信息按除权除息日合并到基础交易日历
|
||||
df_base = df_base.merge(df_div[['ex_date', 'cash_div_tax']],
|
||||
left_on='trade_date', right_on='ex_date', how='left')
|
||||
# 填充空值:无分红日期现金分红设为0
|
||||
df_base['cash_div_tax'] = df_base['cash_div_tax'].fillna(0)
|
||||
|
||||
# 5. 计算TTM年度分红:滚动计算过去gap_days天的现金分红总和
|
||||
# 修正滚动窗口计算:使用固定窗口大小,确保在窗口期内正确累加
|
||||
df_base['cash_div_year'] = df_base['cash_div_tax'].rolling(window=gap_days, min_periods=0).sum()
|
||||
|
||||
|
||||
print(f"分红填充数据S1:\n {df_base.to_string()} ")
|
||||
# 6. 截取指定日期范围:保留原始查询日期范围内的数据
|
||||
df_base = df_base[df_base['trade_date'] >= start_date]
|
||||
print(f"分红填充数据S2:\n {df_base.to_string()} ")
|
||||
# 7. 获取日线行情数据:查询指定股票的日线收盘价
|
||||
df_daily = pro.daily(ts_code=ts_code, start_date=start_date, end_date=end_date,
|
||||
fields='ts_code,trade_date,close')
|
||||
|
||||
# 8. 合并数据:将行情数据与分红数据按交易日合并
|
||||
result = df_base.merge(df_daily, on='trade_date', how='left')
|
||||
# 填充股票代码:确保所有行都有股票代码
|
||||
result['ts_code'] = result['ts_code'].fillna(ts_code)
|
||||
|
||||
# 9. 计算股息率:当TTM分红>0时计算(分红/收盘价)*100,否则为0
|
||||
result['div_yield'] = np.where(
|
||||
result['cash_div_year'] > 0,
|
||||
(result['cash_div_year'] / result['close']) * 100,
|
||||
0
|
||||
)
|
||||
# 精度处理:保留配置指定的小数位数
|
||||
result['div_yield'] = result['div_yield'].round(PRECISION_CONFIG)
|
||||
|
||||
# 10. 整理列顺序:选择并排列最终输出的列
|
||||
result = result[['ts_code', 'trade_date', 'close', 'cash_div_tax', 'cash_div_year', 'div_yield']]
|
||||
|
||||
return result
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 测试代码
|
||||
test_code = "600900.SH"
|
||||
test_start = "20230101"
|
||||
test_end = "20251231"
|
||||
|
||||
try:
|
||||
result = get_dividend_yield(test_code, test_start, test_end)
|
||||
print(f"股票 {test_code} 的分红股息率数据:")
|
||||
print(result.head(100))
|
||||
print(f"\n数据形状: {result.shape}")
|
||||
print(f"\n数据列名: {result.columns.tolist()}")
|
||||
|
||||
# 检查是否有分红数据
|
||||
if not result.empty:
|
||||
print(f"\n股息率统计:")
|
||||
print(result['div_yield'].describe())
|
||||
else:
|
||||
print("未找到分红数据")
|
||||
|
||||
except Exception as e:
|
||||
print(f"测试过程中出现错误: {e}")
|
||||
Reference in New Issue
Block a user