feat: 量化引擎加固 — 新增测试 + 数据/因子/回测层优化
- 新增 finance/tests/ 6 个测试套件(agents/backtest/dao_upsert/factors/features/fundamental_lookahead) - 数据层: data_manager / dao 优化,新增 upsert 逻辑 - 因子层: 基本面因子抽象定位 _mapping、ROE/PE/PB 重构 - 回测层: vectorbt/engine 大改动(251 行),report 增强 - ML 层: features/backtest_integration 特征工程与回测优化 - CLI: agent_cli 重构 - config/settings 扩充配置项
This commit is contained in:
+64
-42
@@ -3,19 +3,24 @@
|
||||
Agent 命令行入口。
|
||||
|
||||
用法:
|
||||
python cli/agent_cli.py daily # 执行每日流程
|
||||
python cli/agent_cli.py picks [N] # 今日选股 Top N
|
||||
python cli/agent_cli.py risk # 风险评估
|
||||
python cli/agent_cli.py research # 因子研究
|
||||
python cli/agent_cli.py report [DATE] # 生成日报
|
||||
python cli/agent_cli.py daily # 执行每日流程
|
||||
python cli/agent_cli.py picks [N] # 今日选股 Top N
|
||||
python cli/agent_cli.py risk # 风险评估
|
||||
python cli/agent_cli.py research # 因子研究
|
||||
python cli/agent_cli.py report [DATE] # 生成日报
|
||||
python cli/agent_cli.py warmup [N] # 首次批量预热
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")
|
||||
|
||||
|
||||
def init_engines():
|
||||
"""初始化所有引擎。"""
|
||||
@@ -26,7 +31,6 @@ def init_engines():
|
||||
from factors.sentiment.sentiment_engine import SentimentEngine
|
||||
from factors.sentiment.news_source import NewsSource
|
||||
from factors.sentiment.qwen_client import QwenClient
|
||||
from factors.registry import get_factor
|
||||
|
||||
dm = DataManager()
|
||||
dm.init_db()
|
||||
@@ -45,42 +49,62 @@ def init_engines():
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("用法: agent_cli.py <daily|picks|risk|research|report|warmup>")
|
||||
print()
|
||||
print(" daily [DATE] — 执行每日完整流程")
|
||||
print(" picks [N] [DATE] — 今日选股 Top N")
|
||||
print(" risk — 风险评估")
|
||||
print(" research — 因子发现与评估")
|
||||
print(" report [DATE] — 生成日报")
|
||||
print(" warmup [N] — 首次批量预热范围股票到 DB 缓存")
|
||||
return
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
p = argparse.ArgumentParser(prog="agent_cli", description="cc-cursor 量化 Agent CLI")
|
||||
sub = p.add_subparsers(dest="cmd", required=True)
|
||||
|
||||
cmd = sys.argv[1]
|
||||
d = sub.add_parser("daily", help="执行每日完整流程")
|
||||
d.add_argument("date", nargs="?", default=None, help="日期 YYYYMMDD(默认今天)")
|
||||
|
||||
pk = sub.add_parser("picks", help="今日选股 Top N")
|
||||
pk.add_argument("top_n", nargs="?", type=int, default=15, help="返回 Top N")
|
||||
pk.add_argument("date", nargs="?", default=None, help="日期 YYYYMMDD")
|
||||
|
||||
sub.add_parser("risk", help="风险评估")
|
||||
|
||||
sub.add_parser("research", help="因子发现与评估")
|
||||
|
||||
rp = sub.add_parser("report", help="生成日报")
|
||||
rp.add_argument("date", nargs="?", default=None, help="日期 YYYYMMDD")
|
||||
|
||||
wm = sub.add_parser("warmup", help="首次批量预热范围股票到 DB 缓存")
|
||||
wm.add_argument("batch_n", nargs="?", type=int, default=50, help="每批股票数")
|
||||
|
||||
return p
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(argv)
|
||||
engines = init_engines()
|
||||
|
||||
from agents.orchestrator import AgentOrchestrator
|
||||
orch = AgentOrchestrator(**engines)
|
||||
orch.setup()
|
||||
|
||||
if cmd == "daily":
|
||||
date = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
results = orch.run_daily(date=date)
|
||||
# 打印日报内容
|
||||
today = datetime.now().strftime("%Y%m%d")
|
||||
|
||||
if args.cmd == "daily":
|
||||
results = orch.run_daily(date=args.date)
|
||||
report_md = results.get("report", {}).get("report_markdown", "")
|
||||
if report_md:
|
||||
print(report_md)
|
||||
# 任一步骤失败 → 非零退出码,便于调度/CI 感知
|
||||
failed = [k for k, v in results.items()
|
||||
if isinstance(v, dict) and "error" in v]
|
||||
if failed:
|
||||
print("每日流程有步骤未完成: {}".format(", ".join(failed)), file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
elif cmd == "picks":
|
||||
n = int(sys.argv[2]) if len(sys.argv) > 2 else 15
|
||||
date = sys.argv[3] if len(sys.argv) > 3 else None
|
||||
result = orch.picks(date=date, top_n=n)
|
||||
elif args.cmd == "picks":
|
||||
result = orch.picks(date=args.date, top_n=args.top_n)
|
||||
print(f"\n选股结果 ({result.get('date', '?')}):")
|
||||
for p in result.get("top_picks", []):
|
||||
print(f" {p['ts_code']:12s} {p.get('name', ''):10s} {p['score']:.4f}")
|
||||
return 0
|
||||
|
||||
elif cmd == "risk":
|
||||
elif args.cmd == "risk":
|
||||
result = orch.risk_check()
|
||||
print(f"\n风险评估:")
|
||||
print(f" 等级: {result['risk_level']}")
|
||||
@@ -93,22 +117,24 @@ def main():
|
||||
print(f" 回撤: {indicators.get('current_drawdown', 0):.1f}%")
|
||||
for a in result.get("alerts", []):
|
||||
print(f" ⚠️ {a}")
|
||||
return 0
|
||||
|
||||
elif cmd == "research":
|
||||
elif args.cmd == "research":
|
||||
result = orch.run_research_cycle()
|
||||
top = result.get("research", {}).get("top_factors", [])
|
||||
print(f"\n因子评估结果:")
|
||||
if not top:
|
||||
print(" (无结果)")
|
||||
return
|
||||
return 0
|
||||
print(f" {'因子':20s} {'IC':>8s} {'IC_IR':>8s} {'多头':>8s} {'空头':>8s} {'得分':>8s}")
|
||||
print(f" {'─'*60}")
|
||||
for f in top:
|
||||
print(f" {f['name']:20s} {f['ic_mean']:>+8.4f} {f['icir']:>8.3f} "
|
||||
f"{f['long_ret']:>+7.1f}% {f['short_ret']:>+7.1f}% {f['score']:>8.4f}")
|
||||
return 0
|
||||
|
||||
elif cmd == "warmup":
|
||||
batch_n = int(sys.argv[2]) if len(sys.argv) > 2 else 50
|
||||
elif args.cmd == "warmup":
|
||||
batch_n = args.batch_n
|
||||
print("首次批量预热: 每次 {} 只股票,分批执行...".format(batch_n))
|
||||
sent = engines.get("sent")
|
||||
dm = engines.get("dm")
|
||||
@@ -129,24 +155,20 @@ def main():
|
||||
print(" {} 失败: {}".format(ts_code, e))
|
||||
print(" 累计同步: {} 条".format(total_synced))
|
||||
print("预热完成: {} 条数据, {} 只新股票已缓存".format(total_synced, len(uncached)))
|
||||
return 0
|
||||
|
||||
elif cmd == "report":
|
||||
date = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
elif args.cmd == "report":
|
||||
date = args.date or today
|
||||
result = orch.generate_report(date=date)
|
||||
print("\n日报已生成: {}".format(result.get("report_path", "?")))
|
||||
# 存入 DB
|
||||
md = result.get("report_markdown", "")
|
||||
if md:
|
||||
from reports.storage import save_report
|
||||
save_report(md, "量化日报", report_date=date or datetime.now().strftime("%Y%m%d"),
|
||||
subject_type="daily", subject_code="")
|
||||
print(" 已存入 DB")
|
||||
# 注意: ReportAgent.execute 内部已保存到 DB,这里不再重复入库
|
||||
if md:
|
||||
print(md)
|
||||
return 0
|
||||
|
||||
else:
|
||||
print(f"未知命令: {cmd}")
|
||||
return 0 # 防御:未匹配(应不会到达)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
sys.exit(main())
|
||||
@@ -89,7 +89,7 @@ def main():
|
||||
test_price = price_df.loc[X_test.index]
|
||||
test_factor = factor_df.loc[X_test.index]
|
||||
bt_engine = VectorBTEngine()
|
||||
benchmark = MLBenchmark([lgb_model, cb_model], fe, test_price, test_factor, bt_engine)
|
||||
benchmark = MLBenchmark([lgb_model, cb_model], fe, test_factor, test_price, bt_engine)
|
||||
result = benchmark.run()
|
||||
print(result.round(2).to_string())
|
||||
print(" > 解读: 回测结果反映 ML 策略在测试集上的实盘表现。")
|
||||
|
||||
Reference in New Issue
Block a user