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:
2026-06-07 15:59:05 +08:00
co-authored by Claude Opus 4.7
commit 271a9343a5
293 changed files with 59598 additions and 0 deletions
+135
View File
@@ -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}")