# 使用 HistoryPanel 操作和分析历史数据 在完成前两章「入门」和「获取并管理金融数据」之后,我们已经可以: - 在本地配置好数据源并下载所需的历史数据; - 使用 `get_history_data()` 或 `get_history_panel()` 获取价格、成交量等基础行情。 本章将在此基础上,通过一个完整的小例子,演示如何使用 `HistoryPanel`: - 统一管理多标的、多指标的历史数据; - 计算收益率、波动率等简单因子; - 叠加 K 线技术指标; - 识别部分蜡烛形态; - 使用 ``where`` 生成与数据同形的 **bool 掩码**,为后续带 ``mask=`` 的研究接口做准备; - 使用 **列属性**(如 ``hp.close``)、**比较运算**(返回 ``numpy`` 布尔数组,可与 ``where`` 衔接)以及 **``hp.loc``**(仅沿时间轴筛选,等价 ``hp[:, :, key]``)。 > 更完整的 API 说明可参考 [HistoryPanel API 参考](../api/HistoryPanel.rst)(含「列属性访问、比较与 ``loc``」与 ``where`` 专节)。 --- ## 1. 准备数据:获取一个 HistoryPanel 假设我们已经下载了沪深 300 指数(`000300.SH`)近一年的日线数据,可以直接通过 `qt.get_kline()` 或 `qt.get_history_data()` 获取一个 `HistoryPanel`: ```python import qteasy as qt # 获取近一年日线 OHLCV,并直接返回 HistoryPanel hp = qt.get_kline( shares='000300.SH', start='20230101', end='20231231', freq='D', as_panel=True, # 关键:返回 HistoryPanel ) print('HistoryPanel 结构:', hp.shape) print('shares:', hp.shares) print('htypes:', hp.htypes) ``` 此时: - `hp.shape` 是一个 `(L, R, C)` 的三元组,分别对应 **股票数 × 时间长度 × 数据类型数**; - `hp.shares` 通常是 `['000300.SH']`; - `hp.htypes` 默认包含 `['open', 'high', 'low', 'close', 'vol']` 等。 --- ## 2. 计算收益率与波动率因子 `HistoryPanel` 内置了针对价格序列的收益与波动率计算接口: ```python # 2.1 计算简单收益率(simple return) ret = hp.returns( price_htype='close', method='simple', # 或 'log' periods=1, as_panel=False, # 返回 DataFrame: index=时间, columns=shares ) print('日收益率:\n', ret.tail()) # 2.2 在收益率基础上计算 20 日滚动波动率 vol = hp.volatility( window=20, price_htype='close', method='simple', annualize=True, # 年化波动率 as_panel=False, # 同样返回 DataFrame ) print('20 日年化波动率:\n', vol.tail()) ``` 典型用法是: - 将 `ret` 或 `vol` 当作因子值矩阵(时间 × 股票); - 直接送入回测引擎,或用于选股、打分等。 --- ## 3. 叠加 K 线技术指标 当 `HistoryPanel` 中包含 OHLC 数据时,可以通过 `hp.kline` 访问一批常用技术指标: ```python # 基于 close 价格生成 20 日简单移动平均线 hp_ma = hp.kline.sma(window=20, price_htype='close') print('新增 htypes:', hp_ma.htypes) # 再叠加 MACD 指标 hp_ma_macd = hp_ma.kline.macd(price_htype='close') print('叠加 MACD 后的 htypes:', hp_ma_macd.htypes) # 将单只股票切成 DataFrame,方便在 pandas / sklearn 中继续处理 df_300 = hp_ma_macd.to_share_frame('000300.SH') print(df_300.tail()) ``` 这里: - `hp.kline.sma(...)` 会在 `htypes` 中追加一列,例如 `sma_20`; - `hp.kline.macd(...)` 会追加 `macd_xxx`、`macd_signal_xxx`、`macd_hist_xxx` 三列; - 最终通过 `to_share_frame(share)` 得到标准的二维 `DataFrame`,可直接与其他数据源联立分析。 ### 3.1 inplace:原地扩列(减少拷贝) 默认情况下,`kline.*` 返回 **新** `HistoryPanel`(不污染原对象)。当你希望在同一个面板上连续追加多列,并减少中间对象时,可以使用 `inplace=True`: ```python # 在原面板上原地追加两列,并返回原面板(支持链式) hp2 = hp.kline.sma(window=20, inplace=True).kline.macd(inplace=True) assert hp2 is hp print('原地扩列后的 htypes:', hp.htypes) ``` > 提示:当新列名与已有列名冲突时,`kline.*` 会抛出英文 `ValueError`,避免静默覆盖。 ### 3.2 第一入口:research_preset(一行拼列 + 直接出图) 如果你希望“拿到数据后立刻得到一张像样的图”,推荐直接使用 `research_preset` 生成官方预设的常用列集合(预设只做显式追加,不在 `plot()` 内隐藏计算): ```python # 生成 OHLCV + MACD + SMA20(预设名以 API 文档为准) hp_preset = hp.research_preset('ohlcv_macd_ma', inplace=False) print('preset htypes tail:', hp_preset.htypes[-8:]) # 多标的时可分页展示(不再静默只画前 5 只) fig = hp_preset.plot(shares=hp_preset.shares, max_shares_per_figure=3, page=1) ``` 当输入面板缺少预设所需的输入列时,会抛出英文 `ValueError`,信息包含缺失列列表与补齐建议。 --- ## 4. 使用 assign 一次派生多列 在实际研究中,经常需要基于已有列派生出多个新因子,例如价差、比例、缩放后的归一化值等。``HistoryPanel.assign()`` 提供了一次性派生多列的 DSL: ```python # 假设 hp 已含 'close' 和 'open' 列 hp2 = hp.assign( spread=lambda p: p['close'].values[:, :, 0] - p['open'].values[:, :, 0], up_ratio=lambda p: p['close'].values[:, :, 0] / np.maximum(p['open'].values[:, :, 0], 1e-6), ) print('新列:', hp2.htypes[-2:]) ``` - `assign` 的关键字参数名称即新列名(htype),值可以是接收当前 `HistoryPanel` 的可调用对象,也可以是可广播到 `(M,L)` 的数组或标量; - 同一次调用内,后面的列可以依赖前面刚新增的列,例如: ```python hp3 = hp.assign( a=lambda p: p['close'].values[:, :, 0] + 1.0, b=lambda p: p['a'].values[:, :, 0] * 2.0, ) ``` 默认 `inplace=False`,返回新面板,不修改原对象;当你希望在原面板上原地扩列时,可以使用: ```python hp.assign(inplace=True, factor=lambda p: p['close'].values[:, :, 0] / 100.0) ``` > 提示:与 `hp['col'] = ...` 一样,`assign` 对已有列名采用“覆盖”语义;空面板上调用 `assign` 会抛出英文 `ValueError`。 --- ## 5. 横截面 rank 与 zscore(cs/ts) 多标的研究中常见的两类标准化需求是: - **截面(cross-sectional)**:固定某个交易日,对所有股票的因子值做标准化或排名; - **时序(time-series rolling)**:固定某只股票,对它自己的历史序列用滚动窗口做标准化。 `HistoryPanel.rank()` 与 `HistoryPanel.zscore()` 提供了这两类最基础变换(仅做变换,不做回归推断): ```python # 5.1 截面排名:逐日对 share 维做排名 hp_rank = hp.rank(by='close') # 追加一列 'rank_close' # 5.2 截面 zscore:逐日横截面标准化(method='cs') hp_cs = hp.zscore(by='close', method='cs') # 追加一列 'cs_z_close' # 5.3 时序 zscore:逐股滚动标准化(method='ts',需要 window) hp_ts = hp.zscore(by='close', method='ts', window=20) # 追加一列 'ts_z_close_20' ``` > 提示:`zscore(method='cs')` 与 `zscore(method='ts')` 语义完全不同,务必显式指定 `method`(并在 `ts` 场景提供 `window`),避免把“截面标准化”误当成“时序标准化”。 ### 5.5 对齐与重采样(align_to / resample):避免 silent 错行 当你手里有两块 `HistoryPanel`,想做逐元素运算(比如 `hp_factor / hp_price`、或两面板相减)时,需要特别注意: - `HistoryPanel` **不会**在运算符里自动对齐两块面板; - 若 `shares` / `hdates` 不一致,直接拿 `.values` 做 NumPy 运算很容易出现“看起来能算、但其实错行”的结果。 因此建议遵循固定流程:**先对齐,再计算**。 ```python # hp_a 与 hp_b 的 shares/hdates 不一定一致 a1, b1 = hp_a.align_to(hp_b, join='inner') # 交集对齐:只保留共同 shares 与共同日期 ratio = a1.values / b1.values # 此时逐元素运算不会错行 print('aligned shape:', a1.shape, b1.shape) ``` =当你需要把日线变成周线/月线等更低频数据时,可用 `resample()`,并**显式**指定每一列的聚合规则(避免语义不明): ```python # 典型 OHLCV 周线:open=first, high=max, low=min, close=last, vol=sum hp_w = hp.resample('W', agg={ 'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last', 'vol': 'sum', }) print('weekly dates:', hp_w.hdates[:3], '...') ``` > 提示:`resample()` 要求 `agg` 覆盖全部 `htypes`。对于非 OHLCV 的自定义因子列,你需要自己决定 `'last'` / `'mean'` 等聚合方式并写进 `agg`。 ### 5.6 用 (M,L) mask 做 plot 高亮(事件日/信号日) 在因子研究里,你经常会得到一个二维布尔矩阵 `mask_ml`,表示“每只股票在哪些日期满足条件”(例如事件日、信号日、可交易池)。 `HistoryPanel.plot()` 的 `highlight={'condition': mask_ml}` 支持直接把它映射到图形中高亮点: - 若 `mask_ml.shape == (M_plot, L)`,其中 `M_plot` 为当前 `plot(shares=...)` 选中的股票数,则按绘图 share 顺序对应; - 若 `mask_ml.shape == (M_all, L)`,其中 `M_all == len(hp.shares)`,则按 **share 名**抽取当前绘图子集(避免 silent 错行); - 当 `layout='overlay'` 时默认 **primary-only** 显示高亮(与既有 Plotly highlight 语义一致)。 ```python import numpy as np M, L, _ = hp.shape mask_ml = np.zeros((M, L), dtype=bool) mask_ml[:, -1] = True # 举例:每只股票的最后一天高亮 fig = hp.plot( shares=hp.shares, layout='stack', highlight={'condition': mask_ml}, ) ``` ### 5.4 Newey–West / Fama–MacBeth(背景:本节不实现) 在计量实证中,你可能会看到 **Newey–West(HAC)稳健标准误** 与 **Fama–MacBeth 两步回归**。它们主要用于**回归推断**(标准误修正、风险溢价显著性检验),不属于 `HistoryPanel.zscore()` 这类“基础变换”。 若需要发表级实证,建议将 `HistoryPanel` 导出为 `DataFrame` 后使用 `statsmodels` 等工具完成推断流程。 --- ## 6. 使用 apply_ta 统一调用技术指标 如果你已经熟悉 `qteasy.tafuncs` 或 ta-lib,可以通过 `HistoryPanel.apply_ta()` 做统一封装: ```python # 在所有股票上应用 'sma' 指标,对 close 做 10 日均线 hp_sma10 = hp.apply_ta( func_name='sma', htype='close', timeperiod=10, as_panel=True, # 返回新的 HistoryPanel,追加一列 'sma' ) print('htypes with sma:', hp_sma10.htypes) ``` 这样可以避免在循环中手动调用 `tafuncs.sma`,`HistoryPanel` 会自动对所有选定股票广播计算。 --- ## 7. 识别蜡烛形态 对于 OHLC 数据,还可以通过 `HistoryPanel.candle_pattern()` 封装 ta-lib 的蜡烛形态识别函数,例如: ```python # 识别锤头线形态(CDLHAMMER),返回时间 × 股票的整数信号矩阵 hammer = hp.candle_pattern( name='cdlhammer', as_panel=False, # 返回 DataFrame ) print('锤头线信号(非 0 代表出现形态):\n', hammer[hammer != 0].dropna(how='all')) ``` 信号含义通常是: - `0`:未识别到该形态; - 正数 / 负数:不同方向或强度的形态信号(取决于具体 ta-lib 函数的定义)。 --- ## 8. 生成研究用 bool 掩码(`where`) 面板 ``values`` 的轴顺序为 **(标的数 M, 时间长度 L, 指标列数 N)**,与 ``hp.shape`` 一致。``hp.where(condition)`` 返回 **同形** 的 ``numpy.ndarray``,``dtype=bool``,且不修改 ``hp``。``cum_return``、``normalize``、``portfolio`` 已支持 ``mask=hp.where(...)``(或与 ``where`` 同广播规则的 ``bool`` 数组)。研究向语义与边界(路径断开、基准日无效、组合与基准关系等)以 API docstring 为准。 **与整表数值比较**(与 ``hp.values > 阈值`` 等价): ```python import numpy as np mask = hp.where(hp.values > 0) # 或先写 cond = hp.values > 0 再 hp.where(cond) assert mask.shape == hp.shape print('True 格点数量:', int(mask.sum())) ``` **按时间维打标**(例如某一列索引为「事件日」,沿所有 htype 复制;以下为示意,需保证 ``cond_ml`` 形状为 ``(M, L)``): ```python M, L, N = hp.shape cond_ml = np.zeros((M, L), dtype=bool) cond_ml[:, -1] = True # 示例:最后一个交易日标记为 True mask_event = hp.where(cond_ml) assert mask_event[:, -1, :].all() and not mask_event[:, 0, :].any() ``` **按收盘价列构造条件**(``callable`` 接收当前 ``HistoryPanel``): ```python # 假设存在 'close' 列;多列面板可用 p.values[:, :, idx] 或子面板 mask_close = hp.where(lambda p: p['close'].values[:, :, 0] > 0) ``` **复合条件与缺失值**: ```python mask_band = hp.where(lambda p: (p.values > -1e9) & (p.values < 1e9)) mask_nan = hp.where(np.isnan(hp.values)) # 标记 NaN 所在格,供排除缺失 ``` **说明**:形状恰好为 ``(M, L)`` 的二维条件表示「每个 (标的, 日) 对所有指标列共用同一布尔值」,库内会先扩展为 ``(M, L, 1)`` 再广播到 ``(M, L, N)``。一维 ``(M,)`` 或 ``(M, 1)`` 表示仅随标的变化,会展开为 ``(M, 1, 1)`` 再广播。更完整的参数与异常说明见 [HistoryPanel API 参考](../api/HistoryPanel.rst) 中 ``where`` 一节。 ### 8.1 累计收益与归一化(`cum_return` / `normalize`) 返回 **新** ``HistoryPanel``(不修改原面板),默认对解析后的 ``close`` 列计算;输出列名为 ``cumret_<列名>``、``norm_<列名>``(与复权列 ``close|b`` 并存时,仍可传 ``htypes=None``,列名仍为 ``cumret_close``)。可与掩码联用: ```python m = hp.where(hp.close > 0) # 或与 hp.values 比较得到 (M,L,N) cr = hp.cum_return(mask=m, method='simple') nm = hp.normalize(base_index=0, mask=m) ``` ### 8.2 组合聚合(`portfolio`) 研究向:沿 **标的维** 对指定列做等权或加权平均,返回 **新** ``HistoryPanel``(``hdates`` 不变)。``mask=`` 与 ``where`` 同广播规则。无 ``groups`` 且指定 ``benchmark`` 时,**基准标的不参与**组合计算,仅用于 ``benchmark_output='tag_along'``(多一行基准曲线)或 ``'excess_only'``(输出 ``excess_<列名>``)。非账户回测、无交易成本。详见 API docstring。 ```python ew = hp.portfolio(mode='equal', benchmark_output='none') m = hp.where(hp.close > 0) ew_m = hp.portfolio(mode='equal', mask=m, benchmark_output='none') # 含基准指数代码 'IDX' 时: # tagged = hp.portfolio(mode='equal', benchmark='IDX', benchmark_output='tag_along', new_share_name='EW') ``` ### 8.3 列属性、比较与 ``loc``(与 pandas 的差异简述) - **列属性(只读)**:列名为合法 Python 标识符时可用 ``hp.close``,等价 ``hp['close']``;含 ``|`` 的复权列名等须用方括号。赋值请统一 ``hp['col'] = ...``。 - **比较**:``hp.close > hp.open``、``hp > 0`` 等得到 **``numpy.ndarray``(bool)**,不是子 ``HistoryPanel``;全形掩码可写 ``hp.where(hp.close > 15)``。两侧均为面板时须 ``shares``、``hdates`` 一致;两列比较时多为两个 **单列** 子面板。 - **``loc``**:``hp.loc[s]`` 等价 ``hp[:, :, s]``,只沿 **时间(hdates)** 轴筛选;**不要**把 ``where`` 得到的 ``(M, L, N)`` 掩码传给 ``loc``,格点条件仍用 ``where`` + 后续 ``mask=``。 细节与边界情况以 [HistoryPanel API 参考](../api/HistoryPanel.rst) 为准(含 ``where``、``cum_return``、``normalize``、``portfolio``)。 ```python # 示意(在已含 open/close 列的 hp 上) # m = hp.where(hp.close > hp.open) # 比较 → bool ndarray → where 广播到 (M,L,N) # tail = hp.loc[-5:] # 仅时间轴,等价 hp[:, :, -5:] ``` --- ## 9. 从 HistoryPanel 研究到 Strategy / Operator:迁移路径 `HistoryPanel` 上的 `cum_return`、`portfolio`、`plot` 等是**研究向**工具:便于肉眼筛因子、看粗组合线,**不包含**交割周期、手续费、滑点、信号类型(PT/PS/VS)解析等与正式回测一致的账户语义。结论若用于实盘或严肃评价,应把**同一经济含义**的规则写进策略的 `realize()`,交给 `Operator` + Backtester。 下面用**同一思想——20 期简单动量**串起两阶段(需本地已配置数据源并下载相应字段;股票代码仅为示意)。 ### 9.1 研究阶段:在多标的 `HistoryPanel` 上算因子并粗看截面 ```python import numpy as np import qteasy as qt # 多标的日线收盘价(htype 以你本地数据源为准,可为 close 或复权列如 close|b) hp = qt.get_history_data( htypes='close', shares=['000001.SZ', '000002.SZ', '600000.SH'], # 示例股票池 start='20200101', end='20231231', freq='d', ) # 20 期简单收益 r_t = P_t / P_{t-20} - 1,直接得到带标签的子面板 hp_mom = hp.returns(price_htype='close', periods=20, method='simple', as_panel=True) # 截面排名:逐日看多标的谁强谁弱(研究向) hp_rank = hp_mom.rank(by='ret_close') # 可选:等权「组合」在收益列上的逐日平均,仅用于目视,非账户回测 hp_ew = hp_mom.portfolio(htypes='ret_close', mode='equal', benchmark_output='none') hp_mom.plot(shares=hp_mom.shares, layout='stack') # 结合前几节 mask 高亮更佳 ``` 这里你在 **三维面板** 上完成了因子列生产、截面变换与粗聚合;若结果满意,进入 9.2 把**因子定义**搬到策略里。 ### 9.2 正式回测阶段:`FactorSorter` + `Operator` 选股类策略常用 `FactorSorter`:`realize()` 返回**每个标的一个标量因子**(长度 = 股票数),框架再按 `max_sel_count`、`sort_ascending` 等做排序与权重。下面动量与 9.1 中 `returns(..., periods=20)` 的经济含义一致,但数据来自策略窗口注入(与回测步对齐、受 `use_latest_data_cycle` 等约束)。 ```python import numpy as np import qteasy as qt from qteasy import StgData class Mom20Factor(qt.FactorSorter): """20 期收盘动量:close[-1]/close[0]-1,与 HP 上 returns(periods=20) 语义对齐。""" def __init__(self) -> None: super().__init__( name='Mom20', description='20-bar simple momentum on daily close', data_types=[StgData('close', freq='d', asset_type='E')], window_length=21, max_sel_count=3, sort_ascending=False, condition='any', use_latest_data_cycle=True, ) def realize(self): close_w = self.get_data('close_E_d') # 形状约为 (window_length, 股票数) out = close_w[-1] / close_w[0] - 1.0 out = np.where(np.isfinite(out), out, np.nan) return out # 以下为骨架:具体 run 参数(股票池、起止日、费率、signal_type 等)见「回测」与 Operator 文档 op = qt.Operator() op.add_strategy(Mom20Factor(), run_freq='d', run_timing='close') # res = qt.run(op, mode=1, ...) # 按项目文档填写 shares、起止日期与回测配置 ``` **注意**: - `Operator` 默认组内可使用 PT 类目标仓位信号;`FactorSorter` 与 PT 的搭配以你项目中的策略文档为准。 - HP 里用全样本算的 rank/zscore,与回测中「每步仅见历史窗口」的因子可能略有数值差异,属预期现象;应以策略内 `get_data` 为准复核防前视。 完整可运行脚本见项目根目录 ``examples/historypanel_research_to_strategy.py``。 --- ## 10. 多源数据拼成 `HistoryPanel`(价格 + 基本面示例) `HistoryPanel` 可以承载任意 `htypes` 列;**多表/多 DataType** 的常见做法是:(1) 一次 `get_history_data` 拉多列;(2) 或分别取两块面板再 `align_to`。 ### 10.1 单次拉取多列(频率与资产类型需一致) 若本地已有日线 `close` 与 `pb`(市净率)等字段,可直接: ```python import numpy as np import qteasy as qt hp = qt.get_history_data( htypes='close, pb', shares=['000001.SZ', '000002.SZ'], start='20200101', end='20231231', freq='d', ) # 估值倒数近似账面市值比(仅作演示,非投资建议) hp2 = hp.assign(bp=lambda p: 1.0 / np.maximum(p['pb'].values[:, :, 0], 1e-8)) # 横截面 zscore:逐日可比 hp_z = hp2.zscore(by='bp', method='cs') ``` 缺列或表未下载时,`get_history_data` 会报错;请先通过数据源教程补齐表数据。 ### 10.2 两块面板对齐后再派生(不同来源/不同拉取) ``align_to`` 要求两侧 **``htypes`` 名称与顺序完全一致**。若你只分别拉取了 ``close`` 与 ``pb`` 两列,需先扩成同构列(缺失侧用 ``NaN`` 占位),对齐后再用 ``assign`` 把有效数值写回同一套列名: ```python import numpy as np import qteasy as qt hp_px = qt.get_history_data(htypes='close', shares=pool, start='20200101', end='20231231', freq='d') hp_pb = qt.get_history_data(htypes='pb', shares=pool, start='20200101', end='20231231', freq='d') c = hp_px['close'].values[:, :, 0] p = hp_pb['pb'].values[:, :, 0] nan_c = np.full_like(c, np.nan) nan_p = np.full_like(p, np.nan) hp_a = qt.HistoryPanel( np.stack([c, nan_p], axis=2), levels=hp_px.shares, rows=hp_px.hdates, columns=['close', 'pb'], ) hp_b = qt.HistoryPanel( np.stack([nan_c, p], axis=2), levels=hp_pb.shares, rows=hp_pb.hdates, columns=['close', 'pb'], ) a, b = hp_a.align_to(hp_b, join='inner') hp_joined = a.assign( close=lambda x: a['close'].values[:, :, 0], pb=lambda x: b['pb'].values[:, :, 0], ) ``` 更多字段(行业、市值等)同理;若可一次 ``get_history_data(htypes='close, pb', ...)``,优先用 **§10.1**,少一步手工同构。核心原则仍是 **先对齐再运算**,避免 NumPy 静默错行。 可运行合成数据演示见项目根目录 ``examples/historypanel_multisource_research.py``。 --- ## 11. 导出到 pandas / statsmodels(宽表、长表与截面回归) `HistoryPanel` 不内置 Newey–West、Fama–MacBeth 等推断流程;需要发表级回归时,应**导出为二维表**后用 `statsmodels` 等库。 ### 11.1 按标的切成多个 `DataFrame`(宽表) ```python by_share = hp.to_df_dict(by='share') df_000001 = by_share['000001.SZ'] # index 为时间,columns 为 htypes ``` ### 11.2 手工堆成长表(便于按日截面分组) ```python import pandas as pd records = [] M, L, N = hp.shape ci = hp.htypes.index('close') fi = hp.htypes.index('factor') # 假设已存在名为 factor 的列 for mi, sh in enumerate(hp.shares): for li, dt in enumerate(hp.hdates): records.append({ 'date': dt, 'share': sh, 'close': hp.values[mi, li, ci], 'factor': hp.values[mi, li, fi], }) df_long = pd.DataFrame(records) ``` ### 11.3 单日截面 OLS 示例(需已安装 statsmodels) ```python # pip install statsmodels import statsmodels.api as sm sub = df_long[df_long['date'] == df_long['date'].iloc[-100]].dropna() y = sub['close'] / sub['close'].mean() - 1.0 # 示例因变量,实际常用前瞻收益 X = sm.add_constant(sub['factor']) model = sm.OLS(y, X, missing='drop').fit() print(model.summary()) ``` 上述因变量仅为演示;实务中应将 **因子与前瞻收益对齐** 并处理停牌、幸存者偏差等——均在计量层完成,而非 `HistoryPanel` 核心职责。 合成数据示例见项目根目录 ``examples/historypanel_statsmodels_export.py``。 --- ## 12. 小结与下一步 通过本章示例,你可以看到: - `HistoryPanel` 提供了统一的三维历史数据容器,适合多标的 / 多指标联合研究; - 在其之上,可以直接调用统计方法(`describe/mean/std/min/max`)、滚动窗口(`rolling`)、收益/波动率(`returns`/`volatility`)、**横截面排名与标准化(`rank`/`zscore`)**、**累计收益与归一化(`cum_return`/`normalize`)**、**组合聚合(`portfolio`)**、K 线指标(`kline.*`)、技术指标桥接(`apply_ta`)、蜡烛形态识别(`candle_pattern`)、**研究掩码(`where`)** 以及 **列属性 / 比较 / `loc`** 等; - 任意一步都可以方便地切回 `DataFrame`,与 pandas / sklearn / statsmodels 等协同工作; - **正式回测**请通过 `Strategy.realize` + `Operator` 完成;研究向 HP 与回测引擎的边界与迁移路径见 **§9**。 下一步建议: - 在 Notebook 中基于你自己关注的股票池构造 `HistoryPanel`,尝试叠加多个因子(均线、波动率、估值等),并画出与价格对齐的图表; - 在阅读 [HistoryPanel API 参考](../api/HistoryPanel.rst) 的基础上,为你的研究题目设计一套「数据 → 因子(HP)→ 规则固化(Strategy)→ 回测(Operator)」的工作流; - 若考虑是否增加独立「因子评价」API 封装,可阅读设计说明 [HistoryPanel 与可选 FactorResearch 层](../design/10-historypanel-factor-research-layer.md)。