# 自定义数据源接入 本项目默认使用 TickFlow。自定义数据源是一个可选扩展: 外部 HTTP 服务负责取数和整理, 本项目只把返回结果映射成内部标准字段, 然后复用现有存储、指标、enriched、策略和前端展示逻辑。 ## 支持范围 当前自定义源支持七类数据: | 数据集 | 配置名 | 说明 | | --- | --- | --- | | 日K | `daily` | 批量返回一组标的在指定区间内的日K。个股、指数与 ETF 的日K同步都会路由到生效源,并按契约传入 `asset_type`(`stock`/`index`/`etf`);仅支持股票的源(如 fuyao/tdxquant/amber)对其他资产类型返回空数据即可,该轮同步会告警并回退 TickFlow,不会写入错误数据 | | 除权因子 | `adj_factor` | 批量返回一组股票的复权因子 | | 实时行情 | `realtime` | 返回全市场快照,用于盘中 enriched 增量计算 | | 分钟K | `minute` | 返回 1m 分钟K(需映射出 symbol / datetime / OHLC / 量; amount 成交额可缺省, 缺省时均价线降级显示 —) | | 五档盘口 | `depth5` | 批量返回一组股票的买卖五档快照(逐批注入代码参数,无起止时间) | | 全量分钟 | `full_minute` | 与 `minute` 同形;声明后可被路由为「全量分钟」生效源,内置服务盘中按当日窗口全市场批量落盘(仅修复轮语义,节奏下限 60s) | | 财务数据 | `financial` | 一个配置覆盖全部五张财务表;请求时注入 `table` 参数由上游分发,五表按报告期累积落库(见下 `financial` 字段契约) | `full_minute` 声明式源只提供修复轮(当日窗口批量);廉价增量端点 (`get_intraday_latest`)是 Python 插件契约,见 [plugin-development.md](./plugin-development.md)。 ## 配置位置 把 YAML 放到运行数据目录下: ```text data/data_sources/*.yaml ``` Dev 模式下,默认位置是项目根目录的 `data/`;Docker 部署中,项目的 `data/` 会挂载为容器内的 `/app/data`。可通过 `DATA_DIR` 覆盖。 Docker 里 URL 填 `127.0.0.1` 指的是容器自身:数据源服务跑在宿主机时,应改用 `host.docker.internal`(Linux `docker run` 需加 `--add-host=host.docker.internal:host-gateway` 才能解析,compose 已内置该映射;也可直接用宿主机内网 IP,如 `172.17.0.1`)。 修改 YAML 后可在「设置 -> 数据源」点击「重新加载」,或调用: ```bash curl -X POST http://127.0.0.1:3018/api/settings/data-sources/reload ``` ## 最小 YAML ```yaml name: mock_source display_name: "Mock 自定义数据源" auth: type: none datasets: daily: url: http://127.0.0.1:3021/daily method: POST batch: 100 rpm: 200 response_path: data field_map: ts_code: symbol trade_date: date open: open high: high low: low close: close vol: volume amt: amount transforms: date: "parse_date(value, '%Y-%m-%d')" adj_factor: url: http://127.0.0.1:3021/adj_factor method: POST batch: 100 rpm: 200 response_path: data field_map: ts_code: symbol trade_date: trade_date factor: ex_factor transforms: trade_date: "parse_date(value, '%Y-%m-%d')" realtime: url: http://127.0.0.1:3021/realtime method: GET rpm: 60 response_path: data field_map: ts_code: symbol name: name last: last_price pre_close: prev_close open: open high: high low: low vol: volume amt: amount pct: change_pct amount_change: change_amount amplitude: amplitude turnover: turnover_rate ``` ## 字段契约 ### daily 必填 | 内部字段 | 含义 | | --- | --- | | `symbol` | 标准代码,如 `000001.SZ` | | `date` | 交易日 | | `open` / `high` / `low` / `close` | 不复权 OHLC | | `volume` | 成交量 | | `amount` | 成交额 | ### adj_factor 必填 | 内部字段 | 含义 | | --- | --- | | `symbol` | 标准代码 | | `trade_date` | 除权日期 | | `ex_factor` | 复权因子 | 可选明细列 (提供即落库, 缺省为空): `dividend` 每股现金分红、`bonus` 每股送转比例、 `allot`/`allot_price` 配股比例与配股价、`prev_close` 除权前收盘 — 供等差显示投影 与全精度因子链重建。 ### realtime 必填 | 内部字段 | 含义 | | --- | --- | | `symbol` | 标准代码 | | `last_price` | 最新价 | | `prev_close` | 昨收 | | `open` / `high` / `low` | 当日 OHLC | | `volume` | 成交量 | 建议实时接口额外提供 `amount`、`change_pct`、`change_amount`、`amplitude`、`turnover_rate`、`name`。缺失时部分字段会由 pipeline 回算,但精度取决于可用输入。 `change_pct`、`amplitude`、`turnover_rate` 统一使用小数制,例如 `0.0366` 表示 `3.66%`。百分制单位必须在 realtime 数据集上**显式声明**,不做数值猜测(数值无法区分两种单位:`0.05` 既可能是 0.05% 也可能是 5%): ```yaml datasets: realtime: url: https://api.example.com/snapshot pct_unit: percent # 接口返回 3.66 表示 3.66%;小数制源声明 decimal 或省略 ``` 处理规则: | 声明 | 行为 | | --- | --- | | `pct_unit: percent` | `change_pct` / `amplitude` / `turnover_rate` 无条件 `/100` | | `pct_unit: decimal` | 三列原样透传 | | 未声明 | `change_pct` 按截面中位数归一(A 股涨跌停 30% 上限使两种单位物理可分);`amplitude` / `turnover_rate` **置 `None`** 交由 pipeline 按价格与股本口径重算,并记录 WARNING | | 列已配置 `transforms` | 视为用户已接管该列单位,原样透传 | ### depth5 必填 | 内部字段 | 含义 | | --- | --- | | `symbol` | 标准代码 | | `asks` | 卖五档,嵌套数组 `[[价,量], ...]`,按卖一至卖五顺序 | | `bids` | 买五档,嵌套数组 `[[价,量], ...]`,按买一至买五顺序 | `asks` / `bids` 每档取前两个元素按数值解析为 `[价格, 手数]`,无法解析的档位被跳过;数量单位沿用现有封单量口径(手),前端计算封单额时再乘 100 换算为股。示例响应行: ```json { "code": "000001.SZ", "sell": [[10.01, 1200], [10.02, 800], [10.03, 500], [10.04, 300], [10.05, 200]], "buy": [[10.00, 900], [9.99, 700], [9.98, 400], [9.97, 350], [9.96, 150]] } ``` 对应映射 `code→symbol`、`sell→asks`、`buy→bids`。声明 depth5 后即可在设置 → 数据源 → 能力路由把五档生效源切到该自定义源;五档独立路由,失败不跨源回退。 ### financial 必填 | 内部字段 | 含义 | | --- | --- | | `symbol` | 标准代码(唯一硬性要求,缺 symbol 的帧被丢弃) | | `period_end` | 报告期 `YYYY-MM-DD`;缺此列的帧不参与报告期合并,页面按时间排序失效 | | `announce_date` | 公告日期 `YYYY-MM-DD`;缺省时按报告期排序,换手率用公告日判业务可用性 | 一个 `financial` 配置覆盖全部五张表,每次请求自动注入 `table` 参数(`metrics` / `income` / `balance_sheet` / `cash_flow` / `shares`) 由上游分发返回对应表数据;`shares` 表额外注入 `latest`(当期 `True` / 全历史 `False`)。映射为「外部字段 → 内部列」一对一:同一外部字段名只能 映射到一个内部列 — 若两张表存在同名字段且含义不同(如 `total` 在 balance_sheet 是资产、在 shares 是股本),无法分别映射,需要上游区分字段名 或只映射其一。两个外部字段映射同一内部列会在保存时被校验拒绝。某批响应 里完全缺失(而非为 null)的字段该批不落该列,跨批/跨轮合并会自动补 null。 每表 canonical 列(前端财务页与回测基本面因子按此消费;不映射的列在 页面显示为 —,不影响其他列): - `metrics` 核心指标: `eps_basic` / `eps_diluted` / `bps`(每股净资产) / `ocfps`(每股经营现金流) / `roe` / `roe_diluted` / `roa` / `gross_margin`(毛利率) / `net_margin`(净利率) / `debt_to_asset_ratio`(资产负债率) / `revenue_yoy`(营收同比) / `net_income_yoy`(净利同比) / `operating_cash_to_revenue`(经营现金/营收) / `inventory_turnover`(存货周转率) —— 注意: 百分比列存**百分点**(12.3 表示 12.3%),与 realtime 的小数制不同 - `income` 利润表: `revenue`(营收) / `operating_cost`(营业成本) / `operating_profit`(营业利润) / `selling_expense`(销售费用) / `admin_expense`(管理费用) / `rd_expense`(研发费用) / `financial_expense`(财务费用) / `non_operating_income`(营业外收入) / `non_operating_expense`(营业外支出) / `total_profit`(利润总额) / `income_tax`(所得税) / `net_income`(净利润) / `net_income_attributable`(归母净利) / `net_income_deducted`(扣非净利) / `basic_eps` / `diluted_eps` —— 金额单位元 - `balance_sheet` 资产负债表: `total_assets`(资产总计) / `total_current_assets`(流动资产) / `total_non_current_assets`(非流动资产) / `cash_and_equivalents`(货币资金) / `accounts_receivable`(应收账款) / `inventory`(存货) / `fixed_assets`(固定资产) / `intangible_assets`(无形资产) / `goodwill`(商誉) / `total_liabilities`(负债合计) / `total_current_liabilities`(流动负债) / `total_non_current_liabilities`(非流动负债) / `short_term_borrowing`(短期借款) / `long_term_borrowing`(长期借款) / `accounts_payable`(应付账款) / `total_equity`(所有者权益) / `equity_attributable`(归母权益) / `retained_earnings`(未分配利润) / `minority_interest`(少数股东权益) —— 金额单位元 - `cash_flow` 现金流量表: `net_operating_cash_flow`(经营净额) / `net_investing_cash_flow`(投资净额) / `net_financing_cash_flow`(筹资净额) / `capex`(资本开支) / `net_cash_change`(现金净增加) —— 金额单位元 - `shares` 股本: `total_shares`(总股本,股) / `float_shares`(流通股本,股) — 历史换手率按公告日(缺则报告期)取有效股本,无 `float_shares` 时回退维表最新股本 被切为财务生效源后,财务同步走独立调度(每周 metrics + 手动全表), 报告期合并按 `(symbol, period_end)` 逐列取最新非空值:多源字段可并集 共存互不覆盖(如 TickFlow 补齐 metrics 历史各期,自定义源提供当期报表), 但切换生效源后**不再拉取的表不会自动清空**,旧数据会保留到被新数据覆盖。 声明了 financial 但上游某表无数据时返回空即可,已有存量靠合并写入保留。 ## 请求约定 - `daily` / `adj_factor` 会按 `batch` 切分 symbols。 - POST 请求会发送 JSON body: `symbols`、`start_time`、`end_time`。 - GET 请求会发送 query 参数: `symbols=000001.SZ,600000.SH`。 - `realtime` 必须是全市场快照接口,不支持逐个 symbol 拉实时行情。 - `depth5` 会按 `batch` 切分 symbols 逐批请求,只注入代码参数(无 `start_time` / `end_time`)。 - `financial` 同样按 `batch` 切分逐批请求,只注入代码参数 + `table` 表名(无起止时间);`shares` 表额外注入 `latest`。 可通过这些字段改参数名: ```yaml symbols_param: symbols start_param: start_time end_param: end_time ``` 分钟数据源如果需要区分资产类型或周期,可继续配置: ```yaml asset_type_param: asset_type freq_param: period ``` 配置后,分钟请求会分别传入 `stock` / `etf` / `index` 和 `1m`;留空时不向上游发送这两个参数,以兼容已有数据源。 ### 请求超时 每个数据集可单独配置请求超时(秒),默认 30: ```yaml timeout: 60 ``` 留空或省略时用默认 30 秒,可配置范围为大于 0 且不超过 300 秒;该值对数据同步与「试拉测试」均生效。在设置页编辑数据源时可在「超时」输入框修改(与 批量 / RPM / 响应路径 同行)。「试拉测试」直接使用当前表单内容,新建数据源或尚未保存的修改也可测试。 ## 鉴权 支持三种简单鉴权: ```yaml auth: type: bearer token_env: MY_DATA_TOKEN ``` ```yaml auth: type: header header: X-Token token_env: MY_DATA_TOKEN ``` ```yaml auth: type: query param: token token_env: MY_DATA_TOKEN ``` Token 可以放在系统环境变量或项目 `.env` 中。 ## 联调流程 1. 启动 mock 数据源: ```bash cd docs/examples/custom-data-source python mock_server.py ``` 2. 复制示例配置: ```bash mkdir -p data/data_sources cp docs/examples/custom-data-source/mock_source.yaml data/data_sources/mock_source.yaml ``` 3. 在「设置 -> 数据源」点击「重新加载」。 4. 使用「试拉测试」选择 `mock_source` 和 `daily` / `adj_factor` / `realtime`。 5. 保存数据源选择: - 日K: `mock_source` - 除权因子: `mock_source` (或保持默认 `tickflow`) - 实时行情: `mock_source` 6. 触发同步或开启实时行情。 ## 常见错误 | 现象 | 处理 | | --- | --- | | 列表里没有 custom 源 | 检查 YAML 是否放在 `data/data_sources/` 并点击重新加载 | | errors 提示 missing mapped fields | `field_map` 没映射到必填内部字段 | | 试拉 rows 为 0 | 检查 `response_path` 是否指向数组 | | 日期列全为空 | 检查 `parse_date` 的格式是否和返回值一致 | | 实时行情没刷新 | 确认实时数据源已保存为 custom,且返回全市场快照 | ## 用 AI 生成映射配置 如果你的数据源 API 文档比较复杂,可以把 API 文档和返回示例丢给 AI,让它帮你生成 `field_map` 和 YAML 配置。 ### 操作步骤 1. 从你的数据源获取 API 文档(接口地址、请求方式、返回字段说明) 2. 试拉一次,拿到返回的 JSON 示例 3. 把下面的 prompt 模板 + API 文档 + JSON 示例一起发给 AI 4. 把 AI 生成的 YAML 贴到 `data/data_sources/xxx.yaml` 5. 在设置页点「重新加载」,再「试拉测试」验证 ### Prompt 模板 复制以下内容发给 AI(替换方括号部分): ```text 我在配置一个自定义数据源接入股票面板。请根据我的 API 文档和返回示例,生成 YAML 配置。 要求: 1. 输出标准 YAML 配置,包含 name / display_name / auth / datasets 2. 每个数据集的 field_map 把我的接口字段名映射到内部字段名 3. 日期类字段如果格式不是 YYYY-MM-DD, 加上 transforms 里的 parse_date 4. 只配置我能提供的接口, 不存在的数据集不要写 内部字段对照表: 日K (daily): symbol = 股票代码, 格式 000001.SZ / 600000.SH date = 交易日期 open / high / low / close = OHLC volume = 成交量 amount = 成交额 除权因子 (adj_factor): symbol = 股票代码 trade_date = 除权日期 ex_factor = 复权因子 实时行情 (realtime): symbol = 股票代码 last_price = 最新价 prev_close = 昨收价 open / high / low = 当日 OHLC volume = 成交量 amount = 成交额 (可缺省) change_pct = 涨跌幅 (小数, 0.0366 = 3.66%) change_amount = 涨跌额 amplitude = 振幅 (小数, 0.024 = 2.4%) turnover_rate = 换手率 (小数, 0.05 = 5%) # 上游若返回百分数值 (3.66 表示 3.66%), 在 realtime 数据集声明 pct_unit: percent, # 不要依赖数值自动识别; 逐列转换也可用 transforms: turnover_rate: "value / 100" 分钟K (minute) 与 全量分钟 (full_minute, 字段同 minute): symbol = 股票代码 # datetime 必须是北京时间墙钟 (如 2026-08-28 09:35:00), 不要返回 UTC; # 入口守卫会自动纠偏 UTC 特征帧, 但契约仍要求源头写对 datetime = 北京时间墙钟 (YYYY-MM-DD HH:MM:SS) open / high / low / close = OHLC volume = 成交量 amount = 成交额 (可缺省: 无法提供可靠成交额时不要映射, 前端均价线降级显示 —) === 我的 API 文档 === [把你的接口文档贴这里: URL / 请求方式 / 参数 / 返回字段说明] === 返回 JSON 示例 === [把试拉的 JSON 返回贴这里] ``` AI 会输出类似这样的结果: ```yaml name: my_source display_name: "我的数据源" auth: type: bearer token_env: MY_API_TOKEN datasets: daily: url: https://api.example.com/kline method: POST batch: 100 rpm: 200 response_path: data.list field_map: ts_code: symbol trade_date: date open: open vol: volume transforms: date: "parse_date(value, '%Y%m%d')" ``` 把这段 YAML 保存为 `data/data_sources/my_source.yaml`,然后在设置页重新加载即可。