Python API¶
backtest() 是面向交互式分析的入口:扁平关键字参数进,可继续提问的结果对象出。它不含任何计算——
参数组装成四份配置后仍由 InputProcessor → PortfolioEngine → Analyzer 驱动,因此与
配置目录路径算出的结果逐值一致。
数据参数¶
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
signals |
DataFrame / 路径 / {名称: 表} / SignalSpec |
必填 | alpha 信号。映射形式一次跑多路,键即信号名 |
prices |
DataFrame / 路径 / PriceSpec |
必填 | 价格面板,提供 close、cap 与交易日历 |
references |
同上 | None |
外部基准序列,以 REF 桶进入结果 |
signal_columns |
dict | None |
源列名 → 契约列名的映射 |
price_columns |
dict | None |
同上,作用于价格面板 |
reference_columns |
dict | None |
同上,作用于基准序列 |
内存 DataFrame 与文件路径等价,可混用。文件格式由后缀推断,支持 feather、parquet、csv。
列名自动识别¶
*_columns 留空时,按契约列名(date / id / alpha / close / cap / fwd_ret / ret)
在源表中同名匹配。源列名与之一致的表因此不必声明映射。
自动识别只在整个映射留空时生效。一旦声明了映射就进入显式模式,此时遗漏某列是有意义的表达——
价格面板不映射 close 正是「本面板无可用价格序列,只供市值与交易日历」的声明方式。
对不上的列会报错并列出源表实际列名,不会静默产出空表:
ContractError: frame<alpha1>: 缺少必需列 ['date', 'id'];源列为 ['anchor', 'sym', 'alpha']。
源列名与之不同时请用 column_map 声明映射。
口径参数¶
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
horizon |
int | 必填 | 每期实现收益的测量期长度,单位随 frequency |
frequency |
str | "daily" |
面板频率,取 "daily" 或 "monthly" |
rebalance_freq |
int | 取 horizon |
调仓间隔,单位随 frequency |
n_buckets |
int | 10 |
分位桶数 |
min_names |
int | 20 |
单期通过分桶所需的最少标的数 |
weights |
str / list | 按数据决定 | 加权方案,取 "ew" / "vw" |
long_short |
bool | True |
是否构建多空 |
long_short_reverse |
bool | False |
反向,即低分位减高分位 |
holding_days |
int | 取 horizon |
年化折算用的持有期数,单位随 frequency |
forward_return_source |
str | 按数据决定 | "prices" 或 "signals" |
clip_lower |
float | None |
前视收益下限截断 |
first_rebalance |
str | None |
首个调仓日,留空取信号起点 |
last_rebalance |
str | None |
末个调仓日 |
calendar_source |
str | "signals" |
调仓日历取自信号日期还是交易日 |
auto_stride |
bool | True |
信号日历原生已够稀疏时不再二次抽稀 |
reference_frequency |
str | "daily" |
基准序列频率,daily 按持有期复利 |
reference_lag |
int | 1 |
日频基准复利起点相对锚点的偏移 |
frequency 决定所有期数的单位¶
horizon、rebalance_freq、holding_days 数的是期,一期有多长由 frequency 决定:"daily" 下是一个交易日,"monthly" 下是一个自然月。年化基数随之取 252 或 12。
月频面板沿用默认的 "daily" 会让年化指标偏离 21 倍且不触发告警,完整写法见月频例子。
三处按数据推导的默认值¶
rebalance_freq 缺省等于 horizon。 两者不等时,相邻两期的持有窗口会重叠或留下空仓缺口,
逐期累乘出的净值、total_equity 与 max_drawdown 因此失真。缺省取同值使窗口首尾相接。
显式给出不等值仍会照常告警,自动缺省不掩盖有意为之的口径差异。
weights 按价格面板是否提供 cap 决定。 有市值列取 ["ew", "vw"],没有则取 ["ew"]。
无市值数据时仍排入市值加权会产出整列 NaN 而不报错。
forward_return_source 按信号是否自带实现收益决定。 信号提供 fwd_ret 时取 "signals",
否则由 close 推算取 "prices"。
自带列优先于 close,是因为两者的明确程度不对等:fwd_ret 是专门算过一遍实现收益的结果,
常含分红与拆股调整;而 close 往往只是价格面板顺带提供的——该面板本来就要承担交易日历与
市值关联两项职责,有没有 close 说明不了口径意图。两种口径在含分红的数据上能差出数量级。
需要强制走价格口径时显式给出 forward_return_source="prices",此时自带列被丢弃并发出
IgnoredForwardReturnWarning。
分析参数¶
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
metrics |
list | 六项基础指标 | 指标清单,可选项见数学口径 |
periods_per_year |
float | 由持有期推导 | 年化因子,显式给出则优先 |
trading_days_per_year |
float | 252.0 |
日度口径下年化因子的分子;月度口径固定取 12 |
risk_free_rate |
float | 0.0 |
无风险利率 |
turnover |
bool | True |
是否计算换手 |
ic |
bool | True |
是否计算信息系数 |
ic_min_names |
int | 20 |
单期计算 IC 所需的最少标的数 |
vol_rescale_to |
str | None |
缩放到某个基准的波动水平 |
curve_clip_lower |
float | -0.99 |
曲线单期收益下限 |
shared_origin |
bool | True |
各曲线是否统一起点 |
output_dir |
str | None |
save() 的默认落点 |
结果对象¶
backtest() 返回 BacktestResult。中间产物原样保留,二次分析不需要重跑。
中间产物¶
| 属性 | 内容 |
|---|---|
returns |
逐期组合收益长表 [date, signal_model, bucket, weight, ret, count] |
curves |
净值与累计对数收益曲线 |
ic |
逐期信息系数 |
turnover |
逐期换手 |
members |
逐期分桶成分明细 |
aligned |
alpha × 前视收益 × 市值的对齐面板 |
bundle / engine / analysis |
三份原始契约产物 |
config |
本次实验的完整 PipelineConfig |
summary(bucket=None, weight=None, signal=None)¶
指标汇总表。不给参数即全部分位。
plot(kind="long_short", weight=None, signal=None, **overrides)¶
按预设名出一张图,返回 matplotlib Figure。
| 预设名 | 内容 |
|---|---|
"long_short" |
多空与外部基准同图对比,带零线 |
"deciles" |
分位走色阶、多空单独强调;桶列表跟随 n_buckets |
weight 留空取首个加权方案。多路信号画分位图时用 signal 指定看哪一路,留空取首路。
其余关键字直接透传给 ChartSpec,用于单图临时调整:
Jupyter 中需要 inline 后端
返回的 Figure 脱离 pyplot 全局状态直接构造,因此 notebook 里要先激活 inline 后端才会渲染:
否则单元格不显示任何内容。脚本中用 fig.savefig(...) 不受影响。
fig = bt.plot("long_short", show_title=False, legend_loc="lower right")
fig.axes[0].set_ylabel("Cumulative log return") # 返回的是原生 Figure,可继续调
fig.savefig("fig3.pdf", dpi=300)
save(output_dir=None, charts=..., tables=..., export_returns=True)¶
图 PNG 与数据表 CSV 落盘,返回文件清单。charts 默认两张预设图,tables 默认
summary / turnover / ic 三张表。
内存 DataFrame 输入时没有数据文件可作落点锚,因此必须给出目录——不猜测落点,也不写进当前工作目录。
to_config(config_dir, data_dir=None)¶
把本次实验导成四份 JSON,供 run_pipeline 复现或随论文归档。
内存 DataFrame 无法写进 JSON,因此内存输入时需给出 data_dir:表会先落盘到该目录,
再把路径写进配置。
bt = alp.backtest(signals=alpha_df, prices=price_df, horizon=5, n_buckets=10)
bt.to_config("paper/configs/", data_dir="paper/data/")
# 复现时
result = alp.run_pipeline("paper/configs/")
全局样式¶
样式字段有五十余个,改动频率远低于口径参数,因此集中在 settings 而非函数签名。
改一次对之后的每次渲染生效:
alp.settings.style.figsize = (10, 6)
alp.settings.style.dpi = 300
alp.settings.style.palette = {"alpha1": "#1f77b4"}
alp.settings.style.gradient_colormap = "plasma"
alp.settings.reset() # 复原到出厂默认
逐字段说明见 visualizer.json 的 style 段。配置中显式写了 style 时以配置为准,
不受全局影响。
更细的控制¶
预设覆盖不到的场景,四个模块仍可单独驱动,配置用 Python 直接构造:
from alpholio import InputProcessor, PortfolioEngine
from alpholio.config_schema import (
InputConfig, PriceSpec, SignalSpec, EngineConfig, ForwardReturnSpec,
)
cfg = InputConfig(
prices=PriceSpec(frame=price_df),
signals=[SignalSpec(name="alpha1", frame=alpha_df)],
)
bundle = InputProcessor(cfg).run()
engine = PortfolioEngine(
EngineConfig(n_buckets=10, forward_return=ForwardReturnSpec(horizon=5))
).run(bundle)
SignalSpec / PriceSpec / ReferenceSpec 的 path 与 frame 恰好给一个:两个都给无从判断
以哪个为准,都不给则没有数据来源。