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
View File
+121
View File
@@ -0,0 +1,121 @@
"""
报告持久化模块。
将 CLI 脚本输出的 Markdown 报告存入 DBmac_report 表)。
同一日期+研究对象的新报告入库时,旧报告自动标记为失效(is_active=0)。
"""
from datetime import datetime
from sqlalchemy import text
from database.connection import get_engine
from database.models import Report, create_all_tables
def save_report(
content: str,
title: str,
report_date: str | None = None,
subject_type: str = "daily",
subject_code: str = "",
) -> int:
"""
保存报告到 DB。
如果同一天、同一研究对象已有报告,先将其标记为 is_active=0
然后插入新报告。
参数:
content: Markdown 报告内容
title: 报告标题
report_date: 报告日期 YYYYMMDD,默认今天
subject_type: 研究对象类型 (stock/index/sector/portfolio/daily)
subject_code: 研究对象代码 (如 000001.SZ 或 000300.SH)
返回:
新报告的 id
"""
report_date = report_date or datetime.now().strftime("%Y%m%d")
engine = get_engine()
# 确保表存在
create_all_tables()
# 将同日期+同对象的旧报告标记失效
with engine.connect() as conn:
conn.execute(
text(
"UPDATE {} SET is_active = 0 "
"WHERE report_date = :d AND subject_type = :st AND subject_code = :sc AND is_active = 1"
.format(Report.__tablename__)
),
{"d": report_date, "st": subject_type, "sc": subject_code},
)
conn.commit()
# 插入新报告
report = Report(
report_date=report_date,
title=title,
subject_type=subject_type,
subject_code=subject_code,
content=content,
created_at=datetime.now(),
is_active=1.0,
)
from sqlalchemy.orm import Session
with Session(engine) as session:
session.add(report)
session.commit()
report_id = report.id
session.expunge_all()
return report_id
def query_reports(
report_date: str | None = None,
subject_type: str | None = None,
subject_code: str | None = None,
active_only: bool = True,
limit: int = 20,
) -> list[dict]:
"""查询报告列表。"""
engine = get_engine()
table = Report.__tablename__
sql = "SELECT * FROM {} WHERE 1=1".format(table)
params = {}
if report_date:
sql += " AND report_date = :d"
params["d"] = report_date
if subject_type:
sql += " AND subject_type = :st"
params["st"] = subject_type
if subject_code:
sql += " AND subject_code = :sc"
params["sc"] = subject_code
if active_only:
sql += " AND is_active = 1"
sql += " ORDER BY id DESC LIMIT :lim"
params["lim"] = limit
with engine.connect() as conn:
rows = conn.execute(text(sql), params).fetchall()
result = []
for r in rows:
d = dict(r._mapping)
# DATE/DATETIME 列 → 字符串
for key in ("report_date", "created_at"):
val = d.get(key)
if hasattr(val, "strftime"):
fmt = "%Y%m%d" if key == "report_date" else "%Y-%m-%d %H:%M:%S"
d[key] = val.strftime(fmt)
result.append(d)
return result