# card **Repository Path**: quant-seminar/card ## Basic Information - **Project Name**: card - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: dev - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-04 - **Last Updated**: 2026-06-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # card `card` 是 Seminar 项目的飞书结果展示服务。它从 `analysis` 服务下载已生成的 pickle 结果,将单因子测试或多因子策略回测结果转换为飞书交互卡片所需的结构化 JSON,并调用飞书消息回复接口发送卡片。 Arena 网站有独立的报告序列化和 VChart 渲染逻辑;`card` 只负责飞书卡片,不承担 Arena 前端展示。 ## 功能概览 - 单因子测试卡片:展示 IC 指标、分组收益显著性、IC 曲线、多空收益曲线、分组净值曲线和回测指标表。 - 多因子策略回测卡片:展示策略说明、收益风险指标、收益曲线、换手率、滚动夏普、回撤、滚动 beta 和绩效表。 - pickle 结果消费:通过 `analysis` 的 `/media/{file_id}` 获取分析结果。 - 飞书消息回复:使用飞书 app token 调用消息回复接口,发送模板卡片。 ## 服务结构 ```text card |-- main.py |-- config.py |-- core | |-- views | | |-- single_factor_analysis.py | | `-- factor_strategy_backtest.py | |-- cards | | |-- base.py | | |-- single_factor_analysis_card.py | | `-- factor_strategy_backtest_card.py | `-- utils | |-- token.py | |-- func.py | `-- logger.py `-- pyproject.toml ``` ## 依赖关系 ```text analysis | | GET /media/{file_id} v card | | POST /open-apis/im/v1/messages/{message_id}/reply v Feishu ``` `card` 不执行因子分析或回测,只负责展示层的数据转换和消息发送。 ## 配置 服务会加载当前目录和上级目录的 `.env`: ```env PROD=true ANALYSIS_SERVICE_URL=http://seminar-analysis:8000 FEISHU_APP_ID=cli_xxx FEISHU_APP_SECRET=xxx ``` `FEISHU_APP_ID` 和 `FEISHU_APP_SECRET` 用于获取飞书 `app_access_token`。 ## 启动 本地开发: ```bash uv sync uvicorn main:app --host 127.0.0.1 --port 8002 --reload ``` 通过主项目 Docker Compose: ```bash docker compose up --build seminar-card ``` 在主项目 `docker-compose.yml` 中,容器端口 `8000` 默认映射到主机端口 `8802`。 ## API ### GET `/single-factor-analysis` 将单因子测试 pickle 转换为飞书单因子分析卡片。 查询参数: | 参数 | 类型 | 说明 | | --- | --- | --- | | `file_id` | `str` | `analysis/media/{file_id}.pkl` 的文件 id | | `message_id` | `str` | 需要回复的飞书消息 id | 流程: 1. 请求 `ANALYSIS_SERVICE_URL/media/{file_id}`。 2. 反序列化 pickle。 3. 读取 `factor_analysis` 和 `ic_analysis`。 4. 构造 `SingleFactorAnalysisCardVariable`。 5. 调用飞书消息回复接口发送模板卡片。 依赖的 pickle 顶层结构: ```python { "factor_analysis": dict, "ic_analysis": pandas.DataFrame } ``` 主要消费字段: | 字段 | 来源 | 用途 | | --- | --- | --- | | `factor_analysis["icTest"]` | IC 汇总统计 | 因子名、IC 均值、IR、Rank IC 等指标 | | `factor_analysis["retStat"]` | 分组收益统计 | p-value 和平均收益柱线图 | | `ic_analysis` | IC 时序表 | IC 和累计 IC 曲线 | | `factor_analysis["retTable"]` | 多空收益表 | 多空收益和累计多空收益曲线 | | `factor_analysis["netValue"]` | 分组净值数组 | 10 组净值曲线 | | `factor_analysis["backtest"]` | 回测统计表 | 各组信息比率、夏普、年化收益、波动、最大回撤 | 卡片变量模型: ```python class SingleFactorAnalysisCardVariable(BaseModel): factor_name: str ic_mean: str ic_gt_0: str ic_gt_003: str ic_std: str ir: str rank_icir: str rank_ic_mean: str retStatChart: dict icChart: dict retTableChart: dict netValueChart: dict backtestTable: list ``` 飞书模板: | 字段 | 值 | | --- | --- | | `template_version_name` | `AAqKoXVGDaXVp` | | `template_id` | `1.0.2` | ### GET `/factor-strategy-backtest` 将多因子策略回测 pickle 转换为飞书策略回测卡片。 查询参数: | 参数 | 类型 | 说明 | | --- | --- | --- | | `file_id` | `str` | `analysis/media/{file_id}.pkl` 的文件 id | | `message_id` | `str` | 需要回复的飞书消息 id | 流程: 1. 请求 `ANALYSIS_SERVICE_URL/media/{file_id}`。 2. 反序列化 pickle。 3. 读取 `description` 和 `values`。 4. 使用 `quantstats` 基于净值序列计算绩效指标。 5. 构造 `FactorStrategyBacktestCardVariable`。 6. 调用飞书消息回复接口发送模板卡片。 依赖的 pickle 顶层结构: ```python { "description": str, "values": pandas.DataFrame } ``` `values` 使用 `DatetimeIndex`,需要包含以下列: | 字段 | 说明 | | --- | --- | | `benchmark` | 基准净值序列 | | `strategy` | 策略净值序列 | | `turnover` | 换手率序列 | 卡片会从 `values` 计算: - 累计收益、CAGR、夏普、最大回撤、年化波动率、Calmar。 - 平均回撤、平均回撤天数、恢复因子、Gain/Pain、Beta、Alpha、相关系数、R^2。 - 策略和基准净值曲线、换手率柱状图。 - 滚动夏普曲线。 - 回撤曲线和最大回撤明细。 - 滚动 beta 曲线。 - 完整绩效指标表。 卡片变量模型: ```python class FactorStrategyBacktestCardVariable(BaseModel): description: str ret_color: str total_ret: str cagr: str sharpe: str max_drawdown: str vol_year: str calmar: str avg_drawdown: str avg_drawdown_days: str recover_factor: str gain_to_pain: str beta: str alpha: str correlation: str r_2: str profit_chart: dict rolling_sharpe_chart: dict drawdown_chart: dict rolling_beta_chart: dict profit_table: list drawdown_table: list ``` 飞书模板: | 字段 | 值 | | --- | --- | | `template_version_name` | `AAqeVbByKgcPw` | | `template_id` | `1.1.0` | ## 飞书发送逻辑 所有卡片继承自 `CardTemplate`。发送时会调用: ```text POST https://open.feishu.cn/open-apis/im/v1/messages/{message_id}/reply ``` 请求体中的 `content` 是飞书模板卡片: ```json { "type": "template", "data": { "template_id": "...", "template_version_name": "...", "template_variable": {} } } ``` 访问令牌通过以下接口获取: ```text POST https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal ``` ## 图表抽样 卡片中部分曲线数据较长,为避免飞书卡片过大,服务会使用 `sample_with_fixed_ends` 对时序数据抽样: - 默认抽样 100 条,策略回测部分图表使用 500 条。 - 永远保留第一行和最后一行。 - 中间数据随机抽样。 ## 与 analysis 的契约 `card` 强依赖 `analysis` 输出的 pickle 结构: | analysis 功能 | pickle 顶层字段 | card 入口 | | --- | --- | --- | | 单因子测试 | `factor_analysis`, `ic_analysis` | `/single-factor-analysis` | | 多因子策略回测 | `description`, `values` | `/factor-strategy-backtest` | 如果 `analysis` 修改 pickle 字段名、DataFrame 列名或指标名称,需要同步更新 `card` 中对应的 `build_card` 逻辑和 Pydantic 变量模型。 注意:当前 Arena 后端已经按研究类型插件消费新版 analysis 报告;`card` 仍保留飞书卡片转换逻辑。在继续使用飞书卡片前,应确认 `card` 期望的 pkl 字段与当前 `analysis` 输出是否一致。 ## 注意事项 - 当前服务直接向飞书发送消息,接口返回值是飞书 API 的 JSON 响应。 - 如果 `analysis/media/{file_id}.pkl` 不存在,服务返回 `{"message": "File not found"}`。 - `quantstats` 的指标名称可能随版本变化;如果升级依赖,需要检查 `factor_strategy_backtest.py` 中对 `metrics.loc[...]` 的索引。 - 单因子分析中的 DolphinDB 中文指标名也是跨服务契约,修改 DolphinDB 输出字段时需要同步更新卡片转换逻辑。