engine.json¶
Portfolio Engine 的配置。职责是把标准化后的面板切成分位桶、按加权方案折算成组合收益,并产出多空与基准行。
对应 EngineConfig,可由 EngineConfig.from_file("configs/engine.json") 单独加载,也可以用 Python 直接构造。
走 backtest() 时这些字段由函数参数组装,对应关系见 Python API 参考。
字段总览¶
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
forward_return.horizon |
int |
必填 | 前视收益的测量期长度,单位随 input.frequency |
forward_return.source |
str |
"prices" |
前视收益来源,取 prices 或 signals |
forward_return.clip_lower |
float \| null |
null |
前视收益的下限裁剪阈值 |
n_buckets |
int |
10 |
分位桶数量,必须 ≥ 2 |
min_names |
int |
20 |
单个截面进入分桶所需的最少有效样本数 |
holding_days |
int \| null |
null |
年化折算所用的持有期数,留空继承 horizon |
weights |
list[str] |
["ew", "vw"] |
加权方案名,取自 WEIGHTERS 注册表 |
weight_options |
dict |
{} |
按方案名传给加权器构造函数的额外参数 |
include_references |
bool |
true |
是否将外部基准纳入结果表 |
reference_lag |
int |
1 |
日频基准复利窗口相对锚点的偏移 |
long_short.enabled |
bool |
true |
是否构建多空 |
long_short.label |
str |
"H-L" |
多空在结果表 bucket 列中的取值 |
long_short.reverse |
bool |
false |
反向多空,即低分位减高分位 |
未列出的键会被拒绝:严格校验会报出未知项并附上可用项清单。
forward_return¶
前视收益口径。这是全包唯一一处定义「一期有多长」的地方。
horizon¶
必填,int,需 ≥ 1。
每期实现收益跨越的期数。单位随 input.frequency:日度口径下数交易日,月度口径下数自然月。三处行为同时以它为准:
- 取价格口径时,逐资产计算
close[t+h] / close[t] - 1(月度口径下t与t+h取各自月份的最后一个可用收盘价) - 日频外部基准在长度为
h的窗口上复利,与组合同窗口才可比 holding_days未单独指定时继承该值
缺失或小于 1 时直接抛 ConfigError,不会拖到下游才失败。
source¶
str,默认 "prices"。
backtest() 的默认值不同
配置这边固定默认 "prices";backtest() 则按数据推导——信号提供 fwd_ret 时取
"signals",否则取 "prices"。
差别在于两者掌握的信息量不同:写配置的人知道自己的数据长什么样,而 backtest()
要在看过数据之后才能定。自带列优先于 close,是因为前者是专门算过一遍实现收益的结果,
后者往往只是价格面板顺带提供的。
逐资产按 close 推算前视收益,纯价格口径,与 alpha 自身的预测期解耦。
要求价格面板提供 close。未映射该列时抛 ConfigError,错误信息直接指回配置本身,而非让下游报出「没有任何调仓日通过分桶」这类指不回原因的消息。
信号文件若同时映射了 fwd_ret,该列被丢弃并发出 IgnoredForwardReturnWarning——配置既已指定价格口径,就以价格为准。
直接取信号文件自带的 fwd_ret 列。适用于只有预测结果、没有价格序列的输入文件。
此时价格面板只承担关联市值与定义交易日历两件事,column_map 中可以不写 close。
信号的 column_map 未映射 fwd_ret 时抛 ConfigError。
其他取值抛 ConfigError。
clip_lower¶
float | null,默认 null。
对前视收益做下限裁剪。常用取值 -1.0,用于防止数据异常产生的低于 −100% 的收益。留空则不裁剪。
分桶¶
n_buckets¶
int,默认 10,必须 ≥ 2。
在每个 (调仓日, 信号) 截面内按 alpha 等频分桶,桶标签为 "0" 至 "n-1" 的字符串,"0" 为 alpha 最低的一组。小于 2 时抛 ContractError。
min_names¶
int,默认 20。
单个截面内 alpha 非空的样本数低于该值时,整个截面作废,不产出任何桶。分位数因取值重复而退化时同样整日作废。
所有调仓日均未通过分桶时抛 ContractError,并在消息中回显当前的 min_names 与 n_buckets。
long_short¶
分位桶两端相减得到的多空组合。
| 子字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled |
bool |
true |
关闭后结果表中不含多空 |
label |
str |
"H-L" |
多空在 bucket 列中的取值 |
reverse |
bool |
false |
false 为高桶减低桶,true 为低桶减高桶 |
多空按 (日, 信号, 加权方案) 对齐相减,count 列取两端成分数之和。任一端缺失时该期多空为空。
加权¶
weights¶
list[str],默认 ["ew", "vw"]。
加权方案名,取自 WEIGHTERS 注册表。内置两项:
| 名称 | 结果表标签 | 行为 |
|---|---|---|
ew |
EW |
等权。桶内成分数为 0 时该桶收益记 NaN |
vw |
VW |
市值加权。缺市值或市值非正的成分权重记 0,等价于剔除后重新归一;市值合计不为正时该桶收益记 NaN |
市值非正的成分为何裁到 0
不裁负值会让市值合计变小甚至跨零,权重随之出现杠杆与反向暴露。
列表为空时抛 ContractError。两个方案产出相同标签时同样抛 ContractError——结果表以标签区分加权方案,重复会导致行无法分辨。
自定义加权器注册后即可在此按名引用。
weight_options¶
dict,默认 {}。
按方案名索引,值作为关键字参数传入对应加权器的构造函数,在加权器内部通过 self.options 访问。内置的 ew 与 vw 不读取任何选项。
基准¶
include_references¶
bool,默认 true。
是否把 input.references 中声明的基准折算后纳入结果表。基准行的 bucket 固定为 "REF",signal_model 取基准名,count 记 0,并对每个加权方案各复制一行,便于下游统一过滤。
一条 references 会覆盖全部加权方案
逐加权方案复制意味着一条基准会同时出现在 EW 与 VW 两套结果中。等权与市值加权各配一条指数时,需在图表层按 weights 与 signals 分别限定,完整配置见日频例子。
reference_lag¶
int,默认 1。
日频基准的复利窗口相对锚点的偏移。
窗口取 [锚点 + lag, 锚点 + lag + horizon),即 horizon 个交易日:
1:次日起算,与持仓建立的时点对齐0:当日起算
窗口内数据不足 horizon 天的锚点被跳过。
窗口两端都落在自然月末:起点取锚点所在月的月末,末端取其后第 horizon 个月的月末。
1:(起点, 末端],月末次日起算0:[起点, 末端),含月末当日
组合那一期测的是「锚点所在月月末收盘 → 目标月月末收盘」,基准必须测同一段才可比。逐日推算会在两端各错一截:锚点落在月内(信号在月中落盘)时,从锚点当天起算会把当月剩下大半个月多算进来,一期变成近两个月;锚点落在月内最后一个交易日(如 3 月 29 日)时,逐日加一个月得到 4 月 29 日,又会把 4 月最后一两天漏在窗口外。锚点本就是自然月末时,两种算法一致。
该口径下只区分「含月末当日」与「次日起算」两种情形,取值须为 0 或 1;其他取值抛 ContractError,不会被悄悄当成 1 处理。基准数据未覆盖到窗口末端的锚点被跳过——月末恰为周末时,末期基准会因此缺一期。
窗口长度取 horizon 而非 holding_days,因为基准与组合必须测同一个窗口才可比。
references[].frequency 为 period 的基准已是周期收益,直接按调仓日历对齐,不受该字段影响。
holding_days¶
int | null,默认 null,留空即继承 forward_return.horizon。单位与 horizon 相同,随 input.frequency 变(日度=交易日,月度=自然月)——名字里的 days 是历史沿用,月度口径下它数的是月。
仅用于年化折算:每年期数按 年化基数 / holding_days 计,基数在日度口径下取 analyzer.trading_days_per_year(默认 252),月度口径下取 12。它不改变任何一期实现收益的测量方式——那由 horizon 单独决定。
显式给出且与 horizon 不等时发出 HoldingPeriodWarning,可算但不中断:
engine.holding_days=10 与 engine.forward_return.horizon=5 不一致:
每期实现收益按 5 期测量(基准同窗口口径),
而年化因子按每年「年化基数/10」期折算。
两者不等意味着组合收益的测量期与声称的持有期不是同一件事,年化收益、波动与夏普会相应偏移。
告警一览¶
引擎在这些情形下发出告警而不中断执行:
| 告警类型 | 触发条件 | 含义 |
|---|---|---|
HoldingPeriodWarning |
holding_days ≠ horizon |
年化口径与测量期不是同一件事 |
HoldingPeriodWarning |
调仓间隔 ≠ horizon |
相邻两期持有窗口重叠或存在空仓缺口 |
IgnoredForwardReturnWarning |
source="prices" 且信号带 fwd_ret |
自带列已丢弃,实际取价格口径 |
DegeneratePriceWarning |
close 逐资产恒定 |
前视收益恒为 0,通常是占位列被当成真实价格 |
调仓间隔与测量期不等会让净值失真
调仓间隔小于 horizon 时窗口互相重叠,逐期累乘会把同一段行情重复计入;大于 horizon 时期间存在空仓缺口。两种情况下 total_equity 与 max_drawdown 均不可信。
通常应让 input.calendar.rebalance_freq 与 horizon 相等。该组合只告警不拦截,因为重叠窗口下的信息系数仍有分析价值。
完整示例¶
{
"n_buckets": 10,
"min_names": 20,
"weights": ["ew", "vw"],
"weight_options": {},
"include_references": true,
"reference_lag": 1,
"long_short": {
"enabled": true,
"label": "H-L",
"reverse": false
},
"forward_return": {
"horizon": 5,
"source": "prices",
"clip_lower": null
}
}
产出¶
引擎的唯一出口是 EngineResult:
| 字段 | 列 | 内容 |
|---|---|---|
returns |
date、signal_model、bucket、weight、ret、count |
逐期组合收益长表 |
members |
date、signal_model、id、bucket |
每期各桶的成分明细 |
aligned |
date、id、signal_model、alpha、fwd_ret、cap |
分桶前的对齐面板 |
meta |
— | 桶数、加权标签、期数、多空两端标签等运行摘要 |